File indexing completed on 2024-05-05 05:45:46

0001 /*
0002     SPDX-FileCopyrightText: 2001-2005, 2009 Otto Bruggeman <bruggie@gmail.com>
0003     SPDX-FileCopyrightText: 2001-2003 John Firebaugh <jfirebaugh@kde.org>
0004     SPDX-FileCopyrightText: 2004 Jeff Snyder <jeff@caffeinated.me.uk>
0005     SPDX-FileCopyrightText: 2007-2011 Kevin Kofler <kevin.kofler@chello.at>
0006     SPDX-FileCopyrightText: 2012 Jean-Nicolas Artaud <jeannicolasartaud@gmail.com>
0007 
0008     SPDX-License-Identifier: GPL-2.0-or-later
0009 */
0010 
0011 #include "kompare_part.h"
0012 
0013 #include <QDialog>
0014 #include <QLayout>
0015 #include <QWidget>
0016 #include <QMenu>
0017 #include <QPainter>
0018 #include <QPrinter>
0019 #include <QPageLayout>
0020 #include <QPrintDialog>
0021 #include <QPrintPreviewDialog>
0022 #include <QTemporaryDir>
0023 #include <QTemporaryFile>
0024 
0025 #include <KPluginMetaData>
0026 #include <KActionCollection>
0027 #include <KJobWidgets>
0028 #include <KLocalizedString>
0029 #include <KMessageBox>
0030 #include <KSharedConfig>
0031 #include <KStandardAction>
0032 #include <KStandardShortcut>
0033 #include <KStandardGuiItem>
0034 #include <KXMLGUIFactory>
0035 
0036 #include <KIO/CopyJob>
0037 #include <KIO/StatJob>
0038 #include <KIO/FileCopyJob>
0039 #include <KIO/MkdirJob>
0040 
0041 #include <KompareDiff2/DiffModel>
0042 #include <KompareDiff2/DiffSettings>
0043 
0044 #include <komparepartdebug.h>
0045 #include "komparelistview.h"
0046 #include "kompareconnectwidget.h"
0047 #include "viewsettings.h"
0048 #include "kompareprefdlg.h"
0049 #include "komparesaveoptionswidget.h"
0050 #include "komparesplitter.h"
0051 #include "kompareview.h"
0052 
0053 using namespace KompareDiff2;
0054 
0055 ViewSettings* KomparePart::m_viewSettings = nullptr;
0056 KompareDiff2::DiffSettings* KomparePart::m_diffSettings = nullptr;
0057 
0058 KomparePart::KomparePart(QWidget* parentWidget, QObject* parent, const KPluginMetaData& metaData, Modus modus) :
0059     KParts::ReadWritePart(parent, metaData),
0060     m_info()
0061 {
0062     // set our XML-UI resource file
0063     setXMLFile(QStringLiteral("komparepartui.rc"));
0064 
0065     if (!m_viewSettings) {
0066         m_viewSettings = new ViewSettings(nullptr);
0067     }
0068     if (!m_diffSettings) {
0069         m_diffSettings = new KompareDiff2::DiffSettings();
0070     }
0071 
0072     readProperties(KSharedConfig::openConfig().data());
0073 
0074     m_view = new KompareView(m_viewSettings, parentWidget);
0075     m_view->setContextMenuPolicy(Qt::CustomContextMenu);
0076     connect(m_view, &QWidget::customContextMenuRequested,
0077             this, &KomparePart::onContextMenuRequested);
0078     setWidget(m_view);
0079     m_splitter = m_view->splitter();
0080 
0081     // This creates the "Model creator" and connects the signals and slots
0082     m_modelList = new KompareDiff2::ModelList(m_diffSettings, this, (modus == ReadWriteModus));
0083 
0084     const auto modelListActions = m_modelList->actionCollection()->actions();
0085     for (QAction* action : modelListActions) {
0086         actionCollection()->addAction(action->objectName(), action);
0087     }
0088     connect(m_modelList, &ModelList::status,
0089             this, &KomparePart::slotSetStatus);
0090     connect(m_modelList, &ModelList::setStatusBarModelInfo,
0091             this, &KomparePart::setStatusBarModelInfo);
0092     connect(m_modelList, &ModelList::error,
0093             this, &KomparePart::slotShowError);
0094     connect(m_modelList, &ModelList::applyAllDifferences,
0095             this, &KomparePart::updateActions);
0096     connect(m_modelList, static_cast<void(ModelList::*)(bool)>(&ModelList::applyDifference),
0097             this, &KomparePart::updateActions);
0098     connect(m_modelList, &ModelList::applyAllDifferences,
0099             this, &KomparePart::appliedChanged);
0100     connect(m_modelList, static_cast<void(ModelList::*)(bool)>(&ModelList::applyDifference),
0101             this, &KomparePart::appliedChanged);
0102     connect(m_modelList, &ModelList::updateActions, this, &KomparePart::updateActions);
0103 
0104     // This is the stuff to connect the "interface" of the kompare part to the model inside
0105     connect(m_modelList, &ModelList::modelsChanged,
0106             this, &KomparePart::modelsChanged);
0107 
0108     typedef void(ModelList::*void_ModelList_argModelDiff)(const DiffModel*, const Difference*);
0109     typedef void(ModelList::*void_ModelList_argDiffBool)(const Difference*, bool);
0110     typedef void(KompareSplitter::*void_KompareSplitter_argModelDiff)(const DiffModel*, const Difference*);
0111     typedef void(KompareSplitter::*void_KompareSplitter_argDiffBool)(const Difference*, bool);
0112     typedef void(KomparePart::*void_KomparePart_argModelDiff)(const DiffModel*, const Difference*);
0113     typedef void(KomparePart::*void_KomparePart_argDiffBool)(const Difference*, bool);
0114     connect(m_modelList, static_cast<void_ModelList_argModelDiff>(&ModelList::setSelection),
0115             this, static_cast<void_KomparePart_argModelDiff>(&KomparePart::setSelection));
0116     connect(this, static_cast<void_KomparePart_argModelDiff>(&KomparePart::selectionChanged),
0117             m_modelList, static_cast<void_ModelList_argModelDiff>(&ModelList::slotSelectionChanged));
0118 
0119     connect(m_modelList, static_cast<void(ModelList::*)(const Difference*)>(&ModelList::setSelection),
0120             this, static_cast<void(KomparePart::*)(const Difference*)>(&KomparePart::setSelection));
0121     connect(this, static_cast<void(KomparePart::*)(const Difference*)>(&KomparePart::selectionChanged),
0122             m_modelList, static_cast<void(ModelList::*)(const Difference*)>(&ModelList::slotSelectionChanged));
0123 
0124     connect(m_modelList, static_cast<void(ModelList::*)(bool)>(&ModelList::applyDifference),
0125             this, static_cast<void(KomparePart::*)(bool)>(&KomparePart::applyDifference));
0126     connect(m_modelList, &ModelList::applyAllDifferences,
0127             this, &KomparePart::applyAllDifferences);
0128     connect(m_modelList, static_cast<void_ModelList_argDiffBool>(&ModelList::applyDifference),
0129             this, static_cast<void_KomparePart_argDiffBool>(&KomparePart::applyDifference));
0130     connect(m_modelList, &ModelList::diffString,
0131             this, &KomparePart::diffString);
0132 
0133     connect(this, &KomparePart::kompareInfo, m_modelList, &ModelList::slotKompareInfo);
0134 
0135     // Here we connect the splitter to the modellist
0136     connect(m_modelList, static_cast<void_ModelList_argModelDiff>(&ModelList::setSelection),
0137             m_splitter,  static_cast<void_KompareSplitter_argModelDiff>(&KompareSplitter::slotSetSelection));
0138 //     connect(m_splitter,  SIGNAL(selectionChanged(const KompareDiff2::Difference*,const KompareDiff2::Difference*)),
0139 //             m_modelList, SLOT(slotSelectionChanged(const KompareDiff2::Difference*,const KompareDiff2::Difference*)));
0140     connect(m_modelList, static_cast<void(ModelList::*)(const Difference*)>(&ModelList::setSelection),
0141             m_splitter,  static_cast<void(KompareSplitter::*)(const Difference*)>(&KompareSplitter::slotSetSelection));
0142     connect(m_splitter,  static_cast<void(KompareSplitter::*)(const Difference*)>(&KompareSplitter::selectionChanged),
0143             m_modelList, static_cast<void(ModelList::*)(const Difference*)>(&ModelList::slotSelectionChanged));
0144 
0145     connect(m_modelList, static_cast<void(ModelList::*)(bool)>(&ModelList::applyDifference),
0146             m_splitter, static_cast<void(KompareSplitter::*)(bool)>(&KompareSplitter::slotApplyDifference));
0147     connect(m_modelList, &ModelList::applyAllDifferences,
0148             m_splitter, &KompareSplitter::slotApplyAllDifferences);
0149     connect(m_modelList, static_cast<void_ModelList_argDiffBool>(&ModelList::applyDifference),
0150             m_splitter, static_cast<void_KompareSplitter_argDiffBool>(&KompareSplitter::slotApplyDifference));
0151     connect(this, &KomparePart::configChanged, m_splitter, &KompareSplitter::configChanged);
0152 
0153     setupActions(modus);
0154 
0155     // we are read-write by default -> uhm what if we are opened by lets say konq in RO mode ?
0156     // Then we should not be doing this...
0157     setReadWrite((modus == ReadWriteModus));
0158 
0159     // we are not modified since we haven't done anything yet
0160     setModified(false);
0161 }
0162 
0163 KomparePart::~KomparePart()
0164 {
0165     // This is the only place allowed to call cleanUpTemporaryFiles
0166     // because before there might still be a use for them (when swapping)
0167     cleanUpTemporaryFiles();
0168 }
0169 
0170 void KomparePart::setupActions(Modus modus)
0171 {
0172     // create our actions
0173 
0174     if (modus == ReadWriteModus) {
0175         m_saveAll = actionCollection()->addAction(QStringLiteral("file_save_all"), this, &KomparePart::saveAll);
0176         m_saveAll->setIcon(QIcon::fromTheme(QStringLiteral("document-save-all")));
0177         m_saveAll->setText(i18nc("@action", "Save &All"));
0178         m_saveDiff = actionCollection()->addAction(QStringLiteral("file_save_diff"), this, &KomparePart::saveDiff);
0179         m_saveDiff->setText(i18nc("@action", "Save &Diff..."));
0180         m_swap = actionCollection()->addAction(QStringLiteral("file_swap"), this, &KomparePart::slotSwap);
0181         m_swap->setText(i18nc("@action", "Swap Source with Destination"));
0182     } else {
0183         m_saveAll = nullptr;
0184         m_saveDiff = nullptr;
0185         m_swap = nullptr;
0186     }
0187     m_diffStats = actionCollection()->addAction(QStringLiteral("file_diffstats"), this, &KomparePart::slotShowDiffstats);
0188     m_diffStats->setText(i18nc("@action", "Show Statistics"));
0189     m_diffRefresh = actionCollection()->addAction(QStringLiteral("file_refreshdiff"), this, &KomparePart::slotRefreshDiff);
0190     m_diffRefresh->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh")));
0191     m_diffRefresh->setText(i18nc("@action", "Refresh Diff"));
0192     actionCollection()->setDefaultShortcuts(m_diffRefresh, KStandardShortcut::reload());
0193 
0194     m_print        = KStandardAction::print(this, &KomparePart::slotFilePrint, actionCollection());
0195     m_printPreview = KStandardAction::printPreview(this, &KomparePart::slotFilePrintPreview, actionCollection());
0196     KStandardAction::preferences(this, &KomparePart::optionsPreferences, actionCollection());
0197 }
0198 
0199 void KomparePart::updateActions()
0200 {
0201     if (m_saveAll) m_saveAll->setEnabled(m_modelList->hasUnsavedChanges());
0202     if (m_saveDiff) m_saveDiff->setEnabled(m_modelList->mode() == KompareDiff2::ComparingFiles || m_modelList->mode() == KompareDiff2::ComparingDirs);
0203     if (m_swap) m_swap->setEnabled(m_modelList->mode() == KompareDiff2::ComparingFiles || m_modelList->mode() == KompareDiff2::ComparingDirs);
0204     m_diffRefresh->setEnabled(m_modelList->mode() == KompareDiff2::ComparingFiles || m_modelList->mode() == KompareDiff2::ComparingDirs);
0205     m_diffStats->setEnabled(m_modelList->modelCount() > 0);
0206     m_print->setEnabled(m_modelList->modelCount() > 0);          // If modellist has models then we have something to print, it's that simple.
0207     m_printPreview->setEnabled(m_modelList);
0208 }
0209 
0210 void KomparePart::setEncoding(const QString& encoding)
0211 {
0212     qCDebug(KOMPAREPART) << "Encoding: " << encoding;
0213     m_modelList->setEncoding(encoding);
0214 }
0215 
0216 bool KomparePart::openDiff(const QUrl& url)
0217 {
0218     qCDebug(KOMPAREPART) << "Url = " << url.url();
0219 
0220     m_info.mode = KompareDiff2::ShowingDiff;
0221     m_info.source = url;
0222     bool result = false;
0223     fetchURL(url, true);
0224 
0225     Q_EMIT kompareInfo(&m_info);
0226 
0227     if (!m_info.localSource.isEmpty())
0228     {
0229         qCDebug(KOMPAREPART) << "Download succeeded ";
0230         result = m_modelList->openDiff(m_info.localSource);
0231         updateActions();
0232         updateCaption();
0233         updateStatus();
0234     }
0235     else
0236     {
0237         qCDebug(KOMPAREPART) << "Download failed !";
0238     }
0239 
0240     return result;
0241 }
0242 
0243 bool KomparePart::openDiff(const QString& diffOutput)
0244 {
0245     bool value = false;
0246 
0247     m_info.mode = KompareDiff2::ShowingDiff;
0248 
0249     Q_EMIT kompareInfo(&m_info);
0250 
0251     if (m_modelList->parseAndOpenDiff(diffOutput) == 0)
0252     {
0253         value = true;
0254         updateActions();
0255         updateCaption();
0256         updateStatus();
0257     }
0258 
0259     return value;
0260 }
0261 
0262 bool KomparePart::openDiff3(const QUrl& diff3Url)
0263 {
0264     // FIXME: Implement this !!!
0265     qCDebug(KOMPAREPART) << "Not implemented yet. Filename is: " << diff3Url;
0266     return false;
0267 }
0268 
0269 bool KomparePart::openDiff3(const QString& diff3Output)
0270 {
0271     // FIXME: Implement this !!!
0272     qCDebug(KOMPAREPART) << "Not implemented yet. diff3 output is: ";
0273     qCDebug(KOMPAREPART) << diff3Output;
0274     return false;
0275 }
0276 
0277 bool KomparePart::fetchURL(const QUrl& url, bool addToSource)
0278 {
0279     // Default value if there is an error is "", we rely on it!
0280     QString tempFileName;
0281     // Only in case of error do we set result to false, don't forget!!
0282     bool result = true;
0283     QTemporaryDir* tmpDir = nullptr;
0284 
0285     if (!url.isLocalFile())
0286     {
0287         KIO::StatJob* statJob = KIO::stat(url);
0288         KJobWidgets::setWindow(statJob, widget());
0289         if (statJob->exec())
0290         {
0291             KIO::UDSEntry node;
0292             node = statJob->statResult();
0293             if (!node.isDir())
0294             {
0295                 tmpDir = new QTemporaryDir(QDir::tempPath() + QLatin1String("/kompare"));
0296                 tmpDir->setAutoRemove(true);
0297                 tempFileName = tmpDir->path() + QLatin1Char('/') + url.fileName();
0298                 KIO::FileCopyJob* copyJob = KIO::file_copy(url, QUrl::fromLocalFile(tempFileName));
0299                 KJobWidgets::setWindow(copyJob, widget());
0300                 if (! copyJob->exec())
0301                 {
0302                     qDebug() << "download error " << copyJob->errorString();
0303                     slotShowError(i18n("<qt>The URL <b>%1</b> cannot be downloaded.</qt>", url.toDisplayString()));
0304                     tempFileName.clear(); // Not sure if download has already touched this tempFileName when there is an error
0305                     result = false;
0306                 }
0307             }
0308             else
0309             {
0310                 tmpDir = new QTemporaryDir(QDir::tempPath() + QLatin1String("/kompare"));
0311                 tmpDir->setAutoRemove(true);   // Yes this is the default but just to make sure
0312                 KIO::CopyJob* copyJob = KIO::copy(url, QUrl::fromLocalFile(tmpDir->path()));
0313                 KJobWidgets::setWindow(copyJob, widget());
0314                 if (! copyJob->exec())
0315                 {
0316                     slotShowError(i18n("<qt>The URL <b>%1</b> cannot be downloaded.</qt>", url.toDisplayString()));
0317                     delete tmpDir;
0318                     tmpDir = nullptr;
0319                     result = false;
0320                 }
0321                 else
0322                 {
0323                     tempFileName = tmpDir->path();
0324                     qCDebug(KOMPAREPART) << "tempFileName = " << tempFileName;
0325                     // If a directory is copied into QTemporaryDir then the directory in
0326                     // here is what I need to add to tempFileName
0327                     QDir dir(tempFileName);
0328                     QStringList entries = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
0329                     if (entries.size() == 1)   // More than 1 entry in here means big problems!!!
0330                     {
0331                         if (!tempFileName.endsWith(QLatin1Char('/')))
0332                             tempFileName += QLatin1Char('/');
0333                         tempFileName += entries.at(0);
0334                         tempFileName += QLatin1Char('/');
0335                     }
0336                     else
0337                     {
0338                         qCDebug(KOMPAREPART) << "Yikes, nothing downloaded?";
0339                         delete tmpDir;
0340                         tmpDir = nullptr;
0341                         tempFileName.clear();
0342                         result = false;
0343                     }
0344                 }
0345             }
0346         }
0347     }
0348     else
0349     {
0350         // is Local already, check if exists
0351         if (QFile::exists(url.toLocalFile())) {
0352             tempFileName = url.toLocalFile();
0353         } else {
0354             slotShowError(i18n("<qt>The URL <b>%1</b> does not exist on your system.</qt>", url.toDisplayString()));
0355             result = false;
0356         }
0357     }
0358 
0359     if (addToSource)
0360     {
0361         m_info.localSource = tempFileName;
0362         m_info.sourceQTempDir = tmpDir;
0363     }
0364     else
0365     {
0366         m_info.localDestination = tempFileName;
0367         m_info.destinationQTempDir = tmpDir;
0368     }
0369 
0370     return result;
0371 }
0372 
0373 void KomparePart::cleanUpTemporaryFiles()
0374 {
0375     qCDebug(KOMPAREPART) << "Cleaning temporary files.";
0376     if (!m_info.localSource.isEmpty())
0377     {
0378         if (m_info.sourceQTempDir)
0379         {
0380             delete m_info.sourceQTempDir;
0381             m_info.sourceQTempDir = nullptr;
0382         }
0383         m_info.localSource.clear();
0384     }
0385     if (!m_info.localDestination.isEmpty())
0386     {
0387         if (!m_info.destinationQTempDir)
0388         {
0389             delete m_info.destinationQTempDir;
0390             m_info.destinationQTempDir = nullptr;
0391         }
0392         m_info.localDestination.clear();
0393     }
0394 }
0395 
0396 void KomparePart::compare(const QUrl& source, const QUrl& destination)
0397 {
0398     // FIXME: This is silly, i can use NetAccess::stat to figure out what it is and not
0399     // wait until i am in the modellist to determine the mode we're supposed to be in.
0400     // That should make the code more readable
0401     // I should store the QTemporaryDir(s)/File(s) in the Info struct as well and delete it at the right time
0402     m_info.source = source;
0403     m_info.destination = destination;
0404 
0405     // FIXME: (Not urgent) But turn this into an enum, for now i cant find a nice name for the enum that has Source and Destination as values
0406     // For now we do not do error checking, user has already been notified and if the localString is empty then we do not diff
0407     fetchURL(source, true);
0408     fetchURL(destination, false);
0409 
0410     Q_EMIT kompareInfo(&m_info);
0411 
0412     compareAndUpdateAll();
0413 }
0414 
0415 void KomparePart::compareFileString(const QUrl& sourceFile, const QString& destination)
0416 {
0417     //Set the modeto specify that the source is a file, and the destination is a string
0418     m_info.mode = KompareDiff2::ComparingFileString;
0419 
0420     m_info.source = sourceFile;
0421     m_info.localDestination = destination;
0422 
0423     fetchURL(sourceFile, true);
0424 
0425     Q_EMIT kompareInfo(&m_info);
0426 
0427     compareAndUpdateAll();
0428 }
0429 
0430 void KomparePart::compareStringFile(const QString& source, const QUrl& destinationFile)
0431 {
0432     //Set the modeto specify that the source is a file, and the destination is a string
0433     m_info.mode = KompareDiff2::ComparingStringFile;
0434 
0435     m_info.localSource = source;
0436     m_info.destination = destinationFile;
0437 
0438     fetchURL(destinationFile, false);
0439 
0440     Q_EMIT kompareInfo(&m_info);
0441 
0442     compareAndUpdateAll();
0443 }
0444 
0445 void KomparePart::compareFiles(const QUrl& sourceFile, const QUrl& destinationFile)
0446 {
0447     m_info.mode = KompareDiff2::ComparingFiles;
0448 
0449     m_info.source = sourceFile;
0450     m_info.destination = destinationFile;
0451 
0452     // FIXME: (Not urgent) But turn this into an enum, for now i cant find a nice name for the enum that has Source and Destination as values
0453     // For now we do not do error checking, user has already been notified and if the localString is empty then we do not diff
0454     fetchURL(sourceFile, true);
0455     fetchURL(destinationFile, false);
0456 
0457     Q_EMIT kompareInfo(&m_info);
0458 
0459     compareAndUpdateAll();
0460 }
0461 
0462 void KomparePart::compareDirs(const QUrl& sourceDirectory, const QUrl& destinationDirectory)
0463 {
0464     m_info.mode = KompareDiff2::ComparingDirs;
0465 
0466     m_info.source = sourceDirectory;
0467     m_info.destination = destinationDirectory;
0468 
0469     fetchURL(sourceDirectory, true);
0470     fetchURL(destinationDirectory, false);
0471 
0472     Q_EMIT kompareInfo(&m_info);
0473 
0474     compareAndUpdateAll();
0475 }
0476 
0477 void KomparePart::compare3Files(const QUrl& /*originalFile*/, const QUrl& /*changedFile1*/, const QUrl& /*changedFile2*/)
0478 {
0479     // FIXME: actually implement this some day :)
0480     updateActions();
0481     updateCaption();
0482     updateStatus();
0483 }
0484 
0485 void KomparePart::openFileAndDiff(const QUrl& file, const QUrl& diffFile)
0486 {
0487     m_info.source = file;
0488     m_info.destination = diffFile;
0489 
0490     fetchURL(file, true);
0491     fetchURL(diffFile, false);
0492     m_info.mode = KompareDiff2::BlendingFile;
0493 
0494     Q_EMIT kompareInfo(&m_info);
0495 
0496     compareAndUpdateAll();
0497 }
0498 
0499 void KomparePart::openDirAndDiff(const QUrl& dir,  const QUrl& diffFile)
0500 {
0501     m_info.source = dir;
0502     m_info.destination = diffFile;
0503 
0504     fetchURL(dir, true);
0505     fetchURL(diffFile, false);
0506     m_info.mode = KompareDiff2::BlendingDir;
0507 
0508     Q_EMIT kompareInfo(&m_info);
0509 
0510     if (!m_info.localSource.isEmpty() && !m_info.localDestination.isEmpty())
0511     {
0512         m_modelList->openDirAndDiff();
0513         //Must this be in here? couldn't we use compareAndUpdateAll as well?
0514         updateActions();
0515         updateCaption();
0516         updateStatus();
0517     }
0518 }
0519 
0520 bool KomparePart::openFile()
0521 {
0522     // This is called from openURL
0523     // This is a little inefficient but i will do it anyway
0524     openDiff(url());
0525     return true;
0526 }
0527 
0528 bool KomparePart::saveAll()
0529 {
0530     bool result = m_modelList->saveAll();
0531     updateActions();
0532     updateCaption();
0533     updateStatus();
0534     return result;
0535 }
0536 
0537 void KomparePart::saveDiff()
0538 {
0539     QDialog dlg(widget());
0540     dlg.setObjectName(QStringLiteral("save_options"));
0541     dlg.setModal(true);
0542     dlg.setWindowTitle(i18nc("@title:window", "Diff Options"));
0543     QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Save | QDialogButtonBox::Cancel, &dlg);
0544     connect(buttons, &QDialogButtonBox::accepted, &dlg, &QDialog::accept);
0545     connect(buttons, &QDialogButtonBox::rejected, &dlg, &QDialog::reject);
0546     KompareSaveOptionsWidget* w = new KompareSaveOptionsWidget(
0547         m_info.localSource,
0548         m_info.localDestination,
0549         m_diffSettings, &dlg);
0550     QVBoxLayout* layout = new QVBoxLayout(&dlg);
0551     layout->addWidget(w);
0552     layout->addWidget(buttons);
0553     dlg.setLayout(layout);
0554 
0555     if (dlg.exec()) {
0556         w->saveOptions();
0557         KSharedConfig::Ptr config = KSharedConfig::openConfig();
0558         saveProperties(config.data());
0559         config->sync();
0560 
0561         QUrl url = QFileDialog::getSaveFileUrl(widget(), i18nc("@title:window", "Save .diff"),
0562                                                m_info.destination,
0563                                                i18n("Patch Files (*.diff *.dif *.patch)"));
0564         qCDebug(KOMPAREPART) << "URL = " << url.toDisplayString();
0565         qCDebug(KOMPAREPART) << "Directory = " << w->directory();
0566         qCDebug(KOMPAREPART) << "DiffSettings = " << m_diffSettings;
0567 
0568         m_modelList->saveDiff(url.url(), w->directory(), m_diffSettings);
0569     }
0570 }
0571 
0572 void KomparePart::slotFilePrint()
0573 {
0574     QPrinter printer;
0575     printer.setPageOrientation(QPageLayout::Landscape);
0576     QPrintDialog* dlg = new QPrintDialog(&printer, nullptr);
0577 
0578     if (dlg->exec() == QDialog::Accepted)
0579     {
0580         // do some printing in qprinter
0581         slotPaintRequested(&printer);
0582     }
0583 
0584     delete dlg;
0585 }
0586 
0587 void KomparePart::slotFilePrintPreview()
0588 {
0589     QPrinter printer;
0590     printer.setPageOrientation(QPageLayout::Landscape);
0591     QPrintPreviewDialog dlg(&printer);
0592 
0593     connect(&dlg, &QPrintPreviewDialog::paintRequested, this, &KomparePart::slotPaintRequested);
0594 
0595     dlg.exec();
0596 }
0597 
0598 void KomparePart::slotPaintRequested(QPrinter* printer)
0599 {
0600     qCDebug(KOMPAREPART) << "Now paint something...";
0601     QPainter p;
0602     p.begin(printer);
0603 
0604     QSize widgetWidth = m_view->size();
0605     qCDebug(KOMPAREPART) << "printer.width()     = " << printer->width();
0606     qCDebug(KOMPAREPART) << "widgetWidth.width() = " << widgetWidth.width();
0607     qreal factor = ((qreal)printer->width()) / ((qreal)widgetWidth.width());
0608 
0609     qCDebug(KOMPAREPART) << "factor              = " << factor;
0610 
0611     p.scale(factor, factor);
0612     m_view->render(&p);
0613 
0614     p.end();
0615     qCDebug(KOMPAREPART) << "Done painting something...";
0616 }
0617 
0618 void KomparePart::slotSetStatus(enum KompareDiff2::Status status)
0619 {
0620     updateActions();
0621 
0622     switch (status) {
0623     case KompareDiff2::RunningDiff:
0624         Q_EMIT setStatusBarText(i18nc("@info:status", "Running diff..."));
0625         break;
0626     case KompareDiff2::Parsing:
0627         Q_EMIT setStatusBarText(i18nc("@info:status", "Parsing diff output..."));
0628         break;
0629     case KompareDiff2::FinishedParsing:
0630         updateStatus();
0631         break;
0632     case KompareDiff2::FinishedWritingDiff:
0633         updateStatus();
0634         Q_EMIT diffURLChanged();
0635         break;
0636     default:
0637         break;
0638     }
0639 }
0640 
0641 void KomparePart::onContextMenuRequested(const QPoint& pos)
0642 {
0643     QMenu* popup = static_cast<QMenu*>(factory()->container(QStringLiteral("mainPopUp"), this));
0644     if (popup)
0645         popup->exec(m_view->mapToGlobal(pos));
0646 }
0647 
0648 void KomparePart::updateCaption()
0649 {
0650     QString source = m_info.source.toDisplayString(QUrl::PreferLocalFile);
0651     QString destination = m_info.destination.toDisplayString(QUrl::PreferLocalFile);
0652 
0653     QString text;
0654 
0655     switch (m_info.mode)
0656     {
0657     case KompareDiff2::ComparingFiles :
0658     case KompareDiff2::ComparingDirs :
0659     case KompareDiff2::BlendingFile :
0660     case KompareDiff2::BlendingDir :
0661         text = source + QLatin1String(" -- ") + destination; // no need to translate this " -- "
0662         break;
0663     case KompareDiff2::ShowingDiff :
0664         text = source;
0665         break;
0666     default:
0667         break;
0668     }
0669 
0670     Q_EMIT setWindowCaption(text);
0671 }
0672 
0673 void KomparePart::updateStatus()
0674 {
0675     QString source = m_info.source.toDisplayString(QUrl::PreferLocalFile);
0676     QString destination = m_info.destination.toDisplayString(QUrl::PreferLocalFile);
0677 
0678     QString text;
0679 
0680     switch (m_info.mode)
0681     {
0682     case KompareDiff2::ComparingFiles :
0683         text = i18nc("@info:status", "Comparing file %1 with file %2" ,
0684                     source,
0685                     destination);
0686         break;
0687     case KompareDiff2::ComparingDirs :
0688         text = i18nc("@info:status", "Comparing files in %1 with files in %2" ,
0689                     source,
0690                     destination);
0691         break;
0692     case KompareDiff2::ShowingDiff :
0693         text = i18nc("@info:status", "Viewing diff output from %1", source);
0694         break;
0695     case KompareDiff2::BlendingFile :
0696         text = i18nc("@info:status", "Blending diff output from %1 into file %2" ,
0697                     source,
0698                     destination);
0699         break;
0700     case KompareDiff2::BlendingDir :
0701         text = i18nc("@info:status", "Blending diff output from %1 into folder %2" ,
0702                     m_info.source.toDisplayString(),
0703                     m_info.destination.toDisplayString());
0704         break;
0705     default:
0706         break;
0707     }
0708 
0709     Q_EMIT setStatusBarText(text);
0710 }
0711 
0712 void KomparePart::compareAndUpdateAll()
0713 {
0714     if (!m_info.localSource.isEmpty() && !m_info.localDestination.isEmpty())
0715     {
0716         switch (m_info.mode)
0717         {
0718         default:
0719         case KompareDiff2::UnknownMode:
0720             m_modelList->compare();
0721             break;
0722 
0723         case KompareDiff2::ComparingStringFile:
0724         case KompareDiff2::ComparingFileString:
0725         case KompareDiff2::ComparingFiles:
0726         case KompareDiff2::ComparingDirs:
0727             m_modelList->compare(m_info.mode);
0728             break;
0729 
0730         case KompareDiff2::BlendingFile:
0731             m_modelList->openFileAndDiff();
0732             break;
0733         }
0734         updateCaption();
0735         updateStatus();
0736     }
0737     updateActions();
0738 }
0739 
0740 void KomparePart::slotShowError(const QString& error)
0741 {
0742     KMessageBox::error(widget(), error);
0743 }
0744 
0745 void KomparePart::slotSwap()
0746 {
0747     if (m_modelList->hasUnsavedChanges())
0748     {
0749         int query = KMessageBox::warningTwoActionsCancel
0750                     (
0751                         widget(),
0752                         i18n("You have made changes to the destination file(s).\n"
0753                              "Would you like to save them?"),
0754                         i18nc("@title:window", "Save Changes?"),
0755                         KStandardGuiItem::save(),
0756                         KStandardGuiItem::discard()
0757                     );
0758 
0759         if (query == KMessageBox::PrimaryAction)
0760             m_modelList->saveAll();
0761 
0762         if (query == KMessageBox::Cancel)
0763             return; // Abort prematurely so no swapping
0764     }
0765 
0766     // Swap the info in the KompareDiff2::Info struct
0767     m_info.swapSourceWithDestination();
0768 
0769     // Update window caption and statusbar text
0770     updateCaption();
0771     updateStatus();
0772 
0773     m_modelList->swap();
0774 }
0775 
0776 void KomparePart::slotRefreshDiff()
0777 {
0778     if (m_modelList->hasUnsavedChanges())
0779     {
0780         int query = KMessageBox::warningTwoActionsCancel
0781                     (
0782                         widget(),
0783                         i18n("You have made changes to the destination file(s).\n"
0784                              "Would you like to save them?"),
0785                         i18nc("@title:window", "Save Changes?"),
0786                         KStandardGuiItem::save(),
0787                         KStandardGuiItem::discard(),
0788                         KStandardGuiItem::cancel()
0789                     );
0790 
0791         if (query == KMessageBox::Cancel)
0792             return; // Abort prematurely so no refreshing
0793 
0794         if (query == KMessageBox::PrimaryAction)
0795             m_modelList->saveAll();
0796     }
0797 
0798     // For this to work properly you have to refetch the files from their (remote) locations
0799     cleanUpTemporaryFiles();
0800     fetchURL(m_info.source, true);
0801     fetchURL(m_info.destination, false);
0802     m_modelList->refresh();
0803 }
0804 
0805 void KomparePart::slotShowDiffstats()
0806 {
0807     // Fetch all the args needed for komparestatsmessagebox
0808     // oldfile, newfile, diffformat, noofhunks, noofdiffs
0809 
0810     QString oldFile;
0811     QString newFile;
0812     QString diffFormat;
0813     int filesInDiff;
0814     int noOfHunks;
0815     int noOfDiffs;
0816 
0817     oldFile = m_modelList->selectedModel() ? m_modelList->selectedModel()->sourceFile()  : QString();
0818     newFile = m_modelList->selectedModel() ? m_modelList->selectedModel()->destinationFile() : QString();
0819 
0820     if (m_modelList->selectedModel())
0821     {
0822         switch (m_info.format) {
0823         case KompareDiff2::Unified :
0824             diffFormat = i18nc("@item diff format", "Unified");
0825             break;
0826         case KompareDiff2::Context :
0827             diffFormat = i18nc("@item diff format", "Context");
0828             break;
0829         case KompareDiff2::RCS :
0830             diffFormat = i18nc("@item diff format", "RCS");
0831             break;
0832         case KompareDiff2::Ed :
0833             diffFormat = i18nc("@item diff format", "Ed");
0834             break;
0835         case KompareDiff2::Normal :
0836             diffFormat = i18nc("@item diff format", "Normal");
0837             break;
0838         case KompareDiff2::UnknownFormat :
0839         default:
0840             diffFormat = i18nc("@item diff format", "Unknown");
0841             break;
0842         }
0843     }
0844     else
0845     {
0846         diffFormat.clear();
0847     }
0848 
0849     filesInDiff = m_modelList->modelCount();
0850 
0851     noOfHunks = m_modelList->selectedModel() ? m_modelList->selectedModel()->hunkCount() : 0;
0852     noOfDiffs = m_modelList->selectedModel() ? m_modelList->selectedModel()->differenceCount() : 0;
0853 
0854     if (m_modelList->modelCount() == 0) {   // no diff loaded yet
0855         KMessageBox::information(nullptr, i18n(
0856             "No diff file, or no 2 files have been diffed. "
0857             "Therefore no stats are available."),
0858             i18nc("@title:window", "Diff Statistics"), QString(), KMessageBox::Options());
0859     }
0860     else if (m_modelList->modelCount() == 1) {   // 1 file in diff, or 2 files compared
0861         KMessageBox::information(nullptr, i18n(
0862             "Statistics:\n"
0863             "\n"
0864             "Old file: %1\n"
0865             "New file: %2\n"
0866             "\n"
0867             "Format: %3\n"
0868             "Number of hunks: %4\n"
0869             "Number of differences: %5",
0870             oldFile, newFile, diffFormat,
0871             noOfHunks, noOfDiffs),
0872             i18nc("@title:window", "Diff Statistics"), QString(), KMessageBox::Options());
0873     } else { // more than 1 file in diff, or 2 directories compared
0874         KMessageBox::information(nullptr, ki18n(
0875             "Statistics:\n"
0876             "\n"
0877             "Number of files in diff file: %1\n"
0878             "Format: %2\n"
0879             "\n"
0880             "Current old file: %3\n"
0881             "Current new file: %4\n"
0882             "\n"
0883             "Number of hunks: %5\n"
0884             "Number of differences: %6")
0885             .subs(filesInDiff).subs(diffFormat).subs(oldFile)
0886             .subs(newFile).subs(noOfHunks).subs(noOfDiffs)
0887             .toString(),
0888             i18nc("@title:window", "Diff Statistics"), QString(), KMessageBox::Options());
0889     }
0890 }
0891 
0892 bool KomparePart::queryClose()
0893 {
0894     if (!m_modelList->hasUnsavedChanges()) return true;
0895 
0896     int query = KMessageBox::warningTwoActionsCancel
0897                 (
0898                     widget(),
0899                     i18n("You have made changes to the destination file(s).\n"
0900                          "Would you like to save them?"),
0901                     i18nc("@title:window", "Save Changes?"),
0902                     KStandardGuiItem::save(),
0903                     KStandardGuiItem::discard(),
0904                     KStandardGuiItem::cancel()
0905                 );
0906 
0907     if (query == KMessageBox::Cancel)
0908         return false;
0909 
0910     if (query == KMessageBox::PrimaryAction)
0911         return m_modelList->saveAll();
0912 
0913     return true;
0914 }
0915 
0916 void KomparePart::setReadWrite(bool readWrite)
0917 {
0918     m_modelList->setReadWrite(readWrite);
0919     KParts::ReadWritePart::setReadWrite(readWrite);
0920 }
0921 
0922 int KomparePart::readProperties(KConfig* config)
0923 {
0924     m_viewSettings->loadSettings(config);
0925     m_diffSettings->loadSettings(config);
0926     Q_EMIT configChanged();
0927     return 0;
0928 }
0929 
0930 int KomparePart::saveProperties(KConfig* config)
0931 {
0932     m_viewSettings->saveSettings(config);
0933     m_diffSettings->saveSettings(config);
0934     return 0;
0935 }
0936 
0937 void KomparePart::optionsPreferences()
0938 {
0939     // show preferences
0940     KomparePrefDlg pref(m_viewSettings, m_diffSettings);
0941 
0942     connect(&pref, &KomparePrefDlg::configChanged, this, &KomparePart::configChanged);
0943 
0944     if (pref.exec())
0945         Q_EMIT configChanged();
0946 }
0947 
0948 #include "moc_kompare_part.cpp"