diff --git a/src/directorymergewindow.cpp b/src/directorymergewindow.cpp index 667179c..345e9cf 100644 --- a/src/directorymergewindow.cpp +++ b/src/directorymergewindow.cpp @@ -1,3110 +1,3105 @@ /*************************************************************************** * Copyright (C) 2003-2007 by Joachim Eibl * * Copyright (C) 2018 Michael Reeves reeves.87@gmail.com * * * * * * 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 "directorymergewindow.h" #include "DirectoryInfo.h" #include "MergeFileInfos.h" #include "PixMapUtils.h" #include "Utils.h" #include "guiutils.h" #include "kdiff3.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 class StatusInfo : public QDialog { KTextEdit* m_pTextEdit; public: explicit StatusInfo(QWidget* pParent) : QDialog(pParent) { QVBoxLayout* pVLayout = new QVBoxLayout(this); m_pTextEdit = new KTextEdit(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) }; static Qt::CaseSensitivity s_eCaseSensitivity = Qt::CaseSensitive; //TODO: clean up this mess. class DirectoryMergeWindow::DirectoryMergeWindowPrivate : public QAbstractItemModel { friend class DirMergeItem; public: DirectoryMergeWindow* mWindow; explicit DirectoryMergeWindowPrivate(DirectoryMergeWindow* pDMW) { mWindow = pDMW; m_pStatusInfo = new StatusInfo(mWindow); m_pStatusInfo->hide(); } ~DirectoryMergeWindowPrivate() 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->parent() == m_pRoot) return QModelIndex(); else { MergeFileInfos* pParentsParent = pMFI->parent()->parent(); return createIndex(pParentsParent->children().indexOf(pMFI->parent()), 0, pMFI->parent()); } } int rowCount(const QModelIndex& parent = QModelIndex()) const override { MergeFileInfos* pParentMFI = getMFI(parent); if(pParentMFI != nullptr) return pParentMFI->children().count(); else return m_pRoot->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->children().count()) return createIndex(row, column, m_pRoot->children()[row]); else if(pParentMFI != nullptr && row < pParentMFI->children().count()) return createIndex(row, column, pParentMFI->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; } bool isThreeWay() const { if(rootMFI() == nullptr || rootMFI()->getDirectoryInfo() == nullptr) return false; return rootMFI()->getDirectoryInfo()->dirC().isValid(); } MergeFileInfos* rootMFI() const { return m_pRoot; } QSharedPointer m_pOptions = nullptr; 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 eDefaultMergeOp); void setAllMergeOperations(e_MergeOperation eDefaultOperation); 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); 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); void buildMergeMap(const QSharedPointer& dirInfo); private: class FileKey { private: const FileAccess* m_pFA; public: explicit FileKey(const FileAccess& fa) : m_pFA(&fa) {} quint32 getParents(const FileAccess* pFA, const FileAccess* v[], quint32 maxSize) const { quint32 s = 0; for(s = 0; pFA->parent() != nullptr; pFA = pFA->parent(), ++s) { if(s == maxSize) break; 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]; quint32 v1Size = getParents(m_pFA, v1, 100); quint32 v2Size = getParents(fk.m_pFA, v2, 100); for(quint32 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; } return v1Size < v2Size; } }; typedef QMap t_fileMergeMap; MergeFileInfos* m_pRoot = new MergeFileInfos(); t_fileMergeMap m_fileMergeMap; public: bool m_bFollowDirLinks = false; bool m_bFollowFileLinks = false; bool m_bSimulatedMergeStarted = false; bool m_bRealMergeStarted = false; bool m_bError = false; bool m_bSyncMode = false; bool m_bDirectoryMerge = false; // if true, then merge is the default operation, otherwise it's diff. bool m_bCaseSensitive = true; bool m_bUnfoldSubdirs = false; bool m_bSkipDirStatus = false; bool m_bScanning = false; // true while in init() DirectoryMergeInfo* m_pDirectoryMergeInfo = nullptr; StatusInfo* m_pStatusInfo = nullptr; 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); 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(const QSharedPointer &dirInfo, bool bDirectoryMerge, bool bReload); void setOpStatus(const QModelIndex& mi, e_OperationStatus eOpStatus) { if(MergeFileInfos* pMFI = getMFI(mi)) { pMFI->setOpStatus(eOpStatus); emit dataChanged(mi, mi); } } QModelIndex nextSibling(const QModelIndex& mi); }; QVariant DirectoryMergeWindow::DirectoryMergeWindowPrivate::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 pMFI->diffStatus().getUnsolvedConflicts(); case s_SolvedCol: return pMFI->diffStatus().getSolvedConflicts(); case s_NonWhiteCol: return pMFI->diffStatus().getNonWhitespaceConflicts(); case s_WhiteCol: return pMFI->diffStatus().getWhitespaceConflicts(); //default : return QVariant(); } if(s_OpCol == index.column()) { bool bDir = pMFI->hasDir(); switch(pMFI->getOperation()) { 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->getOpStatus()) { 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 PixMapUtils::getOnePixmap(eAgeEnd, pMFI->hasLink(), pMFI->hasDir()); } if(s_ACol == index.column()) { return PixMapUtils::getOnePixmap(pMFI->getAgeA(), pMFI->isLinkA(), pMFI->isDirA()); } if(s_BCol == index.column()) { return PixMapUtils::getOnePixmap(pMFI->getAgeB(), pMFI->isLinkB(), pMFI->isDirB()); } if(s_CCol == index.column()) { return PixMapUtils::getOnePixmap(pMFI->getAgeC(), pMFI->isLinkC(), pMFI->isDirC()); } } 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::DirectoryMergeWindowPrivate::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::DirectoryMergeWindowPrivate* d; public: explicit DirMergeItemDelegate(DirectoryMergeWindow* pParent) : QStyledItemDelegate(pParent), m_pDMW(pParent), d(pParent->d) { } void paint(QPainter* thePainter, const QStyleOptionViewItem& option, const QModelIndex& index) const override { QtNumberType 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; thePainter->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) { QSharedPointer pOpts = d->m_pOptions; QColor c(i == 1 ? pOpts->m_colorA : i == 2 ? pOpts->m_colorB : pOpts->m_colorC); thePainter->setPen(c); // highlight() ); thePainter->drawRect(x + 2, y + yOffset, icon.width(), icon.height()); thePainter->setPen(QPen(c, 0, Qt::DotLine)); thePainter->drawRect(x + 1, y + yOffset - 1, icon.width() + 2, icon.height() + 2); thePainter->setPen(Qt::white); QString s(QChar('A' + i - 1)); thePainter->drawText(x + 2 + (icon.width() - Utils::getHorizontalAdvance(thePainter->fontMetrics(), s)) / 2, y + yOffset + (icon.height() + thePainter->fontMetrics().ascent()) / 2 - 1, s); } else { thePainter->setPen(m_pDMW->palette().window().color()); thePainter->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(thePainter, 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, const QSharedPointer &pOptions, KDiff3App* const app) : QTreeView(pParent) { d = new DirectoryMergeWindowPrivate(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; mApp = app; setSortingEnabled(true); } DirectoryMergeWindow::~DirectoryMergeWindow() { delete d; } 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; } 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->rootMFI()->getDirectoryInfo(), true); //fix file visibilities after reload or menu will be out of sync with display if changed from defaults. updateFileVisibilities(); } void DirectoryMergeWindow::DirectoryMergeWindowPrivate::calcDirStatus(bool bThreeDirs, const QModelIndex& mi, int& nofFiles, int& nofDirs, int& nofEqualFiles, int& nofManualMerges) { MergeFileInfos* pMFI = getMFI(mi); if(pMFI->hasDir()) { ++nofDirs; } else { ++nofFiles; if(pMFI->isEqualAB() && (!bThreeDirs || pMFI->isEqualAC())) { ++nofEqualFiles; } else { if(pMFI->getOperation() == eMergeABCToDest || pMFI->getOperation() == 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( const QSharedPointer &dirInfo, bool bDirectoryMerge, bool bReload) { return d->init(dirInfo, bDirectoryMerge, bReload); } void DirectoryMergeWindow::DirectoryMergeWindowPrivate::buildMergeMap(const QSharedPointer& dirInfo) { t_DirectoryList::iterator dirIterator; if(dirInfo->dirA().isValid()) { for(dirIterator = dirInfo->getDirListA().begin(); dirIterator != dirInfo->getDirListA().end(); ++dirIterator) { MergeFileInfos& mfi = m_fileMergeMap[FileKey(*dirIterator)]; mfi.setFileInfoA(&(*dirIterator)); mfi.setDirectoryInfo(dirInfo); } } if(dirInfo->dirB().isValid()) { for(dirIterator = dirInfo->getDirListB().begin(); dirIterator != dirInfo->getDirListB().end(); ++dirIterator) { MergeFileInfos& mfi = m_fileMergeMap[FileKey(*dirIterator)]; mfi.setFileInfoB(&(*dirIterator)); mfi.setDirectoryInfo(dirInfo); } } if(dirInfo->dirC().isValid()) { for(dirIterator = dirInfo->getDirListC().begin(); dirIterator != dirInfo->getDirListC().end(); ++dirIterator) { MergeFileInfos& mfi = m_fileMergeMap[FileKey(*dirIterator)]; mfi.setFileInfoC(&(*dirIterator)); mfi.setDirectoryInfo(dirInfo); } } } bool DirectoryMergeWindow::DirectoryMergeWindowPrivate::init( const QSharedPointer &dirInfo, bool bDirectoryMerge, bool bReload) { //set root data now that we have the directory info. rootMFI()->setDirectoryInfo(dirInfo); 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 mWindow->checkIfCanContinue(&bCanContinue); if(!bCanContinue) return false; emit mWindow->startDiffMerge("", "", "", "", "", "", "", nullptr); // hide main window } mWindow->show(); mWindow->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->getOperation(); // ++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->clear(); m_mergeItemList.clear(); endResetModel(); m_currentIndexForOperation = m_mergeItemList.end(); if(!bReload) { m_pDirShowIdenticalFiles->setChecked(true); m_pDirShowDifferentFiles->setChecked(true); m_pDirShowFilesOnlyInA->setChecked(true); m_pDirShowFilesOnlyInB->setChecked(true); m_pDirShowFilesOnlyInC->setChecked(true); } Q_ASSERT(dirInfo != nullptr); FileAccess dirA = dirInfo->dirA(); FileAccess dirB = dirInfo->dirB(); FileAccess dirC = dirInfo->dirC(); const FileAccess dirDest = dirInfo->destDir(); // 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(!dirA.isDir() || !dirB.isDir() || (dirC.isValid() && !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", dirA.prettyAbsPath()); } if(!dirB.isDir()) { text += i18n("Dir B \"%1\" does not exist or is not a directory.\n", dirB.prettyAbsPath()); } if(dirC.isValid() && !dirC.isDir()) { text += i18n("Dir C \"%1\" does not exist or is not a directory.\n", dirC.prettyAbsPath()); } KMessageBox::sorry(mWindow, text, i18n("Directory Open Error")); return false; } if(dirC.isValid() && (dirDest.prettyAbsPath() == dirA.prettyAbsPath() || dirDest.prettyAbsPath() == dirB.prettyAbsPath())) { KMessageBox::error(mWindow, 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 mWindow->statusBarMessage(i18n("Scanning directories...")); m_bSyncMode = m_pOptions->m_bDmSyncMode && !dirC.isValid() && !dirDest.isValid(); m_fileMergeMap.clear(); s_eCaseSensitivity = m_bCaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive; // calc how many directories will be read: double nofScans = (dirA.isValid() ? 1 : 0) + (dirB.isValid() ? 1 : 0) + (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); mWindow->setColumnHidden(s_CCol, !dirC.isValid()); mWindow->setColumnHidden(s_WhiteCol, !m_pOptions->m_bDmFullAnalysis); mWindow->setColumnHidden(s_NonWhiteCol, !m_pOptions->m_bDmFullAnalysis); mWindow->setColumnHidden(s_UnsolvedCol, !m_pOptions->m_bDmFullAnalysis); mWindow->setColumnHidden(s_SolvedCol, !(m_pOptions->m_bDmFullAnalysis && dirC.isValid())); bool bListDirSuccessA = true; bool bListDirSuccessB = true; bool bListDirSuccessC = true; if(dirA.isValid()) { pp.setInformation(i18n("Reading Directory A")); pp.setSubRangeTransformation(currentScan / nofScans, (currentScan + 1) / nofScans); ++currentScan; bListDirSuccessA = dirInfo->listDirA(*m_pOptions); } if(dirB.isValid()) { pp.setInformation(i18n("Reading Directory B")); pp.setSubRangeTransformation(currentScan / nofScans, (currentScan + 1) / nofScans); ++currentScan; bListDirSuccessB = dirInfo->listDirB(*m_pOptions); } e_MergeOperation eDefaultMergeOp; if(dirC.isValid()) { pp.setInformation(i18n("Reading Directory C")); pp.setSubRangeTransformation(currentScan / nofScans, (currentScan + 1) / nofScans); ++currentScan; bListDirSuccessC = dirInfo->listDirC(*m_pOptions); eDefaultMergeOp = eMergeABCToDest; } else eDefaultMergeOp = m_bSyncMode ? eMergeToAB : eMergeABToDest; buildMergeMap(dirInfo); bool bContinue = true; if(!bListDirSuccessA || !bListDirSuccessB || !bListDirSuccessC) { QString s = i18n("Some subdirectories were not readable in"); if(!bListDirSuccessA) s += "\nA: " + dirA.prettyAbsPath(); if(!bListDirSuccessB) s += "\nB: " + dirB.prettyAbsPath(); if(!bListDirSuccessC) s += "\nC: " + dirC.prettyAbsPath(); s += '\n'; s += i18n("Check the permissions of the subdirectories."); bContinue = KMessageBox::Continue == KMessageBox::warningContinueCancel(mWindow, s); } if(bContinue) { prepareListView(pp); mWindow->updateFileVisibilities(); for(int childIdx = 0; childIdx < rowCount(); ++childIdx) { QModelIndex mi = index(childIdx, 0, QModelIndex()); calcSuggestedOperation(mi, eDefaultMergeOp); } } mWindow->sortByColumn(0, Qt::AscendingOrder); for(int column = 0; column < columnCount(QModelIndex()); ++column) mWindow->resizeColumnToContents(column); // Try to improve the view a little bit. QWidget* pParent = mWindow->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); } m_bScanning = false; emit mWindow->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(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(dirC.isValid()) s += '\n' + i18n("Number of manual merges: %1", nofManualMerges); KMessageBox::information(mWindow, 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; } inline QString DirectoryMergeWindow::getDirNameA() const { return d->rootMFI()->getDirectoryInfo()->dirA().prettyAbsPath(); } inline QString DirectoryMergeWindow::getDirNameB() const { return d->rootMFI()->getDirectoryInfo()->dirB().prettyAbsPath(); } inline QString DirectoryMergeWindow::getDirNameC() const { return d->rootMFI()->getDirectoryInfo()->dirC().prettyAbsPath(); } inline QString DirectoryMergeWindow::getDirNameDest() const { return d->rootMFI()->getDirectoryInfo()->destDir().prettyAbsPath(); } 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->isThreeWay() ? 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->isThreeWay(); 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) { MergeFileInfos* pMFI = d->getMFI(currentIndex()); if(pMFI == nullptr) return; bool bThreeDirs = pMFI->getDirectoryInfo()->dirC().isValid(); bool bMergeMode = bThreeDirs || !d->m_bSyncMode; bool bFTConflict = pMFI == nullptr ? false : pMFI->conflictingFileTypes(); 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::DirectoryMergeWindowPrivate::setAllMergeOperations(e_MergeOperation eDefaultOperation) { if(KMessageBox::Yes == KMessageBox::warningYesNo(mWindow, 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); } } } QModelIndex DirectoryMergeWindow::DirectoryMergeWindowPrivate::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::DirectoryMergeWindowPrivate::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() && mWindow->isRowHidden(mi.row(), mi.parent()) && !bFindInvisible); } return mi; } void DirectoryMergeWindow::DirectoryMergeWindowPrivate::prepareListView(ProgressProxy& pp) { QStringList errors; //TODO clear(); PixMapUtils::initPixmaps(m_pOptions->m_newestFileColor, m_pOptions->m_oldestFileColor, m_pOptions->m_midAgeFileColor, m_pOptions->m_missingFileColor); mWindow->setRootIsDecorated(true); 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. mfi.compareFilesAndCalcAges(errors, m_pOptions, mWindow); // 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->addChild(&mfi); //new DirMergeItem( this, filePart, &mfi ); mfi.setParent(m_pRoot); } else { FileAccess* pFA = mfi.getFileInfoA() ? mfi.getFileInfoA() : mfi.getFileInfoB() ? mfi.getFileInfoB() : mfi.getFileInfoC(); MergeFileInfos& dirMfi = pFA->parent() ? m_fileMergeMap[FileKey(*pFA->parent())] : *m_pRoot; // parent dirMfi.addChild(&mfi); //new DirMergeItem( dirMfi.m_pDMI, filePart, &mfi ); mfi.setParent(&dirMfi); // // Equality for parent dirs is set in updateFileVisibilities() } mfi.updateAge(); } if(errors.size() > 0) { if(errors.size() < 15) { KMessageBox::errorList(mWindow, i18n("Some files could not be processed."), errors); } else { KMessageBox::error(mWindow, i18n("Some files could not be processed.")); } } beginResetModel(); endResetModel(); } void DirectoryMergeWindow::DirectoryMergeWindowPrivate::calcSuggestedOperation(const QModelIndex& mi, e_MergeOperation eDefaultMergeOp) { MergeFileInfos* pMFI = getMFI(mi); if(pMFI == nullptr) return; bool bCheckC = pMFI->getDirectoryInfo()->dirC().isValid(); bool bCopyNewer = m_pOptions->m_bDmCopyNewer; bool bOtherDest = !((pMFI->getDirectoryInfo()->destDir().absoluteFilePath() == pMFI->getDirectoryInfo()->dirA().absoluteFilePath()) || (pMFI->getDirectoryInfo()->destDir().absoluteFilePath() == pMFI->getDirectoryInfo()->dirB().absoluteFilePath()) || (bCheckC && pMFI->getDirectoryInfo()->destDir().absoluteFilePath() == pMFI->getDirectoryInfo()->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(pMFI->isEqualAB()) { setMergeOperation(mi, bOtherDest ? eCopyBToDest : eNoOperation); } else if(pMFI->existsInA() && pMFI->existsInB()) { if(!bCopyNewer || pMFI->isDirA()) setMergeOperation(mi, eDefaultMergeOp); else if(bCopyNewer && pMFI->m_bConflictingAges) { setMergeOperation(mi, eConflictingAges); } else { if(pMFI->getAgeA() == eNew) setMergeOperation(mi, eDefaultMergeOp == eMergeToAB ? eCopyAToB : eCopyAToDest); else setMergeOperation(mi, eDefaultMergeOp == eMergeToAB ? eCopyBToA : eCopyBToDest); } } else if(!pMFI->existsInA() && pMFI->existsInB()) { if(eDefaultMergeOp == eMergeABToDest) setMergeOperation(mi, eCopyBToDest); else if(eDefaultMergeOp == eMergeToB) setMergeOperation(mi, eNoOperation); else setMergeOperation(mi, eCopyBToA); } else if(pMFI->existsInA() && !pMFI->existsInB()) { if(eDefaultMergeOp == eMergeABToDest) setMergeOperation(mi, eCopyAToDest); else if(eDefaultMergeOp == eMergeToA) setMergeOperation(mi, eNoOperation); else setMergeOperation(mi, eCopyAToB); } else //if ( !pMFI->existsInA() && !pMFI->existsInB() ) { setMergeOperation(mi, eNoOperation); } } else { if(pMFI->isEqualAB() && pMFI->isEqualAC()) { setMergeOperation(mi, bOtherDest ? eCopyCToDest : eNoOperation); } else if(pMFI->existsInA() && pMFI->existsInB() && pMFI->existsInC()) { - if(pMFI->isEqualAB()) + if(pMFI->isEqualAB() || pMFI->isEqualBC()) setMergeOperation(mi, eCopyCToDest); else if(pMFI->isEqualAC()) setMergeOperation(mi, eCopyBToDest); - else if(pMFI->isEqualBC()) - setMergeOperation(mi, eCopyCToDest); else setMergeOperation(mi, eMergeABCToDest); } else if(pMFI->existsInA() && pMFI->existsInB() && !pMFI->existsInC()) { if(pMFI->isEqualAB()) setMergeOperation(mi, eDeleteFromDest); else setMergeOperation(mi, eChangedAndDeleted); } else if(pMFI->existsInA() && !pMFI->existsInB() && pMFI->existsInC()) { if(pMFI->isEqualAC()) setMergeOperation(mi, eDeleteFromDest); else setMergeOperation(mi, eChangedAndDeleted); } else if(!pMFI->existsInA() && pMFI->existsInB() && pMFI->existsInC()) { if(pMFI->isEqualBC()) setMergeOperation(mi, eCopyCToDest); else setMergeOperation(mi, eMergeABCToDest); } else if(!pMFI->existsInA() && !pMFI->existsInB() && pMFI->existsInC()) { setMergeOperation(mi, eCopyCToDest); } else if(!pMFI->existsInA() && pMFI->existsInB() && !pMFI->existsInC()) { setMergeOperation(mi, eCopyBToDest); } else if(pMFI->existsInA() && !pMFI->existsInB() && !pMFI->existsInC()) { setMergeOperation(mi, eDeleteFromDest); } else //if ( !pMFI->existsInA() && !pMFI->existsInB() && !pMFI->existsInC() ) { setMergeOperation(mi, eNoOperation); } } // Now check if file/dir-types fit. if(pMFI->conflictingFileTypes()) { 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(!pMFI->existsInA()) { eMO = eDeleteB; } break; case eCopyBToA: if(!pMFI->existsInB()) { eMO = eDeleteA; } break; case eCopyAToDest: if(!pMFI->existsInA()) { eMO = eDeleteFromDest; } break; case eCopyBToDest: if(!pMFI->existsInB()) { eMO = eDeleteFromDest; } break; case eCopyCToDest: if(!pMFI->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(pMFI->getDirectoryInfo()->dirA(), pMFI->getDirectoryInfo()->dirB(), pMFI->getDirectoryInfo()->dirC(), pMFI->getDirectoryInfo()->destDir(), *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; if(c == s_OpCol) { bool bThreeDirs = d->isThreeWay(); QMenu m(this); if(bThreeDirs) { m.addAction(d->m_pDirCurrentDoNothing); int count = 0; if(pMFI->existsInA()) { m.addAction(d->m_pDirCurrentChooseA); ++count; } if(pMFI->existsInB()) { m.addAction(d->m_pDirCurrentChooseB); ++count; } if(pMFI->existsInC()) { m.addAction(d->m_pDirCurrentChooseC); ++count; } if(!pMFI->conflictingFileTypes() && count > 1) m.addAction(d->m_pDirCurrentMerge); m.addAction(d->m_pDirCurrentDelete); } else if(d->m_bSyncMode) { m.addAction(d->m_pDirCurrentSyncDoNothing); if(pMFI->existsInA()) m.addAction(d->m_pDirCurrentSyncCopyAToB); if(pMFI->existsInB()) m.addAction(d->m_pDirCurrentSyncCopyBToA); if(pMFI->existsInA()) m.addAction(d->m_pDirCurrentSyncDeleteA); if(pMFI->existsInB()) m.addAction(d->m_pDirCurrentSyncDeleteB); if(pMFI->existsInA() && pMFI->existsInB()) { m.addAction(d->m_pDirCurrentSyncDeleteAAndB); if(!pMFI->conflictingFileTypes()) { m.addAction(d->m_pDirCurrentSyncMergeToA); m.addAction(d->m_pDirCurrentSyncMergeToB); m.addAction(d->m_pDirCurrentSyncMergeToAAndB); } } } else { m.addAction(d->m_pDirCurrentDoNothing); if(pMFI->existsInA()) { m.addAction(d->m_pDirCurrentChooseA); } if(pMFI->existsInB()) { m.addAction(d->m_pDirCurrentChooseB); } if(!pMFI->conflictingFileTypes() && pMFI->existsInA() && pMFI->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 && pMFI->existsInA()) { itemPath = pMFI->fullNameA(); } else if(c == s_BCol && pMFI->existsInB()) { itemPath = pMFI->fullNameB(); } else if(c == s_CCol && pMFI->existsInC()) { itemPath = pMFI->fullNameC(); } 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 = pMFI->fullNameA(); } else if(c == s_BCol && pMFI->existsInB()) { itemPath = pMFI->fullNameB(); } else if(c == s_CCol && pMFI->existsInC()) { itemPath = pMFI->fullNameC(); } 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::DirectoryMergeWindowPrivate::getFileName(const QModelIndex& mi) { MergeFileInfos* pMFI = getMFI(mi); if(pMFI != nullptr) { return mi.column() == s_ACol ? pMFI->getFileInfoA()->absoluteFilePath() : mi.column() == s_BCol ? pMFI->getFileInfoB()->absoluteFilePath() : mi.column() == s_CCol ? pMFI->getFileInfoC()->absoluteFilePath() : QString(""); } return QString(); } bool DirectoryMergeWindow::DirectoryMergeWindowPrivate::isDir(const QModelIndex& mi) { MergeFileInfos* pMFI = getMFI(mi); if(pMFI != nullptr) { return mi.column() == s_ACol ? pMFI->isDirA() : mi.column() == s_BCol ? pMFI->isDirB() : pMFI->isDirC(); } return false; } void DirectoryMergeWindow::DirectoryMergeWindowPrivate::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 mWindow->updateAvailabilities(); } //TODO //void DirMergeItem::init(MergeFileInfos* pMFI) //{ // pMFI->m_pDMI = this; //no not here // m_pMFI = pMFI; // TotalDiffStatus& tds = pMFI->m_totalDiffStatus; // if ( m_pMFI->dirA() || m_pMFI->dirB() || m_pMFI->isDirC() ) // { // } // else // { // setText( s_UnsolvedCol, QString::number( tds.getUnsolvedConflicts() ) ); // setText( s_SolvedCol, QString::number( tds.getSolvedConflicts() ) ); // setText( s_NonWhiteCol, QString::number( tds.getUnsolvedConflicts() + tds.getSolvedConflicts() - tds.getWhitespaceConflicts() ) ); // setText( s_WhiteCol, QString::number( tds.getWhitespaceConflicts() ) ); // } // setSizeHint( s_ACol, QSize(17,17) ); // Iconsize // setSizeHint( s_BCol, QSize(17,17) ); // Iconsize // setSizeHint( s_CCol, QSize(17,17) ); // Iconsize //} void DirectoryMergeWindow::DirectoryMergeWindowPrivate::sort(int column, Qt::SortOrder order) { Q_UNUSED(column); beginResetModel(); m_pRoot->sort(order); endResetModel(); } void DirectoryMergeWindow::DirectoryMergeWindowPrivate::setMergeOperation(const QModelIndex& mi, e_MergeOperation eMergeOp, bool bRecursive) { MergeFileInfos* pMFI = getMFI(mi); if(pMFI == nullptr) return; if(eMergeOp != pMFI->getOperation()) { pMFI->startOperation(); setOpStatus(mi, eOpStatusNone); } pMFI->setOperation(eMergeOp); if(bRecursive) { e_MergeOperation eChildrenMergeOp = pMFI->getOperation(); if(eChildrenMergeOp == eConflictingFileTypes) eChildrenMergeOp = eMergeABCToDest; for(int childIdx = 0; childIdx < pMFI->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->hasDir())) { emit startDiffMerge( pMFI->existsInA() ? pMFI->getFileInfoA()->absoluteFilePath() : QString(""), pMFI->existsInB() ? pMFI->getFileInfoB()->absoluteFilePath() : QString(""), pMFI->existsInC() ? pMFI->getFileInfoC()->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->hasDir() || pMFI->conflictingFileTypes()); } 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 == pMFI->fullNameDest()) { if(pMFI->getOperation() == eMergeToAB) { bool bSuccess = d->copyFLD(pMFI->fullNameB(), pMFI->fullNameA()); 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); pMFI->setOperation(eCopyBToA); return; } } d->setOpStatus(mi, eOpStatusDone); pMFI->endOperation(); if(d->m_mergeItemList.size() == 1) { d->m_mergeItemList.clear(); d->m_bRealMergeStarted = false; } } emit updateAvailabilities(); } bool DirectoryMergeWindow::DirectoryMergeWindowPrivate::canContinue() { bool bCanContinue = false; emit mWindow->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->isOperationRunning()) { setOpStatus(mi, eOpStatusNotSaved); pMFI->endOperation(); if(m_mergeItemList.size() == 1) { m_mergeItemList.clear(); m_bRealMergeStarted = false; } } } return bCanContinue; } bool DirectoryMergeWindow::DirectoryMergeWindowPrivate::executeMergeOperation(MergeFileInfos& mfi, bool& bSingleFileMerge) { bool bCreateBackups = m_pOptions->m_bDmCreateBakFiles; // First decide destname QString destName; switch(mfi.getOperation()) { 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 = mfi.fullNameB(); break; case eMergeToA: case eDeleteA: case eCopyBToA: destName = mfi.fullNameA(); break; case eMergeABToDest: case eMergeABCToDest: case eCopyAToDest: case eCopyBToDest: case eCopyCToDest: case eDeleteFromDest: destName = mfi.fullNameDest(); break; default: KMessageBox::error(mWindow, i18n("Unknown merge operation. (This must never happen!)")); } bool bSuccess = false; bSingleFileMerge = false; switch(mfi.getOperation()) { case eNoOperation: bSuccess = true; break; case eCopyAToDest: case eCopyAToB: bSuccess = copyFLD(mfi.fullNameA(), destName); break; case eCopyBToDest: case eCopyBToA: bSuccess = copyFLD(mfi.fullNameB(), destName); break; case eCopyCToDest: bSuccess = copyFLD(mfi.fullNameC(), destName); break; case eDeleteFromDest: case eDeleteA: case eDeleteB: bSuccess = deleteFLD(destName, bCreateBackups); break; case eDeleteAB: bSuccess = deleteFLD(mfi.fullNameA(), bCreateBackups) && deleteFLD(mfi.fullNameB(), bCreateBackups); break; case eMergeABToDest: case eMergeToA: case eMergeToAB: case eMergeToB: bSuccess = mergeFLD(mfi.fullNameA(), mfi.fullNameB(), "", destName, bSingleFileMerge); break; case eMergeABCToDest: bSuccess = mergeFLD( mfi.existsInA() ? mfi.fullNameA() : QString(""), mfi.existsInB() ? mfi.fullNameB() : QString(""), mfi.existsInC() ? mfi.fullNameC() : QString(""), destName, bSingleFileMerge); break; default: KMessageBox::error(mWindow, 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::DirectoryMergeWindowPrivate::prepareMergeStart(const QModelIndex& miBegin, const QModelIndex& miEnd, bool bVerbose) { if(bVerbose) { int status = KMessageBox::warningYesNoCancel(mWindow, 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->isOperationRunning()) { m_mergeItemList.push_back(mi); QString errorText; if(pMFI->getOperation() == eConflictingFileTypes) { errorText = i18n("The highlighted item has a different type in the different directories. Select what to do."); } if(pMFI->getOperation() == eConflictingAges) { errorText = i18n("The modification dates of the file are equal but the files are not. Select what to do."); } if(pMFI->getOperation() == eChangedAndDeleted) { errorText = i18n("The highlighted item was changed in one directory and deleted in the other. Select what to do."); } if(!errorText.isEmpty()) { mWindow->scrollTo(mi, QAbstractItemView::EnsureVisible); mWindow->setCurrentIndex(mi); KMessageBox::error(mWindow, errorText); m_mergeItemList.clear(); m_bRealMergeStarted = false; return; } } } m_currentIndexForOperation = m_mergeItemList.begin(); } 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) { d->m_mergeItemList.clear(); d->m_mergeItemList.push_back(currentIndex()); d->m_currentIndexForOperation = d->m_mergeItemList.begin(); bool bDummy = false; d->mergeFLD( pMFI->existsInA() ? pMFI->getFileInfoA()->absoluteFilePath() : QString(""), pMFI->existsInB() ? pMFI->getFileInfoB()->absoluteFilePath() : QString(""), pMFI->existsInC() ? pMFI->getFileInfoC()->absoluteFilePath() : QString(""), pMFI->fullNameDest(), 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::DirectoryMergeWindowPrivate::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->isOperationRunning()) ++nrOfCompletedItems; if(!pMFI->isSimOpRunning()) ++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(mWindow, 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->endSimOp(); } } else { if(rowCount(miCurrent) == 0) { if(pMFI->isOperationRunning()) { setOpStatus(miCurrent, bSkipItem ? eOpStatusSkipped : eOpStatusDone); pMFI->endOperation(); 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->isOperationRunning()) || (bSim && !pMFI->isSimOpRunning())) { bDone = false; break; } } if(bDone) { pMFI = getMFI(miParent); if(bSim) pMFI->endSimOp(); else { setOpStatus(miParent, eOpStatusDone); pMFI->endOperation(); } } miParent = miParent.parent(); } } } if(!miCurrent.isValid()) // end? { if(m_bRealMergeStarted) { if(bVerbose) { KMessageBox::information(mWindow, 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)->startSimOp(); } 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; } pMFI = getMFI(miCurrent); pp.setInformation(pMFI->subPath(), bSim ? nrOfCompletedSimItems : nrOfCompletedItems, false // bRedrawUpdate ); bSuccess = executeMergeOperation(*pMFI, bSingleFileMerge); // Here the real operation happens. if(bSuccess) { if(bSim) ++nrOfCompletedSimItems; else ++nrOfCompletedItems; bContinueWithCurrentItem = false; } if(pp.wasCancelled()) break; } // end while //g_pProgressDialog->hide(); mWindow->setCurrentIndex(miCurrent); mWindow->scrollTo(miCurrent, EnsureVisible); if(!bSuccess && !bSingleFileMerge) { KMessageBox::error(mWindow, 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 mWindow->updateAvailabilities(); if(m_currentIndexForOperation == m_mergeItemList.end()) { m_mergeItemList.clear(); m_bRealMergeStarted = false; } } bool DirectoryMergeWindow::DirectoryMergeWindowPrivate::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; Q_ASSERT(fi2.fileName() != "." && fi2.fileName() != ".."); 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)); // krazy:exclude=syscalls return false; } } } else { bool bSuccess = fi.removeFile(); if(!bSuccess) { m_pStatusInfo->addText(i18n("Error: delete operation failed.")); return false; } } } return true; } bool DirectoryMergeWindow::DirectoryMergeWindowPrivate::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); mWindow->scrollTo(*m_currentIndexForOperation, EnsureVisible); emit mWindow->startDiffMerge(nameA, nameB, nameC, nameDest, "", "", "", nullptr); return false; } bool DirectoryMergeWindow::DirectoryMergeWindowPrivate::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::DirectoryMergeWindowPrivate::renameFLD(const QString& srcName, const QString& destName) { if(srcName == destName) return true; FileAccess destFile = FileAccess(destName, true); if(destFile.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(destFile); if(!bSuccess) { m_pStatusInfo->addText(i18n("Error: Rename failed.")); return false; } return true; } bool DirectoryMergeWindow::DirectoryMergeWindowPrivate::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) { 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() ? i18n("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(i18n("B: ")); m_pInfoB->setText(dirB.prettyAbsPath()); if(dirC.absoluteFilePath() == dirDest.absoluteFilePath()) { m_pC->setText(i18n("C (Dest): ")); bHideDest = true; } else m_pC->setText(i18n("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.getFileInfoA()); addListViewItem(i18n("B"), dirB.prettyAbsPath(), mfi.getFileInfoB()); addListViewItem(i18n("C"), dirC.prettyAbsPath(), mfi.getFileInfoC()); 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); } void DirectoryMergeWindow::slotSaveMergeState() { //slotStatusMsg(i18n("Saving Directory Merge State ...")); QString dirMergeStateFilename = QFileDialog::getSaveFileName(this, i18n("Save Directory Merge State As..."), QDir::currentPath()); if(!dirMergeStateFilename.isEmpty()) { QFile file(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->isThreeWay(); 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->hasDir(); if(loop == 0 && bDir) { bool bChange = false; if(!pMFI->isEqualAB() && pMFI->isDirA() == pMFI->isDirB() && pMFI->isLinkA() == pMFI->isLinkB()) { pMFI->m_bEqualAB = true; bChange = true; } if(!pMFI->isEqualBC() && pMFI->isDirC() == pMFI->isDirB() && pMFI->isLinkC() == pMFI->isLinkB()) { pMFI->m_bEqualBC = true; bChange = true; } if(!pMFI->isEqualAC() && pMFI->isDirA() == pMFI->isDirC() && pMFI->isLinkA() == pMFI->isLinkC()) { pMFI->m_bEqualAC = true; bChange = true; } if(bChange) pMFI->updateAge(); } 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->isEqualAB() && (pMFI->isEqualAC() || !bThreeDirs)) || ((bShowDifferent || bDir) && existCount >= 2 && (!pMFI->isEqualAB() || !(pMFI->isEqualAC() || !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 && !Utils::wildcardMultiMatch(d->m_pOptions->m_DmDirAntiPattern, fileName, d->m_bCaseSensitive)) || (Utils::wildcardMultiMatch(d->m_pOptions->m_DmFilePattern, fileName, d->m_bCaseSensitive) && !Utils::wildcardMultiMatch(d->m_pOptions->m_DmFileAntiPattern, fileName, d->m_bCaseSensitive))); setRowHidden(mi.row(), mi.parent(), !bVisible); bool bEqual = bThreeDirs ? pMFI->isEqualAB() && pMFI->isEqualAC() : pMFI->isEqualAB(); if(!bEqual && bVisible && loop == 0) // Set all parents to "not equal" { MergeFileInfos* p2 = pMFI->parent(); while(p2 != nullptr) { bool bChange = false; if(!pMFI->isEqualAB() && p2->isEqualAB()) { p2->m_bEqualAB = false; bChange = true; } if(!pMFI->isEqualAC() && p2->isEqualAC()) { p2->m_bEqualAC = false; bChange = true; } if(!pMFI->isEqualBC() && p2->isEqualBC()) { p2->m_bEqualBC = false; bChange = true; } if(bChange) p2->updateAge(); else break; p2 = p2->parent(); } } 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(KDiff3App* pKDiff3App, KActionCollection* ac) { #include "xpm/showequalfiles.xpm" #include "xpm/showfilesonlyina.xpm" #include "xpm/showfilesonlyinb.xpm" #include "xpm/showfilesonlyinc.xpm" #include "xpm/startmerge.xpm" d->m_pDirStartOperation = GuiUtils::createAction(i18n("Start/Continue Directory Merge"), QKeySequence(Qt::Key_F7), this, &DirectoryMergeWindow::slotRunOperationForAllItems, ac, "dir_start_operation"); d->m_pDirRunOperationForCurrentItem = GuiUtils::createAction(i18n("Run Operation for Current Item"), QKeySequence(Qt::Key_F6), this, &DirectoryMergeWindow::slotRunOperationForCurrentItem, ac, "dir_run_operation_for_current_item"); d->m_pDirCompareCurrent = GuiUtils::createAction(i18n("Compare Selected File"), this, &DirectoryMergeWindow::compareCurrentFile, ac, "dir_compare_current"); d->m_pDirMergeCurrent = GuiUtils::createAction(i18n("Merge Current File"), QIcon(QPixmap(startmerge)), i18n("Merge\nFile"), pKDiff3App, &KDiff3App::slotMergeCurrentFile, ac, "merge_current"); d->m_pDirFoldAll = GuiUtils::createAction(i18n("Fold All Subdirs"), this, &DirectoryMergeWindow::collapseAll, ac, "dir_fold_all"); d->m_pDirUnfoldAll = GuiUtils::createAction(i18n("Unfold All Subdirs"), this, &DirectoryMergeWindow::expandAll, ac, "dir_unfold_all"); d->m_pDirRescan = GuiUtils::createAction(i18n("Rescan"), QKeySequence(Qt::SHIFT + Qt::Key_F5), this, &DirectoryMergeWindow::reload, ac, "dir_rescan"); d->m_pDirSaveMergeState = nullptr; //GuiUtils::createAction< QAction >(i18n("Save Directory Merge State ..."), 0, this, &DirectoryMergeWindow::slotSaveMergeState, ac, "dir_save_merge_state"); d->m_pDirLoadMergeState = nullptr; //GuiUtils::createAction< QAction >(i18n("Load Directory Merge State ..."), 0, this, &DirectoryMergeWindow::slotLoadMergeState, ac, "dir_load_merge_state"); d->m_pDirChooseAEverywhere = GuiUtils::createAction(i18n("Choose A for All Items"), this, &DirectoryMergeWindow::slotChooseAEverywhere, ac, "dir_choose_a_everywhere"); d->m_pDirChooseBEverywhere = GuiUtils::createAction(i18n("Choose B for All Items"), this, &DirectoryMergeWindow::slotChooseBEverywhere, ac, "dir_choose_b_everywhere"); d->m_pDirChooseCEverywhere = GuiUtils::createAction(i18n("Choose C for All Items"), this, &DirectoryMergeWindow::slotChooseCEverywhere, ac, "dir_choose_c_everywhere"); d->m_pDirAutoChoiceEverywhere = GuiUtils::createAction(i18n("Auto-Choose Operation for All Items"), this, &DirectoryMergeWindow::slotAutoChooseEverywhere, ac, "dir_autochoose_everywhere"); d->m_pDirDoNothingEverywhere = GuiUtils::createAction(i18n("No Operation for All Items"), this, &DirectoryMergeWindow::slotNoOpEverywhere, ac, "dir_nothing_everywhere"); // d->m_pDirSynchronizeDirectories = GuiUtils::createAction< KToggleAction >(i18n("Synchronize Directories"), 0, this, &DirectoryMergeWindow::slotSynchronizeDirectories, ac, "dir_synchronize_directories"); // d->m_pDirChooseNewerFiles = GuiUtils::createAction< KToggleAction >(i18n("Copy Newer Files Instead of Merging"), 0, this, &DirectoryMergeWindow::slotChooseNewerFiles, ac, "dir_choose_newer_files"); d->m_pDirShowIdenticalFiles = GuiUtils::createAction(i18n("Show Identical Files"), QIcon(QPixmap(showequalfiles)), i18n("Identical\nFiles"), this, &DirectoryMergeWindow::slotShowIdenticalFiles, ac, "dir_show_identical_files"); d->m_pDirShowDifferentFiles = GuiUtils::createAction(i18n("Show Different Files"), this, &DirectoryMergeWindow::slotShowDifferentFiles, ac, "dir_show_different_files"); d->m_pDirShowFilesOnlyInA = GuiUtils::createAction(i18n("Show Files only in A"), QIcon(QPixmap(showfilesonlyina)), i18n("Files\nonly in A"), this, &DirectoryMergeWindow::slotShowFilesOnlyInA, ac, "dir_show_files_only_in_a"); d->m_pDirShowFilesOnlyInB = GuiUtils::createAction(i18n("Show Files only in B"), QIcon(QPixmap(showfilesonlyinb)), i18n("Files\nonly in B"), this, &DirectoryMergeWindow::slotShowFilesOnlyInB, ac, "dir_show_files_only_in_b"); d->m_pDirShowFilesOnlyInC = GuiUtils::createAction(i18n("Show Files only in C"), QIcon(QPixmap(showfilesonlyinc)), i18n("Files\nonly in C"), this, &DirectoryMergeWindow::slotShowFilesOnlyInC, ac, "dir_show_files_only_in_c"); d->m_pDirShowIdenticalFiles->setChecked(d->m_pOptions->m_bDmShowIdenticalFiles); d->m_pDirCompareExplicit = GuiUtils::createAction(i18n("Compare Explicitly Selected Files"), this, &DirectoryMergeWindow::slotCompareExplicitlySelectedFiles, ac, "dir_compare_explicitly_selected_files"); d->m_pDirMergeExplicit = GuiUtils::createAction(i18n("Merge Explicitly Selected Files"), this, &DirectoryMergeWindow::slotMergeExplicitlySelectedFiles, ac, "dir_merge_explicitly_selected_files"); d->m_pDirCurrentDoNothing = GuiUtils::createAction(i18n("Do Nothing"), this, &DirectoryMergeWindow::slotCurrentDoNothing, ac, "dir_current_do_nothing"); d->m_pDirCurrentChooseA = GuiUtils::createAction(i18n("A"), this, &DirectoryMergeWindow::slotCurrentChooseA, ac, "dir_current_choose_a"); d->m_pDirCurrentChooseB = GuiUtils::createAction(i18n("B"), this, &DirectoryMergeWindow::slotCurrentChooseB, ac, "dir_current_choose_b"); d->m_pDirCurrentChooseC = GuiUtils::createAction(i18n("C"), this, &DirectoryMergeWindow::slotCurrentChooseC, ac, "dir_current_choose_c"); d->m_pDirCurrentMerge = GuiUtils::createAction(i18n("Merge"), this, &DirectoryMergeWindow::slotCurrentMerge, ac, "dir_current_merge"); d->m_pDirCurrentDelete = GuiUtils::createAction(i18n("Delete (if exists)"), this, &DirectoryMergeWindow::slotCurrentDelete, ac, "dir_current_delete"); d->m_pDirCurrentSyncDoNothing = GuiUtils::createAction(i18n("Do Nothing"), this, &DirectoryMergeWindow::slotCurrentDoNothing, ac, "dir_current_sync_do_nothing"); d->m_pDirCurrentSyncCopyAToB = GuiUtils::createAction(i18n("Copy A to B"), this, &DirectoryMergeWindow::slotCurrentCopyAToB, ac, "dir_current_sync_copy_a_to_b"); d->m_pDirCurrentSyncCopyBToA = GuiUtils::createAction(i18n("Copy B to A"), this, &DirectoryMergeWindow::slotCurrentCopyBToA, ac, "dir_current_sync_copy_b_to_a"); d->m_pDirCurrentSyncDeleteA = GuiUtils::createAction(i18n("Delete A"), this, &DirectoryMergeWindow::slotCurrentDeleteA, ac, "dir_current_sync_delete_a"); d->m_pDirCurrentSyncDeleteB = GuiUtils::createAction(i18n("Delete B"), this, &DirectoryMergeWindow::slotCurrentDeleteB, ac, "dir_current_sync_delete_b"); d->m_pDirCurrentSyncDeleteAAndB = GuiUtils::createAction(i18n("Delete A && B"), this, &DirectoryMergeWindow::slotCurrentDeleteAAndB, ac, "dir_current_sync_delete_a_and_b"); d->m_pDirCurrentSyncMergeToA = GuiUtils::createAction(i18n("Merge to A"), this, &DirectoryMergeWindow::slotCurrentMergeToA, ac, "dir_current_sync_merge_to_a"); d->m_pDirCurrentSyncMergeToB = GuiUtils::createAction(i18n("Merge to B"), this, &DirectoryMergeWindow::slotCurrentMergeToB, ac, "dir_current_sync_merge_to_b"); d->m_pDirCurrentSyncMergeToAAndB = GuiUtils::createAction(i18n("Merge to A && B"), this, &DirectoryMergeWindow::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); bool bThreeDirs = d->isThreeWay(); 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() && bThreeDirs); MergeFileInfos* pMFI = d->getMFI(currentIndex()); bool bItemActive = bDirCompare && isVisible() && pMFI != nullptr; // && hasFocus(); bool bMergeMode = bThreeDirs || !d->m_bSyncMode; bool bFTConflict = pMFI == nullptr ? false : pMFI->conflictingFileTypes(); 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/fileaccess.cpp b/src/fileaccess.cpp index a01b0d9..ccbe14b 100644 --- a/src/fileaccess.cpp +++ b/src/fileaccess.cpp @@ -1,1252 +1,1249 @@ /*************************************************************************** * Copyright (C) 2003-2007 by Joachim Eibl * * Copyright (C) 2018 Michael Reeves reeves.87@gmail.com * * * * 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 "fileaccess.h" #include "cvsignorelist.h" #include "common.h" #include "Logging.h" #include "progress.h" #include "ProgressProxyExtender.h" #include "Utils.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include FileAccess::FileAccess(const QString& name, bool bWantToWrite) { reset(); setFile(name, bWantToWrite); } FileAccess::FileAccess() { reset(); } void FileAccess::reset() { m_fileInfo = QFileInfo(); m_bExists = false; m_bFile = false; m_bDir = false; m_bSymLink = false; m_bWritable = false; m_bHidden = false; m_size = 0; m_modificationTime = QDateTime::fromMSecsSinceEpoch(0); m_url = QUrl(); m_bValidData = false; m_name = QString(); m_linkTarget = ""; //m_fileType = -1; tmpFile.clear(); tmpFile = QSharedPointer::create(); realFile = nullptr; } FileAccess::~FileAccess() { tmpFile.clear(); } /* Needed only during directory listing right now. */ void FileAccess::setFile(FileAccess* pParent, const QFileInfo& fi) { reset(); m_fileInfo = fi; m_url = QUrl::fromLocalFile(m_fileInfo.filePath()); if(!m_url.scheme().isEmpty()) m_url.setScheme(QLatin1Literal("file")); m_pParent = pParent; loadData(); } void FileAccess::setFile(const QString& name, bool bWantToWrite) { if(name.isEmpty()) return; QUrl url = QUrl::fromUserInput(name, QString(), QUrl::AssumeLocalFile); setFile(url, bWantToWrite); } void FileAccess::setFile(const QUrl& url, bool bWantToWrite) { reset(); Q_ASSERT(parent() == nullptr || url != parent()->url()); m_url = url; //QUrl::isLocalFile assumes the scheme is set. if(m_url.scheme().isEmpty()) m_url.setScheme(QLatin1Literal("file")); if(m_url.isLocalFile() || !m_url.isValid()) // Treat invalid urls as local files. { m_fileInfo.setFile(m_url.toLocalFile()); m_pParent = nullptr; loadData(); } else { m_name = m_url.fileName(); FileAccessJobHandler jh(this); // A friend, which writes to the parameters of this class! jh.stat(2 /*all details*/, bWantToWrite); // returns bSuccess, ignored m_bValidData = true; // After running stat() the variables are initialised // and valid even if the file doesn't exist and the stat // query failed. } } void FileAccess::loadData() { m_fileInfo.setCaching(true); if(parent() == nullptr) m_baseDir.setPath(m_fileInfo.absoluteFilePath()); else m_baseDir = m_pParent->m_baseDir; //convert to absolute path that doesn't depend on the current directory. m_fileInfo.makeAbsolute(); m_bSymLink = m_fileInfo.isSymLink(); m_bFile = m_fileInfo.isFile(); m_bDir = m_fileInfo.isDir(); m_bExists = m_fileInfo.exists(); m_size = m_fileInfo.size(); m_modificationTime = m_fileInfo.lastModified(); m_bHidden = m_fileInfo.isHidden(); m_bWritable = m_fileInfo.isWritable(); m_bReadable = m_fileInfo.isReadable(); m_bExecutable = m_fileInfo.isExecutable(); m_name = m_fileInfo.fileName(); if(isLocal() && m_name.isEmpty()) { m_name = m_fileInfo.absoluteDir().dirName(); } if(isLocal() && m_bSymLink) { m_linkTarget = m_fileInfo.symLinkTarget(); #ifndef Q_OS_WIN // Unfortunately Qt5 symLinkTarget/readLink always returns an absolute path, even if the link is relative char* s = (char*)malloc(PATH_MAX + 1); ssize_t len = readlink(QFile::encodeName(absoluteFilePath()).constData(), s, PATH_MAX); if(len > 0) { s[len] = '\0'; m_linkTarget = QFile::decodeName(s); } free(s); #endif } realFile = QSharedPointer::create(absoluteFilePath()); m_bValidData = true; } void FileAccess::addPath(const QString& txt) { if(!isLocal() && m_url.isValid()) { QUrl url = m_url.adjusted(QUrl::StripTrailingSlash); url.setPath(url.path() + '/' + txt); setFile(url); // reinitialise } else { QString slash = (txt.isEmpty() || txt[0] == '/') ? QLatin1String("") : QLatin1String("/"); setFile(absoluteFilePath() + slash + txt); } } /* Filetype: S_IFMT 0170000 bitmask for the file type bitfields S_IFSOCK 0140000 socket S_IFLNK 0120000 symbolic link S_IFREG 0100000 regular file S_IFBLK 0060000 block device S_IFDIR 0040000 directory S_IFCHR 0020000 character device S_IFIFO 0010000 fifo S_ISUID 0004000 set UID bit S_ISGID 0002000 set GID bit (see below) S_ISVTX 0001000 sticky bit (see below) Access: S_IRWXU 00700 mask for file owner permissions S_IRUSR 00400 owner has read permission S_IWUSR 00200 owner has write permission S_IXUSR 00100 owner has execute permission S_IRWXG 00070 mask for group permissions S_IRGRP 00040 group has read permission S_IWGRP 00020 group has write permission S_IXGRP 00010 group has execute permission S_IRWXO 00007 mask for permissions for others (not in group) S_IROTH 00004 others have read permission S_IWOTH 00002 others have write permission S_IXOTH 00001 others have execute permission */ void FileAccess::setFromUdsEntry(const KIO::UDSEntry& e, FileAccess *parent) { long acc = 0; long fileType = 0; QVector fields = e.fields(); QString filePath; m_pParent = parent; for(QVector::ConstIterator ei = fields.constBegin(); ei != fields.constEnd(); ++ei) { uint f = *ei; switch(f) { case KIO::UDSEntry::UDS_SIZE: m_size = e.numberValue(f); break; case KIO::UDSEntry::UDS_NAME: filePath = e.stringValue(f); break; // During listDir the relative path is given here. case KIO::UDSEntry::UDS_MODIFICATION_TIME: m_modificationTime = QDateTime::fromMSecsSinceEpoch(e.numberValue(f)); break; case KIO::UDSEntry::UDS_LINK_DEST: m_linkTarget = e.stringValue(f); break; case KIO::UDSEntry::UDS_ACCESS: { #ifndef Q_OS_WIN acc = e.numberValue(f); m_bReadable = (acc & S_IRUSR) != 0; m_bWritable = (acc & S_IWUSR) != 0; m_bExecutable = (acc & S_IXUSR) != 0; #endif break; } case KIO::UDSEntry::UDS_FILE_TYPE: { fileType = e.numberValue(f); m_bDir = (fileType & QT_STAT_MASK) == QT_STAT_DIR; m_bFile = (fileType & QT_STAT_MASK) == QT_STAT_REG; m_bSymLink = (fileType & QT_STAT_MASK) == QT_STAT_LNK; m_bExists = fileType != 0; //m_fileType = fileType; break; } case KIO::UDSEntry::UDS_URL: m_url = QUrl(e.stringValue(f)); break; case KIO::UDSEntry::UDS_MIME_TYPE: - break; case KIO::UDSEntry::UDS_GUESSED_MIME_TYPE: - break; case KIO::UDSEntry::UDS_XML_PROPERTIES: - break; default: break; } } m_fileInfo = QFileInfo(filePath); m_fileInfo.setCaching(true); if(m_url.isEmpty()) { if(parent != nullptr) { m_url = parent->url().resolved(QUrl(filePath)); //Verify that the scheme doesn't change. Q_ASSERT(m_url.scheme() == parent->url().scheme()); } else { /* Invalid entry we don't know the full url because KIO didn't tell us and there is no parent node supplied. This is a bug if it happens and should be logged . However it is a recoverable error. */ qCWarning(kdiffFileAccess) << i18n("Unable to determine full url. No parent specified."); return; } } m_name = m_fileInfo.fileName(); if(isLocal() && m_name.isEmpty()) { m_name = m_fileInfo.absoluteDir().dirName(); } m_bExists = m_fileInfo.exists(); //insure modification time is initialized if it wasn't already. if(m_modificationTime == QDateTime::fromMSecsSinceEpoch(0)) m_modificationTime = m_fileInfo.lastModified(); m_bValidData = true; m_bSymLink = !m_linkTarget.isEmpty(); #ifndef Q_OS_WIN m_bHidden = m_name[0] == '.'; #endif } bool FileAccess::isValid() const { return m_bValidData; } bool FileAccess::isNormal() const { return !exists() || isFile() || isDir() || isSymLink(); } bool FileAccess::isFile() const { if(!isLocal()) return m_bFile; else return m_fileInfo.isFile(); } bool FileAccess::isDir() const { if(!isLocal()) return m_bDir; else return m_fileInfo.isDir(); } bool FileAccess::isSymLink() const { if(!isLocal()) return m_bSymLink; else return m_fileInfo.isSymLink(); } bool FileAccess::exists() const { if(!isLocal()) return m_bExists; else return m_fileInfo.exists(); } qint64 FileAccess::size() const { if(!isLocal()) return m_size; else return m_fileInfo.size(); } QUrl FileAccess::url() const { QUrl url = m_url; if(url.isLocalFile()) { url = QUrl::fromLocalFile(absoluteFilePath()); } return url; } bool FileAccess::isLocal() const { return m_url.isLocalFile() || !m_url.isValid(); } bool FileAccess::isReadable() const { //This can be very slow in some network setups so use cached value if(!isLocal()) return m_bReadable; else return m_fileInfo.isReadable(); } bool FileAccess::isWritable() const { //This can be very slow in some network setups so use cached value if(!isLocal()) return m_bWritable; else return m_fileInfo.isWritable(); } bool FileAccess::isExecutable() const { //This can be very slow in some network setups so use cached value if(!isLocal()) return m_bExecutable; else return m_fileInfo.isExecutable(); } bool FileAccess::isHidden() const { if(!(isLocal())) return m_bHidden; else return m_fileInfo.isHidden(); } QString FileAccess::readLink() const { return m_linkTarget; } QString FileAccess::absoluteFilePath() const { if(!isLocal()) return m_url.url(); // return complete url return m_fileInfo.absoluteFilePath(); } // Full abs path // Just the name-part of the path, without parent directories QString FileAccess::fileName(bool needTmp) const { if(!isLocal()) return (needTmp) ? m_localCopy : m_name; else return m_name; } QString FileAccess::fileRelPath() const { QString path = m_baseDir.relativeFilePath(m_fileInfo.absoluteFilePath()); return path; } FileAccess* FileAccess::parent() const { return m_pParent; } QString FileAccess::prettyAbsPath() const { return isLocal() ? absoluteFilePath() : m_url.toDisplayString(); } QDateTime FileAccess::lastModified() const { Q_ASSERT(!m_modificationTime.isNull()); return m_modificationTime; } bool FileAccess::interruptableReadFile(void* pDestBuffer, qint64 maxLength) { ProgressProxy pp; const qint64 maxChunkSize = 100000; qint64 i = 0; pp.setMaxNofSteps(maxLength / maxChunkSize + 1); while(i < maxLength) { qint64 nextLength = std::min(maxLength - i, maxChunkSize); qint64 reallyRead = read((char*)pDestBuffer + i, nextLength); if(reallyRead != nextLength) { setStatusText(i18n("Failed to read file: %1", absoluteFilePath())); return false; } i += reallyRead; pp.setCurrent(qFloor(double(i) / maxLength * 100)); if(pp.wasCancelled()) return false; } return true; } bool FileAccess::readFile(void* pDestBuffer, qint64 maxLength) { bool success = false; //Avoid hang on linux for special files. if(!isNormal()) return true; if(isLocal() || !m_localCopy.isEmpty()) { if(open(QIODevice::ReadOnly))//krazy:exclude=syscalls { success = interruptableReadFile(pDestBuffer, maxLength); // maxLength == f.read( (char*)pDestBuffer, maxLength ) close(); } } else { FileAccessJobHandler jh(this); success = jh.get(pDestBuffer, maxLength); } close(); Q_ASSERT(!realFile->isOpen() && !tmpFile->isOpen()); return success; } bool FileAccess::writeFile(const void* pSrcBuffer, qint64 length) { ProgressProxy pp; if(isLocal()) { if(realFile->open(QIODevice::WriteOnly)) { const qint64 maxChunkSize = 100000; pp.setMaxNofSteps(length / maxChunkSize + 1); qint64 i = 0; while(i < length) { qint64 nextLength = std::min(length - i, maxChunkSize); qint64 reallyWritten = realFile->write((char*)pSrcBuffer + i, nextLength); if(reallyWritten != nextLength) { realFile->close(); return false; } i += reallyWritten; pp.step(); if(pp.wasCancelled()) { realFile->close(); return false; } } if(isExecutable()) // value is true if the old file was executable { // Preserve attributes realFile->setPermissions(realFile->permissions() | QFile::ExeUser); } realFile->close(); return true; } } else { FileAccessJobHandler jh(this); bool success = jh.put(pSrcBuffer, length, true /*overwrite*/); close(); Q_ASSERT(!realFile->isOpen() && !tmpFile->isOpen()); return success; } close(); Q_ASSERT(!realFile->isOpen() && !tmpFile->isOpen()); return false; } bool FileAccess::copyFile(const QString& dest) { FileAccessJobHandler jh(this); return jh.copyFile(dest); // Handles local and remote copying. } bool FileAccess::rename(const FileAccess& dest) { FileAccessJobHandler jh(this); return jh.rename(dest); } bool FileAccess::removeFile() { if(isLocal()) { return QDir().remove(absoluteFilePath()); } else { FileAccessJobHandler jh(this); return jh.removeFile(url()); } } bool FileAccess::listDir(t_DirectoryList* pDirList, bool bRecursive, bool bFindHidden, const QString& filePattern, const QString& fileAntiPattern, const QString& dirAntiPattern, bool bFollowDirLinks, bool bUseCvsIgnore) { FileAccessJobHandler jh(this); return jh.listDir(pDirList, bRecursive, bFindHidden, filePattern, fileAntiPattern, dirAntiPattern, bFollowDirLinks, bUseCvsIgnore); } QString FileAccess::getTempName() const { return m_localCopy; } const QString FileAccess::errorString() const { return getStatusText(); } bool FileAccess::open(const QFile::OpenMode flags) { bool result; result = createLocalCopy(); if(!result) { setStatusText(i18n("Creating temp copy of %1 failed.", absoluteFilePath())); return result; } if(m_localCopy.isEmpty() && realFile != nullptr) { bool r = realFile->open(flags); setStatusText(i18n("Opening %1 failed. %2", absoluteFilePath(), realFile->errorString())); return r; } bool r = tmpFile->open(); setStatusText(i18n("Opening %1 failed. %2", tmpFile->fileName(), tmpFile->errorString())); return r; } qint64 FileAccess::read(char* data, const qint64 maxlen) { if(!isNormal()) { //This is not an error special files should be skipped setStatusText(QString()); return 0; } qint64 len = 0; if(m_localCopy.isEmpty() && realFile != nullptr) { len = realFile->read(data, maxlen); if(len != maxlen) { setStatusText(i18n("Error reading from %1. %2", absoluteFilePath(), realFile->errorString())); } } else { len = tmpFile->read(data, maxlen); if(len != maxlen) { setStatusText(i18n("Error reading from %1. %2", absoluteFilePath(), tmpFile->errorString())); } } return len; } void FileAccess::close() { if(m_localCopy.isEmpty() && realFile != nullptr) { realFile->close(); } tmpFile->close(); } bool FileAccess::createLocalCopy() { if(isLocal() || !m_localCopy.isEmpty()) return true; tmpFile->setAutoRemove(true); tmpFile->open(); tmpFile->close(); m_localCopy = tmpFile->fileName(); return copyFile(tmpFile->fileName()); } //static tempfile Generator void FileAccess::createTempFile(QTemporaryFile& tmpFile) { tmpFile.setAutoRemove(true); tmpFile.open(); tmpFile.close(); } bool FileAccess::makeDir(const QString& dirName) { FileAccessJobHandler fh(nullptr); return fh.mkDir(dirName); } bool FileAccess::removeDir(const QString& dirName) { FileAccessJobHandler fh(nullptr); return fh.rmDir(dirName); } bool FileAccess::symLink(const QString& linkTarget, const QString& linkLocation) { if(linkTarget.isEmpty() || linkLocation.isEmpty()) return false; return QFile::link(linkTarget, linkLocation); //FileAccessJobHandler fh(0); //return fh.symLink( linkTarget, linkLocation ); } bool FileAccess::exists(const QString& name) { FileAccess fa(name); return fa.exists(); } // If the size couldn't be determined by stat() then the file is copied to a local temp file. qint64 FileAccess::sizeForReading() { if(!isLocal() && m_size == 0) { // Size couldn't be determined. Copy the file to a local temp place. createLocalCopy(); QString localCopy = tmpFile->fileName(); bool bSuccess = copyFile(localCopy); if(bSuccess) { QFileInfo fi(localCopy); m_size = fi.size(); m_localCopy = localCopy; return m_size; } else { return 0; } } else return size(); } QString FileAccess::getStatusText() const { return m_statusText; } void FileAccess::setStatusText(const QString& s) { m_statusText = s; } QString FileAccess::cleanPath(const QString& path) // static { FileAccess fa(path); if(fa.isLocal()) { return QDir().cleanPath(path); } else { return path; } } bool FileAccess::createBackup(const QString& bakExtension) { if(exists()) { // First rename the existing file to the bak-file. If a bak-file file exists, delete that. QString bakName = absoluteFilePath() + bakExtension; FileAccess bakFile(bakName, true /*bWantToWrite*/); if(bakFile.exists()) { bool bSuccess = bakFile.removeFile(); if(!bSuccess) { setStatusText(i18n("While trying to make a backup, deleting an older backup failed.\nFilename: %1", bakName)); return false; } } bool bSuccess = rename(bakFile); // krazy:exclude=syscalls if(!bSuccess) { setStatusText(i18n("While trying to make a backup, renaming failed.\nFilenames: %1 -> %2", absoluteFilePath(), bakName)); return false; } } return true; } void FileAccess::doError() { m_bExists = false; } void FileAccess::filterList(t_DirectoryList* pDirList, const QString& filePattern, const QString& fileAntiPattern, const QString& dirAntiPattern, const bool bUseCvsIgnore) { CvsIgnoreList cvsIgnoreList; if(bUseCvsIgnore) { cvsIgnoreList.init(*this, pDirList); } //TODO: Ask os for this information don't hard code it. #if defined(Q_OS_WIN) bool bCaseSensitive = false; #else bool bCaseSensitive = true; #endif // Now remove all entries that should be ignored: t_DirectoryList::iterator i; for(i = pDirList->begin(); i != pDirList->end();) { t_DirectoryList::iterator i2 = i; ++i2; QString fileName = i->fileName(); if((i->isFile() && (!Utils::wildcardMultiMatch(filePattern, fileName, bCaseSensitive) || Utils::wildcardMultiMatch(fileAntiPattern, fileName, bCaseSensitive))) || (i->isDir() && Utils::wildcardMultiMatch(dirAntiPattern, fileName, bCaseSensitive)) || (bUseCvsIgnore && cvsIgnoreList.matches(fileName, bCaseSensitive))) { // Remove it pDirList->erase(i); i = i2; } else { ++i; } } } FileAccessJobHandler::FileAccessJobHandler(FileAccess* pFileAccess) { m_pFileAccess = pFileAccess; } bool FileAccessJobHandler::stat(short detail, bool bWantToWrite) { m_bSuccess = false; m_pFileAccess->setStatusText(QString()); KIO::StatJob* pStatJob = KIO::stat(m_pFileAccess->url(), bWantToWrite ? KIO::StatJob::DestinationSide : KIO::StatJob::SourceSide, detail, KIO::HideProgressInfo); connect(pStatJob, &KIO::StatJob::result, this, &FileAccessJobHandler::slotStatResult); ProgressProxy::enterEventLoop(pStatJob, i18n("Getting file status: %1", m_pFileAccess->prettyAbsPath())); return m_bSuccess; } void FileAccessJobHandler::slotStatResult(KJob* pJob) { if(pJob->error() != KJob::NoError) { //pJob->uiDelegate()->showErrorMessage(); m_pFileAccess->doError(); m_bSuccess = true; } else { m_bSuccess = true; const KIO::UDSEntry e = static_cast(pJob)->statResult(); m_pFileAccess->setFromUdsEntry(e, m_pFileAccess); } ProgressProxy::exitEventLoop(); } bool FileAccessJobHandler::get(void* pDestBuffer, long maxLength) { ProgressProxyExtender pp; // Implicitly used in slotPercent() if(maxLength > 0 && !pp.wasCancelled()) { KIO::TransferJob* pJob = KIO::get(m_pFileAccess->url(), KIO::NoReload); m_transferredBytes = 0; m_pTransferBuffer = (char*)pDestBuffer; m_maxLength = maxLength; m_bSuccess = false; m_pFileAccess->setStatusText(QString()); connect(pJob, &KIO::TransferJob::result, this, &FileAccessJobHandler::slotSimpleJobResult); connect(pJob, &KIO::TransferJob::data, this, &FileAccessJobHandler::slotGetData); connect(pJob, SIGNAL(percent(KJob*,ulong)), &pp, SLOT(slotPercent(KJob*,ulong))); ProgressProxy::enterEventLoop(pJob, i18n("Reading file: %1", m_pFileAccess->prettyAbsPath())); return m_bSuccess; } else return true; } void FileAccessJobHandler::slotGetData(KJob* pJob, const QByteArray& newData) { if(pJob->error() != KJob::NoError) { pJob->uiDelegate()->showErrorMessage(); } else { qint64 length = std::min(qint64(newData.size()), m_maxLength - m_transferredBytes); ::memcpy(m_pTransferBuffer + m_transferredBytes, newData.data(), newData.size()); m_transferredBytes += length; } } bool FileAccessJobHandler::put(const void* pSrcBuffer, long maxLength, bool bOverwrite, bool bResume, int permissions) { ProgressProxyExtender pp; // Implicitly used in slotPercent() if(maxLength > 0) { KIO::TransferJob* pJob = KIO::put(m_pFileAccess->url(), permissions, KIO::HideProgressInfo | (bOverwrite ? KIO::Overwrite : KIO::DefaultFlags) | (bResume ? KIO::Resume : KIO::DefaultFlags)); m_transferredBytes = 0; m_pTransferBuffer = (char*)pSrcBuffer; m_maxLength = maxLength; m_bSuccess = false; m_pFileAccess->setStatusText(QString()); connect(pJob, &KIO::TransferJob::result, this, &FileAccessJobHandler::slotPutJobResult); connect(pJob, &KIO::TransferJob::dataReq, this, &FileAccessJobHandler::slotPutData); connect(pJob, SIGNAL(percent(KJob*,ulong)), &pp, SLOT(slotPercent(KJob*,ulong))); ProgressProxy::enterEventLoop(pJob, i18n("Writing file: %1", m_pFileAccess->prettyAbsPath())); return m_bSuccess; } else return true; } void FileAccessJobHandler::slotPutData(KIO::Job* pJob, QByteArray& data) { if(pJob->error() != KJob::NoError) { pJob->uiDelegate()->showErrorMessage(); } else { /* Think twice before doing this in new code. The maxChunkSize must be able to fit a 32-bit int. Given that the fallowing is safe. */ qint64 maxChunkSize = 100000; qint64 length = std::min(maxChunkSize, m_maxLength - m_transferredBytes); data.resize((int)length); if(data.size() == (int)length) { if(length > 0) { ::memcpy(data.data(), m_pTransferBuffer + m_transferredBytes, data.size()); m_transferredBytes += length; } } else { KMessageBox::error(ProgressProxy::getDialog(), i18n("Out of memory")); data.resize(0); m_bSuccess = false; } } } void FileAccessJobHandler::slotPutJobResult(KJob* pJob) { if(pJob->error() != KJob::NoError) { pJob->uiDelegate()->showErrorMessage(); } else { m_bSuccess = (m_transferredBytes == m_maxLength); // Special success condition } ProgressProxy::exitEventLoop(); // Close the dialog, return from exec() } bool FileAccessJobHandler::mkDir(const QString& dirName) { if(dirName.isEmpty()) return false; FileAccess dir(dirName); if(dir.isLocal()) { return QDir().mkdir(dir.absoluteFilePath()); } else { m_bSuccess = false; KIO::SimpleJob* pJob = KIO::mkdir(dir.url()); connect(pJob, &KIO::SimpleJob::result, this, &FileAccessJobHandler::slotSimpleJobResult); ProgressProxy::enterEventLoop(pJob, i18n("Making directory: %1", dirName)); return m_bSuccess; } } bool FileAccessJobHandler::rmDir(const QString& dirName) { if(dirName.isEmpty()) return false; FileAccess fa(dirName); if(fa.isLocal()) { return QDir().rmdir(fa.absoluteFilePath()); } else { m_bSuccess = false; KIO::SimpleJob* pJob = KIO::rmdir(fa.url()); connect(pJob, &KIO::SimpleJob::result, this, &FileAccessJobHandler::slotSimpleJobResult); ProgressProxy::enterEventLoop(pJob, i18n("Removing directory: %1", dirName)); return m_bSuccess; } } bool FileAccessJobHandler::removeFile(const QUrl& fileName) { if(fileName.isEmpty()) return false; else { m_bSuccess = false; KIO::SimpleJob* pJob = KIO::file_delete(fileName, KIO::HideProgressInfo); connect(pJob, &KIO::SimpleJob::result, this, &FileAccessJobHandler::slotSimpleJobResult); ProgressProxy::enterEventLoop(pJob, i18n("Removing file: %1", fileName.toDisplayString())); return m_bSuccess; } } bool FileAccessJobHandler::symLink(const QUrl& linkTarget, const QUrl& linkLocation) { if(linkTarget.isEmpty() || linkLocation.isEmpty()) return false; else { m_bSuccess = false; KIO::CopyJob* pJob = KIO::link(linkTarget, linkLocation, KIO::HideProgressInfo); connect(pJob, &KIO::CopyJob::result, this, &FileAccessJobHandler::slotSimpleJobResult); ProgressProxy::enterEventLoop(pJob, i18n("Creating symbolic link: %1 -> %2", linkLocation.toDisplayString(), linkTarget.toDisplayString())); return m_bSuccess; } } bool FileAccessJobHandler::rename(const FileAccess& destFile) { if(destFile.fileName().isEmpty()) return false; if(m_pFileAccess->isLocal() && destFile.isLocal()) { return QDir().rename(m_pFileAccess->absoluteFilePath(), destFile.absoluteFilePath()); } else { ProgressProxyExtender pp; int permissions = -1; m_bSuccess = false; KIO::FileCopyJob* pJob = KIO::file_move(m_pFileAccess->url(), destFile.url(), permissions, KIO::HideProgressInfo); connect(pJob, &KIO::FileCopyJob::result, this, &FileAccessJobHandler::slotSimpleJobResult); connect(pJob, SIGNAL(percent(KJob*,ulong)), &pp, SLOT(slotPercent(KJob*,ulong))); ProgressProxy::enterEventLoop(pJob, i18n("Renaming file: %1 -> %2", m_pFileAccess->prettyAbsPath(), destFile.prettyAbsPath())); return m_bSuccess; } } void FileAccessJobHandler::slotSimpleJobResult(KJob* pJob) { if(pJob->error() != KJob::NoError) { pJob->uiDelegate()->showErrorMessage(); } else { m_bSuccess = true; } ProgressProxy::exitEventLoop(); // Close the dialog, return from exec() } // Copy local or remote files. bool FileAccessJobHandler::copyFile(const QString& inDest) { ProgressProxyExtender pp; FileAccess dest; dest.setFile(inDest); m_pFileAccess->setStatusText(QString()); if(!m_pFileAccess->isNormal() || !dest.isNormal()) return false; int permissions = (m_pFileAccess->isExecutable() ? 0111 : 0) + (m_pFileAccess->isWritable() ? 0222 : 0) + (m_pFileAccess->isReadable() ? 0444 : 0); m_bSuccess = false; KIO::FileCopyJob* pJob = KIO::file_copy(m_pFileAccess->url(), dest.url(), permissions, KIO::HideProgressInfo|KIO::Overwrite); connect(pJob, &KIO::FileCopyJob::result, this, &FileAccessJobHandler::slotSimpleJobResult); connect(pJob, SIGNAL(percent(KJob*,ulong)), &pp, SLOT(slotPercent(KJob*,ulong))); ProgressProxy::enterEventLoop(pJob, i18n("Copying file: %1 -> %2", m_pFileAccess->prettyAbsPath(), dest.prettyAbsPath())); return m_bSuccess; // Note that the KIO-slave preserves the original date, if this is supported. } bool FileAccessJobHandler::listDir(t_DirectoryList* pDirList, bool bRecursive, bool bFindHidden, const QString& filePattern, const QString& fileAntiPattern, const QString& dirAntiPattern, bool bFollowDirLinks, const bool bUseCvsIgnore) { ProgressProxyExtender pp; m_pDirList = pDirList; m_pDirList->clear(); m_bFindHidden = bFindHidden; m_bRecursive = bRecursive; m_bFollowDirLinks = bFollowDirLinks; // Only relevant if bRecursive==true. m_fileAntiPattern = fileAntiPattern; m_filePattern = filePattern; m_dirAntiPattern = dirAntiPattern; if(pp.wasCancelled()) return true; // Cancelled is not an error. pp.setInformation(i18n("Reading directory: %1", m_pFileAccess->absoluteFilePath()), 0, false); if(m_pFileAccess->isLocal()) { m_bSuccess = true; QDir dir(m_pFileAccess->absoluteFilePath()); dir.setSorting(QDir::Name | QDir::DirsFirst); if(bFindHidden) dir.setFilter(QDir::Files | QDir::Dirs | QDir::Hidden | QDir::System | QDir::NoDotAndDotDot); else dir.setFilter(QDir::Files | QDir::Dirs | QDir::System | QDir::NoDotAndDotDot); QFileInfoList fiList = dir.entryInfoList(); if(fiList.isEmpty()) { /* Sadly Qt provides no error information making this case ambigious. A readablity check is the best we can do. */ m_bSuccess = dir.isReadable(); } else { foreach(const QFileInfo& fi, fiList) // for each file... { if(pp.wasCancelled()) break; Q_ASSERT(fi.fileName() != "." && fi.fileName() != ".."); FileAccess fa; fa.setFile(m_pFileAccess, fi); pDirList->push_back(fa); } } } else { KIO::ListJob* pListJob = nullptr; pListJob = KIO::listDir(m_pFileAccess->url(), KIO::HideProgressInfo, true /*bFindHidden*/); m_bSuccess = false; if(pListJob != nullptr) { connect(pListJob, &KIO::ListJob::entries, this, &FileAccessJobHandler::slotListDirProcessNewEntries); connect(pListJob, &KIO::ListJob::result, this, &FileAccessJobHandler::slotSimpleJobResult); connect(pListJob, &KIO::ListJob::infoMessage, &pp, &ProgressProxyExtender::slotListDirInfoMessage); // This line makes the transfer via fish unreliable.:-( /*if(m_pFileAccess->url().scheme() != QLatin1Literal("fish")){ connect( pListJob, static_cast(&KIO::ListJob::percent), &pp, &ProgressProxyExtender::slotPercent); }*/ ProgressProxy::enterEventLoop(pListJob, i18n("Listing directory: %1", m_pFileAccess->prettyAbsPath())); } } m_pFileAccess->filterList(pDirList, filePattern, fileAntiPattern, dirAntiPattern, bUseCvsIgnore); if(bRecursive) { t_DirectoryList::iterator i; t_DirectoryList subDirsList; for(i = m_pDirList->begin(); i != m_pDirList->end(); ++i) { if(i->isDir() && (!i->isSymLink() || m_bFollowDirLinks)) { t_DirectoryList dirList; i->listDir(&dirList, bRecursive, bFindHidden, filePattern, fileAntiPattern, dirAntiPattern, bFollowDirLinks, bUseCvsIgnore); // append data onto the main list subDirsList.splice(subDirsList.end(), dirList); } } m_pDirList->splice(m_pDirList->end(), subDirsList); } return m_bSuccess; } void FileAccessJobHandler::slotListDirProcessNewEntries(KIO::Job*, const KIO::UDSEntryList& l) { //This function is called for non-local urls. Don't use QUrl::fromLocalFile here as it does not handle these. KIO::UDSEntryList::ConstIterator i; for(i = l.begin(); i != l.end(); ++i) { const KIO::UDSEntry& e = *i; FileAccess fa; fa.setFromUdsEntry(e, m_pFileAccess); //must be manually filtered KDE does not supply API for ignoring these. if(fa.fileName() != "." && fa.fileName() != "..") { //quick fix to preserve behavoir without creating invalid urls. TODO: look for altertive machanism for use with next major release. fa.setFile(fa.url()); m_pDirList->push_back(fa); } } } //#include "fileaccess.moc" diff --git a/src/kdiff3.cpp b/src/kdiff3.cpp index b7680f3..19a204e 100644 --- a/src/kdiff3.cpp +++ b/src/kdiff3.cpp @@ -1,1044 +1,1042 @@ /*************************************************************************** * Copyright (C) 2003-2007 by Joachim Eibl * * Copyright (C) 2018 Michael Reeves reeves.87@gmail.com * * * * 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. * * * ***************************************************************************/ // application specific includes #include "kdiff3.h" #include "directorymergewindow.h" #include "fileaccess.h" #include "guiutils.h" #include "kdiff3_part.h" #include "kdiff3_shell.h" #include "optiondialog.h" #include "progress.h" #include "smalldialogs.h" #include "difftextwindow.h" #include "mergeresultwindow.h" #include "RLPainter.h" #include "Utils.h" #ifndef Q_OS_WIN #include #endif // include files for QT #include #include #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 #include #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(name); m_pKDiff3Part = pKDiff3Part; m_pKDiff3Shell = qobject_cast(pParent); setWindowTitle("KDiff3"); setOpaqueResize(false); // faster resizing setUpdatesEnabled(false); KCrash::initialize(); // 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); // Needed before any file operations via FileAccess happen. if(g_pProgressDialog == nullptr) { 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->getOptions(); 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")); #ifndef Q_OS_WIN if(isatty(fileno(stderr)) != 1) #endif { QPointer pDialog = QPointer(new QDialog(this)); pDialog->setAttribute(Qt::WA_DeleteOnClose); pDialog->setModal(true); pDialog->setWindowTitle(title); QVBoxLayout* pVBoxLayout = new QVBoxLayout(pDialog); QPointer pTextEdit = QPointer(new KTextEdit(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) else { // Launched from a console QTextStream outStream(stdout); outStream << title << "\n"; outStream << s;//newline already appended by parseOptions } #endif exit(1); } } m_sd1.setOptions(m_pOptions); m_sd2.setOptions(m_pOptions); m_sd3.setOptions(m_pOptions); #ifdef ENABLE_AUTO m_bAutoFlag = hasArgs && KDiff3Shell::getParser()->isSet("auto"); #else m_bAutoFlag = false; #endif 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) { QTextStream(stderr) << i18n("Option --auto used, but no output file specified.") << "\n"; } 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]); } //Set m_bDirCompare flag m_bDirCompare = FileAccess(m_sd1.getFilename()).isDir(); 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()); MergeResultWindow::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()) { viewStatusBar->setChecked(m_pOptions->isStatusBarVisable()); slotViewStatusBar(); KToolBar *mainToolBar = toolBar(MAIN_TOOLBAR_NAME); if(mainToolBar != nullptr){ mainToolBar->mainWindow()->addToolBar(m_pOptions->getToolbarPos(), 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; 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, this); 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, static_cast(&DirectoryMergeWindow::updateAvailabilities), this, &KDiff3App::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(QString()); } } void KDiff3App::completeInit(const QString& fn1, const QString& fn2, const QString& fn3) { if(m_pKDiff3Shell != nullptr) { QSize size = m_pOptions->getGeometry(); QPoint pos = m_pOptions->getPosition(); 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) { //Here we want the extra setup showMaximized does since the window has not be shown before if(m_pOptions->isMaximised()) m_pKDiff3Shell->showMaximized();// krazy:exclude=qmethods 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); } //should not happen now. Q_ASSERT(m_bDirCompare == FileAccess(m_sd1.getFilename()).isDir()); bool bSuccess = improveFilenames(false); if(m_bAutoFlag && m_bAutoMode && m_bDirCompare) { QTextStream(stderr) << i18n("Option --auto ignored for directory comparison.") << "\n"; m_bAutoMode = false; } if(!m_bDirCompare) { m_pDirectoryMergeSplitter->hide(); mainInit(); if(m_bAutoMode) { SourceData* pSD = nullptr; if(m_sd3.isEmpty()) { if(m_totalDiffStatus.isBinaryEqualAB()) { pSD = &m_sd1; } } else { - if(m_totalDiffStatus.isBinaryEqualBC()) { - pSD = &m_sd3; // B==C (assume A is old) - } - else if(m_totalDiffStatus.isBinaryEqualAB()) + if(m_totalDiffStatus.isBinaryEqualBC() || m_totalDiffStatus.isBinaryEqualAB()) { - pSD = &m_sd3; // assuming C has changed + //if B==C (assume A is old), if A==B then C has changed + pSD = &m_sd3; } else if(m_totalDiffStatus.isBinaryEqualAC()) { pSD = &m_sd2; // assuming B has changed } } if(pSD != nullptr) { // Save this file directly, not via the merge result window. FileAccess fa(m_outputFilename); if(m_pOptions->m_bDmCreateBakFiles && fa.exists()) { fa.createBackup(".orig"); } 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 != nullptr) { if(m_pOptions->isMaximised()) //We want showMaximized here as the window has never been shown. m_pKDiff3Shell->showMaximized();// krazy:exclude=qmethods 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 cannot recover from this. } fileOpen = KStandardAction::open(this, &KDiff3App::slotFileOpen, ac); fileOpen->setStatusTip(i18n("Opens documents for comparison...")); fileReload = GuiUtils::createAction(i18n("Reload"), QKeySequence(QKeySequence::Refresh), this, &KDiff3App::slotReload, ac, QLatin1String("file_reload")); fileSave = KStandardAction::save(this, &KDiff3App::slotFileSave, ac); fileSave->setStatusTip(i18n("Saves the merge result. All conflicts must be solved!")); fileSaveAs = KStandardAction::saveAs(this, &KDiff3App::slotFileSaveAs, ac); fileSaveAs->setStatusTip(i18n("Saves the current document as...")); #ifndef QT_NO_PRINTER filePrint = KStandardAction::print(this, &KDiff3App::slotFilePrint, ac); filePrint->setStatusTip(i18n("Print the differences")); #endif fileQuit = KStandardAction::quit(this, &KDiff3App::slotFileQuit, ac); fileQuit->setStatusTip(i18n("Quits the application")); editCut = KStandardAction::cut(this, &KDiff3App::slotEditCut, ac); editCut->setShortcuts(QKeySequence::Cut); editCut->setStatusTip(i18n("Cuts the selected section and puts it to the clipboard")); editCopy = KStandardAction::copy(this, &KDiff3App::slotEditCopy, ac); editCopy->setShortcut(QKeySequence::Copy); editCopy->setStatusTip(i18n("Copies the selected section to the clipboard")); editPaste = KStandardAction::paste(this, &KDiff3App::slotEditPaste, ac); editPaste->setStatusTip(i18n("Pastes the clipboard contents to current position")); editCut->setShortcut(QKeySequence::Paste); editSelectAll = KStandardAction::selectAll(this, &KDiff3App::slotEditSelectAll, ac); editSelectAll->setStatusTip(i18n("Select everything in current window")); editFind = KStandardAction::find(this, &KDiff3App::slotEditFind, ac); editFind->setShortcut(QKeySequence::Find); editFind->setStatusTip(i18n("Search for a string")); editFindNext = KStandardAction::findNext(this, &KDiff3App::slotEditFindNext, ac); editFindNext->setStatusTip(i18n("Search again for the string")); /* FIXME figure out how to implement this action viewToolBar = KStandardAction::showToolbar(this, &KDiff3App::slotViewToolBar, ac); viewToolBar->setStatusTip(i18n("Enables/disables the toolbar")); */ viewStatusBar = KStandardAction::showStatusbar(this, &KDiff3App::slotViewStatusBar, ac); viewStatusBar->setStatusTip(i18n("Enables/disables the statusbar")); KStandardAction::keyBindings(this, &KDiff3App::slotConfigureKeys, ac); QAction* pAction = KStandardAction::preferences(this, &KDiff3App::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" goCurrent = GuiUtils::createAction(i18n("Go to Current Delta"), QIcon(QPixmap(currentpos)), i18n("Current\nDelta"), QKeySequence(Qt::CTRL + Qt::Key_Space), this, &KDiff3App::slotGoCurrent, ac, "go_current"); goTop = GuiUtils::createAction(i18n("Go to First Delta"), QIcon(QPixmap(upend)), i18n("First\nDelta"), this, &KDiff3App::slotGoTop, ac, "go_top"); goBottom = GuiUtils::createAction(i18n("Go to Last Delta"), QIcon(QPixmap(downend)), i18n("Last\nDelta"), this, &KDiff3App::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 = GuiUtils::createAction(i18n("Go to Previous Delta"), QIcon(QPixmap(up1arrow)), i18n("Prev\nDelta"), QKeySequence(Qt::CTRL + Qt::Key_Up), this, &KDiff3App::slotGoPrevDelta, ac, "go_prev_delta"); goPrevDelta->setToolTip(goPrevDelta->text() + omitsWhitespace); goNextDelta = GuiUtils::createAction(i18n("Go to Next Delta"), QIcon(QPixmap(down1arrow)), i18n("Next\nDelta"), QKeySequence(Qt::CTRL + Qt::Key_Down), this, &KDiff3App::slotGoNextDelta, ac, "go_next_delta"); goNextDelta->setToolTip(goNextDelta->text() + omitsWhitespace); goPrevConflict = GuiUtils::createAction(i18n("Go to Previous Conflict"), QIcon(QPixmap(up2arrow)), i18n("Prev\nConflict"), QKeySequence(Qt::CTRL + Qt::Key_PageUp), this, &KDiff3App::slotGoPrevConflict, ac, "go_prev_conflict"); goPrevConflict->setToolTip(goPrevConflict->text() + omitsWhitespace); goNextConflict = GuiUtils::createAction(i18n("Go to Next Conflict"), QIcon(QPixmap(down2arrow)), i18n("Next\nConflict"), QKeySequence(Qt::CTRL + Qt::Key_PageDown), this, &KDiff3App::slotGoNextConflict, ac, "go_next_conflict"); goNextConflict->setToolTip(goNextConflict->text() + omitsWhitespace); goPrevUnsolvedConflict = GuiUtils::createAction(i18n("Go to Previous Unsolved Conflict"), QIcon(QPixmap(prevunsolved)), i18n("Prev\nUnsolved"), this, &KDiff3App::slotGoPrevUnsolvedConflict, ac, "go_prev_unsolved_conflict"); goPrevUnsolvedConflict->setToolTip(goPrevUnsolvedConflict->text() + includeWhitespace); goNextUnsolvedConflict = GuiUtils::createAction(i18n("Go to Next Unsolved Conflict"), QIcon(QPixmap(nextunsolved)), i18n("Next\nUnsolved"), this, &KDiff3App::slotGoNextUnsolvedConflict, ac, "go_next_unsolved_conflict"); goNextUnsolvedConflict->setToolTip(goNextUnsolvedConflict->text() + includeWhitespace); chooseA = GuiUtils::createAction(i18n("Select Line(s) From A"), QIcon(QPixmap(iconA)), i18n("Choose\nA"), QKeySequence(Qt::CTRL + Qt::Key_1), this, &KDiff3App::slotChooseA, ac, "merge_choose_a"); chooseB = GuiUtils::createAction(i18n("Select Line(s) From B"), QIcon(QPixmap(iconB)), i18n("Choose\nB"), QKeySequence(Qt::CTRL + Qt::Key_2), this, &KDiff3App::slotChooseB, ac, "merge_choose_b"); chooseC = GuiUtils::createAction(i18n("Select Line(s) From C"), QIcon(QPixmap(iconC)), i18n("Choose\nC"), QKeySequence(Qt::CTRL + Qt::Key_3), this, &KDiff3App::slotChooseC, ac, "merge_choose_c"); autoAdvance = GuiUtils::createAction(i18n("Automatically Go to Next Unsolved Conflict After Source Selection"), QIcon(QPixmap(autoadvance)), i18n("Auto\nNext"), this, &KDiff3App::slotAutoAdvanceToggled, ac, "merge_autoadvance"); showWhiteSpaceCharacters = GuiUtils::createAction(i18n("Show Space && Tabulator Characters"), QIcon(QPixmap(showwhitespacechars)), i18n("White\nCharacters"), this, &KDiff3App::slotShowWhiteSpaceToggled, ac, "diff_show_whitespace_characters"); showWhiteSpace = GuiUtils::createAction(i18n("Show White Space"), QIcon(QPixmap(showwhitespace)), i18n("White\nDeltas"), this, &KDiff3App::slotShowWhiteSpaceToggled, ac, "diff_show_whitespace"); showLineNumbers = GuiUtils::createAction(i18n("Show Line Numbers"), QIcon(QPixmap(showlinenumbers)), i18n("Line\nNumbers"), this, &KDiff3App::slotShowLineNumbersToggled, ac, "diff_showlinenumbers"); autoSolve = GuiUtils::createAction(i18n("Automatically Solve Simple Conflicts"), this, &KDiff3App::slotAutoSolve, ac, "merge_autosolve"); unsolve = GuiUtils::createAction(i18n("Set Deltas to Conflicts"), this, &KDiff3App::slotUnsolve, ac, "merge_autounsolve"); mergeRegExp = GuiUtils::createAction(i18n("Run Regular Expression Auto Merge"), this, &KDiff3App::slotRegExpAutoMerge, ac, "merge_regexp_automerge"); mergeHistory = GuiUtils::createAction(i18n("Automatically Solve History Conflicts"), this, &KDiff3App::slotMergeHistory, ac, "merge_versioncontrol_history"); splitDiff = GuiUtils::createAction(i18n("Split Diff At Selection"), this, &KDiff3App::slotSplitDiff, ac, "merge_splitdiff"); joinDiffs = GuiUtils::createAction(i18n("Join Selected Diffs"), this, &KDiff3App::slotJoinDiffs, ac, "merge_joindiffs"); showWindowA = GuiUtils::createAction(i18n("Show Window A"), this, &KDiff3App::slotShowWindowAToggled, ac, "win_show_a"); showWindowB = GuiUtils::createAction(i18n("Show Window B"), this, &KDiff3App::slotShowWindowBToggled, ac, "win_show_b"); showWindowC = GuiUtils::createAction(i18n("Show Window C"), this, &KDiff3App::slotShowWindowCToggled, ac, "win_show_c"); overviewModeNormal = GuiUtils::createAction(i18n("Normal Overview"), this, &KDiff3App::slotOverviewNormal, ac, "diff_overview_normal"); overviewModeAB = GuiUtils::createAction(i18n("A vs. B Overview"), this, &KDiff3App::slotOverviewAB, ac, "diff_overview_ab"); overviewModeAC = GuiUtils::createAction(i18n("A vs. C Overview"), this, &KDiff3App::slotOverviewAC, ac, "diff_overview_ac"); overviewModeBC = GuiUtils::createAction(i18n("B vs. C Overview"), this, &KDiff3App::slotOverviewBC, ac, "diff_overview_bc"); wordWrap = GuiUtils::createAction(i18n("Word Wrap Diff Windows"), this, &KDiff3App::slotWordWrapToggled, ac, "diff_wordwrap"); addManualDiffHelp = GuiUtils::createAction(i18n("Add Manual Diff Alignment"), QKeySequence(Qt::CTRL + Qt::Key_Y), this, &KDiff3App::slotAddManualDiffHelp, ac, "diff_add_manual_diff_help"); clearManualDiffHelpList = GuiUtils::createAction(i18n("Clear All Manual Diff Alignments"), QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_Y), this, &KDiff3App::slotClearManualDiffHelpList, ac, "diff_clear_manual_diff_help_list"); winFocusNext = GuiUtils::createAction(i18n("Focus Next Window"), QKeySequence(Qt::ALT + Qt::Key_Right), this, &KDiff3App::slotWinFocusNext, ac, "win_focus_next"); winFocusPrev = GuiUtils::createAction(i18n("Focus Prev Window"), QKeySequence(Qt::ALT + Qt::Key_Left), this, &KDiff3App::slotWinFocusPrev, ac, "win_focus_prev"); winToggleSplitOrientation = GuiUtils::createAction(i18n("Toggle Split Orientation"), this, &KDiff3App::slotWinToggleSplitterOrientation, ac, "win_toggle_split_orientation"); dirShowBoth = GuiUtils::createAction(i18n("Dir && Text Split Screen View"), this, &KDiff3App::slotDirShowBoth, ac, "win_dir_show_both"); dirShowBoth->setChecked(true); dirViewToggle = GuiUtils::createAction(i18n("Toggle Between Dir && Text View"), this, &KDiff3App::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->setMaximised(m_pKDiff3Shell->isMaximized()); if(!m_pKDiff3Shell->isMaximized() && m_pKDiff3Shell->isVisible()) { m_pOptions->setGeometry(m_pKDiff3Shell->size()); m_pOptions->setPosition(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(std::move(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 KDiff3App::slotFilePrint() { if(m_pDiffTextWindow1 == nullptr) return; #ifdef QT_NO_PRINTER slotStatusMsg(i18n("Printing not implemented.")); #else QPrinter printer; QPointer printDialog=QPointer(new QPrintDialog(&printer, this)); LineRef firstSelectionD3LIdx; LineRef lastSelectionD3LIdx; m_pDiffTextWindow1->getSelectionRange(&firstSelectionD3LIdx, &lastSelectionD3LIdx, eD3LLineCoords); if(firstSelectionD3LIdx < 0 && m_pDiffTextWindow2 != nullptr) { m_pDiffTextWindow2->getSelectionRange(&firstSelectionD3LIdx, &lastSelectionD3LIdx, eD3LLineCoords); } if(firstSelectionD3LIdx < 0 && m_pDiffTextWindow3 != nullptr) { 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 RLPainter painter(&printer, m_pOptions->m_bRightToLeftLanguage, width(), Utils::getHorizontalAdvance(fontMetrics(),'W')); QPaintDevice* pPaintDevice = painter.device(); int dpiy = pPaintDevice->logicalDpiY(); int columnDistance = qRound((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 = Utils::getHorizontalAdvance(fm, 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(); } LineCount totalNofLines = std::max(m_pDiffTextWindow1->getNofLines(), m_pDiffTextWindow2->getNofLines()); if(m_bTripleDiff && m_pDiffTextWindow3 != nullptr) 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; LineRef selectionEndLine; 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); m_pDiffTextWindow1->printWindow(painter, view1, headerText1, 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); m_pDiffTextWindow2->printWindow(painter, view2, headerText2, line, linesPerPage, m_pOptions->m_fgColor); if(m_bTripleDiff && m_pDiffTextWindow3 != nullptr) { painter.setPen(m_pOptions->m_colorC); QString headerText3 = m_sd3.getAliasName() + ", " + topLineText + ": " + QString::number(m_pDiffTextWindow3->calcTopLineInFile(line) + 1); m_pDiffTextWindow3->printWindow(painter, view3, headerText3, 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() - Utils::getHorizontalAdvance(painter.fontMetrics(), 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->setToolbarState(viewToolBar->isChecked()); /////////////////////////////////////////////////////////////////// // turn Toolbar on or off if(toolBar(MAIN_TOOLBAR_NAME) != nullptr) { if(!m_pOptions->isToolBarVisable()) { toolBar(MAIN_TOOLBAR_NAME)->hide(); } else { toolBar(MAIN_TOOLBAR_NAME)->show(); } } slotStatusMsg(i18n("Ready.")); } void KDiff3App::slotViewStatusBar() { slotStatusMsg(i18n("Toggle the statusbar...")); m_pOptions->setStatusBarState(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" diff --git a/src/mergeresultwindow.cpp b/src/mergeresultwindow.cpp index 78adce8..a1bcaa8 100644 --- a/src/mergeresultwindow.cpp +++ b/src/mergeresultwindow.cpp @@ -1,3329 +1,3316 @@ /*************************************************************************** * Copyright (C) 2002-2007 by Joachim Eibl * * Copyright (C) 2018 Michael Reeves reeves.87@gmail.com * * * * 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 "mergeresultwindow.h" #include "options.h" #include "RLPainter.h" #include "guiutils.h" #include "Utils.h" // for Utils #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 #include #include #include #include #include #include #include #include #include bool g_bAutoSolve = true; #undef leftInfoWidth QAction* MergeResultWindow::chooseAEverywhere = nullptr; QAction* MergeResultWindow::chooseBEverywhere = nullptr; QAction* MergeResultWindow::chooseCEverywhere = nullptr; QAction* MergeResultWindow::chooseAForUnsolvedConflicts = nullptr; QAction* MergeResultWindow::chooseBForUnsolvedConflicts = nullptr; QAction* MergeResultWindow::chooseCForUnsolvedConflicts = nullptr; QAction* MergeResultWindow::chooseAForUnsolvedWhiteSpaceConflicts = nullptr; QAction* MergeResultWindow::chooseBForUnsolvedWhiteSpaceConflicts = nullptr; QAction* MergeResultWindow::chooseCForUnsolvedWhiteSpaceConflicts = nullptr; MergeResultWindow::MergeResultWindow( QWidget* pParent, const QSharedPointer &pOptions, QStatusBar* pStatusBar) : QWidget(pParent) { setObjectName("MergeResultWindow"); setFocusPolicy(Qt::ClickFocus); m_firstLine = 0; m_horizScrollOffset = 0; m_nofLines = 0; m_bMyUpdate = false; m_bInsertMode = true; m_scrollDeltaX = 0; m_scrollDeltaY = 0; m_bModified = false; m_eOverviewMode = Overview::eOMNormal; m_pldA = nullptr; m_pldB = nullptr; m_pldC = nullptr; m_sizeA = 0; m_sizeB = 0; m_sizeC = 0; m_pDiff3LineList = nullptr; m_pTotalDiffStatus = nullptr; m_pStatusBar = pStatusBar; if(m_pStatusBar != nullptr) connect(m_pStatusBar, &QStatusBar::messageChanged, this, &MergeResultWindow::slotStatusMessageChanged); m_pOptions = pOptions; setUpdatesEnabled(false); m_delayedDrawTimer = 0; m_cursorXPos = 0; m_cursorOldXPixelPos = 0; m_cursorYPos = 0; m_bCursorOn = true; m_bCursorUpdate = false; m_maxTextWidth = -1; connect(&m_cursorTimer, &QTimer::timeout, this, &MergeResultWindow::slotCursorUpdate); m_cursorTimer.setSingleShot(true); m_cursorTimer.start(500 /*ms*/); m_selection.reset(); setMinimumSize(QSize(20, 20)); setFont(m_pOptions->m_font); } void MergeResultWindow::init( const QVector* pLineDataA, LineRef sizeA, const QVector* pLineDataB, LineRef sizeB, const QVector* pLineDataC, LineRef sizeC, const Diff3LineList* pDiff3LineList, TotalDiffStatus* pTotalDiffStatus) { m_firstLine = 0; m_horizScrollOffset = 0; m_nofLines = 0; m_bMyUpdate = false; m_bInsertMode = true; m_scrollDeltaX = 0; m_scrollDeltaY = 0; setModified(false); m_pldA = pLineDataA; m_pldB = pLineDataB; m_pldC = pLineDataC; m_sizeA = sizeA; m_sizeB = sizeB; m_sizeC = sizeC; m_pDiff3LineList = pDiff3LineList; m_pTotalDiffStatus = pTotalDiffStatus; m_selection.reset(); m_cursorXPos = 0; m_cursorOldXPixelPos = 0; m_cursorYPos = 0; m_maxTextWidth = -1; merge(g_bAutoSolve, Invalid); g_bAutoSolve = true; update(); updateSourceMask(); showUnsolvedConflictsStatusMessage(); } //This must be called before KXMLGui::SetXMLFile and friends or the actions will not be shown in the menu. //At that point in startup we don't have a MergeResultWindow object so we cannot connect the signals yet. void MergeResultWindow::initActions(KActionCollection* ac) { if(ac == nullptr){ KMessageBox::error(nullptr, "actionCollection==0"); exit(-1);//we cannot recover from this. } chooseAEverywhere = GuiUtils::createAction(i18n("Choose A Everywhere"), QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_1), ac, "merge_choose_a_everywhere"); chooseBEverywhere = GuiUtils::createAction(i18n("Choose B Everywhere"), QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_2), ac, "merge_choose_b_everywhere"); chooseCEverywhere = GuiUtils::createAction(i18n("Choose C Everywhere"), QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_3), ac, "merge_choose_c_everywhere"); chooseAForUnsolvedConflicts = GuiUtils::createAction(i18n("Choose A for All Unsolved Conflicts"), ac, "merge_choose_a_for_unsolved_conflicts"); chooseBForUnsolvedConflicts = GuiUtils::createAction(i18n("Choose B for All Unsolved Conflicts"), ac, "merge_choose_b_for_unsolved_conflicts"); chooseCForUnsolvedConflicts = GuiUtils::createAction(i18n("Choose C for All Unsolved Conflicts"), ac, "merge_choose_c_for_unsolved_conflicts"); chooseAForUnsolvedWhiteSpaceConflicts = GuiUtils::createAction(i18n("Choose A for All Unsolved Whitespace Conflicts"), ac, "merge_choose_a_for_unsolved_whitespace_conflicts"); chooseBForUnsolvedWhiteSpaceConflicts = GuiUtils::createAction(i18n("Choose B for All Unsolved Whitespace Conflicts"), ac, "merge_choose_b_for_unsolved_whitespace_conflicts"); chooseCForUnsolvedWhiteSpaceConflicts = GuiUtils::createAction(i18n("Choose C for All Unsolved Whitespace Conflicts"), ac, "merge_choose_c_for_unsolved_whitespace_conflicts"); } void MergeResultWindow::connectActions() { QObject::connect(chooseAEverywhere, &QAction::triggered, this, &MergeResultWindow::slotChooseAEverywhere); QObject::connect(chooseBEverywhere, &QAction::triggered, this, &MergeResultWindow::slotChooseBEverywhere); QObject::connect(chooseCEverywhere, &QAction::triggered, this, &MergeResultWindow::slotChooseCEverywhere); QObject::connect(chooseAForUnsolvedConflicts, &QAction::triggered, this, &MergeResultWindow::slotChooseAForUnsolvedConflicts); QObject::connect(chooseBForUnsolvedConflicts, &QAction::triggered, this, &MergeResultWindow::slotChooseBForUnsolvedConflicts); QObject::connect(chooseCForUnsolvedConflicts, &QAction::triggered, this, &MergeResultWindow::slotChooseCForUnsolvedConflicts); QObject::connect(chooseAForUnsolvedWhiteSpaceConflicts, &QAction::triggered, this, &MergeResultWindow::slotChooseAForUnsolvedWhiteSpaceConflicts); QObject::connect(chooseBForUnsolvedWhiteSpaceConflicts, &QAction::triggered, this, &MergeResultWindow::slotChooseBForUnsolvedWhiteSpaceConflicts); QObject::connect(chooseCForUnsolvedWhiteSpaceConflicts, &QAction::triggered, this, &MergeResultWindow::slotChooseCForUnsolvedWhiteSpaceConflicts); } void MergeResultWindow::showUnsolvedConflictsStatusMessage() { if(m_pStatusBar != nullptr) { int wsc; int nofUnsolved = getNrOfUnsolvedConflicts(&wsc); m_persistentStatusMessage = i18n("Number of remaining unsolved conflicts: %1 (of which %2 are whitespace)", nofUnsolved, wsc); m_pStatusBar->showMessage(m_persistentStatusMessage); } } void MergeResultWindow::slotUpdateAvailabilities(bool bMergeEditorVisible,const bool bTripleDiff) { chooseAEverywhere->setEnabled(bMergeEditorVisible); chooseBEverywhere->setEnabled(bMergeEditorVisible); chooseCEverywhere->setEnabled(bMergeEditorVisible && bTripleDiff); chooseAForUnsolvedConflicts->setEnabled(bMergeEditorVisible); chooseBForUnsolvedConflicts->setEnabled(bMergeEditorVisible); chooseCForUnsolvedConflicts->setEnabled(bMergeEditorVisible && bTripleDiff); chooseAForUnsolvedWhiteSpaceConflicts->setEnabled(bMergeEditorVisible); chooseBForUnsolvedWhiteSpaceConflicts->setEnabled(bMergeEditorVisible); chooseCForUnsolvedWhiteSpaceConflicts->setEnabled(bMergeEditorVisible && bTripleDiff); } void MergeResultWindow::slotStatusMessageChanged(const QString& s) { if(s.isEmpty() && !m_persistentStatusMessage.isEmpty()) { m_pStatusBar->showMessage(m_persistentStatusMessage, 0); } } void MergeResultWindow::reset() { m_pDiff3LineList = nullptr; m_pTotalDiffStatus = nullptr; m_pldA = nullptr; m_pldB = nullptr; m_pldC = nullptr; if(!m_persistentStatusMessage.isEmpty()) { m_persistentStatusMessage = QString(); } } // Calculate the merge information for the given Diff3Line. // Results will be stored in mergeDetails, bConflict, bLineRemoved and src. void Diff3Line::mergeOneLine( e_MergeDetails& mergeDetails, bool& bConflict, bool& bLineRemoved, e_SrcSelector& src, bool bTwoInputs) const { mergeDetails = eDefault; bConflict = false; bLineRemoved = false; src = None; if(bTwoInputs) // Only two input files { if(getLineA().isValid() && getLineB().isValid()) { if(pFineAB == nullptr) { mergeDetails = eNoChange; src = A; } else { mergeDetails = eBChanged; bConflict = true; } } else { - if(getLineA().isValid() && !getLineB().isValid()) - { - mergeDetails = eBDeleted; - bConflict = true; - } - else if(!getLineA().isValid() && getLineB().isValid()) - { - mergeDetails = eBDeleted; - bConflict = true; - } + mergeDetails = eBDeleted; + bConflict = true; } return; } // A is base. if(getLineA().isValid() && getLineB().isValid() && getLineC().isValid()) { if(pFineAB == nullptr && pFineBC == nullptr && pFineCA == nullptr) { mergeDetails = eNoChange; src = A; } else if(pFineAB == nullptr && pFineBC != nullptr && pFineCA != nullptr) { mergeDetails = eCChanged; src = C; } else if(pFineAB != nullptr && pFineBC != nullptr && pFineCA == nullptr) { mergeDetails = eBChanged; src = B; } else if(pFineAB != nullptr && pFineBC == nullptr && pFineCA != nullptr) { mergeDetails = eBCChangedAndEqual; src = C; } else if(pFineAB != nullptr && pFineBC != nullptr && pFineCA != nullptr) { mergeDetails = eBCChanged; bConflict = true; } else Q_ASSERT(true); } else if(getLineA().isValid() && getLineB().isValid() && !getLineC().isValid()) { if(pFineAB != nullptr) { mergeDetails = eBChanged_CDeleted; bConflict = true; } else { mergeDetails = eCDeleted; bLineRemoved = true; src = C; } } else if(getLineA().isValid() && !getLineB().isValid() && getLineC().isValid()) { if(pFineCA != nullptr) { mergeDetails = eCChanged_BDeleted; bConflict = true; } else { mergeDetails = eBDeleted; bLineRemoved = true; src = B; } } else if(!getLineA().isValid() && getLineB().isValid() && getLineC().isValid()) { if(pFineBC != nullptr) { mergeDetails = eBCAdded; bConflict = true; } else // B==C { mergeDetails = eBCAddedAndEqual; src = C; } } else if(!getLineA().isValid() && !getLineB().isValid() && getLineC().isValid()) { mergeDetails = eCAdded; src = C; } else if(!getLineA().isValid() && getLineB().isValid() && !getLineC().isValid()) { mergeDetails = eBAdded; src = B; } else if(getLineA().isValid() && !getLineB().isValid() && !getLineC().isValid()) { mergeDetails = eBCDeleted; bLineRemoved = true; src = C; } else Q_ASSERT(true); } bool MergeResultWindow::sameKindCheck(const MergeLine& ml1, const MergeLine& ml2) { if(ml1.bConflict && ml2.bConflict) { // Both lines have conflicts: If one is only a white space conflict and // the other one is a real conflict, then this line returns false. return ml1.id3l->isEqualAC() == ml2.id3l->isEqualAC() && ml1.id3l->isEqualAB() == ml2.id3l->isEqualAB(); } else return ( (!ml1.bConflict && !ml2.bConflict && ml1.bDelta && ml2.bDelta && ml1.srcSelect == ml2.srcSelect && (ml1.mergeDetails == ml2.mergeDetails || (ml1.mergeDetails != eBCAddedAndEqual && ml2.mergeDetails != eBCAddedAndEqual))) || (!ml1.bDelta && !ml2.bDelta)); } void MergeResultWindow::merge(bool bAutoSolve, e_SrcSelector defaultSelector, bool bConflictsOnly, bool bWhiteSpaceOnly) { if(!bConflictsOnly) { if(m_bModified) { int result = KMessageBox::warningYesNo(this, i18n("The output has been modified.\n" "If you continue your changes will be lost."), i18n("Warning"), KStandardGuiItem::cont(), KStandardGuiItem::cancel()); if(result == KMessageBox::No) return; } m_mergeLineList.clear(); int lineIdx = 0; Diff3LineList::const_iterator it; for(it = m_pDiff3LineList->begin(); it != m_pDiff3LineList->end(); ++it, ++lineIdx) { const Diff3Line& d = *it; MergeLine ml; bool bLineRemoved; d.mergeOneLine( ml.mergeDetails, ml.bConflict, bLineRemoved, ml.srcSelect, m_pldC == nullptr); // Automatic solving for only whitespace changes. if(ml.bConflict && ((m_pldC == nullptr && (d.isEqualAB() || (d.bWhiteLineA && d.bWhiteLineB))) || (m_pldC != nullptr && ((d.isEqualAB() && d.isEqualAC()) || (d.bWhiteLineA && d.bWhiteLineB && d.bWhiteLineC))))) { ml.bWhiteSpaceConflict = true; } ml.d3lLineIdx = lineIdx; ml.bDelta = ml.srcSelect != A; ml.id3l = it; ml.srcRangeLength = 1; MergeLine* back = m_mergeLineList.empty() ? nullptr : &m_mergeLineList.back(); bool bSame = back != nullptr && sameKindCheck(ml, *back); if(bSame) { ++back->srcRangeLength; if(back->bWhiteSpaceConflict && !ml.bWhiteSpaceConflict) back->bWhiteSpaceConflict = false; } else { m_mergeLineList.push_back(ml); } if(!ml.bConflict) { MergeLine& tmpBack = m_mergeLineList.back(); MergeEditLine mel(ml.id3l); mel.setSource(ml.srcSelect, bLineRemoved); tmpBack.mergeEditLineList.push_back(mel); } else if(back == nullptr || !back->bConflict || !bSame) { MergeLine& tmpBack = m_mergeLineList.back(); MergeEditLine mel(ml.id3l); mel.setConflict(); tmpBack.mergeEditLineList.push_back(mel); } } } bool bSolveWhiteSpaceConflicts = false; if(bAutoSolve) // when true, then the other params are not used and we can change them here. (see all invocations of merge()) { if(m_pldC == nullptr && m_pOptions->m_whiteSpace2FileMergeDefault != None) // Only two inputs { Q_ASSERT(m_pOptions->m_whiteSpace2FileMergeDefault <= Max && m_pOptions->m_whiteSpace2FileMergeDefault >= Min); defaultSelector = (e_SrcSelector)m_pOptions->m_whiteSpace2FileMergeDefault; bWhiteSpaceOnly = true; bSolveWhiteSpaceConflicts = true; } else if(m_pldC != nullptr && m_pOptions->m_whiteSpace3FileMergeDefault != None) { Q_ASSERT(m_pOptions->m_whiteSpace3FileMergeDefault <= Max && m_pOptions->m_whiteSpace2FileMergeDefault >= Min); defaultSelector = (e_SrcSelector)m_pOptions->m_whiteSpace3FileMergeDefault; bWhiteSpaceOnly = true; bSolveWhiteSpaceConflicts = true; } } if(!bAutoSolve || bSolveWhiteSpaceConflicts) { // Change all auto selections MergeLineList::iterator mlIt; for(mlIt = m_mergeLineList.begin(); mlIt != m_mergeLineList.end(); ++mlIt) { MergeLine& ml = *mlIt; bool bConflict = ml.mergeEditLineList.empty() || ml.mergeEditLineList.begin()->isConflict(); if(ml.bDelta && (!bConflictsOnly || bConflict) && (!bWhiteSpaceOnly || ml.bWhiteSpaceConflict)) { ml.mergeEditLineList.clear(); if(defaultSelector == Invalid && ml.bDelta) { MergeEditLine mel(ml.id3l); ; mel.setConflict(); ml.bConflict = true; ml.mergeEditLineList.push_back(mel); } else { Diff3LineList::const_iterator d3llit = ml.id3l; int j; for(j = 0; j < ml.srcRangeLength; ++j) { MergeEditLine mel(d3llit); mel.setSource(defaultSelector, false); LineRef srcLine = defaultSelector == A ? d3llit->getLineA() : defaultSelector == B ? d3llit->getLineB() : defaultSelector == C ? d3llit->getLineC() : LineRef(); if(srcLine.isValid()) { ml.mergeEditLineList.push_back(mel); } ++d3llit; } if(ml.mergeEditLineList.empty()) // Make a line nevertheless { MergeEditLine mel(ml.id3l); mel.setRemoved(defaultSelector); ml.mergeEditLineList.push_back(mel); } } } } } MergeLineList::iterator mlIt; for(mlIt = m_mergeLineList.begin(); mlIt != m_mergeLineList.end(); ++mlIt) { MergeLine& ml = *mlIt; // Remove all lines that are empty, because no src lines are there. LineRef oldSrcLine; e_SrcSelector oldSrc = Invalid; MergeEditLineList::iterator melIt; for(melIt = ml.mergeEditLineList.begin(); melIt != ml.mergeEditLineList.end();) { MergeEditLine& mel = *melIt; e_SrcSelector melsrc = mel.src(); LineRef srcLine = mel.isRemoved() ? LineRef() : melsrc == A ? mel.id3l()->getLineA() : melsrc == B ? mel.id3l()->getLineB() : melsrc == C ? mel.id3l()->getLineC() : LineRef(); // At least one line remains because oldSrc != melsrc for first line in list // Other empty lines will be removed if(!srcLine.isValid() && !oldSrcLine.isValid() && oldSrc == melsrc) melIt = ml.mergeEditLineList.erase(melIt); else ++melIt; oldSrcLine = srcLine; oldSrc = melsrc; } } if(bAutoSolve && !bConflictsOnly) { if(m_pOptions->m_bRunHistoryAutoMergeOnMergeStart) slotMergeHistory(); if(m_pOptions->m_bRunRegExpAutoMergeOnMergeStart) slotRegExpAutoMerge(); if(m_pldC != nullptr && !doRelevantChangesExist()) emit noRelevantChangesDetected(); } int nrOfSolvedConflicts = 0; int nrOfUnsolvedConflicts = 0; int nrOfWhiteSpaceConflicts = 0; MergeLineList::iterator i; for(i = m_mergeLineList.begin(); i != m_mergeLineList.end(); ++i) { if(i->bConflict) ++nrOfUnsolvedConflicts; else if(i->bDelta) ++nrOfSolvedConflicts; if(i->bWhiteSpaceConflict) ++nrOfWhiteSpaceConflicts; } m_pTotalDiffStatus->setUnsolvedConflicts(nrOfUnsolvedConflicts); m_pTotalDiffStatus->setSolvedConflicts(nrOfSolvedConflicts); m_pTotalDiffStatus->setWhitespaceConflicts(nrOfWhiteSpaceConflicts); m_cursorXPos = 0; m_cursorOldXPixelPos = 0; m_cursorYPos = 0; m_maxTextWidth = -1; //m_firstLine = 0; // Must not set line/column without scrolling there //m_horizScrollOffset = 0; setModified(false); m_currentMergeLineIt = m_mergeLineList.begin(); slotGoTop(); emit updateAvailabilities(); update(); } void MergeResultWindow::setFirstLine(QtNumberType firstLine) { m_firstLine = std::max(0, firstLine); update(); } void MergeResultWindow::setHorizScrollOffset(int horizScrollOffset) { m_horizScrollOffset = std::max(0, horizScrollOffset); update(); } int MergeResultWindow::getMaxTextWidth() { if(m_maxTextWidth < 0) { m_maxTextWidth = 0; MergeLineList::iterator mlIt = m_mergeLineList.begin(); for(mlIt = m_mergeLineList.begin(); mlIt != m_mergeLineList.end(); ++mlIt) { MergeLine& ml = *mlIt; MergeEditLineList::iterator melIt; for(melIt = ml.mergeEditLineList.begin(); melIt != ml.mergeEditLineList.end(); ++melIt) { MergeEditLine& mel = *melIt; QString s = mel.getString(m_pldA, m_pldB, m_pldC); QTextLayout textLayout(s, font(), this); textLayout.beginLayout(); textLayout.createLine(); textLayout.endLayout(); if(m_maxTextWidth < textLayout.maximumWidth()) { m_maxTextWidth = qCeil(textLayout.maximumWidth()); } } } m_maxTextWidth += 5; // cursorwidth } return m_maxTextWidth; } int MergeResultWindow::getNofLines() { return m_nofLines; } int MergeResultWindow::getVisibleTextAreaWidth() { // QFontMetrics fm = fontMetrics(); // FIXME used? return width() - getTextXOffset(); } int MergeResultWindow::getNofVisibleLines() { QFontMetrics fm = fontMetrics(); return (height() - 3) / fm.lineSpacing() - 2; } int MergeResultWindow::getTextXOffset() { QFontMetrics fm = fontMetrics(); return 3 * Utils::getHorizontalAdvance(fm, '0'); } void MergeResultWindow::resizeEvent(QResizeEvent* e) { QWidget::resizeEvent(e); emit resizeSignal(); } Overview::e_OverviewMode MergeResultWindow::getOverviewMode() { return m_eOverviewMode; } void MergeResultWindow::setOverviewMode(Overview::e_OverviewMode eOverviewMode) { m_eOverviewMode = eOverviewMode; } // Check whether we should ignore current delta when moving to next/previous delta bool MergeResultWindow::checkOverviewIgnore(MergeLineList::iterator& i) { if(m_eOverviewMode == Overview::eOMNormal) return false; if(m_eOverviewMode == Overview::eOMAvsB) return i->mergeDetails == eCAdded || i->mergeDetails == eCDeleted || i->mergeDetails == eCChanged; if(m_eOverviewMode == Overview::eOMAvsC) return i->mergeDetails == eBAdded || i->mergeDetails == eBDeleted || i->mergeDetails == eBChanged; if(m_eOverviewMode == Overview::eOMBvsC) return i->mergeDetails == eBCAddedAndEqual || i->mergeDetails == eBCDeleted || i->mergeDetails == eBCChangedAndEqual; return false; } // Go to prev/next delta/conflict or first/last delta. void MergeResultWindow::go(e_Direction eDir, e_EndPoint eEndPoint) { Q_ASSERT(eDir == eUp || eDir == eDown); MergeLineList::iterator i = m_currentMergeLineIt; bool bSkipWhiteConflicts = !m_pOptions->m_bShowWhiteSpace; if(eEndPoint == eEnd) { if(eDir == eUp) i = m_mergeLineList.begin(); // first mergeline else i = --m_mergeLineList.end(); // last mergeline while(isItAtEnd(eDir == eUp, i) && !i->bDelta) { if(eDir == eUp) ++i; // search downwards else --i; // search upwards } } else if(eEndPoint == eDelta && isItAtEnd(eDir != eUp, i)) { do { if(eDir == eUp) --i; else ++i; } while(isItAtEnd(eDir != eUp, i) && (!i->bDelta || checkOverviewIgnore(i) || (bSkipWhiteConflicts && i->bWhiteSpaceConflict))); } else if(eEndPoint == eConflict && isItAtEnd(eDir != eUp, i)) { do { if(eDir == eUp) --i; else ++i; } while(isItAtEnd(eDir != eUp, i) && (!i->bConflict || (bSkipWhiteConflicts && i->bWhiteSpaceConflict))); } else if(isItAtEnd(eDir != eUp, i) && eEndPoint == eUnsolvedConflict) { do { if(eDir == eUp) --i; else ++i; } while(isItAtEnd(eDir != eUp, i) && !i->mergeEditLineList.begin()->isConflict()); } if(isVisible()) setFocus(); setFastSelector(i); } bool MergeResultWindow::isDeltaAboveCurrent() { bool bSkipWhiteConflicts = !m_pOptions->m_bShowWhiteSpace; if(m_mergeLineList.empty()) return false; MergeLineList::iterator i = m_currentMergeLineIt; if(i == m_mergeLineList.begin()) return false; do { --i; if(i->bDelta && !checkOverviewIgnore(i) && !(bSkipWhiteConflicts && i->bWhiteSpaceConflict)) return true; } while(i != m_mergeLineList.begin()); return false; } bool MergeResultWindow::isDeltaBelowCurrent() { bool bSkipWhiteConflicts = !m_pOptions->m_bShowWhiteSpace; if(m_mergeLineList.empty()) return false; MergeLineList::iterator i = m_currentMergeLineIt; if(i != m_mergeLineList.end()) { ++i; for(; i != m_mergeLineList.end(); ++i) { if(i->bDelta && !checkOverviewIgnore(i) && !(bSkipWhiteConflicts && i->bWhiteSpaceConflict)) return true; } } return false; } bool MergeResultWindow::isConflictAboveCurrent() { if(m_mergeLineList.empty()) return false; MergeLineList::iterator i = m_currentMergeLineIt; if(i == m_mergeLineList.begin()) return false; bool bSkipWhiteConflicts = !m_pOptions->m_bShowWhiteSpace; do { --i; if(i->bConflict && !(bSkipWhiteConflicts && i->bWhiteSpaceConflict)) return true; } while(i != m_mergeLineList.begin()); return false; } bool MergeResultWindow::isConflictBelowCurrent() { MergeLineList::iterator i = m_currentMergeLineIt; if(m_mergeLineList.empty()) return false; bool bSkipWhiteConflicts = !m_pOptions->m_bShowWhiteSpace; if(i != m_mergeLineList.end()) { ++i; for(; i != m_mergeLineList.end(); ++i) { if(i->bConflict && !(bSkipWhiteConflicts && i->bWhiteSpaceConflict)) return true; } } return false; } bool MergeResultWindow::isUnsolvedConflictAtCurrent() { if(m_mergeLineList.empty()) return false; MergeLineList::iterator i = m_currentMergeLineIt; return i->mergeEditLineList.begin()->isConflict(); } bool MergeResultWindow::isUnsolvedConflictAboveCurrent() { if(m_mergeLineList.empty()) return false; MergeLineList::iterator i = m_currentMergeLineIt; if(i == m_mergeLineList.begin()) return false; do { --i; if(i->mergeEditLineList.begin()->isConflict()) return true; } while(i != m_mergeLineList.begin()); return false; } bool MergeResultWindow::isUnsolvedConflictBelowCurrent() { MergeLineList::iterator i = m_currentMergeLineIt; if(m_mergeLineList.empty()) return false; if(i != m_mergeLineList.end()) { ++i; for(; i != m_mergeLineList.end(); ++i) { if(i->mergeEditLineList.begin()->isConflict()) return true; } } return false; } void MergeResultWindow::slotGoTop() { go(eUp, eEnd); } void MergeResultWindow::slotGoCurrent() { setFastSelector(m_currentMergeLineIt); } void MergeResultWindow::slotGoBottom() { go(eDown, eEnd); } void MergeResultWindow::slotGoPrevDelta() { go(eUp, eDelta); } void MergeResultWindow::slotGoNextDelta() { go(eDown, eDelta); } void MergeResultWindow::slotGoPrevConflict() { go(eUp, eConflict); } void MergeResultWindow::slotGoNextConflict() { go(eDown, eConflict); } void MergeResultWindow::slotGoPrevUnsolvedConflict() { go(eUp, eUnsolvedConflict); } void MergeResultWindow::slotGoNextUnsolvedConflict() { go(eDown, eUnsolvedConflict); } /** The line is given as a index in the Diff3LineList. The function calculates the corresponding iterator. */ void MergeResultWindow::slotSetFastSelectorLine(LineIndex line) { MergeLineList::iterator i; for(i = m_mergeLineList.begin(); i != m_mergeLineList.end(); ++i) { if(line >= i->d3lLineIdx && line < i->d3lLineIdx + i->srcRangeLength) { //if ( i->bDelta ) { setFastSelector(i); } break; } } } int MergeResultWindow::getNrOfUnsolvedConflicts(int* pNrOfWhiteSpaceConflicts) { int nrOfUnsolvedConflicts = 0; if(pNrOfWhiteSpaceConflicts != nullptr) *pNrOfWhiteSpaceConflicts = 0; MergeLineList::iterator mlIt = m_mergeLineList.begin(); for(mlIt = m_mergeLineList.begin(); mlIt != m_mergeLineList.end(); ++mlIt) { MergeLine& ml = *mlIt; MergeEditLineList::iterator melIt = ml.mergeEditLineList.begin(); if(melIt->isConflict()) { ++nrOfUnsolvedConflicts; if(ml.bWhiteSpaceConflict && pNrOfWhiteSpaceConflicts != nullptr) ++*pNrOfWhiteSpaceConflicts; } } return nrOfUnsolvedConflicts; } void MergeResultWindow::showNrOfConflicts() { if(!m_pOptions->m_bShowInfoDialogs) return; int nrOfConflicts = 0; MergeLineList::iterator i; for(i = m_mergeLineList.begin(); i != m_mergeLineList.end(); ++i) { if(i->bConflict || i->bDelta) ++nrOfConflicts; } QString totalInfo; if(m_pTotalDiffStatus->bBinaryAEqB && m_pTotalDiffStatus->bBinaryAEqC) totalInfo += i18n("All input files are binary equal."); else if(m_pTotalDiffStatus->bTextAEqB && m_pTotalDiffStatus->bTextAEqC) totalInfo += i18n("All input files contain the same text."); else { if(m_pTotalDiffStatus->bBinaryAEqB) totalInfo += i18n("Files %1 and %2 are binary equal.\n", i18n("A"), i18n("B")); else if(m_pTotalDiffStatus->bTextAEqB) totalInfo += i18n("Files %1 and %2 have equal text.\n", i18n("A"), i18n("B")); if(m_pTotalDiffStatus->bBinaryAEqC) totalInfo += i18n("Files %1 and %2 are binary equal.\n", i18n("A"), i18n("C")); else if(m_pTotalDiffStatus->bTextAEqC) totalInfo += i18n("Files %1 and %2 have equal text.\n", i18n("A"), i18n("C")); if(m_pTotalDiffStatus->bBinaryBEqC) totalInfo += i18n("Files %1 and %2 are binary equal.\n", i18n("B"), i18n("C")); else if(m_pTotalDiffStatus->bTextBEqC) totalInfo += i18n("Files %1 and %2 have equal text.\n", i18n("B"), i18n("C")); } int nrOfUnsolvedConflicts = getNrOfUnsolvedConflicts(); KMessageBox::information(this, i18n("Total number of conflicts: %1\n" "Nr of automatically solved conflicts: %2\n" "Nr of unsolved conflicts: %3\n" "%4", nrOfConflicts, nrOfConflicts - nrOfUnsolvedConflicts, nrOfUnsolvedConflicts, totalInfo), i18n("Conflicts")); } void MergeResultWindow::setFastSelector(MergeLineList::iterator i) { if(i == m_mergeLineList.end()) return; m_currentMergeLineIt = i; emit setFastSelectorRange(i->d3lLineIdx, i->srcRangeLength); int line1 = 0; MergeLineList::iterator mlIt = m_mergeLineList.begin(); for(mlIt = m_mergeLineList.begin(); mlIt != m_mergeLineList.end(); ++mlIt) { if(mlIt == m_currentMergeLineIt) break; line1 += mlIt->mergeEditLineList.size(); } int nofLines = m_currentMergeLineIt->mergeEditLineList.size(); int newFirstLine = getBestFirstLine(line1, nofLines, m_firstLine, getNofVisibleLines()); if(newFirstLine != m_firstLine) { emit scrollMergeResultWindow(0, newFirstLine - m_firstLine); } if(m_selection.isEmpty()) { m_cursorXPos = 0; m_cursorOldXPixelPos = 0; m_cursorYPos = line1; } update(); updateSourceMask(); emit updateAvailabilities(); } void MergeResultWindow::choose(e_SrcSelector selector) { if(m_currentMergeLineIt == m_mergeLineList.end()) return; setModified(); // First find range for which this change works. MergeLine& ml = *m_currentMergeLineIt; MergeEditLineList::iterator melIt; // Now check if selector is active for this range already. bool bActive = false; // Remove unneeded lines in the range. for(melIt = ml.mergeEditLineList.begin(); melIt != ml.mergeEditLineList.end();) { MergeEditLine& mel = *melIt; if(mel.src() == selector) bActive = true; if(mel.src() == selector || !mel.isEditableText() || mel.isModified()) melIt = ml.mergeEditLineList.erase(melIt); else ++melIt; } if(!bActive) // Selected source wasn't active. { // Append the lines from selected source here at rangeEnd. Diff3LineList::const_iterator d3llit = ml.id3l; int j; for(j = 0; j < ml.srcRangeLength; ++j) { MergeEditLine mel(d3llit); mel.setSource(selector, false); ml.mergeEditLineList.push_back(mel); ++d3llit; } } if(!ml.mergeEditLineList.empty()) { // Remove all lines that are empty, because no src lines are there. for(melIt = ml.mergeEditLineList.begin(); melIt != ml.mergeEditLineList.end();) { MergeEditLine& mel = *melIt; LineRef srcLine = mel.src() == A ? mel.id3l()->getLineA() : mel.src() == B ? mel.id3l()->getLineB() : mel.src() == C ? mel.id3l()->getLineC() : LineRef(); if(!srcLine.isValid()) melIt = ml.mergeEditLineList.erase(melIt); else ++melIt; } } if(ml.mergeEditLineList.empty()) { // Insert a dummy line: MergeEditLine mel(ml.id3l); if(bActive) mel.setConflict(); // All src entries deleted => conflict else mel.setRemoved(selector); // No lines in corresponding src found. ml.mergeEditLineList.push_back(mel); } if(m_cursorYPos >= m_nofLines) { m_cursorYPos = m_nofLines - 1; m_cursorXPos = 0; } m_maxTextWidth = -1; update(); updateSourceMask(); emit updateAvailabilities(); showUnsolvedConflictsStatusMessage(); } // bConflictsOnly: automatically choose for conflicts only (true) or for everywhere (false) void MergeResultWindow::chooseGlobal(e_SrcSelector selector, bool bConflictsOnly, bool bWhiteSpaceOnly) { resetSelection(); merge(false, selector, bConflictsOnly, bWhiteSpaceOnly); setModified(true); update(); showUnsolvedConflictsStatusMessage(); } void MergeResultWindow::slotAutoSolve() { resetSelection(); merge(true, Invalid); setModified(true); update(); showUnsolvedConflictsStatusMessage(); } void MergeResultWindow::slotUnsolve() { resetSelection(); merge(false, Invalid); setModified(true); update(); showUnsolvedConflictsStatusMessage(); } static QString calcHistoryLead(const QString& s) { // Return the start of the line until the first white char after the first non white char. int i; for(i = 0; i < s.length(); ++i) { if(s[i] != ' ' && s[i] != '\t') { for(; i < s.length(); ++i) { if(s[i] == ' ' || s[i] == '\t') { return s.left(i); } } return s; // Very unlikely } } return QString(); // Must be an empty string, not a null string. } static void findHistoryRange(const QRegExp& historyStart, bool bThreeFiles, const Diff3LineList* pD3LList, Diff3LineList::const_iterator& iBegin, Diff3LineList::const_iterator& iEnd, int& idxBegin, int& idxEnd) { QString historyLead; // Search for start of history for(iBegin = pD3LList->begin(), idxBegin = 0; iBegin != pD3LList->end(); ++iBegin, ++idxBegin) { if(historyStart.exactMatch(iBegin->getString(A)) && historyStart.exactMatch(iBegin->getString(B)) && (!bThreeFiles || historyStart.exactMatch(iBegin->getString(C)))) { historyLead = calcHistoryLead(iBegin->getString(A)); break; } } // Search for end of history for(iEnd = iBegin, idxEnd = idxBegin; iEnd != pD3LList->end(); ++iEnd, ++idxEnd) { QString sA = iEnd->getString(A); QString sB = iEnd->getString(B); QString sC = iEnd->getString(C); if(!((sA.isEmpty() || historyLead == calcHistoryLead(sA)) && (sB.isEmpty() || historyLead == calcHistoryLead(sB)) && (!bThreeFiles || sC.isEmpty() || historyLead == calcHistoryLead(sC)))) { break; // End of the history } } } bool findParenthesesGroups(const QString& s, QStringList& sl) { sl.clear(); int i = 0; std::list startPosStack; int length = s.length(); for(i = 0; i < length; ++i) { if(s[i] == '\\' && i + 1 < length && (s[i + 1] == '\\' || s[i + 1] == '(' || s[i + 1] == ')')) { ++i; continue; } if(s[i] == '(') { startPosStack.push_back(i); } else if(s[i] == ')') { if(startPosStack.empty()) return false; // Parentheses don't match int startPos = startPosStack.back(); startPosStack.pop_back(); sl.push_back(s.mid(startPos + 1, i - startPos - 1)); } } return startPosStack.empty(); // false if parentheses don't match } QString calcHistorySortKey(const QString& keyOrder, QRegExp& matchedRegExpr, const QStringList& parenthesesGroupList) { QStringList keyOrderList = keyOrder.split(','); QString key; for(QStringList::iterator keyIt = keyOrderList.begin(); keyIt != keyOrderList.end(); ++keyIt) { if(keyIt->isEmpty()) continue; bool bOk = false; int groupIdx = keyIt->toInt(&bOk); if(!bOk || groupIdx < 0 || groupIdx > parenthesesGroupList.size()) continue; QString s = matchedRegExpr.cap(groupIdx); if(groupIdx == 0) { key += s + ' '; continue; } QString groupRegExp = parenthesesGroupList[groupIdx - 1]; if(groupRegExp.indexOf('|') < 0 || groupRegExp.indexOf('(') >= 0) { bOk = false; int i = s.toInt(&bOk); if(bOk && i >= 0 && i < 10000) s.sprintf("%04d", i); // This should help for correct sorting of numbers. key += s + ' '; } else { // Assume that the groupRegExp consists of something like "Jan|Feb|Mar|Apr" // s is the string that managed to match. // Now we want to know at which position it occurred. e.g. Jan=0, Feb=1, Mar=2, etc. QStringList sl = groupRegExp.split('|'); int idx = sl.indexOf(s); if(idx < 0) { // Didn't match } else { QString sIdx; sIdx.sprintf("%02d", idx + 1); // Up to 99 words in the groupRegExp (more than 12 aren't expected) key += sIdx + ' '; } } } return key; } void MergeResultWindow::collectHistoryInformation( e_SrcSelector src, Diff3LineList::const_iterator &iHistoryBegin, Diff3LineList::const_iterator &iHistoryEnd, HistoryMap& historyMap, std::list& hitList // list of iterators ) { std::list::iterator itHitListFront = hitList.begin(); Diff3LineList::const_iterator id3l = iHistoryBegin; QString historyLead; { const LineData* pld = id3l->getLineData(src); historyLead = calcHistoryLead(pld->getLine()); } QRegExp historyStart(m_pOptions->m_historyStartRegExp); if(id3l == iHistoryEnd) return; ++id3l; // Skip line with "$Log ... $" QRegExp newHistoryEntry(m_pOptions->m_historyEntryStartRegExp); QStringList parenthesesGroups; findParenthesesGroups(m_pOptions->m_historyEntryStartRegExp, parenthesesGroups); QString key; MergeEditLineList melList; bool bPrevLineIsEmpty = true; bool bUseRegExp = !m_pOptions->m_historyEntryStartRegExp.isEmpty(); for(; id3l != iHistoryEnd; ++id3l) { const LineData* pld = id3l->getLineData(src); if(!pld) continue; const QString& oriLine = pld->getLine(); if(historyLead.isEmpty()) historyLead = calcHistoryLead(oriLine); QString sLine = oriLine.mid(historyLead.length()); if((!bUseRegExp && !sLine.trimmed().isEmpty() && bPrevLineIsEmpty) || (bUseRegExp && newHistoryEntry.exactMatch(sLine))) { if(!key.isEmpty() && !melList.empty()) { // Only insert new HistoryMapEntry if key not found; in either case p.first is a valid iterator to element key. std::pair p = historyMap.insert(HistoryMap::value_type(key, HistoryMapEntry())); HistoryMapEntry& hme = p.first->second; if(src == A) hme.mellA = melList; if(src == B) hme.mellB = melList; if(src == C) hme.mellC = melList; if(p.second) // Not in list yet? { hitList.insert(itHitListFront, p.first); } } if(!bUseRegExp) key = sLine; else key = calcHistorySortKey(m_pOptions->m_historyEntryStartSortKeyOrder, newHistoryEntry, parenthesesGroups); melList.clear(); melList.push_back(MergeEditLine(id3l, src)); } else if(!historyStart.exactMatch(oriLine)) { melList.push_back(MergeEditLine(id3l, src)); } bPrevLineIsEmpty = sLine.trimmed().isEmpty(); } if(!key.isEmpty()) { // Only insert new HistoryMapEntry if key not found; in either case p.first is a valid iterator to element key. std::pair p = historyMap.insert(HistoryMap::value_type(key, HistoryMapEntry())); HistoryMapEntry& hme = p.first->second; if(src == A) hme.mellA = melList; if(src == B) hme.mellB = melList; if(src == C) hme.mellC = melList; if(p.second) // Not in list yet? { hitList.insert(itHitListFront, p.first); } } // End of the history } MergeEditLineList& MergeResultWindow::HistoryMapEntry::choice(bool bThreeInputs) { if(!bThreeInputs) return mellA.empty() ? mellB : mellA; else { if(mellA.empty()) return mellC.empty() ? mellB : mellC; // A doesn't exist, return one that exists else if(!mellB.empty() && !mellC.empty()) { // A, B and C exist return mellA; } else return mellB.empty() ? mellB : mellC; // A exists, return the one that doesn't exist } } bool MergeResultWindow::HistoryMapEntry::staysInPlace(bool bThreeInputs, Diff3LineList::const_iterator& iHistoryEnd) { // The entry should stay in place if the decision made by the automerger is correct. Diff3LineList::const_iterator& iHistoryLast = iHistoryEnd; --iHistoryLast; if(!bThreeInputs) { if(!mellA.empty() && !mellB.empty() && mellA.begin()->id3l() == mellB.begin()->id3l() && mellA.back().id3l() == iHistoryLast && mellB.back().id3l() == iHistoryLast) { iHistoryEnd = mellA.begin()->id3l(); return true; } else { return false; } } else { if(!mellA.empty() && !mellB.empty() && !mellC.empty() && mellA.begin()->id3l() == mellB.begin()->id3l() && mellA.begin()->id3l() == mellC.begin()->id3l() && mellA.back().id3l() == iHistoryLast && mellB.back().id3l() == iHistoryLast && mellC.back().id3l() == iHistoryLast) { iHistoryEnd = mellA.begin()->id3l(); return true; } else { return false; } } } void MergeResultWindow::slotMergeHistory() { Diff3LineList::const_iterator iD3LHistoryBegin; Diff3LineList::const_iterator iD3LHistoryEnd; int d3lHistoryBeginLineIdx = -1; int d3lHistoryEndLineIdx = -1; // Search for history start, history end in the diff3LineList findHistoryRange(QRegExp(m_pOptions->m_historyStartRegExp), m_pldC != nullptr, m_pDiff3LineList, iD3LHistoryBegin, iD3LHistoryEnd, d3lHistoryBeginLineIdx, d3lHistoryEndLineIdx); if(iD3LHistoryBegin != m_pDiff3LineList->end()) { // Now collect the historyMap information HistoryMap historyMap; std::list hitList; if(m_pldC == nullptr) { collectHistoryInformation(A, iD3LHistoryBegin, iD3LHistoryEnd, historyMap, hitList); collectHistoryInformation(B, iD3LHistoryBegin, iD3LHistoryEnd, historyMap, hitList); } else { collectHistoryInformation(A, iD3LHistoryBegin, iD3LHistoryEnd, historyMap, hitList); collectHistoryInformation(B, iD3LHistoryBegin, iD3LHistoryEnd, historyMap, hitList); collectHistoryInformation(C, iD3LHistoryBegin, iD3LHistoryEnd, historyMap, hitList); } Diff3LineList::const_iterator iD3LHistoryOrigEnd = iD3LHistoryEnd; bool bHistoryMergeSorting = m_pOptions->m_bHistoryMergeSorting && !m_pOptions->m_historyEntryStartSortKeyOrder.isEmpty() && !m_pOptions->m_historyEntryStartRegExp.isEmpty(); if(m_pOptions->m_maxNofHistoryEntries == -1) { // Remove parts from the historyMap and hitList that stay in place if(bHistoryMergeSorting) { while(!historyMap.empty()) { HistoryMap::iterator hMapIt = historyMap.begin(); if(hMapIt->second.staysInPlace(m_pldC != nullptr, iD3LHistoryEnd)) historyMap.erase(hMapIt); else break; } } else { while(!hitList.empty()) { HistoryMap::iterator hMapIt = hitList.back(); if(hMapIt->second.staysInPlace(m_pldC != nullptr, iD3LHistoryEnd)) hitList.pop_back(); else break; } } while(iD3LHistoryOrigEnd != iD3LHistoryEnd) { --iD3LHistoryOrigEnd; --d3lHistoryEndLineIdx; } } MergeLineList::iterator iMLLStart = splitAtDiff3LineIdx(d3lHistoryBeginLineIdx); MergeLineList::iterator iMLLEnd = splitAtDiff3LineIdx(d3lHistoryEndLineIdx); // Now join all MergeLines in the history MergeLineList::iterator i = iMLLStart; if(i != iMLLEnd) { ++i; while(i != iMLLEnd) { iMLLStart->join(*i); i = m_mergeLineList.erase(i); } } iMLLStart->mergeEditLineList.clear(); // Now insert the complete history into the first MergeLine of the history iMLLStart->mergeEditLineList.push_back(MergeEditLine(iD3LHistoryBegin, m_pldC == nullptr ? B : C)); QString lead = calcHistoryLead(iD3LHistoryBegin->getString(A)); MergeEditLine mel(m_pDiff3LineList->end()); mel.setString(lead); iMLLStart->mergeEditLineList.push_back(mel); int historyCount = 0; if(bHistoryMergeSorting) { // Create a sorted history HistoryMap::reverse_iterator hmit; for(hmit = historyMap.rbegin(); hmit != historyMap.rend(); ++hmit) { if(historyCount == m_pOptions->m_maxNofHistoryEntries) break; ++historyCount; HistoryMapEntry& hme = hmit->second; MergeEditLineList& mell = hme.choice(m_pldC != nullptr); if(!mell.empty()) iMLLStart->mergeEditLineList.splice(iMLLStart->mergeEditLineList.end(), mell, mell.begin(), mell.end()); } } else { // Create history in order of appearance std::list::iterator hlit; for(hlit = hitList.begin(); hlit != hitList.end(); ++hlit) { if(historyCount == m_pOptions->m_maxNofHistoryEntries) break; ++historyCount; HistoryMapEntry& hme = (*hlit)->second; MergeEditLineList& mell = hme.choice(m_pldC != nullptr); if(!mell.empty()) iMLLStart->mergeEditLineList.splice(iMLLStart->mergeEditLineList.end(), mell, mell.begin(), mell.end()); } // If the end of start is empty and the first line at the end is empty remove the last line of start if(!iMLLStart->mergeEditLineList.empty() && !iMLLEnd->mergeEditLineList.empty()) { QString lastLineOfStart = iMLLStart->mergeEditLineList.back().getString(m_pldA, m_pldB, m_pldC); QString firstLineOfEnd = iMLLEnd->mergeEditLineList.front().getString(m_pldA, m_pldB, m_pldC); if(lastLineOfStart.mid(lead.length()).trimmed().isEmpty() && firstLineOfEnd.mid(lead.length()).trimmed().isEmpty()) iMLLStart->mergeEditLineList.pop_back(); } } setFastSelector(iMLLStart); update(); } } void MergeResultWindow::slotRegExpAutoMerge() { if(m_pOptions->m_autoMergeRegExp.isEmpty()) return; QRegExp vcsKeywords(m_pOptions->m_autoMergeRegExp); MergeLineList::iterator i; for(i = m_mergeLineList.begin(); i != m_mergeLineList.end(); ++i) { if(i->bConflict) { Diff3LineList::const_iterator id3l = i->id3l; if(vcsKeywords.exactMatch(id3l->getString(A)) && vcsKeywords.exactMatch(id3l->getString(B)) && (m_pldC == nullptr || vcsKeywords.exactMatch(id3l->getString(C)))) { MergeEditLine& mel = *i->mergeEditLineList.begin(); mel.setSource(m_pldC == nullptr ? B : C, false); splitAtDiff3LineIdx(i->d3lLineIdx + 1); } } } update(); } // This doesn't detect user modifications and should only be called after automatic merge // This will only do something for three file merge. // Irrelevant changes are those where all contributions from B are already contained in C. // Also irrelevant are conflicts automatically solved (automerge regexp and history automerge) // Precondition: The VCS-keyword would also be C. bool MergeResultWindow::doRelevantChangesExist() { if(m_pldC == nullptr || m_mergeLineList.size() <= 1) return true; MergeLineList::iterator i; for(i = m_mergeLineList.begin(); i != m_mergeLineList.end(); ++i) { if((i->bConflict && i->mergeEditLineList.begin()->src() != C) || i->srcSelect == B) { return true; } } return false; } // Returns the iterator to the MergeLine after the split MergeLineList::iterator MergeResultWindow::splitAtDiff3LineIdx(int d3lLineIdx) { MergeLineList::iterator i; for(i = m_mergeLineList.begin(); i != m_mergeLineList.end(); ++i) { if(i->d3lLineIdx == d3lLineIdx) { // No split needed, this is the beginning of a MergeLine return i; } else if(i->d3lLineIdx > d3lLineIdx) { // The split must be in the previous MergeLine --i; MergeLine& ml = *i; MergeLine newML; ml.split(newML, d3lLineIdx); ++i; return m_mergeLineList.insert(i, newML); } } // The split must be in the previous MergeLine --i; MergeLine& ml = *i; MergeLine newML; ml.split(newML, d3lLineIdx); ++i; return m_mergeLineList.insert(i, newML); } void MergeResultWindow::slotSplitDiff(int firstD3lLineIdx, int lastD3lLineIdx) { if(lastD3lLineIdx >= 0) splitAtDiff3LineIdx(lastD3lLineIdx + 1); setFastSelector(splitAtDiff3LineIdx(firstD3lLineIdx)); } void MergeResultWindow::slotJoinDiffs(int firstD3lLineIdx, int lastD3lLineIdx) { MergeLineList::iterator i; MergeLineList::iterator iMLLStart = m_mergeLineList.end(); MergeLineList::iterator iMLLEnd = m_mergeLineList.end(); for(i = m_mergeLineList.begin(); i != m_mergeLineList.end(); ++i) { MergeLine& ml = *i; if(firstD3lLineIdx >= ml.d3lLineIdx && firstD3lLineIdx < ml.d3lLineIdx + ml.srcRangeLength) { iMLLStart = i; } if(lastD3lLineIdx >= ml.d3lLineIdx && lastD3lLineIdx < ml.d3lLineIdx + ml.srcRangeLength) { iMLLEnd = i; ++iMLLEnd; break; } } bool bJoined = false; for(i = iMLLStart; i != iMLLEnd && i != m_mergeLineList.end();) { if(i == iMLLStart) { ++i; } else { iMLLStart->join(*i); i = m_mergeLineList.erase(i); bJoined = true; } } if(bJoined) { iMLLStart->mergeEditLineList.clear(); // Insert a conflict line as placeholder iMLLStart->mergeEditLineList.push_back(MergeEditLine(iMLLStart->id3l)); } setFastSelector(iMLLStart); } void MergeResultWindow::myUpdate(int afterMilliSecs) { if(m_delayedDrawTimer) killTimer(m_delayedDrawTimer); m_bMyUpdate = true; m_delayedDrawTimer = startTimer(afterMilliSecs); } void MergeResultWindow::timerEvent(QTimerEvent*) { killTimer(m_delayedDrawTimer); m_delayedDrawTimer = 0; if(m_bMyUpdate) { update(); m_bMyUpdate = false; } if(m_scrollDeltaX != 0 || m_scrollDeltaY != 0) { m_selection.end(m_selection.getLastLine() + m_scrollDeltaY, m_selection.getLastPos() + m_scrollDeltaX); emit scrollMergeResultWindow(m_scrollDeltaX, m_scrollDeltaY); killTimer(m_delayedDrawTimer); m_delayedDrawTimer = startTimer(50); } } /// Converts the cursor-posOnScreen into a text index, considering tabulators. int convertToPosInText(const QString& /*s*/, int posOnScreen, int /*tabSize*/) { return posOnScreen; } // int localPosOnScreen = 0; // int size=s.length(); // for ( int i=0; i=posOnScreen ) // return i; // // All letters except tabulator have width one. // int letterWidth = s[i]!='\t' ? 1 : tabber( localPosOnScreen, tabSize ); // localPosOnScreen += letterWidth; // if ( localPosOnScreen>posOnScreen ) // return i; // } // return size; //} /// Converts the index into the text to a cursor-posOnScreen considering tabulators. int convertToPosOnScreen(const QString& /*p*/, int posInText, int /*tabSize*/) { return posInText; } // int posOnScreen = 0; // for ( int i=0; i MergeResultWindow::getTextLayoutForLine(int line, const QString& str, QTextLayout& textLayout) { // tabs QTextOption textOption; #if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) textOption.setTabStop(QFontMetricsF(font()).width(' ') * m_pOptions->m_tabSize); #else textOption.setTabStopDistance(QFontMetricsF(font()).width(' ') * m_pOptions->m_tabSize); #endif if(m_pOptions->m_bShowWhiteSpaceCharacters) { textOption.setFlags(QTextOption::ShowTabsAndSpaces); } textLayout.setTextOption(textOption); if(m_pOptions->m_bShowWhiteSpaceCharacters) { // This additional format is only necessary for the tab arrow QVector formats; QTextLayout::FormatRange formatRange; formatRange.start = 0; formatRange.length = str.length(); formatRange.format.setFont(font()); formats.append(formatRange); textLayout.setFormats(formats); } QVector selectionFormat; textLayout.beginLayout(); if(m_selection.lineWithin(line)) { int firstPosInText = convertToPosInText(str, m_selection.firstPosInLine(line), m_pOptions->m_tabSize); int lastPosInText = convertToPosInText(str, m_selection.lastPosInLine(line), m_pOptions->m_tabSize); int lengthInText = std::max(0, lastPosInText - firstPosInText); if(lengthInText > 0) m_selection.bSelectionContainsData = true; QTextLayout::FormatRange selection; selection.start = firstPosInText; selection.length = lengthInText; selection.format.setBackground(palette().highlight()); selection.format.setForeground(palette().highlightedText().color()); selectionFormat.push_back(selection); } QTextLine textLine = textLayout.createLine(); textLine.setPosition(QPointF(0, fontMetrics().leading())); textLayout.endLayout(); int cursorWidth = 5; if(m_pOptions->m_bRightToLeftLanguage) textLayout.setPosition(QPointF(width() - textLayout.maximumWidth() - getTextXOffset() + m_horizScrollOffset - cursorWidth, 0)); else textLayout.setPosition(QPointF(getTextXOffset() - m_horizScrollOffset, 0)); return selectionFormat; } void MergeResultWindow::writeLine( RLPainter& p, int line, const QString& str, int srcSelect, e_MergeDetails mergeDetails, int rangeMark, bool bUserModified, bool bLineRemoved, bool bWhiteSpaceConflict) { const QFontMetrics& fm = fontMetrics(); int fontHeight = fm.lineSpacing(); int fontAscent = fm.ascent(); int topLineYOffset = 0; int xOffset = getTextXOffset(); int yOffset = (line - m_firstLine) * fontHeight; if(yOffset < 0 || yOffset > height()) return; yOffset += topLineYOffset; QString srcName = QChar(' '); if(bUserModified) srcName = QChar('m'); else if(srcSelect == A && mergeDetails != eNoChange) srcName = i18n("A"); else if(srcSelect == B) srcName = i18n("B"); else if(srcSelect == C) srcName = i18n("C"); if(rangeMark & 4) { p.fillRect(xOffset, yOffset, width(), fontHeight, m_pOptions->m_currentRangeBgColor); } if((srcSelect > 0 || bUserModified) && !bLineRemoved) { if(!m_pOptions->m_bRightToLeftLanguage) p.setClipRect(QRectF(xOffset, 0, width() - xOffset, height())); else p.setClipRect(QRectF(0, 0, width() - xOffset, height())); int outPos = 0; QString s; int size = str.length(); for(int i = 0; i < size; ++i) { int spaces = 1; if(str[i] == '\t') { spaces = tabber(outPos, m_pOptions->m_tabSize); for(int j = 0; j < spaces; ++j) s += ' '; } else { s += str[i]; } outPos += spaces; } p.setPen(m_pOptions->m_fgColor); QTextLayout textLayout(str, font(), this); QVector selectionFormat = getTextLayoutForLine(line, str, textLayout); textLayout.draw(&p, QPointF(0, yOffset), selectionFormat); if(line == m_cursorYPos) { m_cursorXPixelPos = qCeil(textLayout.lineAt(0).cursorToX(m_cursorXPos)); if(m_pOptions->m_bRightToLeftLanguage) m_cursorXPixelPos += qCeil(textLayout.position().x() - m_horizScrollOffset); } p.setClipping(false); p.setPen(m_pOptions->m_fgColor); p.drawText(1, yOffset + fontAscent, srcName, true); } else if(bLineRemoved) { p.setPen(m_pOptions->m_colorForConflict); p.drawText(xOffset, yOffset + fontAscent, i18n("")); p.drawText(1, yOffset + fontAscent, srcName); if(m_cursorYPos == line) m_cursorXPos = 0; } else if(srcSelect == 0) { p.setPen(m_pOptions->m_colorForConflict); if(bWhiteSpaceConflict) p.drawText(xOffset, yOffset + fontAscent, i18n("")); else p.drawText(xOffset, yOffset + fontAscent, i18n("")); p.drawText(1, yOffset + fontAscent, "?"); if(m_cursorYPos == line) m_cursorXPos = 0; } else Q_ASSERT(true); xOffset -= Utils::getHorizontalAdvance(fm, '0'); p.setPen(m_pOptions->m_fgColor); if(rangeMark & 1) // begin mark { p.drawLine(xOffset, yOffset + 1, xOffset, yOffset + fontHeight / 2); p.drawLine(xOffset, yOffset + 1, xOffset - 2, yOffset + 1); } else { p.drawLine(xOffset, yOffset, xOffset, yOffset + fontHeight / 2); } if(rangeMark & 2) // end mark { p.drawLine(xOffset, yOffset + fontHeight / 2, xOffset, yOffset + fontHeight - 1); p.drawLine(xOffset, yOffset + fontHeight - 1, xOffset - 2, yOffset + fontHeight - 1); } else { p.drawLine(xOffset, yOffset + fontHeight / 2, xOffset, yOffset + fontHeight); } if(rangeMark & 4) { p.fillRect(xOffset + 3, yOffset, 3, fontHeight, m_pOptions->m_fgColor); /* p.setPen( blue ); p.drawLine( xOffset+2, yOffset, xOffset+2, yOffset+fontHeight-1 ); p.drawLine( xOffset+3, yOffset, xOffset+3, yOffset+fontHeight-1 );*/ } } void MergeResultWindow::setPaintingAllowed(bool bPaintingAllowed) { setUpdatesEnabled(bPaintingAllowed); if(!bPaintingAllowed) { m_currentMergeLineIt = m_mergeLineList.end(); reset(); } else update(); } void MergeResultWindow::paintEvent(QPaintEvent*) { if(m_pDiff3LineList == nullptr) return; bool bOldSelectionContainsData = m_selection.selectionContainsData(); const QFontMetrics& fm = fontMetrics(); int fontWidth = Utils::getHorizontalAdvance(fm, '0'); if(!m_bCursorUpdate) // Don't redraw everything for blinking cursor? { m_selection.bSelectionContainsData = false; if(size() != m_pixmap.size()) m_pixmap = QPixmap(size()); RLPainter p(&m_pixmap, m_pOptions->m_bRightToLeftLanguage, width(), fontWidth); p.setFont(font()); p.QPainter::fillRect(rect(), m_pOptions->m_bgColor); //int visibleLines = height() / fontHeight; int lastVisibleLine = m_firstLine + getNofVisibleLines() + 5; LineRef line = 0; MergeLineList::iterator mlIt = m_mergeLineList.begin(); for(mlIt = m_mergeLineList.begin(); mlIt != m_mergeLineList.end(); ++mlIt) { MergeLine& ml = *mlIt; if(line > lastVisibleLine || line + ml.mergeEditLineList.size() < m_firstLine) { line += ml.mergeEditLineList.size(); } else { MergeEditLineList::iterator melIt; for(melIt = ml.mergeEditLineList.begin(); melIt != ml.mergeEditLineList.end(); ++melIt) { if(line >= m_firstLine && line <= lastVisibleLine) { MergeEditLine& mel = *melIt; MergeEditLineList::iterator melIt1 = melIt; ++melIt1; int rangeMark = 0; if(melIt == ml.mergeEditLineList.begin()) rangeMark |= 1; // Begin range mark if(melIt1 == ml.mergeEditLineList.end()) rangeMark |= 2; // End range mark if(mlIt == m_currentMergeLineIt) rangeMark |= 4; // Mark of the current line QString s; s = mel.getString(m_pldA, m_pldB, m_pldC); writeLine(p, line, s, mel.src(), ml.mergeDetails, rangeMark, mel.isModified(), mel.isRemoved(), ml.bWhiteSpaceConflict); } ++line; } } } if(line != m_nofLines) { m_nofLines = line; emit resizeSignal(); } p.end(); } QPainter painter(this); if(!m_bCursorUpdate) painter.drawPixmap(0, 0, m_pixmap); else { painter.drawPixmap(0, 0, m_pixmap); // Draw everything. (Internally cursor rect is clipped anyway.) m_bCursorUpdate = false; } if(m_bCursorOn && hasFocus() && m_cursorYPos >= m_firstLine) { painter.setPen(m_pOptions->m_fgColor); QString str = getString(m_cursorYPos); QTextLayout textLayout(str, font(), this); getTextLayoutForLine(m_cursorYPos, str, textLayout); textLayout.drawCursor(&painter, QPointF(0, (m_cursorYPos - m_firstLine) * fontMetrics().lineSpacing()), m_cursorXPos); } painter.end(); if(!bOldSelectionContainsData && m_selection.selectionContainsData()) emit newSelection(); } void MergeResultWindow::updateSourceMask() { int srcMask = 0; int enabledMask = 0; if(!hasFocus() || m_pDiff3LineList == nullptr || !updatesEnabled() || m_currentMergeLineIt == m_mergeLineList.end()) { srcMask = 0; enabledMask = 0; } else { enabledMask = m_pldC == nullptr ? 3 : 7; MergeLine& ml = *m_currentMergeLineIt; srcMask = 0; bool bModified = false; MergeEditLineList::iterator melIt; for(melIt = ml.mergeEditLineList.begin(); melIt != ml.mergeEditLineList.end(); ++melIt) { MergeEditLine& mel = *melIt; if(mel.src() == A) srcMask |= 1; if(mel.src() == B) srcMask |= 2; if(mel.src() == C) srcMask |= 4; if(mel.isModified() || !mel.isEditableText()) bModified = true; } if(ml.mergeDetails == eNoChange) { srcMask = 0; enabledMask = bModified ? 1 : 0; } } emit sourceMask(srcMask, enabledMask); } void MergeResultWindow::focusInEvent(QFocusEvent* e) { updateSourceMask(); QWidget::focusInEvent(e); } LineRef MergeResultWindow::convertToLine(int y) { const QFontMetrics& fm = fontMetrics(); int fontHeight = fm.lineSpacing(); int topLineYOffset = 0; int yOffset = topLineYOffset - m_firstLine * fontHeight; LineRef line = std::min((y - yOffset) / fontHeight, m_nofLines - 1); return line; } void MergeResultWindow::mousePressEvent(QMouseEvent* e) { m_bCursorOn = true; int xOffset = getTextXOffset(); LineRef line = convertToLine(e->y()); QString s = getString(line); QTextLayout textLayout(s, font(), this); getTextLayoutForLine(line, s, textLayout); QtNumberType pos = textLayout.lineAt(0).xToCursor(e->x() - textLayout.position().x()); bool bLMB = e->button() == Qt::LeftButton; bool bMMB = e->button() == Qt::MidButton; bool bRMB = e->button() == Qt::RightButton; if((bLMB && (e->x() < xOffset)) || bRMB) // Fast range selection { m_cursorXPos = 0; m_cursorOldXPixelPos = 0; m_cursorYPos = std::max((LineRef::LineType)line, 0); int l = 0; MergeLineList::iterator i = m_mergeLineList.begin(); for(i = m_mergeLineList.begin(); i != m_mergeLineList.end(); ++i) { if(l == line) break; l += i->mergeEditLineList.size(); if(l > line) break; } m_selection.reset(); // Disable current selection m_bCursorOn = true; setFastSelector(i); if(bRMB) { emit showPopupMenu(QCursor::pos()); } } else if(bLMB) // Normal cursor placement { pos = std::max(pos, 0); line = std::max((LineRef::LineType)line, 0); if(e->QInputEvent::modifiers() & Qt::ShiftModifier) { if(!m_selection.isValidFirstLine()) m_selection.start(line, pos); m_selection.end(line, pos); } else { // Selection m_selection.reset(); m_selection.start(line, pos); m_selection.end(line, pos); } m_cursorXPos = pos; m_cursorXPixelPos = qCeil(textLayout.lineAt(0).cursorToX(pos)); if(m_pOptions->m_bRightToLeftLanguage) m_cursorXPixelPos += qCeil(textLayout.position().x() - m_horizScrollOffset); m_cursorOldXPixelPos = m_cursorXPixelPos; m_cursorYPos = line; update(); //showStatusLine( line, m_winIdx, m_pFilename, m_pDiff3LineList, m_pStatusBar ); } else if(bMMB) // Paste clipboard { pos = std::max(pos, 0); line = std::max((LineRef::LineType)line, 0); m_selection.reset(); m_cursorXPos = pos; m_cursorOldXPixelPos = m_cursorXPixelPos; m_cursorYPos = line; pasteClipboard(true); } } void MergeResultWindow::mouseDoubleClickEvent(QMouseEvent* e) { if(e->button() == Qt::LeftButton) { LineRef line = convertToLine(e->y()); QString s = getString(line); QTextLayout textLayout(s, font(), this); getTextLayoutForLine(line, s, textLayout); int pos = textLayout.lineAt(0).xToCursor(e->x() - textLayout.position().x()); m_cursorXPos = pos; m_cursorOldXPixelPos = m_cursorXPixelPos; m_cursorYPos = line; if(!s.isEmpty()) { int pos1, pos2; calcTokenPos(s, pos, pos1, pos2, m_pOptions->m_tabSize); resetSelection(); m_selection.start(line, convertToPosOnScreen(s, pos1, m_pOptions->m_tabSize)); m_selection.end(line, convertToPosOnScreen(s, pos2, m_pOptions->m_tabSize)); update(); // emit selectionEnd() happens in the mouseReleaseEvent. } } } void MergeResultWindow::mouseReleaseEvent(QMouseEvent* e) { if(e->button() == Qt::LeftButton) { if(m_delayedDrawTimer) { killTimer(m_delayedDrawTimer); m_delayedDrawTimer = 0; } if(m_selection.isValidFirstLine()) { emit selectionEnd(); } } } void MergeResultWindow::mouseMoveEvent(QMouseEvent* e) { LineRef line = convertToLine(e->y()); QString s = getString(line); QTextLayout textLayout(s, font(), this); getTextLayoutForLine(line, s, textLayout); int pos = textLayout.lineAt(0).xToCursor(e->x() - textLayout.position().x()); m_cursorXPos = pos; m_cursorOldXPixelPos = m_cursorXPixelPos; m_cursorYPos = line; if(m_selection.isValidFirstLine()) { m_selection.end(line, pos); myUpdate(0); //showStatusLine( line, m_winIdx, m_pFilename, m_pDiff3LineList, m_pStatusBar ); // Scroll because mouse moved out of the window const QFontMetrics& fm = fontMetrics(); int fontWidth = Utils::getHorizontalAdvance(fm, '0'); int topLineYOffset = 0; int deltaX = 0; int deltaY = 0; if(!m_pOptions->m_bRightToLeftLanguage) { if(e->x() < getTextXOffset()) deltaX = -1; if(e->x() > width()) deltaX = +1; } else { if(e->x() > width() - 1 - getTextXOffset()) deltaX = -1; if(e->x() < fontWidth) deltaX = +1; } if(e->y() < topLineYOffset) deltaY = -1; if(e->y() > height()) deltaY = +1; m_scrollDeltaX = deltaX; m_scrollDeltaY = deltaY; if(deltaX != 0 || deltaY != 0) { emit scrollMergeResultWindow(deltaX, deltaY); } } } void MergeResultWindow::slotCursorUpdate() { m_cursorTimer.stop(); m_bCursorOn = !m_bCursorOn; if(isVisible()) { m_bCursorUpdate = true; const QFontMetrics& fm = fontMetrics(); int topLineYOffset = 0; int yOffset = (m_cursorYPos - m_firstLine) * fm.lineSpacing() + topLineYOffset; repaint(0, yOffset, width(), fm.lineSpacing() + 2); m_bCursorUpdate = false; } m_cursorTimer.start(500); } void MergeResultWindow::wheelEvent(QWheelEvent* e) { int d = -e->delta() * QApplication::wheelScrollLines() / 120; e->accept(); emit scrollMergeResultWindow(0, std::min(d, getNofVisibleLines())); } bool MergeResultWindow::event(QEvent* e) { if(e->type() == QEvent::KeyPress) { QKeyEvent* ke = static_cast(e); if(ke->key() == Qt::Key_Tab) { // special tab handling here to avoid moving focus keyPressEvent(ke); return true; } } return QWidget::event(e); } void MergeResultWindow::keyPressEvent(QKeyEvent* e) { int y = m_cursorYPos; MergeLineList::iterator mlIt; MergeEditLineList::iterator melIt; calcIteratorFromLineNr(y, mlIt, melIt); QString str = melIt->getString(m_pldA, m_pldB, m_pldC); int x = convertToPosInText(str, m_cursorXPos, m_pOptions->m_tabSize); QTextLayout textLayoutOrig(str, font(), this); getTextLayoutForLine(y, str, textLayoutOrig); bool bCtrl = (e->QInputEvent::modifiers() & Qt::ControlModifier) != 0; bool bShift = (e->QInputEvent::modifiers() & Qt::ShiftModifier) != 0; #ifdef Q_OS_WIN bool bAlt = (e->QInputEvent::modifiers() & Qt::AltModifier) != 0; if(bCtrl && bAlt) { bCtrl = false; bAlt = false; } // AltGr-Key pressed. #endif bool bYMoveKey = false; // Special keys switch(e->key()) { case Qt::Key_Escape: - break; //case Key_Tab: break; case Qt::Key_Backtab: break; case Qt::Key_Delete: { - if(deleteSelection2(str, x, y, mlIt, melIt)) break; - if(!melIt->isEditableText()) break; + if(deleteSelection2(str, x, y, mlIt, melIt) || !melIt->isEditableText()) break; + if(x >= str.length()) { if(y < m_nofLines - 1) { setModified(); MergeLineList::iterator mlIt1; MergeEditLineList::iterator melIt1; calcIteratorFromLineNr(y + 1, mlIt1, melIt1); if(melIt1->isEditableText()) { QString s2 = melIt1->getString(m_pldA, m_pldB, m_pldC); melIt->setString(str + s2); // Remove the line if(mlIt1->mergeEditLineList.size() > 1) mlIt1->mergeEditLineList.erase(melIt1); else melIt1->setRemoved(); } } } else { QString s = str.left(x); s += str.midRef(x + 1); melIt->setString(s); setModified(); } break; } case Qt::Key_Backspace: { if(deleteSelection2(str, x, y, mlIt, melIt)) break; if(!melIt->isEditableText()) break; if(x == 0) { if(y > 0) { setModified(); MergeLineList::iterator mlIt1; MergeEditLineList::iterator melIt1; calcIteratorFromLineNr(y - 1, mlIt1, melIt1); if(melIt1->isEditableText()) { QString s1 = melIt1->getString(m_pldA, m_pldB, m_pldC); melIt1->setString(s1 + str); // Remove the previous line if(mlIt->mergeEditLineList.size() > 1) mlIt->mergeEditLineList.erase(melIt); else melIt->setRemoved(); --y; x = str.length(); } } } else { QString s = str.left(x - 1); s += str.midRef(x); --x; melIt->setString(s); setModified(); } break; } case Qt::Key_Return: case Qt::Key_Enter: { if(!melIt->isEditableText()) break; deleteSelection2(str, x, y, mlIt, melIt); setModified(); QString indentation; if(m_pOptions->m_bAutoIndentation) { // calc last indentation MergeLineList::iterator mlIt1 = mlIt; MergeEditLineList::iterator melIt1 = melIt; for(;;) { const QString s = melIt1->getString(m_pldA, m_pldB, m_pldC); if(!s.isEmpty()) { int i; for(i = 0; i < s.length(); ++i) { if(s[i] != ' ' && s[i] != '\t') break; } if(i < s.length()) { indentation = s.left(i); break; } } // Go back one line if(melIt1 != mlIt1->mergeEditLineList.begin()) --melIt1; else { if(mlIt1 == m_mergeLineList.begin()) break; --mlIt1; melIt1 = mlIt1->mergeEditLineList.end(); --melIt1; } } } MergeEditLine mel(mlIt->id3l); // Associate every mel with an id3l, even if not really valid. mel.setString(indentation + str.mid(x)); if(x < str.length()) // Cut off the old line. { // Since ps possibly points into melIt->str, first copy it into a temporary. QString temp = str.left(x); melIt->setString(temp); } ++melIt; mlIt->mergeEditLineList.insert(melIt, mel); x = indentation.length(); ++y; break; } case Qt::Key_Insert: m_bInsertMode = !m_bInsertMode; break; case Qt::Key_Pause: - break; case Qt::Key_Print: - break; case Qt::Key_SysReq: break; case Qt::Key_Home: x = 0; if(bCtrl) { y = 0; } break; // cursor movement case Qt::Key_End: x = INT_MAX; if(bCtrl) { y = INT_MAX; } break; case Qt::Key_Left: case Qt::Key_Right: if((e->key() == Qt::Key_Left) != m_pOptions->m_bRightToLeftLanguage) { if(!bCtrl) { int newX = textLayoutOrig.previousCursorPosition(x); if(newX == x && y > 0) { --y; x = INT_MAX; } else { x = newX; } } else { while(x > 0 && (str[x - 1] == ' ' || str[x - 1] == '\t')) { int newX = textLayoutOrig.previousCursorPosition(x); if(newX == x) break; x = newX; } while(x > 0 && (str[x - 1] != ' ' && str[x - 1] != '\t')) { int newX = textLayoutOrig.previousCursorPosition(x); if(newX == x) break; x = newX; } } } else { if(!bCtrl) { int newX = textLayoutOrig.nextCursorPosition(x); if(newX == x && y < m_nofLines - 1) { ++y; x = 0; } else { x = newX; } } else { while(x < str.length() && (str[x] == ' ' || str[x] == '\t')) { int newX = textLayoutOrig.nextCursorPosition(x); if(newX == x) break; x = newX; } while(x < str.length() && (str[x] != ' ' && str[x] != '\t')) { int newX = textLayoutOrig.nextCursorPosition(x); if(newX == x) break; x = newX; } } } break; case Qt::Key_Up: if(!bCtrl) { --y; bYMoveKey = true; } break; case Qt::Key_Down: if(!bCtrl) { ++y; bYMoveKey = true; } break; case Qt::Key_PageUp: if(!bCtrl) { y -= getNofVisibleLines(); bYMoveKey = true; } break; case Qt::Key_PageDown: if(!bCtrl) { y += getNofVisibleLines(); bYMoveKey = true; } break; default: { QString t = e->text(); if(t.isEmpty() || bCtrl) { e->ignore(); return; } else { if(bCtrl) { e->ignore(); return; } else { if(!melIt->isEditableText()) break; deleteSelection2(str, x, y, mlIt, melIt); setModified(); // Characters to insert QString s = str; if(t[0] == '\t' && m_pOptions->m_bReplaceTabs) { int spaces = (m_cursorXPos / m_pOptions->m_tabSize + 1) * m_pOptions->m_tabSize - m_cursorXPos; t.fill(' ', spaces); } if(m_bInsertMode) s.insert(x, t); else s.replace(x, t.length(), t); melIt->setString(s); x += t.length(); bShift = false; } } } } y = qBound(0, y, m_nofLines - 1); calcIteratorFromLineNr(y, mlIt, melIt); str = melIt->getString(m_pldA, m_pldB, m_pldC); x = qBound(0, x, (int)str.length()); int newFirstLine = m_firstLine; int newHorizScrollOffset = m_horizScrollOffset; if(y < m_firstLine) newFirstLine = y; else if(y > m_firstLine + getNofVisibleLines()) newFirstLine = y - getNofVisibleLines(); QTextLayout textLayout(str, font(), this); getTextLayoutForLine(m_cursorYPos, str, textLayout); // try to preserve cursor x pixel position when moving to another line if(bYMoveKey) { if(m_pOptions->m_bRightToLeftLanguage) x = textLayout.lineAt(0).xToCursor(m_cursorOldXPixelPos - (textLayout.position().x() - m_horizScrollOffset)); else x = textLayout.lineAt(0).xToCursor(m_cursorOldXPixelPos); } m_cursorXPixelPos = qCeil(textLayout.lineAt(0).cursorToX(x)); int hF = 1; // horizontal factor if(m_pOptions->m_bRightToLeftLanguage) { m_cursorXPixelPos += qCeil(textLayout.position().x() - m_horizScrollOffset); hF = -1; } int cursorWidth = 5; if(m_cursorXPixelPos < hF * m_horizScrollOffset) newHorizScrollOffset = hF * m_cursorXPixelPos; else if(m_cursorXPixelPos > hF * m_horizScrollOffset + getVisibleTextAreaWidth() - cursorWidth) newHorizScrollOffset = hF * (m_cursorXPixelPos - (getVisibleTextAreaWidth() - cursorWidth)); int newCursorX = x; if(bShift) { if(!m_selection.isValidFirstLine()) m_selection.start(m_cursorYPos, m_cursorXPos); m_selection.end(y, newCursorX); } else m_selection.reset(); m_cursorYPos = y; m_cursorXPos = newCursorX; // TODO if width of current line exceeds the current maximum width then force recalculating the scrollbars if(textLayout.maximumWidth() > getMaxTextWidth()) { m_maxTextWidth = qCeil(textLayout.maximumWidth()); emit resizeSignal(); } if(!bYMoveKey) m_cursorOldXPixelPos = m_cursorXPixelPos; m_bCursorOn = true; m_cursorTimer.start(500); update(); if(newFirstLine != m_firstLine || newHorizScrollOffset != m_horizScrollOffset) { emit scrollMergeResultWindow(newHorizScrollOffset - m_horizScrollOffset, newFirstLine - m_firstLine); return; } } void MergeResultWindow::calcIteratorFromLineNr( int line, MergeLineList::iterator& mlIt, MergeEditLineList::iterator& melIt) { for(mlIt = m_mergeLineList.begin(); mlIt != m_mergeLineList.end(); ++mlIt) { MergeLine& ml = *mlIt; if(line > ml.mergeEditLineList.size()) { line -= ml.mergeEditLineList.size(); } else { for(melIt = ml.mergeEditLineList.begin(); melIt != ml.mergeEditLineList.end(); ++melIt) { --line; if(line < 0) return; } } } } QString MergeResultWindow::getSelection() { QString selectionString; int line = 0; MergeLineList::iterator mlIt = m_mergeLineList.begin(); for(mlIt = m_mergeLineList.begin(); mlIt != m_mergeLineList.end(); ++mlIt) { MergeLine& ml = *mlIt; MergeEditLineList::iterator melIt; for(melIt = ml.mergeEditLineList.begin(); melIt != ml.mergeEditLineList.end(); ++melIt) { MergeEditLine& mel = *melIt; if(m_selection.lineWithin(line)) { int outPos = 0; if(mel.isEditableText()) { const QString str = mel.getString(m_pldA, m_pldB, m_pldC); // Consider tabs for(int i = 0; i < str.length(); ++i) { int spaces = 1; if(str[i] == '\t') { spaces = tabber(outPos, m_pOptions->m_tabSize); } if(m_selection.within(line, outPos)) { selectionString += str[i]; } outPos += spaces; } } else if(mel.isConflict()) { selectionString += i18n(""); } if(m_selection.within(line, outPos)) { #ifdef Q_OS_WIN selectionString += '\r'; #endif selectionString += '\n'; } } ++line; } } return selectionString; } bool MergeResultWindow::deleteSelection2(QString& s, int& x, int& y, MergeLineList::iterator& mlIt, MergeEditLineList::iterator& melIt) { if(m_selection.selectionContainsData()) { Q_ASSERT(m_selection.isValidFirstLine()); deleteSelection(); y = m_cursorYPos; calcIteratorFromLineNr(y, mlIt, melIt); s = melIt->getString(m_pldA, m_pldB, m_pldC); x = convertToPosInText(s, m_cursorXPos, m_pOptions->m_tabSize); return true; } return false; } void MergeResultWindow::deleteSelection() { if(!m_selection.selectionContainsData()) { return; } Q_ASSERT(m_selection.isValidFirstLine()); setModified(); LineRef line = 0; MergeLineList::iterator mlItFirst; MergeEditLineList::iterator melItFirst; QString firstLineString; LineRef firstLine; LineRef lastLine; MergeLineList::iterator mlIt; for(mlIt = m_mergeLineList.begin(); mlIt != m_mergeLineList.end(); ++mlIt) { MergeLine& ml = *mlIt; MergeEditLineList::iterator melIt; for(melIt = ml.mergeEditLineList.begin(); melIt != ml.mergeEditLineList.end(); ++melIt) { MergeEditLine& mel = *melIt; if(mel.isEditableText() && m_selection.lineWithin(line)) { if(!firstLine.isValid()) firstLine = line; lastLine = line; } ++line; } } if(!firstLine.isValid()) { return; // Nothing to delete. } line = 0; for(mlIt = m_mergeLineList.begin(); mlIt != m_mergeLineList.end(); ++mlIt) { MergeLine& ml = *mlIt; MergeEditLineList::iterator melIt, melIt1; for(melIt = ml.mergeEditLineList.begin(); melIt != ml.mergeEditLineList.end();) { MergeEditLine& mel = *melIt; melIt1 = melIt; ++melIt1; if(mel.isEditableText() && m_selection.lineWithin(line)) { QString lineString = mel.getString(m_pldA, m_pldB, m_pldC); int firstPosInLine = m_selection.firstPosInLine(line); int lastPosInLine = m_selection.lastPosInLine(line); if(line == firstLine) { mlItFirst = mlIt; melItFirst = melIt; int pos = convertToPosInText(lineString, firstPosInLine, m_pOptions->m_tabSize); firstLineString = lineString.left(pos); } if(line == lastLine) { // This is the last line in the selection int pos = convertToPosInText(lineString, lastPosInLine, m_pOptions->m_tabSize); firstLineString += lineString.midRef(pos); // rest of line melItFirst->setString(firstLineString); } if(line != firstLine || (m_selection.endPos() - m_selection.beginPos()) == lineString.length()) { // Remove the line if(mlIt->mergeEditLineList.size() > 1) mlIt->mergeEditLineList.erase(melIt); else melIt->setRemoved(); } } ++line; melIt = melIt1; } } m_cursorYPos = m_selection.beginLine(); m_cursorXPos = m_selection.beginPos(); m_cursorOldXPixelPos = m_cursorXPixelPos; m_selection.reset(); } void MergeResultWindow::pasteClipboard(bool bFromSelection) { //checking of m_selection if needed is done by deleteSelection no need for check here. deleteSelection(); setModified(); int y = m_cursorYPos; MergeLineList::iterator mlIt; MergeEditLineList::iterator melIt, melItAfter; calcIteratorFromLineNr(y, mlIt, melIt); melItAfter = melIt; ++melItAfter; QString str = melIt->getString(m_pldA, m_pldB, m_pldC); int x = convertToPosInText(str, m_cursorXPos, m_pOptions->m_tabSize); if(!QApplication::clipboard()->supportsSelection()) bFromSelection = false; QString clipBoard = QApplication::clipboard()->text(bFromSelection ? QClipboard::Selection : QClipboard::Clipboard); QString currentLine = str.left(x); QString endOfLine = str.mid(x); int i; int len = clipBoard.length(); for(i = 0; i < len; ++i) { QChar c = clipBoard[i]; if(c == '\r') continue; if(c == '\n') { melIt->setString(currentLine); MergeEditLine mel(mlIt->id3l); // Associate every mel with an id3l, even if not really valid. melIt = mlIt->mergeEditLineList.insert(melItAfter, mel); currentLine = ""; x = 0; ++y; } else { currentLine += c; ++x; } } currentLine += endOfLine; melIt->setString(currentLine); m_cursorYPos = y; m_cursorXPos = convertToPosOnScreen(currentLine, x, m_pOptions->m_tabSize); m_cursorOldXPixelPos = m_cursorXPixelPos; update(); } void MergeResultWindow::resetSelection() { m_selection.reset(); update(); } void MergeResultWindow::setModified(bool bModified) { if(bModified != m_bModified) { m_bModified = bModified; emit modifiedChanged(m_bModified); } } /// Saves and returns true when successful. bool MergeResultWindow::saveDocument(const QString& fileName, QTextCodec* pEncoding, e_LineEndStyle eLineEndStyle) { // Are still conflicts somewhere? if(getNrOfUnsolvedConflicts() > 0) { KMessageBox::error(this, i18n("Not all conflicts are solved yet.\n" "File not saved."), i18n("Conflicts Left")); return false; } if(eLineEndStyle == eLineEndStyleConflict || eLineEndStyle == eLineEndStyleUndefined) { KMessageBox::error(this, i18n("There is a line end style conflict. Please choose the line end style manually.\n" "File not saved."), i18n("Conflicts Left")); return false; } update(); FileAccess file(fileName, true /*bWantToWrite*/); if(m_pOptions->m_bDmCreateBakFiles && file.exists()) { bool bSuccess = file.createBackup(".orig"); if(!bSuccess) { KMessageBox::error(this, file.getStatusText() + i18n("\n\nCreating backup failed. File not saved."), i18n("File Save Error")); return false; } } QByteArray dataArray; QTextStream textOutStream(&dataArray, QIODevice::WriteOnly); if(pEncoding->name() == "UTF-8") textOutStream.setGenerateByteOrderMark(false); // Shouldn't be necessary. Bug in Qt or docs else textOutStream.setGenerateByteOrderMark(true); // Only for UTF-16 textOutStream.setCodec(pEncoding); int line = 0; MergeLineList::iterator mlIt = m_mergeLineList.begin(); for(mlIt = m_mergeLineList.begin(); mlIt != m_mergeLineList.end(); ++mlIt) { MergeLine& ml = *mlIt; MergeEditLineList::iterator melIt; for(melIt = ml.mergeEditLineList.begin(); melIt != ml.mergeEditLineList.end(); ++melIt) { MergeEditLine& mel = *melIt; if(mel.isEditableText()) { QString str = mel.getString(m_pldA, m_pldB, m_pldC); if(line > 0) // Prepend line feed, but not for first line { if(eLineEndStyle == eLineEndStyleDos) { str.prepend("\r\n"); } else { str.prepend("\n"); } } textOutStream << str; ++line; } } } textOutStream.flush(); bool bSuccess = file.writeFile(dataArray.data(), dataArray.size()); if(!bSuccess) { KMessageBox::error(this, i18n("Error while writing."), i18n("File Save Error")); return false; } setModified(false); update(); return true; } QString MergeResultWindow::getString(int lineIdx) { MergeLineList::iterator mlIt; MergeEditLineList::iterator melIt; if(m_mergeLineList.empty()) { return QString(); } calcIteratorFromLineNr(lineIdx, mlIt, melIt); QString s = melIt->getString(m_pldA, m_pldB, m_pldC); return s; } bool MergeResultWindow::findString(const QString& s, LineRef& d3vLine, int& posInLine, bool bDirDown, bool bCaseSensitive) { int it = d3vLine; int endIt = bDirDown ? getNofLines() : -1; int step = bDirDown ? 1 : -1; int startPos = posInLine; for(; it != endIt; it += step) { QString line = getString(it); if(!line.isEmpty()) { int pos = line.indexOf(s, startPos, bCaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive); if(pos != -1) { d3vLine = it; posInLine = pos; return true; } startPos = 0; } } return false; } void MergeResultWindow::setSelection(int firstLine, int startPos, int lastLine, int endPos) { if(lastLine >= getNofLines()) { lastLine = getNofLines() - 1; QString s = getString(lastLine); endPos = s.length(); } m_selection.reset(); m_selection.start(firstLine, convertToPosOnScreen(getString(firstLine), startPos, m_pOptions->m_tabSize)); m_selection.end(lastLine, convertToPosOnScreen(getString(lastLine), endPos, m_pOptions->m_tabSize)); update(); } WindowTitleWidget::WindowTitleWidget(const QSharedPointer &pOptions) { m_pOptions = pOptions; setAutoFillBackground(true); QHBoxLayout* pHLayout = new QHBoxLayout(this); pHLayout->setMargin(2); pHLayout->setSpacing(2); m_pLabel = new QLabel(i18n("Output:")); pHLayout->addWidget(m_pLabel); m_pFileNameLineEdit = new FileNameLineEdit(); pHLayout->addWidget(m_pFileNameLineEdit, 6); m_pFileNameLineEdit->installEventFilter(this);//for focus tracking m_pFileNameLineEdit->setAcceptDrops(true); m_pFileNameLineEdit->setReadOnly(true); //m_pBrowseButton = new QPushButton("..."); //pHLayout->addWidget( m_pBrowseButton, 0 ); //connect( m_pBrowseButton, &QPushButton::clicked), this, &MergeResultWindow::slotBrowseButtonClicked); m_pModifiedLabel = new QLabel(i18n("[Modified]")); pHLayout->addWidget(m_pModifiedLabel); m_pModifiedLabel->setMinimumSize(m_pModifiedLabel->sizeHint()); m_pModifiedLabel->setText(""); pHLayout->addStretch(1); m_pEncodingLabel = new QLabel(i18n("Encoding for saving:")); pHLayout->addWidget(m_pEncodingLabel); m_pEncodingSelector = new QComboBox(); m_pEncodingSelector->setSizeAdjustPolicy(QComboBox::AdjustToContents); pHLayout->addWidget(m_pEncodingSelector, 2); setEncodings(nullptr, nullptr, nullptr); m_pLineEndStyleLabel = new QLabel(i18n("Line end style:")); pHLayout->addWidget(m_pLineEndStyleLabel); m_pLineEndStyleSelector = new QComboBox(); m_pLineEndStyleSelector->setSizeAdjustPolicy(QComboBox::AdjustToContents); pHLayout->addWidget(m_pLineEndStyleSelector); setLineEndStyles(eLineEndStyleUndefined, eLineEndStyleUndefined, eLineEndStyleUndefined); } void WindowTitleWidget::setFileName(const QString& fileName) { m_pFileNameLineEdit->setText(QDir::toNativeSeparators(fileName)); } QString WindowTitleWidget::getFileName() { return m_pFileNameLineEdit->text(); } //static QString getLineEndStyleName( e_LineEndStyle eLineEndStyle ) //{ // if ( eLineEndStyle == eLineEndStyleDos ) // return "DOS"; // else if ( eLineEndStyle == eLineEndStyleUnix ) // return "Unix"; // return QString(); //} void WindowTitleWidget::setLineEndStyles(e_LineEndStyle eLineEndStyleA, e_LineEndStyle eLineEndStyleB, e_LineEndStyle eLineEndStyleC) { m_pLineEndStyleSelector->clear(); QString dosUsers; if(eLineEndStyleA == eLineEndStyleDos) dosUsers += i18n("A"); if(eLineEndStyleB == eLineEndStyleDos) dosUsers += QLatin1String(dosUsers.isEmpty() ? "" : ", ") + i18n("B"); if(eLineEndStyleC == eLineEndStyleDos) dosUsers += QLatin1String(dosUsers.isEmpty() ? "" : ", ") + i18n("C"); QString unxUsers; if(eLineEndStyleA == eLineEndStyleUnix) unxUsers += i18n("A"); if(eLineEndStyleB == eLineEndStyleUnix) unxUsers += QLatin1String(unxUsers.isEmpty() ? "" : ", ") + i18n("B"); if(eLineEndStyleC == eLineEndStyleUnix) unxUsers += QLatin1String(unxUsers.isEmpty() ? "" : ", ") + i18n("C"); m_pLineEndStyleSelector->addItem(i18n("Unix") + (unxUsers.isEmpty() ? QString("") : QLatin1String(" (") + unxUsers + QLatin1String(")"))); m_pLineEndStyleSelector->addItem(i18n("DOS") + (dosUsers.isEmpty() ? QString("") : QLatin1String(" (") + dosUsers + QLatin1String(")"))); e_LineEndStyle autoChoice = (e_LineEndStyle)m_pOptions->m_lineEndStyle; if(m_pOptions->m_lineEndStyle == eLineEndStyleAutoDetect) { if(eLineEndStyleA != eLineEndStyleUndefined && eLineEndStyleB != eLineEndStyleUndefined && eLineEndStyleC != eLineEndStyleUndefined) { if(eLineEndStyleA == eLineEndStyleB) autoChoice = eLineEndStyleC; else if(eLineEndStyleA == eLineEndStyleC) autoChoice = eLineEndStyleB; else autoChoice = eLineEndStyleConflict; //conflict (not likely while only two values exist) } else { e_LineEndStyle c1, c2; if(eLineEndStyleA == eLineEndStyleUndefined) { c1 = eLineEndStyleB; c2 = eLineEndStyleC; } else if(eLineEndStyleB == eLineEndStyleUndefined) { c1 = eLineEndStyleA; c2 = eLineEndStyleC; } else /*if( eLineEndStyleC == eLineEndStyleUndefined )*/ { c1 = eLineEndStyleA; c2 = eLineEndStyleB; } if(c1 == c2 && c1 != eLineEndStyleUndefined) autoChoice = c1; else autoChoice = eLineEndStyleConflict; } } if(autoChoice == eLineEndStyleUnix) m_pLineEndStyleSelector->setCurrentIndex(0); else if(autoChoice == eLineEndStyleDos) m_pLineEndStyleSelector->setCurrentIndex(1); else if(autoChoice == eLineEndStyleConflict) { m_pLineEndStyleSelector->addItem(i18n("Conflict")); m_pLineEndStyleSelector->setCurrentIndex(2); } } e_LineEndStyle WindowTitleWidget::getLineEndStyle() { int current = m_pLineEndStyleSelector->currentIndex(); if(current == 0) return eLineEndStyleUnix; else if(current == 1) return eLineEndStyleDos; else return eLineEndStyleConflict; } void WindowTitleWidget::setEncodings(QTextCodec* pCodecForA, QTextCodec* pCodecForB, QTextCodec* pCodecForC) { m_pEncodingSelector->clear(); // First sort codec names: std::map names; QList mibs = QTextCodec::availableMibs(); foreach(int i, mibs) { QTextCodec* c = QTextCodec::codecForMib(i); if(c != nullptr) names[QLatin1String(c->name())] = c; } if(pCodecForA != nullptr) m_pEncodingSelector->addItem(i18n("Codec from A: %1", QLatin1String(pCodecForA->name())), QVariant::fromValue((void*)pCodecForA)); if(pCodecForB != nullptr) m_pEncodingSelector->addItem(i18n("Codec from B: %1", QLatin1String(pCodecForB->name())), QVariant::fromValue((void*)pCodecForB)); if(pCodecForC != nullptr) m_pEncodingSelector->addItem(i18n("Codec from C: %1", QLatin1String(pCodecForC->name())), QVariant::fromValue((void*)pCodecForC)); std::map::iterator it; for(it = names.begin(); it != names.end(); ++it) { m_pEncodingSelector->addItem(it->first, QVariant::fromValue((void*)it->second)); } m_pEncodingSelector->setMinimumSize(m_pEncodingSelector->sizeHint()); if(pCodecForC != nullptr && pCodecForB != nullptr && pCodecForA != nullptr) { - if(pCodecForA == pCodecForB) - m_pEncodingSelector->setCurrentIndex(2); // C - else if(pCodecForA == pCodecForC) + if(pCodecForA == pCodecForC) m_pEncodingSelector->setCurrentIndex(1); // B else m_pEncodingSelector->setCurrentIndex(2); // C } else if(pCodecForA != nullptr && pCodecForB != nullptr) m_pEncodingSelector->setCurrentIndex(1); // B else m_pEncodingSelector->setCurrentIndex(0); } QTextCodec* WindowTitleWidget::getEncoding() { return (QTextCodec*)m_pEncodingSelector->itemData(m_pEncodingSelector->currentIndex()).value(); } void WindowTitleWidget::setEncoding(QTextCodec* pEncoding) { int idx = m_pEncodingSelector->findText(QLatin1String(pEncoding->name())); if(idx >= 0) m_pEncodingSelector->setCurrentIndex(idx); } //void WindowTitleWidget::slotBrowseButtonClicked() //{ // QString current = m_pFileNameLineEdit->text(); // // QUrl newURL = KFileDialog::getSaveUrl( current, 0, this, i18n("Select file (not saving yet)")); // if ( !newURL.isEmpty() ) // { // m_pFileNameLineEdit->setText( newURL.url() ); // } //} void WindowTitleWidget::slotSetModified(bool bModified) { m_pModifiedLabel->setText(bModified ? i18n("[Modified]") : ""); } bool WindowTitleWidget::eventFilter(QObject* o, QEvent* e) { Q_UNUSED(o); if(e->type() == QEvent::FocusIn || e->type() == QEvent::FocusOut) { QPalette p = m_pLabel->palette(); QColor c1 = m_pOptions->m_fgColor; QColor c2 = Qt::lightGray; if(e->type() == QEvent::FocusOut) c2 = m_pOptions->m_bgColor; p.setColor(QPalette::Window, c2); setPalette(p); p.setColor(QPalette::WindowText, c1); m_pLabel->setPalette(p); m_pEncodingLabel->setPalette(p); m_pEncodingSelector->setPalette(p); } return false; } //#include "mergeresultwindow.moc"