File indexing completed on 2024-05-26 13:37:29

0001 // clang-format off
0002 /*
0003  * KDiff3 - Text Diff And Merge Tool
0004  *
0005  * SPDX-FileCopyrightText: 2002-2011 Joachim Eibl, joachim.eibl at gmx.de
0006  * SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com
0007  * SPDX-License-Identifier: GPL-2.0-or-later
0008  */
0009 // clang-format on
0010 
0011 #include "directorymergewindow.h"
0012 
0013 #include "compat.h"
0014 #include "CompositeIgnoreList.h"
0015 #include "defmac.h"
0016 #include "DirectoryInfo.h"
0017 #include "guiutils.h"
0018 #include "kdiff3.h"
0019 #include "Logging.h"
0020 #include "MergeFileInfos.h"
0021 #include "options.h"
0022 #include "PixMapUtils.h"
0023 #include "progress.h"
0024 #include "TypeUtils.h"
0025 #include "Utils.h"
0026 
0027 #include <map>
0028 #include <memory>
0029 
0030 #include <QAction>
0031 #include <QApplication>
0032 #include <QDialogButtonBox>
0033 #include <QDir>
0034 #include <QElapsedTimer>
0035 #include <QFileDialog>
0036 #include <QKeyEvent>
0037 #include <QLabel>
0038 #include <QLayout>
0039 #include <QMenu>
0040 #include <QPainter>
0041 #include <QSplitter>
0042 #include <QStyledItemDelegate>
0043 #include <QTextEdit>
0044 #include <QTextStream>
0045 
0046 #include <KLocalizedString>
0047 #include <KMessageBox>
0048 #include <KToggleAction>
0049 
0050 struct DirectoryMergeWindow::ItemInfo {
0051     bool bExpanded;
0052     bool bOperationComplete;
0053     QString status;
0054     e_MergeOperation eMergeOperation;
0055 };
0056 
0057 class StatusInfo: public QDialog
0058 {
0059   private:
0060     QTextEdit* m_pTextEdit;
0061 
0062   public:
0063     explicit StatusInfo(QWidget* pParent):
0064         QDialog(pParent)
0065     {
0066         QVBoxLayout* pVLayout = new QVBoxLayout(this);
0067         m_pTextEdit = new QTextEdit(this);
0068         pVLayout->addWidget(m_pTextEdit);
0069         setObjectName("StatusInfo");
0070         setWindowFlags(Qt::Dialog);
0071         m_pTextEdit->setWordWrapMode(QTextOption::NoWrap);
0072         m_pTextEdit->setReadOnly(true);
0073         QDialogButtonBox* box = new QDialogButtonBox(QDialogButtonBox::Close, this);
0074         chk_connect_a(box, &QDialogButtonBox::rejected, this, &QDialog::accept);
0075         pVLayout->addWidget(box);
0076     }
0077 
0078     bool isEmpty()
0079     {
0080         return m_pTextEdit->toPlainText().isEmpty();
0081     }
0082 
0083     void addText(const QString& s)
0084     {
0085         m_pTextEdit->append(s);
0086     }
0087 
0088     void clear()
0089     {
0090         m_pTextEdit->clear();
0091     }
0092 
0093     void setVisible(bool bVisible) override
0094     {
0095         if(bVisible)
0096         {
0097             m_pTextEdit->moveCursor(QTextCursor::End);
0098             m_pTextEdit->moveCursor(QTextCursor::StartOfLine);
0099             m_pTextEdit->ensureCursorVisible();
0100         }
0101 
0102         QDialog::setVisible(bVisible);
0103         if(bVisible)
0104             setWindowState(windowState() | Qt::WindowMaximized);
0105     }
0106 };
0107 
0108 enum Columns
0109 {
0110     s_NameCol = 0,
0111     s_ACol = 1,
0112     s_BCol = 2,
0113     s_CCol = 3,
0114     s_OpCol = 4,
0115     s_OpStatusCol = 5,
0116     s_UnsolvedCol = 6, // Number of unsolved conflicts (for 3 input files)
0117     s_SolvedCol = 7,   // Number of auto-solvable conflicts (for 3 input files)
0118     s_NonWhiteCol = 8, // Number of nonwhite deltas (for 2 input files)
0119     s_WhiteCol = 9     // Number of white deltas (for 2 input files)
0120 };
0121 
0122 static Qt::CaseSensitivity s_eCaseSensitivity = Qt::CaseSensitive;
0123 
0124 class DirectoryMergeWindow::DirectoryMergeWindowPrivate: public QAbstractItemModel
0125 {
0126     friend class DirMergeItem;
0127 
0128   public:
0129     DirectoryMergeWindowPrivate(DirectoryMergeWindow* pDMW, KDiff3App& app):
0130         m_app(app)
0131     {
0132         mWindow = pDMW;
0133         m_pStatusInfo = new StatusInfo(mWindow);
0134         m_pStatusInfo->hide();
0135     }
0136     ~DirectoryMergeWindowPrivate() override
0137     {
0138         delete m_pRoot;
0139     }
0140 
0141     bool init(bool bDirectoryMerge, bool bReload);
0142 
0143     // Implement QAbstractItemModel
0144     [[nodiscard]] QVariant data(const QModelIndex& index, qint32 role = Qt::DisplayRole) const override;
0145 
0146     //Qt::ItemFlags flags ( const QModelIndex & index ) const
0147     [[nodiscard]] QModelIndex parent(const QModelIndex& index) const override
0148     {
0149         MergeFileInfos* pMFI = getMFI(index);
0150         if(pMFI == nullptr || pMFI == m_pRoot || pMFI->parent() == m_pRoot)
0151             return QModelIndex();
0152 
0153         MergeFileInfos* pParentsParent = pMFI->parent()->parent();
0154         return createIndex(SafeInt<qint32>(pParentsParent->children().indexOf(pMFI->parent())), 0, pMFI->parent());
0155     }
0156 
0157     [[nodiscard]] qint32 rowCount(const QModelIndex& parent = QModelIndex()) const override
0158     {
0159         MergeFileInfos* pParentMFI = getMFI(parent);
0160         if(pParentMFI != nullptr)
0161             return SafeInt<qint32>(pParentMFI->children().count());
0162         else
0163             return SafeInt<qint32>(m_pRoot->children().count());
0164     }
0165 
0166     [[nodiscard]] qint32 columnCount(const QModelIndex& /*parent*/) const override
0167     {
0168         return 10;
0169     }
0170 
0171     [[nodiscard]] QModelIndex index(qint32 row, qint32 column, const QModelIndex& parent) const override
0172     {
0173         MergeFileInfos* pParentMFI = getMFI(parent);
0174         if(pParentMFI == nullptr && row < m_pRoot->children().count())
0175             return createIndex(row, column, m_pRoot->children()[row]);
0176         else if(pParentMFI != nullptr && row < pParentMFI->children().count())
0177             return createIndex(row, column, pParentMFI->children()[row]);
0178         else
0179             return QModelIndex();
0180     }
0181 
0182     [[nodiscard]] QVariant headerData(qint32 section, Qt::Orientation orientation, qint32 role = Qt::DisplayRole) const override;
0183 
0184     void sort(qint32 column, Qt::SortOrder order) override;
0185 
0186     void selectItemAndColumn(const QModelIndex& mi, bool bContextMenu);
0187 
0188     void setOpStatus(const QModelIndex& mi, e_OperationStatus eOpStatus)
0189     {
0190         if(MergeFileInfos* pMFI = getMFI(mi))
0191         {
0192             pMFI->setOpStatus(eOpStatus);
0193             Q_EMIT dataChanged(mi, mi);
0194         }
0195     }
0196 
0197     QModelIndex nextSibling(const QModelIndex& mi);
0198 
0199     // private data and helper methods
0200     [[nodiscard]] MergeFileInfos* getMFI(const QModelIndex& mi) const
0201     {
0202         if(mi.isValid())
0203             return (MergeFileInfos*)mi.internalPointer();
0204         else
0205             return nullptr;
0206     }
0207 
0208     /*
0209         returns whether or not we doing three way directory comparision
0210 
0211         This will return false when comparing three files.
0212         Use KDiff3App::isTripleDiff() if this is a problem.
0213     */
0214     [[nodiscard]] bool isDirThreeWay() const
0215     {
0216         return MergeFileInfos::isThreeWay();
0217     }
0218 
0219     [[nodiscard]] MergeFileInfos* rootMFI() const { return m_pRoot; }
0220 
0221     void calcDirStatus(bool bThreeDirs, const QModelIndex& mi,
0222                        qint32& nofFiles, qint32& nofDirs, qint32& nofEqualFiles, qint32& nofManualMerges);
0223 
0224     void mergeContinue(bool bStart, bool bVerbose);
0225 
0226     void prepareListView();
0227     void calcSuggestedOperation(const QModelIndex& mi, e_MergeOperation eDefaultMergeOp);
0228     void setAllMergeOperations(e_MergeOperation eDefaultOperation);
0229 
0230     bool canContinue();
0231     QModelIndex treeIterator(QModelIndex mi, bool bVisitChildren = true, bool bFindInvisible = false);
0232     void prepareMergeStart(const QModelIndex& miBegin, const QModelIndex& miEnd, bool bVerbose);
0233     bool executeMergeOperation(const MergeFileInfos& mfi, bool& bSingleFileMerge);
0234 
0235     void scanDirectory(const QString& dirName, DirectoryList& dirList);
0236     void scanLocalDirectory(const QString& dirName, DirectoryList& dirList);
0237 
0238     void setMergeOperation(const QModelIndex& mi, e_MergeOperation eMergeOp, bool bRecursive = true);
0239     [[nodiscard]] bool isDir(const QModelIndex& mi) const;
0240     [[nodiscard]] QString getFileName(const QModelIndex& mi) const;
0241 
0242     bool copyFLD(const QString& srcName, const QString& destName);
0243     bool deleteFLD(const QString& name, bool bCreateBackup);
0244     bool makeDir(const QString& name, bool bQuiet = false);
0245     bool renameFLD(const QString& srcName, const QString& destName);
0246     bool mergeFLD(const QString& nameA, const QString& nameB, const QString& nameC,
0247                   const QString& nameDest, bool& bSingleFileMerge);
0248 
0249     void buildMergeMap(const QSharedPointer<DirectoryInfo>& dirInfo);
0250 
0251   private:
0252     class FileKey
0253     {
0254       private:
0255         const FileAccess* m_pFA;
0256 
0257       public:
0258         explicit FileKey(const FileAccess& fa):
0259             m_pFA(&fa) {}
0260 
0261         quint32 getParents(const FileAccess* pFA, const FileAccess* v[], quint32 maxSize) const
0262         {
0263             quint32 s = 0;
0264             for(s = 0; pFA->parent() != nullptr; pFA = pFA->parent(), ++s)
0265             {
0266                 if(s == maxSize)
0267                     break;
0268                 v[s] = pFA;
0269             }
0270             return s;
0271         }
0272 
0273         // This is essentially the same as
0274         // qint32 r = filePath().compare( fa.filePath() )
0275         // if ( r<0 ) return true;
0276         // if ( r==0 ) return m_col < fa.m_col;
0277         // return false;
0278         bool operator<(const FileKey& fk) const
0279         {
0280             const FileAccess* v1[100];
0281             const FileAccess* v2[100];
0282             quint32 v1Size = getParents(m_pFA, v1, 100);
0283             quint32 v2Size = getParents(fk.m_pFA, v2, 100);
0284 
0285             for(quint32 i = 0; i < v1Size && i < v2Size; ++i)
0286             {
0287                 qint32 r = v1[v1Size - i - 1]->fileName().compare(v2[v2Size - i - 1]->fileName(), s_eCaseSensitivity);
0288                 if(r < 0)
0289                     return true;
0290                 else if(r > 0)
0291                     return false;
0292             }
0293 
0294             return v1Size < v2Size;
0295         }
0296     };
0297 
0298     typedef QMap<FileKey, MergeFileInfos> t_fileMergeMap;
0299 
0300     MergeFileInfos* m_pRoot = new MergeFileInfos();
0301 
0302     t_fileMergeMap m_fileMergeMap;
0303 
0304   public:
0305     DirectoryMergeWindow* mWindow;
0306     KDiff3App& m_app;
0307 
0308     bool m_bFollowDirLinks = false;
0309     bool m_bFollowFileLinks = false;
0310     bool m_bSimulatedMergeStarted = false;
0311     bool m_bRealMergeStarted = false;
0312     bool m_bError = false;
0313     bool m_bSyncMode = false;
0314     bool m_bDirectoryMerge = false; // if true, then merge is the default operation, otherwise it's diff.
0315     bool m_bCaseSensitive = true;
0316     bool m_bUnfoldSubdirs = false;
0317     bool m_bSkipDirStatus = false;
0318     bool m_bScanning = false; // true while in init()
0319 
0320     DirectoryMergeInfo* m_pDirectoryMergeInfo = nullptr;
0321     StatusInfo* m_pStatusInfo = nullptr;
0322 
0323     typedef std::list<QModelIndex> MergeItemList; // linked list
0324     MergeItemList m_mergeItemList;
0325     MergeItemList::iterator m_currentIndexForOperation;
0326 
0327     QModelIndex m_selection1Index;
0328     QModelIndex m_selection2Index;
0329     QModelIndex m_selection3Index;
0330 
0331     QPointer<QAction> m_pDirStartOperation;
0332     QPointer<QAction> m_pDirRunOperationForCurrentItem;
0333     QPointer<QAction> m_pDirCompareCurrent;
0334     QPointer<QAction> m_pDirMergeCurrent;
0335     QPointer<QAction> m_pDirRescan;
0336     QPointer<QAction> m_pDirChooseAEverywhere;
0337     QPointer<QAction> m_pDirChooseBEverywhere;
0338     QPointer<QAction> m_pDirChooseCEverywhere;
0339     QPointer<QAction> m_pDirAutoChoiceEverywhere;
0340     QPointer<QAction> m_pDirDoNothingEverywhere;
0341     QPointer<QAction> m_pDirFoldAll;
0342     QPointer<QAction> m_pDirUnfoldAll;
0343 
0344     QPointer<KToggleAction> m_pDirShowIdenticalFiles;
0345     QPointer<KToggleAction> m_pDirShowDifferentFiles;
0346     QPointer<KToggleAction> m_pDirShowFilesOnlyInA;
0347     QPointer<KToggleAction> m_pDirShowFilesOnlyInB;
0348     QPointer<KToggleAction> m_pDirShowFilesOnlyInC;
0349 
0350     QPointer<KToggleAction> m_pDirSynchronizeDirectories;
0351     QPointer<KToggleAction> m_pDirChooseNewerFiles;
0352 
0353     QPointer<QAction> m_pDirCompareExplicit;
0354     QPointer<QAction> m_pDirMergeExplicit;
0355 
0356     QPointer<QAction> m_pDirCurrentDoNothing;
0357     QPointer<QAction> m_pDirCurrentChooseA;
0358     QPointer<QAction> m_pDirCurrentChooseB;
0359     QPointer<QAction> m_pDirCurrentChooseC;
0360     QPointer<QAction> m_pDirCurrentMerge;
0361     QPointer<QAction> m_pDirCurrentDelete;
0362 
0363     QPointer<QAction> m_pDirCurrentSyncDoNothing;
0364     QPointer<QAction> m_pDirCurrentSyncCopyAToB;
0365     QPointer<QAction> m_pDirCurrentSyncCopyBToA;
0366     QPointer<QAction> m_pDirCurrentSyncDeleteA;
0367     QPointer<QAction> m_pDirCurrentSyncDeleteB;
0368     QPointer<QAction> m_pDirCurrentSyncDeleteAAndB;
0369     QPointer<QAction> m_pDirCurrentSyncMergeToA;
0370     QPointer<QAction> m_pDirCurrentSyncMergeToB;
0371     QPointer<QAction> m_pDirCurrentSyncMergeToAAndB;
0372 
0373     QPointer<QAction> m_pDirSaveMergeState;
0374     QPointer<QAction> m_pDirLoadMergeState;
0375 };
0376 
0377 QVariant DirectoryMergeWindow::DirectoryMergeWindowPrivate::data(const QModelIndex& index, qint32 role) const
0378 {
0379     MergeFileInfos* pMFI = getMFI(index);
0380     if(pMFI)
0381     {
0382         if(role == Qt::DisplayRole)
0383         {
0384             switch(index.column())
0385             {
0386                 case s_NameCol:
0387                     return QFileInfo(pMFI->subPath()).fileName();
0388                 case s_ACol:
0389                     return QStringLiteral("A");
0390                 case s_BCol:
0391                     return QStringLiteral("B");
0392                 case s_CCol:
0393                     return QStringLiteral("C");
0394                 //case s_OpCol:       return i18n("Operation");
0395                 //case s_OpStatusCol: return i18n("Status");
0396                 case s_UnsolvedCol:
0397                     return pMFI->diffStatus().getUnsolvedConflicts();
0398                 case s_SolvedCol:
0399                     return pMFI->diffStatus().getSolvedConflicts();
0400                 case s_NonWhiteCol:
0401                     return pMFI->diffStatus().getNonWhitespaceConflicts();
0402                 case s_WhiteCol:
0403                     return pMFI->diffStatus().getWhitespaceConflicts();
0404                     //default :           return QVariant();
0405             }
0406 
0407             if(s_OpCol == index.column())
0408             {
0409                 bool bDir = pMFI->hasDir();
0410                 switch(pMFI->getOperation())
0411                 {
0412                     case eNoOperation:
0413                         return "";
0414                         break;
0415                     case eCopyAToB:
0416                         return i18nc("Operation column message", "Copy A to B");
0417                         break;
0418                     case eCopyBToA:
0419                         return i18nc("Operation column message", "Copy B to A");
0420                         break;
0421                     case eDeleteA:
0422                         return i18nc("Operation column message", "Delete A");
0423                         break;
0424                     case eDeleteB:
0425                         return i18nc("Operation column message", "Delete B");
0426                         break;
0427                     case eDeleteAB:
0428                         return i18nc("Operation column message", "Delete A & B");
0429                         break;
0430                     case eMergeToA:
0431                         return i18nc("Operation column message", "Merge to A");
0432                         break;
0433                     case eMergeToB:
0434                         return i18nc("Operation column message", "Merge to B");
0435                         break;
0436                     case eMergeToAB:
0437                         return i18nc("Operation column message", "Merge to A & B");
0438                         break;
0439                     case eCopyAToDest:
0440                         return QStringLiteral("A");
0441                         break;
0442                     case eCopyBToDest:
0443                         return QStringLiteral("B");
0444                         break;
0445                     case eCopyCToDest:
0446                         return QStringLiteral("C");
0447                         break;
0448                     case eDeleteFromDest:
0449                         return i18nc("Operation column message", "Delete (if exists)");
0450                         break;
0451                     case eMergeABCToDest:
0452                     case eMergeABToDest:
0453                         return bDir ? i18nc("Operation column message (Directory merge)", "Merge") : i18nc("Operation column message (File merge)", "Merge (manual)");
0454                         break;
0455                     case eConflictingFileTypes:
0456                         return i18nc("Operation column message", "Error: Conflicting File Types");
0457                         break;
0458                     case eChangedAndDeleted:
0459                         return i18nc("Operation column message", "Error: Changed and Deleted");
0460                         break;
0461                     case eConflictingAges:
0462                         return i18nc("Operation column message", "Error: Dates are equal but files are not.");
0463                         break;
0464                     default:
0465                         assert(false);
0466                         break;
0467                 }
0468             }
0469             if(s_OpStatusCol == index.column())
0470             {
0471                 switch(pMFI->getOpStatus())
0472                 {
0473                     case eOpStatusNone:
0474                         return "";
0475                     case eOpStatusDone:
0476                         return i18nc("Status column message", "Done");
0477                     case eOpStatusError:
0478                         return i18nc("Status column message", "Error");
0479                     case eOpStatusSkipped:
0480                         return i18nc("Status column message", "Skipped.");
0481                     case eOpStatusNotSaved:
0482                         return i18nc("Status column message", "Not saved.");
0483                     case eOpStatusInProgress:
0484                         return i18nc("Status column message", "In progress...");
0485                     case eOpStatusToDo:
0486                         return i18nc("Status column message", "To do.");
0487                 }
0488             }
0489         }
0490         else if(role == Qt::DecorationRole)
0491         {
0492             if(s_NameCol == index.column())
0493             {
0494                 return PixMapUtils::getOnePixmap(eAgeEnd, pMFI->hasLink(), pMFI->hasDir());
0495             }
0496 
0497             if(s_ACol == index.column())
0498             {
0499                 return PixMapUtils::getOnePixmap(pMFI->getAgeA(), pMFI->isLinkA(), pMFI->isDirA());
0500             }
0501             if(s_BCol == index.column())
0502             {
0503                 return PixMapUtils::getOnePixmap(pMFI->getAgeB(), pMFI->isLinkB(), pMFI->isDirB());
0504             }
0505             if(s_CCol == index.column())
0506             {
0507                 return PixMapUtils::getOnePixmap(pMFI->getAgeC(), pMFI->isLinkC(), pMFI->isDirC());
0508             }
0509         }
0510         else if(role == Qt::TextAlignmentRole)
0511         {
0512             if(s_UnsolvedCol == index.column() || s_SolvedCol == index.column() || s_NonWhiteCol == index.column() || s_WhiteCol == index.column())
0513                 return Qt::AlignRight;
0514         }
0515     }
0516     return QVariant();
0517 }
0518 
0519 QVariant DirectoryMergeWindow::DirectoryMergeWindowPrivate::headerData(qint32 section, Qt::Orientation orientation, qint32 role) const
0520 {
0521     if(orientation == Qt::Horizontal && section >= 0 && section < columnCount(QModelIndex()) && role == Qt::DisplayRole)
0522     {
0523         switch(section)
0524         {
0525             case s_NameCol:
0526                 return i18nc("Column title", "Name");
0527             case s_ACol:
0528                 return QStringLiteral("A");
0529             case s_BCol:
0530                 return QStringLiteral("B");
0531             case s_CCol:
0532                 return QStringLiteral("C");
0533             case s_OpCol:
0534                 return i18nc("Column title", "Operation");
0535             case s_OpStatusCol:
0536                 return i18nc("Column title", "Status");
0537             case s_UnsolvedCol:
0538                 return i18nc("Column title", "Unsolved");
0539             case s_SolvedCol:
0540                 return i18nc("Column title", "Solved");
0541             case s_NonWhiteCol:
0542                 return i18nc("Column title", "Nonwhite");
0543             case s_WhiteCol:
0544                 return i18nc("Column title", "White");
0545             default:
0546                 return QVariant();
0547         }
0548     }
0549     return QVariant();
0550 }
0551 
0552 qint32 DirectoryMergeWindow::getIntFromIndex(const QModelIndex& index) const
0553 {
0554     return index == d->m_selection1Index ? 1 : index == d->m_selection2Index ? 2 : index == d->m_selection3Index ? 3 : 0;
0555 }
0556 
0557 // Previously  Q3ListViewItem::paintCell(p,cg,column,width,align);
0558 class DirectoryMergeWindow::DirMergeItemDelegate: public QStyledItemDelegate
0559 {
0560   private:
0561     DirectoryMergeWindow* m_pDMW;
0562 
0563   public:
0564     explicit DirMergeItemDelegate(DirectoryMergeWindow* pParent):
0565         QStyledItemDelegate(pParent), m_pDMW(pParent)
0566     {
0567     }
0568 
0569     void paint(QPainter* thePainter, const QStyleOptionViewItem& option, const QModelIndex& index) const override
0570     {
0571         qint32 column = index.column();
0572         if(column == s_ACol || column == s_BCol || column == s_CCol)
0573         {
0574             QVariant value = index.data(Qt::DecorationRole);
0575             QPixmap icon;
0576             if(value.isValid())
0577             {
0578 #if(QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
0579                 if(value.type() == QVariant::Icon)
0580 #else
0581                 if(value.typeId() == QMetaType::QIcon)
0582 #endif
0583                 {
0584                     icon = qvariant_cast<QIcon>(value).pixmap(16, 16);
0585                     //icon = qvariant_cast<QIcon>(value);
0586                     //decorationRect = QRect(QPoint(0, 0), icon.actualSize(option.decorationSize, iconMode, iconState));
0587                 }
0588                 else
0589                 {
0590                     icon = qvariant_cast<QPixmap>(value);
0591                     //decorationRect = QRect(QPoint(0, 0), option.decorationSize).intersected(pixmap.rect());
0592                 }
0593             }
0594 
0595             qint32 x = option.rect.left();
0596             qint32 y = option.rect.top();
0597             //QPixmap icon = value.value<QPixmap>(); //pixmap(column);
0598             if(!icon.isNull())
0599             {
0600                 const auto dpr = thePainter->device()->devicePixelRatioF();
0601                 const qint32 w = qRound(icon.width() / dpr);
0602                 const qint32 h = qRound(icon.height() / dpr);
0603                 qint32 yOffset = (sizeHint(option, index).height() - h) / 2;
0604                 thePainter->drawPixmap(x + 2, y + yOffset, icon);
0605 
0606                 qint32 i = m_pDMW->getIntFromIndex(index);
0607                 if(i != 0)
0608                 {
0609                     QColor c(i == 1 ? gOptions->aColor() : i == 2 ? gOptions->bColor() :
0610                                                                     gOptions->cColor());
0611                     thePainter->setPen(c); // highlight() );
0612                     thePainter->drawRect(x + 2, y + yOffset, w, h);
0613                     thePainter->setPen(QPen(c, 0, Qt::DotLine));
0614                     thePainter->drawRect(x + 1, y + yOffset - 1, w + 2, h + 2);
0615                     thePainter->setPen(Qt::white);
0616                     QString s(QChar('A' + i - 1));
0617 
0618                     thePainter->drawText(x + 2 + (w - thePainter->fontMetrics().horizontalAdvance(s)) / 2,
0619                                          y + yOffset + (h + thePainter->fontMetrics().ascent()) / 2 - 1,
0620                                          s);
0621                 }
0622                 else
0623                 {
0624                     thePainter->setPen(m_pDMW->palette().window().color());
0625                     thePainter->drawRect(x + 1, y + yOffset - 1, w + 2, h + 2);
0626                 }
0627                 return;
0628             }
0629         }
0630 
0631         QStyleOptionViewItem option2 = option;
0632         if(column >= s_UnsolvedCol)
0633         {
0634             option2.displayAlignment = Qt::AlignRight;
0635         }
0636         QStyledItemDelegate::paint(thePainter, option2, index);
0637     }
0638     [[nodiscard]] QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const override
0639     {
0640         QSize sz = QStyledItemDelegate::sizeHint(option, index);
0641         return sz.expandedTo(QSize(0, 18));
0642     }
0643 };
0644 
0645 DirectoryMergeWindow::DirectoryMergeWindow(QWidget* pParent, KDiff3App& app):
0646     QTreeView(pParent)
0647 {
0648     d = std::make_unique<DirectoryMergeWindowPrivate>(this, app);
0649     setModel(d.get());
0650     setItemDelegate(new DirMergeItemDelegate(this));
0651     chk_connect_a(this, &DirectoryMergeWindow::doubleClicked, this, &DirectoryMergeWindow::onDoubleClick);
0652     chk_connect_a(this, &DirectoryMergeWindow::expanded, this, &DirectoryMergeWindow::onExpanded);
0653 
0654     setSortingEnabled(true);
0655 }
0656 
0657 DirectoryMergeWindow::~DirectoryMergeWindow() = default;
0658 
0659 void DirectoryMergeWindow::setDirectoryMergeInfo(DirectoryMergeInfo* p)
0660 {
0661     d->m_pDirectoryMergeInfo = p;
0662 }
0663 bool DirectoryMergeWindow::isDirectoryMergeInProgress()
0664 {
0665     return d->m_bRealMergeStarted;
0666 }
0667 bool DirectoryMergeWindow::isSyncMode()
0668 {
0669     return d->m_bSyncMode;
0670 }
0671 bool DirectoryMergeWindow::isScanning()
0672 {
0673     return d->m_bScanning;
0674 }
0675 
0676 qint32 DirectoryMergeWindow::totalColumnWidth()
0677 {
0678     qint32 w = 0;
0679     for(qint32 i = 0; i < s_OpStatusCol; ++i)
0680     {
0681         w += columnWidth(i);
0682     }
0683     return w;
0684 }
0685 
0686 void DirectoryMergeWindow::reload()
0687 {
0688     if(isDirectoryMergeInProgress())
0689     {
0690         KMessageBox::ButtonCode result = Compat::warningTwoActions(this,
0691                                                                    i18n("You are currently doing a folder merge. Are you sure, you want to abort the merge and rescan the folder?"),
0692                                                                    i18nc("Error dialog title", "Warning"),
0693                                                                    KGuiItem(i18nc("Title for rescan button", "Rescan")),
0694                                                                    KGuiItem(i18nc("Title for continue button", "Continue Merging")));
0695         if(result != Compat::PrimaryAction)
0696             return;
0697     }
0698 
0699     init(true);
0700     //fix file visibilities after reload or menu will be out of sync with display if changed from defaults.
0701     updateFileVisibilities();
0702 }
0703 
0704 void DirectoryMergeWindow::DirectoryMergeWindowPrivate::calcDirStatus(bool bThreeDirs, const QModelIndex& mi,
0705                                                                       qint32& nofFiles, qint32& nofDirs, qint32& nofEqualFiles, qint32& nofManualMerges)
0706 {
0707     const MergeFileInfos* pMFI = getMFI(mi);
0708     if(pMFI->hasDir())
0709     {
0710         ++nofDirs;
0711     }
0712     else
0713     {
0714         ++nofFiles;
0715         if(pMFI->isEqualAB() && (!bThreeDirs || pMFI->isEqualAC()))
0716         {
0717             ++nofEqualFiles;
0718         }
0719         else
0720         {
0721             if(pMFI->getOperation() == eMergeABCToDest || pMFI->getOperation() == eMergeABToDest)
0722                 ++nofManualMerges;
0723         }
0724     }
0725     for(qint32 childIdx = 0; childIdx < rowCount(mi); ++childIdx)
0726         calcDirStatus(bThreeDirs, index(childIdx, 0, mi), nofFiles, nofDirs, nofEqualFiles, nofManualMerges);
0727 }
0728 
0729 bool DirectoryMergeWindow::init(
0730     bool bDirectoryMerge,
0731     bool bReload)
0732 {
0733     return d->init(bDirectoryMerge, bReload);
0734 }
0735 
0736 void DirectoryMergeWindow::DirectoryMergeWindowPrivate::buildMergeMap(const QSharedPointer<DirectoryInfo>& dirInfo)
0737 {
0738     if(dirInfo->dirA().isValid())
0739     {
0740         for(FileAccess& fileRecord: dirInfo->getDirListA())
0741         {
0742             MergeFileInfos& mfi = m_fileMergeMap[FileKey(fileRecord)];
0743 
0744             mfi.setFileInfoA(&fileRecord);
0745         }
0746     }
0747 
0748     if(dirInfo->dirB().isValid())
0749     {
0750         for(FileAccess& fileRecord: dirInfo->getDirListB())
0751         {
0752             MergeFileInfos& mfi = m_fileMergeMap[FileKey(fileRecord)];
0753 
0754             mfi.setFileInfoB(&(fileRecord));
0755         }
0756     }
0757 
0758     if(dirInfo->dirC().isValid())
0759     {
0760         for(FileAccess& fileRecord: dirInfo->getDirListC())
0761         {
0762             MergeFileInfos& mfi = m_fileMergeMap[FileKey(fileRecord)];
0763 
0764             mfi.setFileInfoC(&(fileRecord));
0765         }
0766     }
0767 }
0768 
0769 bool DirectoryMergeWindow::DirectoryMergeWindowPrivate::init(
0770     bool bDirectoryMerge,
0771     bool bReload)
0772 {
0773     if(gOptions->m_bDmFullAnalysis)
0774     {
0775         QStringList errors;
0776         // A full analysis uses the same resources that a normal text-diff/merge uses.
0777         // So make sure that the user saves his data first.
0778         if(!m_app.canContinue())
0779             return false;
0780         Q_EMIT mWindow->startDiffMerge(errors, "", "", "", "", "", "", "", nullptr); // hide main window
0781     }
0782 
0783     mWindow->show();
0784     mWindow->setUpdatesEnabled(true);
0785 
0786     std::map<QString, ItemInfo> expandedDirsMap;
0787 
0788     if(bReload)
0789     {
0790         // Remember expanded items TODO
0791         //QTreeWidgetItemIterator it( this );
0792         //while ( *it )
0793         //{
0794         //   DirMergeItem* pDMI = static_cast<DirMergeItem*>( *it );
0795         //   t_ItemInfo& ii = expandedDirsMap[ pDMI->m_pMFI->subPath() ];
0796         //   ii.bExpanded = pDMI->isExpanded();
0797         //   ii.bOperationComplete = pDMI->m_pMFI->m_bOperationComplete;
0798         //   ii.status = pDMI->text( s_OpStatusCol );
0799         //   ii.eMergeOperation = pDMI->m_pMFI->getOperation();
0800         //   ++it;
0801         //}
0802     }
0803 
0804     ProgressScope pp;
0805     m_bFollowDirLinks = gOptions->m_bDmFollowDirLinks;
0806     m_bFollowFileLinks = gOptions->m_bDmFollowFileLinks;
0807     m_bSimulatedMergeStarted = false;
0808     m_bRealMergeStarted = false;
0809     m_bError = false;
0810     m_bDirectoryMerge = bDirectoryMerge;
0811     m_selection1Index = QModelIndex();
0812     m_selection2Index = QModelIndex();
0813     m_selection3Index = QModelIndex();
0814     m_bCaseSensitive = gOptions->m_bDmCaseSensitiveFilenameComparison;
0815     m_bUnfoldSubdirs = gOptions->m_bDmUnfoldSubdirs;
0816     m_bSkipDirStatus = gOptions->m_bDmSkipDirStatus;
0817 
0818     beginResetModel();
0819     m_pRoot->clear();
0820     m_mergeItemList.clear();
0821     endResetModel();
0822 
0823     m_currentIndexForOperation = m_mergeItemList.end();
0824 
0825     if(!bReload)
0826     {
0827         m_pDirShowIdenticalFiles->setChecked(true);
0828         m_pDirShowDifferentFiles->setChecked(true);
0829         m_pDirShowFilesOnlyInA->setChecked(true);
0830         m_pDirShowFilesOnlyInB->setChecked(true);
0831         m_pDirShowFilesOnlyInC->setChecked(true);
0832     }
0833 
0834     assert(gDirInfo != nullptr);
0835     const FileAccess& dirA = gDirInfo->dirA();
0836     const FileAccess& dirB = gDirInfo->dirB();
0837     const FileAccess& dirC = gDirInfo->dirC();
0838     const FileAccess& dirDest = gDirInfo->destDir();
0839     // Check if all input directories exist and are valid. The dest dir is not tested now.
0840     // The test will happen only when we are going to write to it.
0841     if(!dirA.isDir() || !dirB.isDir() || (dirC.isValid() && !dirC.isDir()))
0842     {
0843         QString text(i18n("Opening of folders failed:"));
0844         text += "\n\n";
0845         if(!dirA.isDir())
0846         {
0847             text += i18n("Folder A \"%1\" does not exist or is not a folder.\n", dirA.prettyAbsPath());
0848         }
0849 
0850         if(!dirB.isDir())
0851         {
0852             text += i18n("Folder B \"%1\" does not exist or is not a folder.\n", dirB.prettyAbsPath());
0853         }
0854 
0855         if(dirC.isValid() && !dirC.isDir())
0856         {
0857             text += i18n("Folder C \"%1\" does not exist or is not a folder.\n", dirC.prettyAbsPath());
0858         }
0859 
0860         KMessageBox::error(mWindow, text, i18nc("Error dialog title", "Folder Opening Error"));
0861         return false;
0862     }
0863 
0864     if(dirC.isValid() &&
0865        (dirDest.prettyAbsPath() == dirA.prettyAbsPath() || dirDest.prettyAbsPath() == dirB.prettyAbsPath()))
0866     {
0867         KMessageBox::error(mWindow,
0868                            i18n("The destination folder must not be the same as A or B when "
0869                                 "three folders are merged.\nCheck again before continuing."),
0870                            i18nc("Error dialog title", "Parameter Warning"));
0871         return false;
0872     }
0873 
0874     m_bScanning = true;
0875     Q_EMIT mWindow->statusBarMessage(i18n("Scanning folders..."));
0876 
0877     m_bSyncMode = gOptions->m_bDmSyncMode && gDirInfo->allowSyncMode();
0878 
0879     m_fileMergeMap.clear();
0880     s_eCaseSensitivity = m_bCaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive;
0881     // calc how many directories will be read:
0882     double nofScans = (dirA.isValid() ? 1 : 0) + (dirB.isValid() ? 1 : 0) + (dirC.isValid() ? 1 : 0);
0883     qint32 currentScan = 0;
0884 
0885     mWindow->setColumnHidden(s_CCol, !dirC.isValid());
0886     mWindow->setColumnHidden(s_WhiteCol, !gOptions->m_bDmFullAnalysis);
0887     mWindow->setColumnHidden(s_NonWhiteCol, !gOptions->m_bDmFullAnalysis);
0888     mWindow->setColumnHidden(s_UnsolvedCol, !gOptions->m_bDmFullAnalysis);
0889     mWindow->setColumnHidden(s_SolvedCol, !(gOptions->m_bDmFullAnalysis && dirC.isValid()));
0890 
0891     bool bListDirSuccessA = true;
0892     bool bListDirSuccessB = true;
0893     bool bListDirSuccessC = true;
0894 
0895     if(dirA.isValid())
0896     {
0897         ProgressProxy::setInformation(i18nc("Status message", "Reading Folder A"));
0898         ProgressProxy::setSubRangeTransformation(currentScan / nofScans, (currentScan + 1) / nofScans);
0899         ++currentScan;
0900 
0901         bListDirSuccessA = gDirInfo->listDirA();
0902     }
0903 
0904     if(dirB.isValid())
0905     {
0906         ProgressProxy::setInformation(i18nc("Status message", "Reading Folder B"));
0907         ProgressProxy::setSubRangeTransformation(currentScan / nofScans, (currentScan + 1) / nofScans);
0908         ++currentScan;
0909 
0910         bListDirSuccessB = gDirInfo->listDirB();
0911     }
0912 
0913     e_MergeOperation eDefaultMergeOp;
0914     if(dirC.isValid())
0915     {
0916         ProgressProxy::setInformation(i18nc("Status message", "Reading Folder C"));
0917         ProgressProxy::setSubRangeTransformation(currentScan / nofScans, (currentScan + 1) / nofScans);
0918         ++currentScan;
0919 
0920         bListDirSuccessC = gDirInfo->listDirC();
0921 
0922         eDefaultMergeOp = eMergeABCToDest;
0923     }
0924     else
0925         eDefaultMergeOp = m_bSyncMode ? eMergeToAB : eMergeABToDest;
0926 
0927     buildMergeMap(gDirInfo);
0928 
0929     bool bContinue = true;
0930     if(!bListDirSuccessA || !bListDirSuccessB || !bListDirSuccessC)
0931     {
0932         QString s = i18nc("Warning text", "Some subfolders were not readable in");
0933         if(!bListDirSuccessA) s += "\nA: " + dirA.prettyAbsPath();
0934         if(!bListDirSuccessB) s += "\nB: " + dirB.prettyAbsPath();
0935         if(!bListDirSuccessC) s += "\nC: " + dirC.prettyAbsPath();
0936         s += '\n';
0937         s += i18nc("Warning text", "Check the permissions of the subfolders.");
0938         bContinue = KMessageBox::Continue == KMessageBox::warningContinueCancel(mWindow, s);
0939     }
0940 
0941     if(bContinue)
0942     {
0943         prepareListView();
0944 
0945         mWindow->updateFileVisibilities();
0946 
0947         for(qint32 childIdx = 0; childIdx < rowCount(); ++childIdx)
0948         {
0949             QModelIndex mi = index(childIdx, 0, QModelIndex());
0950             calcSuggestedOperation(mi, eDefaultMergeOp);
0951         }
0952     }
0953 
0954     mWindow->sortByColumn(0, Qt::AscendingOrder);
0955 
0956     for(qint32 column = 0; column < columnCount(QModelIndex()); ++column)
0957         mWindow->resizeColumnToContents(column);
0958 
0959     m_bScanning = false;
0960     Q_EMIT mWindow->statusBarMessage(i18nc("Status bar idle message.", "Ready."));
0961 
0962     if(bContinue && !m_bSkipDirStatus)
0963     {
0964         // Generate a status report
0965         qint32 nofFiles = 0;
0966         qint32 nofDirs = 0;
0967         qint32 nofEqualFiles = 0;
0968         qint32 nofManualMerges = 0;
0969         //TODO
0970         for(qint32 childIdx = 0; childIdx < rowCount(); ++childIdx)
0971             calcDirStatus(dirC.isValid(), index(childIdx, 0, QModelIndex()),
0972                           nofFiles, nofDirs, nofEqualFiles, nofManualMerges);
0973 
0974         QString s;
0975         s = i18n("Folder Comparison Status\n\n"
0976                  "Number of subfolders: %1\n"
0977                  "Number of equal files: %2\n"
0978                  "Number of different files: %3",
0979                  nofDirs, nofEqualFiles, nofFiles - nofEqualFiles);
0980 
0981         if(dirC.isValid())
0982             s += '\n' + i18n("Number of manual merges: %1", nofManualMerges);
0983         KMessageBox::information(mWindow, s);
0984         //
0985         //TODO
0986         //if ( topLevelItemCount()>0 )
0987         //{
0988         //   topLevelItem(0)->setSelected(true);
0989         //   setCurrentItem( topLevelItem(0) );
0990         //}
0991     }
0992 
0993     if(bReload)
0994     {
0995         // Remember expanded items
0996         //TODO
0997         //QTreeWidgetItemIterator it( this );
0998         //while ( *it )
0999         //{
1000         //   DirMergeItem* pDMI = static_cast<DirMergeItem*>( *it );
1001         //   std::map<QString,t_ItemInfo>::iterator i = expandedDirsMap.find( pDMI->m_pMFI->subPath() );
1002         //   if ( i!=expandedDirsMap.end() )
1003         //   {
1004         //      t_ItemInfo& ii = i->second;
1005         //      pDMI->setExpanded( ii.bExpanded );
1006         //      //pDMI->m_pMFI->setMergeOperation( ii.eMergeOperation, false ); unsafe, might have changed
1007         //      pDMI->m_pMFI->m_bOperationComplete = ii.bOperationComplete;
1008         //      pDMI->setText( s_OpStatusCol, ii.status );
1009         //   }
1010         //   ++it;
1011         //}
1012     }
1013     else if(m_bUnfoldSubdirs)
1014     {
1015         m_pDirUnfoldAll->trigger();
1016     }
1017 
1018     return true;
1019 }
1020 
1021 QString DirectoryMergeWindow::getDirNameA() const
1022 {
1023     return gDirInfo->dirA().prettyAbsPath();
1024 }
1025 
1026 QString DirectoryMergeWindow::getDirNameB() const
1027 {
1028     return gDirInfo->dirB().prettyAbsPath();
1029 }
1030 
1031 QString DirectoryMergeWindow::getDirNameC() const
1032 {
1033     return gDirInfo->dirC().prettyAbsPath();
1034 }
1035 
1036 QString DirectoryMergeWindow::getDirNameDest() const
1037 {
1038     return gDirInfo->destDir().prettyAbsPath();
1039 }
1040 
1041 void DirectoryMergeWindow::onExpanded()
1042 {
1043     resizeColumnToContents(s_NameCol);
1044 }
1045 
1046 void DirectoryMergeWindow::slotChooseAEverywhere()
1047 {
1048     d->setAllMergeOperations(eCopyAToDest);
1049 }
1050 
1051 void DirectoryMergeWindow::slotChooseBEverywhere()
1052 {
1053     d->setAllMergeOperations(eCopyBToDest);
1054 }
1055 
1056 void DirectoryMergeWindow::slotChooseCEverywhere()
1057 {
1058     d->setAllMergeOperations(eCopyCToDest);
1059 }
1060 
1061 void DirectoryMergeWindow::slotAutoChooseEverywhere()
1062 {
1063     e_MergeOperation eDefaultMergeOp = d->isDirThreeWay() ? eMergeABCToDest : d->m_bSyncMode ? eMergeToAB :
1064                                                                                                eMergeABToDest;
1065     d->setAllMergeOperations(eDefaultMergeOp);
1066 }
1067 
1068 void DirectoryMergeWindow::slotNoOpEverywhere()
1069 {
1070     d->setAllMergeOperations(eNoOperation);
1071 }
1072 
1073 void DirectoryMergeWindow::slotFoldAllSubdirs()
1074 {
1075     collapseAll();
1076 }
1077 
1078 void DirectoryMergeWindow::slotUnfoldAllSubdirs()
1079 {
1080     expandAll();
1081 }
1082 
1083 // Merge current item (merge mode)
1084 void DirectoryMergeWindow::slotCurrentDoNothing()
1085 {
1086     d->setMergeOperation(currentIndex(), eNoOperation);
1087 }
1088 
1089 void DirectoryMergeWindow::slotCurrentChooseA()
1090 {
1091     d->setMergeOperation(currentIndex(), d->m_bSyncMode ? eCopyAToB : eCopyAToDest);
1092 }
1093 
1094 void DirectoryMergeWindow::slotCurrentChooseB()
1095 {
1096     d->setMergeOperation(currentIndex(), d->m_bSyncMode ? eCopyBToA : eCopyBToDest);
1097 }
1098 
1099 void DirectoryMergeWindow::slotCurrentChooseC()
1100 {
1101     d->setMergeOperation(currentIndex(), eCopyCToDest);
1102 }
1103 
1104 void DirectoryMergeWindow::slotCurrentMerge()
1105 {
1106     bool bThreeDirs = d->isDirThreeWay();
1107     d->setMergeOperation(currentIndex(), bThreeDirs ? eMergeABCToDest : eMergeABToDest);
1108 }
1109 
1110 void DirectoryMergeWindow::slotCurrentDelete()
1111 {
1112     d->setMergeOperation(currentIndex(), eDeleteFromDest);
1113 }
1114 // Sync current item
1115 void DirectoryMergeWindow::slotCurrentCopyAToB()
1116 {
1117     d->setMergeOperation(currentIndex(), eCopyAToB);
1118 }
1119 
1120 void DirectoryMergeWindow::slotCurrentCopyBToA()
1121 {
1122     d->setMergeOperation(currentIndex(), eCopyBToA);
1123 }
1124 
1125 void DirectoryMergeWindow::slotCurrentDeleteA()
1126 {
1127     d->setMergeOperation(currentIndex(), eDeleteA);
1128 }
1129 
1130 void DirectoryMergeWindow::slotCurrentDeleteB()
1131 {
1132     d->setMergeOperation(currentIndex(), eDeleteB);
1133 }
1134 
1135 void DirectoryMergeWindow::slotCurrentDeleteAAndB()
1136 {
1137     d->setMergeOperation(currentIndex(), eDeleteAB);
1138 }
1139 
1140 void DirectoryMergeWindow::slotCurrentMergeToA()
1141 {
1142     d->setMergeOperation(currentIndex(), eMergeToA);
1143 }
1144 void DirectoryMergeWindow::slotCurrentMergeToB()
1145 {
1146     d->setMergeOperation(currentIndex(), eMergeToB);
1147 }
1148 
1149 void DirectoryMergeWindow::slotCurrentMergeToAAndB()
1150 {
1151     d->setMergeOperation(currentIndex(), eMergeToAB);
1152 }
1153 
1154 void DirectoryMergeWindow::keyPressEvent(QKeyEvent* keyEvent)
1155 {
1156     if((keyEvent->modifiers() & Qt::ControlModifier) != 0)
1157     {
1158         MergeFileInfos* pMFI = d->getMFI(currentIndex());
1159         if(pMFI == nullptr)
1160             return;
1161 
1162         bool bThreeDirs = MergeFileInfos::isThreeWay();
1163         bool bMergeMode = bThreeDirs || !d->m_bSyncMode;
1164         bool bFTConflict = pMFI->conflictingFileTypes();
1165 
1166         switch(keyEvent->key())
1167         {
1168             case Qt::Key_1:
1169                 if(pMFI->existsInA())
1170                 {
1171                     slotCurrentChooseA();
1172                 }
1173                 return;
1174             case Qt::Key_2:
1175                 if(pMFI->existsInB())
1176                 {
1177                     slotCurrentChooseB();
1178                 }
1179                 return;
1180             case Qt::Key_Space:
1181                 slotCurrentDoNothing();
1182                 return;
1183         }
1184 
1185         if(bMergeMode)
1186         {
1187             switch(keyEvent->key())
1188             {
1189                 case Qt::Key_3:
1190                     if(pMFI->existsInC())
1191                     {
1192                         slotCurrentChooseC();
1193                     }
1194                     return;
1195 
1196                 case Qt::Key_4:
1197                     if(!bFTConflict)
1198                     {
1199                         slotCurrentMerge();
1200                     }
1201                     return;
1202                 case Qt::Key_Delete:
1203                     slotCurrentDelete();
1204                     return;
1205             }
1206         }
1207         else
1208         {
1209             switch(keyEvent->key())
1210             {
1211                 case Qt::Key_4:
1212                     if(!bFTConflict)
1213                     {
1214                         slotCurrentMergeToAAndB();
1215                     }
1216                     return;
1217                 case Qt::Key_Delete:
1218                     if(pMFI->existsInA() && pMFI->existsInB())
1219                         slotCurrentDeleteAAndB();
1220                     else if(pMFI->existsInA())
1221                         slotCurrentDeleteA();
1222                     else if(pMFI->existsInB())
1223                         slotCurrentDeleteB();
1224                     return;
1225             }
1226         }
1227     }
1228     //Override Qt's default behavior for this key.
1229     else if(keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Enter)
1230     {
1231         onDoubleClick(currentIndex());
1232         return;
1233     }
1234 
1235     QTreeView::keyPressEvent(keyEvent);
1236 }
1237 
1238 void DirectoryMergeWindow::focusInEvent(QFocusEvent*)
1239 {
1240     Q_EMIT updateAvailabilities();
1241 }
1242 void DirectoryMergeWindow::focusOutEvent(QFocusEvent*)
1243 {
1244     Q_EMIT updateAvailabilities();
1245 }
1246 
1247 void DirectoryMergeWindow::DirectoryMergeWindowPrivate::setAllMergeOperations(e_MergeOperation eDefaultOperation)
1248 {
1249     if(Compat::PrimaryAction == Compat::warningTwoActions(mWindow,
1250                                                           i18n("This affects all merge operations."),
1251                                                           i18n("Changing All Merge Operations"),
1252                                                           KStandardGuiItem::cont(),
1253                                                           KStandardGuiItem::cancel()))
1254     {
1255         for(qint32 i = 0; i < rowCount(); ++i)
1256         {
1257             calcSuggestedOperation(index(i, 0, QModelIndex()), eDefaultOperation);
1258         }
1259     }
1260 }
1261 
1262 QModelIndex DirectoryMergeWindow::DirectoryMergeWindowPrivate::nextSibling(const QModelIndex& mi)
1263 {
1264     QModelIndex miParent = mi.parent();
1265     qint32 currentIdx = mi.row();
1266     if(currentIdx + 1 < mi.model()->rowCount(miParent))
1267         return mi.model()->index(mi.row() + 1, 0, miParent); // next child of parent
1268     return QModelIndex();
1269 }
1270 
1271 // Iterate through the complete tree. Start by specifying QListView::firstChild().
1272 QModelIndex DirectoryMergeWindow::DirectoryMergeWindowPrivate::treeIterator(QModelIndex mi, bool bVisitChildren, bool bFindInvisible)
1273 {
1274     if(mi.isValid())
1275     {
1276         do
1277         {
1278             if(bVisitChildren && mi.model()->rowCount(mi) != 0)
1279                 mi = mi.model()->index(0, 0, mi);
1280             else
1281             {
1282                 QModelIndex miNextSibling = nextSibling(mi);
1283                 if(miNextSibling.isValid())
1284                     mi = miNextSibling;
1285                 else
1286                 {
1287                     mi = mi.parent();
1288                     while(mi.isValid())
1289                     {
1290                         miNextSibling = nextSibling(mi);
1291                         if(miNextSibling.isValid())
1292                         {
1293                             mi = miNextSibling;
1294                             break;
1295                         }
1296                         else
1297                         {
1298                             mi = mi.parent();
1299                         }
1300                     }
1301                 }
1302             }
1303         } while(mi.isValid() && mWindow->isRowHidden(mi.row(), mi.parent()) && !bFindInvisible);
1304     }
1305     return mi;
1306 }
1307 
1308 void DirectoryMergeWindow::DirectoryMergeWindowPrivate::prepareListView()
1309 {
1310     QStringList errors;
1311     //TODO   clear();
1312     PixMapUtils::initPixmaps(gOptions->newestFileColor(), gOptions->oldestFileColor(),
1313                              gOptions->midAgeFileColor(), gOptions->missingFileColor());
1314 
1315     mWindow->setRootIsDecorated(true);
1316 
1317     QtSizeType nrOfFiles = m_fileMergeMap.size();
1318     qint32 currentIdx = 1;
1319     QElapsedTimer t;
1320     t.start();
1321     ProgressProxy::setMaxNofSteps(nrOfFiles);
1322 
1323     for(MergeFileInfos& mfi: m_fileMergeMap)
1324     {
1325         const QString& fileName = mfi.subPath();
1326 
1327         ProgressProxy::setInformation(
1328             i18n("Processing %1 / %2\n%3", currentIdx, nrOfFiles, fileName), currentIdx, false);
1329         if(ProgressProxy::wasCancelled()) break;
1330         ++currentIdx;
1331 
1332         // The comparisons and calculations for each file take place here.
1333         if(!mfi.compareFilesAndCalcAges(errors, mWindow) && errors.size() >= 30)
1334             break;
1335 
1336         // Get dirname from fileName: Search for "/" from end:
1337         QtSizeType pos = fileName.lastIndexOf('/');
1338         QString dirPart;
1339         QString filePart;
1340         if(pos == -1)
1341         {
1342             // Top dir
1343             filePart = fileName;
1344         }
1345         else
1346         {
1347             dirPart = fileName.left(pos);
1348             filePart = fileName.mid(pos + 1);
1349         }
1350 
1351         if(dirPart.isEmpty()) // Top level
1352         {
1353             m_pRoot->addChild(&mfi); // new DirMergeItem( this, filePart, &mfi );
1354             mfi.setParent(m_pRoot);
1355         }
1356         else
1357         {
1358             const FileAccess* pFA = mfi.getFileInfoA() ? mfi.getFileInfoA() : mfi.getFileInfoB() ? mfi.getFileInfoB() :
1359                                                                                                    mfi.getFileInfoC();
1360             MergeFileInfos& dirMfi = pFA->parent() ? m_fileMergeMap[FileKey(*pFA->parent())] : *m_pRoot; // parent
1361 
1362             dirMfi.addChild(&mfi); // new DirMergeItem( dirMfi.m_pDMI, filePart, &mfi );
1363             mfi.setParent(&dirMfi);
1364             //   // Equality for parent dirs is set in updateFileVisibilities()
1365         }
1366 
1367         mfi.updateAge();
1368     }
1369 
1370     if(errors.size() > 0)
1371     {
1372         if(errors.size() < 15)
1373         {
1374             KMessageBox::errorList(mWindow, i18n("Some files could not be processed."), errors);
1375         }
1376         else if(errors.size() < 30)
1377         {
1378             KMessageBox::error(mWindow, i18n("Some files could not be processed."));
1379         }
1380         else
1381             KMessageBox::error(mWindow, i18n("Aborting due to too many errors."));
1382     }
1383 
1384     beginResetModel();
1385     endResetModel();
1386 }
1387 
1388 void DirectoryMergeWindow::DirectoryMergeWindowPrivate::calcSuggestedOperation(const QModelIndex& mi, e_MergeOperation eDefaultMergeOp)
1389 {
1390     const MergeFileInfos* pMFI = getMFI(mi);
1391     if(pMFI == nullptr)
1392         return;
1393 
1394     bool bCheckC = MergeFileInfos::isThreeWay();
1395     bool bCopyNewer = gOptions->m_bDmCopyNewer;
1396     bool bOtherDest = !((gDirInfo->destDir().absoluteFilePath() == gDirInfo->dirA().absoluteFilePath()) ||
1397                         (gDirInfo->destDir().absoluteFilePath() == gDirInfo->dirB().absoluteFilePath()) ||
1398                         (bCheckC && gDirInfo->destDir().absoluteFilePath() == gDirInfo->dirC().absoluteFilePath()));
1399 
1400     //Crash and burn in debug mode these states are never valid.
1401     //The checks are duplicated here so they show in the assert text.
1402     assert(!(eDefaultMergeOp == eMergeABCToDest && !bCheckC));
1403     assert(!(eDefaultMergeOp == eMergeToAB && bCheckC));
1404 
1405     //Check for two bugged states that are recoverable. This should never happen!
1406     if(Q_UNLIKELY(eDefaultMergeOp == eMergeABCToDest && !bCheckC))
1407     {
1408         qCWarning(kdiffMain) << "Invalid State detected in DirectoryMergeWindow::DirectoryMergeWindowPrivate::calcSuggestedOperation";
1409         eDefaultMergeOp = eMergeABToDest;
1410     }
1411     if(Q_UNLIKELY(eDefaultMergeOp == eMergeToAB && bCheckC))
1412     {
1413         qCWarning(kdiffMain) << "Invalid State detected in DirectoryMergeWindow::DirectoryMergeWindowPrivate::calcSuggestedOperation";
1414         eDefaultMergeOp = eMergeABCToDest;
1415     }
1416 
1417     if(eDefaultMergeOp == eMergeToA || eDefaultMergeOp == eMergeToB ||
1418        eDefaultMergeOp == eMergeABCToDest || eDefaultMergeOp == eMergeABToDest || eDefaultMergeOp == eMergeToAB)
1419     {
1420         if(!bCheckC)
1421         {
1422             if(pMFI->isEqualAB())
1423             {
1424                 setMergeOperation(mi, bOtherDest ? eCopyBToDest : eNoOperation);
1425             }
1426             else if(pMFI->existsInA() && pMFI->existsInB())
1427             {
1428                 //TODO: verify conditions here
1429                 if(!bCopyNewer || pMFI->isDirA())
1430                     setMergeOperation(mi, eDefaultMergeOp);
1431                 else if(pMFI->conflictingAges())
1432                 {
1433                     setMergeOperation(mi, eConflictingAges);
1434                 }
1435                 else
1436                 {
1437                     if(pMFI->getAgeA() == eNew)
1438                         setMergeOperation(mi, eDefaultMergeOp == eMergeToAB ? eCopyAToB : eCopyAToDest);
1439                     else
1440                         setMergeOperation(mi, eDefaultMergeOp == eMergeToAB ? eCopyBToA : eCopyBToDest);
1441                 }
1442             }
1443             else if(!pMFI->existsInA() && pMFI->existsInB())
1444             {
1445                 if(eDefaultMergeOp == eMergeABToDest)
1446                     setMergeOperation(mi, eCopyBToDest);
1447                 else if(eDefaultMergeOp == eMergeToB)
1448                     setMergeOperation(mi, eNoOperation);
1449                 else
1450                     setMergeOperation(mi, eCopyBToA);
1451             }
1452             else if(pMFI->existsInA() && !pMFI->existsInB())
1453             {
1454                 if(eDefaultMergeOp == eMergeABToDest)
1455                     setMergeOperation(mi, eCopyAToDest);
1456                 else if(eDefaultMergeOp == eMergeToA)
1457                     setMergeOperation(mi, eNoOperation);
1458                 else
1459                     setMergeOperation(mi, eCopyAToB);
1460             }
1461             else //if ( !pMFI->existsInA() && !pMFI->existsInB() )
1462             {
1463                 setMergeOperation(mi, eNoOperation);
1464             }
1465         }
1466         else
1467         {
1468             if(pMFI->isEqualAB() && pMFI->isEqualAC())
1469             {
1470                 setMergeOperation(mi, bOtherDest ? eCopyCToDest : eNoOperation);
1471             }
1472             else if(pMFI->existsInA() && pMFI->existsInB() && pMFI->existsInC())
1473             {
1474                 if(pMFI->isEqualAB() || pMFI->isEqualBC())
1475                     setMergeOperation(mi, eCopyCToDest);
1476                 else if(pMFI->isEqualAC())
1477                     setMergeOperation(mi, eCopyBToDest);
1478                 else
1479                     setMergeOperation(mi, eMergeABCToDest);
1480             }
1481             else if(pMFI->existsInA() && pMFI->existsInB() && !pMFI->existsInC())
1482             {
1483                 if(pMFI->isEqualAB())
1484                     setMergeOperation(mi, eDeleteFromDest);
1485                 else
1486                     setMergeOperation(mi, eChangedAndDeleted);
1487             }
1488             else if(pMFI->existsInA() && !pMFI->existsInB() && pMFI->existsInC())
1489             {
1490                 if(pMFI->isEqualAC())
1491                     setMergeOperation(mi, eDeleteFromDest);
1492                 else
1493                     setMergeOperation(mi, eChangedAndDeleted);
1494             }
1495             else if(!pMFI->existsInA() && pMFI->existsInB() && pMFI->existsInC())
1496             {
1497                 if(pMFI->isEqualBC())
1498                     setMergeOperation(mi, eCopyCToDest);
1499                 else
1500                     setMergeOperation(mi, eMergeABCToDest);
1501             }
1502             else if(!pMFI->existsInA() && !pMFI->existsInB() && pMFI->existsInC())
1503             {
1504                 setMergeOperation(mi, eCopyCToDest);
1505             }
1506             else if(!pMFI->existsInA() && pMFI->existsInB() && !pMFI->existsInC())
1507             {
1508                 setMergeOperation(mi, eCopyBToDest);
1509             }
1510             else if(pMFI->existsInA() && !pMFI->existsInB() && !pMFI->existsInC())
1511             {
1512                 setMergeOperation(mi, eDeleteFromDest);
1513             }
1514             else //if ( !pMFI->existsInA() && !pMFI->existsInB() && !pMFI->existsInC() )
1515             {
1516                 setMergeOperation(mi, eNoOperation);
1517             }
1518         }
1519 
1520         // Now check if file/dir-types fit.
1521         if(pMFI->conflictingFileTypes())
1522         {
1523             setMergeOperation(mi, eConflictingFileTypes);
1524         }
1525     }
1526     else
1527     {
1528         e_MergeOperation eMO = eDefaultMergeOp;
1529         switch(eDefaultMergeOp)
1530         {
1531             case eConflictingFileTypes:
1532             case eChangedAndDeleted:
1533             case eConflictingAges:
1534             case eDeleteA:
1535             case eDeleteB:
1536             case eDeleteAB:
1537             case eDeleteFromDest:
1538             case eNoOperation:
1539                 break;
1540             case eCopyAToB:
1541                 if(!pMFI->existsInA())
1542                 {
1543                     eMO = eDeleteB;
1544                 }
1545                 break;
1546             case eCopyBToA:
1547                 if(!pMFI->existsInB())
1548                 {
1549                     eMO = eDeleteA;
1550                 }
1551                 break;
1552             case eCopyAToDest:
1553                 if(!pMFI->existsInA())
1554                 {
1555                     eMO = eDeleteFromDest;
1556                 }
1557                 break;
1558             case eCopyBToDest:
1559                 if(!pMFI->existsInB())
1560                 {
1561                     eMO = eDeleteFromDest;
1562                 }
1563                 break;
1564             case eCopyCToDest:
1565                 if(!pMFI->existsInC())
1566                 {
1567                     eMO = eDeleteFromDest;
1568                 }
1569                 break;
1570 
1571             case eMergeToA:
1572             case eMergeToB:
1573             case eMergeToAB:
1574             case eMergeABCToDest:
1575             case eMergeABToDest:
1576                 break;
1577             default:
1578                 assert(false);
1579                 break;
1580         }
1581         setMergeOperation(mi, eMO);
1582     }
1583 }
1584 
1585 void DirectoryMergeWindow::onDoubleClick(const QModelIndex& mi)
1586 {
1587     if(!mi.isValid())
1588         return;
1589 
1590     d->m_bSimulatedMergeStarted = false;
1591     if(d->m_bDirectoryMerge)
1592         mergeCurrentFile();
1593     else
1594         compareCurrentFile();
1595 }
1596 
1597 void DirectoryMergeWindow::currentChanged(const QModelIndex& current, const QModelIndex& previous)
1598 {
1599     QTreeView::currentChanged(current, previous);
1600     MergeFileInfos* pMFI = d->getMFI(current);
1601     if(pMFI == nullptr)
1602         return;
1603 
1604     d->m_pDirectoryMergeInfo->setInfo(gDirInfo->dirA(), gDirInfo->dirB(), gDirInfo->dirC(), gDirInfo->destDir(), *pMFI);
1605 }
1606 
1607 void DirectoryMergeWindow::mousePressEvent(QMouseEvent* e)
1608 {
1609     QTreeView::mousePressEvent(e);
1610     QModelIndex mi = indexAt(e->pos());
1611     qint32 c = mi.column();
1612 #if(QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
1613     QPoint p = e->globalPos();
1614 #else
1615     QPoint p = e->globalPosition().toPoint();
1616 #endif
1617     MergeFileInfos* pMFI = d->getMFI(mi);
1618     if(pMFI == nullptr)
1619         return;
1620 
1621     if(c == s_OpCol)
1622     {
1623         bool bThreeDirs = d->isDirThreeWay();
1624 
1625         QMenu m(this);
1626         if(bThreeDirs)
1627         {
1628             m.addAction(d->m_pDirCurrentDoNothing);
1629             qint32 count = 0;
1630             if(pMFI->existsInA())
1631             {
1632                 m.addAction(d->m_pDirCurrentChooseA);
1633                 ++count;
1634             }
1635             if(pMFI->existsInB())
1636             {
1637                 m.addAction(d->m_pDirCurrentChooseB);
1638                 ++count;
1639             }
1640             if(pMFI->existsInC())
1641             {
1642                 m.addAction(d->m_pDirCurrentChooseC);
1643                 ++count;
1644             }
1645             if(!pMFI->conflictingFileTypes() && count > 1) m.addAction(d->m_pDirCurrentMerge);
1646             m.addAction(d->m_pDirCurrentDelete);
1647         }
1648         else if(d->m_bSyncMode)
1649         {
1650             m.addAction(d->m_pDirCurrentSyncDoNothing);
1651             if(pMFI->existsInA()) m.addAction(d->m_pDirCurrentSyncCopyAToB);
1652             if(pMFI->existsInB()) m.addAction(d->m_pDirCurrentSyncCopyBToA);
1653             if(pMFI->existsInA()) m.addAction(d->m_pDirCurrentSyncDeleteA);
1654             if(pMFI->existsInB()) m.addAction(d->m_pDirCurrentSyncDeleteB);
1655             if(pMFI->existsInA() && pMFI->existsInB())
1656             {
1657                 m.addAction(d->m_pDirCurrentSyncDeleteAAndB);
1658                 if(!pMFI->conflictingFileTypes())
1659                 {
1660                     m.addAction(d->m_pDirCurrentSyncMergeToA);
1661                     m.addAction(d->m_pDirCurrentSyncMergeToB);
1662                     m.addAction(d->m_pDirCurrentSyncMergeToAAndB);
1663                 }
1664             }
1665         }
1666         else
1667         {
1668             m.addAction(d->m_pDirCurrentDoNothing);
1669             if(pMFI->existsInA())
1670             {
1671                 m.addAction(d->m_pDirCurrentChooseA);
1672             }
1673             if(pMFI->existsInB())
1674             {
1675                 m.addAction(d->m_pDirCurrentChooseB);
1676             }
1677             if(!pMFI->conflictingFileTypes() && pMFI->existsInA() && pMFI->existsInB()) m.addAction(d->m_pDirCurrentMerge);
1678             m.addAction(d->m_pDirCurrentDelete);
1679         }
1680 
1681         m.exec(p);
1682     }
1683     else if(c == s_ACol || c == s_BCol || c == s_CCol)
1684     {
1685         QString itemPath;
1686         if(c == s_ACol && pMFI->existsInA())
1687         {
1688             itemPath = pMFI->fullNameA();
1689         }
1690         else if(c == s_BCol && pMFI->existsInB())
1691         {
1692             itemPath = pMFI->fullNameB();
1693         }
1694         else if(c == s_CCol && pMFI->existsInC())
1695         {
1696             itemPath = pMFI->fullNameC();
1697         }
1698 
1699         if(!itemPath.isEmpty())
1700         {
1701             d->selectItemAndColumn(mi, e->button() == Qt::RightButton);
1702         }
1703     }
1704 }
1705 
1706 #ifndef QT_NO_CONTEXTMENU
1707 void DirectoryMergeWindow::contextMenuEvent(QContextMenuEvent* e)
1708 {
1709     QModelIndex mi = indexAt(e->pos());
1710     qint32 c = mi.column();
1711 
1712     MergeFileInfos* pMFI = d->getMFI(mi);
1713     if(pMFI == nullptr)
1714         return;
1715     if(c == s_ACol || c == s_BCol || c == s_CCol)
1716     {
1717         QString itemPath;
1718         if(c == s_ACol && pMFI->existsInA())
1719         {
1720             itemPath = pMFI->fullNameA();
1721         }
1722         else if(c == s_BCol && pMFI->existsInB())
1723         {
1724             itemPath = pMFI->fullNameB();
1725         }
1726         else if(c == s_CCol && pMFI->existsInC())
1727         {
1728             itemPath = pMFI->fullNameC();
1729         }
1730 
1731         if(!itemPath.isEmpty())
1732         {
1733             d->selectItemAndColumn(mi, true);
1734             QMenu m(this);
1735             m.addAction(d->m_pDirCompareExplicit);
1736             m.addAction(d->m_pDirMergeExplicit);
1737 
1738             m.popup(e->globalPos());
1739         }
1740     }
1741 }
1742 #endif
1743 
1744 QString DirectoryMergeWindow::DirectoryMergeWindowPrivate::getFileName(const QModelIndex& mi) const
1745 {
1746     MergeFileInfos* pMFI = getMFI(mi);
1747     if(pMFI != nullptr)
1748     {
1749         return mi.column() == s_ACol ? pMFI->getFileInfoA()->absoluteFilePath() : mi.column() == s_BCol ? pMFI->getFileInfoB()->absoluteFilePath() : mi.column() == s_CCol ? pMFI->getFileInfoC()->absoluteFilePath() : QString("");
1750     }
1751     return QString();
1752 }
1753 
1754 bool DirectoryMergeWindow::DirectoryMergeWindowPrivate::isDir(const QModelIndex& mi) const
1755 {
1756     MergeFileInfos* pMFI = getMFI(mi);
1757     if(pMFI != nullptr)
1758     {
1759         return mi.column() == s_ACol ? pMFI->isDirA() : mi.column() == s_BCol ? pMFI->isDirB() : pMFI->isDirC();
1760     }
1761     return false;
1762 }
1763 
1764 void DirectoryMergeWindow::DirectoryMergeWindowPrivate::selectItemAndColumn(const QModelIndex& mi, bool bContextMenu)
1765 {
1766     if(bContextMenu && (mi == m_selection1Index || mi == m_selection2Index || mi == m_selection3Index))
1767         return;
1768 
1769     QModelIndex old1 = m_selection1Index;
1770     QModelIndex old2 = m_selection2Index;
1771     QModelIndex old3 = m_selection3Index;
1772 
1773     bool bReset = false;
1774 
1775     if(m_selection1Index.isValid())
1776     {
1777         if(isDir(m_selection1Index) != isDir(mi))
1778             bReset = true;
1779     }
1780 
1781     if(bReset || m_selection3Index.isValid() || mi == m_selection1Index || mi == m_selection2Index || mi == m_selection3Index)
1782     {
1783         // restart
1784         m_selection1Index = QModelIndex();
1785         m_selection2Index = QModelIndex();
1786         m_selection3Index = QModelIndex();
1787     }
1788     else if(!m_selection1Index.isValid())
1789     {
1790         m_selection1Index = mi;
1791         m_selection2Index = QModelIndex();
1792         m_selection3Index = QModelIndex();
1793     }
1794     else if(!m_selection2Index.isValid())
1795     {
1796         m_selection2Index = mi;
1797         m_selection3Index = QModelIndex();
1798     }
1799     else if(!m_selection3Index.isValid())
1800     {
1801         m_selection3Index = mi;
1802     }
1803     if(old1.isValid()) Q_EMIT dataChanged(old1, old1);
1804     if(old2.isValid()) Q_EMIT dataChanged(old2, old2);
1805     if(old3.isValid()) Q_EMIT dataChanged(old3, old3);
1806     if(m_selection1Index.isValid()) Q_EMIT dataChanged(m_selection1Index, m_selection1Index);
1807     if(m_selection2Index.isValid()) Q_EMIT dataChanged(m_selection2Index, m_selection2Index);
1808     if(m_selection3Index.isValid()) Q_EMIT dataChanged(m_selection3Index, m_selection3Index);
1809     Q_EMIT mWindow->updateAvailabilities();
1810 }
1811 
1812 //TODO
1813 //void DirMergeItem::init(MergeFileInfos* pMFI)
1814 //{
1815 //   pMFI->m_pDMI = this; //no not here
1816 //   m_pMFI = pMFI;
1817 //   TotalDiffStatus& tds = pMFI->m_totalDiffStatus;
1818 //   if ( m_pMFI->dirA() || m_pMFI->dirB() || m_pMFI->isDirC() )
1819 //   {
1820 //   }
1821 //   else
1822 //   {
1823 //      setText( s_UnsolvedCol, QString::number( tds.getUnsolvedConflicts() ) );
1824 //      setText( s_SolvedCol,   QString::number( tds.getSolvedConflicts() ) );
1825 //      setText( s_NonWhiteCol, QString::number( tds.getUnsolvedConflicts() + tds.getSolvedConflicts() - tds.getWhitespaceConflicts() ) );
1826 //      setText( s_WhiteCol,    QString::number( tds.getWhitespaceConflicts() ) );
1827 //   }
1828 //   setSizeHint( s_ACol, QSize(17,17) ); // Iconsize
1829 //   setSizeHint( s_BCol, QSize(17,17) ); // Iconsize
1830 //   setSizeHint( s_CCol, QSize(17,17) ); // Iconsize
1831 //}
1832 
1833 void DirectoryMergeWindow::DirectoryMergeWindowPrivate::sort([[maybe_unused]] qint32 column, Qt::SortOrder order)
1834 {
1835     beginResetModel();
1836     m_pRoot->sort(order);
1837     endResetModel();
1838 }
1839 
1840 void DirectoryMergeWindow::DirectoryMergeWindowPrivate::setMergeOperation(const QModelIndex& mi, e_MergeOperation eMergeOp, bool bRecursive)
1841 {
1842     MergeFileInfos* pMFI = getMFI(mi);
1843     if(pMFI == nullptr)
1844         return;
1845 
1846     if(eMergeOp != pMFI->getOperation())
1847     {
1848         pMFI->startOperation();
1849         setOpStatus(mi, eOpStatusNone);
1850     }
1851 
1852     pMFI->setOperation(eMergeOp);
1853     if(bRecursive)
1854     {
1855         e_MergeOperation eChildrenMergeOp = pMFI->getOperation();
1856         if(eChildrenMergeOp == eConflictingFileTypes)
1857             eChildrenMergeOp = MergeFileInfos::isThreeWay() ? eMergeABCToDest : eMergeABToDest;
1858 
1859         for(qint32 childIdx = 0; childIdx < pMFI->children().count(); ++childIdx)
1860         {
1861             calcSuggestedOperation(index(childIdx, 0, mi), eChildrenMergeOp);
1862         }
1863     }
1864 }
1865 
1866 void DirectoryMergeWindow::compareCurrentFile()
1867 {
1868     if(!d->canContinue()) return;
1869 
1870     if(d->m_bRealMergeStarted)
1871     {
1872         KMessageBox::error(this, i18n("This operation is currently not possible."), i18n("Operation Not Possible"));
1873         return;
1874     }
1875     QStringList errors;
1876     if(MergeFileInfos* pMFI = d->getMFI(currentIndex()))
1877     {
1878         if(!(pMFI->hasDir()))
1879         {
1880             Q_EMIT startDiffMerge(errors,
1881                                   pMFI->existsInA() ? pMFI->getFileInfoA()->absoluteFilePath() : QString(""),
1882                                   pMFI->existsInB() ? pMFI->getFileInfoB()->absoluteFilePath() : QString(""),
1883                                   pMFI->existsInC() ? pMFI->getFileInfoC()->absoluteFilePath() : QString(""),
1884                                   "",
1885                                   "", "", "", nullptr);
1886         }
1887     }
1888     Q_EMIT updateAvailabilities();
1889 }
1890 
1891 void DirectoryMergeWindow::slotCompareExplicitlySelectedFiles()
1892 {
1893     if(!d->isDir(d->m_selection1Index) && !d->canContinue()) return;
1894 
1895     if(d->m_bRealMergeStarted)
1896     {
1897         KMessageBox::error(this, i18n("This operation is currently not possible."), i18n("Operation Not Possible"));
1898         return;
1899     }
1900 
1901     QStringList errors;
1902     Q_EMIT startDiffMerge(errors,
1903                           d->getFileName(d->m_selection1Index),
1904                           d->getFileName(d->m_selection2Index),
1905                           d->getFileName(d->m_selection3Index),
1906                           "",
1907                           "", "", "", nullptr);
1908     d->m_selection1Index = QModelIndex();
1909     d->m_selection2Index = QModelIndex();
1910     d->m_selection3Index = QModelIndex();
1911 
1912     Q_EMIT updateAvailabilities();
1913     update();
1914 }
1915 
1916 void DirectoryMergeWindow::slotMergeExplicitlySelectedFiles()
1917 {
1918     if(!d->isDir(d->m_selection1Index) && !d->canContinue()) return;
1919 
1920     if(d->m_bRealMergeStarted)
1921     {
1922         KMessageBox::error(this, i18n("This operation is currently not possible."), i18n("Operation Not Possible"));
1923         return;
1924     }
1925     QStringList errors;
1926     QString fn1 = d->getFileName(d->m_selection1Index);
1927     QString fn2 = d->getFileName(d->m_selection2Index);
1928     QString fn3 = d->getFileName(d->m_selection3Index);
1929 
1930     Q_EMIT startDiffMerge(errors, fn1, fn2, fn3,
1931                           fn3.isEmpty() ? fn2 : fn3,
1932                           "", "", "", nullptr);
1933     d->m_selection1Index = QModelIndex();
1934     d->m_selection2Index = QModelIndex();
1935     d->m_selection3Index = QModelIndex();
1936 
1937     Q_EMIT updateAvailabilities();
1938     update();
1939 }
1940 
1941 bool DirectoryMergeWindow::isFileSelected()
1942 {
1943     if(MergeFileInfos* pMFI = d->getMFI(currentIndex()))
1944     {
1945         return !(pMFI->hasDir() || pMFI->conflictingFileTypes());
1946     }
1947     return false;
1948 }
1949 
1950 void DirectoryMergeWindow::mergeResultSaved(const QString& fileName)
1951 {
1952     QModelIndex mi = (d->m_mergeItemList.empty() || d->m_currentIndexForOperation == d->m_mergeItemList.end())
1953                          ? QModelIndex()
1954                          : *d->m_currentIndexForOperation;
1955 
1956     MergeFileInfos* pMFI = d->getMFI(mi);
1957     if(pMFI == nullptr)
1958     {
1959         // This can happen if the same file is saved and modified and saved again. Nothing to do then.
1960         return;
1961     }
1962     if(fileName == pMFI->fullNameDest())
1963     {
1964         if(pMFI->getOperation() == eMergeToAB)
1965         {
1966             bool bSuccess = d->copyFLD(pMFI->fullNameB(), pMFI->fullNameA());
1967             if(!bSuccess)
1968             {
1969                 KMessageBox::error(this, i18n("An error occurred while copying."));
1970                 d->m_pStatusInfo->setWindowTitle(i18n("Merge Error"));
1971                 d->m_pStatusInfo->exec();
1972                 //if ( m_pStatusInfo->firstChild()!=0 )
1973                 //   m_pStatusInfo->ensureItemVisible( m_pStatusInfo->last() );
1974                 d->m_bError = true;
1975                 d->setOpStatus(mi, eOpStatusError);
1976                 pMFI->setOperation(eCopyBToA);
1977                 return;
1978             }
1979         }
1980         d->setOpStatus(mi, eOpStatusDone);
1981         pMFI->endOperation();
1982         if(d->m_mergeItemList.size() == 1)
1983         {
1984             d->m_mergeItemList.clear();
1985             d->m_bRealMergeStarted = false;
1986         }
1987     }
1988 
1989     Q_EMIT updateAvailabilities();
1990 }
1991 
1992 bool DirectoryMergeWindow::DirectoryMergeWindowPrivate::canContinue()
1993 {
1994     if(m_app.canContinue() && !m_bError)
1995     {
1996         QModelIndex mi = (m_mergeItemList.empty() || m_currentIndexForOperation == m_mergeItemList.end()) ? QModelIndex() : *m_currentIndexForOperation;
1997         MergeFileInfos* pMFI = getMFI(mi);
1998         if(pMFI && pMFI->isOperationRunning())
1999         {
2000             setOpStatus(mi, eOpStatusNotSaved);
2001             pMFI->endOperation();
2002             if(m_mergeItemList.size() == 1)
2003             {
2004                 m_mergeItemList.clear();
2005                 m_bRealMergeStarted = false;
2006             }
2007         }
2008 
2009         return true;
2010     }
2011     return false;
2012 }
2013 
2014 bool DirectoryMergeWindow::DirectoryMergeWindowPrivate::executeMergeOperation(const MergeFileInfos& mfi, bool& bSingleFileMerge)
2015 {
2016     bool bCreateBackups = gOptions->m_bDmCreateBakFiles;
2017     // First decide destname
2018     QString destName;
2019     switch(mfi.getOperation())
2020     {
2021         case eNoOperation:
2022         case eDeleteAB:
2023             break;
2024         case eMergeToAB: // let the user save in B. In mergeResultSaved() the file will be copied to A.
2025         case eMergeToB:
2026         case eDeleteB:
2027         case eCopyAToB:
2028             destName = mfi.fullNameB();
2029             break;
2030         case eMergeToA:
2031         case eDeleteA:
2032         case eCopyBToA:
2033             destName = mfi.fullNameA();
2034             break;
2035         case eMergeABToDest:
2036         case eMergeABCToDest:
2037         case eCopyAToDest:
2038         case eCopyBToDest:
2039         case eCopyCToDest:
2040         case eDeleteFromDest:
2041             /*
2042                 Do not replace with code that ignores gDirInfo->destDir().
2043                 Any such patch will be rejected. KDiff3 intentionally supports custom destination directories.
2044             */
2045             destName = mfi.fullNameDest();
2046             break;
2047         default:
2048             KMessageBox::error(mWindow, i18n("Unknown merge operation. (This must never happen!)"));
2049     }
2050 
2051     bool bSuccess = false;
2052     bSingleFileMerge = false;
2053     switch(mfi.getOperation())
2054     {
2055         case eNoOperation:
2056             bSuccess = true;
2057             break;
2058         case eCopyAToDest:
2059         case eCopyAToB:
2060             bSuccess = copyFLD(mfi.fullNameA(), destName);
2061             break;
2062         case eCopyBToDest:
2063         case eCopyBToA:
2064             bSuccess = copyFLD(mfi.fullNameB(), destName);
2065             break;
2066         case eCopyCToDest:
2067             bSuccess = copyFLD(mfi.fullNameC(), destName);
2068             break;
2069         case eDeleteFromDest:
2070         case eDeleteA:
2071         case eDeleteB:
2072             bSuccess = deleteFLD(destName, bCreateBackups);
2073             break;
2074         case eDeleteAB:
2075             bSuccess = deleteFLD(mfi.fullNameA(), bCreateBackups) &&
2076                        deleteFLD(mfi.fullNameB(), bCreateBackups);
2077             break;
2078         case eMergeABToDest:
2079         case eMergeToA:
2080         case eMergeToAB:
2081         case eMergeToB:
2082             bSuccess = mergeFLD(mfi.fullNameA(), mfi.fullNameB(), "",
2083                                 destName, bSingleFileMerge);
2084             break;
2085         case eMergeABCToDest:
2086             bSuccess = mergeFLD(
2087                 mfi.existsInA() ? mfi.fullNameA() : QString(""),
2088                 mfi.existsInB() ? mfi.fullNameB() : QString(""),
2089                 mfi.existsInC() ? mfi.fullNameC() : QString(""),
2090                 destName, bSingleFileMerge);
2091             break;
2092         default:
2093             KMessageBox::error(mWindow, i18n("Unknown merge operation."));
2094     }
2095 
2096     return bSuccess;
2097 }
2098 
2099 // Check if the merge can start, and prepare the m_mergeItemList which then contains all
2100 // items that must be merged.
2101 void DirectoryMergeWindow::DirectoryMergeWindowPrivate::prepareMergeStart(const QModelIndex& miBegin, const QModelIndex& miEnd, bool bVerbose)
2102 {
2103     if(bVerbose)
2104     {
2105         KMessageBox::ButtonCode status = Compat::warningTwoActionsCancel(mWindow,
2106                                                                          i18n("The merge is about to begin.\n\n"
2107                                                                               "Choose \"Do it\" if you have read the instructions and know what you are doing.\n"
2108                                                                               "Choosing \"Simulate it\" will tell you what would happen.\n\n"
2109                                                                               "Be aware that this program still has beta status "
2110                                                                               "and there is NO WARRANTY whatsoever! Make backups of your vital data!"),
2111                                                                          i18nc("Caption", "Starting Merge"),
2112                                                                          KGuiItem(i18nc("Button title to confirm merge", "Do It")),
2113                                                                          KGuiItem(i18nc("Button title to simulate merge", "Simulate It")));
2114         if(status == Compat::PrimaryAction)
2115             m_bRealMergeStarted = true;
2116         else if(status == Compat::SecondaryAction)
2117             m_bSimulatedMergeStarted = true;
2118         else
2119             return;
2120     }
2121     else
2122     {
2123         m_bRealMergeStarted = true;
2124     }
2125 
2126     m_mergeItemList.clear();
2127     if(!miBegin.isValid())
2128         return;
2129 
2130     for(QModelIndex mi = miBegin; mi != miEnd; mi = treeIterator(mi))
2131     {
2132         MergeFileInfos* pMFI = getMFI(mi);
2133         if(pMFI && pMFI->isOperationRunning())
2134         {
2135             m_mergeItemList.push_back(mi);
2136             QString errorText;
2137             if(pMFI->getOperation() == eConflictingFileTypes)
2138             {
2139                 errorText = i18n("The highlighted item has a different type in the different folders. Select what to do.");
2140             }
2141             if(pMFI->getOperation() == eConflictingAges)
2142             {
2143                 errorText = i18n("The modification dates of the file are equal but the files are not. Select what to do.");
2144             }
2145             if(pMFI->getOperation() == eChangedAndDeleted)
2146             {
2147                 errorText = i18n("The highlighted item was changed in one folder and deleted in the other. Select what to do.");
2148             }
2149             if(!errorText.isEmpty())
2150             {
2151                 mWindow->scrollTo(mi, QAbstractItemView::EnsureVisible);
2152                 mWindow->setCurrentIndex(mi);
2153                 KMessageBox::error(mWindow, errorText);
2154                 m_mergeItemList.clear();
2155                 m_bRealMergeStarted = false;
2156                 return;
2157             }
2158         }
2159     }
2160 
2161     m_currentIndexForOperation = m_mergeItemList.begin();
2162 }
2163 
2164 void DirectoryMergeWindow::slotRunOperationForCurrentItem()
2165 {
2166     if(!d->canContinue()) return;
2167 
2168     bool bVerbose = false;
2169     if(d->m_mergeItemList.empty())
2170     {
2171         QModelIndex miBegin = currentIndex();
2172         QModelIndex miEnd = d->treeIterator(miBegin, false, false); // find next visible sibling (no children)
2173 
2174         d->prepareMergeStart(miBegin, miEnd, bVerbose);
2175         d->mergeContinue(true, bVerbose);
2176     }
2177     else
2178         d->mergeContinue(false, bVerbose);
2179 }
2180 
2181 void DirectoryMergeWindow::slotRunOperationForAllItems()
2182 {
2183     if(!d->canContinue()) return;
2184 
2185     bool bVerbose = true;
2186     if(d->m_mergeItemList.empty())
2187     {
2188         QModelIndex miBegin = d->rowCount() > 0 ? d->index(0, 0, QModelIndex()) : QModelIndex();
2189 
2190         d->prepareMergeStart(miBegin, QModelIndex(), bVerbose);
2191         d->mergeContinue(true, bVerbose);
2192     }
2193     else
2194         d->mergeContinue(false, bVerbose);
2195 }
2196 
2197 void DirectoryMergeWindow::mergeCurrentFile()
2198 {
2199     if(!d->canContinue()) return;
2200 
2201     if(d->m_bRealMergeStarted)
2202     {
2203         KMessageBox::error(this, i18n("This operation is currently not possible because folder merge is currently running."), i18n("Operation Not Possible"));
2204         return;
2205     }
2206 
2207     if(isFileSelected())
2208     {
2209         MergeFileInfos* pMFI = d->getMFI(currentIndex());
2210         if(pMFI != nullptr)
2211         {
2212             d->m_mergeItemList.clear();
2213             d->m_mergeItemList.push_back(currentIndex());
2214             d->m_currentIndexForOperation = d->m_mergeItemList.begin();
2215             bool bDummy = false;
2216             d->mergeFLD(
2217                 pMFI->existsInA() ? pMFI->getFileInfoA()->absoluteFilePath() : QString(""),
2218                 pMFI->existsInB() ? pMFI->getFileInfoB()->absoluteFilePath() : QString(""),
2219                 pMFI->existsInC() ? pMFI->getFileInfoC()->absoluteFilePath() : QString(""),
2220                 pMFI->fullNameDest(),
2221                 bDummy);
2222         }
2223     }
2224     Q_EMIT updateAvailabilities();
2225 }
2226 
2227 // When bStart is true then m_currentIndexForOperation must still be processed.
2228 // When bVerbose is true then a messagebox will tell when the merge is complete.
2229 void DirectoryMergeWindow::DirectoryMergeWindowPrivate::mergeContinue(bool bStart, bool bVerbose)
2230 {
2231     ProgressScope pp;
2232     if(m_mergeItemList.empty())
2233         return;
2234 
2235     qint32 nrOfItems = 0;
2236     qint32 nrOfCompletedItems = 0;
2237     qint32 nrOfCompletedSimItems = 0;
2238 
2239     // Count the number of completed items (for the progress bar).
2240     for(const QModelIndex& i: m_mergeItemList)
2241     {
2242         MergeFileInfos* pMFI = getMFI(i);
2243         ++nrOfItems;
2244         if(!pMFI->isOperationRunning())
2245             ++nrOfCompletedItems;
2246         if(!pMFI->isSimOpRunning())
2247             ++nrOfCompletedSimItems;
2248     }
2249 
2250     m_pStatusInfo->hide();
2251     m_pStatusInfo->clear();
2252 
2253     QModelIndex miCurrent = m_currentIndexForOperation == m_mergeItemList.end() ? QModelIndex() : *m_currentIndexForOperation;
2254 
2255     bool bContinueWithCurrentItem = bStart; // true for first item, else false
2256     bool bSkipItem = false;
2257     if(!bStart && m_bError && miCurrent.isValid())
2258     {
2259         KMessageBox::ButtonCode status = Compat::warningTwoActionsCancel(mWindow,
2260                                                                          i18n("There was an error in the last step.\n"
2261                                                                               "Do you want to continue with the item that caused the error or do you want to skip this item?"),
2262                                                                          i18nc("Caption for message dialog", "Continue merge after an error"),
2263                                                                          KGuiItem(i18nc("Continue button title", "Continue With Last Item")),
2264                                                                          KGuiItem(i18nc("Skip button title", "Skip Item")));
2265         if(status == Compat::PrimaryAction)
2266             bContinueWithCurrentItem = true;
2267         else if(status == Compat::SecondaryAction)
2268             bSkipItem = true;
2269         else
2270             return;
2271         m_bError = false;
2272     }
2273 
2274     ProgressProxy::setMaxNofSteps(nrOfItems);
2275 
2276     bool bSuccess = true;
2277     bool bSingleFileMerge = false;
2278     bool bSim = m_bSimulatedMergeStarted;
2279     while(bSuccess)
2280     {
2281         MergeFileInfos* pMFI = getMFI(miCurrent);
2282 
2283         if(pMFI == nullptr)
2284         {
2285             m_mergeItemList.clear();
2286             m_bRealMergeStarted = false;
2287             break;
2288         }
2289 
2290         if(!bContinueWithCurrentItem)
2291         {
2292             if(bSim)
2293             {
2294                 if(rowCount(miCurrent) == 0)
2295                 {
2296                     pMFI->endSimOp();
2297                 }
2298             }
2299             else
2300             {
2301                 if(rowCount(miCurrent) == 0)
2302                 {
2303                     if(pMFI->isOperationRunning())
2304                     {
2305                         setOpStatus(miCurrent, bSkipItem ? eOpStatusSkipped : eOpStatusDone);
2306                         pMFI->endOperation();
2307                         bSkipItem = false;
2308                     }
2309                 }
2310                 else
2311                 {
2312                     setOpStatus(miCurrent, eOpStatusInProgress);
2313                 }
2314             }
2315 
2316             // Depth first
2317             QModelIndex miPrev = miCurrent;
2318             ++m_currentIndexForOperation;
2319             miCurrent = m_currentIndexForOperation == m_mergeItemList.end() ? QModelIndex() : *m_currentIndexForOperation;
2320             if((!miCurrent.isValid() || miCurrent.parent() != miPrev.parent()) && miPrev.parent().isValid())
2321             {
2322                 // Check if the parent may be set to "Done"
2323                 QModelIndex miParent = miPrev.parent();
2324                 bool bDone = true;
2325                 while(bDone && miParent.isValid())
2326                 {
2327                     for(qint32 childIdx = 0; childIdx < rowCount(miParent); ++childIdx)
2328                     {
2329                         pMFI = getMFI(index(childIdx, 0, miParent));
2330                         if((!bSim && pMFI->isOperationRunning()) || (bSim && !pMFI->isSimOpRunning()))
2331                         {
2332                             bDone = false;
2333                             break;
2334                         }
2335                     }
2336                     if(bDone)
2337                     {
2338                         pMFI = getMFI(miParent);
2339                         if(bSim)
2340                             pMFI->endSimOp();
2341                         else
2342                         {
2343                             setOpStatus(miParent, eOpStatusDone);
2344                             pMFI->endOperation();
2345                         }
2346                     }
2347                     miParent = miParent.parent();
2348                 }
2349             }
2350         }
2351 
2352         if(!miCurrent.isValid()) // end?
2353         {
2354             if(m_bRealMergeStarted)
2355             {
2356                 if(bVerbose)
2357                 {
2358                     KMessageBox::information(mWindow, i18n("Merge operation complete."), i18n("Merge Complete"));
2359                 }
2360                 m_bRealMergeStarted = false;
2361                 m_pStatusInfo->setWindowTitle(i18n("Merge Complete"));
2362             }
2363             if(m_bSimulatedMergeStarted)
2364             {
2365                 m_bSimulatedMergeStarted = false;
2366                 QModelIndex mi = rowCount() > 0 ? index(0, 0, QModelIndex()) : QModelIndex();
2367                 for(; mi.isValid(); mi = treeIterator(mi))
2368                 {
2369                     getMFI(mi)->startSimOp();
2370                 }
2371                 m_pStatusInfo->setWindowTitle(i18n("Simulated merge complete: Check if you agree with the proposed operations."));
2372                 m_pStatusInfo->exec();
2373             }
2374             m_mergeItemList.clear();
2375             m_bRealMergeStarted = false;
2376             return;
2377         }
2378 
2379         pMFI = getMFI(miCurrent);
2380 
2381         ProgressProxy::setInformation(pMFI->subPath(),
2382                                       bSim ? nrOfCompletedSimItems : nrOfCompletedItems,
2383                                       false // bRedrawUpdate
2384         );
2385 
2386         bSuccess = executeMergeOperation(*pMFI, bSingleFileMerge); // Here the real operation happens.
2387 
2388         if(bSuccess)
2389         {
2390             if(bSim)
2391                 ++nrOfCompletedSimItems;
2392             else
2393                 ++nrOfCompletedItems;
2394             bContinueWithCurrentItem = false;
2395         }
2396 
2397         if(ProgressProxy::wasCancelled())
2398             break;
2399     } // end while
2400 
2401     //g_pProgressDialog->hide();
2402 
2403     mWindow->setCurrentIndex(miCurrent);
2404     mWindow->scrollTo(miCurrent, EnsureVisible);
2405     if(!bSuccess && !bSingleFileMerge)
2406     {
2407         KMessageBox::error(mWindow, i18n("An error occurred. Press OK to see detailed information."));
2408         m_pStatusInfo->setWindowTitle(i18n("Merge Error"));
2409         m_pStatusInfo->exec();
2410         //if ( m_pStatusInfo->firstChild()!=0 )
2411         //   m_pStatusInfo->ensureItemVisible( m_pStatusInfo->last() );
2412         m_bError = true;
2413 
2414         setOpStatus(miCurrent, eOpStatusError);
2415     }
2416     else
2417     {
2418         m_bError = false;
2419     }
2420     Q_EMIT mWindow->updateAvailabilities();
2421 
2422     if(m_currentIndexForOperation == m_mergeItemList.end())
2423     {
2424         m_mergeItemList.clear();
2425         m_bRealMergeStarted = false;
2426     }
2427 }
2428 
2429 bool DirectoryMergeWindow::DirectoryMergeWindowPrivate::deleteFLD(const QString& name, bool bCreateBackup)
2430 {
2431     FileAccess fi(name, true);
2432     if(!fi.exists())
2433         return true;
2434 
2435     if(bCreateBackup)
2436     {
2437         bool bSuccess = renameFLD(name, name + ".orig");
2438         if(!bSuccess)
2439         {
2440             m_pStatusInfo->addText(i18n("Error: While deleting %1: Creating backup failed.", name));
2441             return false;
2442         }
2443     }
2444     else
2445     {
2446         if(fi.isDir() && !fi.isSymLink())
2447             m_pStatusInfo->addText(i18n("delete folder recursively( %1 )", name));
2448         else
2449             m_pStatusInfo->addText(i18n("delete( %1 )", name));
2450 
2451         if(m_bSimulatedMergeStarted)
2452         {
2453             return true;
2454         }
2455 
2456         if(fi.isDir() && !fi.isSymLink()) // recursive directory delete only for real dirs, not symlinks
2457         {
2458             DirectoryList dirList;
2459             CompositeIgnoreList ignoreList;
2460             bool bSuccess = fi.listDir(&dirList, false, true, "*", "", "", false, ignoreList); // not recursive, find hidden files
2461 
2462             if(!bSuccess)
2463             {
2464                 // No Permission to read directory or other error.
2465                 m_pStatusInfo->addText(i18n("Error: delete folder operation failed while trying to read the folder."));
2466                 return false;
2467             }
2468 
2469             for(const FileAccess& fi2: dirList) // for each file...
2470             {
2471                 assert(fi2.fileName() != "." && fi2.fileName() != "..");
2472 
2473                 bSuccess = deleteFLD(fi2.absoluteFilePath(), false);
2474                 if(!bSuccess) break;
2475             }
2476             if(bSuccess)
2477             {
2478                 bSuccess = FileAccess::removeDir(name);
2479                 if(!bSuccess)
2480                 {
2481                     m_pStatusInfo->addText(i18n("Error: rmdir( %1 ) operation failed.", name)); // krazy:exclude=syscalls
2482                     return false;
2483                 }
2484             }
2485         }
2486         else
2487         {
2488             bool bSuccess = fi.removeFile();
2489             if(!bSuccess)
2490             {
2491                 m_pStatusInfo->addText(i18n("Error: delete operation failed."));
2492                 return false;
2493             }
2494         }
2495     }
2496     return true;
2497 }
2498 
2499 bool DirectoryMergeWindow::DirectoryMergeWindowPrivate::mergeFLD(const QString& nameA, const QString& nameB, const QString& nameC, const QString& nameDest, bool& bSingleFileMerge)
2500 {
2501     FileAccess fi(nameA);
2502     if(fi.isDir())
2503     {
2504         return makeDir(nameDest);
2505     }
2506 
2507     QStringList errors;
2508     // Make sure that the dir exists, into which we will save the file later.
2509     QtSizeType pos = nameDest.lastIndexOf('/');
2510     if(pos > 0)
2511     {
2512         QString parentName = nameDest.left(pos);
2513         bool bSuccess = makeDir(parentName, true /*quiet*/);
2514         if(!bSuccess)
2515             return false;
2516     }
2517 
2518     m_pStatusInfo->addText(i18n("manual merge( %1, %2, %3 -> %4)", nameA, nameB, nameC, nameDest));
2519     if(m_bSimulatedMergeStarted)
2520     {
2521         m_pStatusInfo->addText(i18n("     Note: After a manual merge the user should continue by pressing F7."));
2522         return true;
2523     }
2524 
2525     bSingleFileMerge = true;
2526     setOpStatus(*m_currentIndexForOperation, eOpStatusInProgress);
2527     mWindow->scrollTo(*m_currentIndexForOperation, EnsureVisible);
2528 
2529     Q_EMIT mWindow->startDiffMerge(errors, nameA, nameB, nameC, nameDest, "", "", "", nullptr);
2530 
2531     return false;
2532 }
2533 
2534 bool DirectoryMergeWindow::DirectoryMergeWindowPrivate::copyFLD(const QString& srcName, const QString& destName)
2535 {
2536     bool bSuccess = false;
2537 
2538     if(srcName == destName)
2539         return true;
2540 
2541     FileAccess fi(srcName);
2542     FileAccess faDest(destName, true);
2543     if(faDest.exists() && !(fi.isDir() && faDest.isDir() && (fi.isSymLink() == faDest.isSymLink())))
2544     {
2545         bSuccess = deleteFLD(destName, gOptions->m_bDmCreateBakFiles);
2546         if(!bSuccess)
2547         {
2548             m_pStatusInfo->addText(i18n("Error: copy( %1 -> %2 ) failed."
2549                                         "Deleting existing destination failed.",
2550                                         srcName, destName));
2551             return bSuccess;
2552         }
2553     }
2554 
2555     if(fi.isSymLink() && ((fi.isDir() && !m_bFollowDirLinks) || (!fi.isDir() && !m_bFollowFileLinks)))
2556     {
2557         m_pStatusInfo->addText(i18n("copyLink( %1 -> %2 )", srcName, destName));
2558 
2559         if(m_bSimulatedMergeStarted)
2560         {
2561             return true;
2562         }
2563         FileAccess destFi(destName);
2564         if(!destFi.isLocal() || !fi.isLocal())
2565         {
2566             m_pStatusInfo->addText(i18n("Error: copyLink failed: Remote links are not yet supported."));
2567             return false;
2568         }
2569 
2570         bSuccess = false;
2571         QString linkTarget = fi.readLink();
2572         if(!linkTarget.isEmpty())
2573         {
2574             bSuccess = FileAccess::symLink(linkTarget, destName);
2575             if(!bSuccess)
2576                 m_pStatusInfo->addText(i18n("Error: copyLink failed."));
2577         }
2578         return bSuccess;
2579     }
2580 
2581     if(fi.isDir())
2582     {
2583         if(faDest.exists())
2584             return true;
2585 
2586         bSuccess = makeDir(destName);
2587         return bSuccess;
2588     }
2589 
2590     QtSizeType pos = destName.lastIndexOf('/');
2591     if(pos > 0)
2592     {
2593         QString parentName = destName.left(pos);
2594         bSuccess = makeDir(parentName, true /*quiet*/);
2595         if(!bSuccess)
2596             return false;
2597     }
2598 
2599     m_pStatusInfo->addText(i18n("copy( %1 -> %2 )", srcName, destName));
2600 
2601     if(m_bSimulatedMergeStarted)
2602     {
2603         return true;
2604     }
2605 
2606     FileAccess faSrc(srcName);
2607     bSuccess = faSrc.copyFile(destName);
2608     if(!bSuccess) m_pStatusInfo->addText(faSrc.getStatusText());
2609     return bSuccess;
2610 }
2611 
2612 // Rename is not an operation that can be selected by the user.
2613 // It will only be used to create backups.
2614 // Hence it will delete an existing destination without making a backup (of the old backup.)
2615 bool DirectoryMergeWindow::DirectoryMergeWindowPrivate::renameFLD(const QString& srcName, const QString& destName)
2616 {
2617     if(srcName == destName)
2618         return true;
2619     FileAccess destFile = FileAccess(destName, true);
2620     if(destFile.exists())
2621     {
2622         bool bSuccess = deleteFLD(destName, false /*no backup*/);
2623         if(!bSuccess)
2624         {
2625             m_pStatusInfo->addText(i18n("Error during rename( %1 -> %2 ): "
2626                                         "Cannot delete existing destination.",
2627                                         srcName, destName));
2628             return false;
2629         }
2630     }
2631 
2632     m_pStatusInfo->addText(i18n("rename( %1 -> %2 )", srcName, destName));
2633     if(m_bSimulatedMergeStarted)
2634     {
2635         return true;
2636     }
2637 
2638     bool bSuccess = FileAccess(srcName).rename(destFile);
2639     if(!bSuccess)
2640     {
2641         m_pStatusInfo->addText(i18n("Error: Rename failed."));
2642         return false;
2643     }
2644 
2645     return true;
2646 }
2647 
2648 bool DirectoryMergeWindow::DirectoryMergeWindowPrivate::makeDir(const QString& name, bool bQuiet)
2649 {
2650     FileAccess fi(name, true);
2651     if(fi.exists() && fi.isDir())
2652         return true;
2653 
2654     if(fi.exists() && !fi.isDir())
2655     {
2656         bool bSuccess = deleteFLD(name, true);
2657         if(!bSuccess)
2658         {
2659             m_pStatusInfo->addText(i18n("Error during makeDir of %1. "
2660                                         "Cannot delete existing file.",
2661                                         name));
2662             return false;
2663         }
2664     }
2665 
2666     QtSizeType pos = name.lastIndexOf('/');
2667     if(pos > 0)
2668     {
2669         QString parentName = name.left(pos);
2670         bool bSuccess = makeDir(parentName, true);
2671         if(!bSuccess)
2672             return false;
2673     }
2674 
2675     if(!bQuiet)
2676         m_pStatusInfo->addText(i18n("makeDir( %1 )", name));
2677 
2678     if(m_bSimulatedMergeStarted)
2679     {
2680         return true;
2681     }
2682 
2683     bool bSuccess = FileAccess::makeDir(name);
2684     if(!bSuccess)
2685     {
2686         m_pStatusInfo->addText(i18n("Error while creating folder."));
2687         return false;
2688     }
2689     return true;
2690 }
2691 
2692 DirectoryMergeInfo::DirectoryMergeInfo(QWidget* pParent):
2693     QFrame(pParent)
2694 {
2695     QVBoxLayout* topLayout = new QVBoxLayout(this);
2696     topLayout->setContentsMargins(0, 0, 0, 0);
2697 
2698     QGridLayout* grid = new QGridLayout();
2699     topLayout->addLayout(grid);
2700     grid->setColumnStretch(1, 10);
2701 
2702     qint32 line = 0;
2703 
2704     m_pA = new QLabel(QStringLiteral("A"), this);
2705     grid->addWidget(m_pA, line, 0);
2706     m_pInfoA = new QLabel(this);
2707     grid->addWidget(m_pInfoA, line, 1);
2708     ++line;
2709 
2710     m_pB = new QLabel(QStringLiteral("B"), this);
2711     grid->addWidget(m_pB, line, 0);
2712     m_pInfoB = new QLabel(this);
2713     grid->addWidget(m_pInfoB, line, 1);
2714     ++line;
2715 
2716     m_pC = new QLabel(QStringLiteral("C"), this);
2717     grid->addWidget(m_pC, line, 0);
2718     m_pInfoC = new QLabel(this);
2719     grid->addWidget(m_pInfoC, line, 1);
2720     ++line;
2721 
2722     m_pDest = new QLabel(i18n("Dest"), this);
2723     grid->addWidget(m_pDest, line, 0);
2724     m_pInfoDest = new QLabel(this);
2725     grid->addWidget(m_pInfoDest, line, 1);
2726     ++line;
2727 
2728     m_pInfoList = new QTreeWidget(this);
2729     topLayout->addWidget(m_pInfoList);
2730     m_pInfoList->setHeaderLabels({i18nc("Header label", "Folder"), i18nc("Header label", "Type"), i18nc("Header label", "Size"),
2731                                   i18nc("Header label", "Attr"), i18nc("Header label", "Last Modification"), i18nc("Header label", "Link-Destination")});
2732     setMinimumSize(100, 100);
2733 
2734     m_pInfoList->installEventFilter(this);
2735     m_pInfoList->setRootIsDecorated(false);
2736 }
2737 
2738 bool DirectoryMergeInfo::eventFilter(QObject* o, QEvent* e)
2739 {
2740     if(e->type() == QEvent::FocusIn && o == m_pInfoList)
2741         Q_EMIT gotFocus();
2742     return false;
2743 }
2744 
2745 void DirectoryMergeInfo::addListViewItem(const QString& dir, const QString& basePath, FileAccess* fi)
2746 {
2747     if(basePath.isEmpty())
2748     {
2749         return;
2750     }
2751 
2752     if(fi != nullptr && fi->exists())
2753     {
2754         QString dateString = fi->lastModified().toString(QLocale::system().dateTimeFormat());
2755 
2756         m_pInfoList->addTopLevelItem(new QTreeWidgetItem(
2757             m_pInfoList,
2758             {dir, QString(fi->isDir() ? i18nc("Header label", "Folder") : i18nc("Header label", "File")) + (fi->isSymLink() ? i18nc("Header label ending", "-Link") : ""), QString::number(fi->size()), QLatin1String(fi->isReadable() ? "r" : " ") + QLatin1String(fi->isWritable() ? "w" : " ") + QLatin1String((fi->isExecutable() ? "x" : " ")), dateString, QString(fi->isSymLink() ? (" -> " + fi->readLink()) : QString(""))}));
2759     }
2760     else
2761     {
2762         m_pInfoList->addTopLevelItem(new QTreeWidgetItem(
2763             m_pInfoList,
2764             {dir, i18nc("Header label", "not available"), "", "", "", ""}));
2765     }
2766 }
2767 
2768 void DirectoryMergeInfo::setInfo(
2769     const FileAccess& dirA,
2770     const FileAccess& dirB,
2771     const FileAccess& dirC,
2772     const FileAccess& dirDest,
2773     const MergeFileInfos& mfi)
2774 {
2775     bool bHideDest = false;
2776     if(dirA.absoluteFilePath() == dirDest.absoluteFilePath())
2777     {
2778         m_pA->setText(i18n("A (Dest): "));
2779         bHideDest = true;
2780     }
2781     else
2782         m_pA->setText(!dirC.isValid() ? i18n("A:    ") : i18n("A (Base): "));
2783 
2784     m_pInfoA->setText(dirA.prettyAbsPath());
2785 
2786     if(dirB.absoluteFilePath() == dirDest.absoluteFilePath())
2787     {
2788         m_pB->setText(i18n("B (Dest): "));
2789         bHideDest = true;
2790     }
2791     else
2792         m_pB->setText(i18n("B:    "));
2793     m_pInfoB->setText(dirB.prettyAbsPath());
2794 
2795     if(dirC.absoluteFilePath() == dirDest.absoluteFilePath())
2796     {
2797         m_pC->setText(i18n("C (Dest): "));
2798         bHideDest = true;
2799     }
2800     else
2801         m_pC->setText(i18n("C:    "));
2802     m_pInfoC->setText(dirC.prettyAbsPath());
2803 
2804     m_pDest->setText(i18n("Dest: "));
2805     m_pInfoDest->setText(dirDest.prettyAbsPath());
2806 
2807     if(!dirC.isValid())
2808     {
2809         m_pC->hide();
2810         m_pInfoC->hide();
2811     }
2812     else
2813     {
2814         m_pC->show();
2815         m_pInfoC->show();
2816     }
2817 
2818     if(!dirDest.isValid() || bHideDest)
2819     {
2820         m_pDest->hide();
2821         m_pInfoDest->hide();
2822     }
2823     else
2824     {
2825         m_pDest->show();
2826         m_pInfoDest->show();
2827     }
2828 
2829     m_pInfoList->clear();
2830     addListViewItem(QStringLiteral("A"), dirA.prettyAbsPath(), mfi.getFileInfoA());
2831     addListViewItem(QStringLiteral("B"), dirB.prettyAbsPath(), mfi.getFileInfoB());
2832     addListViewItem(QStringLiteral("C"), dirC.prettyAbsPath(), mfi.getFileInfoC());
2833     if(!bHideDest)
2834     {
2835         FileAccess fiDest(dirDest.prettyAbsPath() + '/' + mfi.subPath(), true);
2836         addListViewItem(i18n("Dest"), dirDest.prettyAbsPath(), &fiDest);
2837     }
2838     for(qint32 i = 0; i < m_pInfoList->columnCount(); ++i)
2839         m_pInfoList->resizeColumnToContents(i);
2840 }
2841 
2842 void DirectoryMergeWindow::slotSaveMergeState()
2843 {
2844     //slotStatusMsg(i18n("Saving Directory Merge State ..."));
2845 
2846     QString dirMergeStateFilename = QFileDialog::getSaveFileName(this, i18n("Save Folder Merge State As..."), QDir::currentPath());
2847     if(!dirMergeStateFilename.isEmpty())
2848     {
2849         QFile file(dirMergeStateFilename);
2850         bool bSuccess = file.open(QIODevice::WriteOnly);
2851         if(bSuccess)
2852         {
2853             QTextStream ts(&file);
2854 
2855             QModelIndex mi(d->index(0, 0, QModelIndex()));
2856             while(mi.isValid())
2857             {
2858                 MergeFileInfos* pMFI = d->getMFI(mi);
2859                 ts << *pMFI;
2860                 mi = d->treeIterator(mi, true, true);
2861             }
2862         }
2863     }
2864 
2865     //slotStatusMsg(i18n("Ready."));
2866 }
2867 
2868 void DirectoryMergeWindow::slotLoadMergeState()
2869 {
2870 }
2871 
2872 void DirectoryMergeWindow::updateFileVisibilities()
2873 {
2874     bool bShowIdentical = d->m_pDirShowIdenticalFiles->isChecked();
2875     bool bShowDifferent = d->m_pDirShowDifferentFiles->isChecked();
2876     bool bShowOnlyInA = d->m_pDirShowFilesOnlyInA->isChecked();
2877     bool bShowOnlyInB = d->m_pDirShowFilesOnlyInB->isChecked();
2878     bool bShowOnlyInC = d->m_pDirShowFilesOnlyInC->isChecked();
2879     bool bThreeDirs = d->isDirThreeWay();
2880     d->m_selection1Index = QModelIndex();
2881     d->m_selection2Index = QModelIndex();
2882     d->m_selection3Index = QModelIndex();
2883 
2884     // in first run set all dirs to equal and determine if they are not equal.
2885     // on second run don't change the equal-status anymore; it is needed to
2886     // set the visibility (when bShowIdentical is false).
2887     for(qint32 loop = 0; loop < 2; ++loop)
2888     {
2889         QModelIndex mi = d->rowCount() > 0 ? d->index(0, 0, QModelIndex()) : QModelIndex();
2890         while(mi.isValid())
2891         {
2892             MergeFileInfos* pMFI = d->getMFI(mi);
2893             bool bDir = pMFI->hasDir();
2894             if(loop == 0 && bDir)
2895             { //Treat all links and directories to equal by default.
2896                 pMFI->updateDirectoryOrLink();
2897             }
2898 
2899             bool bVisible =
2900                 (bShowIdentical && pMFI->existsEveryWhere() && pMFI->isEqualAB() && (pMFI->isEqualAC() || !bThreeDirs)) ||
2901                 ((bShowDifferent || bDir) && pMFI->existsCount() >= 2 && (!pMFI->isEqualAB() || !(pMFI->isEqualAC() || !bThreeDirs))) ||
2902                 (bShowOnlyInA && pMFI->onlyInA()) || (bShowOnlyInB && pMFI->onlyInB()) || (bShowOnlyInC && pMFI->onlyInC());
2903 
2904             QString fileName = pMFI->fileName();
2905             bVisible = bVisible && ((bDir && !Utils::wildcardMultiMatch(gOptions->m_DmDirAntiPattern, fileName, d->m_bCaseSensitive)) || (Utils::wildcardMultiMatch(gOptions->m_DmFilePattern, fileName, d->m_bCaseSensitive) && !Utils::wildcardMultiMatch(gOptions->m_DmFileAntiPattern, fileName, d->m_bCaseSensitive)));
2906 
2907             if(loop != 0)
2908                 setRowHidden(mi.row(), mi.parent(), !bVisible);
2909 
2910             bool bEqual = bThreeDirs ? pMFI->isEqualAB() && pMFI->isEqualAC() : pMFI->isEqualAB();
2911             if(!bEqual && bVisible && loop == 0) // Set all parents to "not equal"
2912             {
2913                 pMFI->updateParents();
2914             }
2915             mi = d->treeIterator(mi, true, true);
2916         }
2917     }
2918 }
2919 
2920 void DirectoryMergeWindow::slotShowIdenticalFiles()
2921 {
2922     gOptions->m_bDmShowIdenticalFiles = d->m_pDirShowIdenticalFiles->isChecked();
2923     updateFileVisibilities();
2924 }
2925 void DirectoryMergeWindow::slotShowDifferentFiles()
2926 {
2927     updateFileVisibilities();
2928 }
2929 void DirectoryMergeWindow::slotShowFilesOnlyInA()
2930 {
2931     updateFileVisibilities();
2932 }
2933 void DirectoryMergeWindow::slotShowFilesOnlyInB()
2934 {
2935     updateFileVisibilities();
2936 }
2937 void DirectoryMergeWindow::slotShowFilesOnlyInC()
2938 {
2939     updateFileVisibilities();
2940 }
2941 
2942 void DirectoryMergeWindow::slotSynchronizeDirectories() {}
2943 void DirectoryMergeWindow::slotChooseNewerFiles() {}
2944 
2945 void DirectoryMergeWindow::initDirectoryMergeActions(KDiff3App* pKDiff3App, KActionCollection* ac)
2946 {
2947 #include "xpm/showequalfiles.xpm"
2948 #include "xpm/showfilesonlyina.xpm"
2949 #include "xpm/showfilesonlyinb.xpm"
2950 #include "xpm/showfilesonlyinc.xpm"
2951 #include "xpm/startmerge.xpm"
2952 
2953     d->m_pDirStartOperation = GuiUtils::createAction<QAction>(i18n("Start/Continue Folder Merge"), QKeySequence(Qt::Key_F7), this, &DirectoryMergeWindow::slotRunOperationForAllItems, ac, "dir_start_operation");
2954     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");
2955     d->m_pDirCompareCurrent = GuiUtils::createAction<QAction>(i18n("Compare Selected File"), this, &DirectoryMergeWindow::compareCurrentFile, ac, "dir_compare_current");
2956     d->m_pDirMergeCurrent = GuiUtils::createAction<QAction>(i18n("Merge Current File"), QIcon(QPixmap(startmerge)), i18n("Merge\nFile"), pKDiff3App, &KDiff3App::slotMergeCurrentFile, ac, "merge_current");
2957     d->m_pDirFoldAll = GuiUtils::createAction<QAction>(i18n("Fold All Subfolders"), this, &DirectoryMergeWindow::collapseAll, ac, "dir_fold_all");
2958     d->m_pDirUnfoldAll = GuiUtils::createAction<QAction>(i18n("Unfold All Subfolders"), this, &DirectoryMergeWindow::expandAll, ac, "dir_unfold_all");
2959     d->m_pDirRescan = GuiUtils::createAction<QAction>(i18n("Rescan"), QKeySequence(Qt::SHIFT | Qt::Key_F5), this, &DirectoryMergeWindow::reload, ac, "dir_rescan");
2960     d->m_pDirSaveMergeState = nullptr; //GuiUtils::createAction< QAction >(i18n("Save Directory Merge State ..."), 0, this, &DirectoryMergeWindow::slotSaveMergeState, ac, "dir_save_merge_state");
2961     d->m_pDirLoadMergeState = nullptr; //GuiUtils::createAction< QAction >(i18n("Load Directory Merge State ..."), 0, this, &DirectoryMergeWindow::slotLoadMergeState, ac, "dir_load_merge_state");
2962     d->m_pDirChooseAEverywhere = GuiUtils::createAction<QAction>(i18n("Choose A for All Items"), this, &DirectoryMergeWindow::slotChooseAEverywhere, ac, "dir_choose_a_everywhere");
2963     d->m_pDirChooseBEverywhere = GuiUtils::createAction<QAction>(i18n("Choose B for All Items"), this, &DirectoryMergeWindow::slotChooseBEverywhere, ac, "dir_choose_b_everywhere");
2964     d->m_pDirChooseCEverywhere = GuiUtils::createAction<QAction>(i18n("Choose C for All Items"), this, &DirectoryMergeWindow::slotChooseCEverywhere, ac, "dir_choose_c_everywhere");
2965     d->m_pDirAutoChoiceEverywhere = GuiUtils::createAction<QAction>(i18n("Auto-Choose Operation for All Items"), this, &DirectoryMergeWindow::slotAutoChooseEverywhere, ac, "dir_autochoose_everywhere");
2966     d->m_pDirDoNothingEverywhere = GuiUtils::createAction<QAction>(i18n("No Operation for All Items"), this, &DirectoryMergeWindow::slotNoOpEverywhere, ac, "dir_nothing_everywhere");
2967 
2968     //   d->m_pDirSynchronizeDirectories = GuiUtils::createAction< KToggleAction >(i18n("Synchronize Directories"), 0, this, &DirectoryMergeWindow::slotSynchronizeDirectories, ac, "dir_synchronize_directories");
2969     //   d->m_pDirChooseNewerFiles = GuiUtils::createAction< KToggleAction >(i18n("Copy Newer Files Instead of Merging"), 0, this, &DirectoryMergeWindow::slotChooseNewerFiles, ac, "dir_choose_newer_files");
2970 
2971     d->m_pDirShowIdenticalFiles = GuiUtils::createAction<KToggleAction>(i18n("Show Identical Files"), QIcon(QPixmap(showequalfiles)), i18n("Identical\nFiles"), this, &DirectoryMergeWindow::slotShowIdenticalFiles, ac, "dir_show_identical_files");
2972     d->m_pDirShowDifferentFiles = GuiUtils::createAction<KToggleAction>(i18n("Show Different Files"), this, &DirectoryMergeWindow::slotShowDifferentFiles, ac, "dir_show_different_files");
2973     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");
2974     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");
2975     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");
2976 
2977     d->m_pDirShowIdenticalFiles->setChecked(gOptions->m_bDmShowIdenticalFiles);
2978 
2979     d->m_pDirCompareExplicit = GuiUtils::createAction<QAction>(i18n("Compare Explicitly Selected Files"), this, &DirectoryMergeWindow::slotCompareExplicitlySelectedFiles, ac, "dir_compare_explicitly_selected_files");
2980     d->m_pDirMergeExplicit = GuiUtils::createAction<QAction>(i18n("Merge Explicitly Selected Files"), this, &DirectoryMergeWindow::slotMergeExplicitlySelectedFiles, ac, "dir_merge_explicitly_selected_files");
2981 
2982     d->m_pDirCurrentDoNothing = GuiUtils::createAction<QAction>(i18n("Do Nothing"), this, &DirectoryMergeWindow::slotCurrentDoNothing, ac, "dir_current_do_nothing");
2983     d->m_pDirCurrentChooseA = GuiUtils::createAction<QAction>(QStringLiteral("A"), this, &DirectoryMergeWindow::slotCurrentChooseA, ac, "dir_current_choose_a");
2984     d->m_pDirCurrentChooseB = GuiUtils::createAction<QAction>(QStringLiteral("B"), this, &DirectoryMergeWindow::slotCurrentChooseB, ac, "dir_current_choose_b");
2985     d->m_pDirCurrentChooseC = GuiUtils::createAction<QAction>(QStringLiteral("C"), this, &DirectoryMergeWindow::slotCurrentChooseC, ac, "dir_current_choose_c");
2986     d->m_pDirCurrentMerge = GuiUtils::createAction<QAction>(i18n("Merge"), this, &DirectoryMergeWindow::slotCurrentMerge, ac, "dir_current_merge");
2987     d->m_pDirCurrentDelete = GuiUtils::createAction<QAction>(i18n("Delete (if exists)"), this, &DirectoryMergeWindow::slotCurrentDelete, ac, "dir_current_delete");
2988 
2989     d->m_pDirCurrentSyncDoNothing = GuiUtils::createAction<QAction>(i18n("Do Nothing"), this, &DirectoryMergeWindow::slotCurrentDoNothing, ac, "dir_current_sync_do_nothing");
2990     d->m_pDirCurrentSyncCopyAToB = GuiUtils::createAction<QAction>(i18n("Copy A to B"), this, &DirectoryMergeWindow::slotCurrentCopyAToB, ac, "dir_current_sync_copy_a_to_b");
2991     d->m_pDirCurrentSyncCopyBToA = GuiUtils::createAction<QAction>(i18n("Copy B to A"), this, &DirectoryMergeWindow::slotCurrentCopyBToA, ac, "dir_current_sync_copy_b_to_a");
2992     d->m_pDirCurrentSyncDeleteA = GuiUtils::createAction<QAction>(i18n("Delete A"), this, &DirectoryMergeWindow::slotCurrentDeleteA, ac, "dir_current_sync_delete_a");
2993     d->m_pDirCurrentSyncDeleteB = GuiUtils::createAction<QAction>(i18n("Delete B"), this, &DirectoryMergeWindow::slotCurrentDeleteB, ac, "dir_current_sync_delete_b");
2994     d->m_pDirCurrentSyncDeleteAAndB = GuiUtils::createAction<QAction>(i18n("Delete A && B"), this, &DirectoryMergeWindow::slotCurrentDeleteAAndB, ac, "dir_current_sync_delete_a_and_b");
2995     d->m_pDirCurrentSyncMergeToA = GuiUtils::createAction<QAction>(i18n("Merge to A"), this, &DirectoryMergeWindow::slotCurrentMergeToA, ac, "dir_current_sync_merge_to_a");
2996     d->m_pDirCurrentSyncMergeToB = GuiUtils::createAction<QAction>(i18n("Merge to B"), this, &DirectoryMergeWindow::slotCurrentMergeToB, ac, "dir_current_sync_merge_to_b");
2997     d->m_pDirCurrentSyncMergeToAAndB = GuiUtils::createAction<QAction>(i18n("Merge to A && B"), this, &DirectoryMergeWindow::slotCurrentMergeToAAndB, ac, "dir_current_sync_merge_to_a_and_b");
2998 }
2999 
3000 void DirectoryMergeWindow::setupConnections(const KDiff3App* app)
3001 {
3002     chk_connect_a(this, &DirectoryMergeWindow::startDiffMerge, app, &KDiff3App::slotFileOpen2);
3003     chk_connect_a(selectionModel(), &QItemSelectionModel::selectionChanged, app, &KDiff3App::slotUpdateAvailabilities);
3004     chk_connect_a(selectionModel(), &QItemSelectionModel::currentChanged, app, &KDiff3App::slotUpdateAvailabilities);
3005     chk_connect_a(this, static_cast<void (DirectoryMergeWindow::*)(void)>(&DirectoryMergeWindow::updateAvailabilities), app, &KDiff3App::slotUpdateAvailabilities);
3006     chk_connect_a(this, &DirectoryMergeWindow::statusBarMessage, app, &KDiff3App::slotStatusMsg);
3007     chk_connect_a(app, &KDiff3App::doRefresh, this, &DirectoryMergeWindow::slotRefresh);
3008 }
3009 
3010 void DirectoryMergeWindow::updateAvailabilities(bool bMergeEditorVisible, bool bDirCompare, bool bDiffWindowVisible,
3011                                                 KToggleAction* chooseA, KToggleAction* chooseB, KToggleAction* chooseC)
3012 {
3013     d->m_pDirStartOperation->setEnabled(bDirCompare);
3014     d->m_pDirRunOperationForCurrentItem->setEnabled(bDirCompare);
3015     d->m_pDirFoldAll->setEnabled(bDirCompare);
3016     d->m_pDirUnfoldAll->setEnabled(bDirCompare);
3017 
3018     d->m_pDirCompareCurrent->setEnabled(bDirCompare && isVisible() && isFileSelected());
3019 
3020     d->m_pDirMergeCurrent->setEnabled((bDirCompare && isVisible() && isFileSelected()) || bDiffWindowVisible);
3021 
3022     d->m_pDirRescan->setEnabled(bDirCompare);
3023 
3024     bool bThreeDirs = d->isDirThreeWay();
3025     d->m_pDirAutoChoiceEverywhere->setEnabled(bDirCompare && isVisible());
3026     d->m_pDirDoNothingEverywhere->setEnabled(bDirCompare && isVisible());
3027     d->m_pDirChooseAEverywhere->setEnabled(bDirCompare && isVisible());
3028     d->m_pDirChooseBEverywhere->setEnabled(bDirCompare && isVisible());
3029     d->m_pDirChooseCEverywhere->setEnabled(bDirCompare && isVisible() && bThreeDirs);
3030 
3031     const MergeFileInfos* pMFI = d->getMFI(currentIndex());
3032 
3033     bool bItemActive = bDirCompare && isVisible() && pMFI != nullptr; //  &&  hasFocus();
3034     bool bMergeMode = bThreeDirs || !d->m_bSyncMode;
3035     bool bFTConflict = pMFI == nullptr ? false : pMFI->conflictingFileTypes();
3036 
3037     bool bDirWindowHasFocus = isVisible() && hasFocus();
3038     assert(!bItemActive || !bDirCompare || !bMergeMode || pMFI != nullptr);
3039 
3040     d->m_pDirShowIdenticalFiles->setEnabled(bDirCompare && isVisible());
3041     d->m_pDirShowDifferentFiles->setEnabled(bDirCompare && isVisible());
3042     d->m_pDirShowFilesOnlyInA->setEnabled(bDirCompare && isVisible());
3043     d->m_pDirShowFilesOnlyInB->setEnabled(bDirCompare && isVisible());
3044     d->m_pDirShowFilesOnlyInC->setEnabled(bDirCompare && isVisible() && bThreeDirs);
3045 
3046     d->m_pDirCompareExplicit->setEnabled(bDirCompare && isVisible() && d->m_selection2Index.isValid());
3047     d->m_pDirMergeExplicit->setEnabled(bDirCompare && isVisible() && d->m_selection2Index.isValid());
3048 
3049     d->m_pDirCurrentDoNothing->setEnabled(bItemActive && bMergeMode);
3050     d->m_pDirCurrentChooseA->setEnabled(bItemActive && bMergeMode && pMFI->existsInA());
3051     d->m_pDirCurrentChooseB->setEnabled(bItemActive && bMergeMode && pMFI->existsInB());
3052     d->m_pDirCurrentChooseC->setEnabled(bItemActive && bMergeMode && pMFI->existsInC());
3053     d->m_pDirCurrentMerge->setEnabled(bItemActive && bMergeMode && !bFTConflict);
3054     d->m_pDirCurrentDelete->setEnabled(bItemActive && bMergeMode);
3055     if(bDirWindowHasFocus)
3056     {
3057         chooseA->setEnabled(bItemActive && (bDirCompare ? pMFI->existsInA() : true));
3058         chooseB->setEnabled(bItemActive && (bDirCompare ? pMFI->existsInB() : true));
3059         chooseC->setEnabled(bItemActive && (bDirCompare ? pMFI->existsInC() : KDiff3App::isTripleDiff()));
3060         chooseA->setChecked(false);
3061         chooseB->setChecked(false);
3062         chooseC->setChecked(false);
3063     }
3064     else
3065     {
3066         chooseA->setEnabled(bMergeEditorVisible);
3067         chooseB->setEnabled(bMergeEditorVisible);
3068         chooseC->setEnabled(bMergeEditorVisible && KDiff3App::isTripleDiff());
3069     }
3070 
3071     d->m_pDirCurrentSyncDoNothing->setEnabled(bItemActive && !bMergeMode);
3072     d->m_pDirCurrentSyncCopyAToB->setEnabled(bItemActive && !bMergeMode && pMFI->existsInA());
3073     d->m_pDirCurrentSyncCopyBToA->setEnabled(bItemActive && !bMergeMode && pMFI->existsInB());
3074     d->m_pDirCurrentSyncDeleteA->setEnabled(bItemActive && !bMergeMode && pMFI->existsInA());
3075     d->m_pDirCurrentSyncDeleteB->setEnabled(bItemActive && !bMergeMode && pMFI->existsInB());
3076     d->m_pDirCurrentSyncDeleteAAndB->setEnabled(bItemActive && !bMergeMode && pMFI->existsInA() && pMFI->existsInB());
3077     d->m_pDirCurrentSyncMergeToA->setEnabled(bItemActive && !bMergeMode && !bFTConflict);
3078     d->m_pDirCurrentSyncMergeToB->setEnabled(bItemActive && !bMergeMode && !bFTConflict);
3079     d->m_pDirCurrentSyncMergeToAAndB->setEnabled(bItemActive && !bMergeMode && !bFTConflict);
3080 }
3081 
3082 //#include "directorymergewindow.moc"