1 /*
2     SPDX-FileCopyrightText: 2001-2005, 2009 Otto Bruggeman <bruggie@gmail.com>
3     SPDX-FileCopyrightText: 2001-2003 John Firebaugh <jfirebaugh@kde.org>
4     SPDX-FileCopyrightText: 2004 Jeff Snyder <jeff@caffeinated.me.uk>
5     SPDX-FileCopyrightText: 2007-2011 Kevin Kofler <kevin.kofler@chello.at>
6     SPDX-FileCopyrightText: 2012 Jean-Nicolas Artaud <jeannicolasartaud@gmail.com>
7 
8     SPDX-License-Identifier: GPL-2.0-or-later
9 */
10 
11 #include "kompare_part.h"
12 
13 #include <QDialog>
14 #include <QLayout>
15 #include <QWidget>
16 #include <QMenu>
17 #include <QPainter>
18 #include <QPrinter>
19 #include <QPageLayout>
20 #include <QPrintDialog>
21 #include <QPrintPreviewDialog>
22 #include <QTemporaryDir>
23 #include <QTemporaryFile>
24 
25 #include <KPluginMetaData>
26 #include <KActionCollection>
27 #include <KJobWidgets>
28 #include <KLocalizedString>
29 #include <KMessageBox>
30 #include <KSharedConfig>
31 #include <KStandardAction>
32 #include <KStandardShortcut>
33 #include <KStandardGuiItem>
34 #include <KXMLGUIFactory>
35 
36 #include <KIO/CopyJob>
37 #include <KIO/StatJob>
38 #include <KIO/FileCopyJob>
39 #include <KIO/MkdirJob>
40 
41 #include <libkomparediff2/diffmodel.h>
42 #include <libkomparediff2/diffsettings.h>
43 
44 #include <komparepartdebug.h>
45 #include "komparelistview.h"
46 #include "kompareconnectwidget.h"
47 #include "viewsettings.h"
48 #include "kompareprefdlg.h"
49 #include "komparesaveoptionswidget.h"
50 #include "komparesplitter.h"
51 #include "kompareview.h"
52 
53 using namespace Diff2;
54 
55 ViewSettings* KomparePart::m_viewSettings = nullptr;
56 DiffSettings* KomparePart::m_diffSettings = nullptr;
57 
KomparePart(QWidget * parentWidget,QObject * parent,const KPluginMetaData & metaData,Modus modus)58 KomparePart::KomparePart(QWidget* parentWidget, QObject* parent, const KPluginMetaData& metaData, Modus modus) :
59     KParts::ReadWritePart(parent),
60     m_info()
61 {
62     setMetaData(metaData);
63 
64     // set our XML-UI resource file
65     setXMLFile(QStringLiteral("komparepartui.rc"));
66 
67     if (!m_viewSettings) {
68         m_viewSettings = new ViewSettings(nullptr);
69     }
70     if (!m_diffSettings) {
71         m_diffSettings = new DiffSettings(nullptr);
72     }
73 
74     readProperties(KSharedConfig::openConfig().data());
75 
76     m_view = new KompareView(m_viewSettings, parentWidget);
77     m_view->setContextMenuPolicy(Qt::CustomContextMenu);
78     connect(m_view, &QWidget::customContextMenuRequested,
79             this, &KomparePart::onContextMenuRequested);
80     setWidget(m_view);
81     m_splitter = m_view->splitter();
82 
83     // This creates the "Model creator" and connects the signals and slots
84     m_modelList = new Diff2::KompareModelList(m_diffSettings, m_splitter, this, "komparemodellist" , (modus == ReadWriteModus));
85 
86     const auto modelListActions = m_modelList->actionCollection()->actions();
87     for (QAction* action : modelListActions) {
88         actionCollection()->addAction(action->objectName(), action);
89     }
90     connect(m_modelList, &KompareModelList::status,
91             this, &KomparePart::slotSetStatus);
92     connect(m_modelList, &KompareModelList::setStatusBarModelInfo,
93             this, &KomparePart::setStatusBarModelInfo);
94     connect(m_modelList, &KompareModelList::error,
95             this, &KomparePart::slotShowError);
96     connect(m_modelList, &KompareModelList::applyAllDifferences,
97             this, &KomparePart::updateActions);
98     connect(m_modelList, static_cast<void(KompareModelList::*)(bool)>(&KompareModelList::applyDifference),
99             this, &KomparePart::updateActions);
100     connect(m_modelList, &KompareModelList::applyAllDifferences,
101             this, &KomparePart::appliedChanged);
102     connect(m_modelList, static_cast<void(KompareModelList::*)(bool)>(&KompareModelList::applyDifference),
103             this, &KomparePart::appliedChanged);
104     connect(m_modelList, &KompareModelList::updateActions, this, &KomparePart::updateActions);
105 
106     // This is the stuff to connect the "interface" of the kompare part to the model inside
107     connect(m_modelList, &KompareModelList::modelsChanged,
108             this, &KomparePart::modelsChanged);
109 
110     typedef void(KompareModelList::*void_KompareModelList_argModelDiff)(const DiffModel*, const Difference*);
111     typedef void(KompareModelList::*void_KompareModelList_argDiffBool)(const Difference*, bool);
112     typedef void(KompareSplitter::*void_KompareSplitter_argModelDiff)(const DiffModel*, const Difference*);
113     typedef void(KompareSplitter::*void_KompareSplitter_argDiffBool)(const Difference*, bool);
114     typedef void(KomparePart::*void_KomparePart_argModelDiff)(const DiffModel*, const Difference*);
115     typedef void(KomparePart::*void_KomparePart_argDiffBool)(const Difference*, bool);
116     connect(m_modelList, static_cast<void_KompareModelList_argModelDiff>(&KompareModelList::setSelection),
117             this, static_cast<void_KomparePart_argModelDiff>(&KomparePart::setSelection));
118     connect(this, static_cast<void_KomparePart_argModelDiff>(&KomparePart::selectionChanged),
119             m_modelList, static_cast<void_KompareModelList_argModelDiff>(&KompareModelList::slotSelectionChanged));
120 
121     connect(m_modelList, static_cast<void(KompareModelList::*)(const Difference*)>(&KompareModelList::setSelection),
122             this, static_cast<void(KomparePart::*)(const Difference*)>(&KomparePart::setSelection));
123     connect(this, static_cast<void(KomparePart::*)(const Difference*)>(&KomparePart::selectionChanged),
124             m_modelList, static_cast<void(KompareModelList::*)(const Difference*)>(&KompareModelList::slotSelectionChanged));
125 
126     connect(m_modelList, static_cast<void(KompareModelList::*)(bool)>(&KompareModelList::applyDifference),
127             this, static_cast<void(KomparePart::*)(bool)>(&KomparePart::applyDifference));
128     connect(m_modelList, &KompareModelList::applyAllDifferences,
129             this, &KomparePart::applyAllDifferences);
130     connect(m_modelList, static_cast<void_KompareModelList_argDiffBool>(&KompareModelList::applyDifference),
131             this, static_cast<void_KomparePart_argDiffBool>(&KomparePart::applyDifference));
132     connect(m_modelList, &KompareModelList::diffString,
133             this, &KomparePart::diffString);
134 
135     connect(this, &KomparePart::kompareInfo, m_modelList, &KompareModelList::slotKompareInfo);
136 
137     // Here we connect the splitter to the modellist
138     connect(m_modelList, static_cast<void_KompareModelList_argModelDiff>(&KompareModelList::setSelection),
139             m_splitter,  static_cast<void_KompareSplitter_argModelDiff>(&KompareSplitter::slotSetSelection));
140 //     connect(m_splitter,  SIGNAL(selectionChanged(const Diff2::Difference*,const Diff2::Difference*)),
141 //             m_modelList, SLOT(slotSelectionChanged(const Diff2::Difference*,const Diff2::Difference*)));
142     connect(m_modelList, static_cast<void(KompareModelList::*)(const Difference*)>(&KompareModelList::setSelection),
143             m_splitter,  static_cast<void(KompareSplitter::*)(const Difference*)>(&KompareSplitter::slotSetSelection));
144     connect(m_splitter,  static_cast<void(KompareSplitter::*)(const Difference*)>(&KompareSplitter::selectionChanged),
145             m_modelList, static_cast<void(KompareModelList::*)(const Difference*)>(&KompareModelList::slotSelectionChanged));
146 
147     connect(m_modelList, static_cast<void(KompareModelList::*)(bool)>(&KompareModelList::applyDifference),
148             m_splitter, static_cast<void(KompareSplitter::*)(bool)>(&KompareSplitter::slotApplyDifference));
149     connect(m_modelList, &KompareModelList::applyAllDifferences,
150             m_splitter, &KompareSplitter::slotApplyAllDifferences);
151     connect(m_modelList, static_cast<void_KompareModelList_argDiffBool>(&KompareModelList::applyDifference),
152             m_splitter, static_cast<void_KompareSplitter_argDiffBool>(&KompareSplitter::slotApplyDifference));
153     connect(this, &KomparePart::configChanged, m_splitter, &KompareSplitter::configChanged);
154 
155     setupActions(modus);
156 
157     // we are read-write by default -> uhm what if we are opened by lets say konq in RO mode ?
158     // Then we should not be doing this...
159     setReadWrite((modus == ReadWriteModus));
160 
161     // we are not modified since we haven't done anything yet
162     setModified(false);
163 }
164 
~KomparePart()165 KomparePart::~KomparePart()
166 {
167     // This is the only place allowed to call cleanUpTemporaryFiles
168     // because before there might still be a use for them (when swapping)
169     cleanUpTemporaryFiles();
170 }
171 
setupActions(Modus modus)172 void KomparePart::setupActions(Modus modus)
173 {
174     // create our actions
175 
176     if (modus == ReadWriteModus) {
177         m_saveAll = actionCollection()->addAction(QStringLiteral("file_save_all"), this, &KomparePart::saveAll);
178         m_saveAll->setIcon(QIcon::fromTheme(QStringLiteral("document-save-all")));
179         m_saveAll->setText(i18nc("@action", "Save &All"));
180         m_saveDiff = actionCollection()->addAction(QStringLiteral("file_save_diff"), this, &KomparePart::saveDiff);
181         m_saveDiff->setText(i18nc("@action", "Save &Diff..."));
182         m_swap = actionCollection()->addAction(QStringLiteral("file_swap"), this, &KomparePart::slotSwap);
183         m_swap->setText(i18nc("@action", "Swap Source with Destination"));
184     } else {
185         m_saveAll = nullptr;
186         m_saveDiff = nullptr;
187         m_swap = nullptr;
188     }
189     m_diffStats = actionCollection()->addAction(QStringLiteral("file_diffstats"), this, &KomparePart::slotShowDiffstats);
190     m_diffStats->setText(i18nc("@action", "Show Statistics"));
191     m_diffRefresh = actionCollection()->addAction(QStringLiteral("file_refreshdiff"), this, &KomparePart::slotRefreshDiff);
192     m_diffRefresh->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh")));
193     m_diffRefresh->setText(i18nc("@action", "Refresh Diff"));
194     actionCollection()->setDefaultShortcuts(m_diffRefresh, KStandardShortcut::reload());
195 
196     m_print        = KStandardAction::print(this, &KomparePart::slotFilePrint, actionCollection());
197     m_printPreview = KStandardAction::printPreview(this, &KomparePart::slotFilePrintPreview, actionCollection());
198     KStandardAction::preferences(this, &KomparePart::optionsPreferences, actionCollection());
199 }
200 
updateActions()201 void KomparePart::updateActions()
202 {
203     if (m_saveAll) m_saveAll->setEnabled(m_modelList->hasUnsavedChanges());
204     if (m_saveDiff) m_saveDiff->setEnabled(m_modelList->mode() == Kompare::ComparingFiles || m_modelList->mode() == Kompare::ComparingDirs);
205     if (m_swap) m_swap->setEnabled(m_modelList->mode() == Kompare::ComparingFiles || m_modelList->mode() == Kompare::ComparingDirs);
206     m_diffRefresh->setEnabled(m_modelList->mode() == Kompare::ComparingFiles || m_modelList->mode() == Kompare::ComparingDirs);
207     m_diffStats->setEnabled(m_modelList->modelCount() > 0);
208     m_print->setEnabled(m_modelList->modelCount() > 0);          // If modellist has models then we have something to print, it's that simple.
209     m_printPreview->setEnabled(m_modelList);
210 }
211 
setEncoding(const QString & encoding)212 void KomparePart::setEncoding(const QString& encoding)
213 {
214     qCDebug(KOMPAREPART) << "Encoding: " << encoding;
215     m_modelList->setEncoding(encoding);
216 }
217 
openDiff(const QUrl & url)218 bool KomparePart::openDiff(const QUrl& url)
219 {
220     qCDebug(KOMPAREPART) << "Url = " << url.url();
221 
222     m_info.mode = Kompare::ShowingDiff;
223     m_info.source = url;
224     bool result = false;
225     fetchURL(url, true);
226 
227     Q_EMIT kompareInfo(&m_info);
228 
229     if (!m_info.localSource.isEmpty())
230     {
231         qCDebug(KOMPAREPART) << "Download succeeded ";
232         result = m_modelList->openDiff(m_info.localSource);
233         updateActions();
234         updateCaption();
235         updateStatus();
236     }
237     else
238     {
239         qCDebug(KOMPAREPART) << "Download failed !";
240     }
241 
242     return result;
243 }
244 
openDiff(const QString & diffOutput)245 bool KomparePart::openDiff(const QString& diffOutput)
246 {
247     bool value = false;
248 
249     m_info.mode = Kompare::ShowingDiff;
250 
251     Q_EMIT kompareInfo(&m_info);
252 
253     if (m_modelList->parseAndOpenDiff(diffOutput) == 0)
254     {
255         value = true;
256         updateActions();
257         updateCaption();
258         updateStatus();
259     }
260 
261     return value;
262 }
263 
openDiff3(const QUrl & diff3Url)264 bool KomparePart::openDiff3(const QUrl& diff3Url)
265 {
266     // FIXME: Implement this !!!
267     qCDebug(KOMPAREPART) << "Not implemented yet. Filename is: " << diff3Url;
268     return false;
269 }
270 
openDiff3(const QString & diff3Output)271 bool KomparePart::openDiff3(const QString& diff3Output)
272 {
273     // FIXME: Implement this !!!
274     qCDebug(KOMPAREPART) << "Not implemented yet. diff3 output is: ";
275     qCDebug(KOMPAREPART) << diff3Output;
276     return false;
277 }
278 
fetchURL(const QUrl & url,bool addToSource)279 bool KomparePart::fetchURL(const QUrl& url, bool addToSource)
280 {
281     // Default value if there is an error is "", we rely on it!
282     QString tempFileName;
283     // Only in case of error do we set result to false, don't forget!!
284     bool result = true;
285     QTemporaryDir* tmpDir = nullptr;
286 
287     if (!url.isLocalFile())
288     {
289         KIO::StatJob* statJob = KIO::stat(url);
290         KJobWidgets::setWindow(statJob, widget());
291         if (statJob->exec())
292         {
293             KIO::UDSEntry node;
294             node = statJob->statResult();
295             if (!node.isDir())
296             {
297                 tmpDir = new QTemporaryDir(QDir::tempPath() + QLatin1String("/kompare"));
298                 tmpDir->setAutoRemove(true);
299                 tempFileName = tmpDir->path() + QLatin1Char('/') + url.fileName();
300                 KIO::FileCopyJob* copyJob = KIO::file_copy(url, QUrl::fromLocalFile(tempFileName));
301                 KJobWidgets::setWindow(copyJob, widget());
302                 if (! copyJob->exec())
303                 {
304                     qDebug() << "download error " << copyJob->errorString();
305                     slotShowError(i18n("<qt>The URL <b>%1</b> cannot be downloaded.</qt>", url.toDisplayString()));
306                     tempFileName.clear(); // Not sure if download has already touched this tempFileName when there is an error
307                     result = false;
308                 }
309             }
310             else
311             {
312                 tmpDir = new QTemporaryDir(QDir::tempPath() + QLatin1String("/kompare"));
313                 tmpDir->setAutoRemove(true);   // Yes this is the default but just to make sure
314                 KIO::CopyJob* copyJob = KIO::copy(url, QUrl::fromLocalFile(tmpDir->path()));
315                 KJobWidgets::setWindow(copyJob, widget());
316                 if (! copyJob->exec())
317                 {
318                     slotShowError(i18n("<qt>The URL <b>%1</b> cannot be downloaded.</qt>", url.toDisplayString()));
319                     delete tmpDir;
320                     tmpDir = nullptr;
321                     result = false;
322                 }
323                 else
324                 {
325                     tempFileName = tmpDir->path();
326                     qCDebug(KOMPAREPART) << "tempFileName = " << tempFileName;
327                     // If a directory is copied into QTemporaryDir then the directory in
328                     // here is what I need to add to tempFileName
329                     QDir dir(tempFileName);
330                     QStringList entries = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
331                     if (entries.size() == 1)   // More than 1 entry in here means big problems!!!
332                     {
333                         if (!tempFileName.endsWith(QLatin1Char('/')))
334                             tempFileName += QLatin1Char('/');
335                         tempFileName += entries.at(0);
336                         tempFileName += QLatin1Char('/');
337                     }
338                     else
339                     {
340                         qCDebug(KOMPAREPART) << "Yikes, nothing downloaded?";
341                         delete tmpDir;
342                         tmpDir = nullptr;
343                         tempFileName.clear();
344                         result = false;
345                     }
346                 }
347             }
348         }
349     }
350     else
351     {
352         // is Local already, check if exists
353         if (QFile::exists(url.toLocalFile())) {
354             tempFileName = url.toLocalFile();
355         } else {
356             slotShowError(i18n("<qt>The URL <b>%1</b> does not exist on your system.</qt>", url.toDisplayString()));
357             result = false;
358         }
359     }
360 
361     if (addToSource)
362     {
363         m_info.localSource = tempFileName;
364         m_info.sourceQTempDir = tmpDir;
365     }
366     else
367     {
368         m_info.localDestination = tempFileName;
369         m_info.destinationQTempDir = tmpDir;
370     }
371 
372     return result;
373 }
374 
cleanUpTemporaryFiles()375 void KomparePart::cleanUpTemporaryFiles()
376 {
377     qCDebug(KOMPAREPART) << "Cleaning temporary files.";
378     if (!m_info.localSource.isEmpty())
379     {
380         if (m_info.sourceQTempDir)
381         {
382             delete m_info.sourceQTempDir;
383             m_info.sourceQTempDir = nullptr;
384         }
385         m_info.localSource.clear();
386     }
387     if (!m_info.localDestination.isEmpty())
388     {
389         if (!m_info.destinationQTempDir)
390         {
391             delete m_info.destinationQTempDir;
392             m_info.destinationQTempDir = nullptr;
393         }
394         m_info.localDestination.clear();
395     }
396 }
397 
compare(const QUrl & source,const QUrl & destination)398 void KomparePart::compare(const QUrl& source, const QUrl& destination)
399 {
400     // FIXME: This is silly, i can use NetAccess::stat to figure out what it is and not
401     // wait until i am in the modellist to determine the mode we're supposed to be in.
402     // That should make the code more readable
403     // I should store the QTemporaryDir(s)/File(s) in the Info struct as well and delete it at the right time
404     m_info.source = source;
405     m_info.destination = destination;
406 
407     // 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
408     // For now we do not do error checking, user has already been notified and if the localString is empty then we do not diff
409     fetchURL(source, true);
410     fetchURL(destination, false);
411 
412     Q_EMIT kompareInfo(&m_info);
413 
414     compareAndUpdateAll();
415 }
416 
compareFileString(const QUrl & sourceFile,const QString & destination)417 void KomparePart::compareFileString(const QUrl& sourceFile, const QString& destination)
418 {
419     //Set the modeto specify that the source is a file, and the destination is a string
420     m_info.mode = Kompare::ComparingFileString;
421 
422     m_info.source = sourceFile;
423     m_info.localDestination = destination;
424 
425     fetchURL(sourceFile, true);
426 
427     Q_EMIT kompareInfo(&m_info);
428 
429     compareAndUpdateAll();
430 }
431 
compareStringFile(const QString & source,const QUrl & destinationFile)432 void KomparePart::compareStringFile(const QString& source, const QUrl& destinationFile)
433 {
434     //Set the modeto specify that the source is a file, and the destination is a string
435     m_info.mode = Kompare::ComparingStringFile;
436 
437     m_info.localSource = source;
438     m_info.destination = destinationFile;
439 
440     fetchURL(destinationFile, false);
441 
442     Q_EMIT kompareInfo(&m_info);
443 
444     compareAndUpdateAll();
445 }
446 
compareFiles(const QUrl & sourceFile,const QUrl & destinationFile)447 void KomparePart::compareFiles(const QUrl& sourceFile, const QUrl& destinationFile)
448 {
449     m_info.mode = Kompare::ComparingFiles;
450 
451     m_info.source = sourceFile;
452     m_info.destination = destinationFile;
453 
454     // 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
455     // For now we do not do error checking, user has already been notified and if the localString is empty then we do not diff
456     fetchURL(sourceFile, true);
457     fetchURL(destinationFile, false);
458 
459     Q_EMIT kompareInfo(&m_info);
460 
461     compareAndUpdateAll();
462 }
463 
compareDirs(const QUrl & sourceDirectory,const QUrl & destinationDirectory)464 void KomparePart::compareDirs(const QUrl& sourceDirectory, const QUrl& destinationDirectory)
465 {
466     m_info.mode = Kompare::ComparingDirs;
467 
468     m_info.source = sourceDirectory;
469     m_info.destination = destinationDirectory;
470 
471     fetchURL(sourceDirectory, true);
472     fetchURL(destinationDirectory, false);
473 
474     Q_EMIT kompareInfo(&m_info);
475 
476     compareAndUpdateAll();
477 }
478 
compare3Files(const QUrl &,const QUrl &,const QUrl &)479 void KomparePart::compare3Files(const QUrl& /*originalFile*/, const QUrl& /*changedFile1*/, const QUrl& /*changedFile2*/)
480 {
481     // FIXME: actually implement this some day :)
482     updateActions();
483     updateCaption();
484     updateStatus();
485 }
486 
openFileAndDiff(const QUrl & file,const QUrl & diffFile)487 void KomparePart::openFileAndDiff(const QUrl& file, const QUrl& diffFile)
488 {
489     m_info.source = file;
490     m_info.destination = diffFile;
491 
492     fetchURL(file, true);
493     fetchURL(diffFile, false);
494     m_info.mode = Kompare::BlendingFile;
495 
496     Q_EMIT kompareInfo(&m_info);
497 
498     compareAndUpdateAll();
499 }
500 
openDirAndDiff(const QUrl & dir,const QUrl & diffFile)501 void KomparePart::openDirAndDiff(const QUrl& dir,  const QUrl& diffFile)
502 {
503     m_info.source = dir;
504     m_info.destination = diffFile;
505 
506     fetchURL(dir, true);
507     fetchURL(diffFile, false);
508     m_info.mode = Kompare::BlendingDir;
509 
510     Q_EMIT kompareInfo(&m_info);
511 
512     if (!m_info.localSource.isEmpty() && !m_info.localDestination.isEmpty())
513     {
514         m_modelList->openDirAndDiff();
515         //Must this be in here? couldn't we use compareAndUpdateAll as well?
516         updateActions();
517         updateCaption();
518         updateStatus();
519     }
520 }
521 
openFile()522 bool KomparePart::openFile()
523 {
524     // This is called from openURL
525     // This is a little inefficient but i will do it anyway
526     openDiff(url());
527     return true;
528 }
529 
saveAll()530 bool KomparePart::saveAll()
531 {
532     bool result = m_modelList->saveAll();
533     updateActions();
534     updateCaption();
535     updateStatus();
536     return result;
537 }
538 
saveDiff()539 void KomparePart::saveDiff()
540 {
541     QDialog dlg(widget());
542     dlg.setObjectName(QStringLiteral("save_options"));
543     dlg.setModal(true);
544     dlg.setWindowTitle(i18nc("@title:window", "Diff Options"));
545     QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Save | QDialogButtonBox::Cancel, &dlg);
546     connect(buttons, &QDialogButtonBox::accepted, &dlg, &QDialog::accept);
547     connect(buttons, &QDialogButtonBox::rejected, &dlg, &QDialog::reject);
548     KompareSaveOptionsWidget* w = new KompareSaveOptionsWidget(
549         m_info.localSource,
550         m_info.localDestination,
551         m_diffSettings, &dlg);
552     QVBoxLayout* layout = new QVBoxLayout(&dlg);
553     layout->addWidget(w);
554     layout->addWidget(buttons);
555     dlg.setLayout(layout);
556 
557     if (dlg.exec()) {
558         w->saveOptions();
559         KSharedConfig::Ptr config = KSharedConfig::openConfig();
560         saveProperties(config.data());
561         config->sync();
562 
563         while (1)
564         {
565             QUrl url = QFileDialog::getSaveFileUrl(widget(), i18nc("@title:window", "Save .diff"),
566                                                    m_info.destination,
567                                                    i18n("Patch Files (*.diff *.dif *.patch)"));
568             if (url.isLocalFile() && QFile::exists(url.toLocalFile())) {
569                 int result = KMessageBox::warningYesNoCancel(widget(), i18n("The file exists or is write-protected; do you want to overwrite it?"), i18nc("@window:title", "File Exists"), KStandardGuiItem::overwrite(), KGuiItem(i18nc("@action:button", "Do Not Overwrite")));
570                 if (result == KMessageBox::Cancel)
571                 {
572                     break;
573                 }
574                 else if (result == KMessageBox::No)
575                 {
576                     continue;
577                 }
578                 else
579                 {
580                     qCDebug(KOMPAREPART) << "URL = " << url.toDisplayString();
581                     qCDebug(KOMPAREPART) << "Directory = " << w->directory();
582                     qCDebug(KOMPAREPART) << "DiffSettings = " << m_diffSettings;
583 
584                     m_modelList->saveDiff(url.url(), w->directory(), m_diffSettings);
585                     break;
586                 }
587             }
588             else
589             {
590                 qCDebug(KOMPAREPART) << "URL = " << url.toDisplayString();
591                 qCDebug(KOMPAREPART) << "Directory = " << w->directory();
592                 qCDebug(KOMPAREPART) << "DiffSettings = " << m_diffSettings;
593 
594                 m_modelList->saveDiff(url.url(), w->directory(), m_diffSettings);
595                 break;
596             }
597         }
598     }
599 }
600 
slotFilePrint()601 void KomparePart::slotFilePrint()
602 {
603     QPrinter printer;
604     printer.setPageOrientation(QPageLayout::Landscape);
605     QPrintDialog* dlg = new QPrintDialog(&printer, nullptr);
606 
607     if (dlg->exec() == QDialog::Accepted)
608     {
609         // do some printing in qprinter
610         slotPaintRequested(&printer);
611     }
612 
613     delete dlg;
614 }
615 
slotFilePrintPreview()616 void KomparePart::slotFilePrintPreview()
617 {
618     QPrinter printer;
619     printer.setPageOrientation(QPageLayout::Landscape);
620     QPrintPreviewDialog dlg(&printer);
621 
622     connect(&dlg, &QPrintPreviewDialog::paintRequested, this, &KomparePart::slotPaintRequested);
623 
624     dlg.exec();
625 }
626 
slotPaintRequested(QPrinter * printer)627 void KomparePart::slotPaintRequested(QPrinter* printer)
628 {
629     qCDebug(KOMPAREPART) << "Now paint something...";
630     QPainter p;
631     p.begin(printer);
632 
633     QSize widgetWidth = m_view->size();
634     qCDebug(KOMPAREPART) << "printer.width()     = " << printer->width();
635     qCDebug(KOMPAREPART) << "widgetWidth.width() = " << widgetWidth.width();
636     qreal factor = ((qreal)printer->width()) / ((qreal)widgetWidth.width());
637 
638     qCDebug(KOMPAREPART) << "factor              = " << factor;
639 
640     p.scale(factor, factor);
641     m_view->render(&p);
642 
643     p.end();
644     qCDebug(KOMPAREPART) << "Done painting something...";
645 }
646 
slotSetStatus(enum Kompare::Status status)647 void KomparePart::slotSetStatus(enum Kompare::Status status)
648 {
649     updateActions();
650 
651     switch (status) {
652     case Kompare::RunningDiff:
653         Q_EMIT setStatusBarText(i18nc("@info:status", "Running diff..."));
654         break;
655     case Kompare::Parsing:
656         Q_EMIT setStatusBarText(i18nc("@info:status", "Parsing diff output..."));
657         break;
658     case Kompare::FinishedParsing:
659         updateStatus();
660         break;
661     case Kompare::FinishedWritingDiff:
662         updateStatus();
663         Q_EMIT diffURLChanged();
664         break;
665     default:
666         break;
667     }
668 }
669 
onContextMenuRequested(const QPoint & pos)670 void KomparePart::onContextMenuRequested(const QPoint& pos)
671 {
672     QMenu* popup = static_cast<QMenu*>(factory()->container(QStringLiteral("mainPopUp"), this));
673     if (popup)
674         popup->exec(m_view->mapToGlobal(pos));
675 }
676 
updateCaption()677 void KomparePart::updateCaption()
678 {
679     QString source = m_info.source.toDisplayString();
680     QString destination = m_info.destination.toDisplayString();
681 
682     QString text;
683 
684     switch (m_info.mode)
685     {
686     case Kompare::ComparingFiles :
687     case Kompare::ComparingDirs :
688     case Kompare::BlendingFile :
689     case Kompare::BlendingDir :
690         text = source + QLatin1String(" -- ") + destination; // no need to translate this " -- "
691         break;
692     case Kompare::ShowingDiff :
693         text = source;
694         break;
695     default:
696         break;
697     }
698 
699     Q_EMIT setWindowCaption(text);
700 }
701 
updateStatus()702 void KomparePart::updateStatus()
703 {
704     QString source = m_info.source.toDisplayString();
705     QString destination = m_info.destination.toDisplayString();
706 
707     QString text;
708 
709     switch (m_info.mode)
710     {
711     case Kompare::ComparingFiles :
712         text = i18nc("@info:status", "Comparing file %1 with file %2" ,
713                     source,
714                     destination);
715         break;
716     case Kompare::ComparingDirs :
717         text = i18nc("@info:status", "Comparing files in %1 with files in %2" ,
718                     source,
719                     destination);
720         break;
721     case Kompare::ShowingDiff :
722         text = i18nc("@info:status", "Viewing diff output from %1", source);
723         break;
724     case Kompare::BlendingFile :
725         text = i18nc("@info:status", "Blending diff output from %1 into file %2" ,
726                     source,
727                     destination);
728         break;
729     case Kompare::BlendingDir :
730         text = i18nc("@info:status", "Blending diff output from %1 into folder %2" ,
731                     m_info.source.toDisplayString(),
732                     m_info.destination.toDisplayString());
733         break;
734     default:
735         break;
736     }
737 
738     Q_EMIT setStatusBarText(text);
739 }
740 
compareAndUpdateAll()741 void KomparePart::compareAndUpdateAll()
742 {
743     if (!m_info.localSource.isEmpty() && !m_info.localDestination.isEmpty())
744     {
745         switch (m_info.mode)
746         {
747         default:
748         case Kompare::UnknownMode:
749             m_modelList->compare();
750             break;
751 
752         case Kompare::ComparingStringFile:
753         case Kompare::ComparingFileString:
754         case Kompare::ComparingFiles:
755         case Kompare::ComparingDirs:
756             m_modelList->compare(m_info.mode);
757             break;
758 
759         case Kompare::BlendingFile:
760             m_modelList->openFileAndDiff();
761             break;
762         }
763         updateCaption();
764         updateStatus();
765     }
766     updateActions();
767 }
768 
slotShowError(const QString & error)769 void KomparePart::slotShowError(const QString& error)
770 {
771     KMessageBox::error(widget(), error);
772 }
773 
slotSwap()774 void KomparePart::slotSwap()
775 {
776     if (m_modelList->hasUnsavedChanges())
777     {
778         int query = KMessageBox::warningYesNoCancel
779                     (
780                         widget(),
781                         i18n("You have made changes to the destination file(s).\n"
782                              "Would you like to save them?"),
783                         i18nc("@title:window", "Save Changes?"),
784                         KStandardGuiItem::save(),
785                         KStandardGuiItem::discard()
786                     );
787 
788         if (query == KMessageBox::Yes)
789             m_modelList->saveAll();
790 
791         if (query == KMessageBox::Cancel)
792             return; // Abort prematurely so no swapping
793     }
794 
795     // Swap the info in the Kompare::Info struct
796     m_info.swapSourceWithDestination();
797 
798     // Update window caption and statusbar text
799     updateCaption();
800     updateStatus();
801 
802     m_modelList->swap();
803 }
804 
slotRefreshDiff()805 void KomparePart::slotRefreshDiff()
806 {
807     if (m_modelList->hasUnsavedChanges())
808     {
809         int query = KMessageBox::warningYesNoCancel
810                     (
811                         widget(),
812                         i18n("You have made changes to the destination file(s).\n"
813                              "Would you like to save them?"),
814                         i18nc("@title:window", "Save Changes?"),
815                         KStandardGuiItem::save(),
816                         KStandardGuiItem::discard()
817                     );
818 
819         if (query == KMessageBox::Cancel)
820             return; // Abort prematurely so no refreshing
821 
822         if (query == KMessageBox::Yes)
823             m_modelList->saveAll();
824     }
825 
826     // For this to work properly you have to refetch the files from their (remote) locations
827     cleanUpTemporaryFiles();
828     fetchURL(m_info.source, true);
829     fetchURL(m_info.destination, false);
830     m_modelList->refresh();
831 }
832 
slotShowDiffstats()833 void KomparePart::slotShowDiffstats()
834 {
835     // Fetch all the args needed for komparestatsmessagebox
836     // oldfile, newfile, diffformat, noofhunks, noofdiffs
837 
838     QString oldFile;
839     QString newFile;
840     QString diffFormat;
841     int filesInDiff;
842     int noOfHunks;
843     int noOfDiffs;
844 
845     oldFile = m_modelList->selectedModel() ? m_modelList->selectedModel()->sourceFile()  : QString();
846     newFile = m_modelList->selectedModel() ? m_modelList->selectedModel()->destinationFile() : QString();
847 
848     if (m_modelList->selectedModel())
849     {
850         switch (m_info.format) {
851         case Kompare::Unified :
852             diffFormat = i18nc("@item diff format", "Unified");
853             break;
854         case Kompare::Context :
855             diffFormat = i18nc("@item diff format", "Context");
856             break;
857         case Kompare::RCS :
858             diffFormat = i18nc("@item diff format", "RCS");
859             break;
860         case Kompare::Ed :
861             diffFormat = i18nc("@item diff format", "Ed");
862             break;
863         case Kompare::Normal :
864             diffFormat = i18nc("@item diff format", "Normal");
865             break;
866         case Kompare::UnknownFormat :
867         default:
868             diffFormat = i18nc("@item diff format", "Unknown");
869             break;
870         }
871     }
872     else
873     {
874         diffFormat.clear();
875     }
876 
877     filesInDiff = m_modelList->modelCount();
878 
879     noOfHunks = m_modelList->selectedModel() ? m_modelList->selectedModel()->hunkCount() : 0;
880     noOfDiffs = m_modelList->selectedModel() ? m_modelList->selectedModel()->differenceCount() : 0;
881 
882     if (m_modelList->modelCount() == 0) {   // no diff loaded yet
883         KMessageBox::information(nullptr, i18n(
884             "No diff file, or no 2 files have been diffed. "
885             "Therefore no stats are available."),
886             i18nc("@title:window", "Diff Statistics"), QString(), KMessageBox::Options());
887     }
888     else if (m_modelList->modelCount() == 1) {   // 1 file in diff, or 2 files compared
889         KMessageBox::information(nullptr, i18n(
890             "Statistics:\n"
891             "\n"
892             "Old file: %1\n"
893             "New file: %2\n"
894             "\n"
895             "Format: %3\n"
896             "Number of hunks: %4\n"
897             "Number of differences: %5",
898             oldFile, newFile, diffFormat,
899             noOfHunks, noOfDiffs),
900             i18nc("@title:window", "Diff Statistics"), QString(), KMessageBox::Options());
901     } else { // more than 1 file in diff, or 2 directories compared
902         KMessageBox::information(nullptr, ki18n(
903             "Statistics:\n"
904             "\n"
905             "Number of files in diff file: %1\n"
906             "Format: %2\n"
907             "\n"
908             "Current old file: %3\n"
909             "Current new file: %4\n"
910             "\n"
911             "Number of hunks: %5\n"
912             "Number of differences: %6")
913             .subs(filesInDiff).subs(diffFormat).subs(oldFile)
914             .subs(newFile).subs(noOfHunks).subs(noOfDiffs)
915             .toString(),
916             i18nc("@title:window", "Diff Statistics"), QString(), KMessageBox::Options());
917     }
918 }
919 
queryClose()920 bool KomparePart::queryClose()
921 {
922     if (!m_modelList->hasUnsavedChanges()) return true;
923 
924     int query = KMessageBox::warningYesNoCancel
925                 (
926                     widget(),
927                     i18n("You have made changes to the destination file(s).\n"
928                          "Would you like to save them?"),
929                     i18nc("@title:window", "Save Changes?"),
930                     KStandardGuiItem::save(),
931                     KStandardGuiItem::discard()
932                 );
933 
934     if (query == KMessageBox::Cancel)
935         return false;
936 
937     if (query == KMessageBox::Yes)
938         return m_modelList->saveAll();
939 
940     return true;
941 }
942 
setReadWrite(bool readWrite)943 void KomparePart::setReadWrite(bool readWrite)
944 {
945     m_modelList->setReadWrite(readWrite);
946     KParts::ReadWritePart::setReadWrite(readWrite);
947 }
948 
readProperties(KConfig * config)949 int KomparePart::readProperties(KConfig* config)
950 {
951     m_viewSettings->loadSettings(config);
952     m_diffSettings->loadSettings(config);
953     Q_EMIT configChanged();
954     return 0;
955 }
956 
saveProperties(KConfig * config)957 int KomparePart::saveProperties(KConfig* config)
958 {
959     m_viewSettings->saveSettings(config);
960     m_diffSettings->saveSettings(config);
961     return 0;
962 }
963 
optionsPreferences()964 void KomparePart::optionsPreferences()
965 {
966     // show preferences
967     KomparePrefDlg pref(m_viewSettings, m_diffSettings);
968 
969     connect(&pref, &KomparePrefDlg::configChanged, this, &KomparePart::configChanged);
970 
971     if (pref.exec())
972         Q_EMIT configChanged();
973 }
974