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