1 /*
2  * KDiff3 - Text Diff And Merge Tool
3  *
4  * SPDX-FileCopyrightText: 2002-2011 Joachim Eibl, joachim.eibl at gmx.de
5  * SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com
6  * SPDX-License-Identifier: GPL-2.0-or-later
7 */
8 
9 
10 #include "directorymergewindow.h"
11 
12 #include "DirectoryInfo.h"
13 #include "MergeFileInfos.h"
14 #include "PixMapUtils.h"
15 #include "Utils.h"
16 #include "guiutils.h"
17 #include "kdiff3.h"
18 #include "options.h"
19 #include "progress.h"
20 
21 #include <algorithm>
22 #include <map>
23 #include <vector>
24 
25 #include <QAction>
26 #include <QApplication>
27 #include <QDialogButtonBox>
28 #include <QDir>
29 #include <QElapsedTimer>
30 #include <QFileDialog>
31 #include <QImage>
32 #include <QKeyEvent>
33 #include <QLabel>
34 #include <QLayout>
35 #include <QMenu>
36 #include <QPainter>
37 #include <QPushButton>
38 #include <QRegExp>
39 #include <QSplitter>
40 #include <QStyledItemDelegate>
41 #include <QTextEdit>
42 #include <QTextStream>
43 
44 #include <KLocalizedString>
45 #include <KMessageBox>
46 #include <KToggleAction>
47 
48 struct DirectoryMergeWindow::t_ItemInfo {
49     bool bExpanded;
50     bool bOperationComplete;
51     QString status;
52     e_MergeOperation eMergeOperation;
53 };
54 
55 class StatusInfo : public QDialog
56 {
57   private:
58     QTextEdit* m_pTextEdit;
59 
60   public:
StatusInfo(QWidget * pParent)61     explicit StatusInfo(QWidget* pParent): QDialog(pParent)
62     {
63         QVBoxLayout* pVLayout = new QVBoxLayout(this);
64         m_pTextEdit = new QTextEdit(this);
65         pVLayout->addWidget(m_pTextEdit);
66         setObjectName("StatusInfo");
67         setWindowFlags(Qt::Dialog);
68         m_pTextEdit->setWordWrapMode(QTextOption::NoWrap);
69         m_pTextEdit->setReadOnly(true);
70         QDialogButtonBox* box = new QDialogButtonBox(QDialogButtonBox::Close, this);
71         chk_connect(box, &QDialogButtonBox::rejected, this, &QDialog::accept);
72         pVLayout->addWidget(box);
73     }
74 
isEmpty()75     bool isEmpty()
76     {
77         return m_pTextEdit->toPlainText().isEmpty();
78     }
79 
addText(const QString & s)80     void addText(const QString& s)
81     {
82         m_pTextEdit->append(s);
83     }
84 
clear()85     void clear()
86     {
87         m_pTextEdit->clear();
88     }
89 
setVisible(bool bVisible)90     void setVisible(bool bVisible) override
91     {
92         if(bVisible)
93         {
94             m_pTextEdit->moveCursor(QTextCursor::End);
95             m_pTextEdit->moveCursor(QTextCursor::StartOfLine);
96             m_pTextEdit->ensureCursorVisible();
97         }
98 
99         QDialog::setVisible(bVisible);
100         if(bVisible)
101             setWindowState(windowState() | Qt::WindowMaximized);
102     }
103 };
104 
105 enum Columns
106 {
107     s_NameCol = 0,
108     s_ACol = 1,
109     s_BCol = 2,
110     s_CCol = 3,
111     s_OpCol = 4,
112     s_OpStatusCol = 5,
113     s_UnsolvedCol = 6, // Number of unsolved conflicts (for 3 input files)
114     s_SolvedCol = 7,   // Number of auto-solvable conflicts (for 3 input files)
115     s_NonWhiteCol = 8, // Number of nonwhite deltas (for 2 input files)
116     s_WhiteCol = 9     // Number of white deltas (for 2 input files)
117 };
118 
119 static Qt::CaseSensitivity s_eCaseSensitivity = Qt::CaseSensitive;
120 
121 class DirectoryMergeWindow::DirectoryMergeWindowPrivate : public QAbstractItemModel
122 {
123     friend class DirMergeItem;
124 
125   public:
DirectoryMergeWindowPrivate(DirectoryMergeWindow * pDMW)126     explicit DirectoryMergeWindowPrivate(DirectoryMergeWindow* pDMW)
127     {
128         mWindow = pDMW;
129         m_pStatusInfo = new StatusInfo(mWindow);
130         m_pStatusInfo->hide();
131     }
~DirectoryMergeWindowPrivate()132     ~DirectoryMergeWindowPrivate() override
133     {
134         delete m_pRoot;
135     }
136 
137     bool init(const QSharedPointer<DirectoryInfo>& dirInfo, bool bDirectoryMerge, bool bReload);
138 
139     // Implement QAbstractItemModel
140     QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
141 
142     //Qt::ItemFlags flags ( const QModelIndex & index ) const
parent(const QModelIndex & index) const143     QModelIndex parent(const QModelIndex& index) const override
144     {
145         MergeFileInfos* pMFI = getMFI(index);
146         if(pMFI == nullptr || pMFI == m_pRoot || pMFI->parent() == m_pRoot)
147             return QModelIndex();
148 
149         MergeFileInfos* pParentsParent = pMFI->parent()->parent();
150         return createIndex(pParentsParent->children().indexOf(pMFI->parent()), 0, pMFI->parent());
151     }
152 
rowCount(const QModelIndex & parent=QModelIndex ()) const153     int rowCount(const QModelIndex& parent = QModelIndex()) const override
154     {
155         MergeFileInfos* pParentMFI = getMFI(parent);
156         if(pParentMFI != nullptr)
157             return pParentMFI->children().count();
158         else
159             return m_pRoot->children().count();
160     }
161 
columnCount(const QModelIndex &) const162     int columnCount(const QModelIndex& /*parent*/) const override
163     {
164         return 10;
165     }
166 
index(int row,int column,const QModelIndex & parent) const167     QModelIndex index(int row, int column, const QModelIndex& parent) const override
168     {
169         MergeFileInfos* pParentMFI = getMFI(parent);
170         if(pParentMFI == nullptr && row < m_pRoot->children().count())
171             return createIndex(row, column, m_pRoot->children()[row]);
172         else if(pParentMFI != nullptr && row < pParentMFI->children().count())
173             return createIndex(row, column, pParentMFI->children()[row]);
174         else
175             return QModelIndex();
176     }
177 
178     QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
179 
180     void sort(int column, Qt::SortOrder order) override;
181 
182     void selectItemAndColumn(const QModelIndex& mi, bool bContextMenu);
183 
setOpStatus(const QModelIndex & mi,e_OperationStatus eOpStatus)184     void setOpStatus(const QModelIndex& mi, e_OperationStatus eOpStatus)
185     {
186         if(MergeFileInfos* pMFI = getMFI(mi))
187         {
188             pMFI->setOpStatus(eOpStatus);
189             Q_EMIT dataChanged(mi, mi);
190         }
191     }
192 
193     QModelIndex nextSibling(const QModelIndex& mi);
194 
195     // private data and helper methods
getMFI(const QModelIndex & mi) const196     MergeFileInfos* getMFI(const QModelIndex& mi) const
197     {
198         if(mi.isValid())
199             return (MergeFileInfos*)mi.internalPointer();
200         else
201             return nullptr;
202     }
203 
isThreeWay() const204     bool isThreeWay() const
205     {
206         if(rootMFI() == nullptr) return false;
207         return rootMFI()->isThreeWay();
208     }
209 
rootMFI() const210     MergeFileInfos* rootMFI() const { return m_pRoot; }
211 
212     void calcDirStatus(bool bThreeDirs, const QModelIndex& mi,
213                        int& nofFiles, int& nofDirs, int& nofEqualFiles, int& nofManualMerges);
214 
215     void mergeContinue(bool bStart, bool bVerbose);
216 
217     void prepareListView(ProgressProxy& pp);
218     void calcSuggestedOperation(const QModelIndex& mi, e_MergeOperation eDefaultMergeOp);
219     void setAllMergeOperations(e_MergeOperation eDefaultOperation);
220 
221     bool canContinue();
222     QModelIndex treeIterator(QModelIndex mi, bool bVisitChildren = true, bool bFindInvisible = false);
223     void prepareMergeStart(const QModelIndex& miBegin, const QModelIndex& miEnd, bool bVerbose);
224     bool executeMergeOperation(MergeFileInfos& mfi, bool& bSingleFileMerge);
225 
226     void scanDirectory(const QString& dirName, t_DirectoryList& dirList);
227     void scanLocalDirectory(const QString& dirName, t_DirectoryList& dirList);
228 
229     void setMergeOperation(const QModelIndex& mi, e_MergeOperation eMergeOp, bool bRecursive = true);
230     bool isDir(const QModelIndex& mi) const;
231     QString getFileName(const QModelIndex& mi) const;
232 
233     bool copyFLD(const QString& srcName, const QString& destName);
234     bool deleteFLD(const QString& name, bool bCreateBackup);
235     bool makeDir(const QString& name, bool bQuiet = false);
236     bool renameFLD(const QString& srcName, const QString& destName);
237     bool mergeFLD(const QString& nameA, const QString& nameB, const QString& nameC,
238                   const QString& nameDest, bool& bSingleFileMerge);
239 
240     void buildMergeMap(const QSharedPointer<DirectoryInfo>& dirInfo);
241 
242   private:
243     class FileKey
244     {
245       private:
246         const FileAccess* m_pFA;
247 
248       public:
FileKey(const FileAccess & fa)249         explicit FileKey(const FileAccess& fa)
250             : m_pFA(&fa) {}
251 
getParents(const FileAccess * pFA,const FileAccess * v[],quint32 maxSize) const252         quint32 getParents(const FileAccess* pFA, const FileAccess* v[], quint32 maxSize) const
253         {
254             quint32 s = 0;
255             for(s = 0; pFA->parent() != nullptr; pFA = pFA->parent(), ++s)
256             {
257                 if(s == maxSize)
258                     break;
259                 v[s] = pFA;
260             }
261             return s;
262         }
263 
264         // This is essentially the same as
265         // int r = filePath().compare( fa.filePath() )
266         // if ( r<0 ) return true;
267         // if ( r==0 ) return m_col < fa.m_col;
268         // return false;
operator <(const FileKey & fk) const269         bool operator<(const FileKey& fk) const
270         {
271             const FileAccess* v1[100];
272             const FileAccess* v2[100];
273             quint32 v1Size = getParents(m_pFA, v1, 100);
274             quint32 v2Size = getParents(fk.m_pFA, v2, 100);
275 
276             for(quint32 i = 0; i < v1Size && i < v2Size; ++i)
277             {
278                 int r = v1[v1Size - i - 1]->fileName().compare(v2[v2Size - i - 1]->fileName(), s_eCaseSensitivity);
279                 if(r < 0)
280                     return true;
281                 else if(r > 0)
282                     return false;
283             }
284 
285             return v1Size < v2Size;
286         }
287     };
288 
289     typedef QMap<FileKey, MergeFileInfos> t_fileMergeMap;
290 
291     MergeFileInfos* m_pRoot = new MergeFileInfos();
292 
293     t_fileMergeMap m_fileMergeMap;
294 
295   public:
296     DirectoryMergeWindow* mWindow;
297     QSharedPointer<Options> m_pOptions = nullptr;
298 
299     bool m_bFollowDirLinks = false;
300     bool m_bFollowFileLinks = false;
301     bool m_bSimulatedMergeStarted = false;
302     bool m_bRealMergeStarted = false;
303     bool m_bError = false;
304     bool m_bSyncMode = false;
305     bool m_bDirectoryMerge = false; // if true, then merge is the default operation, otherwise it's diff.
306     bool m_bCaseSensitive = true;
307     bool m_bUnfoldSubdirs = false;
308     bool m_bSkipDirStatus = false;
309     bool m_bScanning = false; // true while in init()
310 
311     DirectoryMergeInfo* m_pDirectoryMergeInfo = nullptr;
312     StatusInfo* m_pStatusInfo = nullptr;
313 
314     typedef std::list<QModelIndex> MergeItemList; // linked list
315     MergeItemList m_mergeItemList;
316     MergeItemList::iterator m_currentIndexForOperation;
317 
318     QModelIndex m_selection1Index;
319     QModelIndex m_selection2Index;
320     QModelIndex m_selection3Index;
321 
322     QPointer<QAction> m_pDirStartOperation;
323     QPointer<QAction> m_pDirRunOperationForCurrentItem;
324     QPointer<QAction> m_pDirCompareCurrent;
325     QPointer<QAction> m_pDirMergeCurrent;
326     QPointer<QAction> m_pDirRescan;
327     QPointer<QAction> m_pDirChooseAEverywhere;
328     QPointer<QAction> m_pDirChooseBEverywhere;
329     QPointer<QAction> m_pDirChooseCEverywhere;
330     QPointer<QAction> m_pDirAutoChoiceEverywhere;
331     QPointer<QAction> m_pDirDoNothingEverywhere;
332     QPointer<QAction> m_pDirFoldAll;
333     QPointer<QAction> m_pDirUnfoldAll;
334 
335     KToggleAction* m_pDirShowIdenticalFiles;
336     KToggleAction* m_pDirShowDifferentFiles;
337     KToggleAction* m_pDirShowFilesOnlyInA;
338     KToggleAction* m_pDirShowFilesOnlyInB;
339     KToggleAction* m_pDirShowFilesOnlyInC;
340 
341     KToggleAction* m_pDirSynchronizeDirectories;
342     KToggleAction* m_pDirChooseNewerFiles;
343 
344     QPointer<QAction> m_pDirCompareExplicit;
345     QPointer<QAction> m_pDirMergeExplicit;
346 
347     QPointer<QAction> m_pDirCurrentDoNothing;
348     QPointer<QAction> m_pDirCurrentChooseA;
349     QPointer<QAction> m_pDirCurrentChooseB;
350     QPointer<QAction> m_pDirCurrentChooseC;
351     QPointer<QAction> m_pDirCurrentMerge;
352     QPointer<QAction> m_pDirCurrentDelete;
353 
354     QPointer<QAction> m_pDirCurrentSyncDoNothing;
355     QPointer<QAction> m_pDirCurrentSyncCopyAToB;
356     QPointer<QAction> m_pDirCurrentSyncCopyBToA;
357     QPointer<QAction> m_pDirCurrentSyncDeleteA;
358     QPointer<QAction> m_pDirCurrentSyncDeleteB;
359     QPointer<QAction> m_pDirCurrentSyncDeleteAAndB;
360     QPointer<QAction> m_pDirCurrentSyncMergeToA;
361     QPointer<QAction> m_pDirCurrentSyncMergeToB;
362     QPointer<QAction> m_pDirCurrentSyncMergeToAAndB;
363 
364     QPointer<QAction> m_pDirSaveMergeState;
365     QPointer<QAction> m_pDirLoadMergeState;
366 };
367 
data(const QModelIndex & index,int role) const368 QVariant DirectoryMergeWindow::DirectoryMergeWindowPrivate::data(const QModelIndex& index, int role) const
369 {
370     MergeFileInfos* pMFI = getMFI(index);
371     if(pMFI)
372     {
373         if(role == Qt::DisplayRole)
374         {
375             switch(index.column())
376             {
377                 case s_NameCol:
378                     return QFileInfo(pMFI->subPath()).fileName();
379                 case s_ACol:
380                     return i18n("A");
381                 case s_BCol:
382                     return i18n("B");
383                 case s_CCol:
384                     return i18n("C");
385                 //case s_OpCol:       return i18n("Operation");
386                 //case s_OpStatusCol: return i18n("Status");
387                 case s_UnsolvedCol:
388                     return pMFI->diffStatus().getUnsolvedConflicts();
389                 case s_SolvedCol:
390                     return pMFI->diffStatus().getSolvedConflicts();
391                 case s_NonWhiteCol:
392                     return pMFI->diffStatus().getNonWhitespaceConflicts();
393                 case s_WhiteCol:
394                     return pMFI->diffStatus().getWhitespaceConflicts();
395                     //default :           return QVariant();
396             }
397 
398             if(s_OpCol == index.column())
399             {
400                 bool bDir = pMFI->hasDir();
401                 switch(pMFI->getOperation())
402                 {
403                     case eNoOperation:
404                         return "";
405                         break;
406                     case eCopyAToB:
407                         return i18n("Copy A to B");
408                         break;
409                     case eCopyBToA:
410                         return i18n("Copy B to A");
411                         break;
412                     case eDeleteA:
413                         return i18n("Delete A");
414                         break;
415                     case eDeleteB:
416                         return i18n("Delete B");
417                         break;
418                     case eDeleteAB:
419                         return i18n("Delete A & B");
420                         break;
421                     case eMergeToA:
422                         return i18n("Merge to A");
423                         break;
424                     case eMergeToB:
425                         return i18n("Merge to B");
426                         break;
427                     case eMergeToAB:
428                         return i18n("Merge to A & B");
429                         break;
430                     case eCopyAToDest:
431                         return i18n("A");
432                         break;
433                     case eCopyBToDest:
434                         return i18n("B");
435                         break;
436                     case eCopyCToDest:
437                         return i18n("C");
438                         break;
439                     case eDeleteFromDest:
440                         return i18n("Delete (if exists)");
441                         break;
442                     case eMergeABCToDest:
443                     case eMergeABToDest:
444                         return bDir ? i18n("Merge") : i18n("Merge (manual)");
445                         break;
446                     case eConflictingFileTypes:
447                         return i18n("Error: Conflicting File Types");
448                         break;
449                     case eChangedAndDeleted:
450                         return i18n("Error: Changed and Deleted");
451                         break;
452                     case eConflictingAges:
453                         return i18n("Error: Dates are equal but files are not.");
454                         break;
455                     default:
456                         Q_ASSERT(true);
457                         break;
458                 }
459             }
460             if(s_OpStatusCol == index.column())
461             {
462                 switch(pMFI->getOpStatus())
463                 {
464                     case eOpStatusNone:
465                         return "";
466                     case eOpStatusDone:
467                         return i18n("Done");
468                     case eOpStatusError:
469                         return i18n("Error");
470                     case eOpStatusSkipped:
471                         return i18n("Skipped.");
472                     case eOpStatusNotSaved:
473                         return i18n("Not saved.");
474                     case eOpStatusInProgress:
475                         return i18n("In progress...");
476                     case eOpStatusToDo:
477                         return i18n("To do.");
478                 }
479             }
480         }
481         else if(role == Qt::DecorationRole)
482         {
483             if(s_NameCol == index.column())
484             {
485                 return PixMapUtils::getOnePixmap(eAgeEnd, pMFI->hasLink(), pMFI->hasDir());
486             }
487 
488             if(s_ACol == index.column())
489             {
490                 return PixMapUtils::getOnePixmap(pMFI->getAgeA(), pMFI->isLinkA(), pMFI->isDirA());
491             }
492             if(s_BCol == index.column())
493             {
494                 return PixMapUtils::getOnePixmap(pMFI->getAgeB(), pMFI->isLinkB(), pMFI->isDirB());
495             }
496             if(s_CCol == index.column())
497             {
498                 return PixMapUtils::getOnePixmap(pMFI->getAgeC(), pMFI->isLinkC(), pMFI->isDirC());
499             }
500         }
501         else if(role == Qt::TextAlignmentRole)
502         {
503             if(s_UnsolvedCol == index.column() || s_SolvedCol == index.column() || s_NonWhiteCol == index.column() || s_WhiteCol == index.column())
504                 return Qt::AlignRight;
505         }
506     }
507     return QVariant();
508 }
509 
headerData(int section,Qt::Orientation orientation,int role) const510 QVariant DirectoryMergeWindow::DirectoryMergeWindowPrivate::headerData(int section, Qt::Orientation orientation, int role) const
511 {
512     if(orientation == Qt::Horizontal && section >= 0 && section < columnCount(QModelIndex()) && role == Qt::DisplayRole)
513     {
514         switch(section)
515         {
516             case s_NameCol:
517                 return i18n("Name");
518             case s_ACol:
519                 return i18n("A");
520             case s_BCol:
521                 return i18n("B");
522             case s_CCol:
523                 return i18n("C");
524             case s_OpCol:
525                 return i18n("Operation");
526             case s_OpStatusCol:
527                 return i18n("Status");
528             case s_UnsolvedCol:
529                 return i18n("Unsolved");
530             case s_SolvedCol:
531                 return i18n("Solved");
532             case s_NonWhiteCol:
533                 return i18n("Nonwhite");
534             case s_WhiteCol:
535                 return i18n("White");
536             default:
537                 return QVariant();
538         }
539     }
540     return QVariant();
541 }
542 
getIntFromIndex(const QModelIndex & index) const543 int DirectoryMergeWindow::getIntFromIndex(const QModelIndex& index) const
544 {
545     return index == d->m_selection1Index ? 1 : index == d->m_selection2Index ? 2 : index == d->m_selection3Index ? 3 : 0;
546 }
547 
getOptions() const548 const QSharedPointer<Options>& DirectoryMergeWindow::getOptions() const
549 {
550     return d->m_pOptions;
551 }
552 
553 // Previously  Q3ListViewItem::paintCell(p,cg,column,width,align);
554 class DirectoryMergeWindow::DirMergeItemDelegate : public QStyledItemDelegate
555 {
556   private:
557     DirectoryMergeWindow* m_pDMW;
getOptions() const558     const QSharedPointer<Options>& getOptions() const { return m_pDMW->getOptions(); }
559 
560   public:
DirMergeItemDelegate(DirectoryMergeWindow * pParent)561     explicit DirMergeItemDelegate(DirectoryMergeWindow* pParent)
562         : QStyledItemDelegate(pParent), m_pDMW(pParent)
563     {
564     }
paint(QPainter * thePainter,const QStyleOptionViewItem & option,const QModelIndex & index) const565     void paint(QPainter* thePainter, const QStyleOptionViewItem& option, const QModelIndex& index) const override
566     {
567         QtNumberType column = index.column();
568         if(column == s_ACol || column == s_BCol || column == s_CCol)
569         {
570             QVariant value = index.data(Qt::DecorationRole);
571             QPixmap icon;
572             if(value.isValid())
573             {
574                 if(value.type() == QVariant::Icon)
575                 {
576                     icon = qvariant_cast<QIcon>(value).pixmap(16, 16);
577                     //icon = qvariant_cast<QIcon>(value);
578                     //decorationRect = QRect(QPoint(0, 0), icon.actualSize(option.decorationSize, iconMode, iconState));
579                 }
580                 else
581                 {
582                     icon = qvariant_cast<QPixmap>(value);
583                     //decorationRect = QRect(QPoint(0, 0), option.decorationSize).intersected(pixmap.rect());
584                 }
585             }
586 
587             int x = option.rect.left();
588             int y = option.rect.top();
589             //QPixmap icon = value.value<QPixmap>(); //pixmap(column);
590             if(!icon.isNull())
591             {
592                 const auto dpr = thePainter->device()->devicePixelRatioF();
593                 const int w = qRound(icon.width() / dpr);
594                 const int h = qRound(icon.height() / dpr);
595                 int yOffset = (sizeHint(option, index).height() - h) / 2;
596                 thePainter->drawPixmap(x + 2, y + yOffset, icon);
597 
598                 int i = m_pDMW->getIntFromIndex(index);
599                 if(i != 0)
600                 {
601                     QColor c(i == 1 ? getOptions()->m_colorA : i == 2 ? getOptions()->m_colorB : getOptions()->m_colorC);
602                     thePainter->setPen(c); // highlight() );
603                     thePainter->drawRect(x + 2, y + yOffset, w, h);
604                     thePainter->setPen(QPen(c, 0, Qt::DotLine));
605                     thePainter->drawRect(x + 1, y + yOffset - 1, w + 2, h + 2);
606                     thePainter->setPen(Qt::white);
607                     QString s(QChar('A' + i - 1));
608 
609                     thePainter->drawText(x + 2 + (w - Utils::getHorizontalAdvance(thePainter->fontMetrics(), s)) / 2,
610                                          y + yOffset + (h + thePainter->fontMetrics().ascent()) / 2 - 1,
611                                          s);
612                 }
613                 else
614                 {
615                     thePainter->setPen(m_pDMW->palette().window().color());
616                     thePainter->drawRect(x + 1, y + yOffset - 1, w + 2, h + 2);
617                 }
618                 return;
619             }
620         }
621 
622         QStyleOptionViewItem option2 = option;
623         if(column >= s_UnsolvedCol)
624         {
625             option2.displayAlignment = Qt::AlignRight;
626         }
627         QStyledItemDelegate::paint(thePainter, option2, index);
628     }
sizeHint(const QStyleOptionViewItem & option,const QModelIndex & index) const629     QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const override
630     {
631         QSize sz = QStyledItemDelegate::sizeHint(option, index);
632         return sz.expandedTo(QSize(0, 18));
633     }
634 };
635 
DirectoryMergeWindow(QWidget * pParent,const QSharedPointer<Options> & pOptions)636 DirectoryMergeWindow::DirectoryMergeWindow(QWidget* pParent, const QSharedPointer<Options>& pOptions)
637     : QTreeView(pParent)
638 {
639     d = new DirectoryMergeWindowPrivate(this);
640     setModel(d);
641     setItemDelegate(new DirMergeItemDelegate(this));
642     chk_connect(this, &DirectoryMergeWindow::doubleClicked, this, &DirectoryMergeWindow::onDoubleClick);
643     chk_connect(this, &DirectoryMergeWindow::expanded, this, &DirectoryMergeWindow::onExpanded);
644 
645     d->m_pOptions = pOptions;
646 
647     setSortingEnabled(true);
648 }
649 
~DirectoryMergeWindow()650 DirectoryMergeWindow::~DirectoryMergeWindow()
651 {
652     delete d;
653 }
654 
setDirectoryMergeInfo(DirectoryMergeInfo * p)655 void DirectoryMergeWindow::setDirectoryMergeInfo(DirectoryMergeInfo* p)
656 {
657     d->m_pDirectoryMergeInfo = p;
658 }
isDirectoryMergeInProgress()659 bool DirectoryMergeWindow::isDirectoryMergeInProgress()
660 {
661     return d->m_bRealMergeStarted;
662 }
isSyncMode()663 bool DirectoryMergeWindow::isSyncMode()
664 {
665     return d->m_bSyncMode;
666 }
isScanning()667 bool DirectoryMergeWindow::isScanning()
668 {
669     return d->m_bScanning;
670 }
671 
totalColumnWidth()672 int DirectoryMergeWindow::totalColumnWidth()
673 {
674     int w = 0;
675     for(int i = 0; i < s_OpStatusCol; ++i)
676     {
677         w += columnWidth(i);
678     }
679     return w;
680 }
681 
reload()682 void DirectoryMergeWindow::reload()
683 {
684     if(isDirectoryMergeInProgress())
685     {
686         int result = KMessageBox::warningYesNo(this,
687                                                i18n("You are currently doing a folder merge. Are you sure, you want to abort the merge and rescan the folder?"),
688                                                i18n("Warning"),
689                                                KGuiItem(i18n("Rescan")),
690                                                KGuiItem(i18n("Continue Merging")));
691         if(result != KMessageBox::Yes)
692             return;
693     }
694 
695     init(gDirInfo, true);
696     //fix file visibilities after reload or menu will be out of sync with display if changed from defaults.
697     updateFileVisibilities();
698 }
699 
calcDirStatus(bool bThreeDirs,const QModelIndex & mi,int & nofFiles,int & nofDirs,int & nofEqualFiles,int & nofManualMerges)700 void DirectoryMergeWindow::DirectoryMergeWindowPrivate::calcDirStatus(bool bThreeDirs, const QModelIndex& mi,
701                                                                       int& nofFiles, int& nofDirs, int& nofEqualFiles, int& nofManualMerges)
702 {
703     const MergeFileInfos* pMFI = getMFI(mi);
704     if(pMFI->hasDir())
705     {
706         ++nofDirs;
707     }
708     else
709     {
710         ++nofFiles;
711         if(pMFI->isEqualAB() && (!bThreeDirs || pMFI->isEqualAC()))
712         {
713             ++nofEqualFiles;
714         }
715         else
716         {
717             if(pMFI->getOperation() == eMergeABCToDest || pMFI->getOperation() == eMergeABToDest)
718                 ++nofManualMerges;
719         }
720     }
721     for(int childIdx = 0; childIdx < rowCount(mi); ++childIdx)
722         calcDirStatus(bThreeDirs, index(childIdx, 0, mi), nofFiles, nofDirs, nofEqualFiles, nofManualMerges);
723 }
724 
init(const QSharedPointer<DirectoryInfo> & dirInfo,bool bDirectoryMerge,bool bReload)725 bool DirectoryMergeWindow::init(
726     const QSharedPointer<DirectoryInfo>& dirInfo,
727     bool bDirectoryMerge,
728     bool bReload)
729 {
730     return d->init(dirInfo, bDirectoryMerge, bReload);
731 }
732 
buildMergeMap(const QSharedPointer<DirectoryInfo> & dirInfo)733 void DirectoryMergeWindow::DirectoryMergeWindowPrivate::buildMergeMap(const QSharedPointer<DirectoryInfo>& dirInfo)
734 {
735     t_DirectoryList::iterator dirIterator;
736 
737     if(dirInfo->dirA().isValid())
738     {
739         for(dirIterator = dirInfo->getDirListA().begin(); dirIterator != dirInfo->getDirListA().end(); ++dirIterator)
740         {
741             MergeFileInfos& mfi = m_fileMergeMap[FileKey(*dirIterator)];
742 
743             mfi.setFileInfoA(&(*dirIterator));
744         }
745     }
746 
747     if(dirInfo->dirB().isValid())
748     {
749         for(dirIterator = dirInfo->getDirListB().begin(); dirIterator != dirInfo->getDirListB().end(); ++dirIterator)
750         {
751             MergeFileInfos& mfi = m_fileMergeMap[FileKey(*dirIterator)];
752 
753             mfi.setFileInfoB(&(*dirIterator));
754         }
755     }
756 
757     if(dirInfo->dirC().isValid())
758     {
759         for(dirIterator = dirInfo->getDirListC().begin(); dirIterator != dirInfo->getDirListC().end(); ++dirIterator)
760         {
761             MergeFileInfos& mfi = m_fileMergeMap[FileKey(*dirIterator)];
762 
763             mfi.setFileInfoC(&(*dirIterator));
764         }
765     }
766 }
767 
init(const QSharedPointer<DirectoryInfo> & dirInfo,bool bDirectoryMerge,bool bReload)768 bool DirectoryMergeWindow::DirectoryMergeWindowPrivate::init(
769     const QSharedPointer<DirectoryInfo>& dirInfo,
770     bool bDirectoryMerge,
771     bool bReload)
772 {
773     if(m_pOptions->m_bDmFullAnalysis)
774     {
775         QStringList errors;
776         // A full analysis uses the same resources that a normal text-diff/merge uses.
777         // So make sure that the user saves his data first.
778         if(!KDiff3App::shouldContinue())
779             return false;
780         Q_EMIT mWindow->startDiffMerge(errors, "", "", "", "", "", "", "", nullptr); // hide main window
781     }
782 
783     mWindow->show();
784     mWindow->setUpdatesEnabled(true);
785 
786     std::map<QString, t_ItemInfo> expandedDirsMap;
787 
788     if(bReload)
789     {
790         // Remember expanded items TODO
791         //QTreeWidgetItemIterator it( this );
792         //while ( *it )
793         //{
794         //   DirMergeItem* pDMI = static_cast<DirMergeItem*>( *it );
795         //   t_ItemInfo& ii = expandedDirsMap[ pDMI->m_pMFI->subPath() ];
796         //   ii.bExpanded = pDMI->isExpanded();
797         //   ii.bOperationComplete = pDMI->m_pMFI->m_bOperationComplete;
798         //   ii.status = pDMI->text( s_OpStatusCol );
799         //   ii.eMergeOperation = pDMI->m_pMFI->getOperation();
800         //   ++it;
801         //}
802     }
803 
804     ProgressProxy pp;
805     m_bFollowDirLinks = m_pOptions->m_bDmFollowDirLinks;
806     m_bFollowFileLinks = m_pOptions->m_bDmFollowFileLinks;
807     m_bSimulatedMergeStarted = false;
808     m_bRealMergeStarted = false;
809     m_bError = false;
810     m_bDirectoryMerge = bDirectoryMerge;
811     m_selection1Index = QModelIndex();
812     m_selection2Index = QModelIndex();
813     m_selection3Index = QModelIndex();
814     m_bCaseSensitive = m_pOptions->m_bDmCaseSensitiveFilenameComparison;
815     m_bUnfoldSubdirs = m_pOptions->m_bDmUnfoldSubdirs;
816     m_bSkipDirStatus = m_pOptions->m_bDmSkipDirStatus;
817 
818     beginResetModel();
819     m_pRoot->clear();
820     m_mergeItemList.clear();
821     endResetModel();
822 
823     m_currentIndexForOperation = m_mergeItemList.end();
824 
825     if(!bReload)
826     {
827         m_pDirShowIdenticalFiles->setChecked(true);
828         m_pDirShowDifferentFiles->setChecked(true);
829         m_pDirShowFilesOnlyInA->setChecked(true);
830         m_pDirShowFilesOnlyInB->setChecked(true);
831         m_pDirShowFilesOnlyInC->setChecked(true);
832     }
833     Q_ASSERT(dirInfo != nullptr);
834     const FileAccess& dirA = dirInfo->dirA();
835     const FileAccess& dirB = dirInfo->dirB();
836     const FileAccess& dirC = dirInfo->dirC();
837     const FileAccess& dirDest = dirInfo->destDir();
838     // Check if all input directories exist and are valid. The dest dir is not tested now.
839     // The test will happen only when we are going to write to it.
840     if(!dirA.isDir() || !dirB.isDir() ||
841        (dirC.isValid() && !dirC.isDir()))
842     {
843         QString text(i18n("Opening of folders failed:"));
844         text += "\n\n";
845         if(!dirA.isDir())
846         {
847             text += i18n("Folder A \"%1\" does not exist or is not a folder.\n", dirA.prettyAbsPath());
848         }
849 
850         if(!dirB.isDir())
851         {
852             text += i18n("Folder B \"%1\" does not exist or is not a folder.\n", dirB.prettyAbsPath());
853         }
854 
855         if(dirC.isValid() && !dirC.isDir())
856         {
857             text += i18n("Folder C \"%1\" does not exist or is not a folder.\n", dirC.prettyAbsPath());
858         }
859 
860         KMessageBox::sorry(mWindow, text, i18n("Folder Opening Error"));
861         return false;
862     }
863 
864     if(dirC.isValid() &&
865        (dirDest.prettyAbsPath() == dirA.prettyAbsPath() || dirDest.prettyAbsPath() == dirB.prettyAbsPath()))
866     {
867         KMessageBox::error(mWindow,
868                            i18n("The destination folder must not be the same as A or B when "
869                                 "three folders are merged.\nCheck again before continuing."),
870                            i18n("Parameter Warning"));
871         return false;
872     }
873 
874     m_bScanning = true;
875     Q_EMIT mWindow->statusBarMessage(i18n("Scanning folders..."));
876 
877     m_bSyncMode = m_pOptions->m_bDmSyncMode && dirInfo->allowSyncMode();
878 
879     m_fileMergeMap.clear();
880     s_eCaseSensitivity = m_bCaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive;
881     // calc how many directories will be read:
882     double nofScans = (dirA.isValid() ? 1 : 0) + (dirB.isValid() ? 1 : 0) + (dirC.isValid() ? 1 : 0);
883     int currentScan = 0;
884 
885     //TODO   setColumnWidthMode(s_UnsolvedCol, Q3ListView::Manual);
886     //   setColumnWidthMode(s_SolvedCol,   Q3ListView::Manual);
887     //   setColumnWidthMode(s_WhiteCol,    Q3ListView::Manual);
888     //   setColumnWidthMode(s_NonWhiteCol, Q3ListView::Manual);
889     mWindow->setColumnHidden(s_CCol, !dirC.isValid());
890     mWindow->setColumnHidden(s_WhiteCol, !m_pOptions->m_bDmFullAnalysis);
891     mWindow->setColumnHidden(s_NonWhiteCol, !m_pOptions->m_bDmFullAnalysis);
892     mWindow->setColumnHidden(s_UnsolvedCol, !m_pOptions->m_bDmFullAnalysis);
893     mWindow->setColumnHidden(s_SolvedCol, !(m_pOptions->m_bDmFullAnalysis && dirC.isValid()));
894 
895     bool bListDirSuccessA = true;
896     bool bListDirSuccessB = true;
897     bool bListDirSuccessC = true;
898 
899     if(dirA.isValid())
900     {
901         pp.setInformation(i18n("Reading Folder A"));
902         pp.setSubRangeTransformation(currentScan / nofScans, (currentScan + 1) / nofScans);
903         ++currentScan;
904 
905         bListDirSuccessA = dirInfo->listDirA(*m_pOptions);
906     }
907 
908     if(dirB.isValid())
909     {
910         pp.setInformation(i18n("Reading Folder B"));
911         pp.setSubRangeTransformation(currentScan / nofScans, (currentScan + 1) / nofScans);
912         ++currentScan;
913 
914         bListDirSuccessB = dirInfo->listDirB(*m_pOptions);
915     }
916 
917     e_MergeOperation eDefaultMergeOp;
918     if(dirC.isValid())
919     {
920         pp.setInformation(i18n("Reading Folder C"));
921         pp.setSubRangeTransformation(currentScan / nofScans, (currentScan + 1) / nofScans);
922         ++currentScan;
923 
924         bListDirSuccessC = dirInfo->listDirC(*m_pOptions);
925 
926         eDefaultMergeOp = eMergeABCToDest;
927     }
928     else
929         eDefaultMergeOp = m_bSyncMode ? eMergeToAB : eMergeABToDest;
930 
931     buildMergeMap(dirInfo);
932 
933     bool bContinue = true;
934     if(!bListDirSuccessA || !bListDirSuccessB || !bListDirSuccessC)
935     {
936         QString s = i18n("Some subfolders were not readable in");
937         if(!bListDirSuccessA) s += "\nA: " + dirA.prettyAbsPath();
938         if(!bListDirSuccessB) s += "\nB: " + dirB.prettyAbsPath();
939         if(!bListDirSuccessC) s += "\nC: " + dirC.prettyAbsPath();
940         s += '\n';
941         s += i18n("Check the permissions of the subfolders.");
942         bContinue = KMessageBox::Continue == KMessageBox::warningContinueCancel(mWindow, s);
943     }
944 
945     if(bContinue)
946     {
947         prepareListView(pp);
948 
949         mWindow->updateFileVisibilities();
950 
951         for(int childIdx = 0; childIdx < rowCount(); ++childIdx)
952         {
953             QModelIndex mi = index(childIdx, 0, QModelIndex());
954             calcSuggestedOperation(mi, eDefaultMergeOp);
955         }
956     }
957 
958     mWindow->sortByColumn(0, Qt::AscendingOrder);
959 
960     for(int column = 0; column < columnCount(QModelIndex()); ++column)
961         mWindow->resizeColumnToContents(column);
962 
963     // Try to improve the view a little bit.
964     QWidget* pParent = mWindow->parentWidget();
965     QSplitter* pSplitter = static_cast<QSplitter*>(pParent);
966     if(pSplitter != nullptr)
967     {
968         QList<int> sizes = pSplitter->sizes();
969         int total = sizes[0] + sizes[1];
970         if(total < 10)
971             total = 100;
972         sizes[0] = total * 6 / 10;
973         sizes[1] = total - sizes[0];
974         pSplitter->setSizes(sizes);
975     }
976 
977     m_bScanning = false;
978     Q_EMIT mWindow->statusBarMessage(i18n("Ready."));
979 
980     if(bContinue && !m_bSkipDirStatus)
981     {
982         // Generate a status report
983         int nofFiles = 0;
984         int nofDirs = 0;
985         int nofEqualFiles = 0;
986         int nofManualMerges = 0;
987         //TODO
988         for(int childIdx = 0; childIdx < rowCount(); ++childIdx)
989             calcDirStatus(dirC.isValid(), index(childIdx, 0, QModelIndex()),
990                           nofFiles, nofDirs, nofEqualFiles, nofManualMerges);
991 
992         QString s;
993         s = i18n("Folder Comparison Status\n\n"
994                  "Number of subfolders: %1\n"
995                  "Number of equal files: %2\n"
996                  "Number of different files: %3",
997                  nofDirs, nofEqualFiles, nofFiles - nofEqualFiles);
998 
999         if(dirC.isValid())
1000             s += '\n' + i18n("Number of manual merges: %1", nofManualMerges);
1001         KMessageBox::information(mWindow, s);
1002         //
1003         //TODO
1004         //if ( topLevelItemCount()>0 )
1005         //{
1006         //   topLevelItem(0)->setSelected(true);
1007         //   setCurrentItem( topLevelItem(0) );
1008         //}
1009     }
1010 
1011     if(bReload)
1012     {
1013         // Remember expanded items
1014         //TODO
1015         //QTreeWidgetItemIterator it( this );
1016         //while ( *it )
1017         //{
1018         //   DirMergeItem* pDMI = static_cast<DirMergeItem*>( *it );
1019         //   std::map<QString,t_ItemInfo>::iterator i = expandedDirsMap.find( pDMI->m_pMFI->subPath() );
1020         //   if ( i!=expandedDirsMap.end() )
1021         //   {
1022         //      t_ItemInfo& ii = i->second;
1023         //      pDMI->setExpanded( ii.bExpanded );
1024         //      //pDMI->m_pMFI->setMergeOperation( ii.eMergeOperation, false ); unsafe, might have changed
1025         //      pDMI->m_pMFI->m_bOperationComplete = ii.bOperationComplete;
1026         //      pDMI->setText( s_OpStatusCol, ii.status );
1027         //   }
1028         //   ++it;
1029         //}
1030     }
1031     else if(m_bUnfoldSubdirs)
1032     {
1033         m_pDirUnfoldAll->trigger();
1034     }
1035 
1036     return true;
1037 }
1038 
getDirNameA() const1039 inline QString DirectoryMergeWindow::getDirNameA() const
1040 {
1041     return gDirInfo->dirA().prettyAbsPath();
1042 }
getDirNameB() const1043 inline QString DirectoryMergeWindow::getDirNameB() const
1044 {
1045     return gDirInfo->dirB().prettyAbsPath();
1046 }
getDirNameC() const1047 inline QString DirectoryMergeWindow::getDirNameC() const
1048 {
1049     return gDirInfo->dirC().prettyAbsPath();
1050 }
getDirNameDest() const1051 inline QString DirectoryMergeWindow::getDirNameDest() const
1052 {
1053     return gDirInfo->destDir().prettyAbsPath();
1054 }
1055 
onExpanded()1056 void DirectoryMergeWindow::onExpanded()
1057 {
1058     resizeColumnToContents(s_NameCol);
1059 }
1060 
slotChooseAEverywhere()1061 void DirectoryMergeWindow::slotChooseAEverywhere()
1062 {
1063     d->setAllMergeOperations(eCopyAToDest);
1064 }
1065 
slotChooseBEverywhere()1066 void DirectoryMergeWindow::slotChooseBEverywhere()
1067 {
1068     d->setAllMergeOperations(eCopyBToDest);
1069 }
1070 
slotChooseCEverywhere()1071 void DirectoryMergeWindow::slotChooseCEverywhere()
1072 {
1073     d->setAllMergeOperations(eCopyCToDest);
1074 }
1075 
slotAutoChooseEverywhere()1076 void DirectoryMergeWindow::slotAutoChooseEverywhere()
1077 {
1078     e_MergeOperation eDefaultMergeOp = d->isThreeWay() ? eMergeABCToDest : d->m_bSyncMode ? eMergeToAB : eMergeABToDest;
1079     d->setAllMergeOperations(eDefaultMergeOp);
1080 }
1081 
slotNoOpEverywhere()1082 void DirectoryMergeWindow::slotNoOpEverywhere()
1083 {
1084     d->setAllMergeOperations(eNoOperation);
1085 }
1086 
slotFoldAllSubdirs()1087 void DirectoryMergeWindow::slotFoldAllSubdirs()
1088 {
1089     collapseAll();
1090 }
1091 
slotUnfoldAllSubdirs()1092 void DirectoryMergeWindow::slotUnfoldAllSubdirs()
1093 {
1094     expandAll();
1095 }
1096 
1097 // Merge current item (merge mode)
slotCurrentDoNothing()1098 void DirectoryMergeWindow::slotCurrentDoNothing()
1099 {
1100     d->setMergeOperation(currentIndex(), eNoOperation);
1101 }
slotCurrentChooseA()1102 void DirectoryMergeWindow::slotCurrentChooseA()
1103 {
1104     d->setMergeOperation(currentIndex(), d->m_bSyncMode ? eCopyAToB : eCopyAToDest);
1105 }
slotCurrentChooseB()1106 void DirectoryMergeWindow::slotCurrentChooseB()
1107 {
1108     d->setMergeOperation(currentIndex(), d->m_bSyncMode ? eCopyBToA : eCopyBToDest);
1109 }
slotCurrentChooseC()1110 void DirectoryMergeWindow::slotCurrentChooseC()
1111 {
1112     d->setMergeOperation(currentIndex(), eCopyCToDest);
1113 }
slotCurrentMerge()1114 void DirectoryMergeWindow::slotCurrentMerge()
1115 {
1116     bool bThreeDirs = d->isThreeWay();
1117     d->setMergeOperation(currentIndex(), bThreeDirs ? eMergeABCToDest : eMergeABToDest);
1118 }
slotCurrentDelete()1119 void DirectoryMergeWindow::slotCurrentDelete()
1120 {
1121     d->setMergeOperation(currentIndex(), eDeleteFromDest);
1122 }
1123 // Sync current item
slotCurrentCopyAToB()1124 void DirectoryMergeWindow::slotCurrentCopyAToB()
1125 {
1126     d->setMergeOperation(currentIndex(), eCopyAToB);
1127 }
slotCurrentCopyBToA()1128 void DirectoryMergeWindow::slotCurrentCopyBToA()
1129 {
1130     d->setMergeOperation(currentIndex(), eCopyBToA);
1131 }
slotCurrentDeleteA()1132 void DirectoryMergeWindow::slotCurrentDeleteA()
1133 {
1134     d->setMergeOperation(currentIndex(), eDeleteA);
1135 }
slotCurrentDeleteB()1136 void DirectoryMergeWindow::slotCurrentDeleteB()
1137 {
1138     d->setMergeOperation(currentIndex(), eDeleteB);
1139 }
slotCurrentDeleteAAndB()1140 void DirectoryMergeWindow::slotCurrentDeleteAAndB()
1141 {
1142     d->setMergeOperation(currentIndex(), eDeleteAB);
1143 }
slotCurrentMergeToA()1144 void DirectoryMergeWindow::slotCurrentMergeToA()
1145 {
1146     d->setMergeOperation(currentIndex(), eMergeToA);
1147 }
slotCurrentMergeToB()1148 void DirectoryMergeWindow::slotCurrentMergeToB()
1149 {
1150     d->setMergeOperation(currentIndex(), eMergeToB);
1151 }
slotCurrentMergeToAAndB()1152 void DirectoryMergeWindow::slotCurrentMergeToAAndB()
1153 {
1154     d->setMergeOperation(currentIndex(), eMergeToAB);
1155 }
1156 
keyPressEvent(QKeyEvent * e)1157 void DirectoryMergeWindow::keyPressEvent(QKeyEvent* e)
1158 {
1159     if((e->QInputEvent::modifiers() & Qt::ControlModifier) != 0)
1160     {
1161         MergeFileInfos* pMFI = d->getMFI(currentIndex());
1162         if(pMFI == nullptr)
1163             return;
1164 
1165         bool bThreeDirs = pMFI->isThreeWay();
1166         bool bMergeMode = bThreeDirs || !d->m_bSyncMode;
1167         bool bFTConflict = pMFI->conflictingFileTypes();
1168 
1169         switch(e->key())
1170         {
1171             case Qt::Key_1:
1172                 if(pMFI->existsInA())
1173                 {
1174                     slotCurrentChooseA();
1175                 }
1176                 return;
1177             case Qt::Key_2:
1178                 if(pMFI->existsInB())
1179                 {
1180                     slotCurrentChooseB();
1181                 }
1182                 return;
1183             case Qt::Key_Space:
1184                 slotCurrentDoNothing();
1185                 return;
1186         }
1187 
1188         if(bMergeMode)
1189         {
1190             switch(e->key())
1191             {
1192                 case Qt::Key_3:
1193                     if(pMFI->existsInC())
1194                     {
1195                         slotCurrentChooseC();
1196                     }
1197                     return;
1198 
1199                 case Qt::Key_4:
1200                     if(!bFTConflict)
1201                     {
1202                         slotCurrentMerge();
1203                     }
1204                     return;
1205                 case Qt::Key_Delete:
1206                     slotCurrentDelete();
1207                     return;
1208                 default:
1209                     break;
1210             }
1211         }
1212         else
1213         {
1214             switch(e->key())
1215             {
1216                 case Qt::Key_4:
1217                     if(!bFTConflict)
1218                     {
1219                         slotCurrentMergeToAAndB();
1220                     }
1221                     return;
1222                 case Qt::Key_Delete:
1223                     if(pMFI->existsInA() && pMFI->existsInB())
1224                         slotCurrentDeleteAAndB();
1225                     else if(pMFI->existsInA())
1226                         slotCurrentDeleteA();
1227                     else if(pMFI->existsInB())
1228                         slotCurrentDeleteB();
1229                     return;
1230                 default:
1231                     break;
1232             }
1233         }
1234     }
1235     //Override Qt's default behavior for this key.
1236     else if(e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter)
1237     {
1238         onDoubleClick(currentIndex());
1239         return;
1240     }
1241 
1242     QTreeView::keyPressEvent(e);
1243 }
1244 
focusInEvent(QFocusEvent *)1245 void DirectoryMergeWindow::focusInEvent(QFocusEvent*)
1246 {
1247     Q_EMIT updateAvailabilities();
1248 }
focusOutEvent(QFocusEvent *)1249 void DirectoryMergeWindow::focusOutEvent(QFocusEvent*)
1250 {
1251     Q_EMIT updateAvailabilities();
1252 }
1253 
setAllMergeOperations(e_MergeOperation eDefaultOperation)1254 void DirectoryMergeWindow::DirectoryMergeWindowPrivate::setAllMergeOperations(e_MergeOperation eDefaultOperation)
1255 {
1256     if(KMessageBox::Yes == KMessageBox::warningYesNo(mWindow,
1257                                                      i18n("This affects all merge operations."),
1258                                                      i18n("Changing All Merge Operations"),
1259                                                      KStandardGuiItem::cont(),
1260                                                      KStandardGuiItem::cancel()))
1261     {
1262         for(int i = 0; i < rowCount(); ++i)
1263         {
1264             calcSuggestedOperation(index(i, 0, QModelIndex()), eDefaultOperation);
1265         }
1266     }
1267 }
1268 
nextSibling(const QModelIndex & mi)1269 QModelIndex DirectoryMergeWindow::DirectoryMergeWindowPrivate::nextSibling(const QModelIndex& mi)
1270 {
1271     QModelIndex miParent = mi.parent();
1272     int currentIdx = mi.row();
1273     if(currentIdx + 1 < mi.model()->rowCount(miParent))
1274         return mi.model()->index(mi.row() + 1, 0, miParent); // next child of parent
1275     return QModelIndex();
1276 }
1277 
1278 // Iterate through the complete tree. Start by specifying QListView::firstChild().
treeIterator(QModelIndex mi,bool bVisitChildren,bool bFindInvisible)1279 QModelIndex DirectoryMergeWindow::DirectoryMergeWindowPrivate::treeIterator(QModelIndex mi, bool bVisitChildren, bool bFindInvisible)
1280 {
1281     if(mi.isValid())
1282     {
1283         do
1284         {
1285             if(bVisitChildren && mi.model()->rowCount(mi) != 0)
1286                 mi = mi.model()->index(0, 0, mi);
1287             else
1288             {
1289                 QModelIndex miNextSibling = nextSibling(mi);
1290                 if(miNextSibling.isValid())
1291                     mi = miNextSibling;
1292                 else
1293                 {
1294                     mi = mi.parent();
1295                     while(mi.isValid())
1296                     {
1297                         miNextSibling = nextSibling(mi);
1298                         if(miNextSibling.isValid())
1299                         {
1300                             mi = miNextSibling;
1301                             break;
1302                         }
1303                         else
1304                         {
1305                             mi = mi.parent();
1306                         }
1307                     }
1308                 }
1309             }
1310         } while(mi.isValid() && mWindow->isRowHidden(mi.row(), mi.parent()) && !bFindInvisible);
1311     }
1312     return mi;
1313 }
1314 
prepareListView(ProgressProxy & pp)1315 void DirectoryMergeWindow::DirectoryMergeWindowPrivate::prepareListView(ProgressProxy& pp)
1316 {
1317     QStringList errors;
1318     //TODO   clear();
1319     PixMapUtils::initPixmaps(m_pOptions->m_newestFileColor, m_pOptions->m_oldestFileColor,
1320                              m_pOptions->m_midAgeFileColor, m_pOptions->m_missingFileColor);
1321 
1322     mWindow->setRootIsDecorated(true);
1323 
1324     int nrOfFiles = m_fileMergeMap.size();
1325     int currentIdx = 1;
1326     QElapsedTimer t;
1327     t.start();
1328     pp.setMaxNofSteps(nrOfFiles);
1329 
1330     for(MergeFileInfos &mfi: m_fileMergeMap)
1331     {
1332         const QString& fileName = mfi.subPath();
1333 
1334         pp.setInformation(
1335             i18n("Processing %1 / %2\n%3", currentIdx, nrOfFiles, fileName), currentIdx, false);
1336         if(pp.wasCancelled()) break;
1337         ++currentIdx;
1338 
1339         // The comparisons and calculations for each file take place here.
1340         if(!mfi.compareFilesAndCalcAges(errors, m_pOptions, mWindow) && errors.size() >= 30)
1341             break;
1342 
1343         // Get dirname from fileName: Search for "/" from end:
1344         int pos = fileName.lastIndexOf('/');
1345         QString dirPart;
1346         QString filePart;
1347         if(pos == -1)
1348         {
1349             // Top dir
1350             filePart = fileName;
1351         }
1352         else
1353         {
1354             dirPart = fileName.left(pos);
1355             filePart = fileName.mid(pos + 1);
1356         }
1357 
1358         if(dirPart.isEmpty()) // Top level
1359         {
1360             m_pRoot->addChild(&mfi); //new DirMergeItem( this, filePart, &mfi );
1361             mfi.setParent(m_pRoot);
1362         }
1363         else
1364         {
1365             const FileAccess* pFA = mfi.getFileInfoA() ? mfi.getFileInfoA() : mfi.getFileInfoB() ? mfi.getFileInfoB() : mfi.getFileInfoC();
1366             MergeFileInfos& dirMfi = pFA->parent() ? m_fileMergeMap[FileKey(*pFA->parent())] : *m_pRoot; // parent
1367 
1368             dirMfi.addChild(&mfi); //new DirMergeItem( dirMfi.m_pDMI, filePart, &mfi );
1369             mfi.setParent(&dirMfi);
1370             //   // Equality for parent dirs is set in updateFileVisibilities()
1371         }
1372 
1373         mfi.updateAge();
1374     }
1375 
1376     if(errors.size() > 0)
1377     {
1378         if(errors.size() < 15)
1379         {
1380             KMessageBox::errorList(mWindow, i18n("Some files could not be processed."), errors);
1381         }
1382         else if(errors.size() < 30)
1383         {
1384             KMessageBox::error(mWindow, i18n("Some files could not be processed."));
1385         }
1386         else
1387             KMessageBox::error(mWindow, i18n("Aborting due to too many errors."));
1388     }
1389 
1390     beginResetModel();
1391     endResetModel();
1392 }
1393 
calcSuggestedOperation(const QModelIndex & mi,e_MergeOperation eDefaultMergeOp)1394 void DirectoryMergeWindow::DirectoryMergeWindowPrivate::calcSuggestedOperation(const QModelIndex& mi, e_MergeOperation eDefaultMergeOp)
1395 {
1396     const MergeFileInfos* pMFI = getMFI(mi);
1397     if(pMFI == nullptr)
1398         return;
1399 
1400     bool bCheckC = pMFI->isThreeWay();
1401     bool bCopyNewer = m_pOptions->m_bDmCopyNewer;
1402     bool bOtherDest = !((gDirInfo->destDir().absoluteFilePath() == gDirInfo->dirA().absoluteFilePath()) ||
1403                         (gDirInfo->destDir().absoluteFilePath() == gDirInfo->dirB().absoluteFilePath()) ||
1404                         (bCheckC && gDirInfo->destDir().absoluteFilePath() == gDirInfo->dirC().absoluteFilePath()));
1405 
1406     //Crash and burn in debug mode these states are never valid.
1407     //The checks are duplicated here so they show in the assert text.
1408     Q_ASSERT(!(eDefaultMergeOp == eMergeABCToDest && !bCheckC));
1409     Q_ASSERT(!(eDefaultMergeOp == eMergeToAB && bCheckC));
1410 
1411     //Check for two bugged states that are recoverable. This should never happen!
1412     if(Q_UNLIKELY(eDefaultMergeOp == eMergeABCToDest && !bCheckC))
1413     {
1414         qCWarning(kdiffMain) << "Invalid State detected in DirectoryMergeWindow::DirectoryMergeWindowPrivate::calcSuggestedOperation";
1415         eDefaultMergeOp = eMergeABToDest;
1416     }
1417     if(Q_UNLIKELY(eDefaultMergeOp == eMergeToAB && bCheckC))
1418     {
1419         qCWarning(kdiffMain) << "Invalid State detected in DirectoryMergeWindow::DirectoryMergeWindowPrivate::calcSuggestedOperation";
1420         eDefaultMergeOp = eMergeABCToDest;
1421     }
1422 
1423     if(eDefaultMergeOp == eMergeToA || eDefaultMergeOp == eMergeToB ||
1424        eDefaultMergeOp == eMergeABCToDest || eDefaultMergeOp == eMergeABToDest || eDefaultMergeOp == eMergeToAB)
1425     {
1426         if(!bCheckC)
1427         {
1428             if(pMFI->isEqualAB())
1429             {
1430                 setMergeOperation(mi, bOtherDest ? eCopyBToDest : eNoOperation);
1431             }
1432             else if(pMFI->existsInA() && pMFI->existsInB())
1433             {
1434                 if(!bCopyNewer || pMFI->isDirA())
1435                     setMergeOperation(mi, eDefaultMergeOp);
1436                 else if(bCopyNewer && pMFI->conflictingAges())
1437                 {
1438                     setMergeOperation(mi, eConflictingAges);
1439                 }
1440                 else
1441                 {
1442                     if(pMFI->getAgeA() == eNew)
1443                         setMergeOperation(mi, eDefaultMergeOp == eMergeToAB ? eCopyAToB : eCopyAToDest);
1444                     else
1445                         setMergeOperation(mi, eDefaultMergeOp == eMergeToAB ? eCopyBToA : eCopyBToDest);
1446                 }
1447             }
1448             else if(!pMFI->existsInA() && pMFI->existsInB())
1449             {
1450                 if(eDefaultMergeOp == eMergeABToDest)
1451                     setMergeOperation(mi, eCopyBToDest);
1452                 else if(eDefaultMergeOp == eMergeToB)
1453                     setMergeOperation(mi, eNoOperation);
1454                 else
1455                     setMergeOperation(mi, eCopyBToA);
1456             }
1457             else if(pMFI->existsInA() && !pMFI->existsInB())
1458             {
1459                 if(eDefaultMergeOp == eMergeABToDest)
1460                     setMergeOperation(mi, eCopyAToDest);
1461                 else if(eDefaultMergeOp == eMergeToA)
1462                     setMergeOperation(mi, eNoOperation);
1463                 else
1464                     setMergeOperation(mi, eCopyAToB);
1465             }
1466             else //if ( !pMFI->existsInA() && !pMFI->existsInB() )
1467             {
1468                 setMergeOperation(mi, eNoOperation);
1469             }
1470         }
1471         else
1472         {
1473             if(pMFI->isEqualAB() && pMFI->isEqualAC())
1474             {
1475                 setMergeOperation(mi, bOtherDest ? eCopyCToDest : eNoOperation);
1476             }
1477             else if(pMFI->existsInA() && pMFI->existsInB() && pMFI->existsInC())
1478             {
1479                 if(pMFI->isEqualAB() || pMFI->isEqualBC())
1480                     setMergeOperation(mi, eCopyCToDest);
1481                 else if(pMFI->isEqualAC())
1482                     setMergeOperation(mi, eCopyBToDest);
1483                 else
1484                     setMergeOperation(mi, eMergeABCToDest);
1485             }
1486             else if(pMFI->existsInA() && pMFI->existsInB() && !pMFI->existsInC())
1487             {
1488                 if(pMFI->isEqualAB())
1489                     setMergeOperation(mi, eDeleteFromDest);
1490                 else
1491                     setMergeOperation(mi, eChangedAndDeleted);
1492             }
1493             else if(pMFI->existsInA() && !pMFI->existsInB() && pMFI->existsInC())
1494             {
1495                 if(pMFI->isEqualAC())
1496                     setMergeOperation(mi, eDeleteFromDest);
1497                 else
1498                     setMergeOperation(mi, eChangedAndDeleted);
1499             }
1500             else if(!pMFI->existsInA() && pMFI->existsInB() && pMFI->existsInC())
1501             {
1502                 if(pMFI->isEqualBC())
1503                     setMergeOperation(mi, eCopyCToDest);
1504                 else
1505                     setMergeOperation(mi, eMergeABCToDest);
1506             }
1507             else if(!pMFI->existsInA() && !pMFI->existsInB() && pMFI->existsInC())
1508             {
1509                 setMergeOperation(mi, eCopyCToDest);
1510             }
1511             else if(!pMFI->existsInA() && pMFI->existsInB() && !pMFI->existsInC())
1512             {
1513                 setMergeOperation(mi, eCopyBToDest);
1514             }
1515             else if(pMFI->existsInA() && !pMFI->existsInB() && !pMFI->existsInC())
1516             {
1517                 setMergeOperation(mi, eDeleteFromDest);
1518             }
1519             else //if ( !pMFI->existsInA() && !pMFI->existsInB() && !pMFI->existsInC() )
1520             {
1521                 setMergeOperation(mi, eNoOperation);
1522             }
1523         }
1524 
1525         // Now check if file/dir-types fit.
1526         if(pMFI->conflictingFileTypes())
1527         {
1528             setMergeOperation(mi, eConflictingFileTypes);
1529         }
1530     }
1531     else
1532     {
1533         e_MergeOperation eMO = eDefaultMergeOp;
1534         switch(eDefaultMergeOp)
1535         {
1536             case eConflictingFileTypes:
1537             case eChangedAndDeleted:
1538             case eConflictingAges:
1539             case eDeleteA:
1540             case eDeleteB:
1541             case eDeleteAB:
1542             case eDeleteFromDest:
1543             case eNoOperation:
1544                 break;
1545             case eCopyAToB:
1546                 if(!pMFI->existsInA())
1547                 {
1548                     eMO = eDeleteB;
1549                 }
1550                 break;
1551             case eCopyBToA:
1552                 if(!pMFI->existsInB())
1553                 {
1554                     eMO = eDeleteA;
1555                 }
1556                 break;
1557             case eCopyAToDest:
1558                 if(!pMFI->existsInA())
1559                 {
1560                     eMO = eDeleteFromDest;
1561                 }
1562                 break;
1563             case eCopyBToDest:
1564                 if(!pMFI->existsInB())
1565                 {
1566                     eMO = eDeleteFromDest;
1567                 }
1568                 break;
1569             case eCopyCToDest:
1570                 if(!pMFI->existsInC())
1571                 {
1572                     eMO = eDeleteFromDest;
1573                 }
1574                 break;
1575 
1576             case eMergeToA:
1577             case eMergeToB:
1578             case eMergeToAB:
1579             case eMergeABCToDest:
1580             case eMergeABToDest:
1581                 break;
1582             default:
1583                 Q_ASSERT(true);
1584                 break;
1585         }
1586         setMergeOperation(mi, eMO);
1587     }
1588 }
1589 
onDoubleClick(const QModelIndex & mi)1590 void DirectoryMergeWindow::onDoubleClick(const QModelIndex& mi)
1591 {
1592     if(!mi.isValid())
1593         return;
1594 
1595     d->m_bSimulatedMergeStarted = false;
1596     if(d->m_bDirectoryMerge)
1597         mergeCurrentFile();
1598     else
1599         compareCurrentFile();
1600 }
1601 
currentChanged(const QModelIndex & current,const QModelIndex & previous)1602 void DirectoryMergeWindow::currentChanged(const QModelIndex& current, const QModelIndex& previous)
1603 {
1604     QTreeView::currentChanged(current, previous);
1605     MergeFileInfos* pMFI = d->getMFI(current);
1606     if(pMFI == nullptr)
1607         return;
1608 
1609     d->m_pDirectoryMergeInfo->setInfo(gDirInfo->dirA(), gDirInfo->dirB(), gDirInfo->dirC(), gDirInfo->destDir(), *pMFI);
1610 }
1611 
mousePressEvent(QMouseEvent * e)1612 void DirectoryMergeWindow::mousePressEvent(QMouseEvent* e)
1613 {
1614     QTreeView::mousePressEvent(e);
1615     QModelIndex mi = indexAt(e->pos());
1616     int c = mi.column();
1617     QPoint p = e->globalPos();
1618     MergeFileInfos* pMFI = d->getMFI(mi);
1619     if(pMFI == nullptr)
1620         return;
1621 
1622     if(c == s_OpCol)
1623     {
1624         bool bThreeDirs = d->isThreeWay();
1625 
1626         QMenu m(this);
1627         if(bThreeDirs)
1628         {
1629             m.addAction(d->m_pDirCurrentDoNothing);
1630             int count = 0;
1631             if(pMFI->existsInA())
1632             {
1633                 m.addAction(d->m_pDirCurrentChooseA);
1634                 ++count;
1635             }
1636             if(pMFI->existsInB())
1637             {
1638                 m.addAction(d->m_pDirCurrentChooseB);
1639                 ++count;
1640             }
1641             if(pMFI->existsInC())
1642             {
1643                 m.addAction(d->m_pDirCurrentChooseC);
1644                 ++count;
1645             }
1646             if(!pMFI->conflictingFileTypes() && count > 1) m.addAction(d->m_pDirCurrentMerge);
1647             m.addAction(d->m_pDirCurrentDelete);
1648         }
1649         else if(d->m_bSyncMode)
1650         {
1651             m.addAction(d->m_pDirCurrentSyncDoNothing);
1652             if(pMFI->existsInA()) m.addAction(d->m_pDirCurrentSyncCopyAToB);
1653             if(pMFI->existsInB()) m.addAction(d->m_pDirCurrentSyncCopyBToA);
1654             if(pMFI->existsInA()) m.addAction(d->m_pDirCurrentSyncDeleteA);
1655             if(pMFI->existsInB()) m.addAction(d->m_pDirCurrentSyncDeleteB);
1656             if(pMFI->existsInA() && pMFI->existsInB())
1657             {
1658                 m.addAction(d->m_pDirCurrentSyncDeleteAAndB);
1659                 if(!pMFI->conflictingFileTypes())
1660                 {
1661                     m.addAction(d->m_pDirCurrentSyncMergeToA);
1662                     m.addAction(d->m_pDirCurrentSyncMergeToB);
1663                     m.addAction(d->m_pDirCurrentSyncMergeToAAndB);
1664                 }
1665             }
1666         }
1667         else
1668         {
1669             m.addAction(d->m_pDirCurrentDoNothing);
1670             if(pMFI->existsInA())
1671             {
1672                 m.addAction(d->m_pDirCurrentChooseA);
1673             }
1674             if(pMFI->existsInB())
1675             {
1676                 m.addAction(d->m_pDirCurrentChooseB);
1677             }
1678             if(!pMFI->conflictingFileTypes() && pMFI->existsInA() && pMFI->existsInB()) m.addAction(d->m_pDirCurrentMerge);
1679             m.addAction(d->m_pDirCurrentDelete);
1680         }
1681 
1682         m.exec(p);
1683     }
1684     else if(c == s_ACol || c == s_BCol || c == s_CCol)
1685     {
1686         QString itemPath;
1687         if(c == s_ACol && pMFI->existsInA())
1688         {
1689             itemPath = pMFI->fullNameA();
1690         }
1691         else if(c == s_BCol && pMFI->existsInB())
1692         {
1693             itemPath = pMFI->fullNameB();
1694         }
1695         else if(c == s_CCol && pMFI->existsInC())
1696         {
1697             itemPath = pMFI->fullNameC();
1698         }
1699 
1700         if(!itemPath.isEmpty())
1701         {
1702             d->selectItemAndColumn(mi, e->button() == Qt::RightButton);
1703         }
1704     }
1705 }
1706 
1707 #ifndef QT_NO_CONTEXTMENU
contextMenuEvent(QContextMenuEvent * e)1708 void DirectoryMergeWindow::contextMenuEvent(QContextMenuEvent* e)
1709 {
1710     QModelIndex mi = indexAt(e->pos());
1711     int c = mi.column();
1712 
1713     MergeFileInfos* pMFI = d->getMFI(mi);
1714     if(pMFI == nullptr)
1715         return;
1716     if(c == s_ACol || c == s_BCol || c == s_CCol)
1717     {
1718         QString itemPath;
1719         if(c == s_ACol && pMFI->existsInA())
1720         {
1721             itemPath = pMFI->fullNameA();
1722         }
1723         else if(c == s_BCol && pMFI->existsInB())
1724         {
1725             itemPath = pMFI->fullNameB();
1726         }
1727         else if(c == s_CCol && pMFI->existsInC())
1728         {
1729             itemPath = pMFI->fullNameC();
1730         }
1731 
1732         if(!itemPath.isEmpty())
1733         {
1734             d->selectItemAndColumn(mi, true);
1735             QMenu m(this);
1736             m.addAction(d->m_pDirCompareExplicit);
1737             m.addAction(d->m_pDirMergeExplicit);
1738 
1739             m.popup(e->globalPos());
1740         }
1741     }
1742 }
1743 #endif
1744 
getFileName(const QModelIndex & mi) const1745 QString DirectoryMergeWindow::DirectoryMergeWindowPrivate::getFileName(const QModelIndex& mi) const
1746 {
1747     MergeFileInfos* pMFI = getMFI(mi);
1748     if(pMFI != nullptr)
1749     {
1750         return mi.column() == s_ACol ? pMFI->getFileInfoA()->absoluteFilePath() : mi.column() == s_BCol ? pMFI->getFileInfoB()->absoluteFilePath() : mi.column() == s_CCol ? pMFI->getFileInfoC()->absoluteFilePath() : QString("");
1751     }
1752     return QString();
1753 }
1754 
isDir(const QModelIndex & mi) const1755 bool DirectoryMergeWindow::DirectoryMergeWindowPrivate::isDir(const QModelIndex& mi) const
1756 {
1757     MergeFileInfos* pMFI = getMFI(mi);
1758     if(pMFI != nullptr)
1759     {
1760         return mi.column() == s_ACol ? pMFI->isDirA() : mi.column() == s_BCol ? pMFI->isDirB() : pMFI->isDirC();
1761     }
1762     return false;
1763 }
1764 
selectItemAndColumn(const QModelIndex & mi,bool bContextMenu)1765 void DirectoryMergeWindow::DirectoryMergeWindowPrivate::selectItemAndColumn(const QModelIndex& mi, bool bContextMenu)
1766 {
1767     if(bContextMenu && (mi == m_selection1Index || mi == m_selection2Index || mi == m_selection3Index))
1768         return;
1769 
1770     QModelIndex old1 = m_selection1Index;
1771     QModelIndex old2 = m_selection2Index;
1772     QModelIndex old3 = m_selection3Index;
1773 
1774     bool bReset = false;
1775 
1776     if(m_selection1Index.isValid())
1777     {
1778         if(isDir(m_selection1Index) != isDir(mi))
1779             bReset = true;
1780     }
1781 
1782     if(bReset || m_selection3Index.isValid() || mi == m_selection1Index || mi == m_selection2Index || mi == m_selection3Index)
1783     {
1784         // restart
1785         m_selection1Index = QModelIndex();
1786         m_selection2Index = QModelIndex();
1787         m_selection3Index = QModelIndex();
1788     }
1789     else if(!m_selection1Index.isValid())
1790     {
1791         m_selection1Index = mi;
1792         m_selection2Index = QModelIndex();
1793         m_selection3Index = QModelIndex();
1794     }
1795     else if(!m_selection2Index.isValid())
1796     {
1797         m_selection2Index = mi;
1798         m_selection3Index = QModelIndex();
1799     }
1800     else if(!m_selection3Index.isValid())
1801     {
1802         m_selection3Index = mi;
1803     }
1804     if(old1.isValid()) Q_EMIT dataChanged(old1, old1);
1805     if(old2.isValid()) Q_EMIT dataChanged(old2, old2);
1806     if(old3.isValid()) Q_EMIT dataChanged(old3, old3);
1807     if(m_selection1Index.isValid()) Q_EMIT dataChanged(m_selection1Index, m_selection1Index);
1808     if(m_selection2Index.isValid()) Q_EMIT dataChanged(m_selection2Index, m_selection2Index);
1809     if(m_selection3Index.isValid()) Q_EMIT dataChanged(m_selection3Index, m_selection3Index);
1810     Q_EMIT mWindow->updateAvailabilities();
1811 }
1812 
1813 //TODO
1814 //void DirMergeItem::init(MergeFileInfos* pMFI)
1815 //{
1816 //   pMFI->m_pDMI = this; //no not here
1817 //   m_pMFI = pMFI;
1818 //   TotalDiffStatus& tds = pMFI->m_totalDiffStatus;
1819 //   if ( m_pMFI->dirA() || m_pMFI->dirB() || m_pMFI->isDirC() )
1820 //   {
1821 //   }
1822 //   else
1823 //   {
1824 //      setText( s_UnsolvedCol, QString::number( tds.getUnsolvedConflicts() ) );
1825 //      setText( s_SolvedCol,   QString::number( tds.getSolvedConflicts() ) );
1826 //      setText( s_NonWhiteCol, QString::number( tds.getUnsolvedConflicts() + tds.getSolvedConflicts() - tds.getWhitespaceConflicts() ) );
1827 //      setText( s_WhiteCol,    QString::number( tds.getWhitespaceConflicts() ) );
1828 //   }
1829 //   setSizeHint( s_ACol, QSize(17,17) ); // Iconsize
1830 //   setSizeHint( s_BCol, QSize(17,17) ); // Iconsize
1831 //   setSizeHint( s_CCol, QSize(17,17) ); // Iconsize
1832 //}
1833 
sort(int column,Qt::SortOrder order)1834 void DirectoryMergeWindow::DirectoryMergeWindowPrivate::sort(int column, Qt::SortOrder order)
1835 {
1836     Q_UNUSED(column);
1837     beginResetModel();
1838     m_pRoot->sort(order);
1839     endResetModel();
1840 }
1841 
setMergeOperation(const QModelIndex & mi,e_MergeOperation eMergeOp,bool bRecursive)1842 void DirectoryMergeWindow::DirectoryMergeWindowPrivate::setMergeOperation(const QModelIndex& mi, e_MergeOperation eMergeOp, bool bRecursive)
1843 {
1844     MergeFileInfos* pMFI = getMFI(mi);
1845     if(pMFI == nullptr)
1846         return;
1847 
1848     if(eMergeOp != pMFI->getOperation())
1849     {
1850         pMFI->startOperation();
1851         setOpStatus(mi, eOpStatusNone);
1852     }
1853 
1854     pMFI->setOperation(eMergeOp);
1855     if(bRecursive)
1856     {
1857         e_MergeOperation eChildrenMergeOp = pMFI->getOperation();
1858         if(eChildrenMergeOp == eConflictingFileTypes) eChildrenMergeOp = eMergeABCToDest;
1859 
1860         for(int childIdx = 0; childIdx < pMFI->children().count(); ++childIdx)
1861         {
1862             calcSuggestedOperation(index(childIdx, 0, mi), eChildrenMergeOp);
1863         }
1864     }
1865 }
1866 
compareCurrentFile()1867 void DirectoryMergeWindow::compareCurrentFile()
1868 {
1869     if(!d->canContinue()) return;
1870 
1871     if(d->m_bRealMergeStarted)
1872     {
1873         KMessageBox::sorry(this, i18n("This operation is currently not possible."), i18n("Operation Not Possible"));
1874         return;
1875     }
1876     QStringList errors;
1877     if(MergeFileInfos* pMFI = d->getMFI(currentIndex()))
1878     {
1879         if(!(pMFI->hasDir()))
1880         {
1881             Q_EMIT startDiffMerge(errors,
1882                 pMFI->existsInA() ? pMFI->getFileInfoA()->absoluteFilePath() : QString(""),
1883                 pMFI->existsInB() ? pMFI->getFileInfoB()->absoluteFilePath() : QString(""),
1884                 pMFI->existsInC() ? pMFI->getFileInfoC()->absoluteFilePath() : QString(""),
1885                 "",
1886                 "", "", "", nullptr);
1887         }
1888     }
1889     Q_EMIT updateAvailabilities();
1890 }
1891 
slotCompareExplicitlySelectedFiles()1892 void DirectoryMergeWindow::slotCompareExplicitlySelectedFiles()
1893 {
1894     if(!d->isDir(d->m_selection1Index) && !d->canContinue()) return;
1895 
1896     if(d->m_bRealMergeStarted)
1897     {
1898         KMessageBox::sorry(this, i18n("This operation is currently not possible."), i18n("Operation Not Possible"));
1899         return;
1900     }
1901 
1902     QStringList errors;
1903     Q_EMIT startDiffMerge(errors,
1904         d->getFileName(d->m_selection1Index),
1905         d->getFileName(d->m_selection2Index),
1906         d->getFileName(d->m_selection3Index),
1907         "",
1908         "", "", "", nullptr);
1909     d->m_selection1Index = QModelIndex();
1910     d->m_selection2Index = QModelIndex();
1911     d->m_selection3Index = QModelIndex();
1912 
1913     Q_EMIT updateAvailabilities();
1914     update();
1915 }
1916 
slotMergeExplicitlySelectedFiles()1917 void DirectoryMergeWindow::slotMergeExplicitlySelectedFiles()
1918 {
1919     if(!d->isDir(d->m_selection1Index) && !d->canContinue()) return;
1920 
1921     if(d->m_bRealMergeStarted)
1922     {
1923         KMessageBox::sorry(this, i18n("This operation is currently not possible."), i18n("Operation Not Possible"));
1924         return;
1925     }
1926     QStringList errors;
1927     QString fn1 = d->getFileName(d->m_selection1Index);
1928     QString fn2 = d->getFileName(d->m_selection2Index);
1929     QString fn3 = d->getFileName(d->m_selection3Index);
1930 
1931     Q_EMIT startDiffMerge(errors, fn1, fn2, fn3,
1932                           fn3.isEmpty() ? fn2 : fn3,
1933                           "", "", "", nullptr);
1934     d->m_selection1Index = QModelIndex();
1935     d->m_selection2Index = QModelIndex();
1936     d->m_selection3Index = QModelIndex();
1937 
1938     Q_EMIT updateAvailabilities();
1939     update();
1940 }
1941 
isFileSelected()1942 bool DirectoryMergeWindow::isFileSelected()
1943 {
1944     if(MergeFileInfos* pMFI = d->getMFI(currentIndex()))
1945     {
1946         return !(pMFI->hasDir() || pMFI->conflictingFileTypes());
1947     }
1948     return false;
1949 }
1950 
mergeResultSaved(const QString & fileName)1951 void DirectoryMergeWindow::mergeResultSaved(const QString& fileName)
1952 {
1953     QModelIndex mi = (d->m_mergeItemList.empty() || d->m_currentIndexForOperation == d->m_mergeItemList.end())
1954                          ? QModelIndex()
1955                          : *d->m_currentIndexForOperation;
1956 
1957     MergeFileInfos* pMFI = d->getMFI(mi);
1958     if(pMFI == nullptr)
1959     {
1960         // This can happen if the same file is saved and modified and saved again. Nothing to do then.
1961         return;
1962     }
1963     if(fileName == pMFI->fullNameDest())
1964     {
1965         if(pMFI->getOperation() == eMergeToAB)
1966         {
1967             bool bSuccess = d->copyFLD(pMFI->fullNameB(), pMFI->fullNameA());
1968             if(!bSuccess)
1969             {
1970                 KMessageBox::error(this, i18n("An error occurred while copying."));
1971                 d->m_pStatusInfo->setWindowTitle(i18n("Merge Error"));
1972                 d->m_pStatusInfo->exec();
1973                 //if ( m_pStatusInfo->firstChild()!=0 )
1974                 //   m_pStatusInfo->ensureItemVisible( m_pStatusInfo->last() );
1975                 d->m_bError = true;
1976                 d->setOpStatus(mi, eOpStatusError);
1977                 pMFI->setOperation(eCopyBToA);
1978                 return;
1979             }
1980         }
1981         d->setOpStatus(mi, eOpStatusDone);
1982         pMFI->endOperation();
1983         if(d->m_mergeItemList.size() == 1)
1984         {
1985             d->m_mergeItemList.clear();
1986             d->m_bRealMergeStarted = false;
1987         }
1988     }
1989 
1990     Q_EMIT updateAvailabilities();
1991 }
1992 
canContinue()1993 bool DirectoryMergeWindow::DirectoryMergeWindowPrivate::canContinue()
1994 {
1995     if(KDiff3App::shouldContinue() && !m_bError)
1996     {
1997         QModelIndex mi = (m_mergeItemList.empty() || m_currentIndexForOperation == m_mergeItemList.end()) ? QModelIndex() : *m_currentIndexForOperation;
1998         MergeFileInfos* pMFI = getMFI(mi);
1999         if(pMFI && pMFI->isOperationRunning())
2000         {
2001             setOpStatus(mi, eOpStatusNotSaved);
2002             pMFI->endOperation();
2003             if(m_mergeItemList.size() == 1)
2004             {
2005                 m_mergeItemList.clear();
2006                 m_bRealMergeStarted = false;
2007             }
2008         }
2009 
2010         return true;
2011     }
2012     return false;
2013 }
2014 
executeMergeOperation(MergeFileInfos & mfi,bool & bSingleFileMerge)2015 bool DirectoryMergeWindow::DirectoryMergeWindowPrivate::executeMergeOperation(MergeFileInfos& mfi, bool& bSingleFileMerge)
2016 {
2017     bool bCreateBackups = m_pOptions->m_bDmCreateBakFiles;
2018     // First decide destname
2019     QString destName;
2020     switch(mfi.getOperation())
2021     {
2022         case eNoOperation:
2023         case eDeleteAB:
2024             break;
2025         case eMergeToAB: // let the user save in B. In mergeResultSaved() the file will be copied to A.
2026         case eMergeToB:
2027         case eDeleteB:
2028         case eCopyAToB:
2029             destName = mfi.fullNameB();
2030             break;
2031         case eMergeToA:
2032         case eDeleteA:
2033         case eCopyBToA:
2034             destName = mfi.fullNameA();
2035             break;
2036         case eMergeABToDest:
2037         case eMergeABCToDest:
2038         case eCopyAToDest:
2039         case eCopyBToDest:
2040         case eCopyCToDest:
2041         case eDeleteFromDest:
2042             destName = mfi.fullNameDest();
2043             break;
2044         default:
2045             KMessageBox::error(mWindow, i18n("Unknown merge operation. (This must never happen!)"));
2046     }
2047 
2048     bool bSuccess = false;
2049     bSingleFileMerge = false;
2050     switch(mfi.getOperation())
2051     {
2052         case eNoOperation:
2053             bSuccess = true;
2054             break;
2055         case eCopyAToDest:
2056         case eCopyAToB:
2057             bSuccess = copyFLD(mfi.fullNameA(), destName);
2058             break;
2059         case eCopyBToDest:
2060         case eCopyBToA:
2061             bSuccess = copyFLD(mfi.fullNameB(), destName);
2062             break;
2063         case eCopyCToDest:
2064             bSuccess = copyFLD(mfi.fullNameC(), destName);
2065             break;
2066         case eDeleteFromDest:
2067         case eDeleteA:
2068         case eDeleteB:
2069             bSuccess = deleteFLD(destName, bCreateBackups);
2070             break;
2071         case eDeleteAB:
2072             bSuccess = deleteFLD(mfi.fullNameA(), bCreateBackups) &&
2073                        deleteFLD(mfi.fullNameB(), bCreateBackups);
2074             break;
2075         case eMergeABToDest:
2076         case eMergeToA:
2077         case eMergeToAB:
2078         case eMergeToB:
2079             bSuccess = mergeFLD(mfi.fullNameA(), mfi.fullNameB(), "",
2080                                 destName, bSingleFileMerge);
2081             break;
2082         case eMergeABCToDest:
2083             bSuccess = mergeFLD(
2084                 mfi.existsInA() ? mfi.fullNameA() : QString(""),
2085                 mfi.existsInB() ? mfi.fullNameB() : QString(""),
2086                 mfi.existsInC() ? mfi.fullNameC() : QString(""),
2087                 destName, bSingleFileMerge);
2088             break;
2089         default:
2090             KMessageBox::error(mWindow, i18n("Unknown merge operation."));
2091     }
2092 
2093     return bSuccess;
2094 }
2095 
2096 // Check if the merge can start, and prepare the m_mergeItemList which then contains all
2097 // items that must be merged.
prepareMergeStart(const QModelIndex & miBegin,const QModelIndex & miEnd,bool bVerbose)2098 void DirectoryMergeWindow::DirectoryMergeWindowPrivate::prepareMergeStart(const QModelIndex& miBegin, const QModelIndex& miEnd, bool bVerbose)
2099 {
2100     if(bVerbose)
2101     {
2102         int status = KMessageBox::warningYesNoCancel(mWindow,
2103                                                      i18n("The merge is about to begin.\n\n"
2104                                                           "Choose \"Do it\" if you have read the instructions and know what you are doing.\n"
2105                                                           "Choosing \"Simulate it\" will tell you what would happen.\n\n"
2106                                                           "Be aware that this program still has beta status "
2107                                                           "and there is NO WARRANTY whatsoever! Make backups of your vital data!"),
2108                                                      i18n("Starting Merge"),
2109                                                      KGuiItem(i18n("Do It")),
2110                                                      KGuiItem(i18n("Simulate It")));
2111         if(status == KMessageBox::Yes)
2112             m_bRealMergeStarted = true;
2113         else if(status == KMessageBox::No)
2114             m_bSimulatedMergeStarted = true;
2115         else
2116             return;
2117     }
2118     else
2119     {
2120         m_bRealMergeStarted = true;
2121     }
2122 
2123     m_mergeItemList.clear();
2124     if(!miBegin.isValid())
2125         return;
2126 
2127     for(QModelIndex mi = miBegin; mi != miEnd; mi = treeIterator(mi))
2128     {
2129         MergeFileInfos* pMFI = getMFI(mi);
2130         if(pMFI && pMFI->isOperationRunning())
2131         {
2132             m_mergeItemList.push_back(mi);
2133             QString errorText;
2134             if(pMFI->getOperation() == eConflictingFileTypes)
2135             {
2136                 errorText = i18n("The highlighted item has a different type in the different folders. Select what to do.");
2137             }
2138             if(pMFI->getOperation() == eConflictingAges)
2139             {
2140                 errorText = i18n("The modification dates of the file are equal but the files are not. Select what to do.");
2141             }
2142             if(pMFI->getOperation() == eChangedAndDeleted)
2143             {
2144                 errorText = i18n("The highlighted item was changed in one folder and deleted in the other. Select what to do.");
2145             }
2146             if(!errorText.isEmpty())
2147             {
2148                 mWindow->scrollTo(mi, QAbstractItemView::EnsureVisible);
2149                 mWindow->setCurrentIndex(mi);
2150                 KMessageBox::error(mWindow, errorText);
2151                 m_mergeItemList.clear();
2152                 m_bRealMergeStarted = false;
2153                 return;
2154             }
2155         }
2156     }
2157 
2158     m_currentIndexForOperation = m_mergeItemList.begin();
2159 }
2160 
slotRunOperationForCurrentItem()2161 void DirectoryMergeWindow::slotRunOperationForCurrentItem()
2162 {
2163     if(!d->canContinue()) return;
2164 
2165     bool bVerbose = false;
2166     if(d->m_mergeItemList.empty())
2167     {
2168         QModelIndex miBegin = currentIndex();
2169         QModelIndex miEnd = d->treeIterator(miBegin, false, false); // find next visible sibling (no children)
2170 
2171         d->prepareMergeStart(miBegin, miEnd, bVerbose);
2172         d->mergeContinue(true, bVerbose);
2173     }
2174     else
2175         d->mergeContinue(false, bVerbose);
2176 }
2177 
slotRunOperationForAllItems()2178 void DirectoryMergeWindow::slotRunOperationForAllItems()
2179 {
2180     if(!d->canContinue()) return;
2181 
2182     bool bVerbose = true;
2183     if(d->m_mergeItemList.empty())
2184     {
2185         QModelIndex miBegin = d->rowCount() > 0 ? d->index(0, 0, QModelIndex()) : QModelIndex();
2186 
2187         d->prepareMergeStart(miBegin, QModelIndex(), bVerbose);
2188         d->mergeContinue(true, bVerbose);
2189     }
2190     else
2191         d->mergeContinue(false, bVerbose);
2192 }
2193 
mergeCurrentFile()2194 void DirectoryMergeWindow::mergeCurrentFile()
2195 {
2196     if(!d->canContinue()) return;
2197 
2198     if(d->m_bRealMergeStarted)
2199     {
2200         KMessageBox::sorry(this, i18n("This operation is currently not possible because folder merge is currently running."), i18n("Operation Not Possible"));
2201         return;
2202     }
2203 
2204     if(isFileSelected())
2205     {
2206         MergeFileInfos* pMFI = d->getMFI(currentIndex());
2207         if(pMFI != nullptr)
2208         {
2209             d->m_mergeItemList.clear();
2210             d->m_mergeItemList.push_back(currentIndex());
2211             d->m_currentIndexForOperation = d->m_mergeItemList.begin();
2212             bool bDummy = false;
2213             d->mergeFLD(
2214                 pMFI->existsInA() ? pMFI->getFileInfoA()->absoluteFilePath() : QString(""),
2215                 pMFI->existsInB() ? pMFI->getFileInfoB()->absoluteFilePath() : QString(""),
2216                 pMFI->existsInC() ? pMFI->getFileInfoC()->absoluteFilePath() : QString(""),
2217                 pMFI->fullNameDest(),
2218                 bDummy);
2219         }
2220     }
2221     Q_EMIT updateAvailabilities();
2222 }
2223 
2224 // When bStart is true then m_currentIndexForOperation must still be processed.
2225 // When bVerbose is true then a messagebox will tell when the merge is complete.
mergeContinue(bool bStart,bool bVerbose)2226 void DirectoryMergeWindow::DirectoryMergeWindowPrivate::mergeContinue(bool bStart, bool bVerbose)
2227 {
2228     ProgressProxy pp;
2229     if(m_mergeItemList.empty())
2230         return;
2231 
2232     int nrOfItems = 0;
2233     int nrOfCompletedItems = 0;
2234     int nrOfCompletedSimItems = 0;
2235 
2236     // Count the number of completed items (for the progress bar).
2237     for(const QModelIndex& i : m_mergeItemList)
2238     {
2239         MergeFileInfos* pMFI = getMFI(i);
2240         ++nrOfItems;
2241         if(!pMFI->isOperationRunning())
2242             ++nrOfCompletedItems;
2243         if(!pMFI->isSimOpRunning())
2244             ++nrOfCompletedSimItems;
2245     }
2246 
2247     m_pStatusInfo->hide();
2248     m_pStatusInfo->clear();
2249 
2250     QModelIndex miCurrent = m_currentIndexForOperation == m_mergeItemList.end() ? QModelIndex() : *m_currentIndexForOperation;
2251 
2252     bool bContinueWithCurrentItem = bStart; // true for first item, else false
2253     bool bSkipItem = false;
2254     if(!bStart && m_bError && miCurrent.isValid())
2255     {
2256         int status = KMessageBox::warningYesNoCancel(mWindow,
2257                                                      i18n("There was an error in the last step.\n"
2258                                                           "Do you want to continue with the item that caused the error or do you want to skip this item?"),
2259                                                      i18n("Continue merge after an error"),
2260                                                      KGuiItem(i18n("Continue With Last Item")),
2261                                                      KGuiItem(i18n("Skip Item")));
2262         if(status == KMessageBox::Yes)
2263             bContinueWithCurrentItem = true;
2264         else if(status == KMessageBox::No)
2265             bSkipItem = true;
2266         else
2267             return;
2268         m_bError = false;
2269     }
2270 
2271     pp.setMaxNofSteps(nrOfItems);
2272 
2273     bool bSuccess = true;
2274     bool bSingleFileMerge = false;
2275     bool bSim = m_bSimulatedMergeStarted;
2276     while(bSuccess)
2277     {
2278         MergeFileInfos* pMFI = getMFI(miCurrent);
2279         if(pMFI == nullptr)
2280         {
2281             m_mergeItemList.clear();
2282             m_bRealMergeStarted = false;
2283             break;
2284         }
2285 
2286         if(pMFI != nullptr && !bContinueWithCurrentItem)
2287         {
2288             if(bSim)
2289             {
2290                 if(rowCount(miCurrent) == 0)
2291                 {
2292                     pMFI->endSimOp();
2293                 }
2294             }
2295             else
2296             {
2297                 if(rowCount(miCurrent) == 0)
2298                 {
2299                     if(pMFI->isOperationRunning())
2300                     {
2301                         setOpStatus(miCurrent, bSkipItem ? eOpStatusSkipped : eOpStatusDone);
2302                         pMFI->endOperation();
2303                         bSkipItem = false;
2304                     }
2305                 }
2306                 else
2307                 {
2308                     setOpStatus(miCurrent, eOpStatusInProgress);
2309                 }
2310             }
2311         }
2312 
2313         if(!bContinueWithCurrentItem)
2314         {
2315             // Depth first
2316             QModelIndex miPrev = miCurrent;
2317             ++m_currentIndexForOperation;
2318             miCurrent = m_currentIndexForOperation == m_mergeItemList.end() ? QModelIndex() : *m_currentIndexForOperation;
2319             if((!miCurrent.isValid() || miCurrent.parent() != miPrev.parent()) && miPrev.parent().isValid())
2320             {
2321                 // Check if the parent may be set to "Done"
2322                 QModelIndex miParent = miPrev.parent();
2323                 bool bDone = true;
2324                 while(bDone && miParent.isValid())
2325                 {
2326                     for(int childIdx = 0; childIdx < rowCount(miParent); ++childIdx)
2327                     {
2328                         pMFI = getMFI(index(childIdx, 0, miParent));
2329                         if((!bSim && pMFI->isOperationRunning()) || (bSim && !pMFI->isSimOpRunning()))
2330                         {
2331                             bDone = false;
2332                             break;
2333                         }
2334                     }
2335                     if(bDone)
2336                     {
2337                         pMFI = getMFI(miParent);
2338                         if(bSim)
2339                             pMFI->endSimOp();
2340                         else
2341                         {
2342                             setOpStatus(miParent, eOpStatusDone);
2343                             pMFI->endOperation();
2344                         }
2345                     }
2346                     miParent = miParent.parent();
2347                 }
2348             }
2349         }
2350 
2351         if(!miCurrent.isValid()) // end?
2352         {
2353             if(m_bRealMergeStarted)
2354             {
2355                 if(bVerbose)
2356                 {
2357                     KMessageBox::information(mWindow, i18n("Merge operation complete."), i18n("Merge Complete"));
2358                 }
2359                 m_bRealMergeStarted = false;
2360                 m_pStatusInfo->setWindowTitle(i18n("Merge Complete"));
2361             }
2362             if(m_bSimulatedMergeStarted)
2363             {
2364                 m_bSimulatedMergeStarted = false;
2365                 QModelIndex mi = rowCount() > 0 ? index(0, 0, QModelIndex()) : QModelIndex();
2366                 for(; mi.isValid(); mi = treeIterator(mi))
2367                 {
2368                     getMFI(mi)->startSimOp();
2369                 }
2370                 m_pStatusInfo->setWindowTitle(i18n("Simulated merge complete: Check if you agree with the proposed operations."));
2371                 m_pStatusInfo->exec();
2372             }
2373             m_mergeItemList.clear();
2374             m_bRealMergeStarted = false;
2375             return;
2376         }
2377 
2378         pMFI = getMFI(miCurrent);
2379 
2380         pp.setInformation(pMFI->subPath(),
2381                           bSim ? nrOfCompletedSimItems : nrOfCompletedItems,
2382                           false // bRedrawUpdate
2383         );
2384 
2385         bSuccess = executeMergeOperation(*pMFI, bSingleFileMerge); // Here the real operation happens.
2386 
2387         if(bSuccess)
2388         {
2389             if(bSim)
2390                 ++nrOfCompletedSimItems;
2391             else
2392                 ++nrOfCompletedItems;
2393             bContinueWithCurrentItem = false;
2394         }
2395 
2396         if(pp.wasCancelled())
2397             break;
2398     } // end while
2399 
2400     //g_pProgressDialog->hide();
2401 
2402     mWindow->setCurrentIndex(miCurrent);
2403     mWindow->scrollTo(miCurrent, EnsureVisible);
2404     if(!bSuccess && !bSingleFileMerge)
2405     {
2406         KMessageBox::error(mWindow, i18n("An error occurred. Press OK to see detailed information."));
2407         m_pStatusInfo->setWindowTitle(i18n("Merge Error"));
2408         m_pStatusInfo->exec();
2409         //if ( m_pStatusInfo->firstChild()!=0 )
2410         //   m_pStatusInfo->ensureItemVisible( m_pStatusInfo->last() );
2411         m_bError = true;
2412 
2413         setOpStatus(miCurrent, eOpStatusError);
2414     }
2415     else
2416     {
2417         m_bError = false;
2418     }
2419     Q_EMIT mWindow->updateAvailabilities();
2420 
2421     if(m_currentIndexForOperation == m_mergeItemList.end())
2422     {
2423         m_mergeItemList.clear();
2424         m_bRealMergeStarted = false;
2425     }
2426 }
2427 
deleteFLD(const QString & name,bool bCreateBackup)2428 bool DirectoryMergeWindow::DirectoryMergeWindowPrivate::deleteFLD(const QString& name, bool bCreateBackup)
2429 {
2430     FileAccess fi(name, true);
2431     if(!fi.exists())
2432         return true;
2433 
2434     if(bCreateBackup)
2435     {
2436         bool bSuccess = renameFLD(name, name + ".orig");
2437         if(!bSuccess)
2438         {
2439             m_pStatusInfo->addText(i18n("Error: While deleting %1: Creating backup failed.", name));
2440             return false;
2441         }
2442     }
2443     else
2444     {
2445         if(fi.isDir() && !fi.isSymLink())
2446             m_pStatusInfo->addText(i18n("delete folder recursively( %1 )", name));
2447         else
2448             m_pStatusInfo->addText(i18n("delete( %1 )", name));
2449 
2450         if(m_bSimulatedMergeStarted)
2451         {
2452             return true;
2453         }
2454 
2455         if(fi.isDir() && !fi.isSymLink()) // recursive directory delete only for real dirs, not symlinks
2456         {
2457             t_DirectoryList dirList;
2458             bool bSuccess = fi.listDir(&dirList, false, true, "*", "", "", false, false); // not recursive, find hidden files
2459 
2460             if(!bSuccess)
2461             {
2462                 // No Permission to read directory or other error.
2463                 m_pStatusInfo->addText(i18n("Error: delete folder operation failed while trying to read the folder."));
2464                 return false;
2465             }
2466 
2467             for(const FileAccess& fi2: dirList) // for each file...
2468             {
2469                 Q_ASSERT(fi2.fileName() != "." && fi2.fileName() != "..");
2470 
2471                 bSuccess = deleteFLD(fi2.absoluteFilePath(), false);
2472                 if(!bSuccess) break;
2473             }
2474             if(bSuccess)
2475             {
2476                 bSuccess = FileAccess::removeDir(name);
2477                 if(!bSuccess)
2478                 {
2479                     m_pStatusInfo->addText(i18n("Error: rmdir( %1 ) operation failed.", name)); // krazy:exclude=syscalls
2480                     return false;
2481                 }
2482             }
2483         }
2484         else
2485         {
2486             bool bSuccess = fi.removeFile();
2487             if(!bSuccess)
2488             {
2489                 m_pStatusInfo->addText(i18n("Error: delete operation failed."));
2490                 return false;
2491             }
2492         }
2493     }
2494     return true;
2495 }
2496 
mergeFLD(const QString & nameA,const QString & nameB,const QString & nameC,const QString & nameDest,bool & bSingleFileMerge)2497 bool DirectoryMergeWindow::DirectoryMergeWindowPrivate::mergeFLD(const QString& nameA, const QString& nameB, const QString& nameC, const QString& nameDest, bool& bSingleFileMerge)
2498 {
2499     FileAccess fi(nameA);
2500     if(fi.isDir())
2501     {
2502         return makeDir(nameDest);
2503     }
2504 
2505     QStringList errors;
2506     // Make sure that the dir exists, into which we will save the file later.
2507     int pos = nameDest.lastIndexOf('/');
2508     if(pos > 0)
2509     {
2510         QString parentName = nameDest.left(pos);
2511         bool bSuccess = makeDir(parentName, true /*quiet*/);
2512         if(!bSuccess)
2513             return false;
2514     }
2515 
2516     m_pStatusInfo->addText(i18n("manual merge( %1, %2, %3 -> %4)", nameA, nameB, nameC, nameDest));
2517     if(m_bSimulatedMergeStarted)
2518     {
2519         m_pStatusInfo->addText(i18n("     Note: After a manual merge the user should continue by pressing F7."));
2520         return true;
2521     }
2522 
2523     bSingleFileMerge = true;
2524     setOpStatus(*m_currentIndexForOperation, eOpStatusInProgress);
2525     mWindow->scrollTo(*m_currentIndexForOperation, EnsureVisible);
2526 
2527     Q_EMIT mWindow->startDiffMerge(errors, nameA, nameB, nameC, nameDest, "", "", "", nullptr);
2528 
2529     return false;
2530 }
2531 
copyFLD(const QString & srcName,const QString & destName)2532 bool DirectoryMergeWindow::DirectoryMergeWindowPrivate::copyFLD(const QString& srcName, const QString& destName)
2533 {
2534     bool bSuccess = false;
2535 
2536     if(srcName == destName)
2537         return true;
2538 
2539     FileAccess fi(srcName);
2540     FileAccess faDest(destName, true);
2541     if(faDest.exists() && !(fi.isDir() && faDest.isDir() && (fi.isSymLink() == faDest.isSymLink())))
2542     {
2543         bSuccess = deleteFLD(destName, m_pOptions->m_bDmCreateBakFiles);
2544         if(!bSuccess)
2545         {
2546             m_pStatusInfo->addText(i18n("Error: copy( %1 -> %2 ) failed."
2547                                         "Deleting existing destination failed.",
2548                                         srcName, destName));
2549             return bSuccess;
2550         }
2551     }
2552 
2553     if(fi.isSymLink() && ((fi.isDir() && !m_bFollowDirLinks) || (!fi.isDir() && !m_bFollowFileLinks)))
2554     {
2555         m_pStatusInfo->addText(i18n("copyLink( %1 -> %2 )", srcName, destName));
2556 
2557         if(m_bSimulatedMergeStarted)
2558         {
2559             return true;
2560         }
2561         FileAccess destFi(destName);
2562         if(!destFi.isLocal() || !fi.isLocal())
2563         {
2564             m_pStatusInfo->addText(i18n("Error: copyLink failed: Remote links are not yet supported."));
2565             return false;
2566         }
2567 
2568         bSuccess = false;
2569         QString linkTarget = fi.readLink();
2570         if(!linkTarget.isEmpty())
2571         {
2572             bSuccess = FileAccess::symLink(linkTarget, destName);
2573             if(!bSuccess)
2574                 m_pStatusInfo->addText(i18n("Error: copyLink failed."));
2575         }
2576         return bSuccess;
2577     }
2578 
2579     if(fi.isDir())
2580     {
2581         if(faDest.exists())
2582             return true;
2583 
2584         bSuccess = makeDir(destName);
2585         return bSuccess;
2586     }
2587 
2588     int pos = destName.lastIndexOf('/');
2589     if(pos > 0)
2590     {
2591         QString parentName = destName.left(pos);
2592         bSuccess = makeDir(parentName, true /*quiet*/);
2593         if(!bSuccess)
2594             return false;
2595     }
2596 
2597     m_pStatusInfo->addText(i18n("copy( %1 -> %2 )", srcName, destName));
2598 
2599     if(m_bSimulatedMergeStarted)
2600     {
2601         return true;
2602     }
2603 
2604     FileAccess faSrc(srcName);
2605     bSuccess = faSrc.copyFile(destName);
2606     if(!bSuccess) m_pStatusInfo->addText(faSrc.getStatusText());
2607     return bSuccess;
2608 }
2609 
2610 // Rename is not an operation that can be selected by the user.
2611 // It will only be used to create backups.
2612 // Hence it will delete an existing destination without making a backup (of the old backup.)
renameFLD(const QString & srcName,const QString & destName)2613 bool DirectoryMergeWindow::DirectoryMergeWindowPrivate::renameFLD(const QString& srcName, const QString& destName)
2614 {
2615     if(srcName == destName)
2616         return true;
2617     FileAccess destFile = FileAccess(destName, true);
2618     if(destFile.exists())
2619     {
2620         bool bSuccess = deleteFLD(destName, false /*no backup*/);
2621         if(!bSuccess)
2622         {
2623             m_pStatusInfo->addText(i18n("Error during rename( %1 -> %2 ): "
2624                                         "Cannot delete existing destination.",
2625                                         srcName, destName));
2626             return false;
2627         }
2628     }
2629 
2630     m_pStatusInfo->addText(i18n("rename( %1 -> %2 )", srcName, destName));
2631     if(m_bSimulatedMergeStarted)
2632     {
2633         return true;
2634     }
2635 
2636     bool bSuccess = FileAccess(srcName).rename(destFile);
2637     if(!bSuccess)
2638     {
2639         m_pStatusInfo->addText(i18n("Error: Rename failed."));
2640         return false;
2641     }
2642 
2643     return true;
2644 }
2645 
makeDir(const QString & name,bool bQuiet)2646 bool DirectoryMergeWindow::DirectoryMergeWindowPrivate::makeDir(const QString& name, bool bQuiet)
2647 {
2648     FileAccess fi(name, true);
2649     if(fi.exists() && fi.isDir())
2650         return true;
2651 
2652     if(fi.exists() && !fi.isDir())
2653     {
2654         bool bSuccess = deleteFLD(name, true);
2655         if(!bSuccess)
2656         {
2657             m_pStatusInfo->addText(i18n("Error during makeDir of %1. "
2658                                         "Cannot delete existing file.",
2659                                         name));
2660             return false;
2661         }
2662     }
2663 
2664     int pos = name.lastIndexOf('/');
2665     if(pos > 0)
2666     {
2667         QString parentName = name.left(pos);
2668         bool bSuccess = makeDir(parentName, true);
2669         if(!bSuccess)
2670             return false;
2671     }
2672 
2673     if(!bQuiet)
2674         m_pStatusInfo->addText(i18n("makeDir( %1 )", name));
2675 
2676     if(m_bSimulatedMergeStarted)
2677     {
2678         return true;
2679     }
2680 
2681     bool bSuccess = FileAccess::makeDir(name);
2682     if(!bSuccess)
2683     {
2684         m_pStatusInfo->addText(i18n("Error while creating folder."));
2685         return false;
2686     }
2687     return true;
2688 }
2689 
DirectoryMergeInfo(QWidget * pParent)2690 DirectoryMergeInfo::DirectoryMergeInfo(QWidget* pParent)
2691     : QFrame(pParent)
2692 {
2693     QVBoxLayout* topLayout = new QVBoxLayout(this);
2694     topLayout->setContentsMargins(0, 0, 0, 0);
2695 
2696     QGridLayout* grid = new QGridLayout();
2697     topLayout->addLayout(grid);
2698     grid->setColumnStretch(1, 10);
2699 
2700     int line = 0;
2701 
2702     m_pA = new QLabel(i18n("A"), this);
2703     grid->addWidget(m_pA, line, 0);
2704     m_pInfoA = new QLabel(this);
2705     grid->addWidget(m_pInfoA, line, 1);
2706     ++line;
2707 
2708     m_pB = new QLabel(i18n("B"), this);
2709     grid->addWidget(m_pB, line, 0);
2710     m_pInfoB = new QLabel(this);
2711     grid->addWidget(m_pInfoB, line, 1);
2712     ++line;
2713 
2714     m_pC = new QLabel(i18n("C"), this);
2715     grid->addWidget(m_pC, line, 0);
2716     m_pInfoC = new QLabel(this);
2717     grid->addWidget(m_pInfoC, line, 1);
2718     ++line;
2719 
2720     m_pDest = new QLabel(i18n("Dest"), this);
2721     grid->addWidget(m_pDest, line, 0);
2722     m_pInfoDest = new QLabel(this);
2723     grid->addWidget(m_pInfoDest, line, 1);
2724     ++line;
2725 
2726     m_pInfoList = new QTreeWidget(this);
2727     topLayout->addWidget(m_pInfoList);
2728     m_pInfoList->setHeaderLabels({i18n("Folder"), i18n("Type"), i18n("Size"),
2729                                   i18n("Attr"), i18n("Last Modification"), i18n("Link-Destination")});
2730     setMinimumSize(100, 100);
2731 
2732     m_pInfoList->installEventFilter(this);
2733     m_pInfoList->setRootIsDecorated(false);
2734 }
2735 
eventFilter(QObject * o,QEvent * e)2736 bool DirectoryMergeInfo::eventFilter(QObject* o, QEvent* e)
2737 {
2738     if(e->type() == QEvent::FocusIn && o == m_pInfoList)
2739         Q_EMIT gotFocus();
2740     return false;
2741 }
2742 
addListViewItem(const QString & dir,const QString & basePath,FileAccess * fi)2743 void DirectoryMergeInfo::addListViewItem(const QString& dir, const QString& basePath, FileAccess* fi)
2744 {
2745     if(basePath.isEmpty())
2746     {
2747         return;
2748     }
2749 
2750     if(fi != nullptr && fi->exists())
2751     {
2752         QString dateString = fi->lastModified().toString(QLocale::system().dateTimeFormat());
2753 
2754         m_pInfoList->addTopLevelItem(new QTreeWidgetItem(
2755             m_pInfoList,
2756             {dir, QString(fi->isDir() ? i18n("Folder") : i18n("File")) + (fi->isSymLink() ? i18n("-Link") : ""), QString::number(fi->size()), QLatin1String(fi->isReadable() ? "r" : " ") + QLatin1String(fi->isWritable() ? "w" : " ") + QLatin1String((fi->isExecutable() ? "x" : " ")), dateString, QString(fi->isSymLink() ? (" -> " + fi->readLink()) : QString(""))}));
2757     }
2758     else
2759     {
2760         m_pInfoList->addTopLevelItem(new QTreeWidgetItem(
2761             m_pInfoList,
2762             {dir, i18n("not available"), "", "", "", ""}));
2763     }
2764 }
2765 
setInfo(const FileAccess & dirA,const FileAccess & dirB,const FileAccess & dirC,const FileAccess & dirDest,MergeFileInfos & mfi)2766 void DirectoryMergeInfo::setInfo(
2767     const FileAccess& dirA,
2768     const FileAccess& dirB,
2769     const FileAccess& dirC,
2770     const FileAccess& dirDest,
2771     MergeFileInfos& mfi)
2772 {
2773     bool bHideDest = false;
2774     if(dirA.absoluteFilePath() == dirDest.absoluteFilePath())
2775     {
2776         m_pA->setText(i18n("A (Dest): "));
2777         bHideDest = true;
2778     }
2779     else
2780         m_pA->setText(!dirC.isValid() ? i18n("A:    ") : i18n("A (Base): "));
2781 
2782     m_pInfoA->setText(dirA.prettyAbsPath());
2783 
2784     if(dirB.absoluteFilePath() == dirDest.absoluteFilePath())
2785     {
2786         m_pB->setText(i18n("B (Dest): "));
2787         bHideDest = true;
2788     }
2789     else
2790         m_pB->setText(i18n("B:    "));
2791     m_pInfoB->setText(dirB.prettyAbsPath());
2792 
2793     if(dirC.absoluteFilePath() == dirDest.absoluteFilePath())
2794     {
2795         m_pC->setText(i18n("C (Dest): "));
2796         bHideDest = true;
2797     }
2798     else
2799         m_pC->setText(i18n("C:    "));
2800     m_pInfoC->setText(dirC.prettyAbsPath());
2801 
2802     m_pDest->setText(i18n("Dest: "));
2803     m_pInfoDest->setText(dirDest.prettyAbsPath());
2804 
2805     if(!dirC.isValid())
2806     {
2807         m_pC->hide();
2808         m_pInfoC->hide();
2809     }
2810     else
2811     {
2812         m_pC->show();
2813         m_pInfoC->show();
2814     }
2815 
2816     if(!dirDest.isValid() || bHideDest)
2817     {
2818         m_pDest->hide();
2819         m_pInfoDest->hide();
2820     }
2821     else
2822     {
2823         m_pDest->show();
2824         m_pInfoDest->show();
2825     }
2826 
2827     m_pInfoList->clear();
2828     addListViewItem(i18n("A"), dirA.prettyAbsPath(), mfi.getFileInfoA());
2829     addListViewItem(i18n("B"), dirB.prettyAbsPath(), mfi.getFileInfoB());
2830     addListViewItem(i18n("C"), dirC.prettyAbsPath(), mfi.getFileInfoC());
2831     if(!bHideDest)
2832     {
2833         FileAccess fiDest(dirDest.prettyAbsPath() + '/' + mfi.subPath(), true);
2834         addListViewItem(i18n("Dest"), dirDest.prettyAbsPath(), &fiDest);
2835     }
2836     for(int i = 0; i < m_pInfoList->columnCount(); ++i)
2837         m_pInfoList->resizeColumnToContents(i);
2838 }
2839 
slotSaveMergeState()2840 void DirectoryMergeWindow::slotSaveMergeState()
2841 {
2842     //slotStatusMsg(i18n("Saving Directory Merge State ..."));
2843 
2844     QString dirMergeStateFilename = QFileDialog::getSaveFileName(this, i18n("Save Folder Merge State As..."), QDir::currentPath());
2845     if(!dirMergeStateFilename.isEmpty())
2846     {
2847         QFile file(dirMergeStateFilename);
2848         bool bSuccess = file.open(QIODevice::WriteOnly);
2849         if(bSuccess)
2850         {
2851             QTextStream ts(&file);
2852 
2853             QModelIndex mi(d->index(0, 0, QModelIndex()));
2854             while(mi.isValid())
2855             {
2856                 MergeFileInfos* pMFI = d->getMFI(mi);
2857                 ts << *pMFI;
2858                 mi = d->treeIterator(mi, true, true);
2859             }
2860         }
2861     }
2862 
2863     //slotStatusMsg(i18n("Ready."));
2864 }
2865 
slotLoadMergeState()2866 void DirectoryMergeWindow::slotLoadMergeState()
2867 {
2868 }
2869 
updateFileVisibilities()2870 void DirectoryMergeWindow::updateFileVisibilities()
2871 {
2872     bool bShowIdentical = d->m_pDirShowIdenticalFiles->isChecked();
2873     bool bShowDifferent = d->m_pDirShowDifferentFiles->isChecked();
2874     bool bShowOnlyInA = d->m_pDirShowFilesOnlyInA->isChecked();
2875     bool bShowOnlyInB = d->m_pDirShowFilesOnlyInB->isChecked();
2876     bool bShowOnlyInC = d->m_pDirShowFilesOnlyInC->isChecked();
2877     bool bThreeDirs = d->isThreeWay();
2878     d->m_selection1Index = QModelIndex();
2879     d->m_selection2Index = QModelIndex();
2880     d->m_selection3Index = QModelIndex();
2881 
2882     // in first run set all dirs to equal and determine if they are not equal.
2883     // on second run don't change the equal-status anymore; it is needed to
2884     // set the visibility (when bShowIdentical is false).
2885     for(int loop = 0; loop < 2; ++loop)
2886     {
2887         QModelIndex mi = d->rowCount() > 0 ? d->index(0, 0, QModelIndex()) : QModelIndex();
2888         while(mi.isValid())
2889         {
2890             MergeFileInfos* pMFI = d->getMFI(mi);
2891             bool bDir = pMFI->hasDir();
2892             if(loop == 0 && bDir)
2893             { //Treat all links and directories to equal by default.
2894                 pMFI->updateDirectoryOrLink();
2895             }
2896 
2897             bool bVisible =
2898                 (bShowIdentical && pMFI->existsEveryWhere() && pMFI->isEqualAB() && (pMFI->isEqualAC() || !bThreeDirs)) ||
2899                 ((bShowDifferent || bDir) && pMFI->existsCount() >= 2 && (!pMFI->isEqualAB() || !(pMFI->isEqualAC() || !bThreeDirs))) ||
2900                 (bShowOnlyInA && pMFI->onlyInA()) || (bShowOnlyInB && pMFI->onlyInB()) || (bShowOnlyInC && pMFI->onlyInC());
2901 
2902             QString fileName = pMFI->fileName();
2903             bVisible = bVisible && ((bDir && !Utils::wildcardMultiMatch(d->m_pOptions->m_DmDirAntiPattern, fileName, d->m_bCaseSensitive)) || (Utils::wildcardMultiMatch(d->m_pOptions->m_DmFilePattern, fileName, d->m_bCaseSensitive) && !Utils::wildcardMultiMatch(d->m_pOptions->m_DmFileAntiPattern, fileName, d->m_bCaseSensitive)));
2904 
2905             if(loop != 0)
2906                 setRowHidden(mi.row(), mi.parent(), !bVisible);
2907 
2908             bool bEqual = bThreeDirs ? pMFI->isEqualAB() && pMFI->isEqualAC() : pMFI->isEqualAB();
2909             if(!bEqual && bVisible && loop == 0) // Set all parents to "not equal"
2910             {
2911                 pMFI->updateParents();
2912             }
2913             mi = d->treeIterator(mi, true, true);
2914         }
2915     }
2916 }
2917 
slotShowIdenticalFiles()2918 void DirectoryMergeWindow::slotShowIdenticalFiles()
2919 {
2920     d->m_pOptions->m_bDmShowIdenticalFiles = d->m_pDirShowIdenticalFiles->isChecked();
2921     updateFileVisibilities();
2922 }
slotShowDifferentFiles()2923 void DirectoryMergeWindow::slotShowDifferentFiles()
2924 {
2925     updateFileVisibilities();
2926 }
slotShowFilesOnlyInA()2927 void DirectoryMergeWindow::slotShowFilesOnlyInA()
2928 {
2929     updateFileVisibilities();
2930 }
slotShowFilesOnlyInB()2931 void DirectoryMergeWindow::slotShowFilesOnlyInB()
2932 {
2933     updateFileVisibilities();
2934 }
slotShowFilesOnlyInC()2935 void DirectoryMergeWindow::slotShowFilesOnlyInC()
2936 {
2937     updateFileVisibilities();
2938 }
2939 
slotSynchronizeDirectories()2940 void DirectoryMergeWindow::slotSynchronizeDirectories() {}
slotChooseNewerFiles()2941 void DirectoryMergeWindow::slotChooseNewerFiles() {}
2942 
initDirectoryMergeActions(KDiff3App * pKDiff3App,KActionCollection * ac)2943 void DirectoryMergeWindow::initDirectoryMergeActions(KDiff3App* pKDiff3App, KActionCollection* ac)
2944 {
2945 #include "xpm/showequalfiles.xpm"
2946 #include "xpm/showfilesonlyina.xpm"
2947 #include "xpm/showfilesonlyinb.xpm"
2948 #include "xpm/showfilesonlyinc.xpm"
2949 #include "xpm/startmerge.xpm"
2950 
2951     d->m_pDirStartOperation = GuiUtils::createAction<QAction>(i18n("Start/Continue Folder Merge"), QKeySequence(Qt::Key_F7), this, &DirectoryMergeWindow::slotRunOperationForAllItems, ac, "dir_start_operation");
2952     d->m_pDirRunOperationForCurrentItem = GuiUtils::createAction<QAction>(i18n("Run Operation for Current Item"), QKeySequence(Qt::Key_F6), this, &DirectoryMergeWindow::slotRunOperationForCurrentItem, ac, "dir_run_operation_for_current_item");
2953     d->m_pDirCompareCurrent = GuiUtils::createAction<QAction>(i18n("Compare Selected File"), this, &DirectoryMergeWindow::compareCurrentFile, ac, "dir_compare_current");
2954     d->m_pDirMergeCurrent = GuiUtils::createAction<QAction>(i18n("Merge Current File"), QIcon(QPixmap(startmerge)), i18n("Merge\nFile"), pKDiff3App, &KDiff3App::slotMergeCurrentFile, ac, "merge_current");
2955     d->m_pDirFoldAll = GuiUtils::createAction<QAction>(i18n("Fold All Subfolders"), this, &DirectoryMergeWindow::collapseAll, ac, "dir_fold_all");
2956     d->m_pDirUnfoldAll = GuiUtils::createAction<QAction>(i18n("Unfold All Subfolders"), this, &DirectoryMergeWindow::expandAll, ac, "dir_unfold_all");
2957     d->m_pDirRescan = GuiUtils::createAction<QAction>(i18n("Rescan"), QKeySequence(Qt::SHIFT + Qt::Key_F5), this, &DirectoryMergeWindow::reload, ac, "dir_rescan");
2958     d->m_pDirSaveMergeState = nullptr; //GuiUtils::createAction< QAction >(i18n("Save Directory Merge State ..."), 0, this, &DirectoryMergeWindow::slotSaveMergeState, ac, "dir_save_merge_state");
2959     d->m_pDirLoadMergeState = nullptr; //GuiUtils::createAction< QAction >(i18n("Load Directory Merge State ..."), 0, this, &DirectoryMergeWindow::slotLoadMergeState, ac, "dir_load_merge_state");
2960     d->m_pDirChooseAEverywhere = GuiUtils::createAction<QAction>(i18n("Choose A for All Items"), this, &DirectoryMergeWindow::slotChooseAEverywhere, ac, "dir_choose_a_everywhere");
2961     d->m_pDirChooseBEverywhere = GuiUtils::createAction<QAction>(i18n("Choose B for All Items"), this, &DirectoryMergeWindow::slotChooseBEverywhere, ac, "dir_choose_b_everywhere");
2962     d->m_pDirChooseCEverywhere = GuiUtils::createAction<QAction>(i18n("Choose C for All Items"), this, &DirectoryMergeWindow::slotChooseCEverywhere, ac, "dir_choose_c_everywhere");
2963     d->m_pDirAutoChoiceEverywhere = GuiUtils::createAction<QAction>(i18n("Auto-Choose Operation for All Items"), this, &DirectoryMergeWindow::slotAutoChooseEverywhere, ac, "dir_autochoose_everywhere");
2964     d->m_pDirDoNothingEverywhere = GuiUtils::createAction<QAction>(i18n("No Operation for All Items"), this, &DirectoryMergeWindow::slotNoOpEverywhere, ac, "dir_nothing_everywhere");
2965 
2966     //   d->m_pDirSynchronizeDirectories = GuiUtils::createAction< KToggleAction >(i18n("Synchronize Directories"), 0, this, &DirectoryMergeWindow::slotSynchronizeDirectories, ac, "dir_synchronize_directories");
2967     //   d->m_pDirChooseNewerFiles = GuiUtils::createAction< KToggleAction >(i18n("Copy Newer Files Instead of Merging"), 0, this, &DirectoryMergeWindow::slotChooseNewerFiles, ac, "dir_choose_newer_files");
2968 
2969     d->m_pDirShowIdenticalFiles = GuiUtils::createAction<KToggleAction>(i18n("Show Identical Files"), QIcon(QPixmap(showequalfiles)), i18n("Identical\nFiles"), this, &DirectoryMergeWindow::slotShowIdenticalFiles, ac, "dir_show_identical_files");
2970     d->m_pDirShowDifferentFiles = GuiUtils::createAction<KToggleAction>(i18n("Show Different Files"), this, &DirectoryMergeWindow::slotShowDifferentFiles, ac, "dir_show_different_files");
2971     d->m_pDirShowFilesOnlyInA = GuiUtils::createAction<KToggleAction>(i18n("Show Files only in A"), QIcon(QPixmap(showfilesonlyina)), i18n("Files\nonly in A"), this, &DirectoryMergeWindow::slotShowFilesOnlyInA, ac, "dir_show_files_only_in_a");
2972     d->m_pDirShowFilesOnlyInB = GuiUtils::createAction<KToggleAction>(i18n("Show Files only in B"), QIcon(QPixmap(showfilesonlyinb)), i18n("Files\nonly in B"), this, &DirectoryMergeWindow::slotShowFilesOnlyInB, ac, "dir_show_files_only_in_b");
2973     d->m_pDirShowFilesOnlyInC = GuiUtils::createAction<KToggleAction>(i18n("Show Files only in C"), QIcon(QPixmap(showfilesonlyinc)), i18n("Files\nonly in C"), this, &DirectoryMergeWindow::slotShowFilesOnlyInC, ac, "dir_show_files_only_in_c");
2974 
2975     d->m_pDirShowIdenticalFiles->setChecked(d->m_pOptions->m_bDmShowIdenticalFiles);
2976 
2977     d->m_pDirCompareExplicit = GuiUtils::createAction<QAction>(i18n("Compare Explicitly Selected Files"), this, &DirectoryMergeWindow::slotCompareExplicitlySelectedFiles, ac, "dir_compare_explicitly_selected_files");
2978     d->m_pDirMergeExplicit = GuiUtils::createAction<QAction>(i18n("Merge Explicitly Selected Files"), this, &DirectoryMergeWindow::slotMergeExplicitlySelectedFiles, ac, "dir_merge_explicitly_selected_files");
2979 
2980     d->m_pDirCurrentDoNothing = GuiUtils::createAction<QAction>(i18n("Do Nothing"), this, &DirectoryMergeWindow::slotCurrentDoNothing, ac, "dir_current_do_nothing");
2981     d->m_pDirCurrentChooseA = GuiUtils::createAction<QAction>(i18n("A"), this, &DirectoryMergeWindow::slotCurrentChooseA, ac, "dir_current_choose_a");
2982     d->m_pDirCurrentChooseB = GuiUtils::createAction<QAction>(i18n("B"), this, &DirectoryMergeWindow::slotCurrentChooseB, ac, "dir_current_choose_b");
2983     d->m_pDirCurrentChooseC = GuiUtils::createAction<QAction>(i18n("C"), this, &DirectoryMergeWindow::slotCurrentChooseC, ac, "dir_current_choose_c");
2984     d->m_pDirCurrentMerge = GuiUtils::createAction<QAction>(i18n("Merge"), this, &DirectoryMergeWindow::slotCurrentMerge, ac, "dir_current_merge");
2985     d->m_pDirCurrentDelete = GuiUtils::createAction<QAction>(i18n("Delete (if exists)"), this, &DirectoryMergeWindow::slotCurrentDelete, ac, "dir_current_delete");
2986 
2987     d->m_pDirCurrentSyncDoNothing = GuiUtils::createAction<QAction>(i18n("Do Nothing"), this, &DirectoryMergeWindow::slotCurrentDoNothing, ac, "dir_current_sync_do_nothing");
2988     d->m_pDirCurrentSyncCopyAToB = GuiUtils::createAction<QAction>(i18n("Copy A to B"), this, &DirectoryMergeWindow::slotCurrentCopyAToB, ac, "dir_current_sync_copy_a_to_b");
2989     d->m_pDirCurrentSyncCopyBToA = GuiUtils::createAction<QAction>(i18n("Copy B to A"), this, &DirectoryMergeWindow::slotCurrentCopyBToA, ac, "dir_current_sync_copy_b_to_a");
2990     d->m_pDirCurrentSyncDeleteA = GuiUtils::createAction<QAction>(i18n("Delete A"), this, &DirectoryMergeWindow::slotCurrentDeleteA, ac, "dir_current_sync_delete_a");
2991     d->m_pDirCurrentSyncDeleteB = GuiUtils::createAction<QAction>(i18n("Delete B"), this, &DirectoryMergeWindow::slotCurrentDeleteB, ac, "dir_current_sync_delete_b");
2992     d->m_pDirCurrentSyncDeleteAAndB = GuiUtils::createAction<QAction>(i18n("Delete A && B"), this, &DirectoryMergeWindow::slotCurrentDeleteAAndB, ac, "dir_current_sync_delete_a_and_b");
2993     d->m_pDirCurrentSyncMergeToA = GuiUtils::createAction<QAction>(i18n("Merge to A"), this, &DirectoryMergeWindow::slotCurrentMergeToA, ac, "dir_current_sync_merge_to_a");
2994     d->m_pDirCurrentSyncMergeToB = GuiUtils::createAction<QAction>(i18n("Merge to B"), this, &DirectoryMergeWindow::slotCurrentMergeToB, ac, "dir_current_sync_merge_to_b");
2995     d->m_pDirCurrentSyncMergeToAAndB = GuiUtils::createAction<QAction>(i18n("Merge to A && B"), this, &DirectoryMergeWindow::slotCurrentMergeToAAndB, ac, "dir_current_sync_merge_to_a_and_b");
2996 }
2997 
setupConnections(const KDiff3App * app)2998 void DirectoryMergeWindow::setupConnections(const KDiff3App* app)
2999 {
3000     chk_connect(this, &DirectoryMergeWindow::startDiffMerge, app, &KDiff3App::slotFileOpen2);
3001     chk_connect(selectionModel(), &QItemSelectionModel::selectionChanged, app, &KDiff3App::slotUpdateAvailabilities);
3002     chk_connect(selectionModel(), &QItemSelectionModel::currentChanged, app, &KDiff3App::slotUpdateAvailabilities);
3003     chk_connect(this, static_cast<void (DirectoryMergeWindow::*) (void)>(&DirectoryMergeWindow::updateAvailabilities), app, &KDiff3App::slotUpdateAvailabilities);
3004     chk_connect(this, &DirectoryMergeWindow::statusBarMessage, app, &KDiff3App::slotStatusMsg);
3005     chk_connect(app, &KDiff3App::doRefresh, this, &DirectoryMergeWindow::slotRefresh);
3006 }
3007 
updateAvailabilities(bool bMergeEditorVisible,bool bDirCompare,bool bDiffWindowVisible,KToggleAction * chooseA,KToggleAction * chooseB,KToggleAction * chooseC)3008 void DirectoryMergeWindow::updateAvailabilities(bool bMergeEditorVisible, bool bDirCompare, bool bDiffWindowVisible,
3009                                                 KToggleAction* chooseA, KToggleAction* chooseB, KToggleAction* chooseC)
3010 {
3011     d->m_pDirStartOperation->setEnabled(bDirCompare);
3012     d->m_pDirRunOperationForCurrentItem->setEnabled(bDirCompare);
3013     d->m_pDirFoldAll->setEnabled(bDirCompare);
3014     d->m_pDirUnfoldAll->setEnabled(bDirCompare);
3015 
3016     d->m_pDirCompareCurrent->setEnabled(bDirCompare && isVisible() && isFileSelected());
3017 
3018     d->m_pDirMergeCurrent->setEnabled((bDirCompare && isVisible() && isFileSelected()) || bDiffWindowVisible);
3019 
3020     d->m_pDirRescan->setEnabled(bDirCompare);
3021 
3022     bool bThreeDirs = d->isThreeWay();
3023     d->m_pDirAutoChoiceEverywhere->setEnabled(bDirCompare && isVisible());
3024     d->m_pDirDoNothingEverywhere->setEnabled(bDirCompare && isVisible());
3025     d->m_pDirChooseAEverywhere->setEnabled(bDirCompare && isVisible());
3026     d->m_pDirChooseBEverywhere->setEnabled(bDirCompare && isVisible());
3027     d->m_pDirChooseCEverywhere->setEnabled(bDirCompare && isVisible() && bThreeDirs);
3028 
3029     MergeFileInfos* pMFI = d->getMFI(currentIndex());
3030 
3031     bool bItemActive = bDirCompare && isVisible() && pMFI != nullptr; //  &&  hasFocus();
3032     bool bMergeMode = bThreeDirs || !d->m_bSyncMode;
3033     bool bFTConflict = pMFI == nullptr ? false : pMFI->conflictingFileTypes();
3034 
3035     bool bDirWindowHasFocus = isVisible() && hasFocus();
3036 
3037     d->m_pDirShowIdenticalFiles->setEnabled(bDirCompare && isVisible());
3038     d->m_pDirShowDifferentFiles->setEnabled(bDirCompare && isVisible());
3039     d->m_pDirShowFilesOnlyInA->setEnabled(bDirCompare && isVisible());
3040     d->m_pDirShowFilesOnlyInB->setEnabled(bDirCompare && isVisible());
3041     d->m_pDirShowFilesOnlyInC->setEnabled(bDirCompare && isVisible() && bThreeDirs);
3042 
3043     d->m_pDirCompareExplicit->setEnabled(bDirCompare && isVisible() && d->m_selection2Index.isValid());
3044     d->m_pDirMergeExplicit->setEnabled(bDirCompare && isVisible() && d->m_selection2Index.isValid());
3045 
3046     d->m_pDirCurrentDoNothing->setEnabled(bItemActive && bMergeMode);
3047     d->m_pDirCurrentChooseA->setEnabled(bItemActive && bMergeMode && pMFI->existsInA());
3048     d->m_pDirCurrentChooseB->setEnabled(bItemActive && bMergeMode && pMFI->existsInB());
3049     d->m_pDirCurrentChooseC->setEnabled(bItemActive && bMergeMode && pMFI->existsInC());
3050     d->m_pDirCurrentMerge->setEnabled(bItemActive && bMergeMode && !bFTConflict);
3051     d->m_pDirCurrentDelete->setEnabled(bItemActive && bMergeMode);
3052     if(bDirWindowHasFocus)
3053     {
3054         chooseA->setEnabled(bItemActive && bDirCompare ? pMFI->existsInA() : true);
3055         chooseB->setEnabled(bItemActive && bDirCompare ? pMFI->existsInB() : true);
3056         chooseC->setEnabled(bItemActive && bDirCompare ? pMFI->existsInC() : KDiff3App::isTripleDiff() );
3057         chooseA->setChecked(false);
3058         chooseB->setChecked(false);
3059         chooseC->setChecked(false);
3060     }
3061     else
3062     {
3063         chooseA->setEnabled(bMergeEditorVisible);
3064         chooseB->setEnabled(bMergeEditorVisible);
3065         chooseC->setEnabled(bMergeEditorVisible && KDiff3App::isTripleDiff());
3066     }
3067 
3068     d->m_pDirCurrentSyncDoNothing->setEnabled(bItemActive && !bMergeMode);
3069     d->m_pDirCurrentSyncCopyAToB->setEnabled(bItemActive && !bMergeMode && pMFI->existsInA());
3070     d->m_pDirCurrentSyncCopyBToA->setEnabled(bItemActive && !bMergeMode && pMFI->existsInB());
3071     d->m_pDirCurrentSyncDeleteA->setEnabled(bItemActive && !bMergeMode && pMFI->existsInA());
3072     d->m_pDirCurrentSyncDeleteB->setEnabled(bItemActive && !bMergeMode && pMFI->existsInB());
3073     d->m_pDirCurrentSyncDeleteAAndB->setEnabled(bItemActive && !bMergeMode && pMFI->existsInA() && pMFI->existsInB());
3074     d->m_pDirCurrentSyncMergeToA->setEnabled(bItemActive && !bMergeMode && !bFTConflict);
3075     d->m_pDirCurrentSyncMergeToB->setEnabled(bItemActive && !bMergeMode && !bFTConflict);
3076     d->m_pDirCurrentSyncMergeToAAndB->setEnabled(bItemActive && !bMergeMode && !bFTConflict);
3077 }
3078 
3079 //#include "directorymergewindow.moc"
3080