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