File indexing completed on 2024-05-19 13:31:44

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 // application specific includes
0012 #include "kdiff3.h"
0013 
0014 #include "compat.h"
0015 #include "defmac.h"
0016 #include "difftextwindow.h"
0017 #include "directorymergewindow.h"
0018 #include "fileaccess.h"
0019 #include "guiutils.h"
0020 #include "kdiff3_shell.h"
0021 #include "mergeresultwindow.h"
0022 #include "optiondialog.h"
0023 #include "progress.h"
0024 #include "RLPainter.h"
0025 #include "smalldialogs.h"
0026 #include "Utils.h"
0027 // Standard c/c++ includes
0028 #include <memory>
0029 #ifndef Q_OS_WIN
0030 #include <unistd.h>
0031 #endif
0032 #include <utility>
0033 // Anything else that isn't Qt/Frameworks
0034 #include <boost/signals2.hpp>
0035 // include files for QT
0036 #include <QCheckBox>
0037 #include <QClipboard>
0038 #include <QCommandLineParser>
0039 #include <QDir>
0040 #include <QDockWidget>
0041 #include <QFileDialog>
0042 #include <QLabel>
0043 #include <QLayout>
0044 #include <QLineEdit>
0045 #include <QMenu>
0046 #include <QMenuBar>
0047 #include <QPaintDevice>
0048 #include <QPainter>
0049 #include <QPointer>
0050 #include <QPrintDialog>
0051 #include <QPrinter>
0052 #include <QPushButton>
0053 #include <QScreen>
0054 #include <QShortcut>
0055 #include <QSplitter>
0056 #include <QStatusBar>
0057 #include <QTextEdit>
0058 #include <QTextStream>
0059 #include <QUrl>
0060 // include files for KDE
0061 #include <KActionCollection>
0062 #include <KConfig>
0063 #include <KCrash>
0064 #include <KLocalizedString>
0065 #include <KMessageBox>
0066 #include <KStandardAction>
0067 #include <KToggleAction>
0068 #include <KToolBar>
0069 
0070 bool KDiff3App::m_bTripleDiff = false;
0071 std::unique_ptr<Options> gOptions = std::make_unique<Options>();
0072 
0073 boost::signals2::signal<QString(), FirstNonEmpty<QString>> KDiff3App::getSelection;
0074 boost::signals2::signal<bool(), or_> KDiff3App::allowCopy;
0075 boost::signals2::signal<bool(), or_> KDiff3App::allowCut;
0076 
0077 /*
0078     To be a constexpr the QLatin1String constructor must be given the size of the string explicitly.
0079     Otherwise it calls strlen which is not a constexpr.
0080 */
0081 constexpr QLatin1String MAIN_TOOLBAR_NAME = QLatin1String("mainToolBar", sizeof("mainToolBar") - 1);
0082 
0083 KActionCollection* KDiff3App::actionCollection() const
0084 {
0085     assert(m_pKDiff3Shell != nullptr);
0086 
0087     return m_pKDiff3Shell->actionCollection();
0088 }
0089 
0090 QStatusBar* KDiff3App::statusBar() const
0091 {
0092     assert(m_pKDiff3Shell != nullptr);
0093 
0094     return m_pKDiff3Shell->statusBar();
0095 }
0096 
0097 KToolBar* KDiff3App::toolBar(const QLatin1String& toolBarId) const
0098 {
0099     assert(m_pKDiff3Shell != nullptr);
0100     return m_pKDiff3Shell->toolBar(toolBarId);
0101 }
0102 
0103 bool KDiff3App::isFileSaved() const
0104 {
0105     return m_bFileSaved;
0106 }
0107 
0108 bool KDiff3App::isDirComparison() const
0109 {
0110     return m_bDirCompare;
0111 }
0112 
0113 /*
0114     Don't call completeInit from here it will be called in KDiff3Shell as needed.
0115 */
0116 KDiff3App::KDiff3App(QWidget* pParent, const QString& name, KDiff3Shell* pKDiff3Shell):
0117     QMainWindow(pParent)
0118 {
0119     setWindowFlags(Qt::Widget);
0120     setObjectName(name);
0121     m_pKDiff3Shell = pKDiff3Shell;
0122 
0123     m_pCentralWidget = new QWidget(this);
0124     QVBoxLayout* pCentralLayout = new QVBoxLayout(m_pCentralWidget);
0125     pCentralLayout->setContentsMargins(0, 0, 0, 0);
0126     pCentralLayout->setSpacing(0);
0127     setCentralWidget(m_pCentralWidget);
0128 
0129     m_pMainWidget = new QWidget(m_pCentralWidget);
0130     m_pMainWidget->setObjectName("MainWidget");
0131     pCentralLayout->addWidget(m_pMainWidget);
0132     m_pMainWidget->hide();
0133 
0134     setWindowTitle("KDiff3");
0135     setUpdatesEnabled(false);
0136 
0137     // set Disabled to same color as enabled to prevent flicker in DirectoryMergeWindow
0138     QPalette pal;
0139     pal.setBrush(QPalette::Base, pal.brush(QPalette::Active, QPalette::Base));
0140     pal.setColor(QPalette::Text, pal.color(QPalette::Active, QPalette::Text));
0141     setPalette(pal);
0142 
0143     // Setup progress ProgressDialog. No progress can be shown before this point.
0144     // ProgressProxy will otherwise emit no-ops to disconnected boost signals.
0145     if(g_pProgressDialog == nullptr)
0146     {
0147         g_pProgressDialog = new ProgressDialog(this, statusBar());
0148         g_pProgressDialog->setStayHidden(true);
0149     }
0150 
0151     // All default values must be set before calling readOptions().
0152     m_pOptionDialog = new OptionDialog(m_pKDiff3Shell != nullptr, this);
0153     chk_connect_a(m_pOptionDialog, &OptionDialog::applyDone, this, &KDiff3App::slotRefresh);
0154 
0155     m_pOptionDialog->readOptions(KSharedConfig::openConfig());
0156 
0157     // Option handling.
0158     QtSizeType argCount = KDiff3Shell::getParser()->optionNames().count() + KDiff3Shell::getParser()->positionalArguments().count();
0159     bool hasArgs = argCount > 0;
0160     if(hasArgs)
0161     {
0162         QString s;
0163         QString title;
0164         if(KDiff3Shell::getParser()->isSet("confighelp"))
0165         {
0166             s = m_pOptionDialog->calcOptionHelp();
0167             title = i18n("Current Configuration:");
0168         }
0169         else
0170         {
0171             s = m_pOptionDialog->parseOptions(KDiff3Shell::getParser()->values("cs"));
0172             title = i18n("Config Option Error:");
0173         }
0174         if(!s.isEmpty())
0175         {
0176 #ifndef Q_OS_WIN
0177             if(isatty(fileno(stderr)) != 1)
0178 #endif
0179             {
0180                 QPointer<QDialog> pDialog = QPointer<QDialog>(new QDialog(this));
0181                 pDialog->setAttribute(Qt::WA_DeleteOnClose);
0182                 pDialog->setModal(true);
0183                 pDialog->setWindowTitle(title);
0184                 QPointer<QVBoxLayout> pVBoxLayout = new QVBoxLayout(pDialog);
0185                 QPointer<QTextEdit> pTextEdit = QPointer<QTextEdit>(new QTextEdit(pDialog));
0186                 pTextEdit->setText(s);
0187                 pTextEdit->setReadOnly(true);
0188                 pTextEdit->setWordWrapMode(QTextOption::NoWrap);
0189                 pVBoxLayout->addWidget(pTextEdit);
0190                 pDialog->resize(600, 400);
0191                 pDialog->exec();
0192             }
0193 #if !defined(Q_OS_WIN)
0194             else
0195             {
0196                 // Launched from a console
0197                 QTextStream outStream(stdout);
0198                 outStream << title << "\n";
0199                 outStream << s;//newline already appended by parseOptions
0200             }
0201 #endif
0202             exit(1);
0203         }
0204     }
0205 
0206 #ifdef ENABLE_AUTO
0207     m_bAutoFlag = hasArgs && KDiff3Shell::getParser()->isSet("auto") && !KDiff3Shell::getParser()->isSet("noauto");
0208 #else
0209     m_bAutoFlag = false;
0210 #endif
0211 
0212     m_bAutoMode = m_bAutoFlag || gOptions->m_bAutoSaveAndQuitOnMergeWithoutConflicts;
0213     if(hasArgs)
0214     {
0215         m_outputFilename = KDiff3Shell::getParser()->value("output");
0216 
0217         if(m_outputFilename.isEmpty())
0218             m_outputFilename = KDiff3Shell::getParser()->value("out");
0219 
0220         if(!m_outputFilename.isEmpty())
0221             m_outputFilename = FileAccess(m_outputFilename, true).absoluteFilePath();
0222 
0223         if(m_bAutoMode && m_outputFilename.isEmpty())
0224         {
0225             if(m_bAutoFlag)
0226             {
0227                 QTextStream(stderr) << i18n("Option --auto used, but no output file specified.") << "\n";
0228             }
0229             m_bAutoMode = false;
0230         }
0231 
0232         if(m_outputFilename.isEmpty() && KDiff3Shell::getParser()->isSet("merge"))
0233         {
0234             m_outputFilename = "unnamed.txt";
0235             m_bDefaultFilename = true;
0236         }
0237         else
0238         {
0239             m_bDefaultFilename = false;
0240         }
0241 
0242         QStringList args = KDiff3Shell::getParser()->positionalArguments();
0243 
0244         m_sd1->setFilename(KDiff3Shell::getParser()->value("base"));
0245         if(m_sd1->isEmpty())
0246         {
0247             if(args.count() > 0) m_sd1->setFilename(args[0]); // args->arg(0)
0248             if(args.count() > 1) m_sd2->setFilename(args[1]);
0249             if(args.count() > 2) m_sd3->setFilename(args[2]);
0250         }
0251         else
0252         {
0253             if(args.count() > 0) m_sd2->setFilename(args[0]);
0254             if(args.count() > 1) m_sd3->setFilename(args[1]);
0255         }
0256         //Set m_bDirCompare flag
0257         m_bDirCompare = m_sd1->isDir();
0258 
0259         QStringList aliasList = KDiff3Shell::getParser()->values("fname");
0260         QStringList::Iterator ali = aliasList.begin();
0261 
0262         QString an1 = KDiff3Shell::getParser()->value("L1");
0263         if(!an1.isEmpty())
0264         {
0265             m_sd1->setAliasName(an1);
0266         }
0267         else if(ali != aliasList.end())
0268         {
0269             m_sd1->setAliasName(*ali);
0270             ++ali;
0271         }
0272 
0273         QString an2 = KDiff3Shell::getParser()->value("L2");
0274         if(!an2.isEmpty())
0275         {
0276             m_sd2->setAliasName(an2);
0277         }
0278         else if(ali != aliasList.end())
0279         {
0280             m_sd2->setAliasName(*ali);
0281             ++ali;
0282         }
0283 
0284         QString an3 = KDiff3Shell::getParser()->value("L3");
0285         if(!an3.isEmpty())
0286         {
0287             m_sd3->setAliasName(an3);
0288         }
0289         else if(ali != aliasList.end())
0290         {
0291             m_sd3->setAliasName(*ali);
0292             ++ali;
0293         }
0294     }
0295     else
0296     {
0297         m_bDefaultFilename = false;
0298     }
0299     g_pProgressDialog->setStayHidden(m_bAutoMode);
0300 
0301     ///////////////////////////////////////////////////////////////////
0302     // call inits to invoke all other construction parts
0303     // Warning: Call this before connecting KDiff3App::slotUpdateAvailabilities or
0304     //  calling KXMLGUIClient::setXMLFile or KXMLGUIClient::createGUI
0305     initActions(actionCollection());
0306 
0307     initStatusBar();
0308 
0309     m_pFindDialog = new FindDialog(this);
0310     chk_connect_a(m_pFindDialog, &FindDialog::findNext, this, &KDiff3App::slotEditFindNext);
0311 
0312     mEscapeAction->setEnabled(gOptions->m_bEscapeKeyQuits);
0313     autoAdvance->setChecked(gOptions->m_bAutoAdvance);
0314     showWhiteSpaceCharacters->setChecked(gOptions->m_bShowWhiteSpaceCharacters);
0315     showWhiteSpace->setChecked(gOptions->m_bShowWhiteSpace);
0316     showWhiteSpaceCharacters->setEnabled(gOptions->m_bShowWhiteSpace);
0317     showLineNumbers->setChecked(gOptions->m_bShowLineNumbers);
0318     wordWrap->setChecked(gOptions->wordWrapOn());
0319 
0320     /*
0321         No need to restore window size and position here that is done later.
0322             See KDiff3App::completeInit
0323     */
0324     viewStatusBar->setChecked(gOptions->isStatusBarVisible());
0325     slotViewStatusBar();
0326 
0327     KToolBar* mainToolBar = toolBar(MAIN_TOOLBAR_NAME);
0328     if(mainToolBar != nullptr)
0329     {
0330         mainToolBar->mainWindow()->addToolBar(Qt::TopToolBarArea, mainToolBar);
0331     }
0332 
0333     slotRefresh();
0334 
0335     m_pDirectoryMergeDock = new QDockWidget(i18n("Directory merge"), this);
0336     m_pDirectoryMergeWindow = new DirectoryMergeWindow(m_pDirectoryMergeDock, *this);
0337     m_pDirectoryMergeDock->setObjectName("DirectoryMergeDock");
0338     m_pDirectoryMergeDock->setWidget(m_pDirectoryMergeWindow);
0339     m_pDirectoryMergeDock->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable);
0340     m_pDirectoryMergeInfoDock = new QDockWidget(i18n("Merge info"), this);
0341     m_pDirectoryMergeInfoDock->setObjectName("MergeInfoDock");
0342     m_pDirectoryMergeInfo = new DirectoryMergeInfo(m_pDirectoryMergeInfoDock);
0343     m_pDirectoryMergeInfoDock->setWidget(m_pDirectoryMergeInfo);
0344     m_pDirectoryMergeInfoDock->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable);
0345     m_pDirectoryMergeWindow->setDirectoryMergeInfo(m_pDirectoryMergeInfo);
0346     //Warning: Make sure DirectoryMergeWindow::initActions is called before this point or we can crash when selectionChanged is sent.
0347     m_pDirectoryMergeWindow->setupConnections(this);
0348     addDockWidget(Qt::LeftDockWidgetArea, m_pDirectoryMergeDock);
0349     splitDockWidget(m_pDirectoryMergeDock, m_pDirectoryMergeInfoDock, Qt::Vertical);
0350 
0351     chk_connect_a(QApplication::clipboard(), &QClipboard::dataChanged, this, &KDiff3App::slotClipboardChanged);
0352     chk_connect_q(this, &KDiff3App::sigRecalcWordWrap, this, &KDiff3App::slotRecalcWordWrap);
0353     chk_connect_a(this, &KDiff3App::finishDrop, this, &KDiff3App::slotFinishDrop);
0354 
0355     connections.push_back(allowCut.connect(boost::bind(&KDiff3App::canCut, this)));
0356     connections.push_back(allowCopy.connect(boost::bind(&KDiff3App::canCopy, this)));
0357 
0358     m_pDirectoryMergeWindow->initDirectoryMergeActions(this, actionCollection());
0359 
0360     if(qApp != nullptr)
0361         chk_connect_a(qApp, &QApplication::focusChanged, this, &KDiff3App::slotFocusChanged);
0362 
0363     chk_connect_a(this, &KDiff3App::updateAvailabilities, this, &KDiff3App::slotUpdateAvailabilities);
0364 }
0365 
0366 /*
0367     This function is only concerned with qt objects that don't support canCut.
0368     allowCut() or's the results from all canCut signals
0369 
0370     returns true if a QLineEdit or QLineEdit is in focus because Qt handles these internally.
0371 */
0372 bool KDiff3App::canCut()
0373 {
0374     QWidget* focus = focusWidget();
0375 
0376     return (qobject_cast<QLineEdit*>(focus) != nullptr || qobject_cast<QTextEdit*>(focus) != nullptr);
0377 }
0378 
0379 /*
0380     Please do not add logic for MergeResultWindow or DiffTextWindow here they have their own handlers.
0381     This function is only concerned with qt objects that don't support canCopy.
0382     allowCopy() or's the results from all canCopy signals sent via boost.
0383 
0384     returns true if a QLineEdit or QLineEdit is in focus because Qt handles these internally.
0385 */
0386 bool KDiff3App::canCopy()
0387 {
0388     QWidget* focus = focusWidget();
0389 
0390     return (qobject_cast<QLineEdit*>(focus) != nullptr || qobject_cast<QTextEdit*>(focus) != nullptr);
0391 }
0392 /*
0393     Make sure Edit menu tracks focus correctly.
0394 */
0395 void KDiff3App::slotFocusChanged([[maybe_unused]] QWidget* old, [[maybe_unused]] QWidget* now)
0396 {
0397     qCDebug(kdiffMain) << "[KDiff3App::slotFocusChanged] old = " << old << ", new =" << now;
0398     //This needs to be called after the focus change is complete
0399     QMetaObject::invokeMethod(this, &KDiff3App::updateAvailabilities, Qt::QueuedConnection);
0400 }
0401 
0402 // Restore and show mainWindow.
0403 void KDiff3App::showMainWindow()
0404 {
0405     if(!m_pKDiff3Shell->isVisible() && !restoreWindow(KSharedConfig::openConfig()))
0406     {
0407         /*
0408             Set default state/geometry from config file.
0409             This will no longer be updated but serves as a fallback.
0410             Qt's restoreState/saveState can handle multiple screens this won't.
0411         */
0412         if(gOptions->isFullScreen())
0413             m_pKDiff3Shell->showFullScreen();
0414         else if(gOptions->isMaximized())
0415             m_pKDiff3Shell->showMaximized();
0416 
0417         QSize size = gOptions->getGeometry();
0418         QPoint pos = gOptions->getPosition();
0419 
0420         if(!size.isEmpty())
0421         {
0422             m_pKDiff3Shell->resize(size);
0423 
0424             QRect visibleRect = QRect(pos, size) & m_pKDiff3Shell->screen()->availableGeometry();
0425             if(visibleRect.width() > 100 && visibleRect.height() > 100)
0426                 m_pKDiff3Shell->move(pos);
0427         }
0428     }
0429 
0430     m_pKDiff3Shell->show();
0431 }
0432 
0433 // Do file comparision.
0434 void KDiff3App::doFileCompare()
0435 {
0436     improveFilenames();
0437     m_pDirectoryMergeDock->hide();
0438     m_pDirectoryMergeInfoDock->hide();
0439 
0440     mainInit(m_totalDiffStatus);
0441 }
0442 
0443 void KDiff3App::completeInit(const QString& fn1, const QString& fn2, const QString& fn3)
0444 {
0445     bool openError = false;
0446     bool bSuccess = true;
0447 
0448     if(!fn1.isEmpty())
0449     {
0450         m_sd1->setFilename(fn1);
0451         m_bDirCompare = m_sd1->isDir();
0452     }
0453     if(!fn2.isEmpty())
0454     {
0455         m_sd2->setFilename(fn2);
0456     }
0457     if(!fn3.isEmpty())
0458     {
0459         m_sd3->setFilename(fn3);
0460     }
0461 
0462     //Should not fail ever.
0463     assert(m_bDirCompare == m_sd1->isDir());
0464     if((m_bDirCompare && (!m_sd2->isDir() || !(m_sd3->isValid() && m_sd3->isDir()))) ||
0465        (!m_bDirCompare && (m_sd2->isDir() || m_sd3->isDir())))
0466     {
0467         KMessageBox::error(this, i18nc("Error message", "Can't compare file with folder."),
0468                            i18nc("Title error message box", "Bad comparison attempt"));
0469 
0470         bSuccess = false;
0471         m_bDirCompare = false;
0472         openError = true;
0473     }
0474 
0475     if(m_bAutoFlag && m_bAutoMode && m_bDirCompare)
0476     {
0477         QTextStream(stderr) << i18n("Option --auto ignored for folder comparison.") << "\n";
0478         m_bAutoMode = false;
0479     }
0480 
0481     if(!m_bAutoMode)
0482         showMainWindow();
0483 
0484     g_pProgressDialog->setStayHidden(false);
0485     //initView does first time setup for ui.
0486     initView();
0487 
0488     if(bSuccess)
0489     {
0490         if(m_bDirCompare)
0491             bSuccess = doDirectoryCompare(false);
0492         else
0493         {
0494             doFileCompare();
0495             if(m_totalDiffStatus->getUnsolvedConflicts() != 0)
0496                 bSuccess = false;
0497 
0498             if(m_bAutoMode && m_totalDiffStatus->getUnsolvedConflicts() == 0)
0499             {
0500                 QSharedPointer<SourceData> pSD = nullptr;
0501                 if(m_sd3->isEmpty())
0502                 {
0503                     if(m_totalDiffStatus->isBinaryEqualAB())
0504                     {
0505                         pSD = m_sd1;
0506                     }
0507                 }
0508                 else
0509                 {
0510                     if(m_totalDiffStatus->isBinaryEqualBC() || m_totalDiffStatus->isBinaryEqualAB())
0511                     {
0512                         //if B==C (assume A is old), if A==B then C has changed
0513                         pSD = m_sd3;
0514                     }
0515                     else if(m_totalDiffStatus->isBinaryEqualAC())
0516                     {
0517                         pSD = m_sd2; // assuming B has changed
0518                     }
0519                 }
0520 
0521                 if(pSD != nullptr)
0522                 {
0523                     // Save this file directly, not via the merge result window.
0524                     FileAccess fa(m_outputFilename);
0525                     if(gOptions->m_bDmCreateBakFiles && fa.exists())
0526                     {
0527                         fa.createBackup(".orig");
0528                     }
0529 
0530                     bSuccess = pSD->saveNormalDataAs(m_outputFilename);
0531                     if(!bSuccess)
0532                         KMessageBox::error(this, i18n("Saving failed."));
0533                 }
0534                 else if(m_pMergeResultWindow->getNumberOfUnsolvedConflicts() == 0)
0535                 {
0536                     bSuccess = m_pMergeResultWindow->saveDocument(m_pMergeResultWindowTitle->getFileName(), m_pMergeResultWindowTitle->getEncoding(), m_pMergeResultWindowTitle->getLineEndStyle());
0537                 }
0538                 if(bSuccess)
0539                 {
0540                     qApp->exit(0);
0541                 }
0542             }
0543         }
0544     }
0545 
0546     if(bSuccess && m_bAutoMode) return;
0547     if(m_bAutoMode)
0548         showMainWindow();
0549 
0550 
0551     m_bAutoMode = false;
0552 
0553     if(statusBar() != nullptr)
0554         statusBar()->setSizeGripEnabled(true);
0555 
0556     slotClipboardChanged(); // For initialisation.
0557 
0558     Q_EMIT updateAvailabilities();
0559 
0560     if(!m_bDirCompare)
0561     {
0562         if((!m_sd1->getErrors().isEmpty()) ||
0563            (!m_sd2->getErrors().isEmpty()) ||
0564            (!m_sd3->getErrors().isEmpty()))
0565         {
0566             QString text(i18n("Opening of these files failed:"));
0567             text += "\n\n";
0568             if(!m_sd1->getErrors().isEmpty())
0569                 text += " - " + m_sd1->getAliasName() + '\n' + m_sd1->getErrors().join('\n') + '\n';
0570             if(!m_sd2->getErrors().isEmpty())
0571                 text += " - " + m_sd2->getAliasName() + '\n' + m_sd2->getErrors().join('\n') + '\n';
0572             if(!m_sd3->getErrors().isEmpty())
0573                 text += " - " + m_sd3->getAliasName() + '\n' + m_sd3->getErrors().join('\n') + '\n';
0574 
0575             KMessageBox::error(this, text, i18n("File open error"));
0576 
0577             openError = true;
0578         }
0579 
0580         if(m_sd1->isEmpty() || m_sd2->isEmpty() || openError)
0581             slotFileOpen();
0582     }
0583     else if(!bSuccess) // Directory open failed
0584     {
0585         slotFileOpen();
0586     }
0587 }
0588 
0589 KDiff3App::~KDiff3App()
0590 {
0591     delete m_totalDiffStatus;
0592 
0593     if(mRunnablesStarted)
0594     {
0595         g_pProgressDialog->cancel(ProgressDialog::eExit);
0596     }
0597 
0598     // Prevent spurious focus change signals from Qt from being picked up by KDiff3App during destruction.
0599     QObject::disconnect(qApp, &QApplication::focusChanged, this, &KDiff3App::slotFocusChanged);
0600 };
0601 
0602 /**
0603  * Helper function used to create actions into the ac collection
0604  */
0605 
0606 void KDiff3App::initActions(KActionCollection* ac)
0607 {
0608     if(Q_UNLIKELY(ac == nullptr))
0609     {
0610         KMessageBox::error(nullptr, "actionCollection==0");
0611         exit(-1); //we cannot recover from this.
0612     }
0613     fileOpen = KStandardAction::open(this, &KDiff3App::slotFileOpen, ac);
0614     fileOpen->setStatusTip(i18n("Opens documents for comparison..."));
0615 
0616     fileReload = GuiUtils::createAction<QAction>(i18n("Reload"), QKeySequence(QKeySequence::Refresh), this, &KDiff3App::slotReload, ac, u8"file_reload");
0617 
0618     fileSave = KStandardAction::save(this, &KDiff3App::slotFileSave, ac);
0619     fileSave->setStatusTip(i18n("Saves the merge result. All conflicts must be solved!"));
0620     fileSaveAs = KStandardAction::saveAs(this, &KDiff3App::slotFileSaveAs, ac);
0621     fileSaveAs->setStatusTip(i18n("Saves the current document as..."));
0622 #ifndef QT_NO_PRINTER
0623     filePrint = KStandardAction::print(this, &KDiff3App::slotFilePrint, ac);
0624     filePrint->setStatusTip(i18n("Print the differences"));
0625 #endif
0626     fileQuit = KStandardAction::quit(this, &KDiff3App::slotFileQuit, ac);
0627     fileQuit->setStatusTip(i18n("Quits the application"));
0628 
0629     editUndo = KStandardAction::undo(this, &KDiff3App::slotEditUndo, ac);
0630     editUndo->setShortcuts(QKeySequence::Undo);
0631     editUndo->setStatusTip(i18n("Undo last action."));
0632 
0633     editCut = KStandardAction::cut(this, &KDiff3App::slotEditCut, ac);
0634     editCut->setShortcuts(QKeySequence::Cut);
0635     editCut->setStatusTip(i18n("Cuts the selected section and puts it to the clipboard"));
0636     editCopy = KStandardAction::copy(this, &KDiff3App::slotEditCopy, ac);
0637     editCopy->setShortcut(QKeySequence::Copy);
0638     editCopy->setStatusTip(i18n("Copies the selected section to the clipboard"));
0639     editPaste = KStandardAction::paste(this, &KDiff3App::slotEditPaste, ac);
0640     editPaste->setStatusTip(i18n("Pastes the clipboard contents to current position"));
0641     editPaste->setShortcut(QKeySequence::Paste);
0642     editSelectAll = KStandardAction::selectAll(this, &KDiff3App::slotEditSelectAll, ac);
0643     editSelectAll->setStatusTip(i18n("Select everything in current window"));
0644     editFind = KStandardAction::find(this, &KDiff3App::slotEditFind, ac);
0645     editFind->setShortcut(QKeySequence::Find);
0646     editFind->setStatusTip(i18n("Search for a string"));
0647     editFindNext = KStandardAction::findNext(this, &KDiff3App::slotEditFindNext, ac);
0648     editFindNext->setStatusTip(i18n("Search again for the string"));
0649 
0650     viewStatusBar = KStandardAction::showStatusbar(this, &KDiff3App::slotViewStatusBar, ac);
0651     viewStatusBar->setStatusTip(i18n("Enables/disables the statusbar"));
0652     KStandardAction::keyBindings(this, &KDiff3App::slotConfigureKeys, ac);
0653     QAction* pAction = KStandardAction::preferences(this, &KDiff3App::slotConfigure, ac);
0654     pAction->setText(i18n("Configure KDiff3..."));
0655 
0656 #include "xpm/autoadvance.xpm"
0657 #include "xpm/currentpos.xpm"
0658 #include "xpm/down1arrow.xpm"
0659 #include "xpm/down2arrow.xpm"
0660 #include "xpm/downend.xpm"
0661 #include "xpm/gotoline.xpm"
0662 #include "xpm/iconA.xpm"
0663 #include "xpm/iconB.xpm"
0664 #include "xpm/iconC.xpm"
0665 #include "xpm/nextunsolved.xpm"
0666 #include "xpm/prevunsolved.xpm"
0667 #include "xpm/showlinenumbers.xpm"
0668 #include "xpm/showwhitespace.xpm"
0669 #include "xpm/showwhitespacechars.xpm"
0670 #include "xpm/up1arrow.xpm"
0671 #include "xpm/up2arrow.xpm"
0672 #include "xpm/upend.xpm"
0673 
0674     mGoCurrent = GuiUtils::createAction<QAction>(i18n("Go to Current Delta"), QIcon(QPixmap(currentpos)), i18n("Current\nDelta"), QKeySequence(Qt::CTRL | Qt::Key_Space), this, &KDiff3App::slotGoCurrent, ac, "go_current");
0675 
0676     mGoTop = GuiUtils::createAction<QAction>(i18n("Go to First Delta"), QIcon(QPixmap(upend)), i18n("First\nDelta"), this, &KDiff3App::slotGoTop, ac, "go_top");
0677 
0678     mGoBottom = GuiUtils::createAction<QAction>(i18n("Go to Last Delta"), QIcon(QPixmap(downend)), i18n("Last\nDelta"), this, &KDiff3App::slotGoBottom, ac, "go_bottom");
0679 
0680     QString omitsWhitespace = ".\n" + i18nc("Tooltip explanation text", "(Skips white space differences when \"Show White Space\" is disabled.)");
0681     QString includeWhitespace = ".\n" + i18nc("Tooltip explanation text", "(Does not skip white space differences even when \"Show White Space\" is disabled.)");
0682     mGoPrevDelta = GuiUtils::createAction<QAction>(i18n("Go to Previous Delta"), QIcon(QPixmap(up1arrow)), i18n("Prev\nDelta"), QKeySequence(Qt::CTRL | Qt::Key_Up), this, &KDiff3App::slotGoPrevDelta, ac, "go_prev_delta");
0683     mGoPrevDelta->setToolTip(mGoPrevDelta->text() + omitsWhitespace);
0684     mGoNextDelta = GuiUtils::createAction<QAction>(i18n("Go to Next Delta"), QIcon(QPixmap(down1arrow)), i18n("Next\nDelta"), QKeySequence(Qt::CTRL | Qt::Key_Down), this, &KDiff3App::slotGoNextDelta, ac, "go_next_delta");
0685     mGoNextDelta->setToolTip(mGoNextDelta->text() + omitsWhitespace);
0686     mGoPrevConflict = GuiUtils::createAction<QAction>(i18n("Go to Previous Conflict"), QIcon(QPixmap(up2arrow)), i18n("Prev\nConflict"), QKeySequence(Qt::CTRL | Qt::Key_PageUp), this, &KDiff3App::slotGoPrevConflict, ac, "go_prev_conflict");
0687     mGoPrevConflict->setToolTip(mGoPrevConflict->text() + omitsWhitespace);
0688     mGoNextConflict = GuiUtils::createAction<QAction>(i18n("Go to Next Conflict"), QIcon(QPixmap(down2arrow)), i18n("Next\nConflict"), QKeySequence(Qt::CTRL | Qt::Key_PageDown), this, &KDiff3App::slotGoNextConflict, ac, "go_next_conflict");
0689     mGoNextConflict->setToolTip(mGoNextConflict->text() + omitsWhitespace);
0690     mGoPrevUnsolvedConflict = GuiUtils::createAction<QAction>(i18n("Go to Previous Unsolved Conflict"), QIcon(QPixmap(prevunsolved)), i18n("Prev\nUnsolved"), QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_PageUp), this, &KDiff3App::slotGoPrevUnsolvedConflict, ac, "go_prev_unsolved_conflict");
0691     mGoPrevUnsolvedConflict->setToolTip(mGoPrevUnsolvedConflict->text() + includeWhitespace);
0692     mGoNextUnsolvedConflict = GuiUtils::createAction<QAction>(i18n("Go to Next Unsolved Conflict"), QIcon(QPixmap(nextunsolved)), i18n("Next\nUnsolved"), QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_PageDown), this, &KDiff3App::slotGoNextUnsolvedConflict, ac, "go_next_unsolved_conflict");
0693     mGoNextUnsolvedConflict->setToolTip(mGoNextUnsolvedConflict->text() + includeWhitespace);
0694     mGotoLine = GuiUtils::createAction<QAction>(i18nc("Title for menu item", "Go to Line"), QIcon(QPixmap(gotoline)), i18nc("Text used for toolbar button.", "Go\nLine"), QKeySequence(Qt::CTRL | Qt::Key_G), this, &KDiff3App::slotGoToLine, ac, "go_to_line");
0695     mGotoLine->setToolTip(mGoNextUnsolvedConflict->text() + ".\n" + i18nc("Tooltip Text", "Goto specified line."));
0696     chooseA = GuiUtils::createAction<KToggleAction>(i18nc("Title for menu item", "Select Line(s) From A"), QIcon(QPixmap(iconA)), i18nc("Text used for select A toolbar button.", "Choose\nA"), QKeySequence(Qt::CTRL | Qt::Key_1), this, &KDiff3App::slotChooseA, ac, "merge_choose_a");
0697     chooseB = GuiUtils::createAction<KToggleAction>(i18nc("Title for menu item", "Select Line(s) From B"), QIcon(QPixmap(iconB)), i18nc("Text used for select B when toolbar button.", "Choose\nB"), QKeySequence(Qt::CTRL | Qt::Key_2), this, &KDiff3App::slotChooseB, ac, "merge_choose_b");
0698     chooseC = GuiUtils::createAction<KToggleAction>(i18nc("Title for menu item", "Select Line(s) From C"), QIcon(QPixmap(iconC)), i18nc("Text used for select C toolbar button.", "Choose\nC"), QKeySequence(Qt::CTRL | Qt::Key_3), this, &KDiff3App::slotChooseC, ac, "merge_choose_c");
0699     autoAdvance = GuiUtils::createAction<KToggleAction>(i18nc("Title for menu item", "Automatically Go to Next Unsolved Conflict After Source Selection"), QIcon(QPixmap(autoadvance)), i18nc("Auto goto next unsolved toolbar text.", "Auto\nNext"), this, &KDiff3App::slotAutoAdvanceToggled, ac, "merge_autoadvance");
0700 
0701     showWhiteSpaceCharacters = GuiUtils::createAction<KToggleAction>(i18n("Show Space && Tabulator Characters"), QIcon(QPixmap(showwhitespacechars)), i18nc("Show whitespace toolbar text.", "White\nCharacters"), this, &KDiff3App::slotShowWhiteSpaceToggled, ac, "diff_show_whitespace_characters");
0702     showWhiteSpace = GuiUtils::createAction<KToggleAction>(i18n("Show White Space"), QIcon(QPixmap(showwhitespace)), i18nc("Show whitespace changes toolbar text.", "White\nDeltas"), this, &KDiff3App::slotShowWhiteSpaceToggled, ac, "diff_show_whitespace");
0703 
0704     showLineNumbers = GuiUtils::createAction<KToggleAction>(i18n("Show Line Numbers"), QIcon(QPixmap(showlinenumbers)), i18nc("Show line numbers toolbar text", "Line\nNumbers"), this, &KDiff3App::slotShowLineNumbersToggled, ac, "diff_showlinenumbers");
0705 
0706     mAutoSolve = GuiUtils::createAction<QAction>(i18n("Automatically Solve Simple Conflicts"), this, &KDiff3App::slotAutoSolve, ac, "merge_autosolve");
0707     mUnsolve = GuiUtils::createAction<QAction>(i18n("Set Deltas to Conflicts"), this, &KDiff3App::slotUnsolve, ac, "merge_autounsolve");
0708     mergeRegExp = GuiUtils::createAction<QAction>(i18n("Run Regular Expression Auto Merge"), this, &KDiff3App::slotRegExpAutoMerge, ac, "merge_regexp_automerge");
0709     mMergeHistory = GuiUtils::createAction<QAction>(i18n("Automatically Solve History Conflicts"), this, &KDiff3App::slotMergeHistory, ac, "merge_versioncontrol_history");
0710     splitDiff = GuiUtils::createAction<QAction>(i18n("Split Diff At Selection"), this, &KDiff3App::slotSplitDiff, ac, "merge_splitdiff");
0711     joinDiffs = GuiUtils::createAction<QAction>(i18n("Join Selected Diffs"), this, &KDiff3App::slotJoinDiffs, ac, "merge_joindiffs");
0712 
0713     showWindowA = GuiUtils::createAction<KToggleAction>(i18n("Show Window A"), this, &KDiff3App::slotShowWindowAToggled, ac, "win_show_a");
0714     showWindowB = GuiUtils::createAction<KToggleAction>(i18n("Show Window B"), this, &KDiff3App::slotShowWindowBToggled, ac, "win_show_b");
0715     showWindowC = GuiUtils::createAction<KToggleAction>(i18n("Show Window C"), this, &KDiff3App::slotShowWindowCToggled, ac, "win_show_c");
0716 
0717     overviewModeNormal = GuiUtils::createAction<KToggleAction>(i18n("Normal Overview"), this, &KDiff3App::slotOverviewNormal, ac, "diff_overview_normal");
0718     overviewModeAB = GuiUtils::createAction<KToggleAction>(i18n("A vs. B Overview"), this, &KDiff3App::slotOverviewAB, ac, "diff_overview_ab");
0719     overviewModeAC = GuiUtils::createAction<KToggleAction>(i18n("A vs. C Overview"), this, &KDiff3App::slotOverviewAC, ac, "diff_overview_ac");
0720     overviewModeBC = GuiUtils::createAction<KToggleAction>(i18n("B vs. C Overview"), this, &KDiff3App::slotOverviewBC, ac, "diff_overview_bc");
0721     wordWrap = GuiUtils::createAction<KToggleAction>(i18n("Word Wrap Diff Windows"), this, &KDiff3App::slotWordWrapToggled, ac, "diff_wordwrap");
0722     addManualDiffHelp = GuiUtils::createAction<QAction>(i18n("Add Manual Diff Alignment"), QKeySequence(Qt::CTRL | Qt::Key_Y), this, &KDiff3App::slotAddManualDiffHelp, ac, "diff_add_manual_diff_help");
0723     clearManualDiffHelpList = GuiUtils::createAction<QAction>(i18n("Clear All Manual Diff Alignments"), QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_Y), this, &KDiff3App::slotClearManualDiffHelpList, ac, "diff_clear_manual_diff_help_list");
0724 
0725     winFocusNext = GuiUtils::createAction<QAction>(i18n("Focus Next Window"), QKeySequence(Qt::ALT | Qt::Key_Right), this, &KDiff3App::slotWinFocusNext, ac, "win_focus_next");
0726     winFocusPrev = GuiUtils::createAction<QAction>(i18n("Focus Prev Window"), QKeySequence(Qt::ALT | Qt::Key_Left), this, &KDiff3App::slotWinFocusPrev, ac, "win_focus_prev");
0727     winToggleSplitOrientation = GuiUtils::createAction<QAction>(i18n("Toggle Split Orientation"), this, &KDiff3App::slotWinToggleSplitterOrientation, ac, "win_toggle_split_orientation");
0728 
0729     dirShowBoth = GuiUtils::createAction<KToggleAction>(i18n("Folder && Text Split Screen View"), this, &KDiff3App::slotDirShowBoth, ac, "win_dir_show_both");
0730     dirShowBoth->setChecked(true);
0731     dirViewToggle = GuiUtils::createAction<QAction>(i18n("Toggle Between Folder && Text View"), this, &KDiff3App::slotDirViewToggle, ac, "win_dir_view_toggle");
0732 
0733     m_pMergeEditorPopupMenu = new QMenu(this);
0734     m_pMergeEditorPopupMenu->addAction(chooseA);
0735     m_pMergeEditorPopupMenu->addAction(chooseB);
0736     m_pMergeEditorPopupMenu->addAction(chooseC);
0737 
0738     mEscapeAction = new QShortcut(Qt::Key_Escape, this, this, &KDiff3App::slotFileQuit);
0739 
0740     MergeResultWindow::initActions(ac);
0741 }
0742 
0743 void KDiff3App::showPopupMenu(const QPoint& point)
0744 {
0745     m_pMergeEditorPopupMenu->popup(point);
0746 }
0747 
0748 void KDiff3App::initStatusBar()
0749 {
0750     ///////////////////////////////////////////////////////////////////
0751     // STATUSBAR
0752     if(statusBar() != nullptr)
0753         statusBar()->showMessage(i18n("Ready."));
0754 }
0755 
0756 void KDiff3App::saveWindow(const KSharedConfigPtr config)
0757 {
0758     KConfigGroup group = config->group(KDIFF3_CONFIG_GROUP);
0759     group.writeEntry("mainWindow-geometry", saveGeometry());
0760     group.writeEntry("mainWindow-state", saveState(1));
0761     group.writeEntry("shell-geometry", m_pKDiff3Shell->saveGeometry());
0762     group.writeEntry("shell-state", m_pKDiff3Shell->saveState());
0763 }
0764 
0765 bool KDiff3App::restoreWindow(const KSharedConfigPtr config)
0766 {
0767     KConfigGroup group = config->group(KDIFF3_CONFIG_GROUP);
0768     if(m_pKDiff3Shell->restoreState(group.readEntry("mainWindow-state", QVariant(QByteArray())).toByteArray()))
0769     {
0770         bool r = m_pKDiff3Shell->restoreGeometry(group.readEntry("mainWindow-geometry", QVariant(QByteArray())).toByteArray());
0771         group.deleteEntry("mainWindow-state");
0772         group.deleteEntry("mainWindow-geometry");
0773         saveWindow(config);
0774         return r;
0775     }
0776 
0777     return (restoreGeometry(group.readEntry("mainWindow-geometry", QVariant(QByteArray())).toByteArray()) &&
0778             restoreState(group.readEntry("mainWindow-state", QVariant(QByteArray())).toByteArray(), 1)) &&
0779            (m_pKDiff3Shell->restoreGeometry(group.readEntry("shell-geometry", QVariant(QByteArray())).toByteArray()) &&
0780             m_pKDiff3Shell->restoreState(group.readEntry("shell-state", QVariant(QByteArray())).toByteArray()));
0781 }
0782 
0783 void KDiff3App::saveOptions(KSharedConfigPtr config)
0784 {
0785     if(!m_bAutoMode)
0786     {
0787         saveWindow(config);
0788         m_pOptionDialog->saveOptions(std::move(config));
0789     }
0790 }
0791 
0792 bool KDiff3App::queryClose()
0793 {
0794     saveOptions(KSharedConfig::openConfig());
0795 
0796     if(m_bOutputModified)
0797     {
0798         KMessageBox::ButtonCode result = Compat::warningTwoActionsCancel(this,
0799                                                                          i18n("The merge result has not been saved."),
0800                                                                          i18nc("Error dialog title", "Warning"),
0801                                                                          KGuiItem(i18n("Save && Quit")),
0802                                                                          KGuiItem(i18n("Quit Without Saving")));
0803         if(result == KMessageBox::Cancel)
0804             return false;
0805         else if(result == Compat::PrimaryAction)
0806         {
0807             slotFileSave();
0808             if(m_bOutputModified)
0809             {
0810                 KMessageBox::error(this, i18n("Saving the merge result failed."), i18nc("Error dialog title", "Warning"));
0811                 return false;
0812             }
0813         }
0814     }
0815 
0816     m_bOutputModified = false;
0817 
0818     if(m_pDirectoryMergeWindow->isDirectoryMergeInProgress())
0819     {
0820         qint32 result = Compat::warningTwoActions(this,
0821                                                i18n("You are currently doing a folder merge. Are you sure, you want to abort?"),
0822                                                i18nc("Error dialog title", "Warning"),
0823                                                KStandardGuiItem::quit(),
0824                                                KStandardGuiItem::cont() /* i18n("Continue Merging") */);
0825         if(result != Compat::PrimaryAction)
0826             return false;
0827     }
0828 
0829     return true;
0830 }
0831 
0832 /////////////////////////////////////////////////////////////////////
0833 // SLOT IMPLEMENTATION
0834 /////////////////////////////////////////////////////////////////////
0835 
0836 void KDiff3App::slotFileSave()
0837 {
0838     if(m_bDefaultFilename)
0839     {
0840         slotFileSaveAs();
0841     }
0842     else
0843     {
0844         slotStatusMsg(i18n("Saving file..."));
0845 
0846         bool bSuccess = m_pMergeResultWindow->saveDocument(m_outputFilename, m_pMergeResultWindowTitle->getEncoding(), m_pMergeResultWindowTitle->getLineEndStyle());
0847         if(bSuccess)
0848         {
0849             m_bFileSaved = true;
0850             m_bOutputModified = false;
0851             if(m_bDirCompare)
0852                 m_pDirectoryMergeWindow->mergeResultSaved(m_outputFilename);
0853         }
0854 
0855         slotStatusMsg(i18n("Ready."));
0856     }
0857 }
0858 
0859 void KDiff3App::slotFileSaveAs()
0860 {
0861     slotStatusMsg(i18n("Saving file with a new filename..."));
0862 
0863     QString s = QFileDialog::getSaveFileUrl(this, i18n("Save As..."), QUrl::fromLocalFile(QDir::currentPath())).url(QUrl::PreferLocalFile);
0864     if(!s.isEmpty())
0865     {
0866         m_outputFilename = s;
0867         m_pMergeResultWindowTitle->setFileName(m_outputFilename);
0868         bool bSuccess = m_pMergeResultWindow->saveDocument(m_outputFilename, m_pMergeResultWindowTitle->getEncoding(), m_pMergeResultWindowTitle->getLineEndStyle());
0869         if(bSuccess)
0870         {
0871             m_bOutputModified = false;
0872             if(m_bDirCompare)
0873                 m_pDirectoryMergeWindow->mergeResultSaved(m_outputFilename);
0874         }
0875         //setWindowTitle(url.fileName(),doc->isModified());
0876 
0877         m_bDefaultFilename = false;
0878     }
0879 
0880     slotStatusMsg(i18n("Ready."));
0881 }
0882 
0883 void KDiff3App::slotFilePrint()
0884 {
0885     if(m_pDiffTextWindow1 == nullptr || m_pDiffTextWindow2 == nullptr)
0886         return;
0887 #ifdef QT_NO_PRINTER
0888     slotStatusMsg(i18n("Printing not implemented."));
0889 #else
0890     QPrinter printer;
0891     QPointer<QPrintDialog> printDialog = QPointer<QPrintDialog>(new QPrintDialog(&printer, this));
0892 
0893     LineRef firstSelectionD3LIdx;
0894     LineRef lastSelectionD3LIdx;
0895 
0896     m_pDiffTextWindow1->getSelectionRange(&firstSelectionD3LIdx, &lastSelectionD3LIdx, eD3LLineCoords);
0897 
0898     if(!firstSelectionD3LIdx.isValid())
0899     {
0900         m_pDiffTextWindow2->getSelectionRange(&firstSelectionD3LIdx, &lastSelectionD3LIdx, eD3LLineCoords);
0901     }
0902     if(!firstSelectionD3LIdx.isValid() && m_pDiffTextWindow3 != nullptr)
0903     {
0904         m_pDiffTextWindow3->getSelectionRange(&firstSelectionD3LIdx, &lastSelectionD3LIdx, eD3LLineCoords);
0905     }
0906 
0907     printDialog->setOption(QPrintDialog::PrintCurrentPage);
0908 
0909     if(firstSelectionD3LIdx.isValid())
0910     {
0911         printDialog->setOption(QPrintDialog::PrintSelection);
0912         printDialog->setPrintRange(QAbstractPrintDialog::Selection);
0913     }
0914 
0915     if(!firstSelectionD3LIdx.isValid())
0916         printDialog->setPrintRange(QAbstractPrintDialog::AllPages);
0917     printDialog->setFromTo(0, 0);
0918 
0919     qint32 currentFirstLine = m_pDiffTextWindow1->getFirstLine();
0920     qint32 currentFirstD3LIdx = m_pDiffTextWindow1->convertLineToDiff3LineIdx(currentFirstLine);
0921 
0922     // do some printer initialization
0923     printer.setFullPage(false);
0924 
0925     // initialize the printer using the print dialog
0926     if(printDialog->exec() == QDialog::Accepted)
0927     {
0928         slotStatusMsg(i18n("Printing..."));
0929         // create a painter to paint on the printer object
0930         RLPainter painter(&printer, gOptions->m_bRightToLeftLanguage, width(), fontMetrics().horizontalAdvance('W'));
0931 
0932         QPaintDevice* pPaintDevice = painter.device();
0933         qint32 dpiy = pPaintDevice->logicalDpiY();
0934         qint32 columnDistance = qRound((0.5 / 2.54) * dpiy); // 0.5 cm between the columns
0935 
0936         qint32 columns = m_bTripleDiff ? 3 : 2;
0937         qint32 columnWidth = (pPaintDevice->width() - (columns - 1) * columnDistance) / columns;
0938 
0939         QFont f = gOptions->defaultFont();
0940         f.setPointSizeF(f.pointSizeF() - 1); // Print with slightly smaller font.
0941         painter.setFont(f);
0942         QFontMetrics fm = painter.fontMetrics();
0943 
0944         QString topLineText = i18n("Top line");
0945 
0946         qint32 headerWidth = fm.horizontalAdvance(m_sd1->getAliasName() + ", " + topLineText + ": 01234567");
0947         qint32 headerLines = headerWidth / columnWidth + 1;
0948 
0949         qint32 headerMargin = headerLines * fm.height() + 3; // Text + one horizontal line
0950         qint32 footerMargin = fm.height() + 3;
0951 
0952         QRect view(0, headerMargin, pPaintDevice->width(), pPaintDevice->height() - (headerMargin + footerMargin));
0953         QRect view1(0 * (columnWidth + columnDistance), view.top(), columnWidth, view.height());
0954         QRect view2(1 * (columnWidth + columnDistance), view.top(), columnWidth, view.height());
0955         QRect view3(2 * (columnWidth + columnDistance), view.top(), columnWidth, view.height());
0956 
0957         LineType linesPerPage = view.height() / fm.lineSpacing();
0958 
0959         m_pEventLoopForPrinting = QPointer<QEventLoop>(new QEventLoop());
0960         if(gOptions->wordWrapOn())
0961         {
0962             // For printing the lines are wrapped differently (this invalidates the first line)
0963             recalcWordWrap(columnWidth);
0964             m_pEventLoopForPrinting->exec();
0965         }
0966 
0967         LineType totalNofLines = std::max(m_pDiffTextWindow1->getNofLines(), m_pDiffTextWindow2->getNofLines());
0968 
0969         if(m_bTripleDiff && m_pDiffTextWindow3 != nullptr)
0970             totalNofLines = std::max(totalNofLines, m_pDiffTextWindow3->getNofLines());
0971 
0972         bool bPrintCurrentPage = false;
0973         bool bFirstPrintedPage = false;
0974 
0975         bool bPrintSelection = false;
0976         quint32 totalNofPages = (totalNofLines + linesPerPage - 1) / linesPerPage;
0977         LineRef line;
0978         LineRef selectionEndLine;
0979         quint32 from = 0, to = 0;
0980 
0981         if(printer.printRange() == QPrinter::AllPages)
0982         {
0983             to = totalNofPages;
0984             from = 1;
0985         }
0986         else if(printer.printRange() == QPrinter::PageRange)
0987         {
0988             from = printer.fromPage(), to = printer.toPage();
0989             /*
0990                 Per Qt docs QPrinter::fromPage and QPrinter::toPage return 0 to indicate they are not set.
0991                 Account for this and other invalid settings the user may try.
0992             */
0993             if(from == 0) from = 1;
0994             if(from > totalNofPages) from = totalNofPages;
0995             if(to == 0 || to > totalNofPages) to = totalNofPages;
0996             if(from > to) to = from;
0997         }
0998         else if(printer.printRange() == QPrinter::CurrentPage)
0999         {
1000             bPrintCurrentPage = true;
1001             // Detect the first visible line in the window.
1002             to = from = line = m_pDiffTextWindow1->convertDiff3LineIdxToLine(currentFirstD3LIdx);
1003         }
1004         else if(printer.printRange() == QPrinter::Selection)
1005         {
1006             bPrintSelection = true;
1007             if(firstSelectionD3LIdx.isValid())
1008             {
1009                 line = m_pDiffTextWindow1->convertDiff3LineIdxToLine(firstSelectionD3LIdx);
1010                 selectionEndLine = m_pDiffTextWindow1->convertDiff3LineIdxToLine(lastSelectionD3LIdx + 1);
1011                 totalNofPages = (selectionEndLine - line + linesPerPage - 1) / linesPerPage;
1012             }
1013         }
1014 
1015         qint32 page = 1;
1016 
1017         ProgressScope pp;
1018         ProgressProxy::setMaxNofSteps(totalNofPages);
1019         quint32 i = from;
1020 
1021         while(bPrintCurrentPage ||
1022               (!bPrintSelection && i <= to) ||
1023               (bPrintSelection && line < selectionEndLine))
1024         {
1025             assert(!(bPrintCurrentPage && i > from));
1026             ProgressProxy::setInformation(i18nc("Status message", "Printing page %1 of %2", page, totalNofPages), false);
1027             ProgressProxy::setCurrent(page - 1);
1028             if(ProgressProxy::wasCancelled())
1029             {
1030                 printer.abort();
1031                 break;
1032             }
1033 
1034             if(!bPrintSelection && !bPrintCurrentPage)
1035             {
1036                 page = i;
1037                 line = (page - 1) * linesPerPage;
1038             }
1039             else if(bPrintSelection)
1040             {
1041                 assert(line < selectionEndLine);
1042 
1043                 if(selectionEndLine - line < linesPerPage)
1044                     linesPerPage = selectionEndLine - line;
1045             }
1046 
1047             if(line.isValid() && line < totalNofLines)
1048             {
1049                 if(bFirstPrintedPage)
1050                     printer.newPage();
1051 
1052                 painter.setClipping(true);
1053 
1054                 painter.setPen(gOptions->aColor());
1055                 QString headerText1 = m_sd1->getAliasName() + ", " + topLineText + ": " + QString::number(m_pDiffTextWindow1->calcTopLineInFile(line) + 1);
1056                 m_pDiffTextWindow1->printWindow(painter, view1, headerText1, line, linesPerPage, gOptions->foregroundColor());
1057 
1058                 painter.setPen(gOptions->bColor());
1059                 QString headerText2 = m_sd2->getAliasName() + ", " + topLineText + ": " + QString::number(m_pDiffTextWindow2->calcTopLineInFile(line) + 1);
1060                 m_pDiffTextWindow2->printWindow(painter, view2, headerText2, line, linesPerPage, gOptions->foregroundColor());
1061 
1062                 if(m_bTripleDiff && m_pDiffTextWindow3 != nullptr)
1063                 {
1064                     painter.setPen(gOptions->cColor());
1065                     QString headerText3 = m_sd3->getAliasName() + ", " + topLineText + ": " + QString::number(m_pDiffTextWindow3->calcTopLineInFile(line) + 1);
1066                     m_pDiffTextWindow3->printWindow(painter, view3, headerText3, line, linesPerPage, gOptions->foregroundColor());
1067                 }
1068                 painter.setClipping(false);
1069 
1070                 painter.setPen(gOptions->foregroundColor());
1071                 painter.drawLine(0, view.bottom() + 3, view.width(), view.bottom() + 3);
1072                 QString s = bPrintCurrentPage ? QString("")
1073                                               : QString::number(page) + '/' + QString::number(totalNofPages);
1074                 if(bPrintSelection) s += i18n(" (Selection)");
1075                 painter.drawText((view.right() - painter.fontMetrics().horizontalAdvance(s)) / 2,
1076                                  view.bottom() + painter.fontMetrics().ascent() + 5, s);
1077 
1078                 bFirstPrintedPage = true;
1079                 if(bPrintCurrentPage) break;
1080             }
1081 
1082             if(bPrintSelection)
1083             {
1084                 line += linesPerPage;
1085                 ++page;
1086             }
1087             else
1088             {
1089                 ++i;
1090             }
1091         }
1092 
1093         painter.end();
1094 
1095         if(gOptions->wordWrapOn())
1096         {
1097             recalcWordWrap();
1098             m_pEventLoopForPrinting->exec();
1099             DiffTextWindow::mVScrollBar->setValue(m_pDiffTextWindow1->convertDiff3LineIdxToLine(currentFirstD3LIdx));
1100         }
1101         m_pEventLoopForPrinting.clear();
1102 
1103         slotStatusMsg(i18n("Printing completed."));
1104     }
1105     else
1106     {
1107         slotStatusMsg(i18n("Printing aborted."));
1108     }
1109 #endif
1110 }
1111 
1112 void KDiff3App::slotFileQuit()
1113 {
1114     slotStatusMsg(i18n("Exiting..."));
1115 
1116     if(!queryClose())
1117         return; // Don't quit
1118 
1119     QApplication::exit(isFileSaved() || isDirComparison() ? 0 : 1);
1120 }
1121 
1122 void KDiff3App::slotViewStatusBar()
1123 {
1124     slotStatusMsg(i18n("Toggle the statusbar..."));
1125     gOptions->setStatusBarState(viewStatusBar->isChecked());
1126     ///////////////////////////////////////////////////////////////////
1127     //turn Statusbar on or off
1128     if(statusBar() != nullptr)
1129     {
1130         if(!viewStatusBar->isChecked())
1131         {
1132             statusBar()->hide();
1133         }
1134         else
1135         {
1136             statusBar()->show();
1137         }
1138     }
1139 
1140     slotStatusMsg(i18n("Ready."));
1141 }
1142 
1143 void KDiff3App::slotStatusMsg(const QString& text)
1144 {
1145     ///////////////////////////////////////////////////////////////////
1146     // change status message permanently
1147     if(statusBar() != nullptr)
1148     {
1149         statusBar()->clearMessage();
1150         statusBar()->showMessage(text);
1151     }
1152 }
1153 
1154 //#include "kdiff3.moc"