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