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