File indexing completed on 2024-05-19 16:47:22

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