diff --git a/src/directorymergewindow.cpp b/src/directorymergewindow.cpp index 42d3e79..8c3ca9f 100644 --- a/src/directorymergewindow.cpp +++ b/src/directorymergewindow.cpp @@ -1,3716 +1,3707 @@ /*************************************************************************** directorymergewindow.cpp ----------------- begin : Sat Oct 19 2002 copyright : (C) 2002-2011 by Joachim Eibl email : joachim.eibl at gmx.de ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include #include "directorymergewindow.h" #include "guiutils.h" #include "options.h" #include "progress.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static bool conflictingFileTypes(MergeFileInfos& mfi); static QPixmap getOnePixmap(e_Age eAge, bool bLink, bool bDir); class StatusInfo : public QDialog { QTextEdit* m_pTextEdit; public: explicit StatusInfo(QWidget* pParent) : QDialog(pParent) { QVBoxLayout* pVLayout = new QVBoxLayout(this); m_pTextEdit = new QTextEdit(this); pVLayout->addWidget(m_pTextEdit); setObjectName("StatusInfo"); setWindowFlags(Qt::Dialog); m_pTextEdit->setWordWrapMode(QTextOption::NoWrap); m_pTextEdit->setReadOnly(true); QDialogButtonBox *box = new QDialogButtonBox(QDialogButtonBox::Close, this); connect(box, &QDialogButtonBox::rejected, this, &QDialog::accept); pVLayout->addWidget(box); } bool isEmpty() { return m_pTextEdit->toPlainText().isEmpty(); } void addText(const QString& s) { m_pTextEdit->append(s); } void clear() { m_pTextEdit->clear(); } void setVisible(bool bVisible) override { if(bVisible) { m_pTextEdit->moveCursor(QTextCursor::End); m_pTextEdit->moveCursor(QTextCursor::StartOfLine); m_pTextEdit->ensureCursorVisible(); } QDialog::setVisible(bVisible); if(bVisible) setWindowState(windowState() | Qt::WindowMaximized); } }; enum Columns { s_NameCol = 0, s_ACol = 1, s_BCol = 2, s_CCol = 3, s_OpCol = 4, s_OpStatusCol = 5, s_UnsolvedCol = 6, // Nr of unsolved conflicts (for 3 input files) s_SolvedCol = 7, // Nr of auto-solvable conflicts (for 3 input files) s_NonWhiteCol = 8, // Nr of nonwhite deltas (for 2 input files) s_WhiteCol = 9 // Nr of white deltas (for 2 input files) }; enum e_OperationStatus { eOpStatusNone, eOpStatusDone, eOpStatusError, eOpStatusSkipped, eOpStatusNotSaved, eOpStatusInProgress, eOpStatusToDo }; class MergeFileInfos { public: MergeFileInfos() { m_bEqualAB = false; m_bEqualAC = false; m_bEqualBC = false; m_pParent = nullptr; m_bOperationComplete = false; m_bSimOpComplete = false; m_eMergeOperation = eNoOperation; m_eOpStatus = eOpStatusNone; m_ageA = eNotThere; m_ageB = eNotThere; m_ageC = eNotThere; m_bConflictingAges = false; m_pFileInfoA = nullptr; m_pFileInfoB = nullptr; m_pFileInfoC = nullptr; } ~MergeFileInfos() { //for( int i=0; i( const MergeFileInfos& ); QString subPath() const { return m_pFileInfoA && m_pFileInfoA->exists() ? m_pFileInfoA->filePath() : m_pFileInfoB && m_pFileInfoB->exists() ? m_pFileInfoB->filePath() : m_pFileInfoC && m_pFileInfoC->exists() ? m_pFileInfoC->filePath() : QString(""); } QString fileName() const { return m_pFileInfoA && m_pFileInfoA->exists() ? m_pFileInfoA->fileName() : m_pFileInfoB && m_pFileInfoB->exists() ? m_pFileInfoB->fileName() : m_pFileInfoC && m_pFileInfoC->exists() ? m_pFileInfoC->fileName() : QString(""); } bool dirA() const { return m_pFileInfoA ? m_pFileInfoA->isDir() : false; } bool dirB() const { return m_pFileInfoB ? m_pFileInfoB->isDir() : false; } bool dirC() const { return m_pFileInfoC ? m_pFileInfoC->isDir() : false; } bool isLinkA() const { return m_pFileInfoA ? m_pFileInfoA->isSymLink() : false; } bool isLinkB() const { return m_pFileInfoB ? m_pFileInfoB->isSymLink() : false; } bool isLinkC() const { return m_pFileInfoC ? m_pFileInfoC->isSymLink() : false; } bool existsInA() const { return m_pFileInfoA != nullptr; } bool existsInB() const { return m_pFileInfoB != nullptr; } bool existsInC() const { return m_pFileInfoC != nullptr; } MergeFileInfos* m_pParent; FileAccess* m_pFileInfoA; FileAccess* m_pFileInfoB; FileAccess* m_pFileInfoC; TotalDiffStatus m_totalDiffStatus; QList m_children; e_MergeOperation m_eMergeOperation : 5; e_OperationStatus m_eOpStatus : 4; e_Age m_ageA : 3; e_Age m_ageB : 3; e_Age m_ageC : 3; bool m_bOperationComplete : 1; bool m_bSimOpComplete : 1; bool m_bEqualAB : 1; bool m_bEqualAC : 1; bool m_bEqualBC : 1; bool m_bConflictingAges : 1; // Equal age but files are not! }; static Qt::CaseSensitivity s_eCaseSensitivity = Qt::CaseSensitive; class DirectoryMergeWindow::Data : public QAbstractItemModel { public: DirectoryMergeWindow* q; explicit Data(DirectoryMergeWindow* pDMW) { q = pDMW; m_pOptions = nullptr; m_pIconLoader = nullptr; m_pDirectoryMergeInfo = nullptr; m_bSimulatedMergeStarted = false; m_bRealMergeStarted = false; m_bError = false; m_bSyncMode = false; m_pStatusInfo = new StatusInfo(q); m_pStatusInfo->hide(); m_bScanning = false; m_bCaseSensitive = true; m_bUnfoldSubdirs = false; m_bSkipDirStatus = false; m_pRoot = new MergeFileInfos; } ~Data() override { delete m_pRoot; } // Implement QAbstractItemModel QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; //Qt::ItemFlags flags ( const QModelIndex & index ) const QModelIndex parent(const QModelIndex& index) const override { MergeFileInfos* pMFI = getMFI(index); if(pMFI == nullptr || pMFI == m_pRoot || pMFI->m_pParent == m_pRoot) return QModelIndex(); else { MergeFileInfos* pParentsParent = pMFI->m_pParent->m_pParent; return createIndex(pParentsParent->m_children.indexOf(pMFI->m_pParent), 0, pMFI->m_pParent); } } int rowCount(const QModelIndex& parent = QModelIndex()) const override { MergeFileInfos* pParentMFI = getMFI(parent); if(pParentMFI != nullptr) return pParentMFI->m_children.count(); else return m_pRoot->m_children.count(); } int columnCount(const QModelIndex& /*parent*/) const override { return 10; } QModelIndex index(int row, int column, const QModelIndex& parent) const override { MergeFileInfos* pParentMFI = getMFI(parent); if(pParentMFI == nullptr && row < m_pRoot->m_children.count()) return createIndex(row, column, m_pRoot->m_children[row]); else if(pParentMFI != nullptr && row < pParentMFI->m_children.count()) return createIndex(row, column, pParentMFI->m_children[row]); else return QModelIndex(); } QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; void sort(int column, Qt::SortOrder order) override; // private data and helper methods MergeFileInfos* getMFI(const QModelIndex& mi) const { if(mi.isValid()) return (MergeFileInfos*)mi.internalPointer(); else return nullptr; } MergeFileInfos* m_pRoot; QString fullNameA(const MergeFileInfos& mfi) { return mfi.existsInA() ? mfi.m_pFileInfoA->absoluteFilePath() : m_dirA.absoluteFilePath() + "/" + mfi.subPath(); } QString fullNameB(const MergeFileInfos& mfi) { return mfi.existsInB() ? mfi.m_pFileInfoB->absoluteFilePath() : m_dirB.absoluteFilePath() + "/" + mfi.subPath(); } QString fullNameC(const MergeFileInfos& mfi) { return mfi.existsInC() ? mfi.m_pFileInfoC->absoluteFilePath() : m_dirC.absoluteFilePath() + "/" + mfi.subPath(); } QString fullNameDest(const MergeFileInfos& mfi) { if(m_dirDestInternal.prettyAbsPath() == m_dirC.prettyAbsPath()) return fullNameC(mfi); else if(m_dirDestInternal.prettyAbsPath() == m_dirB.prettyAbsPath()) return fullNameB(mfi); else return m_dirDestInternal.absoluteFilePath() + "/" + mfi.subPath(); } FileAccess m_dirA; FileAccess m_dirB; FileAccess m_dirC; FileAccess m_dirDest; FileAccess m_dirDestInternal; Options* m_pOptions; void calcDirStatus(bool bThreeDirs, const QModelIndex& mi, int& nofFiles, int& nofDirs, int& nofEqualFiles, int& nofManualMerges); void mergeContinue(bool bStart, bool bVerbose); void prepareListView(ProgressProxy& pp); void calcSuggestedOperation(const QModelIndex& mi, e_MergeOperation eDefaultOperation); void setAllMergeOperations(e_MergeOperation eDefaultOperation); friend class MergeFileInfos; bool canContinue(); QModelIndex treeIterator(QModelIndex mi, bool bVisitChildren = true, bool bFindInvisible = false); void prepareMergeStart(const QModelIndex& miBegin, const QModelIndex& miEnd, bool bVerbose); bool executeMergeOperation(MergeFileInfos& mfi, bool& bSingleFileMerge); void scanDirectory(const QString& dirName, t_DirectoryList& dirList); void scanLocalDirectory(const QString& dirName, t_DirectoryList& dirList); bool fastFileComparison(FileAccess& fi1, FileAccess& fi2, bool& bError, QString& status); void compareFilesAndCalcAges(MergeFileInfos& mfi); void setMergeOperation(const QModelIndex& mi, e_MergeOperation eMergeOp, bool bRecursive = true); bool isDir(const QModelIndex& mi); QString getFileName(const QModelIndex& mi); bool copyFLD(const QString& srcName, const QString& destName); bool deleteFLD(const QString& name, bool bCreateBackup); bool makeDir(const QString& name, bool bQuiet = false); bool renameFLD(const QString& srcName, const QString& destName); bool mergeFLD(const QString& nameA, const QString& nameB, const QString& nameC, const QString& nameDest, bool& bSingleFileMerge); t_DirectoryList m_dirListA; t_DirectoryList m_dirListB; t_DirectoryList m_dirListC; QString m_dirMergeStateFilename; void buildMergeMap(void); class FileKey { public: const FileAccess* m_pFA; explicit FileKey(const FileAccess& fa) : m_pFA(&fa) {} int getParents(const FileAccess* pFA, const FileAccess* v[]) const { int s = 0; for(s = 0; pFA->parent() != nullptr; pFA = pFA->parent(), ++s) v[s] = pFA; return s; } // This is essentially the same as // int r = filePath().compare( fa.filePath() ) // if ( r<0 ) return true; // if ( r==0 ) return m_col < fa.m_col; // return false; bool operator<(const FileKey& fk) const { const FileAccess* v1[100]; const FileAccess* v2[100]; int v1Size = getParents(m_pFA, v1); int v2Size = getParents(fk.m_pFA, v2); for(int i = 0; i < v1Size && i < v2Size; ++i) { int r = v1[v1Size - i - 1]->fileName().compare(v2[v2Size - i - 1]->fileName(), s_eCaseSensitivity); if(r < 0) return true; else if(r > 0) return false; } if(v1Size < v2Size) return true; return false; } }; typedef QMap t_fileMergeMap; t_fileMergeMap m_fileMergeMap; bool m_bFollowDirLinks; bool m_bFollowFileLinks; bool m_bSimulatedMergeStarted; bool m_bRealMergeStarted; bool m_bError; bool m_bSyncMode; bool m_bDirectoryMerge; // if true, then merge is the default operation, otherwise it's diff. bool m_bCaseSensitive; bool m_bUnfoldSubdirs; bool m_bSkipDirStatus; bool m_bScanning; // true while in init() KIconLoader* m_pIconLoader; DirectoryMergeInfo* m_pDirectoryMergeInfo; StatusInfo* m_pStatusInfo; typedef std::list MergeItemList; // linked list MergeItemList m_mergeItemList; MergeItemList::iterator m_currentIndexForOperation; QModelIndex m_selection1Index; QModelIndex m_selection2Index; QModelIndex m_selection3Index; void selectItemAndColumn(const QModelIndex& mi, bool bContextMenu); friend class DirMergeItem; QAction* m_pDirStartOperation; QAction* m_pDirRunOperationForCurrentItem; QAction* m_pDirCompareCurrent; QAction* m_pDirMergeCurrent; QAction* m_pDirRescan; QAction* m_pDirChooseAEverywhere; QAction* m_pDirChooseBEverywhere; QAction* m_pDirChooseCEverywhere; QAction* m_pDirAutoChoiceEverywhere; QAction* m_pDirDoNothingEverywhere; QAction* m_pDirFoldAll; QAction* m_pDirUnfoldAll; KToggleAction* m_pDirShowIdenticalFiles; KToggleAction* m_pDirShowDifferentFiles; KToggleAction* m_pDirShowFilesOnlyInA; KToggleAction* m_pDirShowFilesOnlyInB; KToggleAction* m_pDirShowFilesOnlyInC; KToggleAction* m_pDirSynchronizeDirectories; KToggleAction* m_pDirChooseNewerFiles; QAction* m_pDirCompareExplicit; QAction* m_pDirMergeExplicit; QAction* m_pDirCurrentDoNothing; QAction* m_pDirCurrentChooseA; QAction* m_pDirCurrentChooseB; QAction* m_pDirCurrentChooseC; QAction* m_pDirCurrentMerge; QAction* m_pDirCurrentDelete; QAction* m_pDirCurrentSyncDoNothing; QAction* m_pDirCurrentSyncCopyAToB; QAction* m_pDirCurrentSyncCopyBToA; QAction* m_pDirCurrentSyncDeleteA; QAction* m_pDirCurrentSyncDeleteB; QAction* m_pDirCurrentSyncDeleteAAndB; QAction* m_pDirCurrentSyncMergeToA; QAction* m_pDirCurrentSyncMergeToB; QAction* m_pDirCurrentSyncMergeToAAndB; QAction* m_pDirSaveMergeState; QAction* m_pDirLoadMergeState; bool init(FileAccess& dirA, FileAccess& dirB, FileAccess& dirC, FileAccess& dirDest, bool bDirectoryMerge, bool bReload); void setOpStatus(const QModelIndex& mi, e_OperationStatus eOpStatus) { if(MergeFileInfos* pMFI = getMFI(mi)) { pMFI->m_eOpStatus = eOpStatus; emit dataChanged(mi, mi); } } }; QVariant DirectoryMergeWindow::Data::data(const QModelIndex& index, int role) const { MergeFileInfos* pMFI = getMFI(index); if(pMFI) { if(role == Qt::DisplayRole) { switch(index.column()) { case s_NameCol: return QFileInfo(pMFI->subPath()).fileName(); case s_ACol: return i18n("A"); case s_BCol: return i18n("B"); case s_CCol: return i18n("C"); //case s_OpCol: return i18n("Operation"); //case s_OpStatusCol: return i18n("Status"); case s_UnsolvedCol: return i18n("Unsolved"); case s_SolvedCol: return i18n("Solved"); case s_NonWhiteCol: return i18n("Nonwhite"); case s_WhiteCol: return i18n("White"); //default : return QVariant(); } if(s_OpCol == index.column()) { bool bDir = pMFI->dirA() || pMFI->dirB() || pMFI->dirC(); switch(pMFI->m_eMergeOperation) { case eNoOperation: return ""; break; case eCopyAToB: return i18n("Copy A to B"); break; case eCopyBToA: return i18n("Copy B to A"); break; case eDeleteA: return i18n("Delete A"); break; case eDeleteB: return i18n("Delete B"); break; case eDeleteAB: return i18n("Delete A & B"); break; case eMergeToA: return i18n("Merge to A"); break; case eMergeToB: return i18n("Merge to B"); break; case eMergeToAB: return i18n("Merge to A & B"); break; case eCopyAToDest: return i18n("A"); break; case eCopyBToDest: return i18n("B"); break; case eCopyCToDest: return i18n("C"); break; case eDeleteFromDest: return i18n("Delete (if exists)"); break; case eMergeABCToDest: return bDir ? i18n("Merge") : i18n("Merge (manual)"); break; case eMergeABToDest: return bDir ? i18n("Merge") : i18n("Merge (manual)"); break; case eConflictingFileTypes: return i18n("Error: Conflicting File Types"); break; case eChangedAndDeleted: return i18n("Error: Changed and Deleted"); break; case eConflictingAges: return i18n("Error: Dates are equal but files are not."); break; default: Q_ASSERT(true); break; } } if(s_OpStatusCol == index.column()) { switch(pMFI->m_eOpStatus) { case eOpStatusNone: return ""; case eOpStatusDone: return i18n("Done"); case eOpStatusError: return i18n("Error"); case eOpStatusSkipped: return i18n("Skipped."); case eOpStatusNotSaved: return i18n("Not saved."); case eOpStatusInProgress: return i18n("In progress..."); case eOpStatusToDo: return i18n("To do."); } } } else if(role == Qt::DecorationRole) { if(s_NameCol == index.column()) { return getOnePixmap(eAgeEnd, pMFI->isLinkA() || pMFI->isLinkB() || pMFI->isLinkC(), pMFI->dirA() || pMFI->dirB() || pMFI->dirC()); } if(s_ACol == index.column()) { return getOnePixmap(pMFI->m_ageA, pMFI->isLinkA(), pMFI->dirA()); } if(s_BCol == index.column()) { return getOnePixmap(pMFI->m_ageB, pMFI->isLinkB(), pMFI->dirB()); } if(s_CCol == index.column()) { return getOnePixmap(pMFI->m_ageC, pMFI->isLinkC(), pMFI->dirC()); } } else if(role == Qt::TextAlignmentRole) { if(s_UnsolvedCol == index.column() || s_SolvedCol == index.column() || s_NonWhiteCol == index.column() || s_WhiteCol == index.column()) return Qt::AlignRight; } } return QVariant(); } QVariant DirectoryMergeWindow::Data::headerData(int section, Qt::Orientation orientation, int role) const { if(orientation == Qt::Horizontal && section >= 0 && section < columnCount(QModelIndex()) && role == Qt::DisplayRole) { switch(section) { case s_NameCol: return i18n("Name"); case s_ACol: return i18n("A"); case s_BCol: return i18n("B"); case s_CCol: return i18n("C"); case s_OpCol: return i18n("Operation"); case s_OpStatusCol: return i18n("Status"); case s_UnsolvedCol: return i18n("Unsolved"); case s_SolvedCol: return i18n("Solved"); case s_NonWhiteCol: return i18n("Nonwhite"); case s_WhiteCol: return i18n("White"); default: return QVariant(); } } return QVariant(); } // Previously Q3ListViewItem::paintCell(p,cg,column,width,align); class DirectoryMergeWindow::DirMergeItemDelegate : public QStyledItemDelegate { DirectoryMergeWindow* m_pDMW; DirectoryMergeWindow::Data* d; public: explicit DirMergeItemDelegate(DirectoryMergeWindow* pParent) : QStyledItemDelegate(pParent), m_pDMW(pParent), d(pParent->d) { } void paint(QPainter* p, const QStyleOptionViewItem& option, const QModelIndex& index) const override { int column = index.column(); if(column == s_ACol || column == s_BCol || column == s_CCol) { QVariant value = index.data(Qt::DecorationRole); QPixmap icon; if(value.isValid()) { if(value.type() == QVariant::Icon) { icon = qvariant_cast(value).pixmap(16, 16); //icon = qvariant_cast(value); //decorationRect = QRect(QPoint(0, 0), icon.actualSize(option.decorationSize, iconMode, iconState)); } else { icon = qvariant_cast(value); //decorationRect = QRect(QPoint(0, 0), option.decorationSize).intersected(pixmap.rect()); } } int x = option.rect.left(); int y = option.rect.top(); //QPixmap icon = value.value(); //pixmap(column); if(!icon.isNull()) { int yOffset = (sizeHint(option, index).height() - icon.height()) / 2; p->drawPixmap(x + 2, y + yOffset, icon); int i = index == d->m_selection1Index ? 1 : index == d->m_selection2Index ? 2 : index == d->m_selection3Index ? 3 : 0; if(i != 0) { Options* pOpts = d->m_pOptions; QColor c(i == 1 ? pOpts->m_colorA : i == 2 ? pOpts->m_colorB : pOpts->m_colorC); p->setPen(c); // highlight() ); p->drawRect(x + 2, y + yOffset, icon.width(), icon.height()); p->setPen(QPen(c, 0, Qt::DotLine)); p->drawRect(x + 1, y + yOffset - 1, icon.width() + 2, icon.height() + 2); p->setPen(Qt::white); QString s(QChar('A' + i - 1)); p->drawText(x + 2 + (icon.width() - p->fontMetrics().width(s)) / 2, y + yOffset + (icon.height() + p->fontMetrics().ascent()) / 2 - 1, s); } else { p->setPen(m_pDMW->palette().background().color()); p->drawRect(x + 1, y + yOffset - 1, icon.width() + 2, icon.height() + 2); } return; } } QStyleOptionViewItem option2 = option; if(column >= s_UnsolvedCol) { option2.displayAlignment = Qt::AlignRight; } QStyledItemDelegate::paint(p, option2, index); } QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const override { QSize sz = QStyledItemDelegate::sizeHint(option, index); return sz.expandedTo(QSize(0, 18)); } }; DirectoryMergeWindow::DirectoryMergeWindow(QWidget* pParent, Options* pOptions, KIconLoader* pIconLoader) : QTreeView(pParent) { d = new Data(this); setModel(d); setItemDelegate(new DirMergeItemDelegate(this)); connect(this, &DirectoryMergeWindow::doubleClicked, this, &DirectoryMergeWindow::onDoubleClick); connect(this, &DirectoryMergeWindow::expanded, this, &DirectoryMergeWindow::onExpanded); d->m_pOptions = pOptions; d->m_pIconLoader = pIconLoader; setSortingEnabled(true); } DirectoryMergeWindow::~DirectoryMergeWindow() { delete d; } QString DirectoryMergeWindow::getDirNameA() { return d->m_dirA.prettyAbsPath(); } QString DirectoryMergeWindow::getDirNameB() { return d->m_dirB.prettyAbsPath(); } QString DirectoryMergeWindow::getDirNameC() { return d->m_dirC.prettyAbsPath(); } QString DirectoryMergeWindow::getDirNameDest() { return d->m_dirDest.prettyAbsPath(); } void DirectoryMergeWindow::setDirectoryMergeInfo(DirectoryMergeInfo* p) { d->m_pDirectoryMergeInfo = p; } bool DirectoryMergeWindow::isDirectoryMergeInProgress() { return d->m_bRealMergeStarted; } bool DirectoryMergeWindow::isSyncMode() { return d->m_bSyncMode; } bool DirectoryMergeWindow::isScanning() { return d->m_bScanning; } bool DirectoryMergeWindow::Data::fastFileComparison( FileAccess& fi1, FileAccess& fi2, bool& bError, QString& status) { ProgressProxy pp; status = ""; bool bEqual = false; bError = true; if(!m_bFollowFileLinks) { if(fi1.isSymLink() != fi2.isSymLink()) { status = i18n("Mix of links and normal files."); return bEqual; } else if(fi1.isSymLink() && fi2.isSymLink()) { bError = false; bEqual = fi1.readLink() == fi2.readLink(); status = i18n("Link: "); return bEqual; } } if(fi1.size() != fi2.size()) { bEqual = false; status = i18n("Size. "); return bEqual; } else if(m_pOptions->m_bDmTrustSize) { bEqual = true; return bEqual; } if(m_pOptions->m_bDmTrustDate) { bEqual = (fi1.lastModified() == fi2.lastModified() && fi1.size() == fi2.size()); bError = false; status = i18n("Date & Size: "); return bEqual; } if(m_pOptions->m_bDmTrustDateFallbackToBinary) { bEqual = (fi1.lastModified() == fi2.lastModified() && fi1.size() == fi2.size()); if(bEqual) { bError = false; status = i18n("Date & Size: "); return bEqual; } } QString fileName1 = fi1.absoluteFilePath(); QString fileName2 = fi2.absoluteFilePath(); if(!fi2.createLocalCopy()) { status = i18n("Creating temp copy of %1 failed.", fileName1); return bEqual; } if(!fi2.createLocalCopy()) { status = i18n("Creating temp copy of %1 failed.", fileName2); return bEqual; } std::vector buf1(100000); std::vector buf2(buf1.size()); QFile file1(fi1.fileName()); if(!file1.open(QIODevice::ReadOnly)) { status = i18n("Opening %1 failed.", fileName1); return bEqual; } QFile file2(fi2.fileName()); if(!file2.open(QIODevice::ReadOnly)) { status = i18n("Opening %1 failed.", fileName2); return bEqual; } pp.setInformation(i18n("Comparing file..."), 0, false); typedef qint64 t_FileSize; t_FileSize fullSize = file1.size(); t_FileSize sizeLeft = fullSize; pp.setMaxNofSteps(fullSize / buf1.size()); while(sizeLeft > 0 && !pp.wasCancelled()) { qint64 len = std::min(sizeLeft, (t_FileSize)buf1.size()); if(len != file1.read(&buf1[0], len)) { status = i18n("Error reading from %1", fileName1); return bEqual; } if(len != file2.read(&buf2[0], len)) { status = i18n("Error reading from %1", fileName2); return bEqual; } if(memcmp(&buf1[0], &buf2[0], len) != 0) { bError = false; return bEqual; } sizeLeft -= len; //pp.setCurrent(double(fullSize-sizeLeft)/fullSize, false ); pp.step(); } // If the program really arrives here, then the files are really equal. bError = false; bEqual = true; return bEqual; } int DirectoryMergeWindow::totalColumnWidth() { int w = 0; for(int i = 0; i < s_OpStatusCol; ++i) { w += columnWidth(i); } return w; } void DirectoryMergeWindow::reload() { if(isDirectoryMergeInProgress()) { int result = KMessageBox::warningYesNo(this, i18n("You are currently doing a directory merge. Are you sure, you want to abort the merge and rescan the directory?"), i18n("Warning"), KGuiItem(i18n("Rescan")), KGuiItem(i18n("Continue Merging"))); if(result != KMessageBox::Yes) return; } init(d->m_dirA, d->m_dirB, d->m_dirC, d->m_dirDest, d->m_bDirectoryMerge, true); //fix file visibilities after reload or menu will be out of sync with display if changed from defaults. updateFileVisibilities(); } // Copy pm2 onto pm1, but preserve the alpha value from pm1 where pm2 is transparent. static QPixmap pixCombiner(const QPixmap* pm1, const QPixmap* pm2) { QImage img1 = pm1->toImage().convertToFormat(QImage::Format_ARGB32); QImage img2 = pm2->toImage().convertToFormat(QImage::Format_ARGB32); for(int y = 0; y < img1.height(); y++) { quint32* line1 = reinterpret_cast(img1.scanLine(y)); quint32* line2 = reinterpret_cast(img2.scanLine(y)); for(int x = 0; x < img1.width(); x++) { if(qAlpha(line2[x]) > 0) line1[x] = (line2[x] | 0xff000000); } } return QPixmap::fromImage(img1); } // like pixCombiner but let the pm1 color shine through static QPixmap pixCombiner2(const QPixmap* pm1, const QPixmap* pm2) { QPixmap pix = *pm1; QPainter p(&pix); p.setOpacity(0.5); p.drawPixmap(0, 0, *pm2); p.end(); return pix; } void DirectoryMergeWindow::Data::calcDirStatus(bool bThreeDirs, const QModelIndex& mi, int& nofFiles, int& nofDirs, int& nofEqualFiles, int& nofManualMerges) { MergeFileInfos* pMFI = getMFI(mi); if(pMFI->dirA() || pMFI->dirB() || pMFI->dirC()) { ++nofDirs; } else { ++nofFiles; if(pMFI->m_bEqualAB && (!bThreeDirs || pMFI->m_bEqualAC)) { ++nofEqualFiles; } else { if(pMFI->m_eMergeOperation == eMergeABCToDest || pMFI->m_eMergeOperation == eMergeABToDest) ++nofManualMerges; } } for(int childIdx = 0; childIdx < rowCount(mi); ++childIdx) calcDirStatus(bThreeDirs, index(childIdx, 0, mi), nofFiles, nofDirs, nofEqualFiles, nofManualMerges); } struct t_ItemInfo { bool bExpanded; bool bOperationComplete; QString status; e_MergeOperation eMergeOperation; }; bool DirectoryMergeWindow::init( FileAccess& dirA, FileAccess& dirB, FileAccess& dirC, FileAccess& dirDest, bool bDirectoryMerge, bool bReload) { return d->init(dirA, dirB, dirC, dirDest, bDirectoryMerge, bReload); } void DirectoryMergeWindow::Data::buildMergeMap(void) { t_DirectoryList::iterator dirIterator; if(m_dirA.isValid()) { for(dirIterator = m_dirListA.begin(); dirIterator != m_dirListA.end(); ++dirIterator) { MergeFileInfos& mfi = m_fileMergeMap[FileKey(*dirIterator)]; mfi.m_pFileInfoA = &(*dirIterator); } } if(m_dirB.isValid()) { for(dirIterator = m_dirListB.begin(); dirIterator != m_dirListB.end(); ++dirIterator) { MergeFileInfos& mfi = m_fileMergeMap[FileKey(*dirIterator)]; mfi.m_pFileInfoB = &(*dirIterator); } } if(m_dirC.isValid()) { for(dirIterator = m_dirListC.begin(); dirIterator != m_dirListC.end(); ++dirIterator) { MergeFileInfos& mfi = m_fileMergeMap[FileKey(*dirIterator)]; mfi.m_pFileInfoC = &(*dirIterator); } } } bool DirectoryMergeWindow::Data::init( FileAccess& dirA, FileAccess& dirB, FileAccess& dirC, FileAccess& dirDest, bool bDirectoryMerge, bool bReload) { if(m_pOptions->m_bDmFullAnalysis) { // A full analysis uses the same resources that a normal text-diff/merge uses. // So make sure that the user saves his data first. bool bCanContinue = false; emit q->checkIfCanContinue(&bCanContinue); if(!bCanContinue) return false; emit q->startDiffMerge("", "", "", "", "", "", "", nullptr); // hide main window } q->show(); q->setUpdatesEnabled(true); std::map expandedDirsMap; if(bReload) { // Remember expanded items TODO //QTreeWidgetItemIterator it( this ); //while ( *it ) //{ // DirMergeItem* pDMI = static_cast( *it ); // t_ItemInfo& ii = expandedDirsMap[ pDMI->m_pMFI->subPath() ]; // ii.bExpanded = pDMI->isExpanded(); // ii.bOperationComplete = pDMI->m_pMFI->m_bOperationComplete; // ii.status = pDMI->text( s_OpStatusCol ); // ii.eMergeOperation = pDMI->m_pMFI->m_eMergeOperation; // ++it; //} } ProgressProxy pp; m_bFollowDirLinks = m_pOptions->m_bDmFollowDirLinks; m_bFollowFileLinks = m_pOptions->m_bDmFollowFileLinks; m_bSimulatedMergeStarted = false; m_bRealMergeStarted = false; m_bError = false; m_bDirectoryMerge = bDirectoryMerge; m_selection1Index = QModelIndex(); m_selection2Index = QModelIndex(); m_selection3Index = QModelIndex(); m_bCaseSensitive = m_pOptions->m_bDmCaseSensitiveFilenameComparison; m_bUnfoldSubdirs = m_pOptions->m_bDmUnfoldSubdirs; m_bSkipDirStatus = m_pOptions->m_bDmSkipDirStatus; beginResetModel(); m_pRoot->m_children.clear(); m_mergeItemList.clear(); endResetModel(); m_currentIndexForOperation = m_mergeItemList.end(); m_dirA = dirA; m_dirB = dirB; m_dirC = dirC; m_dirDest = dirDest; if(!bReload) { m_pDirShowIdenticalFiles->setChecked(true); m_pDirShowDifferentFiles->setChecked(true); m_pDirShowFilesOnlyInA->setChecked(true); m_pDirShowFilesOnlyInB->setChecked(true); m_pDirShowFilesOnlyInC->setChecked(true); } // Check if all input directories exist and are valid. The dest dir is not tested now. // The test will happen only when we are going to write to it. if(!m_dirA.isDir() || !m_dirB.isDir() || (m_dirC.isValid() && !m_dirC.isDir())) { QString text(i18n("Opening of directories failed:")); text += "\n\n"; if(!dirA.isDir()) { text += i18n("Dir A \"%1\" does not exist or is not a directory.\n", m_dirA.prettyAbsPath()); } if(!dirB.isDir()) { text += i18n("Dir B \"%1\" does not exist or is not a directory.\n", m_dirB.prettyAbsPath()); } if(m_dirC.isValid() && !m_dirC.isDir()) { text += i18n("Dir C \"%1\" does not exist or is not a directory.\n", m_dirC.prettyAbsPath()); } KMessageBox::sorry(q, text, i18n("Directory Open Error")); return false; } if(m_dirC.isValid() && (m_dirDest.prettyAbsPath() == m_dirA.prettyAbsPath() || m_dirDest.prettyAbsPath() == m_dirB.prettyAbsPath())) { KMessageBox::error(q, i18n("The destination directory must not be the same as A or B when " "three directories are merged.\nCheck again before continuing."), i18n("Parameter Warning")); return false; } m_bScanning = true; emit q->statusBarMessage(i18n("Scanning directories...")); m_bSyncMode = m_pOptions->m_bDmSyncMode && !m_dirC.isValid() && !m_dirDest.isValid(); if(m_dirDest.isValid()) m_dirDestInternal = m_dirDest; else m_dirDestInternal = m_dirC.isValid() ? m_dirC : m_dirB; QString origCurrentDirectory = QDir::currentPath(); m_fileMergeMap.clear(); s_eCaseSensitivity = m_bCaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive; // calc how many directories will be read: double nofScans = (m_dirA.isValid() ? 1 : 0) + (m_dirB.isValid() ? 1 : 0) + (m_dirC.isValid() ? 1 : 0); int currentScan = 0; //TODO setColumnWidthMode(s_UnsolvedCol, Q3ListView::Manual); // setColumnWidthMode(s_SolvedCol, Q3ListView::Manual); // setColumnWidthMode(s_WhiteCol, Q3ListView::Manual); // setColumnWidthMode(s_NonWhiteCol, Q3ListView::Manual); q->setColumnHidden(s_CCol, !m_dirC.isValid()); q->setColumnHidden(s_WhiteCol, !m_pOptions->m_bDmFullAnalysis); q->setColumnHidden(s_NonWhiteCol, !m_pOptions->m_bDmFullAnalysis); q->setColumnHidden(s_UnsolvedCol, !m_pOptions->m_bDmFullAnalysis); q->setColumnHidden(s_SolvedCol, !(m_pOptions->m_bDmFullAnalysis && m_dirC.isValid())); bool bListDirSuccessA = true; bool bListDirSuccessB = true; bool bListDirSuccessC = true; m_dirListA.clear(); m_dirListB.clear(); m_dirListC.clear(); if(m_dirA.isValid()) { pp.setInformation(i18n("Reading Directory A")); pp.setSubRangeTransformation(currentScan / nofScans, (currentScan + 1) / nofScans); ++currentScan; bListDirSuccessA = m_dirA.listDir(&m_dirListA, m_pOptions->m_bDmRecursiveDirs, m_pOptions->m_bDmFindHidden, m_pOptions->m_DmFilePattern, m_pOptions->m_DmFileAntiPattern, m_pOptions->m_DmDirAntiPattern, m_pOptions->m_bDmFollowDirLinks, m_pOptions->m_bDmUseCvsIgnore); } if(m_dirB.isValid()) { pp.setInformation(i18n("Reading Directory B")); pp.setSubRangeTransformation(currentScan / nofScans, (currentScan + 1) / nofScans); ++currentScan; bListDirSuccessB = m_dirB.listDir(&m_dirListB, m_pOptions->m_bDmRecursiveDirs, m_pOptions->m_bDmFindHidden, m_pOptions->m_DmFilePattern, m_pOptions->m_DmFileAntiPattern, m_pOptions->m_DmDirAntiPattern, m_pOptions->m_bDmFollowDirLinks, m_pOptions->m_bDmUseCvsIgnore); } e_MergeOperation eDefaultMergeOp; if(m_dirC.isValid()) { pp.setInformation(i18n("Reading Directory C")); pp.setSubRangeTransformation(currentScan / nofScans, (currentScan + 1) / nofScans); ++currentScan; bListDirSuccessC = m_dirC.listDir(&m_dirListC, m_pOptions->m_bDmRecursiveDirs, m_pOptions->m_bDmFindHidden, m_pOptions->m_DmFilePattern, m_pOptions->m_DmFileAntiPattern, m_pOptions->m_DmDirAntiPattern, m_pOptions->m_bDmFollowDirLinks, m_pOptions->m_bDmUseCvsIgnore); eDefaultMergeOp = eMergeABCToDest; } else eDefaultMergeOp = m_bSyncMode ? eMergeToAB : eMergeABToDest; buildMergeMap(); bool bContinue = true; if(!bListDirSuccessA || !bListDirSuccessB || !bListDirSuccessC) { QString s = i18n("Some subdirectories were not readable in"); if(!bListDirSuccessA) s += "\nA: " + m_dirA.prettyAbsPath(); if(!bListDirSuccessB) s += "\nB: " + m_dirB.prettyAbsPath(); if(!bListDirSuccessC) s += "\nC: " + m_dirC.prettyAbsPath(); s += "\n"; s += i18n("Check the permissions of the subdirectories."); bContinue = KMessageBox::Continue == KMessageBox::warningContinueCancel(q, s); } if(bContinue) { prepareListView(pp); q->updateFileVisibilities(); for(int childIdx = 0; childIdx < rowCount(); ++childIdx) { QModelIndex mi = index(childIdx, 0, QModelIndex()); calcSuggestedOperation(mi, eDefaultMergeOp); } } q->sortByColumn(0, Qt::AscendingOrder); for(int column = 0; column < columnCount(QModelIndex()); ++column) q->resizeColumnToContents(column); // Try to improve the view a little bit. QWidget* pParent = q->parentWidget(); QSplitter* pSplitter = static_cast(pParent); if(pSplitter != nullptr) { QList sizes = pSplitter->sizes(); int total = sizes[0] + sizes[1]; if(total < 10) total = 100; sizes[0] = total * 6 / 10; sizes[1] = total - sizes[0]; pSplitter->setSizes(sizes); } QDir::setCurrent(origCurrentDirectory); m_bScanning = false; emit q->statusBarMessage(i18n("Ready.")); if(bContinue && !m_bSkipDirStatus) { // Generate a status report int nofFiles = 0; int nofDirs = 0; int nofEqualFiles = 0; int nofManualMerges = 0; //TODO for(int childIdx = 0; childIdx < rowCount(); ++childIdx) calcDirStatus(m_dirC.isValid(), index(childIdx, 0, QModelIndex()), nofFiles, nofDirs, nofEqualFiles, nofManualMerges); QString s; s = i18n("Directory Comparison Status\n\n" "Number of subdirectories: %1\n" "Number of equal files: %2\n" "Number of different files: %3", nofDirs, nofEqualFiles, nofFiles - nofEqualFiles); if(m_dirC.isValid()) s += "\n" + i18n("Number of manual merges: %1", nofManualMerges); KMessageBox::information(q, s); // //TODO //if ( topLevelItemCount()>0 ) //{ // topLevelItem(0)->setSelected(true); // setCurrentItem( topLevelItem(0) ); //} } if(bReload) { // Remember expanded items //TODO //QTreeWidgetItemIterator it( this ); //while ( *it ) //{ // DirMergeItem* pDMI = static_cast( *it ); // std::map::iterator i = expandedDirsMap.find( pDMI->m_pMFI->subPath() ); // if ( i!=expandedDirsMap.end() ) // { // t_ItemInfo& ii = i->second; // pDMI->setExpanded( ii.bExpanded ); // //pDMI->m_pMFI->setMergeOperation( ii.eMergeOperation, false ); unsafe, might have changed // pDMI->m_pMFI->m_bOperationComplete = ii.bOperationComplete; // pDMI->setText( s_OpStatusCol, ii.status ); // } // ++it; //} } else if(m_bUnfoldSubdirs) { m_pDirUnfoldAll->trigger(); } return true; } void DirectoryMergeWindow::onExpanded() { resizeColumnToContents(s_NameCol); } void DirectoryMergeWindow::slotChooseAEverywhere() { d->setAllMergeOperations(eCopyAToDest); } void DirectoryMergeWindow::slotChooseBEverywhere() { d->setAllMergeOperations(eCopyBToDest); } void DirectoryMergeWindow::slotChooseCEverywhere() { d->setAllMergeOperations(eCopyCToDest); } void DirectoryMergeWindow::slotAutoChooseEverywhere() { e_MergeOperation eDefaultMergeOp = d->m_dirC.isValid() ? eMergeABCToDest : d->m_bSyncMode ? eMergeToAB : eMergeABToDest; d->setAllMergeOperations(eDefaultMergeOp); } void DirectoryMergeWindow::slotNoOpEverywhere() { d->setAllMergeOperations(eNoOperation); } void DirectoryMergeWindow::slotFoldAllSubdirs() { collapseAll(); } void DirectoryMergeWindow::slotUnfoldAllSubdirs() { expandAll(); } // Merge current item (merge mode) void DirectoryMergeWindow::slotCurrentDoNothing() { d->setMergeOperation(currentIndex(), eNoOperation); } void DirectoryMergeWindow::slotCurrentChooseA() { d->setMergeOperation(currentIndex(), d->m_bSyncMode ? eCopyAToB : eCopyAToDest); } void DirectoryMergeWindow::slotCurrentChooseB() { d->setMergeOperation(currentIndex(), d->m_bSyncMode ? eCopyBToA : eCopyBToDest); } void DirectoryMergeWindow::slotCurrentChooseC() { d->setMergeOperation(currentIndex(), eCopyCToDest); } void DirectoryMergeWindow::slotCurrentMerge() { bool bThreeDirs = d->m_dirC.isValid(); d->setMergeOperation(currentIndex(), bThreeDirs ? eMergeABCToDest : eMergeABToDest); } void DirectoryMergeWindow::slotCurrentDelete() { d->setMergeOperation(currentIndex(), eDeleteFromDest); } // Sync current item void DirectoryMergeWindow::slotCurrentCopyAToB() { d->setMergeOperation(currentIndex(), eCopyAToB); } void DirectoryMergeWindow::slotCurrentCopyBToA() { d->setMergeOperation(currentIndex(), eCopyBToA); } void DirectoryMergeWindow::slotCurrentDeleteA() { d->setMergeOperation(currentIndex(), eDeleteA); } void DirectoryMergeWindow::slotCurrentDeleteB() { d->setMergeOperation(currentIndex(), eDeleteB); } void DirectoryMergeWindow::slotCurrentDeleteAAndB() { d->setMergeOperation(currentIndex(), eDeleteAB); } void DirectoryMergeWindow::slotCurrentMergeToA() { d->setMergeOperation(currentIndex(), eMergeToA); } void DirectoryMergeWindow::slotCurrentMergeToB() { d->setMergeOperation(currentIndex(), eMergeToB); } void DirectoryMergeWindow::slotCurrentMergeToAAndB() { d->setMergeOperation(currentIndex(), eMergeToAB); } void DirectoryMergeWindow::keyPressEvent(QKeyEvent* e) { if((e->QInputEvent::modifiers() & Qt::ControlModifier) != 0) { bool bThreeDirs = d->m_dirC.isValid(); MergeFileInfos* pMFI = d->getMFI(currentIndex()); if(pMFI == nullptr) return; bool bMergeMode = bThreeDirs || !d->m_bSyncMode; bool bFTConflict = pMFI == nullptr ? false : conflictingFileTypes(*pMFI); if(bMergeMode) { switch(e->key()) { case Qt::Key_1: if(pMFI->existsInA()) { slotCurrentChooseA(); } return; case Qt::Key_2: if(pMFI->existsInB()) { slotCurrentChooseB(); } return; case Qt::Key_3: if(pMFI->existsInC()) { slotCurrentChooseC(); } return; case Qt::Key_Space: slotCurrentDoNothing(); return; case Qt::Key_4: if(!bFTConflict) { slotCurrentMerge(); } return; case Qt::Key_Delete: slotCurrentDelete(); return; default: break; } } else { switch(e->key()) { case Qt::Key_1: if(pMFI->existsInA()) { slotCurrentCopyAToB(); } return; case Qt::Key_2: if(pMFI->existsInB()) { slotCurrentCopyBToA(); } return; case Qt::Key_Space: slotCurrentDoNothing(); return; case Qt::Key_4: if(!bFTConflict) { slotCurrentMergeToAAndB(); } return; case Qt::Key_Delete: if(pMFI->existsInA() && pMFI->existsInB()) slotCurrentDeleteAAndB(); else if(pMFI->existsInA()) slotCurrentDeleteA(); else if(pMFI->existsInB()) slotCurrentDeleteB(); return; default: break; } } } else if(e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter) { onDoubleClick(currentIndex()); return; } QTreeView::keyPressEvent(e); } void DirectoryMergeWindow::focusInEvent(QFocusEvent*) { emit updateAvailabilities(); } void DirectoryMergeWindow::focusOutEvent(QFocusEvent*) { emit updateAvailabilities(); } void DirectoryMergeWindow::Data::setAllMergeOperations(e_MergeOperation eDefaultOperation) { if(KMessageBox::Yes == KMessageBox::warningYesNo(q, i18n("This affects all merge operations."), i18n("Changing All Merge Operations"), KStandardGuiItem::cont(), KStandardGuiItem::cancel())) { for(int i = 0; i < rowCount(); ++i) { calcSuggestedOperation(index(i, 0, QModelIndex()), eDefaultOperation); } } } void DirectoryMergeWindow::Data::compareFilesAndCalcAges(MergeFileInfos& mfi) { std::map dateMap; if(mfi.existsInA()) { dateMap[mfi.m_pFileInfoA->lastModified()] = 0; } if(mfi.existsInB()) { dateMap[mfi.m_pFileInfoB->lastModified()] = 1; } if(mfi.existsInC()) { dateMap[mfi.m_pFileInfoC->lastModified()] = 2; } if(m_pOptions->m_bDmFullAnalysis) { if((mfi.existsInA() && mfi.dirA()) || (mfi.existsInB() && mfi.dirB()) || (mfi.existsInC() && mfi.dirC())) { // If any input is a directory, don't start any comparison. mfi.m_bEqualAB = mfi.existsInA() && mfi.existsInB(); mfi.m_bEqualAC = mfi.existsInA() && mfi.existsInC(); mfi.m_bEqualBC = mfi.existsInB() && mfi.existsInC(); } else { emit q->startDiffMerge( mfi.existsInA() ? mfi.m_pFileInfoA->absoluteFilePath() : QString(""), mfi.existsInB() ? mfi.m_pFileInfoB->absoluteFilePath() : QString(""), mfi.existsInC() ? mfi.m_pFileInfoC->absoluteFilePath() : QString(""), "", "", "", "", &mfi.m_totalDiffStatus); int nofNonwhiteConflicts = mfi.m_totalDiffStatus.nofUnsolvedConflicts + mfi.m_totalDiffStatus.nofSolvedConflicts - mfi.m_totalDiffStatus.nofWhitespaceConflicts; if(m_pOptions->m_bDmWhiteSpaceEqual && nofNonwhiteConflicts == 0) { mfi.m_bEqualAB = mfi.existsInA() && mfi.existsInB(); mfi.m_bEqualAC = mfi.existsInA() && mfi.existsInC(); mfi.m_bEqualBC = mfi.existsInB() && mfi.existsInC(); } else { mfi.m_bEqualAB = mfi.m_totalDiffStatus.bBinaryAEqB; mfi.m_bEqualBC = mfi.m_totalDiffStatus.bBinaryBEqC; mfi.m_bEqualAC = mfi.m_totalDiffStatus.bBinaryAEqC; } } } else { bool bError; QString eqStatus; if(mfi.existsInA() && mfi.existsInB()) { if(mfi.dirA()) mfi.m_bEqualAB = true; else mfi.m_bEqualAB = fastFileComparison(*mfi.m_pFileInfoA, *mfi.m_pFileInfoB, bError, eqStatus); } if(mfi.existsInA() && mfi.existsInC()) { if(mfi.dirA()) mfi.m_bEqualAC = true; else mfi.m_bEqualAC = fastFileComparison(*mfi.m_pFileInfoA, *mfi.m_pFileInfoC, bError, eqStatus); } if(mfi.existsInB() && mfi.existsInC()) { if(mfi.m_bEqualAB && mfi.m_bEqualAC) mfi.m_bEqualBC = true; else { if(mfi.dirB()) mfi.m_bEqualBC = true; else mfi.m_bEqualBC = fastFileComparison(*mfi.m_pFileInfoB, *mfi.m_pFileInfoC, bError, eqStatus); } } } if(mfi.isLinkA() != mfi.isLinkB()) mfi.m_bEqualAB = false; if(mfi.isLinkA() != mfi.isLinkC()) mfi.m_bEqualAC = false; if(mfi.isLinkB() != mfi.isLinkC()) mfi.m_bEqualBC = false; if(mfi.dirA() != mfi.dirB()) mfi.m_bEqualAB = false; if(mfi.dirA() != mfi.dirC()) mfi.m_bEqualAC = false; if(mfi.dirB() != mfi.dirC()) mfi.m_bEqualBC = false; Q_ASSERT(eNew == 0 && eMiddle == 1 && eOld == 2); // The map automatically sorts the keys. int age = eNew; std::map::reverse_iterator i; for(i = dateMap.rbegin(); i != dateMap.rend(); ++i) { int n = i->second; if(n == 0 && mfi.m_ageA == eNotThere) { mfi.m_ageA = (e_Age)age; ++age; if(mfi.m_bEqualAB) { mfi.m_ageB = mfi.m_ageA; ++age; } if(mfi.m_bEqualAC) { mfi.m_ageC = mfi.m_ageA; ++age; } } else if(n == 1 && mfi.m_ageB == eNotThere) { mfi.m_ageB = (e_Age)age; ++age; if(mfi.m_bEqualAB) { mfi.m_ageA = mfi.m_ageB; ++age; } if(mfi.m_bEqualBC) { mfi.m_ageC = mfi.m_ageB; ++age; } } else if(n == 2 && mfi.m_ageC == eNotThere) { mfi.m_ageC = (e_Age)age; ++age; if(mfi.m_bEqualAC) { mfi.m_ageA = mfi.m_ageC; ++age; } if(mfi.m_bEqualBC) { mfi.m_ageB = mfi.m_ageC; ++age; } } } // The checks below are necessary when the dates of the file are equal but the // files are not. One wouldn't expect this to happen, yet it happens sometimes. if(mfi.existsInC() && mfi.m_ageC == eNotThere) { mfi.m_ageC = (e_Age)age; ++age; mfi.m_bConflictingAges = true; } if(mfi.existsInB() && mfi.m_ageB == eNotThere) { mfi.m_ageB = (e_Age)age; ++age; mfi.m_bConflictingAges = true; } if(mfi.existsInA() && mfi.m_ageA == eNotThere) { mfi.m_ageA = (e_Age)age; ++age; mfi.m_bConflictingAges = true; } if(mfi.m_ageA != eOld && mfi.m_ageB != eOld && mfi.m_ageC != eOld) { if(mfi.m_ageA == eMiddle) mfi.m_ageA = eOld; if(mfi.m_ageB == eMiddle) mfi.m_ageB = eOld; if(mfi.m_ageC == eMiddle) mfi.m_ageC = eOld; } } static QPixmap* s_pm_dir; static QPixmap* s_pm_file; static QPixmap* pmNotThere; static QPixmap* pmNew; static QPixmap* pmOld; static QPixmap* pmMiddle; static QPixmap* pmLink; static QPixmap* pmDirLink; static QPixmap* pmFileLink; static QPixmap* pmNewLink; static QPixmap* pmOldLink; static QPixmap* pmMiddleLink; static QPixmap* pmNewDir; static QPixmap* pmMiddleDir; static QPixmap* pmOldDir; static QPixmap* pmNewDirLink; static QPixmap* pmMiddleDirLink; static QPixmap* pmOldDirLink; static QPixmap colorToPixmap(QColor c) { QPixmap pm(16, 16); QPainter p(&pm); p.setPen(Qt::black); p.setBrush(c); p.drawRect(0, 0, pm.width(), pm.height()); return pm; } static void initPixmaps(QColor newest, QColor oldest, QColor middle, QColor notThere) { if(pmNew == nullptr) { pmNotThere = new QPixmap; pmNew = new QPixmap; pmOld = new QPixmap; pmMiddle = new QPixmap; #include "xpm/link_arrow.xpm" pmLink = new QPixmap(link_arrow); pmDirLink = new QPixmap; pmFileLink = new QPixmap; pmNewLink = new QPixmap; pmOldLink = new QPixmap; pmMiddleLink = new QPixmap; pmNewDir = new QPixmap; pmMiddleDir = new QPixmap; pmOldDir = new QPixmap; pmNewDirLink = new QPixmap; pmMiddleDirLink = new QPixmap; pmOldDirLink = new QPixmap; } *pmNotThere = colorToPixmap(notThere); *pmNew = colorToPixmap(newest); *pmOld = colorToPixmap(oldest); *pmMiddle = colorToPixmap(middle); *pmDirLink = pixCombiner(s_pm_dir, pmLink); *pmFileLink = pixCombiner(s_pm_file, pmLink); *pmNewLink = pixCombiner(pmNew, pmLink); *pmOldLink = pixCombiner(pmOld, pmLink); *pmMiddleLink = pixCombiner(pmMiddle, pmLink); *pmNewDir = pixCombiner2(pmNew, s_pm_dir); *pmMiddleDir = pixCombiner2(pmMiddle, s_pm_dir); *pmOldDir = pixCombiner2(pmOld, s_pm_dir); *pmNewDirLink = pixCombiner(pmNewDir, pmLink); *pmMiddleDirLink = pixCombiner(pmMiddleDir, pmLink); *pmOldDirLink = pixCombiner(pmOldDir, pmLink); } static QPixmap getOnePixmap(e_Age eAge, bool bLink, bool bDir) { static QPixmap* ageToPm[] = {pmNew, pmMiddle, pmOld, pmNotThere, s_pm_file}; static QPixmap* ageToPmLink[] = {pmNewLink, pmMiddleLink, pmOldLink, pmNotThere, pmFileLink}; static QPixmap* ageToPmDir[] = {pmNewDir, pmMiddleDir, pmOldDir, pmNotThere, s_pm_dir}; static QPixmap* ageToPmDirLink[] = {pmNewDirLink, pmMiddleDirLink, pmOldDirLink, pmNotThere, pmDirLink}; QPixmap** ppPm = bDir ? (bLink ? ageToPmDirLink : ageToPmDir) : (bLink ? ageToPmLink : ageToPm); return *ppPm[eAge]; } static void setPixmaps(MergeFileInfos& mfi, bool) { if(mfi.dirA() || mfi.dirB() || mfi.dirC()) { mfi.m_ageA = eNotThere; mfi.m_ageB = eNotThere; mfi.m_ageC = eNotThere; int age = eNew; if(mfi.existsInC()) { mfi.m_ageC = (e_Age)age; if(mfi.m_bEqualAC) mfi.m_ageA = (e_Age)age; if(mfi.m_bEqualBC) mfi.m_ageB = (e_Age)age; ++age; } if(mfi.existsInB() && mfi.m_ageB == eNotThere) { mfi.m_ageB = (e_Age)age; if(mfi.m_bEqualAB) mfi.m_ageA = (e_Age)age; ++age; } if(mfi.existsInA() && mfi.m_ageA == eNotThere) { mfi.m_ageA = (e_Age)age; } if(mfi.m_ageA != eOld && mfi.m_ageB != eOld && mfi.m_ageC != eOld) { if(mfi.m_ageA == eMiddle) mfi.m_ageA = eOld; if(mfi.m_ageB == eMiddle) mfi.m_ageB = eOld; if(mfi.m_ageC == eMiddle) mfi.m_ageC = eOld; } } } static QModelIndex nextSibling(const QModelIndex& mi) { QModelIndex miParent = mi.parent(); int currentIdx = mi.row(); if(currentIdx + 1 < mi.model()->rowCount(miParent)) return mi.model()->index(mi.row() + 1, 0, miParent); // next child of parent return QModelIndex(); } // Iterate through the complete tree. Start by specifying QListView::firstChild(). QModelIndex DirectoryMergeWindow::Data::treeIterator(QModelIndex mi, bool bVisitChildren, bool bFindInvisible) { if(mi.isValid()) { do { if(bVisitChildren && mi.model()->rowCount(mi) != 0) mi = mi.model()->index(0, 0, mi); else { QModelIndex miNextSibling = nextSibling(mi); if(miNextSibling.isValid()) mi = miNextSibling; else { mi = mi.parent(); while(mi.isValid()) { miNextSibling = nextSibling(mi); if(miNextSibling.isValid()) { mi = miNextSibling; break; } else { mi = mi.parent(); } } } } } while(mi.isValid() && q->isRowHidden(mi.row(), mi.parent()) && !bFindInvisible); } return mi; } void DirectoryMergeWindow::Data::prepareListView(ProgressProxy& pp) { static bool bFirstTime = true; if(bFirstTime) { #include "xpm/file.xpm" #include "xpm/folder.xpm" // FIXME specify correct icon loader group s_pm_dir = new QPixmap(m_pIconLoader->loadIcon("folder", KIconLoader::NoGroup, KIconLoader::Small)); if(s_pm_dir->size() != QSize(16, 16)) { delete s_pm_dir; s_pm_dir = new QPixmap(folder_pm); } s_pm_file = new QPixmap(file_pm); bFirstTime = false; } //TODO clear(); initPixmaps(m_pOptions->m_newestFileColor, m_pOptions->m_oldestFileColor, m_pOptions->m_midAgeFileColor, m_pOptions->m_missingFileColor); q->setRootIsDecorated(true); bool bCheckC = m_dirC.isValid(); t_fileMergeMap::iterator j; int nrOfFiles = m_fileMergeMap.size(); int currentIdx = 1; QTime t; t.start(); pp.setMaxNofSteps(nrOfFiles); for(j = m_fileMergeMap.begin(); j != m_fileMergeMap.end(); ++j) { MergeFileInfos& mfi = j.value(); // const QString& fileName = j->first; const QString& fileName = mfi.subPath(); pp.setInformation( i18n("Processing %1 / %2\n%3", currentIdx, nrOfFiles, fileName), currentIdx, false); if(pp.wasCancelled()) break; ++currentIdx; // The comparisons and calculations for each file take place here. compareFilesAndCalcAges(mfi); // Get dirname from fileName: Search for "/" from end: int pos = fileName.lastIndexOf('/'); QString dirPart; QString filePart; if(pos == -1) { // Top dir filePart = fileName; } else { dirPart = fileName.left(pos); filePart = fileName.mid(pos + 1); } if(dirPart.isEmpty()) // Top level { m_pRoot->m_children.push_back(&mfi); //new DirMergeItem( this, filePart, &mfi ); mfi.m_pParent = m_pRoot; } else { FileAccess* pFA = mfi.m_pFileInfoA ? mfi.m_pFileInfoA : mfi.m_pFileInfoB ? mfi.m_pFileInfoB : mfi.m_pFileInfoC; MergeFileInfos& dirMfi = pFA->parent() ? m_fileMergeMap[FileKey(*pFA->parent())] : *m_pRoot; // parent dirMfi.m_children.push_back(&mfi); //new DirMergeItem( dirMfi.m_pDMI, filePart, &mfi ); mfi.m_pParent = &dirMfi; // // Equality for parent dirs is set in updateFileVisibilities() } setPixmaps(mfi, bCheckC); } beginResetModel(); endResetModel(); } static bool conflictingFileTypes(MergeFileInfos& mfi) { // Now check if file/dir-types fit. if(mfi.isLinkA() || mfi.isLinkB() || mfi.isLinkC()) { if((mfi.existsInA() && !mfi.isLinkA()) || (mfi.existsInB() && !mfi.isLinkB()) || (mfi.existsInC() && !mfi.isLinkC())) { return true; } } if(mfi.dirA() || mfi.dirB() || mfi.dirC()) { if((mfi.existsInA() && !mfi.dirA()) || (mfi.existsInB() && !mfi.dirB()) || (mfi.existsInC() && !mfi.dirC())) { return true; } } return false; } void DirectoryMergeWindow::Data::calcSuggestedOperation(const QModelIndex& mi, e_MergeOperation eDefaultMergeOp) { MergeFileInfos* pMFI = getMFI(mi); if(pMFI == nullptr) return; MergeFileInfos& mfi = *pMFI; bool bCheckC = m_dirC.isValid(); bool bCopyNewer = m_pOptions->m_bDmCopyNewer; bool bOtherDest = !((m_dirDestInternal.absoluteFilePath() == m_dirA.absoluteFilePath()) || (m_dirDestInternal.absoluteFilePath() == m_dirB.absoluteFilePath()) || (bCheckC && m_dirDestInternal.absoluteFilePath() == m_dirC.absoluteFilePath())); if(eDefaultMergeOp == eMergeABCToDest && !bCheckC) { eDefaultMergeOp = eMergeABToDest; } if(eDefaultMergeOp == eMergeToAB && bCheckC) { Q_ASSERT(true); } if(eDefaultMergeOp == eMergeToA || eDefaultMergeOp == eMergeToB || eDefaultMergeOp == eMergeABCToDest || eDefaultMergeOp == eMergeABToDest || eDefaultMergeOp == eMergeToAB) { if(!bCheckC) { if(mfi.m_bEqualAB) { setMergeOperation(mi, bOtherDest ? eCopyBToDest : eNoOperation); } else if(mfi.existsInA() && mfi.existsInB()) { if(!bCopyNewer || mfi.dirA()) setMergeOperation(mi, eDefaultMergeOp); else if(bCopyNewer && mfi.m_bConflictingAges) { setMergeOperation(mi, eConflictingAges); } else { if(mfi.m_ageA == eNew) setMergeOperation(mi, eDefaultMergeOp == eMergeToAB ? eCopyAToB : eCopyAToDest); else setMergeOperation(mi, eDefaultMergeOp == eMergeToAB ? eCopyBToA : eCopyBToDest); } } else if(!mfi.existsInA() && mfi.existsInB()) { if(eDefaultMergeOp == eMergeABToDest) setMergeOperation(mi, eCopyBToDest); else if(eDefaultMergeOp == eMergeToB) setMergeOperation(mi, eNoOperation); else setMergeOperation(mi, eCopyBToA); } else if(mfi.existsInA() && !mfi.existsInB()) { if(eDefaultMergeOp == eMergeABToDest) setMergeOperation(mi, eCopyAToDest); else if(eDefaultMergeOp == eMergeToA) setMergeOperation(mi, eNoOperation); else setMergeOperation(mi, eCopyAToB); } else //if ( !mfi.existsInA() && !mfi.existsInB() ) { setMergeOperation(mi, eNoOperation); } } else { if(mfi.m_bEqualAB && mfi.m_bEqualAC) { setMergeOperation(mi, bOtherDest ? eCopyCToDest : eNoOperation); } else if(mfi.existsInA() && mfi.existsInB() && mfi.existsInC()) { if(mfi.m_bEqualAB) setMergeOperation(mi, eCopyCToDest); else if(mfi.m_bEqualAC) setMergeOperation(mi, eCopyBToDest); else if(mfi.m_bEqualBC) setMergeOperation(mi, eCopyCToDest); else setMergeOperation(mi, eMergeABCToDest); } else if(mfi.existsInA() && mfi.existsInB() && !mfi.existsInC()) { if(mfi.m_bEqualAB) setMergeOperation(mi, eDeleteFromDest); else setMergeOperation(mi, eChangedAndDeleted); } else if(mfi.existsInA() && !mfi.existsInB() && mfi.existsInC()) { if(mfi.m_bEqualAC) setMergeOperation(mi, eDeleteFromDest); else setMergeOperation(mi, eChangedAndDeleted); } else if(!mfi.existsInA() && mfi.existsInB() && mfi.existsInC()) { if(mfi.m_bEqualBC) setMergeOperation(mi, eCopyCToDest); else setMergeOperation(mi, eMergeABCToDest); } else if(!mfi.existsInA() && !mfi.existsInB() && mfi.existsInC()) { setMergeOperation(mi, eCopyCToDest); } else if(!mfi.existsInA() && mfi.existsInB() && !mfi.existsInC()) { setMergeOperation(mi, eCopyBToDest); } else if(mfi.existsInA() && !mfi.existsInB() && !mfi.existsInC()) { setMergeOperation(mi, eDeleteFromDest); } else //if ( !mfi.existsInA() && !mfi.existsInB() && !mfi.existsInC() ) { setMergeOperation(mi, eNoOperation); } } // Now check if file/dir-types fit. if(conflictingFileTypes(mfi)) { setMergeOperation(mi, eConflictingFileTypes); } } else { e_MergeOperation eMO = eDefaultMergeOp; switch(eDefaultMergeOp) { case eConflictingFileTypes: case eChangedAndDeleted: case eConflictingAges: case eDeleteA: case eDeleteB: case eDeleteAB: case eDeleteFromDest: case eNoOperation: break; case eCopyAToB: if(!mfi.existsInA()) { eMO = eDeleteB; } break; case eCopyBToA: if(!mfi.existsInB()) { eMO = eDeleteA; } break; case eCopyAToDest: if(!mfi.existsInA()) { eMO = eDeleteFromDest; } break; case eCopyBToDest: if(!mfi.existsInB()) { eMO = eDeleteFromDest; } break; case eCopyCToDest: if(!mfi.existsInC()) { eMO = eDeleteFromDest; } break; case eMergeToA: case eMergeToB: case eMergeToAB: case eMergeABCToDest: case eMergeABToDest: break; default: Q_ASSERT(true); break; } setMergeOperation(mi, eMO); } } void DirectoryMergeWindow::onDoubleClick(const QModelIndex& mi) { if(!mi.isValid()) return; d->m_bSimulatedMergeStarted = false; if(d->m_bDirectoryMerge) mergeCurrentFile(); else compareCurrentFile(); } void DirectoryMergeWindow::currentChanged(const QModelIndex& current, const QModelIndex& previous) { QTreeView::currentChanged(current, previous); MergeFileInfos* pMFI = d->getMFI(current); if(pMFI == nullptr) return; d->m_pDirectoryMergeInfo->setInfo(d->m_dirA, d->m_dirB, d->m_dirC, d->m_dirDestInternal, *pMFI); } void DirectoryMergeWindow::mousePressEvent(QMouseEvent* e) { QTreeView::mousePressEvent(e); QModelIndex mi = indexAt(e->pos()); int c = mi.column(); QPoint p = e->globalPos(); MergeFileInfos* pMFI = d->getMFI(mi); if(pMFI == nullptr) return; MergeFileInfos& mfi = *pMFI; if(c == s_OpCol) { bool bThreeDirs = d->m_dirC.isValid(); QMenu m(this); if(bThreeDirs) { m.addAction(d->m_pDirCurrentDoNothing); int count = 0; if(mfi.existsInA()) { m.addAction(d->m_pDirCurrentChooseA); ++count; } if(mfi.existsInB()) { m.addAction(d->m_pDirCurrentChooseB); ++count; } if(mfi.existsInC()) { m.addAction(d->m_pDirCurrentChooseC); ++count; } if(!conflictingFileTypes(mfi) && count > 1) m.addAction(d->m_pDirCurrentMerge); m.addAction(d->m_pDirCurrentDelete); } else if(d->m_bSyncMode) { m.addAction(d->m_pDirCurrentSyncDoNothing); if(mfi.existsInA()) m.addAction(d->m_pDirCurrentSyncCopyAToB); if(mfi.existsInB()) m.addAction(d->m_pDirCurrentSyncCopyBToA); if(mfi.existsInA()) m.addAction(d->m_pDirCurrentSyncDeleteA); if(mfi.existsInB()) m.addAction(d->m_pDirCurrentSyncDeleteB); if(mfi.existsInA() && mfi.existsInB()) { m.addAction(d->m_pDirCurrentSyncDeleteAAndB); if(!conflictingFileTypes(mfi)) { m.addAction(d->m_pDirCurrentSyncMergeToA); m.addAction(d->m_pDirCurrentSyncMergeToB); m.addAction(d->m_pDirCurrentSyncMergeToAAndB); } } } else { m.addAction(d->m_pDirCurrentDoNothing); if(mfi.existsInA()) { m.addAction(d->m_pDirCurrentChooseA); } if(mfi.existsInB()) { m.addAction(d->m_pDirCurrentChooseB); } if(!conflictingFileTypes(mfi) && mfi.existsInA() && mfi.existsInB()) m.addAction(d->m_pDirCurrentMerge); m.addAction(d->m_pDirCurrentDelete); } m.exec(p); } else if(c == s_ACol || c == s_BCol || c == s_CCol) { QString itemPath; if(c == s_ACol && mfi.existsInA()) { itemPath = d->fullNameA(mfi); } else if(c == s_BCol && mfi.existsInB()) { itemPath = d->fullNameB(mfi); } else if(c == s_CCol && mfi.existsInC()) { itemPath = d->fullNameC(mfi); } if(!itemPath.isEmpty()) { d->selectItemAndColumn(mi, e->button() == Qt::RightButton); } } } #ifndef QT_NO_CONTEXTMENU void DirectoryMergeWindow::contextMenuEvent(QContextMenuEvent* e) { QModelIndex mi = indexAt(e->pos()); int c = mi.column(); MergeFileInfos* pMFI = d->getMFI(mi); if(pMFI == nullptr) return; if(c == s_ACol || c == s_BCol || c == s_CCol) { QString itemPath; if(c == s_ACol && pMFI->existsInA()) { itemPath = d->fullNameA(*pMFI); } else if(c == s_BCol && pMFI->existsInB()) { itemPath = d->fullNameB(*pMFI); } else if(c == s_CCol && pMFI->existsInC()) { itemPath = d->fullNameC(*pMFI); } if(!itemPath.isEmpty()) { d->selectItemAndColumn(mi, true); QMenu m(this); m.addAction(d->m_pDirCompareExplicit); m.addAction(d->m_pDirMergeExplicit); m.popup(e->globalPos()); } } } #endif QString DirectoryMergeWindow::Data::getFileName(const QModelIndex& mi) { MergeFileInfos* pMFI = getMFI(mi); if(pMFI != nullptr) { return mi.column() == s_ACol ? pMFI->m_pFileInfoA->absoluteFilePath() : mi.column() == s_BCol ? pMFI->m_pFileInfoB->absoluteFilePath() : mi.column() == s_CCol ? pMFI->m_pFileInfoC->absoluteFilePath() : QString(""); } return ""; } bool DirectoryMergeWindow::Data::isDir(const QModelIndex& mi) { MergeFileInfos* pMFI = getMFI(mi); if(pMFI != nullptr) { return mi.column() == s_ACol ? pMFI->dirA() : mi.column() == s_BCol ? pMFI->dirB() : pMFI->dirC(); } return false; } void DirectoryMergeWindow::Data::selectItemAndColumn(const QModelIndex& mi, bool bContextMenu) { if(bContextMenu && (mi == m_selection1Index || mi == m_selection2Index || mi == m_selection3Index)) return; QModelIndex old1 = m_selection1Index; QModelIndex old2 = m_selection2Index; QModelIndex old3 = m_selection3Index; bool bReset = false; if(m_selection1Index.isValid()) { if(isDir(m_selection1Index) != isDir(mi)) bReset = true; } if(bReset || m_selection3Index.isValid() || mi == m_selection1Index || mi == m_selection2Index || mi == m_selection3Index) { // restart m_selection1Index = QModelIndex(); m_selection2Index = QModelIndex(); m_selection3Index = QModelIndex(); } else if(!m_selection1Index.isValid()) { m_selection1Index = mi; m_selection2Index = QModelIndex(); m_selection3Index = QModelIndex(); } else if(!m_selection2Index.isValid()) { m_selection2Index = mi; m_selection3Index = QModelIndex(); } else if(!m_selection3Index.isValid()) { m_selection3Index = mi; } if(old1.isValid()) emit dataChanged(old1, old1); if(old2.isValid()) emit dataChanged(old2, old2); if(old3.isValid()) emit dataChanged(old3, old3); if(m_selection1Index.isValid()) emit dataChanged(m_selection1Index, m_selection1Index); if(m_selection2Index.isValid()) emit dataChanged(m_selection2Index, m_selection2Index); if(m_selection3Index.isValid()) emit dataChanged(m_selection3Index, m_selection3Index); emit q->updateAvailabilities(); } //TODO //void DirMergeItem::init(MergeFileInfos* pMFI) //{ // pMFI->m_pDMI = this; // m_pMFI = pMFI; // TotalDiffStatus& tds = pMFI->m_totalDiffStatus; // if ( m_pMFI->dirA() || m_pMFI->dirB() || m_pMFI->dirC() ) // { // } // else // { // setText( s_UnsolvedCol, QString::number( tds.nofUnsolvedConflicts ) ); // setText( s_SolvedCol, QString::number( tds.nofSolvedConflicts ) ); // setText( s_NonWhiteCol, QString::number( tds.nofUnsolvedConflicts + tds.nofSolvedConflicts - tds.nofWhitespaceConflicts ) ); // setText( s_WhiteCol, QString::number( tds.nofWhitespaceConflicts ) ); // } // setSizeHint( s_ACol, QSize(17,17) ); // Iconsize // setSizeHint( s_BCol, QSize(17,17) ); // Iconsize // setSizeHint( s_CCol, QSize(17,17) ); // Iconsize //} class MfiCompare { Qt::SortOrder mOrder; public: explicit MfiCompare(Qt::SortOrder order) { mOrder = order; } bool operator()(MergeFileInfos* pMFI1, MergeFileInfos* pMFI2) { bool bDir1 = pMFI1->dirA() || pMFI1->dirB() || pMFI1->dirC(); bool bDir2 = pMFI2->dirA() || pMFI2->dirB() || pMFI2->dirC(); if(bDir1 == bDir2) { if(mOrder == Qt::AscendingOrder) { return pMFI1->fileName().compare(pMFI2->fileName(), Qt::CaseInsensitive) < 0; } else { return pMFI1->fileName().compare(pMFI2->fileName(), Qt::CaseInsensitive) > 0; } } else return bDir1; } }; static void sortHelper(MergeFileInfos* pMFI, Qt::SortOrder order) { std::sort(pMFI->m_children.begin(), pMFI->m_children.end(), MfiCompare(order)); for(int i = 0; i < pMFI->m_children.count(); ++i) sortHelper(pMFI->m_children[i], order); } void DirectoryMergeWindow::Data::sort(int column, Qt::SortOrder order) { Q_UNUSED(column); beginResetModel(); sortHelper(m_pRoot, order); endResetModel(); } // //DirMergeItem::~DirMergeItem() //{ // m_pMFI->m_pDMI = 0; //} void DirectoryMergeWindow::Data::setMergeOperation(const QModelIndex& mi, e_MergeOperation eMOp, bool bRecursive) { MergeFileInfos* pMFI = getMFI(mi); if(pMFI == nullptr) return; MergeFileInfos& mfi = *pMFI; if(eMOp != mfi.m_eMergeOperation) { mfi.m_bOperationComplete = false; setOpStatus(mi, eOpStatusNone); } mfi.m_eMergeOperation = eMOp; if(bRecursive) { e_MergeOperation eChildrenMergeOp = mfi.m_eMergeOperation; if(eChildrenMergeOp == eConflictingFileTypes) eChildrenMergeOp = eMergeABCToDest; for(int childIdx = 0; childIdx < mfi.m_children.count(); ++childIdx) { calcSuggestedOperation(index(childIdx, 0, mi), eChildrenMergeOp); } } } void DirectoryMergeWindow::compareCurrentFile() { if(!d->canContinue()) return; if(d->m_bRealMergeStarted) { KMessageBox::sorry(this, i18n("This operation is currently not possible."), i18n("Operation Not Possible")); return; } if(MergeFileInfos* pMFI = d->getMFI(currentIndex())) { if(!(pMFI->dirA() || pMFI->dirB() || pMFI->dirC())) { emit startDiffMerge( pMFI->existsInA() ? pMFI->m_pFileInfoA->absoluteFilePath() : QString(""), pMFI->existsInB() ? pMFI->m_pFileInfoB->absoluteFilePath() : QString(""), pMFI->existsInC() ? pMFI->m_pFileInfoC->absoluteFilePath() : QString(""), "", "", "", "", nullptr); } } emit updateAvailabilities(); } void DirectoryMergeWindow::slotCompareExplicitlySelectedFiles() { if(!d->isDir(d->m_selection1Index) && !d->canContinue()) return; if(d->m_bRealMergeStarted) { KMessageBox::sorry(this, i18n("This operation is currently not possible."), i18n("Operation Not Possible")); return; } emit startDiffMerge( d->getFileName(d->m_selection1Index), d->getFileName(d->m_selection2Index), d->getFileName(d->m_selection3Index), "", "", "", "", nullptr); d->m_selection1Index = QModelIndex(); d->m_selection2Index = QModelIndex(); d->m_selection3Index = QModelIndex(); emit updateAvailabilities(); update(); } void DirectoryMergeWindow::slotMergeExplicitlySelectedFiles() { if(!d->isDir(d->m_selection1Index) && !d->canContinue()) return; if(d->m_bRealMergeStarted) { KMessageBox::sorry(this, i18n("This operation is currently not possible."), i18n("Operation Not Possible")); return; } QString fn1 = d->getFileName(d->m_selection1Index); QString fn2 = d->getFileName(d->m_selection2Index); QString fn3 = d->getFileName(d->m_selection3Index); emit startDiffMerge(fn1, fn2, fn3, fn3.isEmpty() ? fn2 : fn3, "", "", "", nullptr); d->m_selection1Index = QModelIndex(); d->m_selection2Index = QModelIndex(); d->m_selection3Index = QModelIndex(); emit updateAvailabilities(); update(); } bool DirectoryMergeWindow::isFileSelected() { if(MergeFileInfos* pMFI = d->getMFI(currentIndex())) { return !(pMFI->dirA() || pMFI->dirB() || pMFI->dirC() || conflictingFileTypes(*pMFI)); } return false; } void DirectoryMergeWindow::mergeResultSaved(const QString& fileName) { QModelIndex mi = (d->m_mergeItemList.empty() || d->m_currentIndexForOperation == d->m_mergeItemList.end()) ? QModelIndex() : *d->m_currentIndexForOperation; MergeFileInfos* pMFI = d->getMFI(mi); if(pMFI == nullptr) { // This can happen if the same file is saved and modified and saved again. Nothing to do then. return; } if(fileName == d->fullNameDest(*pMFI)) { MergeFileInfos& mfi = *pMFI; if(mfi.m_eMergeOperation == eMergeToAB) { bool bSuccess = d->copyFLD(d->fullNameB(mfi), d->fullNameA(mfi)); if(!bSuccess) { KMessageBox::error(this, i18n("An error occurred while copying.")); d->m_pStatusInfo->setWindowTitle(i18n("Merge Error")); d->m_pStatusInfo->exec(); //if ( m_pStatusInfo->firstChild()!=0 ) // m_pStatusInfo->ensureItemVisible( m_pStatusInfo->last() ); d->m_bError = true; d->setOpStatus(mi, eOpStatusError); mfi.m_eMergeOperation = eCopyBToA; return; } } d->setOpStatus(mi, eOpStatusDone); pMFI->m_bOperationComplete = true; if(d->m_mergeItemList.size() == 1) { d->m_mergeItemList.clear(); d->m_bRealMergeStarted = false; } } emit updateAvailabilities(); } bool DirectoryMergeWindow::Data::canContinue() { bool bCanContinue = false; emit q->checkIfCanContinue(&bCanContinue); if(bCanContinue && !m_bError) { QModelIndex mi = (m_mergeItemList.empty() || m_currentIndexForOperation == m_mergeItemList.end()) ? QModelIndex() : *m_currentIndexForOperation; MergeFileInfos* pMFI = getMFI(mi); if(pMFI && !pMFI->m_bOperationComplete) { setOpStatus(mi, eOpStatusNotSaved); pMFI->m_bOperationComplete = true; if(m_mergeItemList.size() == 1) { m_mergeItemList.clear(); m_bRealMergeStarted = false; } } } return bCanContinue; } bool DirectoryMergeWindow::Data::executeMergeOperation(MergeFileInfos& mfi, bool& bSingleFileMerge) { bool bCreateBackups = m_pOptions->m_bDmCreateBakFiles; // First decide destname QString destName; switch(mfi.m_eMergeOperation) { case eNoOperation: break; case eDeleteAB: break; case eMergeToAB: // let the user save in B. In mergeResultSaved() the file will be copied to A. case eMergeToB: case eDeleteB: case eCopyAToB: destName = fullNameB(mfi); break; case eMergeToA: case eDeleteA: case eCopyBToA: destName = fullNameA(mfi); break; case eMergeABToDest: case eMergeABCToDest: case eCopyAToDest: case eCopyBToDest: case eCopyCToDest: case eDeleteFromDest: destName = fullNameDest(mfi); break; default: KMessageBox::error(q, i18n("Unknown merge operation. (This must never happen!)")); } bool bSuccess = false; bSingleFileMerge = false; switch(mfi.m_eMergeOperation) { case eNoOperation: bSuccess = true; break; case eCopyAToDest: case eCopyAToB: bSuccess = copyFLD(fullNameA(mfi), destName); break; case eCopyBToDest: case eCopyBToA: bSuccess = copyFLD(fullNameB(mfi), destName); break; case eCopyCToDest: bSuccess = copyFLD(fullNameC(mfi), destName); break; case eDeleteFromDest: case eDeleteA: case eDeleteB: bSuccess = deleteFLD(destName, bCreateBackups); break; case eDeleteAB: bSuccess = deleteFLD(fullNameA(mfi), bCreateBackups) && deleteFLD(fullNameB(mfi), bCreateBackups); break; case eMergeABToDest: case eMergeToA: case eMergeToAB: case eMergeToB: bSuccess = mergeFLD(fullNameA(mfi), fullNameB(mfi), "", destName, bSingleFileMerge); break; case eMergeABCToDest: bSuccess = mergeFLD( mfi.existsInA() ? fullNameA(mfi) : QString(""), mfi.existsInB() ? fullNameB(mfi) : QString(""), mfi.existsInC() ? fullNameC(mfi) : QString(""), destName, bSingleFileMerge); break; default: KMessageBox::error(q, i18n("Unknown merge operation.")); } return bSuccess; } // Check if the merge can start, and prepare the m_mergeItemList which then contains all // items that must be merged. void DirectoryMergeWindow::Data::prepareMergeStart(const QModelIndex& miBegin, const QModelIndex& miEnd, bool bVerbose) { if(bVerbose) { int status = KMessageBox::warningYesNoCancel(q, i18n("The merge is about to begin.\n\n" "Choose \"Do it\" if you have read the instructions and know what you are doing.\n" "Choosing \"Simulate it\" will tell you what would happen.\n\n" "Be aware that this program still has beta status " "and there is NO WARRANTY whatsoever! Make backups of your vital data!"), i18n("Starting Merge"), KGuiItem(i18n("Do It")), KGuiItem(i18n("Simulate It"))); if(status == KMessageBox::Yes) m_bRealMergeStarted = true; else if(status == KMessageBox::No) m_bSimulatedMergeStarted = true; else return; } else { m_bRealMergeStarted = true; } m_mergeItemList.clear(); if(!miBegin.isValid()) return; for(QModelIndex mi = miBegin; mi != miEnd; mi = treeIterator(mi)) { MergeFileInfos* pMFI = getMFI(mi); if(pMFI && !pMFI->m_bOperationComplete) { m_mergeItemList.push_back(mi); QString errorText; if(pMFI->m_eMergeOperation == eConflictingFileTypes) { errorText = i18n("The highlighted item has a different type in the different directories. Select what to do."); } if(pMFI->m_eMergeOperation == eConflictingAges) { errorText = i18n("The modification dates of the file are equal but the files are not. Select what to do."); } if(pMFI->m_eMergeOperation == eChangedAndDeleted) { errorText = i18n("The highlighted item was changed in one directory and deleted in the other. Select what to do."); } if(!errorText.isEmpty()) { q->scrollTo(mi, QAbstractItemView::EnsureVisible); q->setCurrentIndex(mi); KMessageBox::error(q, errorText); m_mergeItemList.clear(); m_bRealMergeStarted = false; return; } } } m_currentIndexForOperation = m_mergeItemList.begin(); return; } void DirectoryMergeWindow::slotRunOperationForCurrentItem() { if(!d->canContinue()) return; bool bVerbose = false; if(d->m_mergeItemList.empty()) { QModelIndex miBegin = currentIndex(); QModelIndex miEnd = d->treeIterator(miBegin, false, false); // find next visible sibling (no children) d->prepareMergeStart(miBegin, miEnd, bVerbose); d->mergeContinue(true, bVerbose); } else d->mergeContinue(false, bVerbose); } void DirectoryMergeWindow::slotRunOperationForAllItems() { if(!d->canContinue()) return; bool bVerbose = true; if(d->m_mergeItemList.empty()) { QModelIndex miBegin = d->rowCount() > 0 ? d->index(0, 0, QModelIndex()) : QModelIndex(); d->prepareMergeStart(miBegin, QModelIndex(), bVerbose); d->mergeContinue(true, bVerbose); } else d->mergeContinue(false, bVerbose); } void DirectoryMergeWindow::mergeCurrentFile() { if(!d->canContinue()) return; if(d->m_bRealMergeStarted) { KMessageBox::sorry(this, i18n("This operation is currently not possible because directory merge is currently running."), i18n("Operation Not Possible")); return; } if(isFileSelected()) { MergeFileInfos* pMFI = d->getMFI(currentIndex()); if(pMFI != nullptr) { MergeFileInfos& mfi = *pMFI; d->m_mergeItemList.clear(); d->m_mergeItemList.push_back(currentIndex()); d->m_currentIndexForOperation = d->m_mergeItemList.begin(); bool bDummy = false; d->mergeFLD( mfi.existsInA() ? mfi.m_pFileInfoA->absoluteFilePath() : QString(""), mfi.existsInB() ? mfi.m_pFileInfoB->absoluteFilePath() : QString(""), mfi.existsInC() ? mfi.m_pFileInfoC->absoluteFilePath() : QString(""), d->fullNameDest(mfi), bDummy); } } emit updateAvailabilities(); } // When bStart is true then m_currentIndexForOperation must still be processed. // When bVerbose is true then a messagebox will tell when the merge is complete. void DirectoryMergeWindow::Data::mergeContinue(bool bStart, bool bVerbose) { ProgressProxy pp; if(m_mergeItemList.empty()) return; int nrOfItems = 0; int nrOfCompletedItems = 0; int nrOfCompletedSimItems = 0; // Count the number of completed items (for the progress bar). for(MergeItemList::iterator i = m_mergeItemList.begin(); i != m_mergeItemList.end(); ++i) { MergeFileInfos* pMFI = getMFI(*i); ++nrOfItems; if(pMFI->m_bOperationComplete) ++nrOfCompletedItems; if(pMFI->m_bSimOpComplete) ++nrOfCompletedSimItems; } m_pStatusInfo->hide(); m_pStatusInfo->clear(); QModelIndex miCurrent = m_currentIndexForOperation == m_mergeItemList.end() ? QModelIndex() : *m_currentIndexForOperation; bool bContinueWithCurrentItem = bStart; // true for first item, else false bool bSkipItem = false; if(!bStart && m_bError && miCurrent.isValid()) { int status = KMessageBox::warningYesNoCancel(q, i18n("There was an error in the last step.\n" "Do you want to continue with the item that caused the error or do you want to skip this item?"), i18n("Continue merge after an error"), KGuiItem(i18n("Continue With Last Item")), KGuiItem(i18n("Skip Item"))); if(status == KMessageBox::Yes) bContinueWithCurrentItem = true; else if(status == KMessageBox::No) bSkipItem = true; else return; m_bError = false; } pp.setMaxNofSteps(nrOfItems); bool bSuccess = true; bool bSingleFileMerge = false; bool bSim = m_bSimulatedMergeStarted; while(bSuccess) { MergeFileInfos* pMFI = getMFI(miCurrent); if(pMFI == nullptr) { m_mergeItemList.clear(); m_bRealMergeStarted = false; break; } if(pMFI != nullptr && !bContinueWithCurrentItem) { if(bSim) { if(rowCount(miCurrent) == 0) { pMFI->m_bSimOpComplete = true; } } else { if(rowCount(miCurrent) == 0) { if(!pMFI->m_bOperationComplete) { setOpStatus(miCurrent, bSkipItem ? eOpStatusSkipped : eOpStatusDone); pMFI->m_bOperationComplete = true; bSkipItem = false; } } else { setOpStatus(miCurrent, eOpStatusInProgress); } } } if(!bContinueWithCurrentItem) { // Depth first QModelIndex miPrev = miCurrent; ++m_currentIndexForOperation; miCurrent = m_currentIndexForOperation == m_mergeItemList.end() ? QModelIndex() : *m_currentIndexForOperation; if((!miCurrent.isValid() || miCurrent.parent() != miPrev.parent()) && miPrev.parent().isValid()) { // Check if the parent may be set to "Done" QModelIndex miParent = miPrev.parent(); bool bDone = true; while(bDone && miParent.isValid()) { for(int childIdx = 0; childIdx < rowCount(miParent); ++childIdx) { pMFI = getMFI(index(childIdx, 0, miParent)); if((!bSim && !pMFI->m_bOperationComplete) || (bSim && pMFI->m_bSimOpComplete)) { bDone = false; break; } } if(bDone) { pMFI = getMFI(miParent); if(bSim) pMFI->m_bSimOpComplete = bDone; else { setOpStatus(miParent, eOpStatusDone); pMFI->m_bOperationComplete = bDone; } } miParent = miParent.parent(); } } } if(!miCurrent.isValid()) // end? { if(m_bRealMergeStarted) { if(bVerbose) { KMessageBox::information(q, i18n("Merge operation complete."), i18n("Merge Complete")); } m_bRealMergeStarted = false; m_pStatusInfo->setWindowTitle(i18n("Merge Complete")); } if(m_bSimulatedMergeStarted) { m_bSimulatedMergeStarted = false; QModelIndex mi = rowCount() > 0 ? index(0, 0, QModelIndex()) : QModelIndex(); for(; mi.isValid(); mi = treeIterator(mi)) { getMFI(mi)->m_bSimOpComplete = false; } m_pStatusInfo->setWindowTitle(i18n("Simulated merge complete: Check if you agree with the proposed operations.")); m_pStatusInfo->exec(); } m_mergeItemList.clear(); m_bRealMergeStarted = false; return; } MergeFileInfos& mfi = *getMFI(miCurrent); pp.setInformation(mfi.subPath(), bSim ? nrOfCompletedSimItems : nrOfCompletedItems, false // bRedrawUpdate ); bSuccess = executeMergeOperation(mfi, bSingleFileMerge); // Here the real operation happens. if(bSuccess) { if(bSim) ++nrOfCompletedSimItems; else ++nrOfCompletedItems; bContinueWithCurrentItem = false; } if(pp.wasCancelled()) break; } // end while //g_pProgressDialog->hide(); q->setCurrentIndex(miCurrent); q->scrollTo(miCurrent, EnsureVisible); if(!bSuccess && !bSingleFileMerge) { KMessageBox::error(q, i18n("An error occurred. Press OK to see detailed information.")); m_pStatusInfo->setWindowTitle(i18n("Merge Error")); m_pStatusInfo->exec(); //if ( m_pStatusInfo->firstChild()!=0 ) // m_pStatusInfo->ensureItemVisible( m_pStatusInfo->last() ); m_bError = true; setOpStatus(miCurrent, eOpStatusError); } else { m_bError = false; } emit q->updateAvailabilities(); if(m_currentIndexForOperation == m_mergeItemList.end()) { m_mergeItemList.clear(); m_bRealMergeStarted = false; } } bool DirectoryMergeWindow::Data::deleteFLD(const QString& name, bool bCreateBackup) { FileAccess fi(name, true); if(!fi.exists()) return true; if(bCreateBackup) { bool bSuccess = renameFLD(name, name + ".orig"); if(!bSuccess) { m_pStatusInfo->addText(i18n("Error: While deleting %1: Creating backup failed.", name)); return false; } } else { if(fi.isDir() && !fi.isSymLink()) m_pStatusInfo->addText(i18n("delete directory recursively( %1 )", name)); else m_pStatusInfo->addText(i18n("delete( %1 )", name)); if(m_bSimulatedMergeStarted) { return true; } if(fi.isDir() && !fi.isSymLink()) // recursive directory delete only for real dirs, not symlinks { t_DirectoryList dirList; bool bSuccess = fi.listDir(&dirList, false, true, "*", "", "", false, false); // not recursive, find hidden files if(!bSuccess) { // No Permission to read directory or other error. m_pStatusInfo->addText(i18n("Error: delete dir operation failed while trying to read the directory.")); return false; } t_DirectoryList::iterator it; // create list iterator for(it = dirList.begin(); it != dirList.end(); ++it) // for each file... { FileAccess& fi2 = *it; if(fi2.fileName() == "." || fi2.fileName() == "..") continue; bSuccess = deleteFLD(fi2.absoluteFilePath(), false); if(!bSuccess) break; } if(bSuccess) { bSuccess = FileAccess::removeDir(name); if(!bSuccess) { m_pStatusInfo->addText(i18n("Error: rmdir( %1 ) operation failed.", name)); return false; } } } else { bool bSuccess = FileAccess::removeFile(name); if(!bSuccess) { m_pStatusInfo->addText(i18n("Error: delete operation failed.")); return false; } } } return true; } bool DirectoryMergeWindow::Data::mergeFLD(const QString& nameA, const QString& nameB, const QString& nameC, const QString& nameDest, bool& bSingleFileMerge) { FileAccess fi(nameA); if(fi.isDir()) { return makeDir(nameDest); } // Make sure that the dir exists, into which we will save the file later. int pos = nameDest.lastIndexOf('/'); if(pos > 0) { QString parentName = nameDest.left(pos); bool bSuccess = makeDir(parentName, true /*quiet*/); if(!bSuccess) return false; } m_pStatusInfo->addText(i18n("manual merge( %1, %2, %3 -> %4)", nameA, nameB, nameC, nameDest)); if(m_bSimulatedMergeStarted) { m_pStatusInfo->addText(i18n(" Note: After a manual merge the user should continue by pressing F7.")); return true; } bSingleFileMerge = true; setOpStatus(*m_currentIndexForOperation, eOpStatusInProgress); q->scrollTo(*m_currentIndexForOperation, EnsureVisible); emit q->startDiffMerge(nameA, nameB, nameC, nameDest, "", "", "", nullptr); return false; } bool DirectoryMergeWindow::Data::copyFLD(const QString& srcName, const QString& destName) { bool bSuccess = false; if(srcName == destName) return true; FileAccess fi(srcName); FileAccess faDest(destName, true); if(faDest.exists() && !(fi.isDir() && faDest.isDir() && (fi.isSymLink() == faDest.isSymLink()))) { bSuccess = deleteFLD(destName, m_pOptions->m_bDmCreateBakFiles); if(!bSuccess) { m_pStatusInfo->addText(i18n("Error: copy( %1 -> %2 ) failed." "Deleting existing destination failed.", srcName, destName)); return bSuccess; } } if(fi.isSymLink() && ((fi.isDir() && !m_bFollowDirLinks) || (!fi.isDir() && !m_bFollowFileLinks))) { m_pStatusInfo->addText(i18n("copyLink( %1 -> %2 )", srcName, destName)); if(m_bSimulatedMergeStarted) { return true; } FileAccess destFi(destName); if(!destFi.isLocal() || !fi.isLocal()) { m_pStatusInfo->addText(i18n("Error: copyLink failed: Remote links are not yet supported.")); return false; } bSuccess = false; QString linkTarget = fi.readLink(); if(!linkTarget.isEmpty()) { bSuccess = FileAccess::symLink(linkTarget, destName); if(!bSuccess) m_pStatusInfo->addText(i18n("Error: copyLink failed.")); } return bSuccess; } if(fi.isDir()) { if(faDest.exists()) return true; else { bSuccess = makeDir(destName); return bSuccess; } } int pos = destName.lastIndexOf('/'); if(pos > 0) { QString parentName = destName.left(pos); bSuccess = makeDir(parentName, true /*quiet*/); if(!bSuccess) return false; } m_pStatusInfo->addText(i18n("copy( %1 -> %2 )", srcName, destName)); if(m_bSimulatedMergeStarted) { return true; } FileAccess faSrc(srcName); bSuccess = faSrc.copyFile(destName); if(!bSuccess) m_pStatusInfo->addText(faSrc.getStatusText()); return bSuccess; } // Rename is not an operation that can be selected by the user. // It will only be used to create backups. // Hence it will delete an existing destination without making a backup (of the old backup.) bool DirectoryMergeWindow::Data::renameFLD(const QString& srcName, const QString& destName) { if(srcName == destName) return true; if(FileAccess(destName, true).exists()) { bool bSuccess = deleteFLD(destName, false /*no backup*/); if(!bSuccess) { m_pStatusInfo->addText(i18n("Error during rename( %1 -> %2 ): " "Cannot delete existing destination.", srcName, destName)); return false; } } m_pStatusInfo->addText(i18n("rename( %1 -> %2 )", srcName, destName)); if(m_bSimulatedMergeStarted) { return true; } bool bSuccess = FileAccess(srcName).rename(destName); if(!bSuccess) { m_pStatusInfo->addText(i18n("Error: Rename failed.")); return false; } return true; } bool DirectoryMergeWindow::Data::makeDir(const QString& name, bool bQuiet) { FileAccess fi(name, true); if(fi.exists() && fi.isDir()) return true; if(fi.exists() && !fi.isDir()) { bool bSuccess = deleteFLD(name, true); if(!bSuccess) { m_pStatusInfo->addText(i18n("Error during makeDir of %1. " "Cannot delete existing file.", name)); return false; } } int pos = name.lastIndexOf('/'); if(pos > 0) { QString parentName = name.left(pos); bool bSuccess = makeDir(parentName, true); if(!bSuccess) return false; } if(!bQuiet) m_pStatusInfo->addText(i18n("makeDir( %1 )", name)); if(m_bSimulatedMergeStarted) { return true; } bool bSuccess = FileAccess::makeDir(name); if(bSuccess == false) { m_pStatusInfo->addText(i18n("Error while creating directory.")); return false; } return true; } DirectoryMergeInfo::DirectoryMergeInfo(QWidget* pParent) : QFrame(pParent) { QVBoxLayout* topLayout = new QVBoxLayout(this); topLayout->setMargin(0); QGridLayout* grid = new QGridLayout(); topLayout->addLayout(grid); grid->setColumnStretch(1, 10); int line = 0; m_pA = new QLabel(i18n("A"), this); grid->addWidget(m_pA, line, 0); m_pInfoA = new QLabel(this); grid->addWidget(m_pInfoA, line, 1); ++line; m_pB = new QLabel(i18n("B"), this); grid->addWidget(m_pB, line, 0); m_pInfoB = new QLabel(this); grid->addWidget(m_pInfoB, line, 1); ++line; m_pC = new QLabel(i18n("C"), this); grid->addWidget(m_pC, line, 0); m_pInfoC = new QLabel(this); grid->addWidget(m_pInfoC, line, 1); ++line; m_pDest = new QLabel(i18n("Dest"), this); grid->addWidget(m_pDest, line, 0); m_pInfoDest = new QLabel(this); grid->addWidget(m_pInfoDest, line, 1); ++line; m_pInfoList = new QTreeWidget(this); topLayout->addWidget(m_pInfoList); m_pInfoList->setHeaderLabels(QStringList() << i18n("Dir") << i18n("Type") << i18n("Size") << i18n("Attr") << i18n("Last Modification") << i18n("Link-Destination")); setMinimumSize(100, 100); m_pInfoList->installEventFilter(this); m_pInfoList->setRootIsDecorated(false); } bool DirectoryMergeInfo::eventFilter(QObject* o, QEvent* e) { if(e->type() == QEvent::FocusIn && o == m_pInfoList) emit gotFocus(); return false; } void DirectoryMergeInfo::addListViewItem(const QString& dir, const QString& basePath, FileAccess* fi) { if(basePath.isEmpty()) { return; } else { if(fi != nullptr && fi->exists()) { QString dateString = fi->lastModified().toString("yyyy-MM-dd hh:mm:ss"); //TODO: Move logic to FileAccess m_pInfoList->addTopLevelItem( new QTreeWidgetItem( m_pInfoList, QStringList() << dir << QString(fi->isDir() ? i18n("Dir") : i18n("File")) + (fi->isSymLink() ? i18n("-Link") : "") << QString::number(fi->size()) << QLatin1String(fi->isReadable() ? "r" : " ") + QLatin1String(fi->isWritable() ? "w" : " ") + QLatin1String((fi->isExecutable() ? "x" : " ")) << dateString << QString(fi->isSymLink() ? (" -> " + fi->readLink()) : QString(""))) ); } else { m_pInfoList->addTopLevelItem(new QTreeWidgetItem( m_pInfoList, QStringList() << dir << i18n("not available") << "" << "" << "" << "")); } } } void DirectoryMergeInfo::setInfo( const FileAccess& dirA, const FileAccess& dirB, const FileAccess& dirC, const FileAccess& dirDest, MergeFileInfos& mfi) { bool bHideDest = false; if(dirA.absoluteFilePath() == dirDest.absoluteFilePath()) { m_pA->setText(i18n("A (Dest): ")); bHideDest = true; } else m_pA->setText(!dirC.isValid() ? QString("A: ") : i18n("A (Base): ")); m_pInfoA->setText(dirA.prettyAbsPath()); if(dirB.absoluteFilePath() == dirDest.absoluteFilePath()) { m_pB->setText(i18n("B (Dest): ")); bHideDest = true; } else m_pB->setText("B: "); m_pInfoB->setText(dirB.prettyAbsPath()); if(dirC.absoluteFilePath() == dirDest.absoluteFilePath()) { m_pC->setText(i18n("C (Dest): ")); bHideDest = true; } else m_pC->setText("C: "); m_pInfoC->setText(dirC.prettyAbsPath()); m_pDest->setText(i18n("Dest: ")); m_pInfoDest->setText(dirDest.prettyAbsPath()); if(!dirC.isValid()) { m_pC->hide(); m_pInfoC->hide(); } else { m_pC->show(); m_pInfoC->show(); } if(!dirDest.isValid() || bHideDest) { m_pDest->hide(); m_pInfoDest->hide(); } else { m_pDest->show(); m_pInfoDest->show(); } m_pInfoList->clear(); addListViewItem(i18n("A"), dirA.prettyAbsPath(), mfi.m_pFileInfoA); addListViewItem(i18n("B"), dirB.prettyAbsPath(), mfi.m_pFileInfoB); addListViewItem(i18n("C"), dirC.prettyAbsPath(), mfi.m_pFileInfoC); if(!bHideDest) { FileAccess fiDest(dirDest.prettyAbsPath() + "/" + mfi.subPath(), true); addListViewItem(i18n("Dest"), dirDest.prettyAbsPath(), &fiDest); } for(int i = 0; i < m_pInfoList->columnCount(); ++i) m_pInfoList->resizeColumnToContents(i); } QTextStream& operator<<(QTextStream& ts, MergeFileInfos& mfi) { ts << "{\n"; ValueMap vm; vm.writeEntry("SubPath", mfi.subPath()); vm.writeEntry("ExistsInA", mfi.existsInA()); vm.writeEntry("ExistsInB", mfi.existsInB()); vm.writeEntry("ExistsInC", mfi.existsInC()); vm.writeEntry("EqualAB", mfi.m_bEqualAB); vm.writeEntry("EqualAC", mfi.m_bEqualAC); vm.writeEntry("EqualBC", mfi.m_bEqualBC); - //DirMergeItem* m_pDMI; - //MergeFileInfos* m_pParent; + vm.writeEntry("MergeOperation", (int)mfi.m_eMergeOperation); vm.writeEntry("DirA", mfi.dirA()); vm.writeEntry("DirB", mfi.dirB()); vm.writeEntry("DirC", mfi.dirC()); vm.writeEntry("LinkA", mfi.isLinkA()); vm.writeEntry("LinkB", mfi.isLinkB()); vm.writeEntry("LinkC", mfi.isLinkC()); vm.writeEntry("OperationComplete", mfi.m_bOperationComplete); - //bool m_bSimOpComplete ); vm.writeEntry("AgeA", (int)mfi.m_ageA); vm.writeEntry("AgeB", (int)mfi.m_ageB); vm.writeEntry("AgeC", (int)mfi.m_ageC); vm.writeEntry("ConflictingAges", mfi.m_bConflictingAges); // Equal age but files are not! - //FileAccess m_fileInfoA; - //FileAccess m_fileInfoB; - //FileAccess m_fileInfoC; - - //TotalDiffStatus m_totalDiffStatus; - vm.save(ts); ts << "}\n"; return ts; } void DirectoryMergeWindow::slotSaveMergeState() { //slotStatusMsg(i18n("Saving Directory Merge State ...")); - //QString s = KFileDialog::getSaveUrl( QDir::currentPath(), 0, this, i18n("Save As...") ).url(); QString s = QFileDialog::getSaveFileName(this, i18n("Save Directory Merge State As..."), QDir::currentPath()); if(!s.isEmpty()) { d->m_dirMergeStateFilename = s; QFile file(d->m_dirMergeStateFilename); bool bSuccess = file.open(QIODevice::WriteOnly); if(bSuccess) { QTextStream ts(&file); QModelIndex mi(d->index(0, 0, QModelIndex())); while(mi.isValid()) { MergeFileInfos* pMFI = d->getMFI(mi); ts << *pMFI; mi = d->treeIterator(mi, true, true); } } } //slotStatusMsg(i18n("Ready.")); } void DirectoryMergeWindow::slotLoadMergeState() { } void DirectoryMergeWindow::updateFileVisibilities() { bool bShowIdentical = d->m_pDirShowIdenticalFiles->isChecked(); bool bShowDifferent = d->m_pDirShowDifferentFiles->isChecked(); bool bShowOnlyInA = d->m_pDirShowFilesOnlyInA->isChecked(); bool bShowOnlyInB = d->m_pDirShowFilesOnlyInB->isChecked(); bool bShowOnlyInC = d->m_pDirShowFilesOnlyInC->isChecked(); bool bThreeDirs = d->m_dirC.isValid(); d->m_selection1Index = QModelIndex(); d->m_selection2Index = QModelIndex(); d->m_selection3Index = QModelIndex(); // in first run set all dirs to equal and determine if they are not equal. // on second run don't change the equal-status anymore; it is needed to // set the visibility (when bShowIdentical is false). for(int loop = 0; loop < 2; ++loop) { QModelIndex mi = d->rowCount() > 0 ? d->index(0, 0, QModelIndex()) : QModelIndex(); while(mi.isValid()) { MergeFileInfos* pMFI = d->getMFI(mi); bool bDir = pMFI->dirA() || pMFI->dirB() || pMFI->dirC(); if(loop == 0 && bDir) { bool bChange = false; if(!pMFI->m_bEqualAB && pMFI->dirA() == pMFI->dirB() && pMFI->isLinkA() == pMFI->isLinkB()) { pMFI->m_bEqualAB = true; bChange = true; } if(!pMFI->m_bEqualBC && pMFI->dirC() == pMFI->dirB() && pMFI->isLinkC() == pMFI->isLinkB()) { pMFI->m_bEqualBC = true; bChange = true; } if(!pMFI->m_bEqualAC && pMFI->dirA() == pMFI->dirC() && pMFI->isLinkA() == pMFI->isLinkC()) { pMFI->m_bEqualAC = true; bChange = true; } if(bChange) setPixmaps(*pMFI, bThreeDirs); } bool bExistsEverywhere = pMFI->existsInA() && pMFI->existsInB() && (pMFI->existsInC() || !bThreeDirs); int existCount = int(pMFI->existsInA()) + int(pMFI->existsInB()) + int(pMFI->existsInC()); bool bVisible = (bShowIdentical && bExistsEverywhere && pMFI->m_bEqualAB && (pMFI->m_bEqualAC || !bThreeDirs)) || ((bShowDifferent || bDir) && existCount >= 2 && (!pMFI->m_bEqualAB || !(pMFI->m_bEqualAC || !bThreeDirs))) || (bShowOnlyInA && pMFI->existsInA() && !pMFI->existsInB() && !pMFI->existsInC()) || (bShowOnlyInB && !pMFI->existsInA() && pMFI->existsInB() && !pMFI->existsInC()) || (bShowOnlyInC && !pMFI->existsInA() && !pMFI->existsInB() && pMFI->existsInC()); QString fileName = pMFI->fileName(); bVisible = bVisible && ((bDir && !wildcardMultiMatch(d->m_pOptions->m_DmDirAntiPattern, fileName, d->m_bCaseSensitive)) || (wildcardMultiMatch(d->m_pOptions->m_DmFilePattern, fileName, d->m_bCaseSensitive) && !wildcardMultiMatch(d->m_pOptions->m_DmFileAntiPattern, fileName, d->m_bCaseSensitive))); setRowHidden(mi.row(), mi.parent(), !bVisible); bool bEqual = bThreeDirs ? pMFI->m_bEqualAB && pMFI->m_bEqualAC : pMFI->m_bEqualAB; if(!bEqual && bVisible && loop == 0) // Set all parents to "not equal" { MergeFileInfos* p2 = pMFI->m_pParent; while(p2 != nullptr) { bool bChange = false; if(!pMFI->m_bEqualAB && p2->m_bEqualAB) { p2->m_bEqualAB = false; bChange = true; } if(!pMFI->m_bEqualAC && p2->m_bEqualAC) { p2->m_bEqualAC = false; bChange = true; } if(!pMFI->m_bEqualBC && p2->m_bEqualBC) { p2->m_bEqualBC = false; bChange = true; } if(bChange) setPixmaps(*p2, bThreeDirs); else break; p2 = p2->m_pParent; } } mi = d->treeIterator(mi, true, true); } } } void DirectoryMergeWindow::slotShowIdenticalFiles() { d->m_pOptions->m_bDmShowIdenticalFiles = d->m_pDirShowIdenticalFiles->isChecked(); updateFileVisibilities(); } void DirectoryMergeWindow::slotShowDifferentFiles() { updateFileVisibilities(); } void DirectoryMergeWindow::slotShowFilesOnlyInA() { updateFileVisibilities(); } void DirectoryMergeWindow::slotShowFilesOnlyInB() { updateFileVisibilities(); } void DirectoryMergeWindow::slotShowFilesOnlyInC() { updateFileVisibilities(); } void DirectoryMergeWindow::slotSynchronizeDirectories() {} void DirectoryMergeWindow::slotChooseNewerFiles() {} void DirectoryMergeWindow::initDirectoryMergeActions(QObject* pKDiff3App, KActionCollection* ac) { #include "xpm/showequalfiles.xpm" #include "xpm/showfilesonlyina.xpm" #include "xpm/showfilesonlyinb.xpm" #include "xpm/showfilesonlyinc.xpm" #include "xpm/startmerge.xpm" DirectoryMergeWindow* p = this; d->m_pDirStartOperation = KDiff3::createAction(i18n("Start/Continue Directory Merge"), QKeySequence(Qt::Key_F7), p, SLOT(slotRunOperationForAllItems()), ac, "dir_start_operation"); d->m_pDirRunOperationForCurrentItem = KDiff3::createAction(i18n("Run Operation for Current Item"), QKeySequence(Qt::Key_F6), p, SLOT(slotRunOperationForCurrentItem()), ac, "dir_run_operation_for_current_item"); d->m_pDirCompareCurrent = KDiff3::createAction(i18n("Compare Selected File"), p, SLOT(compareCurrentFile()), ac, "dir_compare_current"); d->m_pDirMergeCurrent = KDiff3::createAction(i18n("Merge Current File"), QIcon(QPixmap(startmerge)), i18n("Merge\nFile"), pKDiff3App, SLOT(slotMergeCurrentFile()), ac, "merge_current"); d->m_pDirFoldAll = KDiff3::createAction(i18n("Fold All Subdirs"), p, SLOT(collapseAll()), ac, "dir_fold_all"); d->m_pDirUnfoldAll = KDiff3::createAction(i18n("Unfold All Subdirs"), p, SLOT(expandAll()), ac, "dir_unfold_all"); d->m_pDirRescan = KDiff3::createAction(i18n("Rescan"), QKeySequence(Qt::SHIFT + Qt::Key_F5), p, SLOT(reload()), ac, "dir_rescan"); d->m_pDirSaveMergeState = nullptr; //KDiff3::createAction< QAction >(i18n("Save Directory Merge State ..."), 0, p, SLOT(slotSaveMergeState()), ac, "dir_save_merge_state"); d->m_pDirLoadMergeState = nullptr; //KDiff3::createAction< QAction >(i18n("Load Directory Merge State ..."), 0, p, SLOT(slotLoadMergeState()), ac, "dir_load_merge_state"); d->m_pDirChooseAEverywhere = KDiff3::createAction(i18n("Choose A for All Items"), p, SLOT(slotChooseAEverywhere()), ac, "dir_choose_a_everywhere"); d->m_pDirChooseBEverywhere = KDiff3::createAction(i18n("Choose B for All Items"), p, SLOT(slotChooseBEverywhere()), ac, "dir_choose_b_everywhere"); d->m_pDirChooseCEverywhere = KDiff3::createAction(i18n("Choose C for All Items"), p, SLOT(slotChooseCEverywhere()), ac, "dir_choose_c_everywhere"); d->m_pDirAutoChoiceEverywhere = KDiff3::createAction(i18n("Auto-Choose Operation for All Items"), p, SLOT(slotAutoChooseEverywhere()), ac, "dir_autochoose_everywhere"); d->m_pDirDoNothingEverywhere = KDiff3::createAction(i18n("No Operation for All Items"), p, SLOT(slotNoOpEverywhere()), ac, "dir_nothing_everywhere"); // d->m_pDirSynchronizeDirectories = KDiff3::createAction< KToggleAction >(i18n("Synchronize Directories"), 0, this, SLOT(slotSynchronizeDirectories()), ac, "dir_synchronize_directories"); // d->m_pDirChooseNewerFiles = KDiff3::createAction< KToggleAction >(i18n("Copy Newer Files Instead of Merging"), 0, this, SLOT(slotChooseNewerFiles()), ac, "dir_choose_newer_files"); d->m_pDirShowIdenticalFiles = KDiff3::createAction(i18n("Show Identical Files"), QIcon(QPixmap(showequalfiles)), i18n("Identical\nFiles"), this, SLOT(slotShowIdenticalFiles()), ac, "dir_show_identical_files"); d->m_pDirShowDifferentFiles = KDiff3::createAction(i18n("Show Different Files"), this, SLOT(slotShowDifferentFiles()), ac, "dir_show_different_files"); d->m_pDirShowFilesOnlyInA = KDiff3::createAction(i18n("Show Files only in A"), QIcon(QPixmap(showfilesonlyina)), i18n("Files\nonly in A"), this, SLOT(slotShowFilesOnlyInA()), ac, "dir_show_files_only_in_a"); d->m_pDirShowFilesOnlyInB = KDiff3::createAction(i18n("Show Files only in B"), QIcon(QPixmap(showfilesonlyinb)), i18n("Files\nonly in B"), this, SLOT(slotShowFilesOnlyInB()), ac, "dir_show_files_only_in_b"); d->m_pDirShowFilesOnlyInC = KDiff3::createAction(i18n("Show Files only in C"), QIcon(QPixmap(showfilesonlyinc)), i18n("Files\nonly in C"), this, SLOT(slotShowFilesOnlyInC()), ac, "dir_show_files_only_in_c"); d->m_pDirShowIdenticalFiles->setChecked(d->m_pOptions->m_bDmShowIdenticalFiles); d->m_pDirCompareExplicit = KDiff3::createAction(i18n("Compare Explicitly Selected Files"), p, SLOT(slotCompareExplicitlySelectedFiles()), ac, "dir_compare_explicitly_selected_files"); d->m_pDirMergeExplicit = KDiff3::createAction(i18n("Merge Explicitly Selected Files"), p, SLOT(slotMergeExplicitlySelectedFiles()), ac, "dir_merge_explicitly_selected_files"); d->m_pDirCurrentDoNothing = KDiff3::createAction(i18n("Do Nothing"), p, SLOT(slotCurrentDoNothing()), ac, "dir_current_do_nothing"); d->m_pDirCurrentChooseA = KDiff3::createAction(i18n("A"), p, SLOT(slotCurrentChooseA()), ac, "dir_current_choose_a"); d->m_pDirCurrentChooseB = KDiff3::createAction(i18n("B"), p, SLOT(slotCurrentChooseB()), ac, "dir_current_choose_b"); d->m_pDirCurrentChooseC = KDiff3::createAction(i18n("C"), p, SLOT(slotCurrentChooseC()), ac, "dir_current_choose_c"); d->m_pDirCurrentMerge = KDiff3::createAction(i18n("Merge"), p, SLOT(slotCurrentMerge()), ac, "dir_current_merge"); d->m_pDirCurrentDelete = KDiff3::createAction(i18n("Delete (if exists)"), p, SLOT(slotCurrentDelete()), ac, "dir_current_delete"); d->m_pDirCurrentSyncDoNothing = KDiff3::createAction(i18n("Do Nothing"), p, SLOT(slotCurrentDoNothing()), ac, "dir_current_sync_do_nothing"); d->m_pDirCurrentSyncCopyAToB = KDiff3::createAction(i18n("Copy A to B"), p, SLOT(slotCurrentCopyAToB()), ac, "dir_current_sync_copy_a_to_b"); d->m_pDirCurrentSyncCopyBToA = KDiff3::createAction(i18n("Copy B to A"), p, SLOT(slotCurrentCopyBToA()), ac, "dir_current_sync_copy_b_to_a"); d->m_pDirCurrentSyncDeleteA = KDiff3::createAction(i18n("Delete A"), p, SLOT(slotCurrentDeleteA()), ac, "dir_current_sync_delete_a"); d->m_pDirCurrentSyncDeleteB = KDiff3::createAction(i18n("Delete B"), p, SLOT(slotCurrentDeleteB()), ac, "dir_current_sync_delete_b"); d->m_pDirCurrentSyncDeleteAAndB = KDiff3::createAction(i18n("Delete A && B"), p, SLOT(slotCurrentDeleteAAndB()), ac, "dir_current_sync_delete_a_and_b"); d->m_pDirCurrentSyncMergeToA = KDiff3::createAction(i18n("Merge to A"), p, SLOT(slotCurrentMergeToA()), ac, "dir_current_sync_merge_to_a"); d->m_pDirCurrentSyncMergeToB = KDiff3::createAction(i18n("Merge to B"), p, SLOT(slotCurrentMergeToB()), ac, "dir_current_sync_merge_to_b"); d->m_pDirCurrentSyncMergeToAAndB = KDiff3::createAction(i18n("Merge to A && B"), p, SLOT(slotCurrentMergeToAAndB()), ac, "dir_current_sync_merge_to_a_and_b"); } void DirectoryMergeWindow::updateAvailabilities(bool bDirCompare, bool bDiffWindowVisible, KToggleAction* chooseA, KToggleAction* chooseB, KToggleAction* chooseC) { d->m_pDirStartOperation->setEnabled(bDirCompare); d->m_pDirRunOperationForCurrentItem->setEnabled(bDirCompare); d->m_pDirFoldAll->setEnabled(bDirCompare); d->m_pDirUnfoldAll->setEnabled(bDirCompare); d->m_pDirCompareCurrent->setEnabled(bDirCompare && isVisible() && isFileSelected()); d->m_pDirMergeCurrent->setEnabled((bDirCompare && isVisible() && isFileSelected()) || bDiffWindowVisible); d->m_pDirRescan->setEnabled(bDirCompare); d->m_pDirAutoChoiceEverywhere->setEnabled(bDirCompare && isVisible()); d->m_pDirDoNothingEverywhere->setEnabled(bDirCompare && isVisible()); d->m_pDirChooseAEverywhere->setEnabled(bDirCompare && isVisible()); d->m_pDirChooseBEverywhere->setEnabled(bDirCompare && isVisible()); d->m_pDirChooseCEverywhere->setEnabled(bDirCompare && isVisible()); bool bThreeDirs = d->m_dirC.isValid(); MergeFileInfos* pMFI = d->getMFI(currentIndex()); bool bItemActive = bDirCompare && isVisible() && pMFI != nullptr; // && hasFocus(); bool bMergeMode = bThreeDirs || !d->m_bSyncMode; bool bFTConflict = pMFI == nullptr ? false : conflictingFileTypes(*pMFI); bool bDirWindowHasFocus = isVisible() && hasFocus(); d->m_pDirShowIdenticalFiles->setEnabled(bDirCompare && isVisible()); d->m_pDirShowDifferentFiles->setEnabled(bDirCompare && isVisible()); d->m_pDirShowFilesOnlyInA->setEnabled(bDirCompare && isVisible()); d->m_pDirShowFilesOnlyInB->setEnabled(bDirCompare && isVisible()); d->m_pDirShowFilesOnlyInC->setEnabled(bDirCompare && isVisible() && bThreeDirs); d->m_pDirCompareExplicit->setEnabled(bDirCompare && isVisible() && d->m_selection2Index.isValid()); d->m_pDirMergeExplicit->setEnabled(bDirCompare && isVisible() && d->m_selection2Index.isValid()); d->m_pDirCurrentDoNothing->setEnabled(bItemActive && bMergeMode); d->m_pDirCurrentChooseA->setEnabled(bItemActive && bMergeMode && pMFI->existsInA()); d->m_pDirCurrentChooseB->setEnabled(bItemActive && bMergeMode && pMFI->existsInB()); d->m_pDirCurrentChooseC->setEnabled(bItemActive && bMergeMode && pMFI->existsInC()); d->m_pDirCurrentMerge->setEnabled(bItemActive && bMergeMode && !bFTConflict); d->m_pDirCurrentDelete->setEnabled(bItemActive && bMergeMode); if(bDirWindowHasFocus) { chooseA->setEnabled(bItemActive && pMFI->existsInA()); chooseB->setEnabled(bItemActive && pMFI->existsInB()); chooseC->setEnabled(bItemActive && pMFI->existsInC()); chooseA->setChecked(false); chooseB->setChecked(false); chooseC->setChecked(false); } d->m_pDirCurrentSyncDoNothing->setEnabled(bItemActive && !bMergeMode); d->m_pDirCurrentSyncCopyAToB->setEnabled(bItemActive && !bMergeMode && pMFI->existsInA()); d->m_pDirCurrentSyncCopyBToA->setEnabled(bItemActive && !bMergeMode && pMFI->existsInB()); d->m_pDirCurrentSyncDeleteA->setEnabled(bItemActive && !bMergeMode && pMFI->existsInA()); d->m_pDirCurrentSyncDeleteB->setEnabled(bItemActive && !bMergeMode && pMFI->existsInB()); d->m_pDirCurrentSyncDeleteAAndB->setEnabled(bItemActive && !bMergeMode && pMFI->existsInA() && pMFI->existsInB()); d->m_pDirCurrentSyncMergeToA->setEnabled(bItemActive && !bMergeMode && !bFTConflict); d->m_pDirCurrentSyncMergeToB->setEnabled(bItemActive && !bMergeMode && !bFTConflict); d->m_pDirCurrentSyncMergeToAAndB->setEnabled(bItemActive && !bMergeMode && !bFTConflict); } //#include "directorymergewindow.moc" diff --git a/src/kdiff3.cpp b/src/kdiff3.cpp index 65e6a10..e7000f9 100644 --- a/src/kdiff3.cpp +++ b/src/kdiff3.cpp @@ -1,1096 +1,1092 @@ /*************************************************************************** kdiff3.cpp - description ------------------- begin : Don Jul 11 12:31:29 CEST 2002 copyright : (C) 2002-2007 by Joachim Eibl email : joachim.eibl at gmx.de ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "difftextwindow.h" #include "mergeresultwindow.h" #include // include files for QT #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // include files for KDE #include #include #include #include //#include #include #include #include #include // application specific includes #include "directorymergewindow.h" #include "fileaccess.h" #include "guiutils.h" // namespace KDiff3 #include "kdiff3.h" #include "kdiff3_part.h" #include "kdiff3_shell.h" #include "optiondialog.h" #include "progress.h" #include "smalldialogs.h" #define ID_STATUS_MSG 1 #define MAIN_TOOLBAR_NAME QLatin1String("mainToolBar") KActionCollection* KDiff3App::actionCollection() { if(m_pKDiff3Shell == nullptr) return m_pKDiff3Part->actionCollection(); else return m_pKDiff3Shell->actionCollection(); } QStatusBar* KDiff3App::statusBar() { if(m_pKDiff3Shell == nullptr) return nullptr; else return m_pKDiff3Shell->statusBar(); } KToolBar* KDiff3App::toolBar(const QLatin1String toolBarId) { if(m_pKDiff3Shell == nullptr) return nullptr; else return m_pKDiff3Shell->toolBar(toolBarId); } bool KDiff3App::isPart() { return m_pKDiff3Shell == nullptr; } bool KDiff3App::isFileSaved() { return m_bFileSaved; } bool KDiff3App::isDirComparison() { return m_bDirCompare; } KDiff3App::KDiff3App(QWidget* pParent, const QString /*name*/, KDiff3Part* pKDiff3Part) : QSplitter(pParent) //previously KMainWindow { setObjectName("KDiff3App"); m_pKDiff3Part = pKDiff3Part; m_pKDiff3Shell = qobject_cast(pParent); setWindowTitle("KDiff3"); setOpaqueResize(false); // faster resizing setUpdatesEnabled(false); // set Disabled to same color as enabled to prevent flicker in DirectoryMergeWindow QPalette pal; pal.setBrush(QPalette::Base, pal.brush(QPalette::Active, QPalette::Base)); pal.setColor(QPalette::Text, pal.color(QPalette::Active, QPalette::Text)); setPalette(pal); m_pMainSplitter = nullptr; m_pDirectoryMergeSplitter = nullptr; m_pDirectoryMergeWindow = nullptr; m_pCornerWidget = nullptr; m_pMainWidget = nullptr; m_pDiffTextWindow1 = nullptr; m_pDiffTextWindow2 = nullptr; m_pDiffTextWindow3 = nullptr; m_pDiffTextWindowFrame1 = nullptr; m_pDiffTextWindowFrame2 = nullptr; m_pDiffTextWindowFrame3 = nullptr; m_pDiffWindowSplitter = nullptr; m_pOverview = nullptr; m_bTripleDiff = false; m_pMergeResultWindow = nullptr; m_pMergeWindowFrame = nullptr; m_bOutputModified = false; m_bFileSaved = false; m_bTimerBlock = false; m_pHScrollBar = nullptr; m_pDiffVScrollBar = nullptr; m_pMergeVScrollBar = nullptr; viewToolBar = nullptr; m_bRecalcWordWrapPosted = false; m_bFinishMainInit = false; m_pEventLoopForPrinting = nullptr; m_bLoadFiles = false; // Needed before any file operations via FileAccess happen. if(!g_pProgressDialog) { g_pProgressDialog = new ProgressDialog(this, statusBar()); g_pProgressDialog->setStayHidden(true); } // All default values must be set before calling readOptions(). m_pOptionDialog = new OptionDialog(m_pKDiff3Shell != nullptr, this); connect(m_pOptionDialog, &OptionDialog::applyDone, this, &KDiff3App::slotRefresh); // This is just a convenience variable to make code that accesses options more readable m_pOptions = &m_pOptionDialog->m_options; m_pOptionDialog->readOptions(KSharedConfig::openConfig()); // Option handling: Only when pParent==0 (no parent) int argCount = KDiff3Shell::getParser()->optionNames().count() + KDiff3Shell::getParser()->positionalArguments().count(); bool hasArgs = !isPart() && argCount > 0; if(hasArgs) { QString s; QString title; if(KDiff3Shell::getParser()->isSet("confighelp")) { s = m_pOptionDialog->calcOptionHelp(); title = i18n("Current Configuration:"); } else { s = m_pOptionDialog->parseOptions(KDiff3Shell::getParser()->values("cs")); title = i18n("Config Option Error:"); } if(!s.isEmpty()) { //KMessageBox::information(0, s,i18n("KDiff3-Usage")); QDialog* pDialog = new QDialog(this); pDialog->setAttribute(Qt::WA_DeleteOnClose); pDialog->setModal(true); pDialog->setWindowTitle(title); QVBoxLayout* pVBoxLayout = new QVBoxLayout(pDialog); QTextEdit* pTextEdit = new QTextEdit(pDialog); pTextEdit->setText(s); pTextEdit->setReadOnly(true); pTextEdit->setWordWrapMode(QTextOption::NoWrap); pVBoxLayout->addWidget(pTextEdit); pDialog->resize(600, 400); pDialog->exec(); #if !defined(Q_OS_WIN) // A windows program has no console printf("%s\n", title.toLatin1().constData()); printf("%s\n", s.toLatin1().constData()); #endif exit(1); } } m_sd1.setOptions(m_pOptions); m_sd2.setOptions(m_pOptions); m_sd3.setOptions(m_pOptions); m_bAutoFlag = false; //disable --auto option git hard codes this unwanted flag. m_bAutoMode = m_bAutoFlag || m_pOptions->m_bAutoSaveAndQuitOnMergeWithoutConflicts; if(hasArgs) { m_outputFilename = KDiff3Shell::getParser()->value("output"); if(m_outputFilename.isEmpty()) m_outputFilename = KDiff3Shell::getParser()->value("out"); if(!m_outputFilename.isEmpty()) m_outputFilename = FileAccess(m_outputFilename, true).absoluteFilePath(); if(m_bAutoMode && m_outputFilename.isEmpty()) { if(m_bAutoFlag) { //KMessageBox::information(this, i18n("Option --auto used, but no output file specified.")); fprintf(stderr, "%s\n", (const char*)i18n("Option --auto used, but no output file specified.").toLatin1()); } m_bAutoMode = false; } if(m_outputFilename.isEmpty() && KDiff3Shell::getParser()->isSet("merge")) { m_outputFilename = "unnamed.txt"; m_bDefaultFilename = true; } else { m_bDefaultFilename = false; } g_bAutoSolve = !KDiff3Shell::getParser()->isSet("qall"); // Note that this is effective only once. QStringList args = KDiff3Shell::getParser()->positionalArguments(); m_sd1.setFilename(KDiff3Shell::getParser()->value("base")); if(m_sd1.isEmpty()) { if(args.count() > 0) m_sd1.setFilename(args[0]); // args->arg(0) if(args.count() > 1) m_sd2.setFilename(args[1]); if(args.count() > 2) m_sd3.setFilename(args[2]); } else { if(args.count() > 0) m_sd2.setFilename(args[0]); if(args.count() > 1) m_sd3.setFilename(args[1]); } //never properly defined and redundant QStringList aliasList; //KDiff3Shell::getParser()->values( "fname" ); QStringList::Iterator ali = aliasList.begin(); QString an1 = KDiff3Shell::getParser()->value("L1"); if(!an1.isEmpty()) { m_sd1.setAliasName(an1); } else if(ali != aliasList.end()) { m_sd1.setAliasName(*ali); ++ali; } QString an2 = KDiff3Shell::getParser()->value("L2"); if(!an2.isEmpty()) { m_sd2.setAliasName(an2); } else if(ali != aliasList.end()) { m_sd2.setAliasName(*ali); ++ali; } QString an3 = KDiff3Shell::getParser()->value("L3"); if(!an3.isEmpty()) { m_sd3.setAliasName(an3); } else if(ali != aliasList.end()) { m_sd3.setAliasName(*ali); ++ali; } } else { m_bDefaultFilename = false; g_bAutoSolve = false; } g_pProgressDialog->setStayHidden(m_bAutoMode); /////////////////////////////////////////////////////////////////// // call inits to invoke all other construction parts initActions(actionCollection()); initStatusBar(); m_pFindDialog = new FindDialog(this); connect(m_pFindDialog, &FindDialog::findNext, this, &KDiff3App::slotEditFindNext); autoAdvance->setChecked(m_pOptions->m_bAutoAdvance); showWhiteSpaceCharacters->setChecked(m_pOptions->m_bShowWhiteSpaceCharacters); showWhiteSpace->setChecked(m_pOptions->m_bShowWhiteSpace); showWhiteSpaceCharacters->setEnabled(m_pOptions->m_bShowWhiteSpace); showLineNumbers->setChecked(m_pOptions->m_bShowLineNumbers); wordWrap->setChecked(m_pOptions->m_bWordWrap); if(!isPart()) { - // TODO - // viewToolBar->setChecked( m_pOptions->m_bShowToolBar ); - // slotViewToolBar(); - viewStatusBar->setChecked(m_pOptions->m_bShowStatusBar); slotViewStatusBar(); KToolBar *mainToolBar = toolBar(MAIN_TOOLBAR_NAME); if(mainToolBar != nullptr){ mainToolBar->mainWindow()->addToolBar(m_pOptions->m_toolBarPos, mainToolBar); } // TODO restore window size/pos? /* QSize size = m_pOptions->m_geometry; QPoint pos = m_pOptions->m_position; if(!size.isEmpty()) { m_pKDiff3Shell->resize( size ); QRect visibleRect = QRect( pos, size ) & QApplication::desktop()->rect(); if ( visibleRect.width()>100 && visibleRect.height()>100 ) m_pKDiff3Shell->move( pos ); }*/ } slotRefresh(); - m_pMainSplitter = this; //new QSplitter(this); + m_pMainSplitter = this; m_pMainSplitter->setOrientation(Qt::Vertical); // setCentralWidget( m_pMainSplitter ); m_pDirectoryMergeSplitter = new QSplitter(m_pMainSplitter); m_pDirectoryMergeSplitter->setObjectName("DirectoryMergeSplitter"); m_pMainSplitter->addWidget(m_pDirectoryMergeSplitter); m_pDirectoryMergeSplitter->setOrientation(Qt::Horizontal); m_pDirectoryMergeWindow = new DirectoryMergeWindow(m_pDirectoryMergeSplitter, m_pOptions, KIconLoader::global()); m_pDirectoryMergeSplitter->addWidget(m_pDirectoryMergeWindow); m_pDirectoryMergeInfo = new DirectoryMergeInfo(m_pDirectoryMergeSplitter); m_pDirectoryMergeWindow->setDirectoryMergeInfo(m_pDirectoryMergeInfo); m_pDirectoryMergeSplitter->addWidget(m_pDirectoryMergeInfo); connect(m_pDirectoryMergeWindow, &DirectoryMergeWindow::startDiffMerge, this, &KDiff3App::slotFileOpen2); connect(m_pDirectoryMergeWindow->selectionModel(), &QItemSelectionModel::selectionChanged, this, &KDiff3App::slotUpdateAvailabilities); connect(m_pDirectoryMergeWindow->selectionModel(), &QItemSelectionModel::currentChanged, this, &KDiff3App::slotUpdateAvailabilities); connect(m_pDirectoryMergeWindow, &DirectoryMergeWindow::checkIfCanContinue, this, &KDiff3App::slotCheckIfCanContinue); connect(m_pDirectoryMergeWindow, SIGNAL(updateAvailabilities()), this, SLOT(slotUpdateAvailabilities())); connect(m_pDirectoryMergeWindow, &DirectoryMergeWindow::statusBarMessage, this, &KDiff3App::slotStatusMsg); connect(QApplication::clipboard(), &QClipboard::dataChanged, this, &KDiff3App::slotClipboardChanged); m_pDirectoryMergeWindow->initDirectoryMergeActions(this, actionCollection()); delete KDiff3Shell::getParser(); if(m_pKDiff3Shell == nullptr) { completeInit(); } } void KDiff3App::completeInit(const QString& fn1, const QString& fn2, const QString& fn3) { if(m_pKDiff3Shell != nullptr) { QSize size = m_pOptions->m_geometry; QPoint pos = m_pOptions->m_position; if(!size.isEmpty()) { m_pKDiff3Shell->resize(size); QRect visibleRect = QRect(pos, size) & QApplication::desktop()->rect(); if(visibleRect.width() > 100 && visibleRect.height() > 100) m_pKDiff3Shell->move(pos); if(!m_bAutoMode) { if(m_pOptions->m_bMaximised) m_pKDiff3Shell->showMaximized(); else m_pKDiff3Shell->show(); } } } if(!fn1.isEmpty()) { m_sd1.setFilename(fn1); } if(!fn2.isEmpty()) { m_sd2.setFilename(fn2); } if(!fn3.isEmpty()) { m_sd3.setFilename(fn3); } bool bSuccess = improveFilenames(false); if(m_bAutoFlag && m_bAutoMode && m_bDirCompare) { fprintf(stderr, "%s\n", (const char*)i18n("Option --auto ignored for directory comparison.").toLatin1()); m_bAutoMode = false; } if(!m_bDirCompare) { m_pDirectoryMergeSplitter->hide(); mainInit(); if(m_bAutoMode) { SourceData* pSD = nullptr; if(m_sd3.isEmpty()) { if(m_totalDiffStatus.bBinaryAEqB) { pSD = &m_sd1; } } else { if(m_totalDiffStatus.bBinaryBEqC) { pSD = &m_sd3; // B==C (assume A is old) } else if(m_totalDiffStatus.bBinaryAEqB) { pSD = &m_sd3; // assuming C has changed } else if(m_totalDiffStatus.bBinaryAEqC) { pSD = &m_sd2; // assuming B has changed } } if(pSD != nullptr) { // Save this file directly, not via the merge result window. bSuccess = false; FileAccess fa(m_outputFilename); if(m_pOptions->m_bDmCreateBakFiles && fa.exists()) { QString newName = m_outputFilename + ".orig"; if(FileAccess::exists(newName)) FileAccess::removeFile(newName); if(!FileAccess::exists(newName)) fa.rename(newName); } bSuccess = pSD->saveNormalDataAs(m_outputFilename); if(bSuccess) ::exit(0); else KMessageBox::error(this, i18n("Saving failed.")); } else if(m_pMergeResultWindow->getNrOfUnsolvedConflicts() == 0) { bSuccess = m_pMergeResultWindow->saveDocument(m_pMergeResultWindowTitle->getFileName(), m_pMergeResultWindowTitle->getEncoding(), m_pMergeResultWindowTitle->getLineEndStyle()); if(bSuccess) ::exit(0); } } } m_bAutoMode = false; if(m_pKDiff3Shell) { if(m_pOptions->m_bMaximised) m_pKDiff3Shell->showMaximized(); else m_pKDiff3Shell->show(); } g_pProgressDialog->setStayHidden(false); if(statusBar() != nullptr) statusBar()->setSizeGripEnabled(true); slotClipboardChanged(); // For initialisation. slotUpdateAvailabilities(); if(!m_bDirCompare && m_pKDiff3Shell != nullptr) { bool bFileOpenError = false; if((!m_sd1.isEmpty() && !m_sd1.hasData()) || (!m_sd2.isEmpty() && !m_sd2.hasData()) || (!m_sd3.isEmpty() && !m_sd3.hasData())) { QString text(i18n("Opening of these files failed:")); text += "\n\n"; if(!m_sd1.isEmpty() && !m_sd1.hasData()) text += " - " + m_sd1.getAliasName() + "\n"; if(!m_sd2.isEmpty() && !m_sd2.hasData()) text += " - " + m_sd2.getAliasName() + "\n"; if(!m_sd3.isEmpty() && !m_sd3.hasData()) text += " - " + m_sd3.getAliasName() + "\n"; KMessageBox::sorry(this, text, i18n("File Open Error")); bFileOpenError = true; } if(m_sd1.isEmpty() || m_sd2.isEmpty() || bFileOpenError) slotFileOpen(); } else if(!bSuccess) // Directory open failed { slotFileOpen(); } } KDiff3App::~KDiff3App() { } /** * Helper function used to create actions into the ac collection */ void KDiff3App::initActions(KActionCollection* ac) { if(ac == nullptr){ KMessageBox::error(nullptr, "actionCollection==0"); exit(-1);//we cann't recover from this. } fileOpen = KStandardAction::open(this, SLOT(slotFileOpen()), ac); fileOpen->setStatusTip(i18n("Opens documents for comparison...")); fileReload = KDiff3::createAction(i18n("Reload"), QKeySequence(QKeySequence::Refresh), this, SLOT(slotReload()), ac, QLatin1String("file_reload")); fileSave = KStandardAction::save(this, SLOT(slotFileSave()), ac); fileSave->setStatusTip(i18n("Saves the merge result. All conflicts must be solved!")); fileSaveAs = KStandardAction::saveAs(this, SLOT(slotFileSaveAs()), ac); fileSaveAs->setStatusTip(i18n("Saves the current document as...")); #ifndef QT_NO_PRINTER filePrint = KStandardAction::print(this, SLOT(slotFilePrint()), ac); filePrint->setStatusTip(i18n("Print the differences")); #endif fileQuit = KStandardAction::quit(this, SLOT(slotFileQuit()), ac); fileQuit->setStatusTip(i18n("Quits the application")); editCut = KStandardAction::cut(this, SLOT(slotEditCut()), ac); editCut->setStatusTip(i18n("Cuts the selected section and puts it to the clipboard")); editCopy = KStandardAction::copy(this, SLOT(slotEditCopy()), ac); editCopy->setStatusTip(i18n("Copies the selected section to the clipboard")); editPaste = KStandardAction::paste(this, SLOT(slotEditPaste()), ac); editPaste->setStatusTip(i18n("Pastes the clipboard contents to current position")); editSelectAll = KStandardAction::selectAll(this, SLOT(slotEditSelectAll()), ac); editSelectAll->setStatusTip(i18n("Select everything in current window")); editFind = KStandardAction::find(this, SLOT(slotEditFind()), ac); editFind->setStatusTip(i18n("Search for a string")); editFindNext = KStandardAction::findNext(this, SLOT(slotEditFindNext()), ac); editFindNext->setStatusTip(i18n("Search again for the string")); /* FIXME figure out how to implement this action viewToolBar = KStandardAction::showToolbar(this, SLOT(slotViewToolBar()), ac); viewToolBar->setStatusTip(i18n("Enables/disables the toolbar")); */ viewStatusBar = KStandardAction::showStatusbar(this, SLOT(slotViewStatusBar()), ac); viewStatusBar->setStatusTip(i18n("Enables/disables the statusbar")); KStandardAction::keyBindings(this, SLOT(slotConfigureKeys()), ac); QAction* pAction = KStandardAction::preferences(this, SLOT(slotConfigure()), ac); if(isPart()) pAction->setText(i18n("Configure KDiff3...")); #include "xpm/autoadvance.xpm" #include "xpm/currentpos.xpm" #include "xpm/down1arrow.xpm" #include "xpm/down2arrow.xpm" #include "xpm/downend.xpm" #include "xpm/iconA.xpm" #include "xpm/iconB.xpm" #include "xpm/iconC.xpm" #include "xpm/nextunsolved.xpm" #include "xpm/prevunsolved.xpm" #include "xpm/showlinenumbers.xpm" #include "xpm/showwhitespace.xpm" #include "xpm/showwhitespacechars.xpm" #include "xpm/up1arrow.xpm" #include "xpm/up2arrow.xpm" #include "xpm/upend.xpm" //#include "reload.xpm" goCurrent = KDiff3::createAction(i18n("Go to Current Delta"), QIcon(QPixmap(currentpos)), i18n("Current\nDelta"), QKeySequence(Qt::CTRL + Qt::Key_Space), this, SLOT(slotGoCurrent()), ac, "go_current"); goTop = KDiff3::createAction(i18n("Go to First Delta"), QIcon(QPixmap(upend)), i18n("First\nDelta"), this, SLOT(slotGoTop()), ac, "go_top"); goBottom = KDiff3::createAction(i18n("Go to Last Delta"), QIcon(QPixmap(downend)), i18n("Last\nDelta"), this, SLOT(slotGoBottom()), ac, "go_bottom"); QString omitsWhitespace = ".\n" + i18n("(Skips white space differences when \"Show White Space\" is disabled.)"); QString includeWhitespace = ".\n" + i18n("(Does not skip white space differences even when \"Show White Space\" is disabled.)"); goPrevDelta = KDiff3::createAction(i18n("Go to Previous Delta"), QIcon(QPixmap(up1arrow)), i18n("Prev\nDelta"), QKeySequence(Qt::CTRL + Qt::Key_Up), this, SLOT(slotGoPrevDelta()), ac, "go_prev_delta"); goPrevDelta->setToolTip(goPrevDelta->text() + omitsWhitespace); goNextDelta = KDiff3::createAction(i18n("Go to Next Delta"), QIcon(QPixmap(down1arrow)), i18n("Next\nDelta"), QKeySequence(Qt::CTRL + Qt::Key_Down), this, SLOT(slotGoNextDelta()), ac, "go_next_delta"); goNextDelta->setToolTip(goNextDelta->text() + omitsWhitespace); goPrevConflict = KDiff3::createAction(i18n("Go to Previous Conflict"), QIcon(QPixmap(up2arrow)), i18n("Prev\nConflict"), QKeySequence(Qt::CTRL + Qt::Key_PageUp), this, SLOT(slotGoPrevConflict()), ac, "go_prev_conflict"); goPrevConflict->setToolTip(goPrevConflict->text() + omitsWhitespace); goNextConflict = KDiff3::createAction(i18n("Go to Next Conflict"), QIcon(QPixmap(down2arrow)), i18n("Next\nConflict"), QKeySequence(Qt::CTRL + Qt::Key_PageDown), this, SLOT(slotGoNextConflict()), ac, "go_next_conflict"); goNextConflict->setToolTip(goNextConflict->text() + omitsWhitespace); goPrevUnsolvedConflict = KDiff3::createAction(i18n("Go to Previous Unsolved Conflict"), QIcon(QPixmap(prevunsolved)), i18n("Prev\nUnsolved"), this, SLOT(slotGoPrevUnsolvedConflict()), ac, "go_prev_unsolved_conflict"); goPrevUnsolvedConflict->setToolTip(goPrevUnsolvedConflict->text() + includeWhitespace); goNextUnsolvedConflict = KDiff3::createAction(i18n("Go to Next Unsolved Conflict"), QIcon(QPixmap(nextunsolved)), i18n("Next\nUnsolved"), this, SLOT(slotGoNextUnsolvedConflict()), ac, "go_next_unsolved_conflict"); goNextUnsolvedConflict->setToolTip(goNextUnsolvedConflict->text() + includeWhitespace); chooseA = KDiff3::createAction(i18n("Select Line(s) From A"), QIcon(QPixmap(iconA)), i18n("Choose\nA"), QKeySequence(Qt::CTRL + Qt::Key_1), this, SLOT(slotChooseA()), ac, "merge_choose_a"); chooseB = KDiff3::createAction(i18n("Select Line(s) From B"), QIcon(QPixmap(iconB)), i18n("Choose\nB"), QKeySequence(Qt::CTRL + Qt::Key_2), this, SLOT(slotChooseB()), ac, "merge_choose_b"); chooseC = KDiff3::createAction(i18n("Select Line(s) From C"), QIcon(QPixmap(iconC)), i18n("Choose\nC"), QKeySequence(Qt::CTRL + Qt::Key_3), this, SLOT(slotChooseC()), ac, "merge_choose_c"); autoAdvance = KDiff3::createAction(i18n("Automatically Go to Next Unsolved Conflict After Source Selection"), QIcon(QPixmap(autoadvance)), i18n("Auto\nNext"), this, SLOT(slotAutoAdvanceToggled()), ac, "merge_autoadvance"); showWhiteSpaceCharacters = KDiff3::createAction(i18n("Show Space && Tabulator Characters"), QIcon(QPixmap(showwhitespacechars)), i18n("White\nCharacters"), this, SLOT(slotShowWhiteSpaceToggled()), ac, "diff_show_whitespace_characters"); showWhiteSpace = KDiff3::createAction(i18n("Show White Space"), QIcon(QPixmap(showwhitespace)), i18n("White\nDeltas"), this, SLOT(slotShowWhiteSpaceToggled()), ac, "diff_show_whitespace"); showLineNumbers = KDiff3::createAction(i18n("Show Line Numbers"), QIcon(QPixmap(showlinenumbers)), i18n("Line\nNumbers"), this, SLOT(slotShowLineNumbersToggled()), ac, "diff_showlinenumbers"); chooseAEverywhere = KDiff3::createAction(i18n("Choose A Everywhere"), QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_1), this, SLOT(slotChooseAEverywhere()), ac, "merge_choose_a_everywhere"); chooseBEverywhere = KDiff3::createAction(i18n("Choose B Everywhere"), QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_2), this, SLOT(slotChooseBEverywhere()), ac, "merge_choose_b_everywhere"); chooseCEverywhere = KDiff3::createAction(i18n("Choose C Everywhere"), QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_3), this, SLOT(slotChooseCEverywhere()), ac, "merge_choose_c_everywhere"); chooseAForUnsolvedConflicts = KDiff3::createAction(i18n("Choose A for All Unsolved Conflicts"), this, SLOT(slotChooseAForUnsolvedConflicts()), ac, "merge_choose_a_for_unsolved_conflicts"); chooseBForUnsolvedConflicts = KDiff3::createAction(i18n("Choose B for All Unsolved Conflicts"), this, SLOT(slotChooseBForUnsolvedConflicts()), ac, "merge_choose_b_for_unsolved_conflicts"); chooseCForUnsolvedConflicts = KDiff3::createAction(i18n("Choose C for All Unsolved Conflicts"), this, SLOT(slotChooseCForUnsolvedConflicts()), ac, "merge_choose_c_for_unsolved_conflicts"); chooseAForUnsolvedWhiteSpaceConflicts = KDiff3::createAction(i18n("Choose A for All Unsolved Whitespace Conflicts"), this, SLOT(slotChooseAForUnsolvedWhiteSpaceConflicts()), ac, "merge_choose_a_for_unsolved_whitespace_conflicts"); chooseBForUnsolvedWhiteSpaceConflicts = KDiff3::createAction(i18n("Choose B for All Unsolved Whitespace Conflicts"), this, SLOT(slotChooseBForUnsolvedWhiteSpaceConflicts()), ac, "merge_choose_b_for_unsolved_whitespace_conflicts"); chooseCForUnsolvedWhiteSpaceConflicts = KDiff3::createAction(i18n("Choose C for All Unsolved Whitespace Conflicts"), this, SLOT(slotChooseCForUnsolvedWhiteSpaceConflicts()), ac, "merge_choose_c_for_unsolved_whitespace_conflicts"); autoSolve = KDiff3::createAction(i18n("Automatically Solve Simple Conflicts"), this, SLOT(slotAutoSolve()), ac, "merge_autosolve"); unsolve = KDiff3::createAction(i18n("Set Deltas to Conflicts"), this, SLOT(slotUnsolve()), ac, "merge_autounsolve"); mergeRegExp = KDiff3::createAction(i18n("Run Regular Expression Auto Merge"), this, SLOT(slotRegExpAutoMerge()), ac, "merge_regexp_automerge"); mergeHistory = KDiff3::createAction(i18n("Automatically Solve History Conflicts"), this, SLOT(slotMergeHistory()), ac, "merge_versioncontrol_history"); splitDiff = KDiff3::createAction(i18n("Split Diff At Selection"), this, SLOT(slotSplitDiff()), ac, "merge_splitdiff"); joinDiffs = KDiff3::createAction(i18n("Join Selected Diffs"), this, SLOT(slotJoinDiffs()), ac, "merge_joindiffs"); showWindowA = KDiff3::createAction(i18n("Show Window A"), this, SLOT(slotShowWindowAToggled()), ac, "win_show_a"); showWindowB = KDiff3::createAction(i18n("Show Window B"), this, SLOT(slotShowWindowBToggled()), ac, "win_show_b"); showWindowC = KDiff3::createAction(i18n("Show Window C"), this, SLOT(slotShowWindowCToggled()), ac, "win_show_c"); overviewModeNormal = KDiff3::createAction(i18n("Normal Overview"), this, SLOT(slotOverviewNormal()), ac, "diff_overview_normal"); overviewModeAB = KDiff3::createAction(i18n("A vs. B Overview"), this, SLOT(slotOverviewAB()), ac, "diff_overview_ab"); overviewModeAC = KDiff3::createAction(i18n("A vs. C Overview"), this, SLOT(slotOverviewAC()), ac, "diff_overview_ac"); overviewModeBC = KDiff3::createAction(i18n("B vs. C Overview"), this, SLOT(slotOverviewBC()), ac, "diff_overview_bc"); wordWrap = KDiff3::createAction(i18n("Word Wrap Diff Windows"), this, SLOT(slotWordWrapToggled()), ac, "diff_wordwrap"); addManualDiffHelp = KDiff3::createAction(i18n("Add Manual Diff Alignment"), QKeySequence(Qt::CTRL + Qt::Key_Y), this, SLOT(slotAddManualDiffHelp()), ac, "diff_add_manual_diff_help"); clearManualDiffHelpList = KDiff3::createAction(i18n("Clear All Manual Diff Alignments"), QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_Y), this, SLOT(slotClearManualDiffHelpList()), ac, "diff_clear_manual_diff_help_list"); winFocusNext = KDiff3::createAction(i18n("Focus Next Window"), QKeySequence(Qt::ALT + Qt::Key_Right), this, SLOT(slotWinFocusNext()), ac, "win_focus_next"); winFocusPrev = KDiff3::createAction(i18n("Focus Prev Window"), QKeySequence(Qt::ALT + Qt::Key_Left), this, SLOT(slotWinFocusPrev()), ac, "win_focus_prev"); winToggleSplitOrientation = KDiff3::createAction(i18n("Toggle Split Orientation"), this, SLOT(slotWinToggleSplitterOrientation()), ac, "win_toggle_split_orientation"); dirShowBoth = KDiff3::createAction(i18n("Dir && Text Split Screen View"), this, SLOT(slotDirShowBoth()), ac, "win_dir_show_both"); dirShowBoth->setChecked(true); dirViewToggle = KDiff3::createAction(i18n("Toggle Between Dir && Text View"), this, SLOT(slotDirViewToggle()), ac, "win_dir_view_toggle"); m_pMergeEditorPopupMenu = new QMenu(this); /* chooseA->plug( m_pMergeEditorPopupMenu ); chooseB->plug( m_pMergeEditorPopupMenu ); chooseC->plug( m_pMergeEditorPopupMenu );*/ m_pMergeEditorPopupMenu->addAction(chooseA); m_pMergeEditorPopupMenu->addAction(chooseB); m_pMergeEditorPopupMenu->addAction(chooseC); } void KDiff3App::showPopupMenu(const QPoint& point) { m_pMergeEditorPopupMenu->popup(point); } void KDiff3App::initStatusBar() { /////////////////////////////////////////////////////////////////// // STATUSBAR if(statusBar() != nullptr) statusBar()->showMessage(i18n("Ready.")); } void KDiff3App::saveOptions(KSharedConfigPtr config) { if(!m_bAutoMode) { if(!isPart()) { m_pOptions->m_bMaximised = m_pKDiff3Shell->isMaximized(); if(!m_pKDiff3Shell->isMaximized() && m_pKDiff3Shell->isVisible()) { m_pOptions->m_geometry = m_pKDiff3Shell->size(); m_pOptions->m_position = m_pKDiff3Shell->pos(); } /* TODO change this option as now KToolbar uses QToolbar positioning style if ( toolBar(MAIN_TOOLBAR_NAME)!=0 ) m_pOptionDialog->m_toolBarPos = (int) toolBar(MAIN_TOOLBAR_NAME)->allowedAreas();*/ } m_pOptionDialog->saveOptions(config); } } bool KDiff3App::queryClose() { saveOptions(KSharedConfig::openConfig()); if(m_bOutputModified) { int result = KMessageBox::warningYesNoCancel(this, i18n("The merge result has not been saved."), i18n("Warning"), KGuiItem(i18n("Save && Quit")), KGuiItem(i18n("Quit Without Saving"))); if(result == KMessageBox::Cancel) return false; else if(result == KMessageBox::Yes) { slotFileSave(); if(m_bOutputModified) { KMessageBox::sorry(this, i18n("Saving the merge result failed."), i18n("Warning")); return false; } } } m_bOutputModified = false; if(m_pDirectoryMergeWindow->isDirectoryMergeInProgress()) { int result = KMessageBox::warningYesNo(this, i18n("You are currently doing a directory merge. Are you sure, you want to abort?"), i18n("Warning"), KStandardGuiItem::quit(), KStandardGuiItem::cont() /* i18n("Continue Merging") */); if(result != KMessageBox::Yes) return false; } return true; } ///////////////////////////////////////////////////////////////////// // SLOT IMPLEMENTATION ///////////////////////////////////////////////////////////////////// void KDiff3App::slotFileSave() { if(m_bDefaultFilename) { slotFileSaveAs(); } else { slotStatusMsg(i18n("Saving file...")); bool bSuccess = m_pMergeResultWindow->saveDocument(m_outputFilename, m_pMergeResultWindowTitle->getEncoding(), m_pMergeResultWindowTitle->getLineEndStyle()); if(bSuccess) { m_bFileSaved = true; m_bOutputModified = false; if(m_bDirCompare) m_pDirectoryMergeWindow->mergeResultSaved(m_outputFilename); } slotStatusMsg(i18n("Ready.")); } } void KDiff3App::slotFileSaveAs() { slotStatusMsg(i18n("Saving file with a new filename...")); QString s = QFileDialog::getSaveFileUrl(this, i18n("Save As..."), QUrl::fromLocalFile(QDir::currentPath())).url(QUrl::PreferLocalFile); if(!s.isEmpty()) { m_outputFilename = s; m_pMergeResultWindowTitle->setFileName(m_outputFilename); bool bSuccess = m_pMergeResultWindow->saveDocument(m_outputFilename, m_pMergeResultWindowTitle->getEncoding(), m_pMergeResultWindowTitle->getLineEndStyle()); if(bSuccess) { m_bOutputModified = false; if(m_bDirCompare) m_pDirectoryMergeWindow->mergeResultSaved(m_outputFilename); } //setWindowTitle(url.fileName(),doc->isModified()); m_bDefaultFilename = false; } slotStatusMsg(i18n("Ready.")); } void printDiffTextWindow(MyPainter& painter, const QRect& view, const QString& headerText, DiffTextWindow* pDiffTextWindow, int line, int linesPerPage, QColor fgColor) { QRect clipRect = view; clipRect.setTop(0); painter.setClipRect(clipRect); painter.translate(view.left(), 0); QFontMetrics fm = painter.fontMetrics(); //if ( fm.width(headerText) > view.width() ) { // A simple wrapline algorithm int l = 0; for(int p = 0; p < headerText.length();) { QString s = headerText.mid(p); int i; for(i = 2; i < s.length(); ++i) if(fm.width(s, i) > view.width()) { --i; break; } //QString s2 = s.left(i); painter.drawText(0, l * fm.height() + fm.ascent(), s.left(i)); p += i; ++l; } painter.setPen(fgColor); painter.drawLine(0, view.top() - 2, view.width(), view.top() - 2); } painter.translate(0, view.top()); pDiffTextWindow->print(painter, view, line, linesPerPage); painter.resetMatrix(); } void KDiff3App::slotFilePrint() { if(m_pDiffTextWindow1 == nullptr) return; #ifdef QT_NO_PRINTER slotStatusMsg(i18n("Printing not implemented.")); #endif #ifndef QT_NO_PRINTER QPrinter printer; QPrintDialog printDialog(&printer, this); LineRef firstSelectionD3LIdx = -1; LineRef lastSelectionD3LIdx = -1; m_pDiffTextWindow1->getSelectionRange(&firstSelectionD3LIdx, &lastSelectionD3LIdx, eD3LLineCoords); if(firstSelectionD3LIdx < 0 && m_pDiffTextWindow2) { m_pDiffTextWindow2->getSelectionRange(&firstSelectionD3LIdx, &lastSelectionD3LIdx, eD3LLineCoords); } if(firstSelectionD3LIdx < 0 && m_pDiffTextWindow3) { m_pDiffTextWindow3->getSelectionRange(&firstSelectionD3LIdx, &lastSelectionD3LIdx, eD3LLineCoords); } if(firstSelectionD3LIdx >= 0) { printDialog.addEnabledOption(QPrintDialog::PrintSelection); //printer.setOptionEnabled(QPrinter::PrintSelection,true); printDialog.setPrintRange(QAbstractPrintDialog::Selection); } if(firstSelectionD3LIdx == -1) printDialog.setPrintRange(QAbstractPrintDialog::AllPages); //printDialog.setMinMax(0,0); printDialog.setFromTo(0, 0); int currentFirstLine = m_pDiffTextWindow1->getFirstLine(); int currentFirstD3LIdx = m_pDiffTextWindow1->convertLineToDiff3LineIdx(currentFirstLine); // do some printer initialization printer.setFullPage(false); // initialize the printer using the print dialog if(printDialog.exec() == QDialog::Accepted) { slotStatusMsg(i18n("Printing...")); // create a painter to paint on the printer object MyPainter painter(&printer, m_pOptions->m_bRightToLeftLanguage, width(), fontMetrics().width('W')); QPaintDevice* pPaintDevice = painter.device(); int dpiy = pPaintDevice->logicalDpiY(); int columnDistance = (int)((0.5 / 2.54) * dpiy); // 0.5 cm between the columns int columns = m_bTripleDiff ? 3 : 2; int columnWidth = (pPaintDevice->width() - (columns - 1) * columnDistance) / columns; QFont f = m_pOptions->m_font; f.setPointSizeF(f.pointSizeF() - 1); // Print with slightly smaller font. painter.setFont(f); QFontMetrics fm = painter.fontMetrics(); QString topLineText = i18n("Top line"); //int headerWidth = fm.width( m_sd1.getAliasName() + ", "+topLineText+": 01234567" ); int headerLines = fm.width(m_sd1.getAliasName() + ", " + topLineText + ": 01234567") / columnWidth + 1; int headerMargin = headerLines * fm.height() + 3; // Text + one horizontal line int footerMargin = fm.height() + 3; QRect view(0, headerMargin, pPaintDevice->width(), pPaintDevice->height() - (headerMargin + footerMargin)); QRect view1(0 * (columnWidth + columnDistance), view.top(), columnWidth, view.height()); QRect view2(1 * (columnWidth + columnDistance), view.top(), columnWidth, view.height()); QRect view3(2 * (columnWidth + columnDistance), view.top(), columnWidth, view.height()); int linesPerPage = view.height() / fm.lineSpacing(); QEventLoop eventLoopForPrinting; m_pEventLoopForPrinting = &eventLoopForPrinting; if(m_pOptions->m_bWordWrap) { // For printing the lines are wrapped differently (this invalidates the first line) recalcWordWrap(columnWidth); m_pEventLoopForPrinting->exec(); } LineRef totalNofLines = std::max(m_pDiffTextWindow1->getNofLines(), m_pDiffTextWindow2->getNofLines()); if(m_bTripleDiff && m_pDiffTextWindow3) totalNofLines = std::max(totalNofLines, m_pDiffTextWindow3->getNofLines()); QList pageList; // = printer.pageList(); bool bPrintCurrentPage = false; bool bFirstPrintedPage = false; bool bPrintSelection = false; int totalNofPages = (totalNofLines + linesPerPage - 1) / linesPerPage; LineRef line = -1; LineRef selectionEndLine = -1; if(printer.printRange() == QPrinter::AllPages) { pageList.clear(); for(int i = 0; i < totalNofPages; ++i) { pageList.push_back(i + 1); } } else if(printer.printRange() == QPrinter::PageRange) { pageList.clear(); for(int i = printer.fromPage(); i <= printer.toPage(); ++i) { pageList.push_back(i); } } if(printer.printRange() == QPrinter::Selection) { bPrintSelection = true; if(firstSelectionD3LIdx >= 0) { line = m_pDiffTextWindow1->convertDiff3LineIdxToLine(firstSelectionD3LIdx); selectionEndLine = m_pDiffTextWindow1->convertDiff3LineIdxToLine(lastSelectionD3LIdx + 1); totalNofPages = (selectionEndLine - line + linesPerPage - 1) / linesPerPage; } } int page = 1; ProgressProxy pp; pp.setMaxNofSteps(totalNofPages); QList::iterator pageListIt = pageList.begin(); for(;;) { pp.setInformation(i18n("Printing page %1 of %2", page, totalNofPages), false); pp.setCurrent(page - 1); if(pp.wasCancelled()) { printer.abort(); break; } if(!bPrintSelection) { if(pageListIt == pageList.end()) break; page = *pageListIt; line = (page - 1) * linesPerPage; if(page == 10000) { // This means "Print the current page" bPrintCurrentPage = true; // Detect the first visible line in the window. line = m_pDiffTextWindow1->convertDiff3LineIdxToLine(currentFirstD3LIdx); } } else { if(line >= selectionEndLine) { break; } else { if(selectionEndLine - line < linesPerPage) linesPerPage = selectionEndLine - line; } } if(line >= 0 && line < totalNofLines) { if(bFirstPrintedPage) printer.newPage(); painter.setClipping(true); painter.setPen(m_pOptions->m_colorA); QString headerText1 = m_sd1.getAliasName() + ", " + topLineText + ": " + QString::number(m_pDiffTextWindow1->calcTopLineInFile(line) + 1); printDiffTextWindow(painter, view1, headerText1, m_pDiffTextWindow1, line, linesPerPage, m_pOptions->m_fgColor); painter.setPen(m_pOptions->m_colorB); QString headerText2 = m_sd2.getAliasName() + ", " + topLineText + ": " + QString::number(m_pDiffTextWindow2->calcTopLineInFile(line) + 1); printDiffTextWindow(painter, view2, headerText2, m_pDiffTextWindow2, line, linesPerPage, m_pOptions->m_fgColor); if(m_bTripleDiff && m_pDiffTextWindow3) { painter.setPen(m_pOptions->m_colorC); QString headerText3 = m_sd3.getAliasName() + ", " + topLineText + ": " + QString::number(m_pDiffTextWindow3->calcTopLineInFile(line) + 1); printDiffTextWindow(painter, view3, headerText3, m_pDiffTextWindow3, line, linesPerPage, m_pOptions->m_fgColor); } painter.setClipping(false); painter.setPen(m_pOptions->m_fgColor); painter.drawLine(0, view.bottom() + 3, view.width(), view.bottom() + 3); QString s = bPrintCurrentPage ? QString("") : QString::number(page) + "/" + QString::number(totalNofPages); if(bPrintSelection) s += i18n(" (Selection)"); painter.drawText((view.right() - painter.fontMetrics().width(s)) / 2, view.bottom() + painter.fontMetrics().ascent() + 5, s); bFirstPrintedPage = true; } if(bPrintSelection) { line += linesPerPage; ++page; } else { ++pageListIt; } } painter.end(); if(m_pOptions->m_bWordWrap) { recalcWordWrap(); m_pEventLoopForPrinting->exec(); m_pDiffVScrollBar->setValue(m_pDiffTextWindow1->convertDiff3LineIdxToLine(currentFirstD3LIdx)); } slotStatusMsg(i18n("Printing completed.")); } else { slotStatusMsg(i18n("Printing aborted.")); } #endif } void KDiff3App::slotFileQuit() { slotStatusMsg(i18n("Exiting...")); if(!queryClose()) return; // Don't quit QApplication::exit(isFileSaved() || isDirComparison() ? 0 : 1); } void KDiff3App::slotViewToolBar() { Q_ASSERT(viewToolBar != nullptr); slotStatusMsg(i18n("Toggling toolbar...")); m_pOptions->m_bShowToolBar = viewToolBar->isChecked(); /////////////////////////////////////////////////////////////////// // turn Toolbar on or off if(toolBar(MAIN_TOOLBAR_NAME) != nullptr) { if(!m_pOptions->m_bShowToolBar) { toolBar(MAIN_TOOLBAR_NAME)->hide(); } else { toolBar(MAIN_TOOLBAR_NAME)->show(); } } slotStatusMsg(i18n("Ready.")); } void KDiff3App::slotViewStatusBar() { slotStatusMsg(i18n("Toggle the statusbar...")); m_pOptions->m_bShowStatusBar = viewStatusBar->isChecked(); /////////////////////////////////////////////////////////////////// //turn Statusbar on or off if(statusBar() != nullptr) { if(!viewStatusBar->isChecked()) { statusBar()->hide(); } else { statusBar()->show(); } } slotStatusMsg(i18n("Ready.")); } void KDiff3App::slotStatusMsg(const QString& text) { /////////////////////////////////////////////////////////////////// // change status message permanently if(statusBar() != nullptr) { statusBar()->clearMessage(); statusBar()->showMessage(text); } } //#include "kdiff3.moc"