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: 2007-2011 Kevin Kofler <kevin.kofler@chello.at>
5 
6     SPDX-License-Identifier: GPL-2.0-or-later
7 */
8 
9 #include "komparenavtreepart.h"
10 
11 #include <QDebug>
12 #include <QTreeWidgetItemIterator>
13 
14 #include <KLocalizedString>
15 #include <KPluginMetaData>
16 #include <KPluginFactory>
17 
18 #include <libkomparediff2/difference.h>
19 #include <libkomparediff2/diffmodel.h>
20 #include <libkomparediff2/diffmodellist.h>
21 #include <libkomparediff2/komparemodellist.h>
22 
23 #include <komparenavviewdebug.h>
24 
25 #define COL_SOURCE        0
26 #define COL_DESTINATION   1
27 #define COL_DIFFERENCE    2
28 
29 using namespace Diff2;
30 
KompareNavTreePart(QWidget * parentWidget,QObject * parent,const KPluginMetaData & metaData,const QVariantList &)31 KompareNavTreePart::KompareNavTreePart(QWidget* parentWidget, QObject* parent,
32                                        const KPluginMetaData& metaData, const QVariantList&)
33     : KParts::ReadOnlyPart(parent),
34       m_splitter(nullptr),
35       m_modelList(nullptr),
36       m_srcDirTree(nullptr),
37       m_destDirTree(nullptr),
38       m_fileList(nullptr),
39       m_changesList(nullptr),
40       m_srcRootItem(nullptr),
41       m_destRootItem(nullptr),
42       m_selectedModel(nullptr),
43       m_selectedDifference(nullptr),
44       m_source(),
45       m_destination(),
46       m_info(nullptr)
47 {
48     setMetaData(metaData);
49 
50     m_splitter = new QSplitter(Qt::Horizontal, parentWidget);
51 
52     setWidget(m_splitter);
53 
54     m_srcDirTree = new QTreeWidget(m_splitter);
55     m_srcDirTree->setHeaderLabel(i18nc("@title:column", "Source Folder"));
56     m_srcDirTree->setRootIsDecorated(false);
57     m_srcDirTree->setSortingEnabled(true);
58     m_srcDirTree->sortByColumn(0, Qt::AscendingOrder);
59 
60     m_destDirTree = new QTreeWidget(m_splitter);
61     m_destDirTree->setHeaderLabel(i18nc("@title:column", "Destination Folder"));
62     m_destDirTree->setRootIsDecorated(false);
63     m_destDirTree->setSortingEnabled(true);
64     m_destDirTree->sortByColumn(0, Qt::AscendingOrder);
65 
66     m_fileList = new QTreeWidget(m_splitter);
67     m_fileList->setHeaderLabels(QStringList {
68         i18nc("@title:column", "Source File"),
69         i18nc("@title:column", "Destination File")
70     } );
71     m_fileList->setAllColumnsShowFocus(true);
72     m_fileList->setRootIsDecorated(false);
73     m_fileList->setSortingEnabled(true);
74     m_fileList->sortByColumn(0, Qt::AscendingOrder);
75 
76     m_changesList = new QTreeWidget(m_splitter);
77     m_changesList->setHeaderLabels(QStringList {
78         i18nc("@title:column", "Source Line"),
79         i18nc("@title:column", "Destination Line"),
80         i18nc("@title:column", "Difference"),
81     } );
82     m_changesList->setAllColumnsShowFocus(true);
83     m_changesList->setRootIsDecorated(false);
84     m_changesList->setSortingEnabled(true);
85     m_changesList->sortByColumn(0, Qt::AscendingOrder);
86 
87     connect(m_srcDirTree, &QTreeWidget::currentItemChanged,
88             this, &KompareNavTreePart::slotSrcDirTreeSelectionChanged);
89     connect(m_destDirTree, &QTreeWidget::currentItemChanged,
90             this, &KompareNavTreePart::slotDestDirTreeSelectionChanged);
91     connect(m_fileList, &QTreeWidget::currentItemChanged,
92             this, &KompareNavTreePart::slotFileListSelectionChanged);
93     connect(m_changesList, &QTreeWidget::currentItemChanged,
94             this, &KompareNavTreePart::slotChangesListSelectionChanged);
95 }
96 
~KompareNavTreePart()97 KompareNavTreePart::~KompareNavTreePart()
98 {
99     m_modelList = nullptr;
100     m_selectedModel = nullptr;
101     m_selectedDifference = nullptr;
102 }
103 
slotKompareInfo(struct Kompare::Info * info)104 void KompareNavTreePart::slotKompareInfo(struct Kompare::Info* info)
105 {
106     m_info = info;
107 }
108 
slotModelsChanged(const DiffModelList * modelList)109 void KompareNavTreePart::slotModelsChanged(const DiffModelList* modelList)
110 {
111     qCDebug(KOMPARENAVVIEW) << "Models (" << modelList << ") have changed... scanning the models... " ;
112 
113     if (modelList)
114     {
115         m_modelList = modelList;
116         m_srcDirTree->clear();
117         m_destDirTree->clear();
118         m_fileList->clear();
119         m_changesList->clear();
120         buildTreeInMemory();
121     }
122     else
123     {
124         m_modelList = modelList;
125         m_srcDirTree->clear();
126         m_destDirTree->clear();
127         m_fileList->clear();
128         m_changesList->clear();
129     }
130 }
131 
buildTreeInMemory()132 void KompareNavTreePart::buildTreeInMemory()
133 {
134     qCDebug(KOMPARENAVVIEW) << "BuildTreeInMemory called" ;
135 
136     if (m_modelList->count() == 0)
137     {
138         qCDebug(KOMPARENAVVIEW) << "No models... weird shit..." ;
139         return; // avoids a crash on clear()
140     }
141 
142     if (!m_info)
143     {
144         qCDebug(KOMPARENAVVIEW) << "No Info... weird shit..." ;
145         return;
146     }
147 
148     QString srcBase;
149     QString destBase;
150 
151     DiffModel* model;
152     model = m_modelList->first();
153     m_selectedModel = nullptr;
154 
155     switch (m_info->mode)
156     {
157     case Kompare::ShowingDiff:
158         // BUG: 107489 No common root because it is a multi directory relative path diff
159         // We need to detect this and create a different rootitem / or so or should we always add this?
160         // Trouble we run into is that the directories do not start with a /
161         // so we have an unknown top root dir
162         // Thinking some more about it i guess it is best to use "" as base and simply show some string
163         // like Unknown filesystem path as root text but only in the case of dirs starting without a /
164         srcBase = model->sourcePath();
165         destBase = model->destinationPath();
166         // FIXME: these tests will not work on windows, we need something else
167         if (srcBase[0] != QLatin1Char('/'))
168             srcBase.clear();
169         if (destBase[0] != QLatin1Char('/'))
170             destBase.clear();
171         break;
172     case Kompare::ComparingFiles:
173         srcBase  = model->sourcePath();
174         destBase = model->destinationPath();
175         break;
176     case Kompare::ComparingDirs:
177         srcBase = m_info->localSource;
178         if (!srcBase.endsWith(QLatin1Char('/')))
179             srcBase += QLatin1Char('/');
180         destBase = m_info->localDestination;
181         if (!destBase.endsWith(QLatin1Char('/')))
182             destBase += QLatin1Char('/');
183         break;
184     case Kompare::BlendingFile:
185     case Kompare::BlendingDir:
186     default:
187         qCDebug(KOMPARENAVVIEW) << "Oops needs to implement this..." ;
188     }
189 
190 //     qCDebug(KOMPARENAVVIEW) << "srcBase  = " << srcBase ;
191 //     qCDebug(KOMPARENAVVIEW) << "destBase = " << destBase ;
192 
193     m_srcRootItem  = new KDirLVI(m_srcDirTree, srcBase);
194     m_destRootItem = new KDirLVI(m_destDirTree, destBase);
195 
196     QString srcPath;
197     QString destPath;
198 
199     // Create the tree from the models
200     DiffModelListConstIterator modelIt = m_modelList->begin();
201     DiffModelListConstIterator mEnd    = m_modelList->end();
202 
203     for (; modelIt != mEnd; ++modelIt)
204     {
205         model = *modelIt;
206         srcPath  = model->sourcePath();
207         destPath = model->destinationPath();
208 
209         qCDebug(KOMPARENAVVIEW) << "srcPath  = " << srcPath  ;
210         qCDebug(KOMPARENAVVIEW) << "destPath = " << destPath ;
211         m_srcRootItem->addModel(srcPath, model, &m_modelToSrcDirItemDict);
212         m_destRootItem->addModel(destPath, model, &m_modelToDestDirItemDict);
213     }
214 //     m_srcDirTree->setSelected( m_srcDirTree->firstChild(), true );
215 }
216 
buildDirectoryTree()217 void KompareNavTreePart::buildDirectoryTree()
218 {
219 // FIXME: afaict this can be deleted
220 //     qCDebug(KOMPARENAVVIEW) << "BuildDirTree called" ;
221 }
222 
compareFromEndAndReturnSame(const QString & string1,const QString & string2)223 QString KompareNavTreePart::compareFromEndAndReturnSame(
224     const QString& string1,
225     const QString& string2)
226 {
227     QString result;
228 
229     int srcLen = string1.length();
230     int destLen = string2.length();
231 
232     while (srcLen != 0 && destLen != 0)
233     {
234         if (string1[--srcLen] == string2[--destLen])
235             result.prepend(string1[srcLen]);
236         else
237             break;
238     }
239 
240     if (srcLen != 0 && destLen != 0 && result.startsWith(QLatin1Char('/')))
241         result = result.remove(0, 1);   // strip leading /, we need it later
242 
243     return result;
244 }
245 
slotSetSelection(const DiffModel * model,const Difference * diff)246 void KompareNavTreePart::slotSetSelection(const DiffModel* model, const Difference* diff)
247 {
248     qCDebug(KOMPARENAVVIEW) << "KompareNavTreePart::slotSetSelection model = " << model << ", diff = " << diff ;
249     if (model == m_selectedModel)
250     {
251         // model is the same, so no need to update that...
252         if (diff != m_selectedDifference)
253         {
254             m_selectedDifference = diff;
255             setSelectedDifference(diff);
256         }
257         return;
258     }
259 
260     // model is different so we need to find the right dirs, file and changeitems to select
261     // if m_selectedModel == NULL then everything needs to be done as well
262     if (!m_selectedModel || model->sourcePath() != m_selectedModel->sourcePath())
263     {   // dirs are different, so we need to update the dirviews as well
264         m_selectedModel = model;
265         m_selectedDifference = diff;
266 
267         setSelectedDir(model);
268         setSelectedFile(model);
269         setSelectedDifference(diff);
270         return;
271     }
272 
273     if (!m_selectedModel || model->sourceFile() != m_selectedModel->sourceFile())
274     {
275         m_selectedModel = model;
276         setSelectedFile(model);
277 
278         m_selectedDifference = diff;
279         setSelectedDifference(diff);
280     }
281 }
282 
setSelectedDir(const DiffModel * model)283 void KompareNavTreePart::setSelectedDir(const DiffModel* model)
284 {
285     KDirLVI* currentDir;
286     currentDir = m_modelToSrcDirItemDict[ model ];
287     qCDebug(KOMPARENAVVIEW) << "Manually setting selection in srcdirtree with currentDir = " << currentDir ;
288     m_srcDirTree->blockSignals(true);
289     m_srcDirTree->setCurrentItem(currentDir);
290     m_srcDirTree->scrollToItem(currentDir);
291     m_srcDirTree->blockSignals(false);
292 
293     currentDir = m_modelToDestDirItemDict[ model ];
294     qCDebug(KOMPARENAVVIEW) << "Manually setting selection in destdirtree with currentDir = " << currentDir ;
295     m_destDirTree->blockSignals(true);
296     m_destDirTree->setCurrentItem(currentDir);
297     m_destDirTree->scrollToItem(currentDir);
298     m_destDirTree->blockSignals(false);
299 
300     m_fileList->blockSignals(true);
301     currentDir->fillFileList(m_fileList, &m_modelToFileItemDict);
302     m_fileList->blockSignals(false);
303 }
304 
setSelectedFile(const DiffModel * model)305 void KompareNavTreePart::setSelectedFile(const DiffModel* model)
306 {
307     KFileLVI* currentFile;
308     currentFile = m_modelToFileItemDict[ model ];
309     qCDebug(KOMPARENAVVIEW) << "Manually setting selection in filelist" ;
310     m_fileList->blockSignals(true);
311     m_fileList->setCurrentItem(currentFile);
312     m_fileList->scrollToItem(currentFile);
313     m_fileList->blockSignals(false);
314 
315     m_changesList->blockSignals(true);
316     currentFile->fillChangesList(m_changesList, &m_diffToChangeItemDict);
317     m_changesList->blockSignals(false);
318 }
319 
setSelectedDifference(const Difference * diff)320 void KompareNavTreePart::setSelectedDifference(const Difference* diff)
321 {
322     KChangeLVI* currentDiff;
323     currentDiff = m_diffToChangeItemDict[ diff ];
324     qCDebug(KOMPARENAVVIEW) << "Manually setting selection in changeslist to " << currentDiff ;
325     m_changesList->blockSignals(true);
326     m_changesList->setCurrentItem(currentDiff);
327     m_changesList->scrollToItem(currentDiff);
328     m_changesList->blockSignals(false);
329 }
330 
slotSetSelection(const Difference * diff)331 void KompareNavTreePart::slotSetSelection(const Difference* diff)
332 {
333 //     qCDebug(KOMPARENAVVIEW) << "Scotty i need more power !!" ;
334     if (m_selectedDifference != diff)
335     {
336 //         qCDebug(KOMPARENAVVIEW) << "But sir, i am giving you all she's got" ;
337         m_selectedDifference = diff;
338         setSelectedDifference(diff);
339     }
340 }
341 
slotSrcDirTreeSelectionChanged(QTreeWidgetItem * item)342 void KompareNavTreePart::slotSrcDirTreeSelectionChanged(QTreeWidgetItem* item)
343 {
344     if (!item)
345         return;
346 
347     qCDebug(KOMPARENAVVIEW) << "Sent by the sourceDirectoryTree with item = " << item ;
348     m_srcDirTree->scrollToItem(item);
349     KDirLVI* dir = static_cast<KDirLVI*>(item);
350     // order the dest tree view to set its selected item to the same as here
351     QString path;
352     // We start with an empty path and after the call path contains the full path
353     path = dir->fullPath(path);
354     KDirLVI* selItem = m_destRootItem->setSelected(path);
355     m_destDirTree->blockSignals(true);
356     m_destDirTree->setCurrentItem(selItem);
357     m_destDirTree->scrollToItem(selItem);
358     m_destDirTree->blockSignals(false);
359     // fill the changes list
360     dir->fillFileList(m_fileList, &m_modelToFileItemDict);
361 }
362 
slotDestDirTreeSelectionChanged(QTreeWidgetItem * item)363 void KompareNavTreePart::slotDestDirTreeSelectionChanged(QTreeWidgetItem* item)
364 {
365     if (!item)
366         return;
367 
368     qCDebug(KOMPARENAVVIEW) << "Sent by the destinationDirectoryTree with item = " << item ;
369     m_destDirTree->scrollToItem(item);
370     KDirLVI* dir = static_cast<KDirLVI*>(item);
371     // order the src tree view to set its selected item to the same as here
372     QString path;
373     // We start with an empty path and after the call path contains the full path
374     path = dir->fullPath(path);
375     KDirLVI* selItem = m_srcRootItem->setSelected(path);
376     m_srcDirTree->blockSignals(true);
377     m_srcDirTree->setCurrentItem(selItem);
378     m_srcDirTree->scrollToItem(selItem);
379     m_srcDirTree->blockSignals(false);
380     // fill the changes list
381     dir->fillFileList(m_fileList, &m_modelToFileItemDict);
382 }
383 
slotFileListSelectionChanged(QTreeWidgetItem * item)384 void KompareNavTreePart::slotFileListSelectionChanged(QTreeWidgetItem* item)
385 {
386     if (!item)
387         return;
388 
389     qCDebug(KOMPARENAVVIEW) << "Sent by the fileList with item = " << item ;
390 
391     KFileLVI* file = static_cast<KFileLVI*>(item);
392     m_selectedModel = file->model();
393     m_changesList->blockSignals(true);
394     file->fillChangesList(m_changesList, &m_diffToChangeItemDict);
395     m_changesList->blockSignals(false);
396 
397     if (m_changesList->currentItem())
398     {
399         // FIXME: This is ugly...
400         m_selectedDifference = (static_cast<KChangeLVI*>(m_changesList->currentItem()))->difference();
401     }
402 
403     Q_EMIT selectionChanged(m_selectedModel, m_selectedDifference);
404 }
405 
slotChangesListSelectionChanged(QTreeWidgetItem * item)406 void KompareNavTreePart::slotChangesListSelectionChanged(QTreeWidgetItem* item)
407 {
408     if (!item)
409         return;
410 
411     qCDebug(KOMPARENAVVIEW) << "Sent by the changesList" ;
412 
413     KChangeLVI* change = static_cast<KChangeLVI*>(item);
414     m_selectedDifference = change->difference();
415 
416     Q_EMIT selectionChanged(m_selectedDifference);
417 }
418 
slotApplyDifference(bool)419 void KompareNavTreePart::slotApplyDifference(bool /*apply*/)
420 {
421     KChangeLVI* clvi = m_diffToChangeItemDict[m_selectedDifference];
422     if (clvi)
423         clvi->setDifferenceText();
424 }
425 
slotApplyAllDifferences(bool)426 void KompareNavTreePart::slotApplyAllDifferences(bool /*apply*/)
427 {
428     QHash<const Diff2::Difference*, KChangeLVI*>::ConstIterator it = m_diffToChangeItemDict.constBegin();
429     QHash<const Diff2::Difference*, KChangeLVI*>::ConstIterator end = m_diffToChangeItemDict.constEnd();
430 
431     qCDebug(KOMPARENAVVIEW) << "m_diffToChangeItemDict.count() = " << m_diffToChangeItemDict.count() ;
432 
433     for (; it != end ; ++it)
434     {
435         it.value()->setDifferenceText();
436     }
437 }
438 
slotApplyDifference(const Difference * diff,bool)439 void KompareNavTreePart::slotApplyDifference(const Difference* diff, bool /*apply*/)
440 {
441     // this applies to the currently selected difference
442     KChangeLVI* clvi = m_diffToChangeItemDict[diff];
443     if (clvi)
444         clvi->setDifferenceText();
445 }
446 
setDifferenceText()447 void KChangeLVI::setDifferenceText()
448 {
449     QString text;
450     switch (m_difference->type()) {
451     case Difference::Change:
452         // Shouldn't this simply be diff->sourceLineCount() ?
453         // because you change the _number of lines_ lines in source, not in dest
454         if (m_difference->applied())
455             text = i18np("Applied: Changes made to %1 line undone", "Applied: Changes made to %1 lines undone",
456                          m_difference->sourceLineCount());
457         else
458             text = i18np("Changed %1 line", "Changed %1 lines",
459                          m_difference->sourceLineCount());
460         break;
461     case Difference::Insert:
462         if (m_difference->applied())
463             text = i18np("Applied: Insertion of %1 line undone", "Applied: Insertion of %1 lines undone",
464                          m_difference->destinationLineCount());
465         else
466             text = i18np("Inserted %1 line", "Inserted %1 lines",
467                          m_difference->destinationLineCount());
468         break;
469     case Difference::Delete:
470         if (m_difference->applied())
471             text = i18np("Applied: Deletion of %1 line undone", "Applied: Deletion of %1 lines undone",
472                          m_difference->sourceLineCount());
473         else
474             text = i18np("Deleted %1 line", "Deleted %1 lines",
475                          m_difference->sourceLineCount());
476         break;
477     default:
478         qCDebug(KOMPARENAVVIEW) << "Unknown or Unchanged enum value when checking for diff->type() in KChangeLVI's constructor" ;
479         text.clear();
480     }
481 
482     setText(2, text);
483 }
484 
KChangeLVI(QTreeWidget * parent,Difference * diff)485 KChangeLVI::KChangeLVI(QTreeWidget* parent, Difference* diff) : QTreeWidgetItem(parent)
486 {
487     m_difference = diff;
488 
489     setText(0, QString::number(diff->sourceLineNumber()));
490     setText(1, QString::number(diff->destinationLineNumber()));
491 
492     setDifferenceText();
493 }
494 
operator <(const QTreeWidgetItem & item) const495 bool KChangeLVI::operator<(const QTreeWidgetItem& item) const
496 {
497     int column = treeWidget()->sortColumn();
498     QString text1 = text(column);
499     QString text2 = item.text(column);
500 
501     // Compare the numbers.
502     if (column < 2 && text1.length() != text2.length())
503         return text1.length() < text2.length();
504     return text1 < text2;
505 }
506 
~KChangeLVI()507 KChangeLVI::~KChangeLVI()
508 {
509 }
510 
KFileLVI(QTreeWidget * parent,DiffModel * model)511 KFileLVI::KFileLVI(QTreeWidget* parent, DiffModel* model) : QTreeWidgetItem(parent)
512 {
513     m_model = model;
514 
515     QString src = model->sourceFile();
516     QString dst = model->destinationFile();
517 
518     setText(0, src);
519     setText(1, dst);
520     setIcon(0, QIcon::fromTheme(getIcon(src)));
521     setIcon(1, QIcon::fromTheme(getIcon(dst)));
522 }
523 
hasExtension(const QString & extensions,const QString & fileName)524 bool KFileLVI::hasExtension(const QString& extensions, const QString& fileName)
525 {
526     const QStringList extList = extensions.split(QLatin1Char(' '));
527     for (const QString& ext : extList) {
528         if (fileName.endsWith(ext, Qt::CaseInsensitive)) {
529             return true;
530         }
531     }
532     return false;
533 }
534 
getIcon(const QString & fileName)535 const QString KFileLVI::getIcon(const QString& fileName)
536 {
537     // C++, C
538     if (hasExtension(QStringLiteral(".h .hpp"), fileName)) {
539         return QStringLiteral("text-x-c++hdr");
540     }
541     if (hasExtension(QStringLiteral(".cpp"), fileName)) {
542         return QStringLiteral("text-x-c++src");
543     }
544     if (hasExtension(QStringLiteral(".c"), fileName)) {
545         return QStringLiteral("text-x-csrc");
546     }
547     // Python
548     if (hasExtension(QStringLiteral(".py .pyw"), fileName)) {
549         return QStringLiteral("text-x-python");
550     }
551     // C#
552     if (hasExtension(QStringLiteral(".cs"), fileName)) {
553         return QStringLiteral("text-x-csharp");
554     }
555     // Objective-C
556     if (hasExtension(QStringLiteral(".m"), fileName)) {
557         return QStringLiteral("text-x-objcsrc");
558     }
559     // Java
560     if (hasExtension(QStringLiteral(".java"), fileName)) {
561         return QStringLiteral("text-x-java");
562     }
563     // Script
564     if (hasExtension(QStringLiteral(".sh"), fileName)) {
565         return QStringLiteral("text-x-script");
566     }
567     // Makefile
568     if (hasExtension(QStringLiteral(".cmake Makefile"), fileName)) {
569         return QStringLiteral("text-x-makefile");
570     }
571     // Ada
572     if (hasExtension(QStringLiteral(".ada .ads .adb"), fileName)) {
573         return QStringLiteral("text-x-adasrc");
574     }
575     // Pascal
576     if (hasExtension(QStringLiteral(".pas"), fileName)) {
577         return QStringLiteral("text-x-pascal");
578     }
579     // Patch
580     if (hasExtension(QStringLiteral(".diff"), fileName)) {
581         return QStringLiteral("text-x-patch");
582     }
583     // Tcl
584     if (hasExtension(QStringLiteral(".tcl"), fileName)) {
585         return QStringLiteral("text-x-tcl");
586     }
587     // Text
588     if (hasExtension(QStringLiteral(".txt"), fileName)) {
589         return QStringLiteral("text-plain");
590     }
591     // Xml
592     if (hasExtension(QStringLiteral(".xml"), fileName)) {
593         return QStringLiteral("text-xml");
594     }
595     // unknown or no file extension
596     return QStringLiteral("text-plain");
597 }
598 
fillChangesList(QTreeWidget * changesList,QHash<const Diff2::Difference *,KChangeLVI * > * diffToChangeItemDict)599 void KFileLVI::fillChangesList(QTreeWidget* changesList, QHash<const Diff2::Difference*, KChangeLVI*>* diffToChangeItemDict)
600 {
601     changesList->clear();
602     diffToChangeItemDict->clear();
603 
604     DifferenceListConstIterator diffIt = m_model->differences()->constBegin();
605     DifferenceListConstIterator dEnd   = m_model->differences()->constEnd();
606 
607     for (; diffIt != dEnd; ++diffIt)
608     {
609         KChangeLVI* change = new KChangeLVI(changesList, *diffIt);
610         diffToChangeItemDict->insert(*diffIt, change);
611     }
612 
613     changesList->setCurrentItem(changesList->topLevelItem(0));
614 }
615 
~KFileLVI()616 KFileLVI::~KFileLVI()
617 {
618 }
619 
KDirLVI(QTreeWidget * parent,const QString & dir)620 KDirLVI::KDirLVI(QTreeWidget* parent, const QString& dir) : QTreeWidgetItem(parent)
621 {
622 //     qCDebug(KOMPARENAVVIEW) << "KDirLVI (QTreeWidget) constructor called with dir = " << dir ;
623     m_rootItem = true;
624     m_dirName = dir;
625     setIcon(0, QIcon::fromTheme(QStringLiteral("folder")));
626     setExpanded(true);
627     if (m_dirName.isEmpty())
628         setText(0, i18nc("@item directory name not known", "Unknown"));
629     else
630         setText(0, m_dirName);
631 }
632 
KDirLVI(KDirLVI * parent,const QString & dir)633 KDirLVI::KDirLVI(KDirLVI* parent, const QString& dir) : QTreeWidgetItem(parent)
634 {
635 //     qCDebug(KOMPARENAVVIEW) << "KDirLVI (KDirLVI) constructor called with dir = " << dir ;
636     m_rootItem = false;
637     m_dirName = dir;
638     setIcon(0, QIcon::fromTheme(QStringLiteral("folder")));
639     setExpanded(true);
640     setText(0, m_dirName);
641 }
642 
643 // addModel always removes it own path from the beginning
addModel(QString & path,DiffModel * model,QHash<const Diff2::DiffModel *,KDirLVI * > * modelToDirItemDict)644 void KDirLVI::addModel(QString& path, DiffModel* model, QHash<const Diff2::DiffModel*, KDirLVI*>* modelToDirItemDict)
645 {
646 //     qCDebug(KOMPARENAVVIEW) << "KDirLVI::addModel called with path = " << path << " from KDirLVI with m_dirName = " << m_dirName ;
647 
648     if (!m_dirName.isEmpty())
649     {
650         if (path.indexOf(m_dirName) > -1)
651             path = path.remove(path.indexOf(m_dirName), m_dirName.length());
652     }
653 
654 //     qCDebug(KOMPARENAVVIEW) << "Path after removal of own dir (\"" << m_dirName << "\") = " << path ;
655 
656     if (path.isEmpty()) {
657         m_modelList.append(model);
658         modelToDirItemDict->insert(model, this);
659         return;
660     }
661 
662     KDirLVI* child;
663 
664     QString dir = path.mid(0, path.indexOf(QLatin1Char('/'), 0) + 1);
665     child = findChild(dir);
666     if (!child)
667     {
668         // does not exist yet so make it
669 //         qCDebug(KOMPARENAVVIEW) << "KDirLVI::addModel creating new KDirLVI because not found" ;
670         child = new KDirLVI(this, dir);
671     }
672 
673     child->addModel(path, model, modelToDirItemDict);
674 }
675 
findChild(const QString & dir)676 KDirLVI* KDirLVI::findChild(const QString& dir)
677 {
678 //     qCDebug(KOMPARENAVVIEW) << "KDirLVI::findChild called with dir = " << dir ;
679     KDirLVI* child;
680     if ((child = static_cast<KDirLVI*>(this->child(0))) != nullptr)
681     {   // has children, check if dir already exists, if so addModel
682         QTreeWidgetItemIterator it(child);
683         while (*it) {
684             child = static_cast<KDirLVI*>(*it);
685 
686             if (dir == child->dirName())
687                 return child;
688             ++it;
689         }
690     }
691 
692     return nullptr;
693 }
694 
fillFileList(QTreeWidget * fileList,QHash<const Diff2::DiffModel *,KFileLVI * > * modelToFileItemDict)695 void KDirLVI::fillFileList(QTreeWidget* fileList, QHash<const Diff2::DiffModel*, KFileLVI*>* modelToFileItemDict)
696 {
697     fileList->clear();
698 
699     DiffModelListIterator modelIt = m_modelList.begin();
700     DiffModelListIterator mEnd    = m_modelList.end();
701     for (; modelIt != mEnd; ++modelIt)
702     {
703         KFileLVI* file = new KFileLVI(fileList, *modelIt);
704         modelToFileItemDict->insert(*modelIt, file);
705     }
706 
707     fileList->setCurrentItem(fileList->topLevelItem(0));
708 }
709 
fullPath(QString & path)710 QString KDirLVI::fullPath(QString& path)
711 {
712 //     if (!path.isEmpty())
713 //         qCDebug(KOMPARENAVVIEW) << "KDirLVI::fullPath called with path = " << path ;
714 //     else
715 //         qCDebug(KOMPARENAVVIEW) << "KDirLVI::fullPath called with empty path..." ;
716 
717     if (m_rootItem)   // don't bother adding rootItem's dir...
718         return path;
719 
720     path = path.prepend(m_dirName);
721 
722     KDirLVI* lviParent = dynamic_cast<KDirLVI*>(parent());
723     if (lviParent)
724     {
725         path = lviParent->fullPath(path);
726     }
727 
728     return path;
729 }
730 
setSelected(const QString & _dir)731 KDirLVI* KDirLVI::setSelected(const QString& _dir)
732 {
733     QString dir(_dir);
734 //     qCDebug(KOMPARENAVVIEW) << "KDirLVI::setSelected called with dir = " << dir ;
735 
736     // root item's dirName is never taken into account... remember that
737     if (!m_rootItem)
738     {
739         dir = dir.remove(0, m_dirName.length());
740     }
741 
742     if (dir.isEmpty())
743     {
744         return this;
745     }
746     KDirLVI* child = static_cast<KDirLVI*>(this->child(0));
747     if (!child)
748         return nullptr;
749 
750     QTreeWidgetItemIterator it(child);
751     while (*it) {
752         child = static_cast<KDirLVI*>(*it);
753 
754         if (dir.startsWith(child->dirName()))
755             return child->setSelected(dir);
756         ++it;
757     }
758 
759     return nullptr;
760 }
761 
~KDirLVI()762 KDirLVI::~KDirLVI()
763 {
764     m_modelList.clear();
765 }
766 
767 K_PLUGIN_FACTORY_WITH_JSON(KompareNavTreePartFactory, "komparenavtreepart.json",
768                            registerPlugin<KompareNavTreePart>();)
769 
770 #include "komparenavtreepart.moc"
771