1 /*
2  * KDiff3 - Text Diff And Merge Tool
3  *
4  * SPDX-FileCopyrightText: 2002-2011 Joachim Eibl, joachim.eibl at gmx.de
5  * SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com
6  * SPDX-License-Identifier: GPL-2.0-or-later
7 */
8 
9 
10 #include "mergeresultwindow.h"
11 
12 #include "defmac.h"
13 #include "kdiff3.h"
14 #include "options.h"
15 #include "RLPainter.h"
16 #include "guiutils.h"
17 #include "Utils.h"             // for Utils
18 
19 #include <QAction>
20 #include <QApplication>
21 #include <QClipboard>
22 #include <QComboBox>
23 #include <QCursor>
24 #include <QDir>
25 #include <QDropEvent>
26 #include <QEvent>
27 #include <QFile>
28 #include <QFocusEvent>
29 #include <QHBoxLayout>
30 #include <QInputEvent>
31 #include <QKeyEvent>
32 #include <QLabel>
33 #include <QLineEdit>
34 #include <QtMath>
35 #include <QMouseEvent>
36 #include <QPaintEvent>
37 #include <QPainter>
38 #include <QPixmap>
39 #include <QPointer>
40 #include <QRegExp>
41 #include <QResizeEvent>
42 #include <QStatusBar>
43 #include <QTextCodec>
44 #include <QTextLayout>
45 #include <QTextStream>
46 #include <QTimerEvent>
47 #include <QUrl>
48 #include <QWheelEvent>
49 
50 #include <KActionCollection>
51 #include <KLocalizedString>
52 #include <KMessageBox>
53 #include <KToggleAction>
54 
55 QScrollBar* MergeResultWindow::mVScrollBar = nullptr;
56 QPointer<QAction> MergeResultWindow::chooseAEverywhere;
57 QPointer<QAction> MergeResultWindow::chooseBEverywhere;
58 QPointer<QAction> MergeResultWindow::chooseCEverywhere;
59 QPointer<QAction> MergeResultWindow::chooseAForUnsolvedConflicts;
60 QPointer<QAction> MergeResultWindow::chooseBForUnsolvedConflicts;
61 QPointer<QAction> MergeResultWindow::chooseCForUnsolvedConflicts;
62 QPointer<QAction> MergeResultWindow::chooseAForUnsolvedWhiteSpaceConflicts;
63 QPointer<QAction> MergeResultWindow::chooseBForUnsolvedWhiteSpaceConflicts;
64 QPointer<QAction> MergeResultWindow::chooseCForUnsolvedWhiteSpaceConflicts;
65 
MergeResultWindow(QWidget * pParent,const QSharedPointer<Options> & pOptions,QStatusBar * pStatusBar)66 MergeResultWindow::MergeResultWindow(
67     QWidget* pParent,
68     const QSharedPointer<Options>& pOptions,
69     QStatusBar* pStatusBar)
70     : QWidget(pParent)
71 {
72     setObjectName("MergeResultWindow");
73     setFocusPolicy(Qt::ClickFocus);
74 
75     mOverviewMode = e_OverviewMode::eOMNormal;
76 
77     m_pStatusBar = pStatusBar;
78     if(m_pStatusBar != nullptr)
79         chk_connect(m_pStatusBar, &QStatusBar::messageChanged, this, &MergeResultWindow::slotStatusMessageChanged);
80 
81     m_pOptions = pOptions;
82     setUpdatesEnabled(false);
83 
84     chk_connect(&m_cursorTimer, &QTimer::timeout, this, &MergeResultWindow::slotCursorUpdate);
85     m_cursorTimer.setSingleShot(true);
86     m_cursorTimer.start(500 /*ms*/);
87     m_selection.reset();
88 
89     setMinimumSize(QSize(20, 20));
90     setFont(m_pOptions->m_font);
91 }
92 
init(const QVector<LineData> * pLineDataA,LineRef sizeA,const QVector<LineData> * pLineDataB,LineRef sizeB,const QVector<LineData> * pLineDataC,LineRef sizeC,const Diff3LineList * pDiff3LineList,TotalDiffStatus * pTotalDiffStatus,bool bAutoSolve)93 void MergeResultWindow::init(
94     const QVector<LineData>* pLineDataA, LineRef sizeA,
95     const QVector<LineData>* pLineDataB, LineRef sizeB,
96     const QVector<LineData>* pLineDataC, LineRef sizeC,
97     const Diff3LineList* pDiff3LineList,
98     TotalDiffStatus* pTotalDiffStatus,
99     bool bAutoSolve)
100 {
101     m_firstLine = 0;
102     m_horizScrollOffset = 0;
103     m_nofLines = 0;
104     m_bMyUpdate = false;
105     m_bInsertMode = true;
106     m_scrollDeltaX = 0;
107     m_scrollDeltaY = 0;
108     setModified(false);
109 
110     m_pldA = pLineDataA;
111     m_pldB = pLineDataB;
112     m_pldC = pLineDataC;
113     m_sizeA = sizeA;
114     m_sizeB = sizeB;
115     m_sizeC = sizeC;
116 
117     m_pDiff3LineList = pDiff3LineList;
118     m_pTotalDiffStatus = pTotalDiffStatus;
119 
120     m_selection.reset();
121     m_cursorXPos = 0;
122     m_cursorOldXPixelPos = 0;
123     m_cursorYPos = 0;
124 
125     m_maxTextWidth = -1;
126 
127     merge(bAutoSolve, e_SrcSelector::Invalid);
128     update();
129     updateSourceMask();
130 
131     showUnsolvedConflictsStatusMessage();
132 }
133 
134 //This must be called before KXMLGui::SetXMLFile and friends or the actions will not be shown in the menu.
135 //At that point in startup we don't have a MergeResultWindow object so we cannot connect the signals yet.
initActions(KActionCollection * ac)136 void MergeResultWindow::initActions(KActionCollection* ac)
137 {
138     if(ac == nullptr)
139     {
140         KMessageBox::error(nullptr, "actionCollection==0");
141         exit(-1);//we cannot recover from this.
142     }
143 
144     chooseAEverywhere = GuiUtils::createAction<QAction>(i18n("Choose A Everywhere"), QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_1), ac, "merge_choose_a_everywhere");
145     chooseBEverywhere = GuiUtils::createAction<QAction>(i18n("Choose B Everywhere"), QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_2), ac, "merge_choose_b_everywhere");
146     chooseCEverywhere = GuiUtils::createAction<QAction>(i18n("Choose C Everywhere"), QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_3), ac, "merge_choose_c_everywhere");
147     chooseAForUnsolvedConflicts = GuiUtils::createAction<QAction>(i18n("Choose A for All Unsolved Conflicts"), ac, "merge_choose_a_for_unsolved_conflicts");
148     chooseBForUnsolvedConflicts = GuiUtils::createAction<QAction>(i18n("Choose B for All Unsolved Conflicts"), ac, "merge_choose_b_for_unsolved_conflicts");
149     chooseCForUnsolvedConflicts = GuiUtils::createAction<QAction>(i18n("Choose C for All Unsolved Conflicts"), ac, "merge_choose_c_for_unsolved_conflicts");
150     chooseAForUnsolvedWhiteSpaceConflicts = GuiUtils::createAction<QAction>(i18n("Choose A for All Unsolved Whitespace Conflicts"), ac, "merge_choose_a_for_unsolved_whitespace_conflicts");
151     chooseBForUnsolvedWhiteSpaceConflicts = GuiUtils::createAction<QAction>(i18n("Choose B for All Unsolved Whitespace Conflicts"), ac, "merge_choose_b_for_unsolved_whitespace_conflicts");
152     chooseCForUnsolvedWhiteSpaceConflicts = GuiUtils::createAction<QAction>(i18n("Choose C for All Unsolved Whitespace Conflicts"), ac, "merge_choose_c_for_unsolved_whitespace_conflicts");
153 }
154 
connectActions() const155 void MergeResultWindow::connectActions() const
156 {
157     chk_connect(chooseAEverywhere, &QAction::triggered, this, &MergeResultWindow::slotChooseAEverywhere);
158     chk_connect(chooseBEverywhere, &QAction::triggered, this, &MergeResultWindow::slotChooseBEverywhere);
159     chk_connect(chooseCEverywhere, &QAction::triggered, this, &MergeResultWindow::slotChooseCEverywhere);
160 
161     chk_connect(chooseAForUnsolvedConflicts, &QAction::triggered, this, &MergeResultWindow::slotChooseAForUnsolvedConflicts);
162     chk_connect(chooseBForUnsolvedConflicts, &QAction::triggered, this, &MergeResultWindow::slotChooseBForUnsolvedConflicts);
163     chk_connect(chooseCForUnsolvedConflicts, &QAction::triggered, this, &MergeResultWindow::slotChooseCForUnsolvedConflicts);
164 
165     chk_connect(chooseAForUnsolvedWhiteSpaceConflicts, &QAction::triggered, this, &MergeResultWindow::slotChooseAForUnsolvedWhiteSpaceConflicts);
166     chk_connect(chooseBForUnsolvedWhiteSpaceConflicts, &QAction::triggered, this, &MergeResultWindow::slotChooseBForUnsolvedWhiteSpaceConflicts);
167     chk_connect(chooseCForUnsolvedWhiteSpaceConflicts, &QAction::triggered, this, &MergeResultWindow::slotChooseCForUnsolvedWhiteSpaceConflicts);
168 }
169 
setupConnections(const KDiff3App * app)170 void MergeResultWindow::setupConnections(const KDiff3App* app)
171 {
172     chk_connect(app, &KDiff3App::cut, this, &MergeResultWindow::slotCut);
173     chk_connect(app, &KDiff3App::copy, this, &MergeResultWindow::slotCopy);
174     chk_connect(app, &KDiff3App::selectAll, this, &MergeResultWindow::slotSelectAll);
175 
176     chk_connect(this, &MergeResultWindow::scrollMergeResultWindow, app, &KDiff3App::scrollMergeResultWindow);
177     chk_connect(this, &MergeResultWindow::sourceMask, app, &KDiff3App::sourceMask);
178     chk_connect(this, &MergeResultWindow::resizeSignal, app, &KDiff3App::setHScrollBarRange);
179     chk_connect(this, &MergeResultWindow::resizeSignal, this, &MergeResultWindow::slotResize);
180 
181     chk_connect(this, &MergeResultWindow::selectionEnd, app, &KDiff3App::slotSelectionEnd);
182     chk_connect(this, &MergeResultWindow::newSelection, app, &KDiff3App::slotSelectionStart);
183     chk_connect(this, &MergeResultWindow::modifiedChanged, app, &KDiff3App::slotOutputModified);
184     chk_connect(this, &MergeResultWindow::updateAvailabilities, app, &KDiff3App::slotUpdateAvailabilities);
185     chk_connect(this, &MergeResultWindow::showPopupMenu, app, &KDiff3App::showPopupMenu);
186     chk_connect(this, &MergeResultWindow::noRelevantChangesDetected, app, &KDiff3App::slotNoRelevantChangesDetected);
187     chk_connect(this, &MergeResultWindow::statusBarMessage, app, &KDiff3App::slotStatusMsg);
188     //connect menu actions
189     chk_connect(app, &KDiff3App::showWhiteSpaceToggled, this, static_cast<void (MergeResultWindow::*)(void)>(&MergeResultWindow::update));
190     chk_connect(app, &KDiff3App::doRefresh, this, &MergeResultWindow::slotRefresh);
191 
192     chk_connect(app, &KDiff3App::autoSolve, this, &MergeResultWindow::slotAutoSolve);
193     chk_connect(app, &KDiff3App::unsolve, this, &MergeResultWindow::slotUnsolve);
194     chk_connect(app, &KDiff3App::mergeHistory, this, &MergeResultWindow::slotMergeHistory);
195     chk_connect(app, &KDiff3App::regExpAutoMerge, this, &MergeResultWindow::slotRegExpAutoMerge);
196 
197     chk_connect(app, &KDiff3App::goCurrent, this, &MergeResultWindow::slotGoCurrent);
198     chk_connect(app, &KDiff3App::goTop, this, &MergeResultWindow::slotGoTop);
199     chk_connect(app, &KDiff3App::goBottom, this, &MergeResultWindow::slotGoBottom);
200     chk_connect(app, &KDiff3App::goPrevUnsolvedConflict, this, &MergeResultWindow::slotGoPrevUnsolvedConflict);
201     chk_connect(app, &KDiff3App::goNextUnsolvedConflict, this, &MergeResultWindow::slotGoNextUnsolvedConflict);
202     chk_connect(app, &KDiff3App::goPrevConflict, this, &MergeResultWindow::slotGoPrevConflict);
203     chk_connect(app, &KDiff3App::goNextConflict, this, &MergeResultWindow::slotGoNextConflict);
204     chk_connect(app, &KDiff3App::goPrevDelta, this, &MergeResultWindow::slotGoPrevDelta);
205     chk_connect(app, &KDiff3App::goNextDelta, this, &MergeResultWindow::slotGoNextDelta);
206 
207     chk_connect(app, &KDiff3App::changeOverViewMode, this, &MergeResultWindow::setOverviewMode);
208 
209     connections.push_back(KDiff3App::allowCut.connect(boost::bind(&MergeResultWindow::canCut, this)));
210     connections.push_back(KDiff3App::allowCopy.connect(boost::bind(&MergeResultWindow::canCopy, this)));
211     connections.push_back(KDiff3App::getSelection.connect(boost::bind(&MergeResultWindow::getSelection, this)));
212 }
213 
slotResize()214 void MergeResultWindow::slotResize()
215 {
216     mVScrollBar->setRange(0, std::max(0, getNofLines() - getNofVisibleLines()));
217     mVScrollBar->setPageStep(getNofVisibleLines());
218 }
219 
slotCut()220 void MergeResultWindow::slotCut()
221 {
222     const QString curSelection = getSelection();
223     Q_ASSERT(!curSelection.isEmpty() && hasFocus());
224     deleteSelection();
225     update();
226 
227     QApplication::clipboard()->setText(curSelection, QClipboard::Clipboard);
228 }
229 
slotCopy()230 void MergeResultWindow::slotCopy()
231 {
232     if(!hasFocus())
233         return;
234 
235     const QString curSelection = getSelection();
236 
237     if(!curSelection.isEmpty())
238     {
239         QApplication::clipboard()->setText(curSelection, QClipboard::Clipboard);
240     }
241 }
242 
slotSelectAll()243 void MergeResultWindow::slotSelectAll()
244 {
245     if(hasFocus())
246     {
247         setSelection(0, 0, getNofLines(), 0);
248     }
249 }
250 
showUnsolvedConflictsStatusMessage()251 void MergeResultWindow::showUnsolvedConflictsStatusMessage()
252 {
253     if(m_pStatusBar != nullptr)
254     {
255         int wsc;
256         int nofUnsolved = getNrOfUnsolvedConflicts(&wsc);
257 
258         m_persistentStatusMessage = i18n("Number of remaining unsolved conflicts: %1 (of which %2 are whitespace)", nofUnsolved, wsc);
259 
260         Q_EMIT statusBarMessage(m_persistentStatusMessage);
261     }
262 }
263 
slotRefresh()264 void MergeResultWindow::slotRefresh()
265 {
266     setFont(m_pOptions->m_font);
267     update();
268 }
269 
slotUpdateAvailabilities()270 void MergeResultWindow::slotUpdateAvailabilities()
271 {
272     const QWidget* frame = qobject_cast<QWidget*>(parent());
273     Q_ASSERT(frame != nullptr);
274     const bool bMergeEditorVisible = frame->isVisible();
275     const bool bTripleDiff = KDiff3App::isTripleDiff();
276 
277     chooseAEverywhere->setEnabled(bMergeEditorVisible);
278     chooseBEverywhere->setEnabled(bMergeEditorVisible);
279     chooseCEverywhere->setEnabled(bMergeEditorVisible && bTripleDiff);
280     chooseAForUnsolvedConflicts->setEnabled(bMergeEditorVisible);
281     chooseBForUnsolvedConflicts->setEnabled(bMergeEditorVisible);
282     chooseCForUnsolvedConflicts->setEnabled(bMergeEditorVisible && bTripleDiff);
283     chooseAForUnsolvedWhiteSpaceConflicts->setEnabled(bMergeEditorVisible);
284     chooseBForUnsolvedWhiteSpaceConflicts->setEnabled(bMergeEditorVisible);
285     chooseCForUnsolvedWhiteSpaceConflicts->setEnabled(bMergeEditorVisible && bTripleDiff);
286 }
287 
slotStatusMessageChanged(const QString & s)288 void MergeResultWindow::slotStatusMessageChanged(const QString& s)
289 {
290     if(s.isEmpty() && !m_persistentStatusMessage.isEmpty())
291     {
292         Q_EMIT statusBarMessage(m_persistentStatusMessage);
293     }
294 }
295 
reset()296 void MergeResultWindow::reset()
297 {
298     m_pDiff3LineList = nullptr;
299     m_pTotalDiffStatus = nullptr;
300     m_pldA = nullptr;
301     m_pldB = nullptr;
302     m_pldC = nullptr;
303     if(!m_persistentStatusMessage.isEmpty())
304     {
305         m_persistentStatusMessage = QString();
306     }
307 }
308 
309 // Calculate the merge information for the given Diff3Line.
310 // Results will be stored in mergeDetails, bConflict, bLineRemoved and src.
mergeOneLine(e_MergeDetails & mergeDetails,bool & bConflict,bool & bLineRemoved,e_SrcSelector & src,bool bTwoInputs) const311 void Diff3Line::mergeOneLine(
312     e_MergeDetails& mergeDetails, bool& bConflict,
313     bool& bLineRemoved, e_SrcSelector& src, bool bTwoInputs) const
314 {
315     mergeDetails = e_MergeDetails::eDefault;
316     bConflict = false;
317     bLineRemoved = false;
318     src = e_SrcSelector::None;
319 
320     if(bTwoInputs) // Only two input files
321     {
322         if(getLineA().isValid() && getLineB().isValid())
323         {
324             if(pFineAB == nullptr)
325             {
326                 mergeDetails = e_MergeDetails::eNoChange;
327                 src = e_SrcSelector::A;
328             }
329             else
330             {
331                 mergeDetails = e_MergeDetails::eBChanged;
332                 bConflict = true;
333             }
334         }
335         else
336         {
337             mergeDetails = e_MergeDetails::eBDeleted;
338             bConflict = true;
339         }
340         return;
341     }
342 
343     // A is base.
344     if(getLineA().isValid() && getLineB().isValid() && getLineC().isValid())
345     {
346         if(pFineAB == nullptr && pFineBC == nullptr && pFineCA == nullptr)
347         {
348             mergeDetails = e_MergeDetails::eNoChange;
349             src = e_SrcSelector::A;
350         }
351         else if(pFineAB == nullptr && pFineBC != nullptr && pFineCA != nullptr)
352         {
353             mergeDetails = e_MergeDetails::eCChanged;
354             src = e_SrcSelector::C;
355         }
356         else if(pFineAB != nullptr && pFineBC != nullptr && pFineCA == nullptr)
357         {
358             mergeDetails = e_MergeDetails::eBChanged;
359             src = e_SrcSelector::B;
360         }
361         else if(pFineAB != nullptr && pFineBC == nullptr && pFineCA != nullptr)
362         {
363             mergeDetails = e_MergeDetails::eBCChangedAndEqual;
364             src = e_SrcSelector::C;
365         }
366         else if(pFineAB != nullptr && pFineBC != nullptr && pFineCA != nullptr)
367         {
368             mergeDetails = e_MergeDetails::eBCChanged;
369             bConflict = true;
370         }
371         else
372             Q_ASSERT(true);
373     }
374     else if(getLineA().isValid() && getLineB().isValid() && !getLineC().isValid())
375     {
376         if(pFineAB != nullptr)
377         {
378             mergeDetails = e_MergeDetails::eBChanged_CDeleted;
379             bConflict = true;
380         }
381         else
382         {
383             mergeDetails = e_MergeDetails::eCDeleted;
384             bLineRemoved = true;
385             src = e_SrcSelector::C;
386         }
387     }
388     else if(getLineA().isValid() && !getLineB().isValid() && getLineC().isValid())
389     {
390         if(pFineCA != nullptr)
391         {
392             mergeDetails = e_MergeDetails::eCChanged_BDeleted;
393             bConflict = true;
394         }
395         else
396         {
397             mergeDetails = e_MergeDetails::eBDeleted;
398             bLineRemoved = true;
399             src = e_SrcSelector::B;
400         }
401     }
402     else if(!getLineA().isValid() && getLineB().isValid() && getLineC().isValid())
403     {
404         if(pFineBC != nullptr)
405         {
406             mergeDetails = e_MergeDetails::eBCAdded;
407             bConflict = true;
408         }
409         else // B==C
410         {
411             mergeDetails = e_MergeDetails::eBCAddedAndEqual;
412             src = e_SrcSelector::C;
413         }
414     }
415     else if(!getLineA().isValid() && !getLineB().isValid() && getLineC().isValid())
416     {
417         mergeDetails = e_MergeDetails::eCAdded;
418         src = e_SrcSelector::C;
419     }
420     else if(!getLineA().isValid() && getLineB().isValid() && !getLineC().isValid())
421     {
422         mergeDetails = e_MergeDetails::eBAdded;
423         src = e_SrcSelector::B;
424     }
425     else if(getLineA().isValid() && !getLineB().isValid() && !getLineC().isValid())
426     {
427         mergeDetails = e_MergeDetails::eBCDeleted;
428         bLineRemoved = true;
429         src = e_SrcSelector::C;
430     }
431     else
432         Q_ASSERT(true);
433 }
434 
sameKindCheck(const MergeLine & ml1,const MergeLine & ml2)435 bool MergeResultWindow::sameKindCheck(const MergeLine& ml1, const MergeLine& ml2)
436 {
437     if(ml1.bConflict && ml2.bConflict)
438     {
439         // Both lines have conflicts: If one is only a white space conflict and
440         // the other one is a real conflict, then this line returns false.
441         return ml1.id3l->isEqualAC() == ml2.id3l->isEqualAC() && ml1.id3l->isEqualAB() == ml2.id3l->isEqualAB();
442     }
443     else
444         return (
445             (!ml1.bConflict && !ml2.bConflict && ml1.bDelta && ml2.bDelta && ml1.srcSelect == ml2.srcSelect && (ml1.mergeDetails == ml2.mergeDetails || (ml1.mergeDetails != e_MergeDetails::eBCAddedAndEqual && ml2.mergeDetails != e_MergeDetails::eBCAddedAndEqual))) ||
446             (!ml1.bDelta && !ml2.bDelta));
447 }
448 
merge(bool bAutoSolve,e_SrcSelector defaultSelector,bool bConflictsOnly,bool bWhiteSpaceOnly)449 void MergeResultWindow::merge(bool bAutoSolve, e_SrcSelector defaultSelector, bool bConflictsOnly, bool bWhiteSpaceOnly)
450 {
451     if(!bConflictsOnly)
452     {
453         if(m_bModified)
454         {
455             int result = KMessageBox::warningYesNo(this,
456                                                    i18n("The output has been modified.\n"
457                                                         "If you continue your changes will be lost."),
458                                                    i18n("Warning"),
459                                                    KStandardGuiItem::cont(),
460                                                    KStandardGuiItem::cancel());
461             if(result == KMessageBox::No)
462                 return;
463         }
464 
465         m_mergeLineList.clear();
466 
467         int lineIdx = 0;
468         Diff3LineList::const_iterator it;
469         for(it = m_pDiff3LineList->begin(); it != m_pDiff3LineList->end(); ++it, ++lineIdx)
470         {
471             const Diff3Line& d = *it;
472 
473             MergeLine ml;
474             bool bLineRemoved;
475             d.mergeOneLine(ml.mergeDetails, ml.bConflict, bLineRemoved, ml.srcSelect, m_pldC == nullptr);
476 
477             // Automatic solving for only whitespace changes.
478             if(ml.bConflict &&
479                ((m_pldC == nullptr && (d.isEqualAB() || (d.isWhiteLine(e_SrcSelector::A) && d.isWhiteLine(e_SrcSelector::B)))) ||
480                 (m_pldC != nullptr && ((d.isEqualAB() && d.isEqualAC()) || (d.isWhiteLine(e_SrcSelector::A) && d.isWhiteLine(e_SrcSelector::B) && d.isWhiteLine(e_SrcSelector::C))))))
481             {
482                 ml.bWhiteSpaceConflict = true;
483             }
484 
485             ml.d3lLineIdx = lineIdx;
486             ml.bDelta = ml.srcSelect != e_SrcSelector::A;
487             ml.id3l = it;
488             ml.srcRangeLength = 1;
489 
490             MergeLine* back = m_mergeLineList.empty() ? nullptr : &m_mergeLineList.back();
491 
492             bool bSame = back != nullptr && sameKindCheck(ml, *back);
493             if(bSame)
494             {
495                 ++back->srcRangeLength;
496                 if(back->bWhiteSpaceConflict && !ml.bWhiteSpaceConflict)
497                     back->bWhiteSpaceConflict = false;
498             }
499             else
500             {
501                 m_mergeLineList.push_back(ml);
502             }
503 
504             if(!ml.bConflict)
505             {
506                 MergeLine& tmpBack = m_mergeLineList.back();
507                 MergeEditLine mel(ml.id3l);
508                 mel.setSource(ml.srcSelect, bLineRemoved);
509                 tmpBack.mergeEditLineList.push_back(mel);
510             }
511             else if(back == nullptr || !back->bConflict || !bSame)
512             {
513                 MergeLine& tmpBack = m_mergeLineList.back();
514                 MergeEditLine mel(ml.id3l);
515                 mel.setConflict();
516                 tmpBack.mergeEditLineList.push_back(mel);
517             }
518         }
519     }
520 
521     bool bSolveWhiteSpaceConflicts = false;
522     if(bAutoSolve) // when true, then the other params are not used and we can change them here. (see all invocations of merge())
523     {
524         if(m_pldC == nullptr && m_pOptions->m_whiteSpace2FileMergeDefault != (int)e_SrcSelector::None) // Only two inputs
525         {
526             Q_ASSERT(m_pOptions->m_whiteSpace2FileMergeDefault <= (int)e_SrcSelector::Max && m_pOptions->m_whiteSpace2FileMergeDefault >= (int)e_SrcSelector::Min);
527             defaultSelector = (e_SrcSelector)m_pOptions->m_whiteSpace2FileMergeDefault;
528             bWhiteSpaceOnly = true;
529             bSolveWhiteSpaceConflicts = true;
530         }
531         else if(m_pldC != nullptr && m_pOptions->m_whiteSpace3FileMergeDefault != (int)e_SrcSelector::None)
532         {
533             Q_ASSERT(m_pOptions->m_whiteSpace3FileMergeDefault <= (int)e_SrcSelector::Max && m_pOptions->m_whiteSpace2FileMergeDefault >= (int)e_SrcSelector::Min);
534             defaultSelector = (e_SrcSelector)m_pOptions->m_whiteSpace3FileMergeDefault;
535             bWhiteSpaceOnly = true;
536             bSolveWhiteSpaceConflicts = true;
537         }
538     }
539 
540     if(!bAutoSolve || bSolveWhiteSpaceConflicts)
541     {
542         // Change all auto selections
543         MergeLineList::iterator mlIt;
544         for(mlIt = m_mergeLineList.begin(); mlIt != m_mergeLineList.end(); ++mlIt)
545         {
546             MergeLine& ml = *mlIt;
547             bool bConflict = ml.mergeEditLineList.empty() || ml.mergeEditLineList.begin()->isConflict();
548             if(ml.bDelta && (!bConflictsOnly || bConflict) && (!bWhiteSpaceOnly || ml.bWhiteSpaceConflict))
549             {
550                 ml.mergeEditLineList.clear();
551                 if(defaultSelector == e_SrcSelector::Invalid && ml.bDelta)
552                 {
553                     MergeEditLine mel(ml.id3l);
554 
555                     mel.setConflict();
556                     ml.bConflict = true;
557                     ml.mergeEditLineList.push_back(mel);
558                 }
559                 else
560                 {
561                     Diff3LineList::const_iterator d3llit = ml.id3l;
562                     int j;
563 
564                     for(j = 0; j < ml.srcRangeLength; ++j)
565                     {
566                         MergeEditLine mel(d3llit);
567                         mel.setSource(defaultSelector, false);
568 
569                         LineRef srcLine = defaultSelector == e_SrcSelector::A ? d3llit->getLineA() : defaultSelector == e_SrcSelector::B ? d3llit->getLineB() : defaultSelector == e_SrcSelector::C ? d3llit->getLineC() : LineRef();
570 
571                         if(srcLine.isValid())
572                         {
573                             ml.mergeEditLineList.push_back(mel);
574                         }
575 
576                         ++d3llit;
577                     }
578 
579                     if(ml.mergeEditLineList.empty()) // Make a line nevertheless
580                     {
581                         MergeEditLine mel(ml.id3l);
582                         mel.setRemoved(defaultSelector);
583                         ml.mergeEditLineList.push_back(mel);
584                     }
585                 }
586             }
587         }
588     }
589 
590     MergeLineList::iterator mlIt;
591     for(mlIt = m_mergeLineList.begin(); mlIt != m_mergeLineList.end(); ++mlIt)
592     {
593         MergeLine& ml = *mlIt;
594         // Remove all lines that are empty, because no src lines are there.
595 
596         LineRef oldSrcLine;
597         e_SrcSelector oldSrc = e_SrcSelector::Invalid;
598         MergeEditLineList::iterator melIt;
599         for(melIt = ml.mergeEditLineList.begin(); melIt != ml.mergeEditLineList.end();)
600         {
601             MergeEditLine& mel = *melIt;
602             e_SrcSelector melsrc = mel.src();
603 
604             LineRef srcLine = mel.isRemoved() ? LineRef() : melsrc == e_SrcSelector::A ? mel.id3l()->getLineA() : melsrc == e_SrcSelector::B ? mel.id3l()->getLineB() : melsrc == e_SrcSelector::C ? mel.id3l()->getLineC() : LineRef();
605 
606             // At least one line remains because oldSrc != melsrc for first line in list
607             // Other empty lines will be removed
608             if(!srcLine.isValid() && !oldSrcLine.isValid() && oldSrc == melsrc)
609                 melIt = ml.mergeEditLineList.erase(melIt);
610             else
611                 ++melIt;
612 
613             oldSrcLine = srcLine;
614             oldSrc = melsrc;
615         }
616     }
617 
618     if(bAutoSolve && !bConflictsOnly)
619     {
620         if(m_pOptions->m_bRunHistoryAutoMergeOnMergeStart)
621             slotMergeHistory();
622         if(m_pOptions->m_bRunRegExpAutoMergeOnMergeStart)
623             slotRegExpAutoMerge();
624         if(m_pldC != nullptr && !doRelevantChangesExist())
625             Q_EMIT noRelevantChangesDetected();
626     }
627 
628     int nrOfSolvedConflicts = 0;
629     int nrOfUnsolvedConflicts = 0;
630     int nrOfWhiteSpaceConflicts = 0;
631 
632     MergeLineList::iterator i;
633     for(i = m_mergeLineList.begin(); i != m_mergeLineList.end(); ++i)
634     {
635         if(i->bConflict)
636             ++nrOfUnsolvedConflicts;
637         else if(i->bDelta)
638             ++nrOfSolvedConflicts;
639 
640         if(i->bWhiteSpaceConflict)
641             ++nrOfWhiteSpaceConflicts;
642     }
643 
644     m_pTotalDiffStatus->setUnsolvedConflicts(nrOfUnsolvedConflicts);
645     m_pTotalDiffStatus->setSolvedConflicts(nrOfSolvedConflicts);
646     m_pTotalDiffStatus->setWhitespaceConflicts(nrOfWhiteSpaceConflicts);
647 
648     m_cursorXPos = 0;
649     m_cursorOldXPixelPos = 0;
650     m_cursorYPos = 0;
651     m_maxTextWidth = -1;
652 
653     //m_firstLine = 0; // Must not set line/column without scrolling there
654     //m_horizScrollOffset = 0;
655 
656     setModified(false);
657 
658     m_currentMergeLineIt = m_mergeLineList.begin();
659     slotGoTop();
660 
661     Q_EMIT updateAvailabilities();
662     update();
663 }
664 
setFirstLine(QtNumberType firstLine)665 void MergeResultWindow::setFirstLine(QtNumberType firstLine)
666 {
667     m_firstLine = std::max(0, firstLine);
668     update();
669 }
670 
setHorizScrollOffset(int horizScrollOffset)671 void MergeResultWindow::setHorizScrollOffset(int horizScrollOffset)
672 {
673     m_horizScrollOffset = std::max(0, horizScrollOffset);
674     update();
675 }
676 
getMaxTextWidth()677 int MergeResultWindow::getMaxTextWidth()
678 {
679     if(m_maxTextWidth < 0)
680     {
681         m_maxTextWidth = 0;
682 
683         MergeLineList::iterator mlIt = m_mergeLineList.begin();
684         for(mlIt = m_mergeLineList.begin(); mlIt != m_mergeLineList.end(); ++mlIt)
685         {
686             MergeLine& ml = *mlIt;
687             MergeEditLineList::iterator melIt;
688             for(melIt = ml.mergeEditLineList.begin(); melIt != ml.mergeEditLineList.end(); ++melIt)
689             {
690                 MergeEditLine& mel = *melIt;
691                 QString s = mel.getString(m_pldA, m_pldB, m_pldC);
692 
693                 QTextLayout textLayout(s, font(), this);
694                 textLayout.beginLayout();
695                 textLayout.createLine();
696                 textLayout.endLayout();
697                 if(m_maxTextWidth < textLayout.maximumWidth())
698                 {
699                     m_maxTextWidth = qCeil(textLayout.maximumWidth());
700                 }
701             }
702         }
703         m_maxTextWidth += 5; // cursorwidth
704     }
705     return m_maxTextWidth;
706 }
707 
getNofLines() const708 int MergeResultWindow::getNofLines() const
709 {
710     return m_nofLines;
711 }
712 
getVisibleTextAreaWidth()713 int MergeResultWindow::getVisibleTextAreaWidth()
714 {
715     return width() - getTextXOffset();
716 }
717 
getNofVisibleLines()718 int MergeResultWindow::getNofVisibleLines()
719 {
720     QFontMetrics fm = fontMetrics();
721     return (height() - 3) / fm.lineSpacing() - 2;
722 }
723 
getTextXOffset()724 int MergeResultWindow::getTextXOffset()
725 {
726     QFontMetrics fm = fontMetrics();
727     return 3 * Utils::getHorizontalAdvance(fm, '0');
728 }
729 
resizeEvent(QResizeEvent * e)730 void MergeResultWindow::resizeEvent(QResizeEvent* e)
731 {
732     QWidget::resizeEvent(e);
733     Q_EMIT resizeSignal();
734 }
735 
getOverviewMode()736 e_OverviewMode MergeResultWindow::getOverviewMode()
737 {
738     return mOverviewMode;
739 }
740 
setOverviewMode(e_OverviewMode eOverviewMode)741 void MergeResultWindow::setOverviewMode(e_OverviewMode eOverviewMode)
742 {
743     mOverviewMode = eOverviewMode;
744 }
745 
746 // Check whether we should ignore current delta when moving to next/previous delta
checkOverviewIgnore(MergeLineList::iterator & i)747 bool MergeResultWindow::checkOverviewIgnore(MergeLineList::iterator& i)
748 {
749     if(mOverviewMode == e_OverviewMode::eOMNormal) return false;
750     if(mOverviewMode == e_OverviewMode::eOMAvsB)
751         return i->mergeDetails == e_MergeDetails::eCAdded || i->mergeDetails == e_MergeDetails::eCDeleted || i->mergeDetails == e_MergeDetails::eCChanged;
752     if(mOverviewMode == e_OverviewMode::eOMAvsC)
753         return i->mergeDetails == e_MergeDetails::eBAdded || i->mergeDetails == e_MergeDetails::eBDeleted || i->mergeDetails == e_MergeDetails::eBChanged;
754     if(mOverviewMode == e_OverviewMode::eOMBvsC)
755         return i->mergeDetails == e_MergeDetails::eBCAddedAndEqual || i->mergeDetails == e_MergeDetails::eBCDeleted || i->mergeDetails == e_MergeDetails::eBCChangedAndEqual;
756     return false;
757 }
758 
759 // Go to prev/next delta/conflict or first/last delta.
go(e_Direction eDir,e_EndPoint eEndPoint)760 void MergeResultWindow::go(e_Direction eDir, e_EndPoint eEndPoint)
761 {
762     Q_ASSERT(eDir == eUp || eDir == eDown);
763     MergeLineList::iterator i = m_currentMergeLineIt;
764     bool bSkipWhiteConflicts = !m_pOptions->m_bShowWhiteSpace;
765     if(eEndPoint == eEnd)
766     {
767         if(eDir == eUp)
768             i = m_mergeLineList.begin(); // first mergeline
769         else
770             i = --m_mergeLineList.end(); // last mergeline
771 
772         while(isItAtEnd(eDir == eUp, i) && !i->bDelta)
773         {
774             if(eDir == eUp)
775                 ++i; // search downwards
776             else
777                 --i; // search upwards
778         }
779     }
780     else if(eEndPoint == eDelta && isItAtEnd(eDir != eUp, i))
781     {
782         do
783         {
784             if(eDir == eUp)
785                 --i;
786             else
787                 ++i;
788         } while(isItAtEnd(eDir != eUp, i) && (!i->bDelta || checkOverviewIgnore(i) || (bSkipWhiteConflicts && i->bWhiteSpaceConflict)));
789     }
790     else if(eEndPoint == eConflict && isItAtEnd(eDir != eUp, i))
791     {
792         do
793         {
794             if(eDir == eUp)
795                 --i;
796             else
797                 ++i;
798         } while(isItAtEnd(eDir != eUp, i) && (!i->bConflict || (bSkipWhiteConflicts && i->bWhiteSpaceConflict)));
799     }
800     else if(isItAtEnd(eDir != eUp, i) && eEndPoint == eUnsolvedConflict)
801     {
802         do
803         {
804             if(eDir == eUp)
805                 --i;
806             else
807                 ++i;
808         } while(isItAtEnd(eDir != eUp, i) && !i->mergeEditLineList.begin()->isConflict());
809     }
810 
811     if(isVisible())
812         setFocus();
813 
814     setFastSelector(i);
815 }
816 
isDeltaAboveCurrent()817 bool MergeResultWindow::isDeltaAboveCurrent()
818 {
819     bool bSkipWhiteConflicts = !m_pOptions->m_bShowWhiteSpace;
820     if(m_mergeLineList.empty()) return false;
821     MergeLineList::iterator i = m_currentMergeLineIt;
822     if(i == m_mergeLineList.begin()) return false;
823     do
824     {
825         --i;
826         if(i->bDelta && !checkOverviewIgnore(i) && !(bSkipWhiteConflicts && i->bWhiteSpaceConflict)) return true;
827     } while(i != m_mergeLineList.begin());
828 
829     return false;
830 }
831 
isDeltaBelowCurrent()832 bool MergeResultWindow::isDeltaBelowCurrent()
833 {
834     bool bSkipWhiteConflicts = !m_pOptions->m_bShowWhiteSpace;
835     if(m_mergeLineList.empty()) return false;
836 
837     MergeLineList::iterator i = m_currentMergeLineIt;
838     if(i != m_mergeLineList.end())
839     {
840         ++i;
841         for(; i != m_mergeLineList.end(); ++i)
842         {
843             if(i->bDelta && !checkOverviewIgnore(i) && !(bSkipWhiteConflicts && i->bWhiteSpaceConflict)) return true;
844         }
845     }
846     return false;
847 }
848 
isConflictAboveCurrent()849 bool MergeResultWindow::isConflictAboveCurrent()
850 {
851     if(m_mergeLineList.empty()) return false;
852     MergeLineList::iterator i = m_currentMergeLineIt;
853     if(i == m_mergeLineList.begin()) return false;
854 
855     bool bSkipWhiteConflicts = !m_pOptions->m_bShowWhiteSpace;
856 
857     do
858     {
859         --i;
860         if(i->bConflict && !(bSkipWhiteConflicts && i->bWhiteSpaceConflict)) return true;
861     } while(i != m_mergeLineList.begin());
862 
863     return false;
864 }
865 
isConflictBelowCurrent()866 bool MergeResultWindow::isConflictBelowCurrent()
867 {
868     MergeLineList::iterator i = m_currentMergeLineIt;
869     if(m_mergeLineList.empty()) return false;
870 
871     bool bSkipWhiteConflicts = !m_pOptions->m_bShowWhiteSpace;
872 
873     if(i != m_mergeLineList.end())
874     {
875         ++i;
876         for(; i != m_mergeLineList.end(); ++i)
877         {
878             if(i->bConflict && !(bSkipWhiteConflicts && i->bWhiteSpaceConflict)) return true;
879         }
880     }
881     return false;
882 }
883 
isUnsolvedConflictAtCurrent()884 bool MergeResultWindow::isUnsolvedConflictAtCurrent()
885 {
886     if(m_mergeLineList.empty()) return false;
887     MergeLineList::iterator i = m_currentMergeLineIt;
888     return i->mergeEditLineList.begin()->isConflict();
889 }
890 
isUnsolvedConflictAboveCurrent()891 bool MergeResultWindow::isUnsolvedConflictAboveCurrent()
892 {
893     if(m_mergeLineList.empty()) return false;
894     MergeLineList::iterator i = m_currentMergeLineIt;
895     if(i == m_mergeLineList.begin()) return false;
896 
897     do
898     {
899         --i;
900         if(i->mergeEditLineList.begin()->isConflict()) return true;
901     } while(i != m_mergeLineList.begin());
902 
903     return false;
904 }
905 
isUnsolvedConflictBelowCurrent()906 bool MergeResultWindow::isUnsolvedConflictBelowCurrent()
907 {
908     MergeLineList::iterator i = m_currentMergeLineIt;
909     if(m_mergeLineList.empty()) return false;
910 
911     if(i != m_mergeLineList.end())
912     {
913         ++i;
914         for(; i != m_mergeLineList.end(); ++i)
915         {
916             if(i->mergeEditLineList.begin()->isConflict()) return true;
917         }
918     }
919     return false;
920 }
921 
slotGoTop()922 void MergeResultWindow::slotGoTop()
923 {
924     go(eUp, eEnd);
925 }
926 
slotGoCurrent()927 void MergeResultWindow::slotGoCurrent()
928 {
929     setFastSelector(m_currentMergeLineIt);
930 }
931 
slotGoBottom()932 void MergeResultWindow::slotGoBottom()
933 {
934     go(eDown, eEnd);
935 }
936 
slotGoPrevDelta()937 void MergeResultWindow::slotGoPrevDelta()
938 {
939     go(eUp, eDelta);
940 }
941 
slotGoNextDelta()942 void MergeResultWindow::slotGoNextDelta()
943 {
944     go(eDown, eDelta);
945 }
946 
slotGoPrevConflict()947 void MergeResultWindow::slotGoPrevConflict()
948 {
949     go(eUp, eConflict);
950 }
951 
slotGoNextConflict()952 void MergeResultWindow::slotGoNextConflict()
953 {
954     go(eDown, eConflict);
955 }
956 
slotGoPrevUnsolvedConflict()957 void MergeResultWindow::slotGoPrevUnsolvedConflict()
958 {
959     go(eUp, eUnsolvedConflict);
960 }
961 
slotGoNextUnsolvedConflict()962 void MergeResultWindow::slotGoNextUnsolvedConflict()
963 {
964     go(eDown, eUnsolvedConflict);
965 }
966 
967 /** The line is given as a index in the Diff3LineList.
968     The function calculates the corresponding iterator. */
slotSetFastSelectorLine(LineIndex line)969 void MergeResultWindow::slotSetFastSelectorLine(LineIndex line)
970 {
971     MergeLineList::iterator i;
972     for(i = m_mergeLineList.begin(); i != m_mergeLineList.end(); ++i)
973     {
974         if(line >= i->d3lLineIdx && line < i->d3lLineIdx + i->srcRangeLength)
975         {
976             //if ( i->bDelta )
977             {
978                 setFastSelector(i);
979             }
980             break;
981         }
982     }
983 }
984 
getNrOfUnsolvedConflicts(int * pNrOfWhiteSpaceConflicts)985 int MergeResultWindow::getNrOfUnsolvedConflicts(int* pNrOfWhiteSpaceConflicts)
986 {
987     int nrOfUnsolvedConflicts = 0;
988     if(pNrOfWhiteSpaceConflicts != nullptr)
989         *pNrOfWhiteSpaceConflicts = 0;
990 
991     MergeLineList::iterator mlIt = m_mergeLineList.begin();
992     for(mlIt = m_mergeLineList.begin(); mlIt != m_mergeLineList.end(); ++mlIt)
993     {
994         MergeLine& ml = *mlIt;
995         MergeEditLineList::iterator melIt = ml.mergeEditLineList.begin();
996         if(melIt->isConflict())
997         {
998             ++nrOfUnsolvedConflicts;
999             if(ml.bWhiteSpaceConflict && pNrOfWhiteSpaceConflicts != nullptr)
1000                 ++*pNrOfWhiteSpaceConflicts;
1001         }
1002     }
1003 
1004     return nrOfUnsolvedConflicts;
1005 }
1006 
showNrOfConflicts()1007 void MergeResultWindow::showNrOfConflicts()
1008 {
1009     if(!m_pOptions->m_bShowInfoDialogs)
1010         return;
1011     int nrOfConflicts = 0;
1012     MergeLineList::iterator i;
1013     for(i = m_mergeLineList.begin(); i != m_mergeLineList.end(); ++i)
1014     {
1015         if(i->bConflict || i->bDelta)
1016             ++nrOfConflicts;
1017     }
1018     QString totalInfo;
1019     if(m_pTotalDiffStatus->isBinaryEqualAB() && m_pTotalDiffStatus->isBinaryEqualAC())
1020         totalInfo += i18n("All input files are binary equal.");
1021     else if(m_pTotalDiffStatus->isTextEqualAB() && m_pTotalDiffStatus->isTextEqualAC())
1022         totalInfo += i18n("All input files contain the same text.");
1023     else
1024     {
1025         if(m_pTotalDiffStatus->isBinaryEqualAB())
1026             totalInfo += i18n("Files %1 and %2 are binary equal.\n", i18n("A"), i18n("B"));
1027         else if(m_pTotalDiffStatus->isTextEqualAB())
1028             totalInfo += i18n("Files %1 and %2 have equal text.\n", i18n("A"), i18n("B"));
1029         if(m_pTotalDiffStatus->isBinaryEqualAC())
1030             totalInfo += i18n("Files %1 and %2 are binary equal.\n", i18n("A"), i18n("C"));
1031         else if(m_pTotalDiffStatus->isTextEqualAC())
1032             totalInfo += i18n("Files %1 and %2 have equal text.\n", i18n("A"), i18n("C"));
1033         if(m_pTotalDiffStatus->isBinaryEqualBC())
1034             totalInfo += i18n("Files %1 and %2 are binary equal.\n", i18n("B"), i18n("C"));
1035         else if(m_pTotalDiffStatus->isTextEqualBC())
1036             totalInfo += i18n("Files %1 and %2 have equal text.\n", i18n("B"), i18n("C"));
1037     }
1038 
1039     int nrOfUnsolvedConflicts = getNrOfUnsolvedConflicts();
1040 
1041     KMessageBox::information(this,
1042                              i18n("Total number of conflicts: %1\n"
1043                                   "Number of automatically solved conflicts: %2\n"
1044                                   "Number of unsolved conflicts: %3\n"
1045                                   "%4",
1046                                   nrOfConflicts, nrOfConflicts - nrOfUnsolvedConflicts,
1047                                   nrOfUnsolvedConflicts, totalInfo),
1048                              i18n("Conflicts"));
1049 }
1050 
setFastSelector(MergeLineList::iterator i)1051 void MergeResultWindow::setFastSelector(MergeLineList::iterator i)
1052 {
1053     if(i == m_mergeLineList.end())
1054         return;
1055     m_currentMergeLineIt = i;
1056     Q_EMIT setFastSelectorRange(i->d3lLineIdx, i->srcRangeLength);
1057 
1058     int line1 = 0;
1059 
1060     MergeLineList::iterator mlIt = m_mergeLineList.begin();
1061     for(mlIt = m_mergeLineList.begin(); mlIt != m_mergeLineList.end(); ++mlIt)
1062     {
1063         if(mlIt == m_currentMergeLineIt)
1064             break;
1065         line1 += mlIt->mergeEditLineList.size();
1066     }
1067 
1068     int nofLines = m_currentMergeLineIt->mergeEditLineList.size();
1069     int newFirstLine = getBestFirstLine(line1, nofLines, m_firstLine, getNofVisibleLines());
1070     if(newFirstLine != m_firstLine)
1071     {
1072         scrollVertically(newFirstLine - m_firstLine);
1073     }
1074 
1075     if(m_selection.isEmpty())
1076     {
1077         m_cursorXPos = 0;
1078         m_cursorOldXPixelPos = 0;
1079         m_cursorYPos = line1;
1080     }
1081 
1082     update();
1083     updateSourceMask();
1084     Q_EMIT updateAvailabilities();
1085 }
1086 
choose(e_SrcSelector selector)1087 void MergeResultWindow::choose(e_SrcSelector selector)
1088 {
1089     if(m_currentMergeLineIt == m_mergeLineList.end())
1090         return;
1091 
1092     setModified();
1093 
1094     // First find range for which this change works.
1095     MergeLine& ml = *m_currentMergeLineIt;
1096 
1097     MergeEditLineList::iterator melIt;
1098 
1099     // Now check if selector is active for this range already.
1100     bool bActive = false;
1101 
1102     // Remove unneeded lines in the range.
1103     for(melIt = ml.mergeEditLineList.begin(); melIt != ml.mergeEditLineList.end();)
1104     {
1105         MergeEditLine& mel = *melIt;
1106         if(mel.src() == selector)
1107             bActive = true;
1108 
1109         if(mel.src() == selector || !mel.isEditableText() || mel.isModified())
1110             melIt = ml.mergeEditLineList.erase(melIt);
1111         else
1112             ++melIt;
1113     }
1114 
1115     if(!bActive) // Selected source wasn't active.
1116     {            // Append the lines from selected source here at rangeEnd.
1117         Diff3LineList::const_iterator d3llit = ml.id3l;
1118         int j;
1119 
1120         for(j = 0; j < ml.srcRangeLength; ++j)
1121         {
1122             MergeEditLine mel(d3llit);
1123             mel.setSource(selector, false);
1124             ml.mergeEditLineList.push_back(mel);
1125 
1126             ++d3llit;
1127         }
1128     }
1129 
1130     if(!ml.mergeEditLineList.empty())
1131     {
1132         // Remove all lines that are empty, because no src lines are there.
1133         for(melIt = ml.mergeEditLineList.begin(); melIt != ml.mergeEditLineList.end();)
1134         {
1135             MergeEditLine& mel = *melIt;
1136 
1137             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();
1138 
1139             if(!srcLine.isValid())
1140                 melIt = ml.mergeEditLineList.erase(melIt);
1141             else
1142                 ++melIt;
1143         }
1144     }
1145 
1146     if(ml.mergeEditLineList.empty())
1147     {
1148         // Insert a dummy line:
1149         MergeEditLine mel(ml.id3l);
1150 
1151         if(bActive)
1152             mel.setConflict(); // All src entries deleted => conflict
1153         else
1154             mel.setRemoved(selector); // No lines in corresponding src found.
1155 
1156         ml.mergeEditLineList.push_back(mel);
1157     }
1158 
1159     if(m_cursorYPos >= m_nofLines)
1160     {
1161         m_cursorYPos = m_nofLines - 1;
1162         m_cursorXPos = 0;
1163     }
1164 
1165     m_maxTextWidth = -1;
1166     update();
1167     updateSourceMask();
1168     Q_EMIT updateAvailabilities();
1169     showUnsolvedConflictsStatusMessage();
1170 }
1171 
1172 // bConflictsOnly: automatically choose for conflicts only (true) or for everywhere (false)
chooseGlobal(e_SrcSelector selector,bool bConflictsOnly,bool bWhiteSpaceOnly)1173 void MergeResultWindow::chooseGlobal(e_SrcSelector selector, bool bConflictsOnly, bool bWhiteSpaceOnly)
1174 {
1175     resetSelection();
1176 
1177     merge(false, selector, bConflictsOnly, bWhiteSpaceOnly);
1178     setModified(true);
1179     update();
1180     showUnsolvedConflictsStatusMessage();
1181 }
1182 
slotAutoSolve()1183 void MergeResultWindow::slotAutoSolve()
1184 {
1185     resetSelection();
1186     merge(true, e_SrcSelector::Invalid);
1187     setModified(true);
1188     update();
1189     showUnsolvedConflictsStatusMessage();
1190     showNrOfConflicts();
1191 }
1192 
slotUnsolve()1193 void MergeResultWindow::slotUnsolve()
1194 {
1195     resetSelection();
1196     merge(false, e_SrcSelector::Invalid);
1197     setModified(true);
1198     update();
1199     showUnsolvedConflictsStatusMessage();
1200 }
1201 
findParenthesesGroups(const QString & s,QStringList & sl)1202 bool findParenthesesGroups(const QString& s, QStringList& sl)
1203 {
1204     sl.clear();
1205     int i = 0;
1206     std::list<int> startPosStack;
1207     int length = s.length();
1208     for(i = 0; i < length; ++i)
1209     {
1210         if(s[i] == '\\' && i + 1 < length && (s[i + 1] == '\\' || s[i + 1] == '(' || s[i + 1] == ')'))
1211         {
1212             ++i;
1213             continue;
1214         }
1215         if(s[i] == '(')
1216         {
1217             startPosStack.push_back(i);
1218         }
1219         else if(s[i] == ')')
1220         {
1221             if(startPosStack.empty())
1222                 return false; // Parentheses don't match
1223             int startPos = startPosStack.back();
1224             startPosStack.pop_back();
1225             sl.push_back(s.mid(startPos + 1, i - startPos - 1));
1226         }
1227     }
1228     return startPosStack.empty(); // false if parentheses don't match
1229 }
1230 
calcHistorySortKey(const QString & keyOrder,QRegExp & matchedRegExpr,const QStringList & parenthesesGroupList)1231 QString calcHistorySortKey(const QString& keyOrder, QRegExp& matchedRegExpr, const QStringList& parenthesesGroupList)
1232 {
1233     const QStringList keyOrderList = keyOrder.split(',');
1234     QString key;
1235 
1236     for(const QString& keyIt : keyOrderList)
1237     {
1238         if(keyIt.isEmpty())
1239             continue;
1240         bool bOk = false;
1241         int groupIdx = keyIt.toInt(&bOk);
1242         if(!bOk || groupIdx < 0 || groupIdx > parenthesesGroupList.size())
1243             continue;
1244         QString s = matchedRegExpr.cap(groupIdx);
1245         if(groupIdx == 0)
1246         {
1247             key += s + ' ';
1248             continue;
1249         }
1250 
1251         QString groupRegExp = parenthesesGroupList[groupIdx - 1];
1252         if(groupRegExp.indexOf('|') < 0 || groupRegExp.indexOf('(') >= 0)
1253         {
1254             bOk = false;
1255             int i = s.toInt(&bOk);
1256             if(bOk && i >= 0 && i < 10000)
1257             {
1258                 s += QString(4 - s.size(), '0'); // This should help for correct sorting of numbers.
1259             }
1260             key += s + ' ';
1261         }
1262         else
1263         {
1264             // Assume that the groupRegExp consists of something like "Jan|Feb|Mar|Apr"
1265             // s is the string that managed to match.
1266             // Now we want to know at which position it occurred. e.g. Jan=0, Feb=1, Mar=2, etc.
1267             QStringList sl = groupRegExp.split('|');
1268             int idx = sl.indexOf(s);
1269             if(idx >= 0)
1270             {
1271                 QString sIdx;
1272                 sIdx.setNum(idx);
1273                 Q_ASSERT(sIdx.size() <= 2);
1274                 sIdx += QString(2 - sIdx.size(), '0'); // Up to 99 words in the groupRegExp (more than 12 aren't expected)
1275                 key += sIdx + ' ';
1276             }
1277         }
1278     }
1279     return key;
1280 }
1281 
collectHistoryInformation(e_SrcSelector src,Diff3LineList::const_iterator & iHistoryBegin,Diff3LineList::const_iterator & iHistoryEnd,HistoryMap & historyMap,std::list<HistoryMap::iterator> & hitList)1282 void MergeResultWindow::collectHistoryInformation(
1283     e_SrcSelector src, Diff3LineList::const_iterator& iHistoryBegin, Diff3LineList::const_iterator& iHistoryEnd,
1284     HistoryMap& historyMap,
1285     std::list<HistoryMap::iterator>& hitList // list of iterators
1286 )
1287 {
1288     std::list<HistoryMap::iterator>::iterator itHitListFront = hitList.begin();
1289     Diff3LineList::const_iterator id3l = iHistoryBegin;
1290     QString historyLead;
1291     {
1292         const LineData* pld = id3l->getLineData(src);
1293 
1294         historyLead = Utils::calcHistoryLead(pld->getLine());
1295     }
1296     QRegExp historyStart(m_pOptions->m_historyStartRegExp);
1297     if(id3l == iHistoryEnd)
1298         return;
1299     ++id3l; // Skip line with "$Log ... $"
1300     QRegExp newHistoryEntry(m_pOptions->m_historyEntryStartRegExp);
1301     QStringList parenthesesGroups;
1302     findParenthesesGroups(m_pOptions->m_historyEntryStartRegExp, parenthesesGroups);
1303     QString key;
1304     MergeEditLineList melList;
1305     bool bPrevLineIsEmpty = true;
1306     bool bUseRegExp = !m_pOptions->m_historyEntryStartRegExp.isEmpty();
1307     for(; id3l != iHistoryEnd; ++id3l)
1308     {
1309         const LineData* pld = id3l->getLineData(src);
1310         if(!pld) continue;
1311 
1312         const QString& oriLine = pld->getLine();
1313         if(historyLead.isEmpty()) historyLead = Utils::calcHistoryLead(oriLine);
1314         QString sLine = oriLine.mid(historyLead.length());
1315         if((!bUseRegExp && !sLine.trimmed().isEmpty() && bPrevLineIsEmpty) || (bUseRegExp && newHistoryEntry.exactMatch(sLine)))
1316         {
1317             if(!key.isEmpty() && !melList.empty())
1318             {
1319                 // Only insert new HistoryMapEntry if key not found; in either case p.first is a valid iterator to element key.
1320                 std::pair<HistoryMap::iterator, bool> p = historyMap.insert(HistoryMap::value_type(key, HistoryMapEntry()));
1321                 HistoryMapEntry& hme = p.first->second;
1322                 if(src == e_SrcSelector::A) hme.mellA = melList;
1323                 if(src == e_SrcSelector::B) hme.mellB = melList;
1324                 if(src == e_SrcSelector::C) hme.mellC = melList;
1325                 if(p.second) // Not in list yet?
1326                 {
1327                     hitList.insert(itHitListFront, p.first);
1328                 }
1329             }
1330 
1331             if(!bUseRegExp)
1332                 key = sLine;
1333             else
1334                 key = calcHistorySortKey(m_pOptions->m_historyEntryStartSortKeyOrder, newHistoryEntry, parenthesesGroups);
1335 
1336             melList.clear();
1337             melList.push_back(MergeEditLine(id3l, src));
1338         }
1339         else if(!historyStart.exactMatch(oriLine))
1340         {
1341             melList.push_back(MergeEditLine(id3l, src));
1342         }
1343 
1344         bPrevLineIsEmpty = sLine.trimmed().isEmpty();
1345     }
1346     if(!key.isEmpty())
1347     {
1348         // Only insert new HistoryMapEntry if key not found; in either case p.first is a valid iterator to element key.
1349         std::pair<HistoryMap::iterator, bool> p = historyMap.insert(HistoryMap::value_type(key, HistoryMapEntry()));
1350         HistoryMapEntry& hme = p.first->second;
1351         if(src == e_SrcSelector::A) hme.mellA = melList;
1352         if(src == e_SrcSelector::B) hme.mellB = melList;
1353         if(src == e_SrcSelector::C) hme.mellC = melList;
1354         if(p.second) // Not in list yet?
1355         {
1356             hitList.insert(itHitListFront, p.first);
1357         }
1358     }
1359     // End of the history
1360 }
1361 
choice(bool bThreeInputs)1362 MergeEditLineList& MergeResultWindow::HistoryMapEntry::choice(bool bThreeInputs)
1363 {
1364     if(!bThreeInputs)
1365         return mellA.empty() ? mellB : mellA;
1366     else
1367     {
1368         if(mellA.empty())
1369             return mellC.empty() ? mellB : mellC; // A doesn't exist, return one that exists
1370         else if(!mellB.empty() && !mellC.empty())
1371         { // A, B and C exist
1372             return mellA;
1373         }
1374         else
1375             return mellB.empty() ? mellB : mellC; // A exists, return the one that doesn't exist
1376     }
1377 }
1378 
staysInPlace(bool bThreeInputs,Diff3LineList::const_iterator & iHistoryEnd)1379 bool MergeResultWindow::HistoryMapEntry::staysInPlace(bool bThreeInputs, Diff3LineList::const_iterator& iHistoryEnd)
1380 {
1381     // The entry should stay in place if the decision made by the automerger is correct.
1382     Diff3LineList::const_iterator& iHistoryLast = iHistoryEnd;
1383     --iHistoryLast;
1384     if(!bThreeInputs)
1385     {
1386         if(!mellA.empty() && !mellB.empty() && mellA.begin()->id3l() == mellB.begin()->id3l() &&
1387            mellA.back().id3l() == iHistoryLast && mellB.back().id3l() == iHistoryLast)
1388         {
1389             iHistoryEnd = mellA.begin()->id3l();
1390             return true;
1391         }
1392         else
1393         {
1394             return false;
1395         }
1396     }
1397     else
1398     {
1399         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)
1400         {
1401             iHistoryEnd = mellA.begin()->id3l();
1402             return true;
1403         }
1404         else
1405         {
1406             return false;
1407         }
1408     }
1409 }
1410 
slotMergeHistory()1411 void MergeResultWindow::slotMergeHistory()
1412 {
1413     Diff3LineList::const_iterator iD3LHistoryBegin;
1414     Diff3LineList::const_iterator iD3LHistoryEnd;
1415     int d3lHistoryBeginLineIdx = -1;
1416     int d3lHistoryEndLineIdx = -1;
1417 
1418     // Search for history start, history end in the diff3LineList
1419     m_pDiff3LineList->findHistoryRange(QRegExp(m_pOptions->m_historyStartRegExp), m_pldC != nullptr, iD3LHistoryBegin, iD3LHistoryEnd, d3lHistoryBeginLineIdx, d3lHistoryEndLineIdx);
1420 
1421     if(iD3LHistoryBegin != m_pDiff3LineList->end())
1422     {
1423         // Now collect the historyMap information
1424         HistoryMap historyMap;
1425         std::list<HistoryMap::iterator> hitList;
1426         if(m_pldC == nullptr)
1427         {
1428             collectHistoryInformation(e_SrcSelector::A, iD3LHistoryBegin, iD3LHistoryEnd, historyMap, hitList);
1429             collectHistoryInformation(e_SrcSelector::B, iD3LHistoryBegin, iD3LHistoryEnd, historyMap, hitList);
1430         }
1431         else
1432         {
1433             collectHistoryInformation(e_SrcSelector::A, iD3LHistoryBegin, iD3LHistoryEnd, historyMap, hitList);
1434             collectHistoryInformation(e_SrcSelector::B, iD3LHistoryBegin, iD3LHistoryEnd, historyMap, hitList);
1435             collectHistoryInformation(e_SrcSelector::C, iD3LHistoryBegin, iD3LHistoryEnd, historyMap, hitList);
1436         }
1437 
1438         Diff3LineList::const_iterator iD3LHistoryOrigEnd = iD3LHistoryEnd;
1439 
1440         bool bHistoryMergeSorting = m_pOptions->m_bHistoryMergeSorting && !m_pOptions->m_historyEntryStartSortKeyOrder.isEmpty() &&
1441                                     !m_pOptions->m_historyEntryStartRegExp.isEmpty();
1442 
1443         if(m_pOptions->m_maxNofHistoryEntries == -1)
1444         {
1445             // Remove parts from the historyMap and hitList that stay in place
1446             if(bHistoryMergeSorting)
1447             {
1448                 while(!historyMap.empty())
1449                 {
1450                     HistoryMap::iterator hMapIt = historyMap.begin();
1451                     if(hMapIt->second.staysInPlace(m_pldC != nullptr, iD3LHistoryEnd))
1452                         historyMap.erase(hMapIt);
1453                     else
1454                         break;
1455                 }
1456             }
1457             else
1458             {
1459                 while(!hitList.empty())
1460                 {
1461                     HistoryMap::iterator hMapIt = hitList.back();
1462                     if(hMapIt->second.staysInPlace(m_pldC != nullptr, iD3LHistoryEnd))
1463                         hitList.pop_back();
1464                     else
1465                         break;
1466                 }
1467             }
1468             while(iD3LHistoryOrigEnd != iD3LHistoryEnd)
1469             {
1470                 --iD3LHistoryOrigEnd;
1471                 --d3lHistoryEndLineIdx;
1472             }
1473         }
1474 
1475         MergeLineList::iterator iMLLStart = splitAtDiff3LineIdx(d3lHistoryBeginLineIdx);
1476         MergeLineList::iterator iMLLEnd = splitAtDiff3LineIdx(d3lHistoryEndLineIdx);
1477         // Now join all MergeLines in the history
1478         MergeLineList::iterator i = iMLLStart;
1479         if(i != iMLLEnd)
1480         {
1481             ++i;
1482             while(i != iMLLEnd)
1483             {
1484                 iMLLStart->join(*i);
1485                 i = m_mergeLineList.erase(i);
1486             }
1487         }
1488         iMLLStart->mergeEditLineList.clear();
1489         // Now insert the complete history into the first MergeLine of the history
1490         iMLLStart->mergeEditLineList.push_back(MergeEditLine(iD3LHistoryBegin, m_pldC == nullptr ? e_SrcSelector::B : e_SrcSelector::C));
1491         QString lead = Utils::calcHistoryLead(iD3LHistoryBegin->getString(e_SrcSelector::A));
1492         MergeEditLine mel(m_pDiff3LineList->end());
1493         mel.setString(lead);
1494         iMLLStart->mergeEditLineList.push_back(mel);
1495 
1496         int historyCount = 0;
1497         if(bHistoryMergeSorting)
1498         {
1499             // Create a sorted history
1500             HistoryMap::reverse_iterator hmit;
1501             for(hmit = historyMap.rbegin(); hmit != historyMap.rend(); ++hmit)
1502             {
1503                 if(historyCount == m_pOptions->m_maxNofHistoryEntries)
1504                     break;
1505                 ++historyCount;
1506                 HistoryMapEntry& hme = hmit->second;
1507                 MergeEditLineList& mell = hme.choice(m_pldC != nullptr);
1508                 if(!mell.empty())
1509                     iMLLStart->mergeEditLineList.splice(iMLLStart->mergeEditLineList.end(), mell, mell.begin(), mell.end());
1510             }
1511         }
1512         else
1513         {
1514             // Create history in order of appearance
1515             std::list<HistoryMap::iterator>::iterator hlit;
1516             for(hlit = hitList.begin(); hlit != hitList.end(); ++hlit)
1517             {
1518                 if(historyCount == m_pOptions->m_maxNofHistoryEntries)
1519                     break;
1520                 ++historyCount;
1521                 HistoryMapEntry& hme = (*hlit)->second;
1522                 MergeEditLineList& mell = hme.choice(m_pldC != nullptr);
1523                 if(!mell.empty())
1524                     iMLLStart->mergeEditLineList.splice(iMLLStart->mergeEditLineList.end(), mell, mell.begin(), mell.end());
1525             }
1526             // If the end of start is empty and the first line at the end is empty remove the last line of start
1527             if(!iMLLStart->mergeEditLineList.empty() && !iMLLEnd->mergeEditLineList.empty())
1528             {
1529                 QString lastLineOfStart = iMLLStart->mergeEditLineList.back().getString(m_pldA, m_pldB, m_pldC);
1530                 QString firstLineOfEnd = iMLLEnd->mergeEditLineList.front().getString(m_pldA, m_pldB, m_pldC);
1531                 if(lastLineOfStart.mid(lead.length()).trimmed().isEmpty() && firstLineOfEnd.mid(lead.length()).trimmed().isEmpty())
1532                     iMLLStart->mergeEditLineList.pop_back();
1533             }
1534         }
1535         setFastSelector(iMLLStart);
1536         update();
1537     }
1538 }
1539 
slotRegExpAutoMerge()1540 void MergeResultWindow::slotRegExpAutoMerge()
1541 {
1542     if(m_pOptions->m_autoMergeRegExp.isEmpty())
1543         return;
1544 
1545     QRegExp vcsKeywords(m_pOptions->m_autoMergeRegExp);
1546     MergeLineList::iterator i;
1547     for(i = m_mergeLineList.begin(); i != m_mergeLineList.end(); ++i)
1548     {
1549         if(i->bConflict)
1550         {
1551             Diff3LineList::const_iterator id3l = i->id3l;
1552             if(vcsKeywords.exactMatch(id3l->getString(e_SrcSelector::A)) &&
1553                vcsKeywords.exactMatch(id3l->getString(e_SrcSelector::B)) &&
1554                (m_pldC == nullptr || vcsKeywords.exactMatch(id3l->getString(e_SrcSelector::C))))
1555             {
1556                 MergeEditLine& mel = *i->mergeEditLineList.begin();
1557                 mel.setSource(m_pldC == nullptr ? e_SrcSelector::B : e_SrcSelector::C, false);
1558                 splitAtDiff3LineIdx(i->d3lLineIdx + 1);
1559             }
1560         }
1561     }
1562     update();
1563 }
1564 
1565 // This doesn't detect user modifications and should only be called after automatic merge
1566 // This will only do something for three file merge.
1567 // Irrelevant changes are those where all contributions from B are already contained in C.
1568 // Also irrelevant are conflicts automatically solved (automerge regexp and history automerge)
1569 // Precondition: The VCS-keyword would also be C.
doRelevantChangesExist()1570 bool MergeResultWindow::doRelevantChangesExist()
1571 {
1572     if(m_pldC == nullptr || m_mergeLineList.size() <= 1)
1573         return true;
1574 
1575     MergeLineList::iterator i;
1576     for(i = m_mergeLineList.begin(); i != m_mergeLineList.end(); ++i)
1577     {
1578         if((i->bConflict && i->mergeEditLineList.begin()->src() != e_SrcSelector::C) || i->srcSelect == e_SrcSelector::B)
1579         {
1580             return true;
1581         }
1582     }
1583 
1584     return false;
1585 }
1586 
1587 // Returns the iterator to the MergeLine after the split
splitAtDiff3LineIdx(int d3lLineIdx)1588 MergeLineList::iterator MergeResultWindow::splitAtDiff3LineIdx(int d3lLineIdx)
1589 {
1590     MergeLineList::iterator i;
1591     for(i = m_mergeLineList.begin(); i != m_mergeLineList.end(); ++i)
1592     {
1593         if(i->d3lLineIdx == d3lLineIdx)
1594         {
1595             // No split needed, this is the beginning of a MergeLine
1596             return i;
1597         }
1598         else if(i->d3lLineIdx > d3lLineIdx)
1599         {
1600             // The split must be in the previous MergeLine
1601             --i;
1602             MergeLine& ml = *i;
1603             MergeLine newML;
1604             ml.split(newML, d3lLineIdx);
1605             ++i;
1606             return m_mergeLineList.insert(i, newML);
1607         }
1608     }
1609     // The split must be in the previous MergeLine
1610     --i;
1611     MergeLine& ml = *i;
1612     MergeLine newML;
1613     ml.split(newML, d3lLineIdx);
1614     ++i;
1615     return m_mergeLineList.insert(i, newML);
1616 }
1617 
slotSplitDiff(int firstD3lLineIdx,int lastD3lLineIdx)1618 void MergeResultWindow::slotSplitDiff(int firstD3lLineIdx, int lastD3lLineIdx)
1619 {
1620     if(lastD3lLineIdx >= 0)
1621         splitAtDiff3LineIdx(lastD3lLineIdx + 1);
1622     setFastSelector(splitAtDiff3LineIdx(firstD3lLineIdx));
1623 }
1624 
slotJoinDiffs(int firstD3lLineIdx,int lastD3lLineIdx)1625 void MergeResultWindow::slotJoinDiffs(int firstD3lLineIdx, int lastD3lLineIdx)
1626 {
1627     MergeLineList::iterator i;
1628     MergeLineList::iterator iMLLStart = m_mergeLineList.end();
1629     MergeLineList::iterator iMLLEnd = m_mergeLineList.end();
1630     for(i = m_mergeLineList.begin(); i != m_mergeLineList.end(); ++i)
1631     {
1632         MergeLine& ml = *i;
1633         if(firstD3lLineIdx >= ml.d3lLineIdx && firstD3lLineIdx < ml.d3lLineIdx + ml.srcRangeLength)
1634         {
1635             iMLLStart = i;
1636         }
1637         if(lastD3lLineIdx >= ml.d3lLineIdx && lastD3lLineIdx < ml.d3lLineIdx + ml.srcRangeLength)
1638         {
1639             iMLLEnd = i;
1640             ++iMLLEnd;
1641             break;
1642         }
1643     }
1644 
1645     bool bJoined = false;
1646     for(i = iMLLStart; i != iMLLEnd && i != m_mergeLineList.end();)
1647     {
1648         if(i == iMLLStart)
1649         {
1650             ++i;
1651         }
1652         else
1653         {
1654             iMLLStart->join(*i);
1655             i = m_mergeLineList.erase(i);
1656             bJoined = true;
1657         }
1658     }
1659     if(bJoined)
1660     {
1661         iMLLStart->mergeEditLineList.clear();
1662         // Insert a conflict line as placeholder
1663         iMLLStart->mergeEditLineList.push_back(MergeEditLine(iMLLStart->id3l));
1664     }
1665     setFastSelector(iMLLStart);
1666 }
1667 
myUpdate(int afterMilliSecs)1668 void MergeResultWindow::myUpdate(int afterMilliSecs)
1669 {
1670     if(m_delayedDrawTimer)
1671         killTimer(m_delayedDrawTimer);
1672     m_bMyUpdate = true;
1673     m_delayedDrawTimer = startTimer(afterMilliSecs);
1674 }
1675 
timerEvent(QTimerEvent *)1676 void MergeResultWindow::timerEvent(QTimerEvent*)
1677 {
1678     killTimer(m_delayedDrawTimer);
1679     m_delayedDrawTimer = 0;
1680 
1681     if(m_bMyUpdate)
1682     {
1683         update();
1684         m_bMyUpdate = false;
1685     }
1686 
1687     if(m_scrollDeltaX != 0 || m_scrollDeltaY != 0)
1688     {
1689         m_selection.end(m_selection.getLastLine() + m_scrollDeltaY, m_selection.getLastPos() + m_scrollDeltaX);
1690         Q_EMIT scrollMergeResultWindow(m_scrollDeltaX, m_scrollDeltaY);
1691         killTimer(m_delayedDrawTimer);
1692         m_delayedDrawTimer = startTimer(50);
1693     }
1694 }
1695 
getTextLayoutForLine(int line,const QString & str,QTextLayout & textLayout)1696 QVector<QTextLayout::FormatRange> MergeResultWindow::getTextLayoutForLine(int line, const QString& str, QTextLayout& textLayout)
1697 {
1698     // tabs
1699     QTextOption textOption;
1700 #if QT_VERSION < QT_VERSION_CHECK(5, 10, 0)
1701     textOption.setTabStop(QFontMetricsF(font()).width(' ') * m_pOptions->m_tabSize);
1702 #elif QT_VERSION < QT_VERSION_CHECK(5, 12, 0)
1703     textOption.setTabStopDistance(QFontMetricsF(font()).width(' ') * m_pOptions->m_tabSize);
1704 #else
1705     textOption.setTabStopDistance(QFontMetricsF(font()).horizontalAdvance(' ') * m_pOptions->m_tabSize);
1706 #endif
1707 
1708     if(m_pOptions->m_bShowWhiteSpaceCharacters)
1709     {
1710         textOption.setFlags(QTextOption::ShowTabsAndSpaces);
1711     }
1712     textLayout.setTextOption(textOption);
1713 
1714     if(m_pOptions->m_bShowWhiteSpaceCharacters)
1715     {
1716         // This additional format is only necessary for the tab arrow
1717         QVector<QTextLayout::FormatRange> formats;
1718         QTextLayout::FormatRange formatRange;
1719         formatRange.start = 0;
1720         formatRange.length = str.length();
1721         formatRange.format.setFont(font());
1722         formats.append(formatRange);
1723         textLayout.setFormats(formats);
1724     }
1725     QVector<QTextLayout::FormatRange> selectionFormat;
1726     textLayout.beginLayout();
1727     if(m_selection.lineWithin(line))
1728     {
1729         int firstPosInText = m_selection.firstPosInLine(line);
1730         int lastPosInText = m_selection.lastPosInLine(line);
1731         int lengthInText = std::max(0, lastPosInText - firstPosInText);
1732         if(lengthInText > 0)
1733             m_selection.bSelectionContainsData = true;
1734 
1735         QTextLayout::FormatRange selection;
1736         selection.start = firstPosInText;
1737         selection.length = lengthInText;
1738         selection.format.setBackground(palette().highlight());
1739         selection.format.setForeground(palette().highlightedText().color());
1740         selectionFormat.push_back(selection);
1741     }
1742     QTextLine textLine = textLayout.createLine();
1743     textLine.setPosition(QPointF(0, fontMetrics().leading()));
1744     textLayout.endLayout();
1745     int cursorWidth = 5;
1746     if(m_pOptions->m_bRightToLeftLanguage)
1747         textLayout.setPosition(QPointF(width() - textLayout.maximumWidth() - getTextXOffset() + m_horizScrollOffset - cursorWidth, 0));
1748     else
1749         textLayout.setPosition(QPointF(getTextXOffset() - m_horizScrollOffset, 0));
1750     return selectionFormat;
1751 }
1752 
writeLine(RLPainter & p,int line,const QString & str,e_SrcSelector srcSelect,e_MergeDetails mergeDetails,int rangeMark,bool bUserModified,bool bLineRemoved,bool bWhiteSpaceConflict)1753 void MergeResultWindow::writeLine(
1754     RLPainter& p, int line, const QString& str,
1755     e_SrcSelector srcSelect, e_MergeDetails mergeDetails, int rangeMark, bool bUserModified, bool bLineRemoved, bool bWhiteSpaceConflict)
1756 {
1757     const QFontMetrics& fm = fontMetrics();
1758     int fontHeight = fm.lineSpacing();
1759     int fontAscent = fm.ascent();
1760 
1761     int topLineYOffset = 0;
1762     int xOffset = getTextXOffset();
1763 
1764     int yOffset = (line - m_firstLine) * fontHeight;
1765     if(yOffset < 0 || yOffset > height())
1766         return;
1767 
1768     yOffset += topLineYOffset;
1769 
1770     QString srcName = QChar(' ');
1771     if(bUserModified)
1772         srcName = QChar('m');
1773     else if(srcSelect == e_SrcSelector::A && mergeDetails != e_MergeDetails::eNoChange)
1774         srcName = i18n("A");
1775     else if(srcSelect == e_SrcSelector::B)
1776         srcName = i18n("B");
1777     else if(srcSelect == e_SrcSelector::C)
1778         srcName = i18n("C");
1779 
1780     if(rangeMark & 4)
1781     {
1782         p.fillRect(xOffset, yOffset, width(), fontHeight, m_pOptions->m_currentRangeBgColor);
1783     }
1784 
1785     if((srcSelect > e_SrcSelector::None || bUserModified) && !bLineRemoved)
1786     {
1787         if(!m_pOptions->m_bRightToLeftLanguage)
1788             p.setClipRect(QRectF(xOffset, 0, width() - xOffset, height()));
1789         else
1790             p.setClipRect(QRectF(0, 0, width() - xOffset, height()));
1791 
1792         int outPos = 0;
1793         QString s;
1794         int size = str.length();
1795         for(int i = 0; i < size; ++i)
1796         {
1797             int spaces = 1;
1798             if(str[i] == '\t')
1799             {
1800                 spaces = tabber(outPos, m_pOptions->m_tabSize);
1801                 for(int j = 0; j < spaces; ++j)
1802                     s += ' ';
1803             }
1804             else
1805             {
1806                 s += str[i];
1807             }
1808             outPos += spaces;
1809         }
1810 
1811         p.setPen(m_pOptions->m_fgColor);
1812 
1813         QTextLayout textLayout(str, font(), this);
1814         QVector<QTextLayout::FormatRange> selectionFormat = getTextLayoutForLine(line, str, textLayout);
1815         textLayout.draw(&p, QPointF(0, yOffset), selectionFormat);
1816 
1817         if(line == m_cursorYPos)
1818         {
1819             m_cursorXPixelPos = qCeil(textLayout.lineAt(0).cursorToX(m_cursorXPos));
1820             if(m_pOptions->m_bRightToLeftLanguage)
1821                 m_cursorXPixelPos += qCeil(textLayout.position().x() - m_horizScrollOffset);
1822         }
1823 
1824         p.setClipping(false);
1825 
1826         p.setPen(m_pOptions->m_fgColor);
1827 
1828         p.drawText(1, yOffset + fontAscent, srcName, true);
1829     }
1830     else if(bLineRemoved)
1831     {
1832         p.setPen(m_pOptions->m_colorForConflict);
1833         p.drawText(xOffset, yOffset + fontAscent, i18n("<No src line>"));
1834         p.drawText(1, yOffset + fontAscent, srcName);
1835         if(m_cursorYPos == line) m_cursorXPos = 0;
1836     }
1837     else if(srcSelect == e_SrcSelector::None)
1838     {
1839         p.setPen(m_pOptions->m_colorForConflict);
1840         if(bWhiteSpaceConflict)
1841             p.drawText(xOffset, yOffset + fontAscent, i18n("<Merge Conflict (Whitespace only)>"));
1842         else
1843             p.drawText(xOffset, yOffset + fontAscent, i18n("<Merge Conflict>"));
1844         p.drawText(1, yOffset + fontAscent, "?");
1845         if(m_cursorYPos == line) m_cursorXPos = 0;
1846     }
1847     else
1848         Q_ASSERT(true);
1849 
1850     xOffset -= Utils::getHorizontalAdvance(fm, '0');
1851     p.setPen(m_pOptions->m_fgColor);
1852     if(rangeMark & 1) // begin mark
1853     {
1854         p.drawLine(xOffset, yOffset + 1, xOffset, yOffset + fontHeight / 2);
1855         p.drawLine(xOffset, yOffset + 1, xOffset - 2, yOffset + 1);
1856     }
1857     else
1858     {
1859         p.drawLine(xOffset, yOffset, xOffset, yOffset + fontHeight / 2);
1860     }
1861 
1862     if(rangeMark & 2) // end mark
1863     {
1864         p.drawLine(xOffset, yOffset + fontHeight / 2, xOffset, yOffset + fontHeight - 1);
1865         p.drawLine(xOffset, yOffset + fontHeight - 1, xOffset - 2, yOffset + fontHeight - 1);
1866     }
1867     else
1868     {
1869         p.drawLine(xOffset, yOffset + fontHeight / 2, xOffset, yOffset + fontHeight);
1870     }
1871 
1872     if(rangeMark & 4)
1873     {
1874         p.fillRect(xOffset + 3, yOffset, 3, fontHeight, m_pOptions->m_fgColor);
1875         /*      p.setPen( blue );
1876       p.drawLine( xOffset+2, yOffset, xOffset+2, yOffset+fontHeight-1 );
1877       p.drawLine( xOffset+3, yOffset, xOffset+3, yOffset+fontHeight-1 );*/
1878     }
1879 }
1880 
setPaintingAllowed(bool bPaintingAllowed)1881 void MergeResultWindow::setPaintingAllowed(bool bPaintingAllowed)
1882 {
1883     setUpdatesEnabled(bPaintingAllowed);
1884     if(!bPaintingAllowed)
1885     {
1886         m_currentMergeLineIt = m_mergeLineList.end();
1887         reset();
1888     }
1889     else
1890         update();
1891 }
1892 
paintEvent(QPaintEvent *)1893 void MergeResultWindow::paintEvent(QPaintEvent*)
1894 {
1895     if(m_pDiff3LineList == nullptr)
1896         return;
1897 
1898     bool bOldSelectionContainsData = m_selection.selectionContainsData();
1899     const QFontMetrics& fm = fontMetrics();
1900     int fontWidth = Utils::getHorizontalAdvance(fm, '0');
1901 
1902     if(!m_bCursorUpdate) // Don't redraw everything for blinking cursor?
1903     {
1904         m_selection.bSelectionContainsData = false;
1905         const auto dpr = devicePixelRatioF();
1906         if(size() * dpr != m_pixmap.size())
1907         {
1908             m_pixmap = QPixmap(size() * dpr);
1909             m_pixmap.setDevicePixelRatio(dpr);
1910         }
1911 
1912         RLPainter p(&m_pixmap, m_pOptions->m_bRightToLeftLanguage, width(), fontWidth);
1913         p.setFont(font());
1914         p.QPainter::fillRect(rect(), m_pOptions->m_bgColor);
1915 
1916         //int visibleLines = height() / fontHeight;
1917 
1918         int lastVisibleLine = m_firstLine + getNofVisibleLines() + 5;
1919         LineRef line = 0;
1920         MergeLineList::iterator mlIt = m_mergeLineList.begin();
1921         for(mlIt = m_mergeLineList.begin(); mlIt != m_mergeLineList.end(); ++mlIt)
1922         {
1923             MergeLine& ml = *mlIt;
1924             if(line > lastVisibleLine || line + ml.mergeEditLineList.size() < m_firstLine)
1925             {
1926                 line += ml.mergeEditLineList.size();
1927             }
1928             else
1929             {
1930                 MergeEditLineList::iterator melIt;
1931                 for(melIt = ml.mergeEditLineList.begin(); melIt != ml.mergeEditLineList.end(); ++melIt)
1932                 {
1933                     if(line >= m_firstLine && line <= lastVisibleLine)
1934                     {
1935                         MergeEditLine& mel = *melIt;
1936                         MergeEditLineList::iterator melIt1 = melIt;
1937                         ++melIt1;
1938 
1939                         int rangeMark = 0;
1940                         if(melIt == ml.mergeEditLineList.begin()) rangeMark |= 1; // Begin range mark
1941                         if(melIt1 == ml.mergeEditLineList.end()) rangeMark |= 2;  // End range mark
1942 
1943                         if(mlIt == m_currentMergeLineIt) rangeMark |= 4; // Mark of the current line
1944 
1945                         QString s;
1946                         s = mel.getString(m_pldA, m_pldB, m_pldC);
1947 
1948                         writeLine(p, line, s, mel.src(), ml.mergeDetails, rangeMark,
1949                                   mel.isModified(), mel.isRemoved(), ml.bWhiteSpaceConflict);
1950                     }
1951                     ++line;
1952                 }
1953             }
1954         }
1955 
1956         if(line != m_nofLines)
1957         {
1958             m_nofLines = line;
1959 
1960             Q_EMIT resizeSignal();
1961         }
1962 
1963         p.end();
1964     }
1965 
1966     QPainter painter(this);
1967 
1968     if(!m_bCursorUpdate)
1969         painter.drawPixmap(0, 0, m_pixmap);
1970     else
1971     {
1972         painter.drawPixmap(0, 0, m_pixmap); // Draw everything. (Internally cursor rect is clipped anyway.)
1973         m_bCursorUpdate = false;
1974     }
1975 
1976     if(m_bCursorOn && hasFocus() && m_cursorYPos >= m_firstLine)
1977     {
1978         painter.setPen(m_pOptions->m_fgColor);
1979 
1980         QString str = getString(m_cursorYPos);
1981         QTextLayout textLayout(str, font(), this);
1982         getTextLayoutForLine(m_cursorYPos, str, textLayout);
1983         textLayout.drawCursor(&painter, QPointF(0, (m_cursorYPos - m_firstLine) * fontMetrics().lineSpacing()), m_cursorXPos);
1984     }
1985 
1986     painter.end();
1987 
1988     if(!bOldSelectionContainsData && m_selection.selectionContainsData())
1989         Q_EMIT newSelection();
1990 }
1991 
updateSourceMask()1992 void MergeResultWindow::updateSourceMask()
1993 {
1994     int srcMask = 0;
1995     int enabledMask = 0;
1996     if(!hasFocus() || m_pDiff3LineList == nullptr || !updatesEnabled() || m_currentMergeLineIt == m_mergeLineList.end())
1997     {
1998         srcMask = 0;
1999         enabledMask = 0;
2000     }
2001     else
2002     {
2003         enabledMask = m_pldC == nullptr ? 3 : 7;
2004         MergeLine& ml = *m_currentMergeLineIt;
2005 
2006         srcMask = 0;
2007         bool bModified = false;
2008         MergeEditLineList::iterator melIt;
2009         for(melIt = ml.mergeEditLineList.begin(); melIt != ml.mergeEditLineList.end(); ++melIt)
2010         {
2011             MergeEditLine& mel = *melIt;
2012             if(mel.src() == e_SrcSelector::A) srcMask |= 1;
2013             if(mel.src() == e_SrcSelector::B) srcMask |= 2;
2014             if(mel.src() == e_SrcSelector::C) srcMask |= 4;
2015             if(mel.isModified() || !mel.isEditableText()) bModified = true;
2016         }
2017 
2018         if(ml.mergeDetails == e_MergeDetails::eNoChange)
2019         {
2020             srcMask = 0;
2021             enabledMask = bModified ? 1 : 0;
2022         }
2023     }
2024 
2025     Q_EMIT sourceMask(srcMask, enabledMask);
2026 }
2027 
focusInEvent(QFocusEvent * e)2028 void MergeResultWindow::focusInEvent(QFocusEvent* e)
2029 {
2030     updateSourceMask();
2031     QWidget::focusInEvent(e);
2032 }
2033 
convertToLine(int y)2034 LineRef MergeResultWindow::convertToLine(int y)
2035 {
2036     const QFontMetrics& fm = fontMetrics();
2037     int fontHeight = fm.lineSpacing();
2038     int topLineYOffset = 0;
2039 
2040     int yOffset = topLineYOffset - m_firstLine * fontHeight;
2041 
2042     LineRef line = std::min((y - yOffset) / fontHeight, m_nofLines - 1);
2043     return line;
2044 }
2045 
mousePressEvent(QMouseEvent * e)2046 void MergeResultWindow::mousePressEvent(QMouseEvent* e)
2047 {
2048     m_bCursorOn = true;
2049 
2050     int xOffset = getTextXOffset();
2051 
2052     LineRef line = convertToLine(e->y());
2053     QString s = getString(line);
2054     QTextLayout textLayout(s, font(), this);
2055     getTextLayoutForLine(line, s, textLayout);
2056     QtNumberType pos = textLayout.lineAt(0).xToCursor(e->x() - textLayout.position().x());
2057 
2058     bool bLMB = e->button() == Qt::LeftButton;
2059     bool bMMB = e->button() == Qt::MiddleButton;
2060     bool bRMB = e->button() == Qt::RightButton;
2061 
2062     if((bLMB && (e->x() < xOffset)) || bRMB) // Fast range selection
2063     {
2064         m_cursorXPos = 0;
2065         m_cursorOldXPixelPos = 0;
2066         m_cursorYPos = std::max((LineRef::LineType)line, 0);
2067         int l = 0;
2068         MergeLineList::iterator i = m_mergeLineList.begin();
2069         for(i = m_mergeLineList.begin(); i != m_mergeLineList.end(); ++i)
2070         {
2071             if(l == line)
2072                 break;
2073 
2074             l += i->mergeEditLineList.size();
2075             if(l > line)
2076                 break;
2077         }
2078         m_selection.reset(); // Disable current selection
2079 
2080         m_bCursorOn = true;
2081         setFastSelector(i);
2082 
2083         if(bRMB)
2084         {
2085             Q_EMIT showPopupMenu(QCursor::pos());
2086         }
2087     }
2088     else if(bLMB) // Normal cursor placement
2089     {
2090         pos = std::max(pos, 0);
2091         line = std::max((LineRef::LineType)line, 0);
2092         if(e->QInputEvent::modifiers() & Qt::ShiftModifier)
2093         {
2094             if(!m_selection.isValidFirstLine())
2095                 m_selection.start(line, pos);
2096             m_selection.end(line, pos);
2097         }
2098         else
2099         {
2100             // Selection
2101             m_selection.reset();
2102             m_selection.start(line, pos);
2103             m_selection.end(line, pos);
2104         }
2105         m_cursorXPos = pos;
2106         m_cursorXPixelPos = qCeil(textLayout.lineAt(0).cursorToX(pos));
2107         if(m_pOptions->m_bRightToLeftLanguage)
2108             m_cursorXPixelPos += qCeil(textLayout.position().x() - m_horizScrollOffset);
2109         m_cursorOldXPixelPos = m_cursorXPixelPos;
2110         m_cursorYPos = line;
2111 
2112         update();
2113         //showStatusLine( line, m_winIdx, m_pFilename, m_pDiff3LineList, m_pStatusBar );
2114     }
2115     else if(bMMB) // Paste clipboard
2116     {
2117         pos = std::max(pos, 0);
2118         line = std::max((LineRef::LineType)line, 0);
2119 
2120         m_selection.reset();
2121         m_cursorXPos = pos;
2122         m_cursorOldXPixelPos = m_cursorXPixelPos;
2123         m_cursorYPos = line;
2124 
2125         pasteClipboard(true);
2126     }
2127 }
2128 
mouseDoubleClickEvent(QMouseEvent * e)2129 void MergeResultWindow::mouseDoubleClickEvent(QMouseEvent* e)
2130 {
2131     if(e->button() == Qt::LeftButton)
2132     {
2133         LineRef line = convertToLine(e->y());
2134         QString s = getString(line);
2135         QTextLayout textLayout(s, font(), this);
2136         getTextLayoutForLine(line, s, textLayout);
2137         int pos = textLayout.lineAt(0).xToCursor(e->x() - textLayout.position().x());
2138         m_cursorXPos = pos;
2139         m_cursorOldXPixelPos = m_cursorXPixelPos;
2140         m_cursorYPos = line;
2141 
2142         if(!s.isEmpty())
2143         {
2144             int pos1, pos2;
2145 
2146             Utils::calcTokenPos(s, pos, pos1, pos2);
2147 
2148             resetSelection();
2149             m_selection.start(line, pos1);
2150             m_selection.end(line, pos2);
2151 
2152             update();
2153             // Q_EMIT selectionEnd() happens in the mouseReleaseEvent.
2154         }
2155     }
2156 }
2157 
mouseReleaseEvent(QMouseEvent * e)2158 void MergeResultWindow::mouseReleaseEvent(QMouseEvent* e)
2159 {
2160     if(e->button() == Qt::LeftButton)
2161     {
2162         if(m_delayedDrawTimer)
2163         {
2164             killTimer(m_delayedDrawTimer);
2165             m_delayedDrawTimer = 0;
2166         }
2167 
2168         if(m_selection.isValidFirstLine())
2169         {
2170             Q_EMIT selectionEnd();
2171         }
2172 
2173         Q_EMIT updateAvailabilities();
2174     }
2175 }
2176 
mouseMoveEvent(QMouseEvent * e)2177 void MergeResultWindow::mouseMoveEvent(QMouseEvent* e)
2178 {
2179     LineRef line = convertToLine(e->y());
2180     QString s = getString(line);
2181     QTextLayout textLayout(s, font(), this);
2182     getTextLayoutForLine(line, s, textLayout);
2183     int pos = textLayout.lineAt(0).xToCursor(e->x() - textLayout.position().x());
2184     m_cursorXPos = pos;
2185     m_cursorOldXPixelPos = m_cursorXPixelPos;
2186     m_cursorYPos = line;
2187     if(m_selection.isValidFirstLine())
2188     {
2189         m_selection.end(line, pos);
2190         myUpdate(0);
2191 
2192         //showStatusLine( line, m_winIdx, m_pFilename, m_pDiff3LineList, m_pStatusBar );
2193 
2194         // Scroll because mouse moved out of the window
2195         const QFontMetrics& fm = fontMetrics();
2196         int fontWidth = Utils::getHorizontalAdvance(fm, '0');
2197         int topLineYOffset = 0;
2198         int deltaX = 0;
2199         int deltaY = 0;
2200         if(!m_pOptions->m_bRightToLeftLanguage)
2201         {
2202             if(e->x() < getTextXOffset()) deltaX = -1;
2203             if(e->x() > width()) deltaX = +1;
2204         }
2205         else
2206         {
2207             if(e->x() > width() - 1 - getTextXOffset()) deltaX = -1;
2208             if(e->x() < fontWidth) deltaX = +1;
2209         }
2210         if(e->y() < topLineYOffset) deltaY = -1;
2211         if(e->y() > height()) deltaY = +1;
2212         m_scrollDeltaX = deltaX;
2213         m_scrollDeltaY = deltaY;
2214         if(deltaX != 0 || deltaY != 0)
2215         {
2216             Q_EMIT scrollMergeResultWindow(deltaX, deltaY);
2217         }
2218     }
2219 }
2220 
slotCursorUpdate()2221 void MergeResultWindow::slotCursorUpdate()
2222 {
2223     m_cursorTimer.stop();
2224     m_bCursorOn = !m_bCursorOn;
2225 
2226     if(isVisible())
2227     {
2228         m_bCursorUpdate = true;
2229 
2230         const QFontMetrics& fm = fontMetrics();
2231         int topLineYOffset = 0;
2232         int yOffset = (m_cursorYPos - m_firstLine) * fm.lineSpacing() + topLineYOffset;
2233 
2234         repaint(0, yOffset, width(), fm.lineSpacing() + 2);
2235 
2236         m_bCursorUpdate = false;
2237     }
2238 
2239     m_cursorTimer.start(500);
2240 }
2241 
wheelEvent(QWheelEvent * pWheelEvent)2242 void MergeResultWindow::wheelEvent(QWheelEvent* pWheelEvent)
2243 {
2244     QPoint delta = pWheelEvent->angleDelta();
2245     //Block diagonal scrolling easily generated unintentionally with track pads.
2246     if(delta.y() != 0 && abs(delta.y()) > abs(delta.x()) && mVScrollBar != nullptr)
2247     {
2248         pWheelEvent->accept();
2249         QCoreApplication::postEvent(mVScrollBar, new QWheelEvent(*pWheelEvent));
2250     }
2251 }
2252 
event(QEvent * e)2253 bool MergeResultWindow::event(QEvent* e)
2254 {
2255     if(e->type() == QEvent::KeyPress)
2256     {
2257         QKeyEvent* ke = static_cast<QKeyEvent*>(e);
2258         if(ke->key() == Qt::Key_Tab)
2259         {
2260             // special tab handling here to avoid moving focus
2261             keyPressEvent(ke);
2262             return true;
2263         }
2264     }
2265     return QWidget::event(e);
2266 }
keyPressEvent(QKeyEvent * e)2267 void MergeResultWindow::keyPressEvent(QKeyEvent* e)
2268 {
2269     int y = m_cursorYPos;
2270     MergeLineList::iterator mlIt;
2271     MergeEditLineList::iterator melIt;
2272     if(!calcIteratorFromLineNr(y, mlIt, melIt))
2273     {
2274         // no data loaded or y out of bounds
2275         e->ignore();
2276         return;
2277     }
2278 
2279     QString str = melIt->getString(m_pldA, m_pldB, m_pldC);
2280     int x = m_cursorXPos;
2281 
2282     QTextLayout textLayoutOrig(str, font(), this);
2283     getTextLayoutForLine(y, str, textLayoutOrig);
2284 
2285     bool bCtrl = (e->QInputEvent::modifiers() & Qt::ControlModifier) != 0;
2286     bool bShift = (e->QInputEvent::modifiers() & Qt::ShiftModifier) != 0;
2287 #ifdef Q_OS_WIN
2288     bool bAlt = (e->QInputEvent::modifiers() & Qt::AltModifier) != 0;
2289     if(bCtrl && bAlt)
2290     {
2291         bCtrl = false;
2292         bAlt = false;
2293     } // AltGr-Key pressed.
2294 #endif
2295 
2296     bool bYMoveKey = false;
2297     // Special keys
2298     switch(e->key())
2299     {
2300         case Qt::Key_Escape:
2301         //case Qt::Key_Tab:
2302         case Qt::Key_Backtab:
2303             break;
2304         case Qt::Key_Delete: {
2305             if(deleteSelection2(str, x, y, mlIt, melIt) || !melIt->isEditableText()) break;
2306 
2307             if(x >= str.length())
2308             {
2309                 if(y < m_nofLines - 1)
2310                 {
2311                     setModified();
2312                     MergeLineList::iterator mlIt1;
2313                     MergeEditLineList::iterator melIt1;
2314                     if(calcIteratorFromLineNr(y + 1, mlIt1, melIt1) && melIt1->isEditableText())
2315                     {
2316                         QString s2 = melIt1->getString(m_pldA, m_pldB, m_pldC);
2317                         melIt->setString(str + s2);
2318 
2319                         // Remove the line
2320                         if(mlIt1->mergeEditLineList.size() > 1)
2321                             mlIt1->mergeEditLineList.erase(melIt1);
2322                         else
2323                             melIt1->setRemoved();
2324                     }
2325                 }
2326             }
2327             else
2328             {
2329                 QString s = str.left(x);
2330                 s += str.midRef(x + 1);
2331                 melIt->setString(s);
2332                 setModified();
2333             }
2334             break;
2335         }
2336         case Qt::Key_Backspace: {
2337             if(deleteSelection2(str, x, y, mlIt, melIt)) break;
2338             if(!melIt->isEditableText()) break;
2339             if(x == 0)
2340             {
2341                 if(y > 0)
2342                 {
2343                     setModified();
2344                     MergeLineList::iterator mlIt1;
2345                     MergeEditLineList::iterator melIt1;
2346                     if(calcIteratorFromLineNr(y - 1, mlIt1, melIt1) && melIt1->isEditableText())
2347                     {
2348                         QString s1 = melIt1->getString(m_pldA, m_pldB, m_pldC);
2349                         melIt1->setString(s1 + str);
2350 
2351                         // Remove the previous line
2352                         if(mlIt->mergeEditLineList.size() > 1)
2353                             mlIt->mergeEditLineList.erase(melIt);
2354                         else
2355                             melIt->setRemoved();
2356 
2357                         --y;
2358                         x = str.length();
2359                     }
2360                 }
2361             }
2362             else
2363             {
2364                 QString s = str.left(x - 1);
2365                 s += str.midRef(x);
2366                 --x;
2367                 melIt->setString(s);
2368                 setModified();
2369             }
2370             break;
2371         }
2372         case Qt::Key_Return:
2373         case Qt::Key_Enter: {
2374             if(!melIt->isEditableText()) break;
2375             deleteSelection2(str, x, y, mlIt, melIt);
2376             setModified();
2377             QString indentation;
2378             if(m_pOptions->m_bAutoIndentation)
2379             { // calc last indentation
2380                 MergeLineList::iterator mlIt1 = mlIt;
2381                 MergeEditLineList::iterator melIt1 = melIt;
2382                 for(;;)
2383                 {
2384                     const QString s = melIt1->getString(m_pldA, m_pldB, m_pldC);
2385                     if(!s.isEmpty())
2386                     {
2387                         int i;
2388                         for(i = 0; i < s.length(); ++i)
2389                         {
2390                             if(s[i] != ' ' && s[i] != '\t') break;
2391                         }
2392                         if(i < s.length())
2393                         {
2394                             indentation = s.left(i);
2395                             break;
2396                         }
2397                     }
2398                     // Go back one line
2399                     if(melIt1 != mlIt1->mergeEditLineList.begin())
2400                         --melIt1;
2401                     else
2402                     {
2403                         if(mlIt1 == m_mergeLineList.begin()) break;
2404                         --mlIt1;
2405                         melIt1 = mlIt1->mergeEditLineList.end();
2406                         --melIt1;
2407                     }
2408                 }
2409             }
2410             MergeEditLine mel(mlIt->id3l); // Associate every mel with an id3l, even if not really valid.
2411             mel.setString(indentation + str.mid(x));
2412 
2413             if(x < str.length()) // Cut off the old line.
2414             {
2415                 // Since ps possibly points into melIt->str, first copy it into a temporary.
2416                 QString temp = str.left(x);
2417                 melIt->setString(temp);
2418             }
2419 
2420             ++melIt;
2421             mlIt->mergeEditLineList.insert(melIt, mel);
2422             x = indentation.length();
2423             ++y;
2424             break;
2425         }
2426         case Qt::Key_Insert:
2427             m_bInsertMode = !m_bInsertMode;
2428             break;
2429         case Qt::Key_Pause:
2430         case Qt::Key_Print:
2431         case Qt::Key_SysReq:
2432             break;
2433         case Qt::Key_Home:
2434             x = 0;
2435             if(bCtrl)
2436             {
2437                 y = 0;
2438             }
2439             break; // cursor movement
2440         case Qt::Key_End:
2441             x = TYPE_MAX(int);
2442             if(bCtrl)
2443             {
2444                 y = TYPE_MAX(int);
2445             }
2446             break;
2447 
2448         case Qt::Key_Left:
2449         case Qt::Key_Right:
2450             if((e->key() == Qt::Key_Left) != m_pOptions->m_bRightToLeftLanguage)
2451             {
2452                 if(!bCtrl)
2453                 {
2454                     int newX = textLayoutOrig.previousCursorPosition(x);
2455                     if(newX == x && y > 0)
2456                     {
2457                         --y;
2458                         x = TYPE_MAX(int);
2459                     }
2460                     else
2461                     {
2462                         x = newX;
2463                     }
2464                 }
2465                 else
2466                 {
2467                     while(x > 0 && (str[x - 1] == ' ' || str[x - 1] == '\t'))
2468                     {
2469                         int newX = textLayoutOrig.previousCursorPosition(x);
2470                         if(newX == x) break;
2471                         x = newX;
2472                     }
2473                     while(x > 0 && (str[x - 1] != ' ' && str[x - 1] != '\t'))
2474                     {
2475                         int newX = textLayoutOrig.previousCursorPosition(x);
2476                         if(newX == x) break;
2477                         x = newX;
2478                     }
2479                 }
2480             }
2481             else
2482             {
2483                 if(!bCtrl)
2484                 {
2485                     int newX = textLayoutOrig.nextCursorPosition(x);
2486                     if(newX == x && y < m_nofLines - 1)
2487                     {
2488                         ++y;
2489                         x = 0;
2490                     }
2491                     else
2492                     {
2493                         x = newX;
2494                     }
2495                 }
2496                 else
2497                 {
2498                     while(x < str.length() && (str[x] == ' ' || str[x] == '\t'))
2499                     {
2500                         int newX = textLayoutOrig.nextCursorPosition(x);
2501                         if(newX == x) break;
2502                         x = newX;
2503                     }
2504                     while(x < str.length() && (str[x] != ' ' && str[x] != '\t'))
2505                     {
2506                         int newX = textLayoutOrig.nextCursorPosition(x);
2507                         if(newX == x) break;
2508                         x = newX;
2509                     }
2510                 }
2511             }
2512             break;
2513 
2514         case Qt::Key_Up:
2515             if(!bCtrl)
2516             {
2517                 --y;
2518                 bYMoveKey = true;
2519             }
2520             break;
2521         case Qt::Key_Down:
2522             if(!bCtrl)
2523             {
2524                 ++y;
2525                 bYMoveKey = true;
2526             }
2527             break;
2528         case Qt::Key_PageUp:
2529             if(!bCtrl)
2530             {
2531                 y -= getNofVisibleLines();
2532                 bYMoveKey = true;
2533             }
2534             break;
2535         case Qt::Key_PageDown:
2536             if(!bCtrl)
2537             {
2538                 y += getNofVisibleLines();
2539                 bYMoveKey = true;
2540             }
2541             break;
2542         default: {
2543             QString t = e->text();
2544             if(t.isEmpty() || bCtrl)
2545             {
2546                 e->ignore();
2547                 return;
2548             }
2549             if(!melIt->isEditableText()) break;
2550             deleteSelection2(str, x, y, mlIt, melIt);
2551 
2552             setModified();
2553             // Characters to insert
2554             QString s = str;
2555             if(t[0] == '\t' && m_pOptions->m_bReplaceTabs)
2556             {
2557                 int spaces = (m_cursorXPos / m_pOptions->m_tabSize + 1) * m_pOptions->m_tabSize - m_cursorXPos;
2558                 t.fill(' ', spaces);
2559             }
2560             if(m_bInsertMode)
2561                 s.insert(x, t);
2562             else
2563                 s.replace(x, t.length(), t);
2564 
2565             melIt->setString(s);
2566             x += t.length();
2567             bShift = false;
2568         } // default case
2569     } // switch(e->key())
2570 
2571 
2572     y = qBound(0, y, m_nofLines - 1);
2573 
2574     str = calcIteratorFromLineNr(y, mlIt, melIt)
2575             ? melIt->getString(m_pldA, m_pldB, m_pldC)
2576             : QString();
2577 
2578     x = qBound(0, x, (int)str.length());
2579 
2580     int newFirstLine = m_firstLine;
2581     int newHorizScrollOffset = m_horizScrollOffset;
2582 
2583     if(y < m_firstLine)
2584         newFirstLine = y;
2585     else if(y > m_firstLine + getNofVisibleLines())
2586         newFirstLine = y - getNofVisibleLines();
2587 
2588     QTextLayout textLayout(str, font(), this);
2589     getTextLayoutForLine(m_cursorYPos, str, textLayout);
2590 
2591     // try to preserve cursor x pixel position when moving to another line
2592     if(bYMoveKey)
2593     {
2594         if(m_pOptions->m_bRightToLeftLanguage)
2595             x = textLayout.lineAt(0).xToCursor(m_cursorOldXPixelPos - (textLayout.position().x() - m_horizScrollOffset));
2596         else
2597             x = textLayout.lineAt(0).xToCursor(m_cursorOldXPixelPos);
2598     }
2599 
2600     m_cursorXPixelPos = qCeil(textLayout.lineAt(0).cursorToX(x));
2601     int hF = 1; // horizontal factor
2602     if(m_pOptions->m_bRightToLeftLanguage)
2603     {
2604         m_cursorXPixelPos += qCeil(textLayout.position().x() - m_horizScrollOffset);
2605         hF = -1;
2606     }
2607     int cursorWidth = 5;
2608     if(m_cursorXPixelPos < hF * m_horizScrollOffset)
2609         newHorizScrollOffset = hF * m_cursorXPixelPos;
2610     else if(m_cursorXPixelPos > hF * m_horizScrollOffset + getVisibleTextAreaWidth() - cursorWidth)
2611         newHorizScrollOffset = hF * (m_cursorXPixelPos - (getVisibleTextAreaWidth() - cursorWidth));
2612 
2613     int newCursorX = x;
2614     if(bShift)
2615     {
2616         if(!m_selection.isValidFirstLine())
2617             m_selection.start(m_cursorYPos, m_cursorXPos);
2618 
2619         m_selection.end(y, newCursorX);
2620     }
2621     else
2622         m_selection.reset();
2623 
2624     m_cursorYPos = y;
2625     m_cursorXPos = newCursorX;
2626 
2627     // TODO if width of current line exceeds the current maximum width then force recalculating the scrollbars
2628     if(textLayout.maximumWidth() > getMaxTextWidth())
2629     {
2630         m_maxTextWidth = qCeil(textLayout.maximumWidth());
2631         Q_EMIT resizeSignal();
2632     }
2633     if(!bYMoveKey)
2634         m_cursorOldXPixelPos = m_cursorXPixelPos;
2635 
2636     m_bCursorOn = true;
2637     m_cursorTimer.start(500);
2638 
2639     Q_EMIT updateAvailabilities();
2640     update();
2641     if(newFirstLine != m_firstLine || newHorizScrollOffset != m_horizScrollOffset)
2642     {
2643         Q_EMIT scrollMergeResultWindow(newHorizScrollOffset - m_horizScrollOffset, newFirstLine - m_firstLine);
2644         return;
2645     }
2646 }
2647 
2648 /**
2649  * Determine MergeLine and MergeEditLine from line number
2650  *
2651  * @param       line
2652  *              line number to look up
2653  * @param[out]  mlIt
2654  *              iterator to merge-line
2655  *              or m_mergeLineList.end() if not available
2656  * @param[out]  melIt
2657  *              iterator to MergeEditLine
2658  *              or mlIt->mergeEditLineList.end() if not available
2659  *              @warning uninitialized if mlIt is not available!
2660  * @return      whether line is available;
2661  *              when true, @p mlIt and @p melIt are set to valid iterators
2662  */
calcIteratorFromLineNr(int line,MergeLineList::iterator & mlIt,MergeEditLineList::iterator & melIt)2663 bool MergeResultWindow::calcIteratorFromLineNr(
2664     int line,
2665     MergeLineList::iterator& mlIt,
2666     MergeEditLineList::iterator& melIt)
2667 {
2668     for(mlIt = m_mergeLineList.begin(); mlIt != m_mergeLineList.end(); ++mlIt)
2669     {
2670         MergeLine& ml = *mlIt;
2671         if(line > ml.mergeEditLineList.size())
2672         {
2673             line -= ml.mergeEditLineList.size();
2674         }
2675         else
2676         {
2677             for(melIt = ml.mergeEditLineList.begin(); melIt != ml.mergeEditLineList.end(); ++melIt)
2678             {
2679                 --line;
2680                 if(line < 0) return true;
2681             }
2682         }
2683     }
2684     return false;
2685 }
2686 
getSelection()2687 QString MergeResultWindow::getSelection()
2688 {
2689     QString selectionString;
2690 
2691     int line = 0;
2692     MergeLineList::iterator mlIt = m_mergeLineList.begin();
2693     for(mlIt = m_mergeLineList.begin(); mlIt != m_mergeLineList.end(); ++mlIt)
2694     {
2695         MergeLine& ml = *mlIt;
2696         MergeEditLineList::iterator melIt;
2697         for(melIt = ml.mergeEditLineList.begin(); melIt != ml.mergeEditLineList.end(); ++melIt)
2698         {
2699             MergeEditLine& mel = *melIt;
2700 
2701             if(m_selection.lineWithin(line))
2702             {
2703                 int outPos = 0;
2704                 if(mel.isEditableText())
2705                 {
2706                     const QString str = mel.getString(m_pldA, m_pldB, m_pldC);
2707 
2708                     // Consider tabs
2709 
2710                     for(int i = 0; i < str.length(); ++i)
2711                     {
2712                         int spaces = 1;
2713                         if(str[i] == '\t')
2714                         {
2715                             spaces = tabber(outPos, m_pOptions->m_tabSize);
2716                         }
2717 
2718                         if(m_selection.within(line, outPos))
2719                         {
2720                             selectionString += str[i];
2721                         }
2722 
2723                         outPos += spaces;
2724                     }
2725                 }
2726                 else if(mel.isConflict())
2727                 {
2728                     selectionString += i18n("<Merge Conflict>");
2729                 }
2730 
2731                 if(m_selection.within(line, outPos))
2732                 {
2733 #ifdef Q_OS_WIN
2734                     selectionString += '\r';
2735 #endif
2736                     selectionString += '\n';
2737                 }
2738             }
2739 
2740             ++line;
2741         }
2742     }
2743 
2744     return selectionString;
2745 }
2746 
deleteSelection2(QString & s,int & x,int & y,MergeLineList::iterator & mlIt,MergeEditLineList::iterator & melIt)2747 bool MergeResultWindow::deleteSelection2(QString& s, int& x, int& y,
2748                                          MergeLineList::iterator& mlIt, MergeEditLineList::iterator& melIt)
2749 {
2750     if(m_selection.selectionContainsData())
2751     {
2752         Q_ASSERT(m_selection.isValidFirstLine());
2753         deleteSelection();
2754         y = m_cursorYPos;
2755         if(!calcIteratorFromLineNr(y, mlIt, melIt))
2756         {
2757             // deleteSelection() should never remove or empty the first line, so
2758             // resolving m_cursorYPos shall always succeed
2759             Q_ASSERT(false);
2760         }
2761 
2762         s = melIt->getString(m_pldA, m_pldB, m_pldC);
2763         x = m_cursorXPos;
2764         return true;
2765     }
2766 
2767     return false;
2768 }
2769 
deleteSelection()2770 void MergeResultWindow::deleteSelection()
2771 {
2772     if(!m_selection.selectionContainsData())
2773     {
2774         return;
2775     }
2776     Q_ASSERT(m_selection.isValidFirstLine());
2777 
2778     setModified();
2779 
2780     LineRef line = 0;
2781     MergeLineList::iterator mlItFirst;
2782     MergeEditLineList::iterator melItFirst;
2783     QString firstLineString;
2784 
2785     LineRef firstLine;
2786     LineRef lastLine;
2787 
2788     MergeLineList::iterator mlIt;
2789     for(mlIt = m_mergeLineList.begin(); mlIt != m_mergeLineList.end(); ++mlIt)
2790     {
2791         MergeLine& ml = *mlIt;
2792         MergeEditLineList::iterator melIt;
2793         for(melIt = ml.mergeEditLineList.begin(); melIt != ml.mergeEditLineList.end(); ++melIt)
2794         {
2795             MergeEditLine& mel = *melIt;
2796 
2797             if(mel.isEditableText() && m_selection.lineWithin(line))
2798             {
2799                 if(!firstLine.isValid())
2800                     firstLine = line;
2801                 lastLine = line;
2802             }
2803 
2804             ++line;
2805         }
2806     }
2807 
2808     if(!firstLine.isValid())
2809     {
2810         return; // Nothing to delete.
2811     }
2812 
2813     line = 0;
2814     for(mlIt = m_mergeLineList.begin(); mlIt != m_mergeLineList.end(); ++mlIt)
2815     {
2816         MergeLine& ml = *mlIt;
2817         MergeEditLineList::iterator melIt, melIt1;
2818         for(melIt = ml.mergeEditLineList.begin(); melIt != ml.mergeEditLineList.end();)
2819         {
2820             MergeEditLine& mel = *melIt;
2821             melIt1 = melIt;
2822             ++melIt1;
2823 
2824             if(mel.isEditableText() && m_selection.lineWithin(line))
2825             {
2826                 QString lineString = mel.getString(m_pldA, m_pldB, m_pldC);
2827 
2828                 int firstPosInLine = m_selection.firstPosInLine(line);
2829                 int lastPosInLine = m_selection.lastPosInLine(line);
2830 
2831                 if(line == firstLine)
2832                 {
2833                     mlItFirst = mlIt;
2834                     melItFirst = melIt;
2835                     int pos = firstPosInLine;
2836                     firstLineString = lineString.left(pos);
2837                 }
2838 
2839                 if(line == lastLine)
2840                 {
2841                     // This is the last line in the selection
2842                     int pos = lastPosInLine;
2843                     firstLineString += lineString.midRef(pos); // rest of line
2844                     melItFirst->setString(firstLineString);
2845                 }
2846 
2847                 if(line != firstLine || (m_selection.endPos() - m_selection.beginPos()) == lineString.length())
2848                 {
2849                     // Remove the line
2850                     if(mlIt->mergeEditLineList.size() > 1)
2851                         mlIt->mergeEditLineList.erase(melIt);
2852                     else
2853                         melIt->setRemoved();
2854                 }
2855             }
2856 
2857             ++line;
2858             melIt = melIt1;
2859         }
2860     }
2861 
2862     m_cursorYPos = m_selection.beginLine();
2863     m_cursorXPos = m_selection.beginPos();
2864     m_cursorOldXPixelPos = m_cursorXPixelPos;
2865 
2866     m_selection.reset();
2867 }
2868 
pasteClipboard(bool bFromSelection)2869 void MergeResultWindow::pasteClipboard(bool bFromSelection)
2870 {
2871     //checking of m_selection if needed is done by deleteSelection no need for check here.
2872     deleteSelection();
2873 
2874     setModified();
2875 
2876     int y = m_cursorYPos;
2877     MergeLineList::iterator mlIt;
2878     MergeEditLineList::iterator melIt, melItAfter;
2879     if (!calcIteratorFromLineNr(y, mlIt, melIt))
2880     {
2881         return;
2882     }
2883     melItAfter = melIt;
2884     ++melItAfter;
2885     QString str = melIt->getString(m_pldA, m_pldB, m_pldC);
2886     int x = m_cursorXPos;
2887 
2888     if(!QApplication::clipboard()->supportsSelection())
2889         bFromSelection = false;
2890 
2891     QString clipBoard = QApplication::clipboard()->text(bFromSelection ? QClipboard::Selection : QClipboard::Clipboard);
2892 
2893     QString currentLine = str.left(x);
2894     QString endOfLine = str.mid(x);
2895     int i;
2896     int len = clipBoard.length();
2897     for(i = 0; i < len; ++i)
2898     {
2899         QChar c = clipBoard[i];
2900         if(c == '\n' || (c == '\r' && clipBoard[i + 1] != '\n'))
2901         {
2902             melIt->setString(currentLine);
2903             MergeEditLine mel(mlIt->id3l); // Associate every mel with an id3l, even if not really valid.
2904             melIt = mlIt->mergeEditLineList.insert(melItAfter, mel);
2905             currentLine = "";
2906             x = 0;
2907             ++y;
2908         }
2909         else
2910         {
2911             if(c == '\r') continue;
2912 
2913             currentLine += c;
2914             ++x;
2915         }
2916     }
2917 
2918     currentLine += endOfLine;
2919     melIt->setString(currentLine);
2920 
2921     m_cursorYPos = y;
2922     m_cursorXPos = x;
2923     m_cursorOldXPixelPos = m_cursorXPixelPos;
2924 
2925     update();
2926 }
2927 
resetSelection()2928 void MergeResultWindow::resetSelection()
2929 {
2930     m_selection.reset();
2931     update();
2932 }
2933 
setModified(bool bModified)2934 void MergeResultWindow::setModified(bool bModified)
2935 {
2936     if(bModified != m_bModified)
2937     {
2938         m_bModified = bModified;
2939         Q_EMIT modifiedChanged(m_bModified);
2940     }
2941 }
2942 
2943 /// Saves and returns true when successful.
saveDocument(const QString & fileName,QTextCodec * pEncoding,e_LineEndStyle eLineEndStyle)2944 bool MergeResultWindow::saveDocument(const QString& fileName, QTextCodec* pEncoding, e_LineEndStyle eLineEndStyle)
2945 {
2946     // Are still conflicts somewhere?
2947     if(getNrOfUnsolvedConflicts() > 0)
2948     {
2949         KMessageBox::error(this,
2950                            i18n("Not all conflicts are solved yet.\n"
2951                                 "File not saved."),
2952                            i18n("Conflicts Left"));
2953         return false;
2954     }
2955 
2956     if(eLineEndStyle == eLineEndStyleConflict || eLineEndStyle == eLineEndStyleUndefined)
2957     {
2958         KMessageBox::error(this,
2959                            i18n("There is a line end style conflict. Please choose the line end style manually.\n"
2960                                 "File not saved."),
2961                            i18n("Conflicts Left"));
2962         return false;
2963     }
2964 
2965     update();
2966 
2967     FileAccess file(fileName, true /*bWantToWrite*/);
2968     if(m_pOptions->m_bDmCreateBakFiles && file.exists())
2969     {
2970         bool bSuccess = file.createBackup(".orig");
2971         if(!bSuccess)
2972         {
2973             KMessageBox::error(this, file.getStatusText() + i18n("\n\nCreating backup failed. File not saved."), i18n("File Save Error"));
2974             return false;
2975         }
2976     }
2977 
2978     QByteArray dataArray;
2979     QTextStream textOutStream(&dataArray, QIODevice::WriteOnly);
2980     if(pEncoding->name() == "UTF-8")
2981         textOutStream.setGenerateByteOrderMark(false); // Shouldn't be necessary. Bug in Qt or docs
2982     else
2983         textOutStream.setGenerateByteOrderMark(true); // Only for UTF-16
2984     textOutStream.setCodec(pEncoding);
2985 
2986     // Determine the line feed for this file
2987     const QString lineFeed(eLineEndStyle == eLineEndStyleDos ? QString("\r\n") : QString("\n"));
2988 
2989     int line = 0;
2990     MergeLineList::iterator mlIt = m_mergeLineList.begin();
2991     for(mlIt = m_mergeLineList.begin(); mlIt != m_mergeLineList.end(); ++mlIt)
2992     {
2993         MergeLine& ml = *mlIt;
2994         MergeEditLineList::iterator melIt;
2995         for(melIt = ml.mergeEditLineList.begin(); melIt != ml.mergeEditLineList.end(); ++melIt)
2996         {
2997             MergeEditLine& mel = *melIt;
2998 
2999             if(mel.isEditableText())
3000             {
3001                 const QString str = mel.getString(m_pldA, m_pldB, m_pldC);
3002 
3003                 if(line > 0 && !mel.isRemoved())
3004                 {
3005                     // Put line feed between lines, but not for the first line
3006                     // or between lines that have been removed (because there
3007                     // isn't a line there).
3008                     textOutStream << lineFeed;
3009                 }
3010 
3011                 textOutStream << str;
3012                 ++line;
3013             }
3014         }
3015     }
3016     textOutStream.flush();
3017     bool bSuccess = file.writeFile(dataArray.data(), dataArray.size());
3018     if(!bSuccess)
3019     {
3020         KMessageBox::error(this, i18n("Error while writing."), i18n("File Save Error"));
3021         return false;
3022     }
3023 
3024     setModified(false);
3025     update();
3026 
3027     return true;
3028 }
3029 
getString(int lineIdx)3030 QString MergeResultWindow::getString(int lineIdx)
3031 {
3032     MergeLineList::iterator mlIt;
3033     MergeEditLineList::iterator melIt;
3034     if(!calcIteratorFromLineNr(lineIdx, mlIt, melIt))
3035     {
3036         return QString();
3037     }
3038     return melIt->getString(m_pldA, m_pldB, m_pldC);
3039 }
3040 
findString(const QString & s,LineRef & d3vLine,int & posInLine,bool bDirDown,bool bCaseSensitive)3041 bool MergeResultWindow::findString(const QString& s, LineRef& d3vLine, int& posInLine, bool bDirDown, bool bCaseSensitive)
3042 {
3043     int it = d3vLine;
3044     int endIt = bDirDown ? getNofLines() : -1;
3045     int step = bDirDown ? 1 : -1;
3046     int startPos = posInLine;
3047 
3048     for(; it != endIt; it += step)
3049     {
3050         QString line = getString(it);
3051         if(!line.isEmpty())
3052         {
3053             int pos = line.indexOf(s, startPos, bCaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive);
3054             if(pos != -1)
3055             {
3056                 d3vLine = it;
3057                 posInLine = pos;
3058                 return true;
3059             }
3060 
3061             startPos = 0;
3062         }
3063     }
3064     return false;
3065 }
3066 
setSelection(int firstLine,int startPos,int lastLine,int endPos)3067 void MergeResultWindow::setSelection(int firstLine, int startPos, int lastLine, int endPos)
3068 {
3069     if(lastLine >= getNofLines())
3070     {
3071         lastLine = getNofLines() - 1;
3072         QString s = getString(lastLine);
3073         endPos = s.length();
3074     }
3075     m_selection.reset();
3076     m_selection.start(firstLine, startPos);
3077     m_selection.end(lastLine, endPos);
3078     update();
3079 }
3080 
scrollVertically(QtNumberType deltaY)3081 void MergeResultWindow::scrollVertically(QtNumberType deltaY)
3082 {
3083     mVScrollBar->setValue(mVScrollBar->value()  + deltaY);
3084 }
3085 
WindowTitleWidget(const QSharedPointer<Options> & pOptions)3086 WindowTitleWidget::WindowTitleWidget(const QSharedPointer<Options>& pOptions)
3087 {
3088     m_pOptions = pOptions;
3089     setAutoFillBackground(true);
3090 
3091     QHBoxLayout* pHLayout = new QHBoxLayout(this);
3092     pHLayout->setContentsMargins(2, 2, 2, 2);
3093     pHLayout->setSpacing(2);
3094 
3095     m_pLabel = new QLabel(i18n("Output:"));
3096     pHLayout->addWidget(m_pLabel);
3097 
3098     m_pFileNameLineEdit = new FileNameLineEdit();
3099     pHLayout->addWidget(m_pFileNameLineEdit, 6);
3100     m_pFileNameLineEdit->installEventFilter(this);//for focus tracking
3101     m_pFileNameLineEdit->setAcceptDrops(true);
3102     m_pFileNameLineEdit->setReadOnly(true);
3103 
3104     //m_pBrowseButton = new QPushButton("...");
3105     //pHLayout->addWidget( m_pBrowseButton, 0 );
3106     //chk_connect( m_pBrowseButton, &QPushButton::clicked), this, &MergeResultWindow::slotBrowseButtonClicked);
3107 
3108     m_pModifiedLabel = new QLabel(i18n("[Modified]"));
3109     pHLayout->addWidget(m_pModifiedLabel);
3110     m_pModifiedLabel->setMinimumSize(m_pModifiedLabel->sizeHint());
3111     m_pModifiedLabel->setText("");
3112 
3113     pHLayout->addStretch(1);
3114 
3115     m_pEncodingLabel = new QLabel(i18n("Encoding for saving:"));
3116     pHLayout->addWidget(m_pEncodingLabel);
3117 
3118     m_pEncodingSelector = new QComboBox();
3119     m_pEncodingSelector->setSizeAdjustPolicy(QComboBox::AdjustToContents);
3120     pHLayout->addWidget(m_pEncodingSelector, 2);
3121     setEncodings(nullptr, nullptr, nullptr);
3122 
3123     m_pLineEndStyleLabel = new QLabel(i18n("Line end style:"));
3124     pHLayout->addWidget(m_pLineEndStyleLabel);
3125     m_pLineEndStyleSelector = new QComboBox();
3126     m_pLineEndStyleSelector->setSizeAdjustPolicy(QComboBox::AdjustToContents);
3127     pHLayout->addWidget(m_pLineEndStyleSelector);
3128     setLineEndStyles(eLineEndStyleUndefined, eLineEndStyleUndefined, eLineEndStyleUndefined);
3129 }
3130 
setFileName(const QString & fileName)3131 void WindowTitleWidget::setFileName(const QString& fileName)
3132 {
3133     m_pFileNameLineEdit->setText(QDir::toNativeSeparators(fileName));
3134 }
3135 
getFileName()3136 QString WindowTitleWidget::getFileName()
3137 {
3138     return m_pFileNameLineEdit->text();
3139 }
3140 
3141 //static QString getLineEndStyleName( e_LineEndStyle eLineEndStyle )
3142 //{
3143 //   if ( eLineEndStyle == eLineEndStyleDos )
3144 //      return "DOS";
3145 //   else if ( eLineEndStyle == eLineEndStyleUnix )
3146 //      return "Unix";
3147 //   return QString();
3148 //}
3149 
setLineEndStyles(e_LineEndStyle eLineEndStyleA,e_LineEndStyle eLineEndStyleB,e_LineEndStyle eLineEndStyleC)3150 void WindowTitleWidget::setLineEndStyles(e_LineEndStyle eLineEndStyleA, e_LineEndStyle eLineEndStyleB, e_LineEndStyle eLineEndStyleC)
3151 {
3152     m_pLineEndStyleSelector->clear();
3153     QString dosUsers;
3154     if(eLineEndStyleA == eLineEndStyleDos)
3155         dosUsers += i18n("A");
3156     if(eLineEndStyleB == eLineEndStyleDos)
3157         dosUsers += QLatin1String(dosUsers.isEmpty() ? "" : ", ") + i18n("B");
3158     if(eLineEndStyleC == eLineEndStyleDos)
3159         dosUsers += QLatin1String(dosUsers.isEmpty() ? "" : ", ") + i18n("C");
3160     QString unxUsers;
3161     if(eLineEndStyleA == eLineEndStyleUnix)
3162         unxUsers += i18n("A");
3163     if(eLineEndStyleB == eLineEndStyleUnix)
3164         unxUsers += QLatin1String(unxUsers.isEmpty() ? "" : ", ") + i18n("B");
3165     if(eLineEndStyleC == eLineEndStyleUnix)
3166         unxUsers += QLatin1String(unxUsers.isEmpty() ? "" : ", ") + i18n("C");
3167 
3168     m_pLineEndStyleSelector->addItem(i18n("Unix") + (unxUsers.isEmpty() ? QString("") : QLatin1String(" (") + unxUsers + QLatin1String(")")));
3169     m_pLineEndStyleSelector->addItem(i18n("DOS") + (dosUsers.isEmpty() ? QString("") : QLatin1String(" (") + dosUsers + QLatin1String(")")));
3170 
3171     e_LineEndStyle autoChoice = (e_LineEndStyle)m_pOptions->m_lineEndStyle;
3172 
3173     if(m_pOptions->m_lineEndStyle == eLineEndStyleAutoDetect)
3174     {
3175         if(eLineEndStyleA != eLineEndStyleUndefined && eLineEndStyleB != eLineEndStyleUndefined && eLineEndStyleC != eLineEndStyleUndefined)
3176         {
3177             if(eLineEndStyleA == eLineEndStyleB)
3178                 autoChoice = eLineEndStyleC;
3179             else if(eLineEndStyleA == eLineEndStyleC)
3180                 autoChoice = eLineEndStyleB;
3181             else
3182                 autoChoice = eLineEndStyleConflict; //conflict (not likely while only two values exist)
3183         }
3184         else
3185         {
3186             e_LineEndStyle c1, c2;
3187             if(eLineEndStyleA == eLineEndStyleUndefined)
3188             {
3189                 c1 = eLineEndStyleB;
3190                 c2 = eLineEndStyleC;
3191             }
3192             else if(eLineEndStyleB == eLineEndStyleUndefined)
3193             {
3194                 c1 = eLineEndStyleA;
3195                 c2 = eLineEndStyleC;
3196             }
3197             else /*if( eLineEndStyleC == eLineEndStyleUndefined )*/
3198             {
3199                 c1 = eLineEndStyleA;
3200                 c2 = eLineEndStyleB;
3201             }
3202             if(c1 == c2 && c1 != eLineEndStyleUndefined)
3203                 autoChoice = c1;
3204             else
3205                 autoChoice = eLineEndStyleConflict;
3206         }
3207     }
3208 
3209     if(autoChoice == eLineEndStyleUnix)
3210         m_pLineEndStyleSelector->setCurrentIndex(0);
3211     else if(autoChoice == eLineEndStyleDos)
3212         m_pLineEndStyleSelector->setCurrentIndex(1);
3213     else if(autoChoice == eLineEndStyleConflict)
3214     {
3215         m_pLineEndStyleSelector->addItem(i18n("Conflict"));
3216         m_pLineEndStyleSelector->setCurrentIndex(2);
3217     }
3218 }
3219 
getLineEndStyle()3220 e_LineEndStyle WindowTitleWidget::getLineEndStyle()
3221 {
3222 
3223     int current = m_pLineEndStyleSelector->currentIndex();
3224     if(current == 0)
3225         return eLineEndStyleUnix;
3226     else if(current == 1)
3227         return eLineEndStyleDos;
3228     else
3229         return eLineEndStyleConflict;
3230 }
3231 
setEncodings(QTextCodec * pCodecForA,QTextCodec * pCodecForB,QTextCodec * pCodecForC)3232 void WindowTitleWidget::setEncodings(QTextCodec* pCodecForA, QTextCodec* pCodecForB, QTextCodec* pCodecForC)
3233 {
3234     m_pEncodingSelector->clear();
3235 
3236     // First sort codec names:
3237     std::map<QString, QTextCodec*> names;
3238     const QList<int> mibs = QTextCodec::availableMibs();
3239     for(int i: mibs)
3240     {
3241         QTextCodec* c = QTextCodec::codecForMib(i);
3242         if(c != nullptr)
3243             names[QLatin1String(c->name())] = c;
3244     }
3245 
3246     if(pCodecForA != nullptr)
3247         m_pEncodingSelector->addItem(i18n("Codec from A: %1", QLatin1String(pCodecForA->name())), QVariant::fromValue((void*)pCodecForA));
3248     if(pCodecForB != nullptr)
3249         m_pEncodingSelector->addItem(i18n("Codec from B: %1", QLatin1String(pCodecForB->name())), QVariant::fromValue((void*)pCodecForB));
3250     if(pCodecForC != nullptr)
3251         m_pEncodingSelector->addItem(i18n("Codec from C: %1", QLatin1String(pCodecForC->name())), QVariant::fromValue((void*)pCodecForC));
3252 
3253     std::map<QString, QTextCodec*>::const_iterator it;
3254     for(it = names.begin(); it != names.end(); ++it)
3255     {
3256         m_pEncodingSelector->addItem(it->first, QVariant::fromValue((void*)it->second));
3257     }
3258     m_pEncodingSelector->setMinimumSize(m_pEncodingSelector->sizeHint());
3259 
3260     if(pCodecForC != nullptr && pCodecForB != nullptr && pCodecForA != nullptr)
3261     {
3262         if(pCodecForA == pCodecForC)
3263             m_pEncodingSelector->setCurrentIndex(1); // B
3264         else
3265             m_pEncodingSelector->setCurrentIndex(2); // C
3266     }
3267     else if(pCodecForA != nullptr && pCodecForB != nullptr)
3268         m_pEncodingSelector->setCurrentIndex(1); // B
3269     else
3270         m_pEncodingSelector->setCurrentIndex(0);
3271 }
3272 
getEncoding()3273 QTextCodec* WindowTitleWidget::getEncoding()
3274 {
3275     return (QTextCodec*)m_pEncodingSelector->itemData(m_pEncodingSelector->currentIndex()).value<void*>();
3276 }
3277 
setEncoding(QTextCodec * pEncoding)3278 void WindowTitleWidget::setEncoding(QTextCodec* pEncoding)
3279 {
3280     int idx = m_pEncodingSelector->findText(QLatin1String(pEncoding->name()));
3281     if(idx >= 0)
3282         m_pEncodingSelector->setCurrentIndex(idx);
3283 }
3284 
3285 //void WindowTitleWidget::slotBrowseButtonClicked()
3286 //{
3287 //   QString current = m_pFileNameLineEdit->text();
3288 //
3289 //   QUrl newURL = KFileDialog::getSaveUrl( current, 0, this, i18n("Select file (not saving yet)"));
3290 //   if ( !newURL.isEmpty() )
3291 //   {
3292 //      m_pFileNameLineEdit->setText( newURL.url() );
3293 //   }
3294 //}
3295 
slotSetModified(bool bModified)3296 void WindowTitleWidget::slotSetModified(bool bModified)
3297 {
3298     m_pModifiedLabel->setText(bModified ? i18n("[Modified]") : "");
3299 }
3300 
eventFilter(QObject * o,QEvent * e)3301 bool WindowTitleWidget::eventFilter(QObject* o, QEvent* e)
3302 {
3303     Q_UNUSED(o);
3304     if(e->type() == QEvent::FocusIn || e->type() == QEvent::FocusOut)
3305     {
3306         QPalette p = m_pLabel->palette();
3307 
3308         QColor c1 = m_pOptions->m_fgColor;
3309         QColor c2 = Qt::lightGray;
3310         if(e->type() == QEvent::FocusOut)
3311             c2 = m_pOptions->m_bgColor;
3312 
3313         p.setColor(QPalette::Window, c2);
3314         setPalette(p);
3315 
3316         p.setColor(QPalette::WindowText, c1);
3317         m_pLabel->setPalette(p);
3318         m_pEncodingLabel->setPalette(p);
3319         m_pEncodingSelector->setPalette(p);
3320     }
3321     return false;
3322 }
3323 
3324 //#include "mergeresultwindow.moc"
3325