File indexing completed on 2024-11-17 05:10:23

0001 /*
0002     SPDX-FileCopyrightText: 2001-2005,2009 Otto Bruggeman <otto.bruggeman@home.nl>
0003     SPDX-FileCopyrightText: 2001-2003 John Firebaugh <jfirebaugh@kde.org>
0004     SPDX-FileCopyrightText: 2007-2010 Kevin Kofler <kevin.kofler@chello.at>
0005     SPDX-FileCopyrightText: 2012 Jean -Nicolas Artaud <jeannicolasartaud@gmail.com>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 
0010 #include "modellist.h"
0011 #include "modellist_p.h"
0012 
0013 // lib
0014 #include "diffhunk.h"
0015 #include "parser.h"
0016 #include <komparediff2_logging.h>
0017 // KF
0018 #include <KActionCollection>
0019 #include <KDirWatch>
0020 #include <KIO/FileCopyJob>
0021 #include <KIO/MkdirJob>
0022 #include <KIO/StatJob>
0023 #include <KIO/UDSEntry>
0024 #include <KLocalizedString>
0025 #include <KStandardAction>
0026 // Qt
0027 #include <QDir>
0028 #include <QFile>
0029 #include <QList>
0030 #include <QMimeDatabase>
0031 #include <QMimeType>
0032 #include <QTextCodec>
0033 #include <QTextStream>
0034 // Std
0035 #include <algorithm>
0036 
0037 using namespace KompareDiff2;
0038 
0039 ModelList::ModelList(DiffSettings *diffSettings, QObject *parent, bool supportReadWrite)
0040     : QObject(parent)
0041     , d_ptr(new ModelListPrivate(diffSettings, supportReadWrite))
0042 {
0043     Q_D(ModelList);
0044 
0045     qCDebug(KOMPAREDIFF2_LOG) << "Show me the arguments: " << diffSettings << ", " << parent;
0046     d->actionCollection = new KActionCollection(this);
0047     if (supportReadWrite) {
0048         d->applyDifference = d->actionCollection->addAction(QStringLiteral("difference_apply"), this, &ModelList::slotActionApplyDifference);
0049         d->applyDifference->setIcon(QIcon::fromTheme(QStringLiteral("arrow-right")));
0050         d->applyDifference->setText(i18nc("@action", "&Apply Difference"));
0051         d->actionCollection->setDefaultShortcut(d->applyDifference, QKeySequence(Qt::Key_Space));
0052         d->unApplyDifference = d->actionCollection->addAction(QStringLiteral("difference_unapply"), this, &ModelList::slotActionUnApplyDifference);
0053         d->unApplyDifference->setIcon(QIcon::fromTheme(QStringLiteral("arrow-left")));
0054         d->unApplyDifference->setText(i18nc("@action", "Un&apply Difference"));
0055         d->actionCollection->setDefaultShortcut(d->unApplyDifference, QKeySequence(Qt::Key_Backspace));
0056         d->applyAll = d->actionCollection->addAction(QStringLiteral("difference_applyall"), this, &ModelList::slotActionApplyAllDifferences);
0057         d->applyAll->setIcon(QIcon::fromTheme(QStringLiteral("arrow-right-double")));
0058         d->applyAll->setText(i18nc("@action", "App&ly All"));
0059         d->actionCollection->setDefaultShortcut(d->applyAll, QKeySequence(Qt::CTRL | Qt::Key_A));
0060         d->unapplyAll = d->actionCollection->addAction(QStringLiteral("difference_unapplyall"), this, &ModelList::slotActionUnapplyAllDifferences);
0061         d->unapplyAll->setIcon(QIcon::fromTheme(QStringLiteral("arrow-left-double")));
0062         d->unapplyAll->setText(i18nc("@action", "&Unapply All"));
0063         d->actionCollection->setDefaultShortcut(d->unapplyAll, QKeySequence(Qt::CTRL | Qt::Key_U));
0064     } else {
0065         d->applyDifference = nullptr;
0066         d->unApplyDifference = nullptr;
0067         d->applyAll = nullptr;
0068         d->unapplyAll = nullptr;
0069     }
0070     d->previousFile = d->actionCollection->addAction(QStringLiteral("difference_previousfile"), this, &ModelList::slotPreviousModel);
0071     d->previousFile->setIcon(QIcon::fromTheme(QStringLiteral("arrow-up-double")));
0072     d->previousFile->setText(i18nc("@action", "P&revious File"));
0073     d->actionCollection->setDefaultShortcut(d->previousFile, QKeySequence(Qt::CTRL | Qt::Key_PageUp));
0074     d->nextFile = d->actionCollection->addAction(QStringLiteral("difference_nextfile"), this, &ModelList::slotNextModel);
0075     d->nextFile->setIcon(QIcon::fromTheme(QStringLiteral("arrow-down-double")));
0076     d->nextFile->setText(i18nc("@action", "N&ext File"));
0077     d->actionCollection->setDefaultShortcut(d->nextFile, QKeySequence(Qt::CTRL | Qt::Key_PageDown));
0078     d->previousDifference = d->actionCollection->addAction(QStringLiteral("difference_previous"), this, &ModelList::slotPreviousDifference);
0079     d->previousDifference->setIcon(QIcon::fromTheme(QStringLiteral("arrow-up")));
0080     d->previousDifference->setText(i18nc("@action", "&Previous Difference"));
0081     d->actionCollection->setDefaultShortcut(d->previousDifference, QKeySequence(Qt::CTRL | Qt::Key_Up));
0082     d->nextDifference = d->actionCollection->addAction(QStringLiteral("difference_next"), this, &ModelList::slotNextDifference);
0083     d->nextDifference->setIcon(QIcon::fromTheme(QStringLiteral("arrow-down")));
0084     d->nextDifference->setText(i18nc("@action", "&Next Difference"));
0085     d->actionCollection->setDefaultShortcut(d->nextDifference, QKeySequence(Qt::CTRL | Qt::Key_Down));
0086     d->previousDifference->setEnabled(false);
0087     d->nextDifference->setEnabled(false);
0088 
0089     if (supportReadWrite) {
0090         d->save = KStandardAction::save(this, &ModelList::slotSaveDestination, d->actionCollection);
0091         d->save->setEnabled(false);
0092     } else {
0093         d->save = nullptr;
0094     }
0095 
0096     d->updateModelListActions();
0097 }
0098 
0099 ModelList::~ModelList() = default;
0100 
0101 bool ModelList::compare()
0102 {
0103     Q_D(ModelList);
0104 
0105     bool result = false;
0106 
0107     bool sourceIsDirectory = ModelListPrivate::isDirectory(d->info->localSource);
0108     bool destinationIsDirectory = ModelListPrivate::isDirectory(d->info->localDestination);
0109 
0110     if (sourceIsDirectory && destinationIsDirectory) {
0111         d->info->mode = ComparingDirs;
0112         result = compare(d->info->mode);
0113     } else if (!sourceIsDirectory && !destinationIsDirectory) {
0114         QFile sourceFile(d->info->localSource);
0115         sourceFile.open(QIODevice::ReadOnly);
0116         QMimeDatabase db;
0117         QString sourceMimeType = (db.mimeTypeForData(sourceFile.readAll())).name();
0118         sourceFile.close();
0119         qCDebug(KOMPAREDIFF2_LOG) << "Mimetype source     : " << sourceMimeType;
0120 
0121         QFile destinationFile(d->info->localDestination);
0122         destinationFile.open(QIODevice::ReadOnly);
0123         QString destinationMimeType = (db.mimeTypeForData(destinationFile.readAll())).name();
0124         destinationFile.close();
0125         qCDebug(KOMPAREDIFF2_LOG) << "Mimetype destination: " << destinationMimeType;
0126 
0127         // Not checking if it is a text file/something diff can even compare, we'll let diff handle that
0128         if (!ModelListPrivate::isDiff(sourceMimeType) && ModelListPrivate::isDiff(destinationMimeType)) {
0129             qCDebug(KOMPAREDIFF2_LOG) << "Blending destination into source...";
0130             d->info->mode = BlendingFile;
0131             result = openFileAndDiff();
0132         } else if (ModelListPrivate::isDiff(sourceMimeType) && !ModelListPrivate::isDiff(destinationMimeType)) {
0133             qCDebug(KOMPAREDIFF2_LOG) << "Blending source into destination...";
0134             d->info->mode = BlendingFile;
0135             // Swap source and destination before calling this
0136             d->info->swapSourceWithDestination();
0137             // Do we need to notify anyone we swapped source and destination?
0138             // No we do not need to notify anyone about swapping source with destination
0139             result = openFileAndDiff();
0140         } else {
0141             qCDebug(KOMPAREDIFF2_LOG) << "Comparing source with destination";
0142             d->info->mode = ComparingFiles;
0143             result = compare(d->info->mode);
0144         }
0145     } else if (sourceIsDirectory && !destinationIsDirectory) {
0146         d->info->mode = BlendingDir;
0147         result = openDirAndDiff();
0148     } else {
0149         d->info->mode = BlendingDir;
0150         // Swap source and destination first in d->info
0151         d->info->swapSourceWithDestination();
0152         // Do we need to notify anyone we swapped source and destination?
0153         // No we do not need to notify anyone about swapping source with destination
0154         result = openDirAndDiff();
0155     }
0156 
0157     return result;
0158 }
0159 
0160 bool ModelList::compare(Mode mode)
0161 {
0162     Q_D(ModelList);
0163 
0164     clear(); // Destroy the old models...
0165 
0166     d->diffProcess = std::make_unique<KompareProcess>(d->diffSettings, Custom, d->info->localSource, d->info->localDestination, QString(), mode);
0167     d->diffProcess->setEncoding(d->encoding);
0168 
0169     connect(d->diffProcess.get(), &KompareProcess::diffHasFinished, this, &ModelList::slotDiffProcessFinished);
0170 
0171     Q_EMIT status(RunningDiff);
0172     d->diffProcess->start();
0173 
0174     return true;
0175 }
0176 
0177 bool ModelList::openFileAndDiff()
0178 {
0179     Q_D(ModelList);
0180 
0181     clear();
0182 
0183     if (parseDiffOutput(d->readFile(d->info->localDestination)) != 0) {
0184         Q_EMIT error(i18n("<qt>No models or no differences, this file: <b>%1</b>, is not a valid diff file.</qt>", d->info->destination.url()));
0185         return false;
0186     }
0187 
0188     d->setDepthAndApplied();
0189 
0190     if (!blendOriginalIntoModelList(d->info->localSource)) {
0191         qCDebug(KOMPAREDIFF2_LOG) << "Oops cant blend original file into modellist : " << d->info->localSource;
0192         Q_EMIT error(
0193             i18n("<qt>There were problems applying the diff <b>%1</b> to the file <b>%2</b>.</qt>", d->info->destination.url(), d->info->source.url()));
0194         return false;
0195     }
0196 
0197     d->updateModelListActions();
0198     show();
0199 
0200     return true;
0201 }
0202 
0203 bool ModelList::openDirAndDiff()
0204 {
0205     Q_D(ModelList);
0206 
0207     clear();
0208 
0209     if (parseDiffOutput(d->readFile(d->info->localDestination)) != 0) {
0210         Q_EMIT error(i18n("<qt>No models or no differences, this file: <b>%1</b>, is not a valid diff file.</qt>", d->info->destination.url()));
0211         return false;
0212     }
0213 
0214     d->setDepthAndApplied();
0215 
0216     // Do our thing :)
0217     if (!blendOriginalIntoModelList(d->info->localSource)) {
0218         // Trouble blending the original into the model
0219         qCDebug(KOMPAREDIFF2_LOG) << "Oops cant blend original dir into modellist : " << d->info->localSource;
0220         Q_EMIT error(
0221             i18n("<qt>There were problems applying the diff <b>%1</b> to the folder <b>%2</b>.</qt>", d->info->destination.url(), d->info->source.url()));
0222         return false;
0223     }
0224 
0225     d->updateModelListActions();
0226     show();
0227 
0228     return true;
0229 }
0230 
0231 void ModelList::slotSaveDestination()
0232 {
0233     Q_D(ModelList);
0234 
0235     // Unnecessary safety check! We can now guarantee that saving is only possible when there is a model and there are unsaved changes
0236     if (d->selectedModel) {
0237         saveDestination(d->selectedModel);
0238         if (d->save)
0239             d->save->setEnabled(false);
0240         Q_EMIT updateActions();
0241     }
0242 }
0243 
0244 bool ModelList::saveDestination(DiffModel *model)
0245 {
0246     Q_D(ModelList);
0247 
0248     qCDebug(KOMPAREDIFF2_LOG) << "ModelList::saveDestination: ";
0249 
0250     // Unnecessary safety check, we can guarantee there are unsaved changes!!!
0251     if (!model->hasUnsavedChanges())
0252         return true;
0253 
0254     QTemporaryFile temp;
0255 
0256     if (!temp.open()) {
0257         Q_EMIT error(i18n("Could not open a temporary file."));
0258         temp.remove();
0259         return false;
0260     }
0261 
0262     QTextStream stream(&temp);
0263     QStringList list;
0264 
0265     const DiffHunkList *hunks = model->hunks();
0266 
0267     for (const DiffHunk *hunk : *hunks) {
0268         const DifferenceList differences = hunk->differences();
0269 
0270         for (const Difference *diff : differences) {
0271             if (!diff->applied()) {
0272                 const DifferenceStringList destinationLines = diff->destinationLines();
0273                 for (const DifferenceString *diffString : destinationLines) {
0274                     list.append(diffString->string());
0275                 }
0276             } else {
0277                 const DifferenceStringList sourceLines = diff->sourceLines();
0278                 for (const DifferenceString *diffString : sourceLines) {
0279                     list.append(diffString->string());
0280                 }
0281             }
0282         }
0283     }
0284 
0285     // qCDebug(KOMPAREDIFF2_LOG) << "Everything: " << endl << list.join( "\n" );
0286 
0287     if (list.count() > 0)
0288         stream << list.join(QString());
0289     if (temp.error() != QFile::NoError) {
0290         Q_EMIT error(i18n("<qt>Could not write to the temporary file <b>%1</b>, deleting it.</qt>", temp.fileName()));
0291         temp.remove();
0292         return false;
0293     }
0294 
0295     temp.close();
0296     if (temp.error() != QFile::NoError) {
0297         Q_EMIT error(i18n("<qt>Could not write to the temporary file <b>%1</b>, deleting it.</qt>", temp.fileName()));
0298         temp.remove();
0299         return false;
0300     }
0301 
0302     bool result = false;
0303 
0304     // Make sure the destination directory exists, it is possible when using -N to not have the destination dir/file available
0305     if (d->info->mode == ComparingDirs) {
0306         // Don't use destination which was used for creating the diff directly, use the original URL!!!
0307         // FIXME!!! Wrong destination this way! Need to add the sub directory to the url!!!!
0308         qCDebug(KOMPAREDIFF2_LOG) << "Tempfilename (save) : " << temp.fileName();
0309         qCDebug(KOMPAREDIFF2_LOG) << "Model->path+file    : " << model->destinationPath() << model->destinationFile();
0310         qCDebug(KOMPAREDIFF2_LOG) << "info->localdest     : " << d->info->localDestination;
0311         QString tmp = model->destinationPath();
0312         if (tmp.startsWith(d->info->localDestination)) // It should, if not serious trouble...
0313             tmp.remove(0, d->info->localDestination.size());
0314         qCDebug(KOMPAREDIFF2_LOG) << "DestinationURL      : " << d->info->destination;
0315         qCDebug(KOMPAREDIFF2_LOG) << "tmp                 : " << tmp;
0316         KIO::UDSEntry entry;
0317         QUrl fullDestinationPath = d->info->destination;
0318         fullDestinationPath.setPath(fullDestinationPath.path() + tmp);
0319         qCDebug(KOMPAREDIFF2_LOG) << "fullDestinationPath : " << fullDestinationPath;
0320         KIO::StatJob *statJob = KIO::stat(fullDestinationPath);
0321         if (!statJob->exec()) {
0322             entry = statJob->statResult();
0323             KIO::MkdirJob *mkdirJob = KIO::mkdir(fullDestinationPath);
0324             if (!mkdirJob->exec()) {
0325                 Q_EMIT error(i18n("<qt>Could not create destination directory <b>%1</b>.\nThe file has not been saved.</qt>", fullDestinationPath.path()));
0326                 return false;
0327             }
0328         }
0329         fullDestinationPath.setPath(fullDestinationPath.path() + model->destinationFile());
0330         KIO::FileCopyJob *copyJob = KIO::file_copy(QUrl::fromLocalFile(temp.fileName()), fullDestinationPath, -1, KIO::Overwrite);
0331         result = copyJob->exec();
0332     } else {
0333         qCDebug(KOMPAREDIFF2_LOG) << "Tempfilename   : " << temp.fileName();
0334         qCDebug(KOMPAREDIFF2_LOG) << "DestinationURL : " << d->info->destination;
0335 
0336         // Get permissions of existing file and copy temporary file with the same permissions
0337         int permissions = -1;
0338         KIO::StatJob *statJob = KIO::stat(d->info->destination);
0339         result = statJob->exec();
0340         if (result)
0341             permissions = statJob->statResult().numberValue(KIO::UDSEntry::UDS_ACCESS);
0342 
0343         KIO::FileCopyJob *copyJob = KIO::file_copy(QUrl::fromLocalFile(temp.fileName()), d->info->destination, permissions, KIO::Overwrite);
0344         result = copyJob->exec();
0345         qCDebug(KOMPAREDIFF2_LOG) << "true or false?" << result;
0346     }
0347 
0348     if (!result) {
0349         // FIXME: Wrong first argument given in case of comparing directories!
0350         Q_EMIT error(
0351             i18n("<qt>Could not upload the temporary file to the destination location <b>%1</b>. The temporary file is still available under: <b>%2</b>. You "
0352                  "can manually copy it to the right place.</qt>",
0353                  d->info->destination.url(),
0354                  temp.fileName()));
0355         // Don't remove file when we delete temp and don't leak it.
0356         temp.setAutoRemove(false);
0357     } else {
0358         temp.remove();
0359     }
0360 
0361     // If saving was fine set all differences to saved so we can start again with a clean slate
0362     if (result) {
0363         const DifferenceList *differences = model->differences();
0364 
0365         for (Difference *diff : *differences) {
0366             diff->setUnsaved(false);
0367         }
0368     }
0369 
0370     return true;
0371 }
0372 
0373 bool ModelList::saveAll()
0374 {
0375     Q_D(ModelList);
0376 
0377     if (modelCount() == 0)
0378         return false;
0379 
0380     for (DiffModel *model : std::as_const(*d->models)) {
0381         if (!saveDestination(model))
0382             return false;
0383     }
0384 
0385     return true;
0386 }
0387 
0388 void ModelList::setEncoding(const QString &encoding)
0389 {
0390     Q_D(ModelList);
0391 
0392     d->encoding = encoding;
0393     if (!encoding.compare(QLatin1String("default"), Qt::CaseInsensitive)) {
0394         d->textCodec = QTextCodec::codecForLocale();
0395     } else {
0396         qCDebug(KOMPAREDIFF2_LOG) << "Encoding : " << encoding;
0397         d->textCodec = QTextCodec::codecForName(encoding.toUtf8());
0398         qCDebug(KOMPAREDIFF2_LOG) << "TextCodec: " << d->textCodec;
0399         if (!d->textCodec)
0400             d->textCodec = QTextCodec::codecForLocale();
0401     }
0402     qCDebug(KOMPAREDIFF2_LOG) << "TextCodec: " << d->textCodec;
0403 }
0404 
0405 void ModelList::setReadWrite(bool isReadWrite)
0406 {
0407     Q_D(ModelList);
0408 
0409     if (d->isReadWrite == isReadWrite)
0410         return;
0411 
0412     d->isReadWrite = isReadWrite;
0413     d->updateModelListActions();
0414 }
0415 
0416 bool ModelList::isReadWrite() const
0417 {
0418     Q_D(const ModelList);
0419 
0420     return d->isReadWrite;
0421 }
0422 
0423 void ModelList::slotDiffProcessFinished(bool success)
0424 {
0425     Q_D(ModelList);
0426 
0427     if (success) {
0428         Q_EMIT status(Parsing);
0429         if (parseDiffOutput(d->diffProcess->diffOutput()) != 0) {
0430             Q_EMIT error(i18n("Could not parse diff output."));
0431         } else {
0432             if (d->info->mode != ShowingDiff) {
0433                 qCDebug(KOMPAREDIFF2_LOG) << "Blend this crap please and do not give me any conflicts...";
0434                 blendOriginalIntoModelList(d->info->localSource);
0435             }
0436             d->updateModelListActions();
0437             show();
0438         }
0439         Q_EMIT status(FinishedParsing);
0440     } else if (d->diffProcess->exitStatus() == 0) {
0441         Q_EMIT error(i18n("The files are identical."));
0442     } else {
0443         Q_EMIT error(d->diffProcess->stdErr());
0444     }
0445 
0446     // delay deletion, see bug 182792
0447     d->diffProcess.release()->deleteLater();
0448 }
0449 
0450 bool ModelList::openDiff(const QString &diffFile)
0451 {
0452     Q_D(ModelList);
0453 
0454     qCDebug(KOMPAREDIFF2_LOG) << "Stupid :) Url = " << diffFile;
0455 
0456     if (diffFile.isEmpty())
0457         return false;
0458 
0459     QString diff = d->readFile(diffFile);
0460 
0461     clear(); // Clear the current models
0462 
0463     Q_EMIT status(Parsing);
0464 
0465     if (parseDiffOutput(diff) != 0) {
0466         Q_EMIT error(i18n("Could not parse diff output."));
0467         return false;
0468     }
0469 
0470     d->updateModelListActions();
0471     show();
0472 
0473     Q_EMIT status(FinishedParsing);
0474 
0475     return true;
0476 }
0477 
0478 bool ModelList::parseAndOpenDiff(const QString &diff)
0479 {
0480     Q_D(ModelList);
0481 
0482     clear(); // Clear the current models
0483 
0484     Q_EMIT status(Parsing);
0485 
0486     if (parseDiffOutput(diff) != 0) {
0487         Q_EMIT error(i18n("Could not parse diff output."));
0488         return false;
0489     }
0490 
0491     d->updateModelListActions();
0492     show();
0493 
0494     Q_EMIT status(FinishedParsing);
0495     return true;
0496 }
0497 
0498 QString ModelList::recreateDiff() const
0499 {
0500     Q_D(const ModelList);
0501 
0502     QString diff;
0503 
0504     for (const DiffModel *model : *d->models) {
0505         diff += model->recreateDiff();
0506     }
0507 
0508     return diff;
0509 }
0510 
0511 bool ModelList::saveDiff(const QString &url, const QString &directory, DiffSettings *diffSettings)
0512 {
0513     Q_D(ModelList);
0514 
0515     qCDebug(KOMPAREDIFF2_LOG) << "ModelList::saveDiff: ";
0516 
0517     d->diffTemp = std::make_unique<QTemporaryFile>();
0518     d->diffURL = QUrl(url); // ### TODO the "url" argument should be a QUrl
0519 
0520     if (!d->diffTemp->open()) {
0521         Q_EMIT error(i18n("Could not open a temporary file."));
0522         d->diffTemp->remove();
0523         d->diffTemp.reset();
0524         return false;
0525     }
0526 
0527     d->diffProcess = std::make_unique<KompareProcess>(diffSettings, Custom, d->info->localSource, d->info->localDestination, directory);
0528     d->diffProcess->setEncoding(d->encoding);
0529 
0530     connect(d->diffProcess.get(), &KompareProcess::diffHasFinished, this, &ModelList::slotWriteDiffOutput);
0531 
0532     Q_EMIT status(RunningDiff);
0533     d->diffProcess->start();
0534     return true;
0535 }
0536 
0537 void ModelList::slotWriteDiffOutput(bool success)
0538 {
0539     Q_D(ModelList);
0540 
0541     qCDebug(KOMPAREDIFF2_LOG) << "Success = " << success;
0542 
0543     if (success) {
0544         QTextStream stream(d->diffTemp.get());
0545 
0546         stream << d->diffProcess->diffOutput();
0547 
0548         d->diffTemp->close();
0549 
0550         if (false /*|| d->diffTemp->status() != 0 */) {
0551             Q_EMIT error(i18n("Could not write to the temporary file."));
0552         }
0553 
0554         KIO::FileCopyJob *copyJob = KIO::file_copy(QUrl::fromLocalFile(d->diffTemp->fileName()), d->diffURL);
0555         copyJob->exec();
0556 
0557         Q_EMIT status(FinishedWritingDiff);
0558     }
0559 
0560     d->diffURL = QUrl();
0561     d->diffTemp->remove();
0562     d->diffTemp.reset();
0563 
0564     d->diffProcess.reset();
0565 }
0566 
0567 void ModelList::slotSelectionChanged(const KompareDiff2::DiffModel *model, const KompareDiff2::Difference *diff)
0568 {
0569     Q_D(ModelList);
0570 
0571     // This method will signal all the other objects about a change in selection,
0572     // it will emit setSelection( const DiffModel*, const Difference* ) to all who are connected
0573     qCDebug(KOMPAREDIFF2_LOG) << "ModelList::slotSelectionChanged( " << model << ", " << diff << " )";
0574     qCDebug(KOMPAREDIFF2_LOG) << "Sender is : " << sender()->metaObject()->className();
0575 //     qCDebug(KOMPAREDIFF2_LOG) << kBacktrace();
0576 
0577     d->selectedModel = const_cast<DiffModel *>(model);
0578     d->modelIndex = d->models->indexOf(d->selectedModel);
0579     qCDebug(KOMPAREDIFF2_LOG) << "d->modelIndex = " << d->modelIndex;
0580     d->selectedDifference = const_cast<Difference *>(diff);
0581 
0582     d->selectedModel->setSelectedDifference(d->selectedDifference);
0583 
0584     // setSelected* search for the argument in the lists and return false if not found
0585     // if found they return true and set the d->selected*
0586     if (!d->setSelectedModel(d->selectedModel)) {
0587         // Backup plan
0588         d->selectedModel = d->firstModel();
0589         d->selectedDifference = d->selectedModel->firstDifference();
0590     } else if (!d->selectedModel->setSelectedDifference(d->selectedDifference)) {
0591         // Another backup plan
0592         d->selectedDifference = d->selectedModel->firstDifference();
0593     }
0594 
0595     Q_EMIT setSelection(model, diff);
0596     Q_EMIT setStatusBarModelInfo(findModel(d->selectedModel),
0597                                  d->selectedModel->findDifference(d->selectedDifference),
0598                                  modelCount(),
0599                                  differenceCount(),
0600                                  d->selectedModel->appliedCount());
0601 
0602     d->updateModelListActions();
0603 }
0604 
0605 void ModelList::slotSelectionChanged(const KompareDiff2::Difference *diff)
0606 {
0607     Q_D(ModelList);
0608 
0609     // This method will emit setSelection( const Difference* ) to whomever is listening
0610     // when for instance in kompareview the selection has changed
0611     qCDebug(KOMPAREDIFF2_LOG) << "ModelList::slotSelectionChanged( " << diff << " )";
0612     qCDebug(KOMPAREDIFF2_LOG) << "Sender is : " << sender()->metaObject()->className();
0613 
0614     d->selectedDifference = const_cast<Difference *>(diff);
0615 
0616     if (!d->selectedModel->setSelectedDifference(d->selectedDifference)) {
0617         // Backup plan
0618         d->selectedDifference = d->selectedModel->firstDifference();
0619     }
0620 
0621     Q_EMIT setSelection(diff);
0622     Q_EMIT setStatusBarModelInfo(findModel(d->selectedModel),
0623                                  d->selectedModel->findDifference(d->selectedDifference),
0624                                  modelCount(),
0625                                  differenceCount(),
0626                                  d->selectedModel->appliedCount());
0627 
0628     d->updateModelListActions();
0629 }
0630 
0631 void ModelList::slotPreviousModel()
0632 {
0633     Q_D(ModelList);
0634 
0635     if ((d->selectedModel = d->prevModel()) != nullptr) {
0636         d->selectedDifference = d->selectedModel->firstDifference();
0637     } else {
0638         d->selectedModel = d->firstModel();
0639         d->selectedDifference = d->selectedModel->firstDifference();
0640     }
0641 
0642     Q_EMIT setSelection(d->selectedModel, d->selectedDifference);
0643     Q_EMIT setStatusBarModelInfo(findModel(d->selectedModel),
0644                                  d->selectedModel->findDifference(d->selectedDifference),
0645                                  modelCount(),
0646                                  differenceCount(),
0647                                  d->selectedModel->appliedCount());
0648     d->updateModelListActions();
0649 }
0650 
0651 void ModelList::slotNextModel()
0652 {
0653     Q_D(ModelList);
0654 
0655     if ((d->selectedModel = d->nextModel()) != nullptr) {
0656         d->selectedDifference = d->selectedModel->firstDifference();
0657     } else {
0658         d->selectedModel = d->lastModel();
0659         d->selectedDifference = d->selectedModel->firstDifference();
0660     }
0661 
0662     Q_EMIT setSelection(d->selectedModel, d->selectedDifference);
0663     Q_EMIT setStatusBarModelInfo(findModel(d->selectedModel),
0664                                  d->selectedModel->findDifference(d->selectedDifference),
0665                                  modelCount(),
0666                                  differenceCount(),
0667                                  d->selectedModel->appliedCount());
0668     d->updateModelListActions();
0669 }
0670 
0671 Mode ModelList::mode() const
0672 {
0673     Q_D(const ModelList);
0674 
0675     return d->info->mode;
0676 }
0677 
0678 const DiffModelList *ModelList::models() const
0679 {
0680     Q_D(const ModelList);
0681 
0682     return d->models.get();
0683 }
0684 
0685 KActionCollection *ModelList::actionCollection() const
0686 {
0687     Q_D(const ModelList);
0688 
0689     return d->actionCollection;
0690 }
0691 
0692 void ModelList::slotPreviousDifference()
0693 {
0694     Q_D(ModelList);
0695 
0696     qCDebug(KOMPAREDIFF2_LOG) << "slotPreviousDifference called";
0697     if ((d->selectedDifference = d->selectedModel->prevDifference()) != nullptr) {
0698         Q_EMIT setSelection(d->selectedDifference);
0699         Q_EMIT setStatusBarModelInfo(findModel(d->selectedModel),
0700                                      d->selectedModel->findDifference(d->selectedDifference),
0701                                      modelCount(),
0702                                      differenceCount(),
0703                                      d->selectedModel->appliedCount());
0704         d->updateModelListActions();
0705         return;
0706     }
0707 
0708     qCDebug(KOMPAREDIFF2_LOG) << "**** no previous difference... ok lets find the previous model...";
0709 
0710     if ((d->selectedModel = d->prevModel()) != nullptr) {
0711         d->selectedDifference = d->selectedModel->lastDifference();
0712 
0713         Q_EMIT setSelection(d->selectedModel, d->selectedDifference);
0714         Q_EMIT setStatusBarModelInfo(findModel(d->selectedModel),
0715                                      d->selectedModel->findDifference(d->selectedDifference),
0716                                      modelCount(),
0717                                      differenceCount(),
0718                                      d->selectedModel->appliedCount());
0719         d->updateModelListActions();
0720         return;
0721     }
0722 
0723     qCDebug(KOMPAREDIFF2_LOG) << "**** !!! No previous model, ok backup plan activated...";
0724 
0725     // Backup plan
0726     d->selectedModel = d->firstModel();
0727     d->selectedDifference = d->selectedModel->firstDifference();
0728 
0729     Q_EMIT setSelection(d->selectedModel, d->selectedDifference);
0730     Q_EMIT setStatusBarModelInfo(findModel(d->selectedModel),
0731                                  d->selectedModel->findDifference(d->selectedDifference),
0732                                  modelCount(),
0733                                  differenceCount(),
0734                                  d->selectedModel->appliedCount());
0735     d->updateModelListActions();
0736 }
0737 
0738 void ModelList::slotNextDifference()
0739 {
0740     Q_D(ModelList);
0741 
0742     qCDebug(KOMPAREDIFF2_LOG) << "slotNextDifference called";
0743     if ((d->selectedDifference = d->selectedModel->nextDifference()) != nullptr) {
0744         Q_EMIT setSelection(d->selectedDifference);
0745         Q_EMIT setStatusBarModelInfo(findModel(d->selectedModel),
0746                                      d->selectedModel->findDifference(d->selectedDifference),
0747                                      modelCount(),
0748                                      differenceCount(),
0749                                      d->selectedModel->appliedCount());
0750         d->updateModelListActions();
0751         return;
0752     }
0753 
0754     qCDebug(KOMPAREDIFF2_LOG) << "**** no next difference... ok lets find the next model...";
0755 
0756     if ((d->selectedModel = d->nextModel()) != nullptr) {
0757         d->selectedDifference = d->selectedModel->firstDifference();
0758 
0759         Q_EMIT setSelection(d->selectedModel, d->selectedDifference);
0760         Q_EMIT setStatusBarModelInfo(findModel(d->selectedModel),
0761                                      d->selectedModel->findDifference(d->selectedDifference),
0762                                      modelCount(),
0763                                      differenceCount(),
0764                                      d->selectedModel->appliedCount());
0765         d->updateModelListActions();
0766         return;
0767     }
0768 
0769     qCDebug(KOMPAREDIFF2_LOG) << "**** !!! No next model, ok backup plan activated...";
0770 
0771     // Backup plan
0772     d->selectedModel = d->lastModel();
0773     d->selectedDifference = d->selectedModel->lastDifference();
0774 
0775     Q_EMIT setSelection(d->selectedModel, d->selectedDifference);
0776     Q_EMIT setStatusBarModelInfo(findModel(d->selectedModel),
0777                                  d->selectedModel->findDifference(d->selectedDifference),
0778                                  modelCount(),
0779                                  differenceCount(),
0780                                  d->selectedModel->appliedCount());
0781     d->updateModelListActions();
0782 }
0783 
0784 void ModelList::slotApplyDifference(bool apply)
0785 {
0786     Q_D(ModelList);
0787 
0788     d->selectedModel->applyDifference(apply);
0789     Q_EMIT applyDifference(apply);
0790 }
0791 
0792 void ModelList::slotApplyAllDifferences(bool apply)
0793 {
0794     Q_D(ModelList);
0795 
0796     d->selectedModel->applyAllDifferences(apply);
0797     Q_EMIT applyAllDifferences(apply);
0798 }
0799 
0800 int ModelList::parseDiffOutput(const QString &diff)
0801 {
0802     Q_D(ModelList);
0803 
0804     qCDebug(KOMPAREDIFF2_LOG) << "ModelList::parseDiffOutput";
0805     Q_EMIT diffString(diff);
0806 
0807     QStringList diffLines = ModelListPrivate::split(diff);
0808 
0809     std::unique_ptr<Parser> parser = std::make_unique<Parser>(this);
0810     bool malformed = false;
0811     d->models.reset(parser->parse(diffLines, &malformed));
0812 
0813     d->info->generator = parser->generator();
0814     d->info->format = parser->format();
0815 
0816     parser.reset();
0817 
0818     if (d->models) {
0819         if (malformed) {
0820             qCDebug(KOMPAREDIFF2_LOG) << "Malformed diff";
0821             Q_EMIT error(i18n("The diff is malformed. Some lines could not be parsed and will not be displayed in the diff view."));
0822             // proceed anyway with the lines which have been parsed
0823         }
0824         d->selectedModel = d->firstModel();
0825         qCDebug(KOMPAREDIFF2_LOG) << "Ok there are differences...";
0826         d->selectedDifference = d->selectedModel->firstDifference();
0827         Q_EMIT setStatusBarModelInfo(0, 0, modelCount(), differenceCount(), 0);
0828     } else {
0829         // Wow trouble, no models, so no differences...
0830         qCDebug(KOMPAREDIFF2_LOG) << "Now i'll be damned, there should be models here !!!";
0831         return -1;
0832     }
0833 
0834     return 0;
0835 }
0836 
0837 bool ModelList::blendOriginalIntoModelList(const QString &localURL)
0838 {
0839     Q_D(ModelList);
0840 
0841     qCDebug(KOMPAREDIFF2_LOG) << "Hurrah we are blending...";
0842     QFileInfo fi(localURL);
0843 
0844     bool result = false;
0845 
0846     QString fileContents;
0847 
0848     if (fi.isDir()) { // is a dir
0849         qCDebug(KOMPAREDIFF2_LOG) << "Blend Dir";
0850 //      QDir dir( localURL, QString(), QDir::Name|QDir::DirsFirst, QDir::TypeMask );
0851         for (DiffModel *model : std::as_const(*d->models)) {
0852             qCDebug(KOMPAREDIFF2_LOG) << "Model : " << model;
0853             QString filename = model->source();
0854             if (!filename.startsWith(localURL))
0855                 filename = QDir(localURL).filePath(filename);
0856             QFileInfo fi2(filename);
0857             if (fi2.exists()) {
0858                 qCDebug(KOMPAREDIFF2_LOG) << "Reading from: " << filename;
0859                 fileContents = d->readFile(filename);
0860                 result = d->blendFile(model, fileContents);
0861             } else {
0862                 qCDebug(KOMPAREDIFF2_LOG) << "File " << filename << " does not exist !";
0863                 qCDebug(KOMPAREDIFF2_LOG) << "Assume empty file !";
0864                 fileContents.truncate(0);
0865                 result = d->blendFile(model, fileContents);
0866             }
0867         }
0868         qCDebug(KOMPAREDIFF2_LOG) << "End of Blend Dir";
0869     } else if (fi.isFile()) { // is a file
0870         qCDebug(KOMPAREDIFF2_LOG) << "Blend File";
0871         qCDebug(KOMPAREDIFF2_LOG) << "Reading from: " << localURL;
0872         fileContents = d->readFile(localURL);
0873 
0874         result = d->blendFile((*d->models)[0], fileContents);
0875         qCDebug(KOMPAREDIFF2_LOG) << "End of Blend File";
0876     }
0877 
0878     return result;
0879 }
0880 
0881 void ModelList::show()
0882 {
0883     Q_D(ModelList);
0884 
0885     qCDebug(KOMPAREDIFF2_LOG) << "ModelList::Show Number of models = " << d->models->count();
0886     Q_EMIT modelsChanged(d->models.get());
0887     Q_EMIT setSelection(d->selectedModel, d->selectedDifference);
0888 }
0889 
0890 const DiffModel *ModelList::modelAt(int i) const
0891 {
0892     Q_D(const ModelList);
0893 
0894     return d->models->at(i);
0895 }
0896 
0897 DiffModel *ModelList::modelAt(int i)
0898 {
0899     Q_D(ModelList);
0900 
0901     return d->models->at(i);
0902 }
0903 
0904 int ModelList::findModel(DiffModel *model) const
0905 {
0906     Q_D(const ModelList);
0907 
0908     return d->models->indexOf(model);
0909 }
0910 
0911 int ModelList::currentModel() const
0912 {
0913     Q_D(const ModelList);
0914 
0915     return d->models->indexOf(d->selectedModel);
0916 }
0917 
0918 int ModelList::currentDifference() const
0919 {
0920     Q_D(const ModelList);
0921 
0922     return d->selectedModel ? d->selectedModel->findDifference(d->selectedDifference) : -1;
0923 }
0924 
0925 const DiffModel *ModelList::selectedModel() const
0926 {
0927     Q_D(const ModelList);
0928 
0929     return d->selectedModel;
0930 }
0931 
0932 const Difference *ModelList::selectedDifference() const
0933 {
0934     Q_D(const ModelList);
0935 
0936     return d->selectedDifference;
0937 }
0938 
0939 void ModelList::clear()
0940 {
0941     Q_D(ModelList);
0942 
0943     if (d->models)
0944         d->models->clear();
0945 
0946     Q_EMIT modelsChanged(d->models.get());
0947 }
0948 
0949 void ModelList::refresh()
0950 {
0951     Q_D(ModelList);
0952 
0953     // FIXME: I can imagine blending also wants to be refreshed so make a switch case here
0954     compare(d->info->mode);
0955 }
0956 
0957 void ModelList::swap()
0958 {
0959     Q_D(ModelList);
0960 
0961     // FIXME Not sure if any mode could be swapped
0962     if (d->info->mode == ComparingFiles)
0963         compare(d->info->mode);
0964     else if (d->info->mode == ComparingDirs)
0965         compare(d->info->mode);
0966 }
0967 
0968 bool ModelList::hasUnsavedChanges() const
0969 {
0970     Q_D(const ModelList);
0971 
0972     if (!d->models) {
0973         return false;
0974     }
0975 
0976     return std::any_of(d->models->constBegin(), d->models->constEnd(), [] (const DiffModel *model) {
0977         return model->hasUnsavedChanges();
0978     });
0979 }
0980 
0981 int ModelList::modelCount() const
0982 {
0983     Q_D(const ModelList);
0984 
0985     return d->models ? d->models->count() : 0;
0986 }
0987 
0988 int ModelList::differenceCount() const
0989 {
0990     Q_D(const ModelList);
0991 
0992     return d->selectedModel ? d->selectedModel->differenceCount() : -1;
0993 }
0994 
0995 int ModelList::appliedCount() const
0996 {
0997     Q_D(const ModelList);
0998 
0999     return d->selectedModel ? d->selectedModel->appliedCount() : -1;
1000 }
1001 
1002 void ModelList::slotKompareInfo(Info *info)
1003 {
1004     Q_D(ModelList);
1005 
1006     d->info = info;
1007 }
1008 
1009 void ModelList::slotActionApplyDifference()
1010 {
1011     Q_D(ModelList);
1012 
1013     if (!d->selectedDifference->applied())
1014         slotApplyDifference(true);
1015     slotNextDifference();
1016     d->updateModelListActions();
1017 }
1018 
1019 void ModelList::slotActionUnApplyDifference()
1020 {
1021     Q_D(ModelList);
1022 
1023     if (d->selectedDifference->applied())
1024         slotApplyDifference(false);
1025     slotPreviousDifference();
1026     d->updateModelListActions();
1027 }
1028 
1029 void ModelList::slotActionApplyAllDifferences()
1030 {
1031     Q_D(ModelList);
1032 
1033     slotApplyAllDifferences(true);
1034     d->updateModelListActions();
1035 }
1036 
1037 void ModelList::slotActionUnapplyAllDifferences()
1038 {
1039     Q_D(ModelList);
1040 
1041     slotApplyAllDifferences(false);
1042     d->updateModelListActions();
1043 }
1044 
1045 #include "moc_modellist.cpp"