File indexing completed on 2024-04-21 05:42:41

0001 // clang-format off
0002 /*
0003  *  This file is part of KDiff3.
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 "compat.h"
0012 #include "defmac.h"
0013 #include "difftextwindow.h"
0014 #include "DirectoryInfo.h"
0015 #include "directorymergewindow.h"
0016 #include "fileaccess.h"
0017 #include "kdiff3.h"
0018 #include "kdiff3_shell.h"
0019 #include "Logging.h"
0020 #include "optiondialog.h"
0021 #include "progress.h"
0022 #include "Utils.h"
0023 
0024 #include "mergeresultwindow.h"
0025 #include "smalldialogs.h"
0026 
0027 #include <algorithm>
0028 #include <cstdio>
0029 #include <list>
0030 #include <typeinfo>
0031 
0032 #include <QCheckBox>
0033 #include <QClipboard>
0034 #include <QComboBox>
0035 #include <QDialog>
0036 #include <QDir>
0037 #include <QDockWidget>
0038 #include <QEvent> // QKeyEvent, QDropEvent, QInputEvent
0039 #include <QFile>
0040 #include <QLayout>
0041 #include <QLineEdit>
0042 #include <QMimeData>
0043 #include <QPointer>
0044 #include <QProcess>
0045 #include <QScrollBar>
0046 #include <QSplitter>
0047 #include <QStatusBar>
0048 #include <QStringList>
0049 #include <QUrl>
0050 
0051 #include <KLocalizedString>
0052 #include <KMessageBox>
0053 #include <KShortcutsDialog>
0054 
0055 void KDiff3App::mainInit(TotalDiffStatus* pTotalDiffStatus, const InitFlags inFlags)
0056 {
0057     ProgressScope pp;
0058     mErrors.clear();
0059     bool bLoadFiles = inFlags & InitFlag::loadFiles;
0060     bool bUseCurrentEncoding = inFlags & InitFlag::useCurrentEncoding;
0061     bool bAutoSolve = inFlags & InitFlag::autoSolve;
0062 
0063     bool bGUI = (inFlags & InitFlag::initGUI);
0064 
0065     IgnoreFlags eIgnoreFlags = IgnoreFlag::none;
0066     if(gOptions->ignoreComments())
0067         eIgnoreFlags |= IgnoreFlag::ignoreComments;
0068 
0069     if(gOptions->whiteSpaceIsEqual())
0070         eIgnoreFlags |= IgnoreFlag::ignoreWhiteSpace;
0071 
0072     assert(pTotalDiffStatus != nullptr);
0073 
0074     bool bVisibleMergeResultWindow = !m_outputFilename.isEmpty();
0075 
0076     //Easier to do here then have all eleven of our call points do the check.
0077     if(m_sd1->isEmpty() && m_sd2->isEmpty() && m_sd3->isEmpty())
0078         bLoadFiles = false;
0079 
0080     if(bGUI)
0081     {
0082         if(bVisibleMergeResultWindow && !gOptions->m_PreProcessorCmd.isEmpty())
0083         {
0084             QString msg = "- " + i18n("PreprocessorCmd: ") + gOptions->m_PreProcessorCmd + '\n';
0085             KMessageBox::ButtonCode result = Compat::warningTwoActions(this,
0086                                                                        i18n("The following option(s) you selected might change data:\n") + msg +
0087                                                                            i18n("\nMost likely this is not wanted during a merge.\n"
0088                                                                                 "Do you want to disable these settings or continue with these settings active?"),
0089                                                                        i18n("Option Unsafe for Merging"),
0090                                                                        KGuiItem(i18n("Use These Options During Merge")),
0091                                                                        KGuiItem(i18n("Disable Unsafe Options")));
0092 
0093             if(result == Compat::SecondaryAction)
0094             {
0095                 gOptions->m_PreProcessorCmd = "";
0096             }
0097         }
0098 
0099         // Because of the progress dialog paint events can occur, but data is invalid,
0100         // so painting must be suppressed
0101         setLockPainting(true);
0102     }
0103 
0104     //insure merge result window never has stale iterators/pointers.
0105     if(m_pMergeResultWindow) m_pMergeResultWindow->reset();
0106     //Clear stale pointers in DiffTextWindow.
0107     if(m_pDiffTextWindow1) m_pDiffTextWindow1->reset();
0108     if(m_pDiffTextWindow2) m_pDiffTextWindow2->reset();
0109     if(m_pDiffTextWindow3) m_pDiffTextWindow3->reset();
0110     m_diff3LineList.clear();
0111     mDiff3LineVector.clear();
0112 
0113     if(bLoadFiles)
0114     {
0115         m_manualDiffHelpList.clear();
0116 
0117         if(m_sd3->isEmpty())
0118             ProgressProxy::setMaxNofSteps(4); // Read 2 files, 1 comparison, 1 finediff
0119         else
0120             ProgressProxy::setMaxNofSteps(9); // Read 3 files, 3 comparisons, 3 finediffs
0121 
0122         // First get all input data.
0123         ProgressProxy::setInformation(i18nc("Status message", "Loading A: %1", m_sd1->getFilename()));
0124         qCInfo(kdiffMain) << "Loading A: " << m_sd1->getFilename();
0125 
0126         if(bUseCurrentEncoding)
0127             m_sd1->readAndPreprocess(m_sd1->getEncoding(), false);
0128         else
0129             m_sd1->readAndPreprocess(gOptions->mEncodingA, gOptions->mAutoDetectA);
0130 
0131         ProgressProxy::step();
0132 
0133         ProgressProxy::setInformation(i18nc("Status message", "Loading B: %1", m_sd2->getFilename()));
0134         qCInfo(kdiffMain) << "Loading B: " << m_sd2->getFilename();
0135 
0136         if(bUseCurrentEncoding)
0137             m_sd2->readAndPreprocess(m_sd2->getEncoding(), false);
0138         else
0139             m_sd2->readAndPreprocess(gOptions->mEncodingB, gOptions->mAutoDetectB);
0140 
0141         ProgressProxy::step();
0142         mErrors.append(m_sd1->getErrors());
0143         mErrors.append(m_sd2->getErrors());
0144     }
0145     else
0146     {
0147         if(m_sd3->isEmpty())
0148             ProgressProxy::setMaxNofSteps(2); // 1 comparison, 1 finediff
0149         else
0150             ProgressProxy::setMaxNofSteps(6); // 3 comparisons, 3 finediffs
0151     }
0152 
0153     pTotalDiffStatus->reset();
0154 
0155     if(mErrors.isEmpty())
0156     {
0157         try
0158         {
0159             // Run the diff.
0160             if(m_sd3->isEmpty())
0161             {
0162                 pTotalDiffStatus->setBinaryEqualAB(m_sd1->isBinaryEqualWith(m_sd2));
0163 
0164                 if(m_sd1->isText() && m_sd2->isText())
0165                 {
0166                     ProgressProxy::setInformation(i18nc("Status message", "Diff: A <-> B"));
0167                     qCInfo(kdiffMain) << "Diff: A <-> B";
0168                     m_manualDiffHelpList.runDiff(m_sd1->getLineDataForDiff(), m_sd1->getSizeLines(), m_sd2->getLineDataForDiff(), m_sd2->getSizeLines(), m_diffList12, e_SrcSelector::A, e_SrcSelector::B);
0169 
0170                     ProgressProxy::step();
0171 
0172                     ProgressProxy::setInformation(i18nc("Status message", "Linediff: A <-> B"));
0173                     qCInfo(kdiffMain) << "Linediff: A <-> B";
0174                     m_diff3LineList.calcDiff3LineListUsingAB(&m_diffList12);
0175 
0176                     pTotalDiffStatus->setTextEqualAB(m_diff3LineList.fineDiff(e_SrcSelector::A, m_sd1->getLineDataForDisplay(), m_sd2->getLineDataForDisplay(), eIgnoreFlags));
0177                     if(m_sd1->getSizeBytes() == 0) pTotalDiffStatus->setTextEqualAB(false);
0178 
0179                     ProgressProxy::step();
0180                 }
0181                 else
0182                 {
0183                     ProgressProxy::step();
0184                     ProgressProxy::step();
0185                 }
0186             }
0187             else
0188             {
0189                 if(bLoadFiles)
0190                 {
0191                     ProgressProxy::setInformation(i18nc("Status message", "Loading C: %1", m_sd3->getFilename()));
0192                     qCInfo(kdiffMain) << "Loading C: " << m_sd3->getFilename();
0193 
0194                     if(bUseCurrentEncoding)
0195                         m_sd3->readAndPreprocess(m_sd3->getEncoding(), false);
0196                     else
0197                         m_sd3->readAndPreprocess(gOptions->mEncodingC, gOptions->mAutoDetectC);
0198 
0199                     ProgressProxy::step();
0200                 }
0201 
0202                 pTotalDiffStatus->setBinaryEqualAB(m_sd1->isBinaryEqualWith(m_sd2));
0203                 pTotalDiffStatus->setBinaryEqualAC(m_sd1->isBinaryEqualWith(m_sd3));
0204                 pTotalDiffStatus->setBinaryEqualBC(m_sd3->isBinaryEqualWith(m_sd2));
0205 
0206                 ProgressProxy::setInformation(i18nc("Status message", "Diff: A <-> B"));
0207                 qCInfo(kdiffMain) << "Diff: A <-> B";
0208 
0209                 if(m_sd1->isText() && m_sd2->isText())
0210                 {
0211                     m_manualDiffHelpList.runDiff(m_sd1->getLineDataForDiff(), m_sd1->getSizeLines(), m_sd2->getLineDataForDiff(), m_sd2->getSizeLines(), m_diffList12, e_SrcSelector::A, e_SrcSelector::B);
0212 
0213                     m_diff3LineList.calcDiff3LineListUsingAB(&m_diffList12);
0214                 }
0215                 ProgressProxy::step();
0216 
0217                 ProgressProxy::setInformation(i18nc("Status message", "Diff: A <-> C"));
0218                 qCInfo(kdiffMain) << "Diff: A <-> C";
0219 
0220                 if(m_sd1->isText() && m_sd3->isText())
0221                 {
0222                     m_manualDiffHelpList.runDiff(m_sd1->getLineDataForDiff(), m_sd1->getSizeLines(), m_sd3->getLineDataForDiff(), m_sd3->getSizeLines(), m_diffList13, e_SrcSelector::A, e_SrcSelector::C);
0223 
0224                     m_diff3LineList.calcDiff3LineListUsingAC(&m_diffList13);
0225                     m_diff3LineList.correctManualDiffAlignment(&m_manualDiffHelpList);
0226                     m_diff3LineList.calcDiff3LineListTrim(m_sd1->getLineDataForDiff(), m_sd2->getLineDataForDiff(), m_sd3->getLineDataForDiff(), &m_manualDiffHelpList);
0227                 }
0228                 ProgressProxy::step();
0229 
0230                 ProgressProxy::setInformation(i18nc("Status message", "Diff: B <-> C"));
0231                 qCInfo(kdiffMain) << "Diff: B <-> C";
0232 
0233                 if(m_sd2->isText() && m_sd3->isText())
0234                 {
0235                     m_manualDiffHelpList.runDiff(m_sd2->getLineDataForDiff(), m_sd2->getSizeLines(), m_sd3->getLineDataForDiff(), m_sd3->getSizeLines(), m_diffList23, e_SrcSelector::B, e_SrcSelector::C);
0236                     if(gOptions->m_bDiff3AlignBC)
0237                     {
0238                         m_diff3LineList.calcDiff3LineListUsingBC(&m_diffList23);
0239                         m_diff3LineList.correctManualDiffAlignment(&m_manualDiffHelpList);
0240                         m_diff3LineList.calcDiff3LineListTrim(m_sd1->getLineDataForDiff(), m_sd2->getLineDataForDiff(), m_sd3->getLineDataForDiff(), &m_manualDiffHelpList);
0241                     }
0242                 }
0243                 ProgressProxy::step();
0244 
0245                 if(!gOptions->m_bDiff3AlignBC)
0246                 {
0247                     m_diff3LineList.debugLineCheck(m_sd1->getSizeLines(), e_SrcSelector::A);
0248                     m_diff3LineList.debugLineCheck(m_sd2->getSizeLines(), e_SrcSelector::B);
0249                     m_diff3LineList.debugLineCheck(m_sd3->getSizeLines(), e_SrcSelector::C);
0250                 }
0251 
0252                 ProgressProxy::setInformation(i18nc("Status message", "Linediff: A <-> B"));
0253                 qCInfo(kdiffMain) << "Linediff: A <-> B";
0254                 if(m_sd1->hasData() && m_sd2->hasData() && m_sd1->isText() && m_sd2->isText())
0255                     pTotalDiffStatus->setTextEqualAB(m_diff3LineList.fineDiff(e_SrcSelector::A, m_sd1->getLineDataForDisplay(), m_sd2->getLineDataForDisplay(), eIgnoreFlags));
0256                 ProgressProxy::step();
0257 
0258                 ProgressProxy::setInformation(i18nc("Status message", "Linediff: B <-> C"));
0259                 qCInfo(kdiffMain) << "Linediff: B <-> C";
0260                 if(m_sd2->hasData() && m_sd3->hasData() && m_sd2->isText() && m_sd3->isText())
0261                     pTotalDiffStatus->setTextEqualBC(m_diff3LineList.fineDiff(e_SrcSelector::B, m_sd2->getLineDataForDisplay(), m_sd3->getLineDataForDisplay(), eIgnoreFlags));
0262                 ProgressProxy::step();
0263 
0264                 ProgressProxy::setInformation(i18nc("Status message", "Linediff: A <-> C"));
0265                 qCInfo(kdiffMain) << "Linediff: A <-> C";
0266                 if(m_sd1->hasData() && m_sd3->hasData() && m_sd1->isText() && m_sd3->isText())
0267                     pTotalDiffStatus->setTextEqualAC(m_diff3LineList.fineDiff(e_SrcSelector::C, m_sd3->getLineDataForDisplay(), m_sd1->getLineDataForDisplay(), eIgnoreFlags));
0268 
0269                 if(!gOptions->m_bDiff3AlignBC)
0270                 {
0271                     m_diff3LineList.debugLineCheck(m_sd2->getSizeLines(), e_SrcSelector::B);
0272                     m_diff3LineList.debugLineCheck(m_sd3->getSizeLines(), e_SrcSelector::C);
0273                 }
0274 
0275                 ProgressProxy::setInformation(i18nc("Status message", "Linediff: A <-> B"));
0276                 if(m_sd1->hasData() && m_sd2->hasData() && m_sd1->isText() && m_sd2->isText())
0277                     pTotalDiffStatus->setTextEqualAB(m_diff3LineList.fineDiff(e_SrcSelector::A, m_sd1->getLineDataForDisplay(), m_sd2->getLineDataForDisplay(), eIgnoreFlags));
0278                 ProgressProxy::step();
0279 
0280                 ProgressProxy::setInformation(i18nc("Status message", "Linediff: B <-> C"));
0281                 if(m_sd3->hasData() && m_sd2->hasData() && m_sd3->isText() && m_sd2->isText())
0282                     pTotalDiffStatus->setTextEqualBC(m_diff3LineList.fineDiff(e_SrcSelector::B, m_sd2->getLineDataForDisplay(), m_sd3->getLineDataForDisplay(), eIgnoreFlags));
0283                 ProgressProxy::step();
0284 
0285                 ProgressProxy::setInformation(i18nc("Status message", "Linediff: A <-> C"));
0286                 if(m_sd1->hasData() && m_sd3->hasData() && m_sd1->isText() && m_sd3->isText())
0287                     pTotalDiffStatus->setTextEqualAC(m_diff3LineList.fineDiff(e_SrcSelector::C, m_sd3->getLineDataForDisplay(), m_sd1->getLineDataForDisplay(), eIgnoreFlags));
0288                 ProgressProxy::step();
0289                 if(m_sd1->getSizeBytes() == 0)
0290                 {
0291                     pTotalDiffStatus->setTextEqualAB(false);
0292                     pTotalDiffStatus->setTextEqualAC(false);
0293                 }
0294                 if(m_sd2->getSizeBytes() == 0)
0295                 {
0296                     pTotalDiffStatus->setTextEqualAB(false);
0297                     pTotalDiffStatus->setTextEqualBC(false);
0298                 }
0299 
0300                 mErrors.append(m_sd3->getErrors());
0301             }
0302         }
0303         catch(const std::bad_alloc&)
0304         {
0305             m_manualDiffHelpList.clear();
0306             m_diff3LineList.clear();
0307             mDiff3LineVector.clear();
0308             m_sd1->reset();
0309             m_sd2->reset();
0310             m_sd3->reset();
0311             mErrors.append(i18nc("Error message", "Not enough memory to complete request."));
0312             ProgressProxy::clear();
0313         }
0314         catch(const std::exception& e)
0315         {
0316             qCCritical(kdiffMain) << "An internal error occurred:" << e.what();
0317 
0318             mErrors.append(i18n("An internal error occurred: %1", QString::fromStdString(e.what())));
0319             ProgressProxy::clear();
0320         }
0321     }
0322     else
0323     {
0324         ProgressProxy::clear();
0325     }
0326 
0327     if(mErrors.isEmpty() && m_sd1->isText() && m_sd2->isText())
0328     {
0329         Diff3Line::m_pDiffBufferInfo->init(&m_diff3LineList,
0330                                            m_sd1->getLineDataForDiff(),
0331                                            m_sd2->getLineDataForDiff(),
0332                                            m_sd3->getLineDataForDiff());
0333 
0334         m_diff3LineList.calcWhiteDiff3Lines(m_sd1->getLineDataForDiff(), m_sd2->getLineDataForDiff(), m_sd3->getLineDataForDiff(), gOptions->ignoreComments());
0335         m_diff3LineList.calcDiff3LineVector(mDiff3LineVector);
0336     }
0337 
0338     // Calc needed lines for display
0339     try
0340     {
0341         m_neededLines = SafeInt<LineType>(m_diff3LineList.size());
0342     }
0343     catch(std::exception&)
0344     {
0345         mErrors.append(i18n("Too many lines in diff. Skipping file."));
0346     }
0347 
0348     m_pMergeResultWindow->connectActions();
0349 
0350     m_pMainWidget->setVisible(bGUI); //sets off multiple resize events internally.
0351 
0352     m_bTripleDiff = !m_sd3->isEmpty();
0353 
0354     m_pMergeResultWindowTitle->setEncodings(m_sd1->getEncoding(), m_sd2->getEncoding(), m_sd3->getEncoding());
0355     if(!gOptions->m_bAutoSelectOutEncoding)
0356         m_pMergeResultWindowTitle->setEncoding(gOptions->mEncodingOut);
0357 
0358     m_pMergeResultWindowTitle->setLineEndStyles(m_sd1->getLineEndStyle(), m_sd2->getLineEndStyle(), m_sd3->getLineEndStyle());
0359 
0360     if(bGUI)
0361     {
0362         const ManualDiffHelpList* pMDHL = &m_manualDiffHelpList;
0363         m_pDiffTextWindow1->init(m_sd1->getAliasName(), m_sd1->getEncoding(), m_sd1->getLineEndStyle(),
0364                                  m_sd1->getLineDataForDisplay(), m_sd1->getSizeLines(), &mDiff3LineVector, pMDHL);
0365         m_pDiffTextWindowFrame1->init();
0366 
0367         m_pDiffTextWindow2->init(m_sd2->getAliasName(), m_sd2->getEncoding(), m_sd2->getLineEndStyle(),
0368                                  m_sd2->getLineDataForDisplay(), m_sd2->getSizeLines(), &mDiff3LineVector, pMDHL);
0369         m_pDiffTextWindowFrame2->init();
0370 
0371         m_pDiffTextWindow3->init(m_sd3->getAliasName(), m_sd3->getEncoding(), m_sd3->getLineEndStyle(),
0372                                  m_sd3->getLineDataForDisplay(), m_sd3->getSizeLines(), &mDiff3LineVector, pMDHL);
0373         m_pDiffTextWindowFrame3->init();
0374 
0375         m_pDiffTextWindowFrame3->setVisible(m_bTripleDiff);
0376     }
0377 
0378     m_bOutputModified = bVisibleMergeResultWindow;
0379 
0380     m_pMergeResultWindow->init(
0381         m_sd1->getLineDataForDisplay(), m_sd1->getSizeLines(),
0382         m_sd2->getLineDataForDisplay(), m_sd2->getSizeLines(),
0383         m_bTripleDiff ? m_sd3->getLineDataForDisplay() : nullptr, m_sd3->getSizeLines(),
0384         &m_diff3LineList,
0385         pTotalDiffStatus, bAutoSolve);
0386     m_pMergeResultWindowTitle->setFileName(m_outputFilename.isEmpty() ? QString("unnamed.txt") : m_outputFilename);
0387 
0388     if(bGUI)
0389     {
0390         m_pOverview->init(&m_diff3LineList);
0391         DiffTextWindow::mVScrollBar->setValue(0);
0392         m_pHScrollBar->setValue(0);
0393         MergeResultWindow::mVScrollBar->setValue(0);
0394         setLockPainting(false);
0395 
0396         if(!bVisibleMergeResultWindow)
0397             m_pMergeWindowFrame->hide();
0398         else
0399             m_pMergeWindowFrame->show();
0400 
0401         // Try to create a meaningful but not too long caption
0402         if(mErrors.isEmpty())
0403         {
0404             createCaption();
0405         }
0406         m_bFinishMainInit = true; // call slotFinishMainInit after finishing the word wrap
0407         m_bLoadFiles = bLoadFiles;
0408         postRecalcWordWrap();
0409     }
0410 }
0411 
0412 void KDiff3App::setLockPainting(bool bLock)
0413 {
0414     if(m_pDiffTextWindow1) m_pDiffTextWindow1->setPaintingAllowed(!bLock);
0415     if(m_pDiffTextWindow2) m_pDiffTextWindow2->setPaintingAllowed(!bLock);
0416     if(m_pDiffTextWindow3) m_pDiffTextWindow3->setPaintingAllowed(!bLock);
0417     if(m_pOverview) m_pOverview->setPaintingAllowed(!bLock);
0418     if(m_pMergeResultWindow) m_pMergeResultWindow->setPaintingAllowed(!bLock);
0419 }
0420 
0421 void KDiff3App::createCaption()
0422 {
0423     // Try to create a meaningful but not too long caption
0424     // 1. If the filenames are equal then show only one filename
0425     QString caption;
0426     QString f1 = m_sd1->getAliasName();
0427     QString f2 = m_sd2->getAliasName();
0428     QString f3 = m_sd3->getAliasName();
0429     QtSizeType p;
0430 
0431     if((p = f1.lastIndexOf('/')) >= 0 || (p = f1.lastIndexOf('\\')) >= 0)
0432         f1 = f1.mid(p + 1);
0433     if((p = f2.lastIndexOf('/')) >= 0 || (p = f2.lastIndexOf('\\')) >= 0)
0434         f2 = f2.mid(p + 1);
0435     if((p = f3.lastIndexOf('/')) >= 0 || (p = f3.lastIndexOf('\\')) >= 0)
0436         f3 = f3.mid(p + 1);
0437 
0438     if(!f1.isEmpty())
0439     {
0440         if((f2.isEmpty() && f3.isEmpty()) ||
0441            (f2.isEmpty() && f1 == f3) || (f3.isEmpty() && f1 == f2) || (f1 == f2 && f1 == f3))
0442             caption = f1;
0443     }
0444     else if(!f2.isEmpty())
0445     {
0446         if(f3.isEmpty() || f2 == f3)
0447             caption = f2;
0448     }
0449     else if(!f3.isEmpty())
0450         caption = f3;
0451 
0452     // 2. If the files don't have the same name then show all names
0453     if(caption.isEmpty() && (!f1.isEmpty() || !f2.isEmpty() || !f3.isEmpty()))
0454     {
0455         caption = (f1.isEmpty() ? QString("") : f1);
0456         caption += QLatin1String(caption.isEmpty() || f2.isEmpty() ? "" : " <-> ") + (f2.isEmpty() ? QString("") : f2);
0457         caption += QLatin1String(caption.isEmpty() || f3.isEmpty() ? "" : " <-> ") + (f3.isEmpty() ? QString("") : f3);
0458     }
0459 
0460     m_pKDiff3Shell->setWindowTitle(caption.isEmpty() ? QString("KDiff3") : caption + QString(" - KDiff3"));
0461 }
0462 
0463 void KDiff3App::setHScrollBarRange()
0464 {
0465     qint32 w1 = m_pDiffTextWindow1 != nullptr && m_pDiffTextWindow1->isVisible() ? m_pDiffTextWindow1->getMaxTextWidth() : 0;
0466     qint32 w2 = m_pDiffTextWindow2 != nullptr && m_pDiffTextWindow2->isVisible() ? m_pDiffTextWindow2->getMaxTextWidth() : 0;
0467     qint32 w3 = m_pDiffTextWindow3 != nullptr && m_pDiffTextWindow3->isVisible() ? m_pDiffTextWindow3->getMaxTextWidth() : 0;
0468 
0469     qint32 wm = m_pMergeResultWindow != nullptr && m_pMergeResultWindow->isVisible() ? m_pMergeResultWindow->getMaxTextWidth() : 0;
0470 
0471     qint32 v1 = m_pDiffTextWindow1 != nullptr && m_pDiffTextWindow1->isVisible() ? m_pDiffTextWindow1->getVisibleTextAreaWidth() : 0;
0472     qint32 v2 = m_pDiffTextWindow2 != nullptr && m_pDiffTextWindow2->isVisible() ? m_pDiffTextWindow2->getVisibleTextAreaWidth() : 0;
0473     qint32 v3 = m_pDiffTextWindow3 != nullptr && m_pDiffTextWindow3->isVisible() ? m_pDiffTextWindow3->getVisibleTextAreaWidth() : 0;
0474     qint32 vm = m_pMergeResultWindow != nullptr && m_pMergeResultWindow->isVisible() ? m_pMergeResultWindow->getVisibleTextAreaWidth() : 0;
0475 
0476     // Find the minimum, but don't consider 0.
0477     qint32 pageStep = v1;
0478 
0479     if((pageStep == 0 || pageStep > v2) && v2 > 0)
0480         pageStep = v2;
0481     if((pageStep == 0 || pageStep > v3) && v3 > 0)
0482         pageStep = v3;
0483     if((pageStep == 0 || pageStep > vm) && vm > 0)
0484         pageStep = vm;
0485 
0486     qint32 rangeMax = 0;
0487     if(w1 > v1 && w1 - v1 > rangeMax && v1 > 0)
0488         rangeMax = w1 - v1;
0489     if(w2 > v2 && w2 - v2 > rangeMax && v2 > 0)
0490         rangeMax = w2 - v2;
0491     if(w3 > v3 && w3 - v3 > rangeMax && v3 > 0)
0492         rangeMax = w3 - v3;
0493     if(wm > vm && wm - vm > rangeMax && vm > 0)
0494         rangeMax = wm - vm;
0495 
0496     m_pHScrollBar->setRange(0, rangeMax);
0497     m_pHScrollBar->setSingleStep(fontMetrics().horizontalAdvance('0') * 10);
0498     m_pHScrollBar->setPageStep(pageStep);
0499 }
0500 
0501 void KDiff3App::resizeDiffTextWindowHeight(qint32 newHeight)
0502 {
0503     m_DTWHeight = newHeight;
0504 
0505     DiffTextWindow::mVScrollBar->setRange(0, std::max(0, m_neededLines + 1 - newHeight));
0506     DiffTextWindow::mVScrollBar->setPageStep(newHeight);
0507     m_pOverview->setRange(DiffTextWindow::mVScrollBar->value(), DiffTextWindow::mVScrollBar->pageStep());
0508 
0509     setHScrollBarRange();
0510 }
0511 
0512 void KDiff3App::scrollDiffTextWindow(qint32 deltaX, qint32 deltaY)
0513 {
0514     if(deltaY != 0 && DiffTextWindow::mVScrollBar != nullptr)
0515     {
0516         DiffTextWindow::mVScrollBar->setValue(DiffTextWindow::mVScrollBar->value() + deltaY);
0517     }
0518     if(deltaX != 0 && m_pHScrollBar != nullptr)
0519         m_pHScrollBar->QScrollBar::setValue(m_pHScrollBar->value() + deltaX);
0520 }
0521 
0522 void KDiff3App::scrollMergeResultWindow(qint32 deltaX, qint32 deltaY)
0523 {
0524     if(deltaY != 0)
0525         MergeResultWindow::mVScrollBar->setValue(MergeResultWindow::mVScrollBar->value() + deltaY);
0526     if(deltaX != 0)
0527         m_pHScrollBar->setValue(m_pHScrollBar->value() + deltaX);
0528 }
0529 
0530 void KDiff3App::sourceMask(qint32 srcMask, qint32 enabledMask)
0531 {
0532     chooseA->blockSignals(true);
0533     chooseB->blockSignals(true);
0534     chooseC->blockSignals(true);
0535     chooseA->setChecked((srcMask & 1) != 0);
0536     chooseB->setChecked((srcMask & 2) != 0);
0537     chooseC->setChecked((srcMask & 4) != 0);
0538     chooseA->blockSignals(false);
0539     chooseB->blockSignals(false);
0540     chooseC->blockSignals(false);
0541     chooseA->setEnabled((enabledMask & 1) != 0);
0542     chooseB->setEnabled((enabledMask & 2) != 0);
0543     chooseC->setEnabled((enabledMask & 4) != 0);
0544 }
0545 
0546 void KDiff3App::initView()
0547 {
0548     // set the main widget here
0549     if(mInitCalled)
0550     {
0551         return;
0552     }
0553 
0554     mInitCalled = true;
0555     //m_pMainWidget // Contains vertical splitter and horiz scrollbar
0556     QVBoxLayout* pVLayout = new QVBoxLayout(m_pMainWidget);
0557     pVLayout->setContentsMargins(0, 0, 0, 0);
0558     pVLayout->setSpacing(0);
0559 
0560     QSplitter* pVSplitter = new QSplitter();
0561     pVSplitter->setObjectName("VSplitter");
0562     pVSplitter->setOpaqueResize(false);
0563     pVSplitter->setOrientation(Qt::Vertical);
0564     pVLayout->addWidget(pVSplitter);
0565 
0566     QWidget* pDiffWindowFrame = new QWidget(); // Contains diff windows, overview and vert scrollbar
0567     pDiffWindowFrame->setObjectName("DiffWindowFrame");
0568     QHBoxLayout* pDiffHLayout = new QHBoxLayout(pDiffWindowFrame);
0569     pDiffHLayout->setContentsMargins(0, 0, 0, 0);
0570     pDiffHLayout->setSpacing(0);
0571     pVSplitter->addWidget(pDiffWindowFrame);
0572 
0573     m_pDiffWindowSplitter = new QSplitter();
0574     m_pDiffWindowSplitter->setObjectName("DiffWindowSplitter");
0575     m_pDiffWindowSplitter->setOpaqueResize(false);
0576 
0577     m_pDiffWindowSplitter->setOrientation(gOptions->m_bHorizDiffWindowSplitting ? Qt::Horizontal : Qt::Vertical);
0578     pDiffHLayout->addWidget(m_pDiffWindowSplitter);
0579 
0580     m_pOverview = new Overview();
0581     m_pOverview->setObjectName("Overview");
0582     pDiffHLayout->addWidget(m_pOverview);
0583 
0584     DiffTextWindow::mVScrollBar = new QScrollBar(Qt::Vertical, pDiffWindowFrame);
0585     pDiffHLayout->addWidget(DiffTextWindow::mVScrollBar);
0586 
0587     chk_connect_a(m_pOverview, &Overview::setLine, DiffTextWindow::mVScrollBar, &QScrollBar::setValue);
0588     chk_connect_a(this, &KDiff3App::showWhiteSpaceToggled, m_pOverview, &Overview::slotRedraw);
0589     chk_connect_a(this, &KDiff3App::changeOverViewMode, m_pOverview, &Overview::setOverviewMode);
0590 
0591     m_pDiffTextWindowFrame1 = new DiffTextWindowFrame(m_pDiffWindowSplitter, e_SrcSelector::A, m_sd1, *this);
0592     m_pDiffWindowSplitter->addWidget(m_pDiffTextWindowFrame1);
0593     m_pDiffTextWindowFrame2 = new DiffTextWindowFrame(m_pDiffWindowSplitter, e_SrcSelector::B, m_sd2, *this);
0594     m_pDiffWindowSplitter->addWidget(m_pDiffTextWindowFrame2);
0595     m_pDiffTextWindowFrame3 = new DiffTextWindowFrame(m_pDiffWindowSplitter, e_SrcSelector::C, m_sd3, *this);
0596     m_pDiffWindowSplitter->addWidget(m_pDiffTextWindowFrame3);
0597     m_pDiffTextWindow1 = m_pDiffTextWindowFrame1->getDiffTextWindow();
0598     m_pDiffTextWindow2 = m_pDiffTextWindowFrame2->getDiffTextWindow();
0599     m_pDiffTextWindow3 = m_pDiffTextWindowFrame3->getDiffTextWindow();
0600 
0601     m_pDiffTextWindowFrame1->setupConnections(this);
0602     m_pDiffTextWindowFrame2->setupConnections(this);
0603     m_pDiffTextWindowFrame3->setupConnections(this);
0604 
0605     // Merge window
0606     m_pMergeWindowFrame = new QWidget(pVSplitter);
0607     m_pMergeWindowFrame->setObjectName("MergeWindowFrame");
0608     pVSplitter->addWidget(m_pMergeWindowFrame);
0609     QHBoxLayout* pMergeHLayout = new QHBoxLayout(m_pMergeWindowFrame);
0610     pMergeHLayout->setContentsMargins(0, 0, 0, 0);
0611     pMergeHLayout->setSpacing(0);
0612     QVBoxLayout* pMergeVLayout = new QVBoxLayout();
0613     pMergeHLayout->addLayout(pMergeVLayout, 1);
0614 
0615     m_pMergeResultWindowTitle = new WindowTitleWidget();
0616     pMergeVLayout->addWidget(m_pMergeResultWindowTitle);
0617 
0618     m_pMergeResultWindow = new MergeResultWindow(m_pMergeWindowFrame, statusBar());
0619     pMergeVLayout->addWidget(m_pMergeResultWindow, 1);
0620 
0621     MergeResultWindow::mVScrollBar = new QScrollBar(Qt::Vertical, m_pMergeWindowFrame);
0622     pMergeHLayout->addWidget(MergeResultWindow::mVScrollBar);
0623 
0624     autoAdvance->setEnabled(true);
0625 
0626     QList<qint32> sizes = pVSplitter->sizes();
0627     qint32 total = sizes[0] + sizes[1];
0628     if(total < 10)
0629         total = 100;
0630     sizes[0] = total / 2;
0631     sizes[1] = total / 2;
0632     pVSplitter->setSizes(sizes);
0633 
0634     QList<qint32> hSizes = {1, 1, 1};
0635 
0636     m_pDiffWindowSplitter->setSizes(hSizes);
0637 
0638     m_pMergeResultWindow->installEventFilter(m_pMergeResultWindowTitle); // for focus tracking
0639 
0640     QHBoxLayout* pHScrollBarLayout = new QHBoxLayout();
0641     pVLayout->addLayout(pHScrollBarLayout);
0642     m_pHScrollBar = new ReversibleScrollBar(Qt::Horizontal, &gOptions->m_bRightToLeftLanguage);
0643     pHScrollBarLayout->addWidget(m_pHScrollBar);
0644     m_pCornerWidget = new QWidget(m_pMainWidget);
0645     pHScrollBarLayout->addWidget(m_pCornerWidget);
0646 
0647     chk_connect_a(DiffTextWindow::mVScrollBar, &QScrollBar::valueChanged, m_pOverview, &Overview::setFirstLine);
0648     chk_connect_a(DiffTextWindow::mVScrollBar, &QScrollBar::valueChanged, m_pDiffTextWindow1, &DiffTextWindow::setFirstLine);
0649     chk_connect_a(m_pHScrollBar, &ReversibleScrollBar::valueChanged2, m_pDiffTextWindow1, &DiffTextWindow::setHorizScrollOffset);
0650     m_pDiffTextWindow1->setupConnections(this);
0651 
0652     chk_connect_a(DiffTextWindow::mVScrollBar, &QScrollBar::valueChanged, m_pDiffTextWindow2, &DiffTextWindow::setFirstLine);
0653     chk_connect_a(m_pHScrollBar, &ReversibleScrollBar::valueChanged2, m_pDiffTextWindow2, &DiffTextWindow::setHorizScrollOffset);
0654     m_pDiffTextWindow2->setupConnections(this);
0655 
0656     chk_connect_a(DiffTextWindow::mVScrollBar, &QScrollBar::valueChanged, m_pDiffTextWindow3, &DiffTextWindow::setFirstLine);
0657     chk_connect_a(m_pHScrollBar, &ReversibleScrollBar::valueChanged2, m_pDiffTextWindow3, &DiffTextWindow::setHorizScrollOffset);
0658     m_pDiffTextWindow3->setupConnections(this);
0659 
0660     MergeResultWindow* p = m_pMergeResultWindow;
0661     chk_connect_a(MergeResultWindow::mVScrollBar, &QScrollBar::valueChanged, p, &MergeResultWindow::setFirstLine);
0662 
0663     chk_connect_a(m_pHScrollBar, &ReversibleScrollBar::valueChanged2, p, &MergeResultWindow::setHorizScrollOffset);
0664     chk_connect_a(p, &MergeResultWindow::modifiedChanged, m_pMergeResultWindowTitle, &WindowTitleWidget::slotSetModified);
0665     p->setupConnections(this);
0666     sourceMask(0, 0);
0667 
0668     chk_connect_a(p, &MergeResultWindow::setFastSelectorRange, m_pDiffTextWindow1, &DiffTextWindow::setFastSelectorRange);
0669     chk_connect_a(p, &MergeResultWindow::setFastSelectorRange, m_pDiffTextWindow2, &DiffTextWindow::setFastSelectorRange);
0670     chk_connect_a(p, &MergeResultWindow::setFastSelectorRange, m_pDiffTextWindow3, &DiffTextWindow::setFastSelectorRange);
0671     chk_connect_a(m_pDiffTextWindow1, &DiffTextWindow::setFastSelectorLine, p, &MergeResultWindow::slotSetFastSelectorLine);
0672     chk_connect_a(m_pDiffTextWindow2, &DiffTextWindow::setFastSelectorLine, p, &MergeResultWindow::slotSetFastSelectorLine);
0673     chk_connect_a(m_pDiffTextWindow3, &DiffTextWindow::setFastSelectorLine, p, &MergeResultWindow::slotSetFastSelectorLine);
0674     chk_connect_a(m_pDiffTextWindow1, &DiffTextWindow::gotFocus, p, &MergeResultWindow::updateSourceMask);
0675     chk_connect_a(m_pDiffTextWindow2, &DiffTextWindow::gotFocus, p, &MergeResultWindow::updateSourceMask);
0676     chk_connect_a(m_pDiffTextWindow3, &DiffTextWindow::gotFocus, p, &MergeResultWindow::updateSourceMask);
0677     chk_connect_a(m_pDirectoryMergeInfo, &DirectoryMergeInfo::gotFocus, p, &MergeResultWindow::updateSourceMask);
0678 
0679     chk_connect_a(m_pDiffTextWindow1, &DiffTextWindow::resizeHeightChangedSignal, this, &KDiff3App::resizeDiffTextWindowHeight);
0680     // The following connects cause the wordwrap to be recalced thrice, just to make sure. Better than forgetting one.
0681     chk_connect_a(m_pDiffTextWindow1, &DiffTextWindow::resizeWidthChangedSignal, this, &KDiff3App::postRecalcWordWrap);
0682     chk_connect_a(m_pDiffTextWindow2, &DiffTextWindow::resizeWidthChangedSignal, this, &KDiff3App::postRecalcWordWrap);
0683     chk_connect_a(m_pDiffTextWindow3, &DiffTextWindow::resizeWidthChangedSignal, this, &KDiff3App::postRecalcWordWrap);
0684 
0685     m_pDiffTextWindow1->setFocus();
0686     m_pMainWidget->setMinimumSize(50, 50);
0687     m_pCornerWidget->setFixedSize(DiffTextWindow::mVScrollBar->width(), m_pHScrollBar->height());
0688     showWindowA->setChecked(true);
0689     showWindowB->setChecked(true);
0690     showWindowC->setChecked(true);
0691 }
0692 
0693 // called after word wrap is complete
0694 void KDiff3App::slotFinishMainInit()
0695 {
0696     assert(m_pDiffTextWindow1 != nullptr && DiffTextWindow::mVScrollBar != nullptr && m_pOverview != nullptr);
0697 
0698     setHScrollBarRange();
0699 
0700     qint32 newHeight = m_pDiffTextWindow1->getNofVisibleLines();
0701     /*qint32 newWidth  = m_pDiffTextWindow1->getNofVisibleColumns();*/
0702     m_DTWHeight = newHeight;
0703 
0704     DiffTextWindow::mVScrollBar->setRange(0, std::max(0, m_neededLines + 1 - newHeight));
0705     DiffTextWindow::mVScrollBar->setPageStep(newHeight);
0706     m_pOverview->setRange(DiffTextWindow::mVScrollBar->value(), DiffTextWindow::mVScrollBar->pageStep());
0707 
0708     qint32 d3l = -1;
0709     if(!m_manualDiffHelpList.empty())
0710         d3l = m_manualDiffHelpList.front().calcManualDiffFirstDiff3LineIdx(mDiff3LineVector);
0711 
0712     setUpdatesEnabled(true);
0713 
0714     if(d3l >= 0)
0715     {
0716         qint32 line = m_pDiffTextWindow1->convertDiff3LineIdxToLine(d3l);
0717         DiffTextWindow::mVScrollBar->setValue(std::max(0, line - 1));
0718     }
0719     else
0720     {
0721         m_pMergeResultWindow->slotGoTop();
0722         if(!m_outputFilename.isEmpty() && !m_pMergeResultWindow->isUnsolvedConflictAtCurrent())
0723             m_pMergeResultWindow->slotGoNextUnsolvedConflict();
0724     }
0725 
0726     if(m_pCornerWidget)
0727         m_pCornerWidget->setFixedSize(DiffTextWindow::mVScrollBar->width(), m_pHScrollBar->height());
0728 
0729     Q_EMIT updateAvailabilities();
0730     bool bVisibleMergeResultWindow = !m_outputFilename.isEmpty();
0731 
0732     if(m_bLoadFiles)
0733     {
0734         if(bVisibleMergeResultWindow)
0735             m_pMergeResultWindow->showNumberOfConflicts(!m_bAutoFlag);
0736         else if(
0737             // Avoid showing this message during startup without parameters.
0738             !(m_sd1->getAliasName().isEmpty() && m_sd2->getAliasName().isEmpty() && m_sd3->getAliasName().isEmpty()) &&
0739             (m_sd1->isValid() && m_sd2->isValid() && m_sd3->isValid()))
0740         {
0741             QString totalInfo;
0742             if(m_totalDiffStatus->isBinaryEqualAB() && m_totalDiffStatus->isBinaryEqualAC())
0743                 totalInfo += i18n("All input files are binary equal.");
0744             else if(m_totalDiffStatus->isTextEqualAB() && m_totalDiffStatus->isTextEqualAC())
0745                 totalInfo += i18n("All input files contain the same text, but are not binary equal.");
0746             else
0747             {
0748                 if(m_totalDiffStatus->isBinaryEqualAB())
0749                     totalInfo += i18n("Files %1 and %2 are binary equal.\n", QStringLiteral("A"), QStringLiteral("B"));
0750                 else if(m_totalDiffStatus->isTextEqualAB())
0751                     totalInfo += i18n("Files %1 and %2 have equal text, but are not binary equal. \n", QStringLiteral("A"), QStringLiteral("B"));
0752                 if(m_totalDiffStatus->isBinaryEqualAC())
0753                     totalInfo += i18n("Files %1 and %2 are binary equal.\n", QStringLiteral("A"), QStringLiteral("C"));
0754                 else if(m_totalDiffStatus->isTextEqualAC())
0755                     totalInfo += i18n("Files %1 and %2 have equal text, but are not binary equal. \n", QStringLiteral("A"), QStringLiteral("C"));
0756                 if(m_totalDiffStatus->isBinaryEqualBC())
0757                     totalInfo += i18n("Files %1 and %2 are binary equal.\n", QStringLiteral("B"), QStringLiteral("C"));
0758                 else if(m_totalDiffStatus->isTextEqualBC())
0759                     totalInfo += i18n("Files %1 and %2 have equal text, but are not binary equal. \n", QStringLiteral("B"), QStringLiteral("C"));
0760             }
0761 
0762             if(!totalInfo.isEmpty())
0763                 KMessageBox::information(this, totalInfo);
0764         }
0765 
0766         if(bVisibleMergeResultWindow && (!m_sd1->isText() || !m_sd2->isText() || !m_sd3->isText()))
0767         {
0768             KMessageBox::information(this, i18n(
0769                                                "Some input files do not seem to be pure text files.\n"
0770                                                "Note that the KDiff3 merge was not meant for binary data.\n"
0771                                                "Continue at your own risk."));
0772         }
0773         if(m_sd1->isIncompleteConversion() || m_sd2->isIncompleteConversion() || m_sd3->isIncompleteConversion())
0774         {
0775             QString files;
0776             if(m_sd1->isIncompleteConversion())
0777                 files += QStringLiteral("A");
0778             if(m_sd2->isIncompleteConversion())
0779                 files += files.isEmpty() ? QStringLiteral("B") : i18n(", B");
0780             if(m_sd3->isIncompleteConversion())
0781                 files += files.isEmpty() ? QStringLiteral("C") : i18n(", C");
0782 
0783             KMessageBox::information(this, i18n("Some input characters could not be converted to valid unicode.\n"
0784                                                 "You might be using the wrong codec. (e.g. UTF-8 for non UTF-8 files).\n"
0785                                                 "Do not save the result if unsure. Continue at your own risk.\n"
0786                                                 "Affected input files are in %1.",
0787                                                 files));
0788         }
0789     }
0790 
0791     if(bVisibleMergeResultWindow && m_pMergeResultWindow)
0792     {
0793         m_pMergeResultWindow->setFocus();
0794     }
0795     else if(m_pDiffTextWindow1)
0796     {
0797         m_pDiffTextWindow1->setFocus();
0798     }
0799 }
0800 
0801 void KDiff3App::resizeEvent(QResizeEvent* e)
0802 {
0803     QMainWindow::resizeEvent(e);
0804     if(m_pCornerWidget)
0805         m_pCornerWidget->setFixedSize(DiffTextWindow::mVScrollBar->width(), m_pHScrollBar->height());
0806 }
0807 
0808 void KDiff3App::wheelEvent(QWheelEvent* pWheelEvent)
0809 {
0810     pWheelEvent->accept();
0811     QPoint delta = pWheelEvent->angleDelta();
0812 
0813     //Block diagonal scrolling easily generated unintentionally with track pads.
0814     if(delta.x() != 0 && abs(delta.y()) < abs(delta.x()) && m_pHScrollBar != nullptr)
0815         QCoreApplication::sendEvent(m_pHScrollBar, pWheelEvent);
0816 }
0817 
0818 void KDiff3App::keyPressEvent(QKeyEvent* keyEvent)
0819 {
0820     bool bCtrl = (keyEvent->modifiers() & Qt::ControlModifier) != 0;
0821 
0822     switch(keyEvent->key())
0823     {
0824         case Qt::Key_Down:
0825         case Qt::Key_Up:
0826         case Qt::Key_PageDown:
0827         case Qt::Key_PageUp:
0828             if(DiffTextWindow::mVScrollBar != nullptr)
0829                 QCoreApplication::sendEvent(DiffTextWindow::mVScrollBar, keyEvent);
0830             return;
0831         case Qt::Key_Left:
0832         case Qt::Key_Right:
0833             if(m_pHScrollBar != nullptr)
0834                 QCoreApplication::sendEvent(m_pHScrollBar, keyEvent);
0835             return;
0836         case Qt::Key_End:
0837         case Qt::Key_Home:
0838             if(bCtrl)
0839             {
0840                 if(DiffTextWindow::mVScrollBar != nullptr)
0841                     QCoreApplication::sendEvent(DiffTextWindow::mVScrollBar, keyEvent);
0842             }
0843             else
0844             {
0845                 if(m_pHScrollBar != nullptr)
0846                     QCoreApplication::sendEvent(m_pHScrollBar, keyEvent);
0847             }
0848             return;
0849     }
0850 
0851     QMainWindow::keyPressEvent(keyEvent);
0852 }
0853 
0854 void KDiff3App::slotFinishDrop()
0855 {
0856     raise();
0857     mainInit(m_totalDiffStatus);
0858 }
0859 
0860 void KDiff3App::slotFileOpen()
0861 {
0862     if(!canContinue()) return;
0863 
0864     if(m_pDirectoryMergeWindow->isDirectoryMergeInProgress())
0865     {
0866         qint32 result = Compat::warningTwoActions(this,
0867                                                i18n("You are currently doing a folder merge. Are you sure, you want to abort?"),
0868                                                i18nc("Error dialog title", "Warning"),
0869                                                KGuiItem(i18n("Abort")),
0870                                                KGuiItem(i18n("Continue Merging")));
0871         if(result != Compat::PrimaryAction)
0872             return;
0873     }
0874 
0875     slotStatusMsg(i18n("Opening files..."));
0876 
0877     for(;;)
0878     {
0879         QPointer<OpenDialog> d = QPointer<OpenDialog>(new OpenDialog(this,
0880                                                                      QDir::toNativeSeparators(m_bDirCompare ? gDirInfo->dirA().prettyAbsPath() : m_sd1->isFromBuffer() ? QString("") :
0881                                                                                                                                                                          m_sd1->getAliasName()),
0882                                                                      QDir::toNativeSeparators(m_bDirCompare ? gDirInfo->dirB().prettyAbsPath() : m_sd2->isFromBuffer() ? QString("") :
0883                                                                                                                                                                          m_sd2->getAliasName()),
0884                                                                      QDir::toNativeSeparators(m_bDirCompare ? gDirInfo->dirC().prettyAbsPath() : m_sd3->isFromBuffer() ? QString("") :
0885                                                                                                                                                                          m_sd3->getAliasName()),
0886                                                                      m_bDirCompare ? !gDirInfo->destDir().prettyAbsPath().isEmpty() : !m_outputFilename.isEmpty(),
0887                                                                      QDir::toNativeSeparators(m_bDefaultFilename ? QString("") : m_outputFilename)));
0888 
0889         qint32 status = d->exec();
0890         if(status == QDialog::Accepted)
0891         {
0892             m_sd1->setFilename(d->getFileA());
0893             m_sd2->setFilename(d->getFileB());
0894             m_sd3->setFilename(d->getFileC());
0895 
0896             if(d->merge())
0897             {
0898                 if(d->getOutputFile().isEmpty())
0899                 {
0900                     m_outputFilename = "unnamed.txt";
0901                     m_bDefaultFilename = true;
0902                 }
0903                 else
0904                 {
0905                     m_outputFilename = d->getOutputFile();
0906                     m_bDefaultFilename = false;
0907                 }
0908             }
0909             else
0910                 m_outputFilename = "";
0911 
0912             m_bDirCompare = m_sd1->isDir();
0913 
0914             if(m_bDirCompare)
0915             {
0916                 bool bSuccess = doDirectoryCompare(false);
0917                 if(bSuccess)
0918                 {
0919                     m_pDirectoryMergeDock->show();
0920                     m_pDirectoryMergeInfoDock->show();
0921                     m_pMainWidget->hide();
0922                     break;
0923                 }
0924             }
0925             else
0926             {
0927                 doFileCompare();
0928 
0929                 if((!m_sd1->getErrors().isEmpty()) ||
0930                    (!m_sd2->getErrors().isEmpty()) ||
0931                    (!m_sd3->getErrors().isEmpty()))
0932                 {
0933                     QString text(i18n("Opening of these files failed:"));
0934                     text += "\n\n";
0935                     if(!m_sd1->getErrors().isEmpty())
0936                         text += " - " + m_sd1->getAliasName() + '\n' + m_sd1->getErrors().join('\n') + '\n';
0937                     if(!m_sd2->getErrors().isEmpty())
0938                         text += " - " + m_sd2->getAliasName() + '\n' + m_sd2->getErrors().join('\n') + '\n';
0939                     if(!m_sd3->getErrors().isEmpty())
0940                         text += " - " + m_sd3->getAliasName() + '\n' + m_sd3->getErrors().join('\n') + '\n';
0941 
0942                     KMessageBox::error(this, text, i18n("File open error"));
0943 
0944                     continue;
0945                 }
0946             }
0947         }
0948         break;
0949     }
0950 
0951     Q_EMIT updateAvailabilities();
0952     slotStatusMsg(i18n("Ready."));
0953 }
0954 
0955 void KDiff3App::slotFileOpen2(QStringList& errors, const QString& fn1, const QString& fn2, const QString& fn3, const QString& ofn,
0956                               const QString& an1, const QString& an2, const QString& an3, TotalDiffStatus* pTotalDiffStatus)
0957 {
0958     if(!canContinue()) return;
0959 
0960     if(fn1.isEmpty() && fn2.isEmpty() && fn3.isEmpty() && ofn.isEmpty())
0961     {
0962         m_pMainWidget->hide();
0963         return;
0964     }
0965 
0966     slotStatusMsg(i18n("Opening files..."));
0967     m_sd1->reset();
0968     m_sd2->reset();
0969     m_sd3->reset();
0970 
0971     m_sd1->setFilename(fn1);
0972     m_sd2->setFilename(fn2);
0973     m_sd3->setFilename(fn3);
0974 
0975     m_sd1->setAliasName(an1);
0976     m_sd2->setAliasName(an2);
0977     m_sd3->setAliasName(an3);
0978 
0979     if(!ofn.isEmpty())
0980     {
0981         m_outputFilename = ofn;
0982         m_bDefaultFilename = false;
0983     }
0984     else
0985     {
0986         m_outputFilename = "";
0987         m_bDefaultFilename = true;
0988     }
0989 
0990     if(!m_sd1->isDir())
0991     {
0992         improveFilenames();
0993         //KDiff3App::slotFileOpen2 needs to handle both GUI and non-GUI diffs.
0994         if(pTotalDiffStatus == nullptr)
0995             mainInit(m_totalDiffStatus);
0996         else
0997             mainInit(pTotalDiffStatus, InitFlag::loadFiles | InitFlag::autoSolve);
0998 
0999         errors.append(mErrors);
1000 
1001         if(m_bDirCompare)
1002         {
1003             errors.append(m_sd1->getErrors());
1004             errors.append(m_sd2->getErrors());
1005             errors.append(m_sd3->getErrors());
1006 
1007             return;
1008         }
1009 
1010         if(m_sd1->isValid() && m_sd2->isValid() && m_sd3->isValid())
1011         {
1012             if(m_pDirectoryMergeWindow != nullptr && m_pDirectoryMergeWindow->isVisible() && !dirShowBoth->isChecked())
1013             {
1014                 slotDirViewToggle();
1015             }
1016         }
1017     }
1018     else
1019         doDirectoryCompare(true); // Create new window for KDiff3 for directory comparison.
1020 
1021     slotStatusMsg(i18n("Ready."));
1022 }
1023 
1024 void KDiff3App::slotFileNameChanged(const QString& fileName, e_SrcSelector winIdx)
1025 {
1026     QStringList errors;
1027     QString fn1 = m_sd1->getFilename();
1028     QString an1 = m_sd1->getAliasName();
1029     QString fn2 = m_sd2->getFilename();
1030     QString an2 = m_sd2->getAliasName();
1031     QString fn3 = m_sd3->getFilename();
1032     QString an3 = m_sd3->getAliasName();
1033 
1034     if(winIdx == e_SrcSelector::A)
1035     {
1036         fn1 = fileName;
1037         an1 = "";
1038     }
1039     else if(winIdx == e_SrcSelector::B)
1040     {
1041         fn2 = fileName;
1042         an2 = "";
1043     }
1044     else if(winIdx == e_SrcSelector::C)
1045     {
1046         fn3 = fileName;
1047         an3 = "";
1048     }
1049 
1050     slotFileOpen2(errors, fn1, fn2, fn3, m_outputFilename, an1, an2, an3, nullptr);
1051 }
1052 
1053 void KDiff3App::slotEditCut()
1054 {
1055     slotStatusMsg(i18n("Cutting selection..."));
1056     Q_EMIT cut();
1057     slotStatusMsg(i18n("Ready."));
1058 }
1059 
1060 void KDiff3App::slotEditCopy()
1061 {
1062     slotStatusMsg(i18n("Copying selection to clipboard..."));
1063 
1064     Q_EMIT copy();
1065 
1066     slotStatusMsg(i18n("Ready."));
1067 }
1068 
1069 void KDiff3App::slotEditPaste()
1070 {
1071     slotStatusMsg(i18n("Inserting clipboard contents..."));
1072 
1073     if(m_pMergeResultWindow != nullptr && m_pMergeResultWindow->isVisible())
1074     {
1075         m_pMergeResultWindow->pasteClipboard(false);
1076     }
1077     else
1078     {
1079         if(canContinue())
1080         {
1081             QString error;
1082             bool do_init = false;
1083 
1084             if(m_pDiffTextWindow1->hasFocus())
1085             {
1086                 m_sd1->setData(QApplication::clipboard()->text(QClipboard::Clipboard));
1087                 const QStringList& errors = m_sd1->getErrors();
1088                 if(!errors.isEmpty())
1089                     error = m_sd1->getErrors()[0];
1090 
1091                 do_init = true;
1092             }
1093             else if(m_pDiffTextWindow2->hasFocus())
1094             {
1095                 m_sd2->setData(QApplication::clipboard()->text(QClipboard::Clipboard));
1096                 const QStringList& errors = m_sd2->getErrors();
1097                 if(!errors.isEmpty())
1098                     error = m_sd2->getErrors()[0];
1099 
1100                 do_init = true;
1101             }
1102             else if(m_pDiffTextWindow3->hasFocus())
1103             {
1104                 m_sd3->setData(QApplication::clipboard()->text(QClipboard::Clipboard));
1105                 const QStringList& errors = m_sd3->getErrors();
1106                 if(!errors.isEmpty())
1107                     error = m_sd3->getErrors()[0];
1108 
1109                 do_init = true;
1110             }
1111 
1112             if(!error.isEmpty())
1113             {
1114                 KMessageBox::error(m_pOptionDialog, error);
1115             }
1116 
1117             if(do_init)
1118             {
1119                 mainInit(m_totalDiffStatus);
1120             }
1121         }
1122     }
1123 
1124     slotStatusMsg(i18n("Ready."));
1125 }
1126 
1127 void KDiff3App::slotEditSelectAll()
1128 {
1129 
1130     Q_EMIT selectAll();
1131 
1132     slotStatusMsg(i18n("Ready."));
1133 }
1134 
1135 void KDiff3App::slotGoCurrent()
1136 {
1137     Q_EMIT goCurrent();
1138 }
1139 
1140 void KDiff3App::slotGoTop()
1141 {
1142     Q_EMIT goTop();
1143 }
1144 
1145 void KDiff3App::slotGoBottom()
1146 {
1147     Q_EMIT goBottom();
1148 }
1149 
1150 void KDiff3App::slotGoPrevUnsolvedConflict()
1151 {
1152     Q_EMIT goPrevUnsolvedConflict();
1153 }
1154 
1155 void KDiff3App::slotGoNextUnsolvedConflict()
1156 {
1157     m_bTimerBlock = false;
1158     Q_EMIT goNextUnsolvedConflict();
1159 }
1160 
1161 void KDiff3App::slotGoPrevConflict()
1162 {
1163     Q_EMIT goPrevConflict();
1164 }
1165 
1166 void KDiff3App::slotGoNextConflict()
1167 {
1168     m_bTimerBlock = false;
1169     Q_EMIT goNextConflict();
1170 }
1171 
1172 void KDiff3App::slotGoPrevDelta()
1173 {
1174     Q_EMIT goPrevDelta();
1175 }
1176 
1177 void KDiff3App::slotGoNextDelta()
1178 {
1179     Q_EMIT goNextDelta();
1180 }
1181 
1182 void KDiff3App::slotGoToLine()
1183 {
1184     QDialog pDialog;
1185     QVBoxLayout* l = new QVBoxLayout(&pDialog);
1186 
1187     QLineEdit* pLineNumEdit = new QLineEdit();
1188     //Limit input to valid 1 based line numbers
1189     pLineNumEdit->setValidator(new QIntValidator(1, DiffTextWindow::mVScrollBar->maximum(), pLineNumEdit));
1190 
1191     QPushButton* pOkButton = new QPushButton(i18n("Ok"));
1192     l->addWidget(pLineNumEdit);
1193     l->addWidget(pOkButton);
1194 
1195     chk_connect(pOkButton, &QPushButton::clicked, &pDialog,
1196                 ([&pDialog, pLineNumEdit]() {
1197                     if(pLineNumEdit->text() != "")
1198                     {
1199                         qint32 lineNum = pLineNumEdit->text().toInt();
1200                         lineNum = qMax(lineNum - 2, 0);
1201                         //No need for anything else here setValue triggers a valueChanged signal internally.
1202                         DiffTextWindow::mVScrollBar->setValue(lineNum);
1203                     }
1204                     pDialog.close();
1205                 }));
1206 
1207     pDialog.setWindowTitle(i18n("Go to Line"));
1208     pDialog.setWindowFlag(Qt::WindowContextHelpButtonHint, false);
1209     pDialog.setFixedSize(260, 110);
1210     pDialog.exec();
1211 }
1212 
1213 void KDiff3App::choose(e_SrcSelector choice)
1214 {
1215     if(!m_bTimerBlock)
1216     {
1217         if(m_pDirectoryMergeWindow && m_pDirectoryMergeWindow->hasFocus())
1218         {
1219             if(choice == e_SrcSelector::A) m_pDirectoryMergeWindow->slotCurrentChooseA();
1220             if(choice == e_SrcSelector::B) m_pDirectoryMergeWindow->slotCurrentChooseB();
1221             if(choice == e_SrcSelector::C) m_pDirectoryMergeWindow->slotCurrentChooseC();
1222 
1223             chooseA->setChecked(false);
1224             chooseB->setChecked(false);
1225             chooseC->setChecked(false);
1226         }
1227         else if(m_pMergeResultWindow)
1228         {
1229             m_pMergeResultWindow->choose(choice);
1230             if(autoAdvance->isChecked())
1231             {
1232                 m_bTimerBlock = true;
1233                 QTimer::singleShot(gOptions->m_autoAdvanceDelay, this, &KDiff3App::slotGoNextUnsolvedConflict);
1234             }
1235         }
1236     }
1237 }
1238 
1239 void KDiff3App::slotChooseA()
1240 {
1241     choose(e_SrcSelector::A);
1242 }
1243 void KDiff3App::slotChooseB()
1244 {
1245     choose(e_SrcSelector::B);
1246 }
1247 void KDiff3App::slotChooseC()
1248 {
1249     choose(e_SrcSelector::C);
1250 }
1251 
1252 void KDiff3App::slotAutoSolve()
1253 {
1254     Q_EMIT autoSolve();
1255 
1256     Q_EMIT updateAvailabilities();
1257 }
1258 
1259 void KDiff3App::slotUnsolve()
1260 {
1261     Q_EMIT unsolve();
1262 }
1263 
1264 void KDiff3App::slotMergeHistory()
1265 {
1266     Q_EMIT mergeHistory();
1267 }
1268 
1269 void KDiff3App::slotRegExpAutoMerge()
1270 {
1271     Q_EMIT regExpAutoMerge();
1272 }
1273 
1274 void KDiff3App::slotSplitDiff()
1275 {
1276     LineRef firstLine;
1277     LineRef lastLine;
1278     QPointer<DiffTextWindow> pDTW = nullptr;
1279     if(m_pDiffTextWindow1)
1280     {
1281         pDTW = m_pDiffTextWindow1;
1282         pDTW->getSelectionRange(&firstLine, &lastLine, eD3LLineCoords);
1283     }
1284     if(!firstLine.isValid() && m_pDiffTextWindow2)
1285     {
1286         pDTW = m_pDiffTextWindow2;
1287         pDTW->getSelectionRange(&firstLine, &lastLine, eD3LLineCoords);
1288     }
1289     if(!firstLine.isValid() && m_pDiffTextWindow3)
1290     {
1291         pDTW = m_pDiffTextWindow3;
1292         pDTW->getSelectionRange(&firstLine, &lastLine, eD3LLineCoords);
1293     }
1294     if(pDTW && firstLine.isValid() && m_pMergeResultWindow)
1295     {
1296         pDTW->resetSelection();
1297 
1298         m_pMergeResultWindow->slotSplitDiff(firstLine, lastLine);
1299     }
1300 }
1301 
1302 void KDiff3App::slotJoinDiffs()
1303 {
1304     LineRef firstLine;
1305     LineRef lastLine;
1306     QPointer<DiffTextWindow> pDTW = nullptr;
1307     if(m_pDiffTextWindow1)
1308     {
1309         pDTW = m_pDiffTextWindow1;
1310         pDTW->getSelectionRange(&firstLine, &lastLine, eD3LLineCoords);
1311     }
1312     if(!firstLine.isValid() && m_pDiffTextWindow2)
1313     {
1314         pDTW = m_pDiffTextWindow2;
1315         pDTW->getSelectionRange(&firstLine, &lastLine, eD3LLineCoords);
1316     }
1317     if(!firstLine.isValid() && m_pDiffTextWindow3)
1318     {
1319         pDTW = m_pDiffTextWindow3;
1320         pDTW->getSelectionRange(&firstLine, &lastLine, eD3LLineCoords);
1321     }
1322     if(pDTW && firstLine.isValid() && m_pMergeResultWindow)
1323     {
1324         pDTW->resetSelection();
1325 
1326         m_pMergeResultWindow->slotJoinDiffs(firstLine, lastLine);
1327     }
1328 }
1329 
1330 void KDiff3App::slotConfigure()
1331 {
1332     m_pOptionDialog->setState();
1333     m_pOptionDialog->setMinimumHeight(m_pOptionDialog->minimumHeight() + 40);
1334     m_pOptionDialog->exec();
1335     mEscapeAction->setEnabled(gOptions->m_bEscapeKeyQuits);
1336     slotRefresh();
1337 }
1338 
1339 void KDiff3App::slotConfigureKeys()
1340 {
1341     KShortcutsDialog::showDialog(actionCollection(), KShortcutsEditor::LetterShortcutsDisallowed, this);
1342 }
1343 
1344 void KDiff3App::slotRefresh()
1345 {
1346     QApplication::setFont(gOptions->appFont());
1347 
1348     Q_EMIT doRefresh();
1349 
1350     if(m_pHScrollBar != nullptr)
1351     {
1352         m_pHScrollBar->setAgain();
1353     }
1354     if(m_pDiffWindowSplitter != nullptr)
1355     {
1356         m_pDiffWindowSplitter->setOrientation(gOptions->m_bHorizDiffWindowSplitting ? Qt::Horizontal : Qt::Vertical);
1357     }
1358 }
1359 
1360 void KDiff3App::slotSelectionStart()
1361 {
1362     const QObject* s = sender();
1363     if(s == nullptr) return;
1364 
1365     if(s != m_pDiffTextWindow1) m_pDiffTextWindow1->resetSelection();
1366     if(s != m_pDiffTextWindow2) m_pDiffTextWindow2->resetSelection();
1367     if(s != m_pDiffTextWindow3) m_pDiffTextWindow3->resetSelection();
1368     if(s != m_pMergeResultWindow) m_pMergeResultWindow->resetSelection();
1369 }
1370 
1371 void KDiff3App::slotSelectionEnd()
1372 {
1373     if(gOptions->m_bAutoCopySelection)
1374     {
1375         slotEditCopy();
1376     }
1377     else
1378     {
1379         QClipboard* clipBoard = QApplication::clipboard();
1380 
1381         if(clipBoard->supportsSelection())
1382         {
1383             QString sCurSelection = getSelection();
1384 
1385             if(!sCurSelection.isEmpty())
1386             {
1387                 clipBoard->setText(sCurSelection, QClipboard::Selection);
1388             }
1389         }
1390     }
1391 
1392     Q_EMIT updateAvailabilities();
1393 }
1394 
1395 void KDiff3App::slotClipboardChanged()
1396 {
1397     const QClipboard* clipboard = QApplication::clipboard();
1398     const QMimeData* mimeData = clipboard->mimeData();
1399     if(mimeData && mimeData->hasText())
1400     {
1401         QString s = clipboard->text();
1402         editPaste->setEnabled(!s.isEmpty());
1403     }
1404     else
1405     {
1406         editPaste->setEnabled(false);
1407     }
1408 }
1409 
1410 void KDiff3App::slotOutputModified(bool bModified)
1411 {
1412     if(bModified && !m_bOutputModified)
1413     {
1414         m_bOutputModified = true;
1415         Q_EMIT updateAvailabilities();
1416     }
1417 }
1418 
1419 void KDiff3App::slotAutoAdvanceToggled()
1420 {
1421     gOptions->m_bAutoAdvance = autoAdvance->isChecked();
1422 }
1423 
1424 void KDiff3App::slotWordWrapToggled()
1425 {
1426     gOptions->setWordWrap(wordWrap->isChecked());
1427     postRecalcWordWrap();
1428 }
1429 
1430 // Enable or disable all widgets except the status bar widget.
1431 void KDiff3App::mainWindowEnable(bool bEnable)
1432 {
1433     if(QMainWindow* pWindow = dynamic_cast<QMainWindow*>(window()))
1434     {
1435         QWidget* pStatusBarWidget = pWindow->statusBar();
1436         pWindow->setEnabled(bEnable);
1437         pStatusBarWidget->setEnabled(true);
1438     }
1439 }
1440 
1441 void KDiff3App::postRecalcWordWrap()
1442 {
1443     if(!m_bRecalcWordWrapPosted)
1444     {
1445         while(DiffTextWindow::maxThreads() > 0) {} //Clear wordwrap threads.
1446         m_bRecalcWordWrapPosted = true;
1447         m_firstD3LIdx = -1;
1448         Q_EMIT sigRecalcWordWrap();
1449     }
1450     else
1451     {
1452         g_pProgressDialog->cancel(ProgressDialog::eResize);
1453     }
1454 }
1455 
1456 void KDiff3App::slotRecalcWordWrap()
1457 {
1458     recalcWordWrap();
1459 }
1460 
1461 // visibleTextWidthForPrinting is >=0 only for printing, otherwise the really visible width is used
1462 void KDiff3App::recalcWordWrap(qint32 visibleTextWidthForPrinting)
1463 {
1464     m_bRecalcWordWrapPosted = true;
1465     mainWindowEnable(false);
1466 
1467     if(m_firstD3LIdx < 0)
1468     {
1469         m_firstD3LIdx = 0;
1470         if(!m_pDiffTextWindow1)
1471             return; //Nothing that happens from here on makes any sense if we don't have a DiffTextWindow.
1472 
1473         m_firstD3LIdx = m_pDiffTextWindow1->convertLineToDiff3LineIdx(m_pDiffTextWindow1->getFirstLine());
1474     }
1475 
1476     // Convert selection to D3L-coords (converting back happens in DiffTextWindow::recalcWordWrap()
1477     if(m_pDiffTextWindow1)
1478         m_pDiffTextWindow1->convertSelectionToD3LCoords();
1479     if(m_pDiffTextWindow2)
1480         m_pDiffTextWindow2->convertSelectionToD3LCoords();
1481     if(m_pDiffTextWindow3)
1482         m_pDiffTextWindow3->convertSelectionToD3LCoords();
1483 
1484     g_pProgressDialog->clearCancelState(); // clear cancelled state if previously set
1485 
1486     if(!m_diff3LineList.empty())
1487     {
1488         if(gOptions->wordWrapOn())
1489         {
1490             m_diff3LineList.recalcWordWrap(true);
1491 
1492             // Let every window calc how many lines will be needed.
1493             if(m_pDiffTextWindow1)
1494             {
1495                 m_pDiffTextWindow1->recalcWordWrap(true, 0, visibleTextWidthForPrinting);
1496             }
1497             if(m_pDiffTextWindow2)
1498             {
1499                 m_pDiffTextWindow2->recalcWordWrap(true, 0, visibleTextWidthForPrinting);
1500             }
1501             if(m_pDiffTextWindow3)
1502             {
1503                 m_pDiffTextWindow3->recalcWordWrap(true, 0, visibleTextWidthForPrinting);
1504             }
1505         }
1506         else
1507         {
1508             m_neededLines = SafeInt<LineType>(m_diff3LineList.size());
1509             if(m_pDiffTextWindow1)
1510                 m_pDiffTextWindow1->recalcWordWrap(false, 0, 0);
1511             if(m_pDiffTextWindow2)
1512                 m_pDiffTextWindow2->recalcWordWrap(false, 0, 0);
1513             if(m_pDiffTextWindow3)
1514                 m_pDiffTextWindow3->recalcWordWrap(false, 0, 0);
1515         }
1516         mRunnablesStarted = DiffTextWindow::startRunnables();
1517         if(!mRunnablesStarted)
1518             slotFinishRecalcWordWrap(visibleTextWidthForPrinting);
1519         else
1520         {
1521             g_pProgressDialog->setInformation(gOptions->wordWrapOn() ? i18n("Word wrap (Cancel disables word wrap)") : i18n("Calculating max width for horizontal scrollbar"),
1522                                               false);
1523         }
1524     }
1525     else
1526     {
1527         //don't leave processing incomplete if m_diff3LineList isEmpty as when an error occurs during reading.
1528         slotFinishRecalcWordWrap(visibleTextWidthForPrinting);
1529     }
1530 }
1531 
1532 void KDiff3App::slotFinishRecalcWordWrap(qint32 visibleTextWidthForPrinting)
1533 {
1534     assert(m_firstD3LIdx >= 0);
1535 
1536     if(mRunnablesStarted)
1537     {
1538         ProgressProxy::endBackgroundTask();
1539         mRunnablesStarted = false;
1540     }
1541 
1542     if(gOptions->wordWrapOn() && g_pProgressDialog->wasCancelled())
1543     {
1544         if(g_pProgressDialog->cancelReason() == ProgressDialog::eUserAbort)
1545         {
1546             wordWrap->setChecked(false);
1547             gOptions->setWordWrap(wordWrap->isChecked());
1548         }
1549 
1550         Q_EMIT sigRecalcWordWrap();
1551         return;
1552     }
1553     else
1554     {
1555         m_bRecalcWordWrapPosted = false;
1556     }
1557 
1558     g_pProgressDialog->setStayHidden(false);
1559 
1560     const bool bPrinting = visibleTextWidthForPrinting >= 0;
1561 
1562     if(!m_diff3LineList.empty())
1563     {
1564         if(gOptions->wordWrapOn())
1565         {
1566             LineType sumOfLines = m_diff3LineList.recalcWordWrap(false);
1567 
1568             // Finish the word wrap
1569             if(m_pDiffTextWindow1)
1570                 m_pDiffTextWindow1->recalcWordWrap(true, sumOfLines, visibleTextWidthForPrinting);
1571             if(m_pDiffTextWindow2)
1572                 m_pDiffTextWindow2->recalcWordWrap(true, sumOfLines, visibleTextWidthForPrinting);
1573             if(m_pDiffTextWindow3)
1574                 m_pDiffTextWindow3->recalcWordWrap(true, sumOfLines, visibleTextWidthForPrinting);
1575 
1576             m_neededLines = sumOfLines;
1577         }
1578         else
1579         {
1580             if(m_pDiffTextWindow1)
1581                 m_pDiffTextWindow1->recalcWordWrap(false, 1, 0);
1582             if(m_pDiffTextWindow2)
1583                 m_pDiffTextWindow2->recalcWordWrap(false, 1, 0);
1584             if(m_pDiffTextWindow3)
1585                 m_pDiffTextWindow3->recalcWordWrap(false, 1, 0);
1586         }
1587         slotStatusMsg(QString());
1588     }
1589 
1590     if(!bPrinting)
1591     {
1592         if(m_pOverview)
1593             m_pOverview->slotRedraw();
1594         if(DiffTextWindow::mVScrollBar)
1595             DiffTextWindow::mVScrollBar->setRange(0, std::max<qint32>(0, SafeInt<qint32>(m_neededLines + 1 - m_DTWHeight)));
1596         if(m_pDiffTextWindow1)
1597         {
1598             if(DiffTextWindow::mVScrollBar)
1599                 DiffTextWindow::mVScrollBar->setValue(m_pDiffTextWindow1->convertDiff3LineIdxToLine(m_firstD3LIdx));
1600 
1601             setHScrollBarRange();
1602             m_pHScrollBar->setValue(0);
1603         }
1604     }
1605     mainWindowEnable(true);
1606 
1607     if(m_bFinishMainInit)
1608     {
1609         m_bFinishMainInit = false;
1610         slotFinishMainInit();
1611     }
1612     if(m_pEventLoopForPrinting)
1613         m_pEventLoopForPrinting->quit();
1614 }
1615 
1616 void KDiff3App::slotShowWhiteSpaceToggled()
1617 {
1618     gOptions->m_bShowWhiteSpaceCharacters = showWhiteSpaceCharacters->isChecked();
1619     gOptions->m_bShowWhiteSpace = showWhiteSpace->isChecked();
1620 
1621     Q_EMIT showWhiteSpaceToggled();
1622 }
1623 
1624 void KDiff3App::slotShowLineNumbersToggled()
1625 {
1626     gOptions->m_bShowLineNumbers = showLineNumbers->isChecked();
1627 
1628     if(wordWrap->isChecked())
1629         recalcWordWrap();
1630 
1631     Q_EMIT showLineNumbersToggled();
1632 }
1633 
1634 /// Return true for success, else false
1635 bool KDiff3App::doDirectoryCompare(const bool bCreateNewInstance)
1636 {
1637     FileAccess f1(m_sd1->getFilename());
1638     FileAccess f2(m_sd2->getFilename());
1639     FileAccess f3(m_sd3->getFilename());
1640     FileAccess f4(m_outputFilename);
1641 
1642     assert(f1.isDir());
1643 
1644     if(bCreateNewInstance)
1645     {
1646         Q_EMIT createNewInstance(f1.absoluteFilePath(), f2.absoluteFilePath(), f3.absoluteFilePath());
1647     }
1648     else
1649     {
1650         //Only a debugging aid now. Used to insure m_bDirCompare is not changed
1651         [[maybe_unused]] const bool bDirCompare = m_bDirCompare;
1652 
1653         FileAccess destDir;
1654 
1655         if(!m_bDefaultFilename) destDir = f4;
1656         m_pDirectoryMergeDock->show();
1657         m_pDirectoryMergeInfoDock->show();
1658         m_pMainWidget->hide();
1659         setUpdatesEnabled(true);
1660 
1661         (*gDirInfo) = DirectoryInfo(f1, f2, f3, destDir);
1662 
1663         bool bSuccess = m_pDirectoryMergeWindow->init(
1664             !m_outputFilename.isEmpty());
1665         //This is a bug if it still happens.
1666         assert(m_bDirCompare == bDirCompare);
1667 
1668         if(bSuccess)
1669         {
1670             m_sd1->reset();
1671             if(m_pDiffTextWindow1 != nullptr)
1672             {
1673                 m_pDiffTextWindow1->init(QString(""), nullptr, eLineEndStyleDos, nullptr, 0, nullptr, nullptr);
1674                 m_pDiffTextWindowFrame1->init();
1675             }
1676             m_sd2->reset();
1677             if(m_pDiffTextWindow2 != nullptr)
1678             {
1679                 m_pDiffTextWindow2->init(QString(""), nullptr, eLineEndStyleDos, nullptr, 0, nullptr, nullptr);
1680                 m_pDiffTextWindowFrame2->init();
1681             }
1682             m_sd3->reset();
1683             if(m_pDiffTextWindow3 != nullptr)
1684             {
1685                 m_pDiffTextWindow3->init(QString(""), nullptr, eLineEndStyleDos, nullptr, 0, nullptr, nullptr);
1686                 m_pDiffTextWindowFrame3->init();
1687             }
1688         }
1689         Q_EMIT updateAvailabilities();
1690         return bSuccess;
1691     }
1692 
1693     return true;
1694 }
1695 /*
1696     If A is targetted to an existing file and the paths point to directories attempt to find that file in the corresponding
1697     directory. If it exists then the filename from A will be appended to the path.
1698 */
1699 void KDiff3App::improveFilenames()
1700 {
1701     FileAccess f1(m_sd1->getFilename());
1702     FileAccess f2(m_sd2->getFilename());
1703     FileAccess f3(m_sd3->getFilename());
1704     FileAccess f4(m_outputFilename);
1705 
1706     if(f1.isFile() && f1.exists())
1707     {
1708         if(f2.isDir())
1709         {
1710             f2.addPath(f1.fileName());
1711             if(f2.isFile() && f2.exists())
1712                 m_sd2->setFileAccess(f2);
1713         }
1714         if(f3.isDir())
1715         {
1716             f3.addPath(f1.fileName());
1717             if(f3.isFile() && f3.exists())
1718                 m_sd3->setFileAccess(f3);
1719         }
1720         if(f4.isDir())
1721         {
1722             f4.addPath(f1.fileName());
1723             if(f4.isFile() && f4.exists())
1724                 m_outputFilename = f4.absoluteFilePath();
1725         }
1726     }
1727 }
1728 
1729 void KDiff3App::slotReload()
1730 {
1731     if(!canContinue()) return;
1732 
1733     mainInit(m_totalDiffStatus);
1734 }
1735 
1736 bool KDiff3App::canContinue()
1737 {
1738     // First test if anything must be saved.
1739     if(m_bOutputModified)
1740     {
1741         qint32 result = Compat::warningTwoActionsCancel(this,
1742                                                      i18n("The merge result has not been saved."),
1743                                                      i18nc("Error dialog title", "Warning"),
1744                                                      KGuiItem(i18n("Save && Continue")),
1745                                                      KGuiItem(i18n("Continue Without Saving")));
1746         if(result == KMessageBox::Cancel)
1747             return false;
1748         else if(result == Compat::PrimaryAction)
1749         {
1750             slotFileSave();
1751             if(m_bOutputModified)
1752             {
1753                 KMessageBox::error(this, i18n("Saving the merge result failed."), i18nc("Error dialog title", "Warning"));
1754                 return false;
1755             }
1756         }
1757     }
1758 
1759     m_bOutputModified = false;
1760     return true;
1761 }
1762 
1763 void KDiff3App::slotDirShowBoth()
1764 {
1765     if(dirShowBoth->isChecked())
1766     {
1767         if(m_pDirectoryMergeDock)
1768             m_pDirectoryMergeDock->setVisible(m_bDirCompare);
1769         if(m_pDirectoryMergeInfoDock)
1770             m_pDirectoryMergeInfoDock->setVisible(m_bDirCompare);
1771 
1772         m_pMainWidget->show();
1773     }
1774     else
1775     {
1776         bool bTextDataAvailable = (m_sd1->hasData() || m_sd2->hasData() || m_sd3->hasData());
1777         if(bTextDataAvailable)
1778         {
1779             m_pMainWidget->show();
1780             m_pDirectoryMergeDock->hide();
1781             m_pDirectoryMergeInfoDock->hide();
1782         }
1783         else if(m_bDirCompare)
1784         {
1785             m_pDirectoryMergeDock->show();
1786             m_pDirectoryMergeInfoDock->show();
1787         }
1788     }
1789 
1790     Q_EMIT updateAvailabilities();
1791 }
1792 
1793 void KDiff3App::slotDirViewToggle()
1794 {
1795     if(m_bDirCompare)
1796     {
1797         if(!m_pDirectoryMergeDock->isVisible())
1798         {
1799             m_pDirectoryMergeDock->show();
1800             m_pDirectoryMergeInfoDock->show();
1801             m_pMainWidget->hide();
1802         }
1803         else
1804         {
1805             m_pDirectoryMergeDock->hide();
1806             m_pDirectoryMergeInfoDock->hide();
1807             m_pMainWidget->show();
1808         }
1809     }
1810     Q_EMIT updateAvailabilities();
1811 }
1812 
1813 void KDiff3App::slotShowWindowAToggled()
1814 {
1815     if(m_pDiffTextWindow1 != nullptr)
1816     {
1817         m_pDiffTextWindowFrame1->setVisible(showWindowA->isChecked());
1818         Q_EMIT updateAvailabilities();
1819     }
1820 }
1821 
1822 void KDiff3App::slotShowWindowBToggled()
1823 {
1824     if(m_pDiffTextWindow2 != nullptr)
1825     {
1826         m_pDiffTextWindowFrame2->setVisible(showWindowB->isChecked());
1827         Q_EMIT updateAvailabilities();
1828     }
1829 }
1830 
1831 void KDiff3App::slotShowWindowCToggled()
1832 {
1833     if(m_pDiffTextWindow3 != nullptr)
1834     {
1835         m_pDiffTextWindowFrame3->setVisible(showWindowC->isChecked());
1836         Q_EMIT updateAvailabilities();
1837     }
1838 }
1839 
1840 void KDiff3App::slotEditFind()
1841 {
1842     m_pFindDialog->restartFind();
1843 
1844     // Use currently selected text:
1845     QString sCurSelection = getSelection();
1846 
1847     if(!sCurSelection.isEmpty() && !sCurSelection.contains('\n'))
1848     {
1849         m_pFindDialog->m_pSearchString->setText(sCurSelection);
1850     }
1851 
1852     if(QDialog::Accepted == m_pFindDialog->exec())
1853     {
1854         slotEditFindNext();
1855     }
1856 }
1857 
1858 void KDiff3App::slotEditFindNext()
1859 {
1860     QString s = m_pFindDialog->m_pSearchString->text();
1861     if(s.isEmpty())
1862     {
1863         slotEditFind();
1864         return;
1865     }
1866 
1867     bool bDirDown = true;
1868     bool bCaseSensitive = m_pFindDialog->m_pCaseSensitive->isChecked();
1869 
1870     LineRef d3vLine = m_pFindDialog->currentLine;
1871     QtSizeType posInLine = m_pFindDialog->currentPos;
1872     LineRef l;
1873     QtSizeType p = 0;
1874     if(m_pFindDialog->getCurrentWindow() == eWindowIndex::A)
1875     {
1876         if(m_pFindDialog->m_pSearchInA->isChecked() && m_pDiffTextWindow1 != nullptr &&
1877            m_pDiffTextWindow1->findString(s, d3vLine, posInLine, bDirDown, bCaseSensitive))
1878         {
1879             m_pDiffTextWindow1->setSelection(d3vLine, posInLine, d3vLine, posInLine + s.length(), l, p);
1880             DiffTextWindow::mVScrollBar->setValue(l - DiffTextWindow::mVScrollBar->pageStep() / 2);
1881             m_pHScrollBar->setValue(std::max<SafeInt<qint32>>(0, p + s.length() - m_pHScrollBar->pageStep()));
1882             m_pFindDialog->currentLine = d3vLine;
1883             m_pFindDialog->currentPos = posInLine + 1;
1884             return;
1885         }
1886         m_pFindDialog->nextWindow();
1887     }
1888 
1889     d3vLine = m_pFindDialog->currentLine;
1890     posInLine = m_pFindDialog->currentPos;
1891     if(m_pFindDialog->getCurrentWindow() == eWindowIndex::B)
1892     {
1893         if(m_pFindDialog->m_pSearchInB->isChecked() && m_pDiffTextWindow2 != nullptr &&
1894            m_pDiffTextWindow2->findString(s, d3vLine, posInLine, bDirDown, bCaseSensitive))
1895         {
1896             m_pDiffTextWindow2->setSelection(d3vLine, posInLine, d3vLine, posInLine + s.length(), l, p);
1897             DiffTextWindow::mVScrollBar->setValue(l - DiffTextWindow::mVScrollBar->pageStep() / 2);
1898             m_pHScrollBar->setValue(std::max<SafeInt<qint32>>(0, p + s.length() - m_pHScrollBar->pageStep()));
1899             m_pFindDialog->currentLine = d3vLine;
1900             m_pFindDialog->currentPos = posInLine + 1;
1901             return;
1902         }
1903 
1904         m_pFindDialog->nextWindow();
1905     }
1906 
1907     d3vLine = m_pFindDialog->currentLine;
1908     posInLine = m_pFindDialog->currentPos;
1909     if(m_pFindDialog->getCurrentWindow() == eWindowIndex::C)
1910     {
1911         if(m_pFindDialog->m_pSearchInC->isChecked() && m_pDiffTextWindow3 != nullptr &&
1912            m_pDiffTextWindow3->findString(s, d3vLine, posInLine, bDirDown, bCaseSensitive))
1913         {
1914             m_pDiffTextWindow3->setSelection(d3vLine, posInLine, d3vLine, posInLine + s.length(), l, p);
1915             DiffTextWindow::mVScrollBar->setValue(l - DiffTextWindow::mVScrollBar->pageStep() / 2);
1916             m_pHScrollBar->setValue(std::max<SafeInt<qint32>>(0, p + s.length() - m_pHScrollBar->pageStep()));
1917             m_pFindDialog->currentLine = d3vLine;
1918             m_pFindDialog->currentPos = posInLine + 1;
1919             return;
1920         }
1921 
1922         m_pFindDialog->nextWindow();
1923     }
1924 
1925     d3vLine = m_pFindDialog->currentLine;
1926     posInLine = m_pFindDialog->currentPos;
1927     if(m_pFindDialog->getCurrentWindow() == eWindowIndex::Output)
1928     {
1929         if(m_pFindDialog->m_pSearchInOutput->isChecked() && m_pMergeResultWindow != nullptr && m_pMergeResultWindow->isVisible() &&
1930            m_pMergeResultWindow->findString(s, d3vLine, posInLine, bDirDown, bCaseSensitive))
1931         {
1932             m_pMergeResultWindow->setSelection(d3vLine, posInLine, d3vLine, posInLine + s.length());
1933             MergeResultWindow::mVScrollBar->setValue(d3vLine - MergeResultWindow::mVScrollBar->pageStep() / 2);
1934             m_pHScrollBar->setValue(std::max<SafeInt<qint32>>(0, posInLine + s.length() - m_pHScrollBar->pageStep()));
1935             m_pFindDialog->currentLine = d3vLine;
1936             m_pFindDialog->currentPos = posInLine + 1;
1937             return;
1938         }
1939 
1940         m_pFindDialog->nextWindow();
1941     }
1942 
1943     KMessageBox::information(this, i18n("Search complete."), i18n("Search Complete"));
1944     m_pFindDialog->restartFind();
1945 }
1946 
1947 void KDiff3App::slotMergeCurrentFile()
1948 {
1949     if(m_bDirCompare && m_pDirectoryMergeWindow->isVisible() && m_pDirectoryMergeWindow->isFileSelected())
1950     {
1951         m_pDirectoryMergeWindow->mergeCurrentFile();
1952     }
1953     else if(m_pMainWidget->isVisible())
1954     {
1955         if(!canContinue()) return;
1956 
1957         if(m_outputFilename.isEmpty())
1958         {
1959             if(!m_sd3->isEmpty() && !m_sd3->isFromBuffer())
1960             {
1961                 m_outputFilename = m_sd3->getFilename();
1962             }
1963             else if(!m_sd2->isEmpty() && !m_sd2->isFromBuffer())
1964             {
1965                 m_outputFilename = m_sd2->getFilename();
1966             }
1967             else if(!m_sd1->isEmpty() && !m_sd1->isFromBuffer())
1968             {
1969                 m_outputFilename = m_sd1->getFilename();
1970             }
1971             else
1972             {
1973                 m_outputFilename = "unnamed.txt";
1974                 m_bDefaultFilename = true;
1975             }
1976         }
1977         mainInit(m_totalDiffStatus);
1978     }
1979 }
1980 
1981 void KDiff3App::slotWinFocusNext()
1982 {
1983     QWidget* focus = qApp->focusWidget();
1984     if(focus == m_pDirectoryMergeWindow && m_pDirectoryMergeWindow->isVisible() && !dirShowBoth->isChecked())
1985     {
1986         slotDirViewToggle();
1987     }
1988 
1989     std::list<QWidget*> visibleWidgetList;
1990     if(m_pDiffTextWindow1 && m_pDiffTextWindow1->isVisible()) visibleWidgetList.push_back(m_pDiffTextWindow1);
1991     if(m_pDiffTextWindow2 && m_pDiffTextWindow2->isVisible()) visibleWidgetList.push_back(m_pDiffTextWindow2);
1992     if(m_pDiffTextWindow3 && m_pDiffTextWindow3->isVisible()) visibleWidgetList.push_back(m_pDiffTextWindow3);
1993     if(m_pMergeResultWindow && m_pMergeResultWindow->isVisible()) visibleWidgetList.push_back(m_pMergeResultWindow);
1994     if(m_bDirCompare /*m_pDirectoryMergeWindow->isVisible()*/) visibleWidgetList.push_back(m_pDirectoryMergeWindow);
1995     //if ( m_pDirectoryMergeInfo->isVisible() ) visibleWidgetList.push_back(m_pDirectoryMergeInfo->getInfoList());
1996     if(visibleWidgetList.empty())
1997         return;
1998 
1999     std::list<QWidget*>::iterator i = std::find(visibleWidgetList.begin(), visibleWidgetList.end(), focus);
2000     ++i;
2001     if(i == visibleWidgetList.end())
2002         i = visibleWidgetList.begin();
2003 
2004     if(*i == m_pDirectoryMergeWindow && !dirShowBoth->isChecked())
2005     {
2006         slotDirViewToggle();
2007     }
2008     (*i)->setFocus();
2009 }
2010 
2011 void KDiff3App::slotWinFocusPrev()
2012 {
2013     QWidget* focus = qApp->focusWidget();
2014     if(focus == m_pDirectoryMergeWindow && m_pDirectoryMergeWindow->isVisible() && !dirShowBoth->isChecked())
2015     {
2016         slotDirViewToggle();
2017     }
2018 
2019     std::list<QWidget*> visibleWidgetList;
2020     if(m_pDiffTextWindow1 && m_pDiffTextWindow1->isVisible()) visibleWidgetList.push_back(m_pDiffTextWindow1);
2021     if(m_pDiffTextWindow2 && m_pDiffTextWindow2->isVisible()) visibleWidgetList.push_back(m_pDiffTextWindow2);
2022     if(m_pDiffTextWindow3 && m_pDiffTextWindow3->isVisible()) visibleWidgetList.push_back(m_pDiffTextWindow3);
2023     if(m_pMergeResultWindow && m_pMergeResultWindow->isVisible()) visibleWidgetList.push_back(m_pMergeResultWindow);
2024     if(m_bDirCompare /* m_pDirectoryMergeWindow->isVisible() */) visibleWidgetList.push_back(m_pDirectoryMergeWindow);
2025     //if ( m_pDirectoryMergeInfo->isVisible() ) visibleWidgetList.push_back(m_pDirectoryMergeInfo->getInfoList());
2026     if(visibleWidgetList.empty())
2027         return;
2028 
2029     std::list<QWidget*>::iterator i = std::find(visibleWidgetList.begin(), visibleWidgetList.end(), focus);
2030     if(i == visibleWidgetList.begin())
2031         i = visibleWidgetList.end();
2032     --i;
2033     //i will never be
2034     if(*i == m_pDirectoryMergeWindow && !dirShowBoth->isChecked())
2035     {
2036         slotDirViewToggle();
2037     }
2038     (*i)->setFocus();
2039 }
2040 
2041 void KDiff3App::slotWinToggleSplitterOrientation()
2042 {
2043     if(m_pDiffWindowSplitter != nullptr)
2044     {
2045         m_pDiffWindowSplitter->setOrientation(
2046             m_pDiffWindowSplitter->orientation() == Qt::Vertical ? Qt::Horizontal : Qt::Vertical);
2047 
2048         gOptions->m_bHorizDiffWindowSplitting = m_pDiffWindowSplitter->orientation() == Qt::Horizontal;
2049     }
2050 }
2051 
2052 void KDiff3App::slotOverviewNormal()
2053 {
2054     Q_EMIT changeOverViewMode(e_OverviewMode::eOMNormal);
2055 
2056     Q_EMIT updateAvailabilities();
2057 }
2058 
2059 void KDiff3App::slotOverviewAB()
2060 {
2061     Q_EMIT changeOverViewMode(e_OverviewMode::eOMAvsB);
2062 
2063     Q_EMIT updateAvailabilities();
2064 }
2065 
2066 void KDiff3App::slotOverviewAC()
2067 {
2068     Q_EMIT changeOverViewMode(e_OverviewMode::eOMAvsC);
2069 
2070     Q_EMIT updateAvailabilities();
2071 }
2072 
2073 void KDiff3App::slotOverviewBC()
2074 {
2075     Q_EMIT changeOverViewMode(e_OverviewMode::eOMBvsC);
2076 
2077     Q_EMIT updateAvailabilities();
2078 }
2079 
2080 void KDiff3App::slotNoRelevantChangesDetected()
2081 {
2082     if(m_bTripleDiff && !m_outputFilename.isEmpty())
2083     {
2084         //KMessageBox::information( this, "No relevant changes detected", "KDiff3" );
2085         if(!gOptions->m_IrrelevantMergeCmd.isEmpty())
2086         {
2087             /*
2088                 QProcess doesn't check for single quotes and uses non-standard escaping syntax for double quotes.
2089                     The distinction between single and double quotes is purely a command shell issue. So
2090                     we split the command string ourselves.
2091             */
2092             QStringList args;
2093             QString program;
2094             Utils::getArguments(gOptions->m_IrrelevantMergeCmd, program, args);
2095             QProcess process;
2096             process.start(program, args);
2097             process.waitForFinished(-1);
2098         }
2099     }
2100 }
2101 
2102 void KDiff3App::slotAddManualDiffHelp()
2103 {
2104     LineRef firstLine;
2105     LineRef lastLine;
2106     e_SrcSelector winIdx = e_SrcSelector::Invalid;
2107     if(m_pDiffTextWindow1)
2108     {
2109         m_pDiffTextWindow1->getSelectionRange(&firstLine, &lastLine, eFileCoords);
2110         winIdx = e_SrcSelector::A;
2111     }
2112     if(!firstLine.isValid() && m_pDiffTextWindow2)
2113     {
2114         m_pDiffTextWindow2->getSelectionRange(&firstLine, &lastLine, eFileCoords);
2115         winIdx = e_SrcSelector::B;
2116     }
2117     if(!firstLine.isValid() && m_pDiffTextWindow3)
2118     {
2119         m_pDiffTextWindow3->getSelectionRange(&firstLine, &lastLine, eFileCoords);
2120         winIdx = e_SrcSelector::C;
2121     }
2122 
2123     if(!firstLine.isValid() || !lastLine.isValid() || lastLine < firstLine)
2124         KMessageBox::information(this, i18n("Nothing is selected in either diff input window."), i18n("Error while adding manual diff range"));
2125     else
2126     {
2127         m_manualDiffHelpList.insertEntry(winIdx, firstLine, lastLine);
2128 
2129         mainInit(m_totalDiffStatus, InitFlag::autoSolve | InitFlag::initGUI); // Init without reload
2130         slotRefresh();
2131     }
2132 }
2133 
2134 void KDiff3App::slotClearManualDiffHelpList()
2135 {
2136     m_manualDiffHelpList.clear();
2137     mainInit(m_totalDiffStatus, InitFlag::autoSolve | InitFlag::initGUI); // Init without reload
2138     slotRefresh();
2139 }
2140 
2141 void KDiff3App::slotEncodingChanged(const QByteArray&)
2142 {
2143     mainInit(m_totalDiffStatus, InitFlag::loadFiles | InitFlag::useCurrentEncoding | InitFlag::autoSolve); // Init with reload
2144     slotRefresh();
2145 }
2146 
2147 void KDiff3App::slotUpdateAvailabilities()
2148 {
2149     if(m_pDiffTextWindow2 == nullptr || m_pDiffTextWindow1 == nullptr || m_pDiffTextWindow3 == nullptr)
2150         return;
2151 
2152     bool bTextDataAvailable = (m_sd1->hasData() || m_sd2->hasData() || m_sd3->hasData());
2153 
2154     if(dirShowBoth->isChecked())
2155     {
2156         if(m_pDirectoryMergeDock != nullptr)
2157             m_pDirectoryMergeDock->setVisible(m_bDirCompare);
2158         if(m_pDirectoryMergeInfoDock != nullptr)
2159             m_pDirectoryMergeInfoDock->setVisible(m_bDirCompare);
2160 
2161         if(!m_pMainWidget->isVisible() &&
2162            bTextDataAvailable && !m_pDirectoryMergeWindow->isScanning())
2163             m_pMainWidget->show();
2164     }
2165 
2166     bool bDiffWindowVisible = m_pMainWidget->isVisible();
2167     bool bMergeEditorVisible = m_pMergeWindowFrame != nullptr && m_pMergeWindowFrame->isVisible() && m_pMergeResultWindow != nullptr;
2168 
2169     m_pDirectoryMergeWindow->updateAvailabilities(bMergeEditorVisible, m_bDirCompare, bDiffWindowVisible, chooseA, chooseB, chooseC);
2170 
2171     dirShowBoth->setEnabled(m_bDirCompare);
2172     dirViewToggle->setEnabled(
2173         m_bDirCompare &&
2174         (m_pDirectoryMergeDock != nullptr && m_pDirectoryMergeInfoDock != nullptr &&
2175          ((!m_pDirectoryMergeDock->isVisible() && m_pMainWidget->isVisible()) ||
2176           (m_pDirectoryMergeDock->isVisible() && !m_pMainWidget->isVisible() && bTextDataAvailable))));
2177 
2178     showWhiteSpaceCharacters->setEnabled(bDiffWindowVisible);
2179     autoAdvance->setEnabled(bMergeEditorVisible);
2180     mAutoSolve->setEnabled(bMergeEditorVisible && m_bTripleDiff);
2181     mUnsolve->setEnabled(bMergeEditorVisible);
2182 
2183     editUndo->setEnabled(false); //Not yet implemented but planned.
2184     editCut->setEnabled(allowCut());
2185     editCopy->setEnabled(allowCopy());
2186 
2187     mMergeHistory->setEnabled(bMergeEditorVisible);
2188     mergeRegExp->setEnabled(bMergeEditorVisible);
2189     showWindowA->setEnabled(bDiffWindowVisible && (m_pDiffTextWindow2->isVisible() || m_pDiffTextWindow3->isVisible()));
2190     showWindowB->setEnabled(bDiffWindowVisible && (m_pDiffTextWindow1->isVisible() || m_pDiffTextWindow3->isVisible()));
2191     showWindowC->setEnabled(bDiffWindowVisible && m_bTripleDiff && (m_pDiffTextWindow1->isVisible() || m_pDiffTextWindow2->isVisible()));
2192     editFind->setEnabled(bDiffWindowVisible);
2193     editFindNext->setEnabled(bDiffWindowVisible);
2194     m_pFindDialog->m_pSearchInC->setEnabled(m_bTripleDiff);
2195     m_pFindDialog->m_pSearchInOutput->setEnabled(bMergeEditorVisible);
2196 
2197     bool bSavable = bMergeEditorVisible && m_pMergeResultWindow->getNumberOfUnsolvedConflicts() == 0;
2198     fileSave->setEnabled(m_bOutputModified && bSavable);
2199     fileSaveAs->setEnabled(bSavable);
2200 
2201     mGoTop->setEnabled(bDiffWindowVisible && m_pMergeResultWindow != nullptr && m_pMergeResultWindow->isDeltaAboveCurrent());
2202     mGoBottom->setEnabled(bDiffWindowVisible && m_pMergeResultWindow != nullptr && m_pMergeResultWindow->isDeltaBelowCurrent());
2203     mGoCurrent->setEnabled(bDiffWindowVisible);
2204     mGoPrevUnsolvedConflict->setEnabled(bMergeEditorVisible && m_pMergeResultWindow->isUnsolvedConflictAboveCurrent());
2205     mGoNextUnsolvedConflict->setEnabled(bMergeEditorVisible && m_pMergeResultWindow->isUnsolvedConflictBelowCurrent());
2206     mGoPrevConflict->setEnabled(bDiffWindowVisible && bMergeEditorVisible && m_pMergeResultWindow->isConflictAboveCurrent());
2207     mGoNextConflict->setEnabled(bDiffWindowVisible && bMergeEditorVisible && m_pMergeResultWindow->isConflictBelowCurrent());
2208     mGoPrevDelta->setEnabled(bDiffWindowVisible && m_pMergeResultWindow != nullptr && m_pMergeResultWindow->isDeltaAboveCurrent());
2209     mGoNextDelta->setEnabled(bDiffWindowVisible && m_pMergeResultWindow != nullptr && m_pMergeResultWindow->isDeltaBelowCurrent());
2210 
2211     overviewModeNormal->setEnabled(m_bTripleDiff && bDiffWindowVisible);
2212     overviewModeAB->setEnabled(m_bTripleDiff && bDiffWindowVisible);
2213     overviewModeAC->setEnabled(m_bTripleDiff && bDiffWindowVisible);
2214     overviewModeBC->setEnabled(m_bTripleDiff && bDiffWindowVisible);
2215     e_OverviewMode overviewMode = m_pOverview == nullptr ? e_OverviewMode::eOMNormal : m_pOverview->getOverviewMode();
2216     overviewModeNormal->setChecked(overviewMode == e_OverviewMode::eOMNormal);
2217     overviewModeAB->setChecked(overviewMode == e_OverviewMode::eOMAvsB);
2218     overviewModeAC->setChecked(overviewMode == e_OverviewMode::eOMAvsC);
2219     overviewModeBC->setChecked(overviewMode == e_OverviewMode::eOMBvsC);
2220 
2221     winToggleSplitOrientation->setEnabled(bDiffWindowVisible && m_pDiffWindowSplitter != nullptr);
2222 }