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