diff --git a/src/common.cpp b/src/common.cpp index 7b856db..17cfde7 100644 --- a/src/common.cpp +++ b/src/common.cpp @@ -1,329 +1,330 @@ /*************************************************************************** * Copyright (C) 2004-2007 by Joachim Eibl * - * joachim.eibl at gmx.de * + * joachim.eibl at gmx.de * + * 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. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "common.h" #include #include #include #include #include #include #include ValueMap::ValueMap() { } ValueMap::~ValueMap() { } void ValueMap::save(QTextStream& ts) { std::map::iterator i; for(i = m_map.begin(); i != m_map.end(); ++i) { QString key = i->first; QString val = i->second; ts << key << "=" << val << "\n"; } } QString ValueMap::getAsString() { QString result; std::map::iterator i; for(i = m_map.begin(); i != m_map.end(); ++i) { QString key = i->first; QString val = i->second; result += key + '=' + val + '\n'; } return result; } void ValueMap::load(QTextStream& ts) { while(!ts.atEnd()) { // until end of file... QString s = ts.readLine(); // line of text excluding '\n' int pos = s.indexOf('='); if(pos > 0) // seems not to have a tag { QString key = s.left(pos); QString val = s.mid(pos + 1); m_map[key] = val; } } } // safeStringJoin and safeStringSplit allow to convert a stringlist into a string and back // safely, even if the individual strings in the list contain the separator character. QString safeStringJoin(const QStringList& sl, char sepChar, char metaChar) { // Join the strings in the list, using the separator ',' // If a string contains the separator character, it will be replaced with "\,". // Any occurrences of "\" (one backslash) will be replaced with "\\" (2 backslashes) Q_ASSERT(sepChar != metaChar); QString sep; sep += sepChar; QString meta; meta += metaChar; QString safeString; QStringList::const_iterator i; for(i = sl.begin(); i != sl.end(); ++i) { QString s = *i; s.replace(meta, meta + meta); // "\" -> "\\" s.replace(sep, meta + sep); // "," -> "\," if(i == sl.begin()) safeString = s; else safeString += sep + s; } return safeString; } // Split a string that was joined with safeStringJoin QStringList safeStringSplit(const QString& s, char sepChar, char metaChar) { Q_ASSERT(sepChar != metaChar); QStringList sl; // Miniparser int i = 0; int len = s.length(); QString b; for(i = 0; i < len; ++i) { if(i + 1 < len && s[i] == metaChar && s[i + 1] == metaChar) { b += metaChar; ++i; } else if(i + 1 < len && s[i] == metaChar && s[i + 1] == sepChar) { b += sepChar; ++i; } else if(s[i] == sepChar) // real separator { sl.push_back(b); b = ""; } else { b += s[i]; } } if(!b.isEmpty()) sl.push_back(b); return sl; } void ValueMap::writeEntry(const QString& k, const QFont& v) { m_map[k] = v.family() + QLatin1String(",") + QString::number(v.pointSize()) + QLatin1String(",") + (v.bold() ? QLatin1String("bold") : QLatin1String("normal")); } void ValueMap::writeEntry(const QString& k, const QColor& v) { m_map[k].setNum(v.red()) + QLatin1String(",") + QString().setNum(v.green()) + QLatin1String(",") + QString().setNum(v.blue()); } void ValueMap::writeEntry(const QString& k, const QSize& v) { m_map[k].setNum(v.width()) + QLatin1String(",") + QString().setNum(v.height()); } void ValueMap::writeEntry(const QString& k, const QPoint& v) { m_map[k].setNum(v.x()) + QLatin1String(",") + QString().setNum(v.y()); } void ValueMap::writeEntry(const QString& k, int v) { m_map[k].setNum(v); } void ValueMap::writeEntry(const QString& k, bool v) { m_map[k].setNum(v); } void ValueMap::writeEntry(const QString& k, const QString& v) { m_map[k] = v; } void ValueMap::writeEntry(const QString& k, const char* v) { m_map[k] = QLatin1String(v); } void ValueMap::writeEntry(const QString& k, const QStringList& v) { m_map[k] = safeStringJoin(v); } QFont ValueMap::readFontEntry(const QString& k, const QFont* defaultVal) { QFont f = *defaultVal; std::map::iterator i = m_map.find(k); if(i != m_map.end()) { f.setFamily(i->second.split(',')[0]); f.setPointSize(i->second.split(',')[1].toInt()); f.setBold(i->second.split(',')[2] == "bold"); } return f; } QColor ValueMap::readColorEntry(const QString& k, const QColor* defaultVal) { QColor c = *defaultVal; std::map::iterator i = m_map.find(k); if(i != m_map.end()) { QString s = i->second; c = QColor(s.split(',')[0].toInt(), s.split(',')[1].toInt(), s.split(',')[2].toInt()); } return c; } QSize ValueMap::readSizeEntry(const QString& k, const QSize* defaultVal) { QSize size = defaultVal ? *defaultVal : QSize(600, 400); std::map::iterator i = m_map.find(k); if(i != m_map.end()) { QString s = i->second; size = QSize(s.split(',')[0].toInt(), s.split(',')[1].toInt()); } return size; } QPoint ValueMap::readPointEntry(const QString& k, const QPoint* defaultVal) { QPoint point = defaultVal ? *defaultVal : QPoint(0, 0); std::map::iterator i = m_map.find(k); if(i != m_map.end()) { QString s = i->second; point = QPoint(s.split(',')[0].toInt(), s.split(',')[1].toInt()); } return point; } bool ValueMap::readBoolEntry(const QString& k, bool bDefault) { bool b = bDefault; std::map::iterator i = m_map.find(k); if(i != m_map.end()) { QString s = i->second; b = (s.split(',')[0].toInt() == 1); } return b; } int ValueMap::readNumEntry(const QString& k, int iDefault) { int ival = iDefault; std::map::iterator i = m_map.find(k); if(i != m_map.end()) { QString s = i->second; ival = s.split(',')[0].toInt(); } return ival; } QString ValueMap::readStringEntry(const QString& k, const QString& sDefault) { QString sval = sDefault; std::map::iterator i = m_map.find(k); if(i != m_map.end()) { sval = i->second; } return sval; } QStringList ValueMap::readListEntry(const QString& k, const QStringList& defaultVal) { QStringList strList; std::map::iterator i = m_map.find(k); if(i != m_map.end()) { strList = safeStringSplit(i->second); return strList; } else return defaultVal; } QString ValueMap::readEntry(const QString& s, const QString& defaultVal) { return readStringEntry(s, defaultVal); } QString ValueMap::readEntry(const QString& s, const char* defaultVal) { return readStringEntry(s, QString::fromLatin1(defaultVal)); } QFont ValueMap::readEntry(const QString& s, const QFont& defaultVal) { return readFontEntry(s, &defaultVal); } QColor ValueMap::readEntry(const QString& s, const QColor defaultVal) { return readColorEntry(s, &defaultVal); } QSize ValueMap::readEntry(const QString& s, const QSize defaultVal) { return readSizeEntry(s, &defaultVal); } QPoint ValueMap::readEntry(const QString& s, const QPoint defaultVal) { return readPointEntry(s, &defaultVal); } bool ValueMap::readEntry(const QString& s, bool bDefault) { return readBoolEntry(s, bDefault); } int ValueMap::readEntry(const QString& s, int iDefault) { return readNumEntry(s, iDefault); } QStringList ValueMap::readEntry(const QString& s, const QStringList& defaultVal) { return readListEntry(s, defaultVal); } diff --git a/src/directorymergewindow.cpp b/src/directorymergewindow.cpp index f3ea8fb..80b57cf 100644 --- a/src/directorymergewindow.cpp +++ b/src/directorymergewindow.cpp @@ -1,3520 +1,3526 @@ /*************************************************************************** * 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 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* q; explicit DirectoryMergeWindowPrivate(DirectoryMergeWindow* pDMW) { q = pDMW; m_pOptions = nullptr; m_pDirectoryMergeInfo = nullptr; m_bSimulatedMergeStarted = false; m_bRealMergeStarted = false; m_bError = false; m_bSyncMode = false; m_pStatusInfo = new StatusInfo(q); m_pStatusInfo->hide(); m_bScanning = false; m_bCaseSensitive = true; m_bUnfoldSubdirs = false; m_bSkipDirStatus = false; m_pRoot = new MergeFileInfos; } ~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; } static void setPixmaps(MergeFileInfos& mfi, bool); Options* m_pOptions; void calcDirStatus(bool bThreeDirs, const QModelIndex& mi, int& nofFiles, int& nofDirs, int& nofEqualFiles, int& nofManualMerges); void mergeContinue(bool bStart, bool bVerbose); void prepareListView(ProgressProxy& pp); void calcSuggestedOperation(const QModelIndex& mi, e_MergeOperation 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); bool fastFileComparison(FileAccess& fi1, FileAccess& fi2, bool& bError, QString& status); bool compareFilesAndCalcAges(MergeFileInfos& mfi, QStringList& errors); void setMergeOperation(const QModelIndex& mi, e_MergeOperation eMergeOp, bool bRecursive = true); bool isDir(const QModelIndex& mi); QString getFileName(const QModelIndex& mi); bool copyFLD(const QString& srcName, const QString& destName); bool deleteFLD(const QString& name, bool bCreateBackup); bool makeDir(const QString& name, bool bQuiet = false); bool renameFLD(const QString& srcName, const QString& destName); bool mergeFLD(const QString& nameA, const QString& nameB, const QString& nameC, const QString& nameDest, bool& bSingleFileMerge); t_DirectoryList m_dirListA; t_DirectoryList m_dirListB; t_DirectoryList m_dirListC; QString m_dirMergeStateFilename; void buildMergeMap(const QSharedPointer& dirInfo); private: class FileKey { public: const FileAccess* m_pFA; explicit FileKey(const FileAccess& fa) : m_pFA(&fa) {} int getParents(const FileAccess* pFA, const FileAccess* v[]) const { int s = 0; for(s = 0; pFA->parent() != nullptr; pFA = pFA->parent(), ++s) v[s] = pFA; return s; } // This is essentially the same as // int r = filePath().compare( fa.filePath() ) // if ( r<0 ) return true; // if ( r==0 ) return m_col < fa.m_col; // return false; bool operator<(const FileKey& fk) const { const FileAccess* v1[100]; const FileAccess* v2[100]; int v1Size = getParents(m_pFA, v1); int v2Size = getParents(fk.m_pFA, v2); for(int i = 0; i < v1Size && i < v2Size; ++i) { int r = v1[v1Size - i - 1]->fileName().compare(v2[v2Size - i - 1]->fileName(), s_eCaseSensitivity); if(r < 0) return true; else if(r > 0) return false; } return v1Size < v2Size; } }; typedef QMap t_fileMergeMap; MergeFileInfos* m_pRoot; public: t_fileMergeMap m_fileMergeMap; bool m_bFollowDirLinks; bool m_bFollowFileLinks; bool m_bSimulatedMergeStarted; bool m_bRealMergeStarted; bool m_bError; bool m_bSyncMode; bool m_bDirectoryMerge; // if true, then merge is the default operation, otherwise it's diff. bool m_bCaseSensitive; bool m_bUnfoldSubdirs; bool m_bSkipDirStatus; bool m_bScanning; // true while in init() DirectoryMergeInfo* m_pDirectoryMergeInfo; StatusInfo* m_pStatusInfo; typedef std::list MergeItemList; // linked list MergeItemList m_mergeItemList; MergeItemList::iterator m_currentIndexForOperation; QModelIndex m_selection1Index; QModelIndex m_selection2Index; QModelIndex m_selection3Index; void selectItemAndColumn(const QModelIndex& mi, bool bContextMenu); 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 i18n("Unsolved"); case s_SolvedCol: return i18n("Solved"); case s_NonWhiteCol: return i18n("Nonwhite"); case s_WhiteCol: return i18n("White"); //default : return QVariant(); } if(s_OpCol == index.column()) { bool bDir = pMFI->dirA() || pMFI->dirB() || pMFI->dirC(); switch(pMFI->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->isLinkA() || pMFI->isLinkB() || pMFI->isLinkC(), pMFI->dirA() || pMFI->dirB() || pMFI->dirC()); } if(s_ACol == index.column()) { return PixMapUtils::getOnePixmap(pMFI->m_ageA, pMFI->isLinkA(), pMFI->dirA()); } if(s_BCol == index.column()) { return PixMapUtils::getOnePixmap(pMFI->m_ageB, pMFI->isLinkB(), pMFI->dirB()); } if(s_CCol == index.column()) { return PixMapUtils::getOnePixmap(pMFI->m_ageC, pMFI->isLinkC(), pMFI->dirC()); } } else if(role == Qt::TextAlignmentRole) { if(s_UnsolvedCol == index.column() || s_SolvedCol == index.column() || s_NonWhiteCol == index.column() || s_WhiteCol == index.column()) return Qt::AlignRight; } } return QVariant(); } QVariant DirectoryMergeWindow::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* p, const QStyleOptionViewItem& option, const QModelIndex& index) const override { int column = index.column(); if(column == s_ACol || column == s_BCol || column == s_CCol) { QVariant value = index.data(Qt::DecorationRole); QPixmap icon; if(value.isValid()) { if(value.type() == QVariant::Icon) { icon = qvariant_cast(value).pixmap(16, 16); //icon = qvariant_cast(value); //decorationRect = QRect(QPoint(0, 0), icon.actualSize(option.decorationSize, iconMode, iconState)); } else { icon = qvariant_cast(value); //decorationRect = QRect(QPoint(0, 0), option.decorationSize).intersected(pixmap.rect()); } } int x = option.rect.left(); int y = option.rect.top(); //QPixmap icon = value.value(); //pixmap(column); if(!icon.isNull()) { int yOffset = (sizeHint(option, index).height() - icon.height()) / 2; p->drawPixmap(x + 2, y + yOffset, icon); int i = index == d->m_selection1Index ? 1 : index == d->m_selection2Index ? 2 : index == d->m_selection3Index ? 3 : 0; if(i != 0) { Options* pOpts = d->m_pOptions; QColor c(i == 1 ? pOpts->m_colorA : i == 2 ? pOpts->m_colorB : pOpts->m_colorC); p->setPen(c); // highlight() ); p->drawRect(x + 2, y + yOffset, icon.width(), icon.height()); p->setPen(QPen(c, 0, Qt::DotLine)); p->drawRect(x + 1, y + yOffset - 1, icon.width() + 2, icon.height() + 2); p->setPen(Qt::white); QString s(QChar('A' + i - 1)); p->drawText(x + 2 + (icon.width() - p->fontMetrics().width(s)) / 2, y + yOffset + (icon.height() + p->fontMetrics().ascent()) / 2 - 1, s); } else { p->setPen(m_pDMW->palette().background().color()); p->drawRect(x + 1, y + yOffset - 1, icon.width() + 2, icon.height() + 2); } return; } } QStyleOptionViewItem option2 = option; if(column >= s_UnsolvedCol) { option2.displayAlignment = Qt::AlignRight; } QStyledItemDelegate::paint(p, option2, index); } QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const override { QSize sz = QStyledItemDelegate::sizeHint(option, index); return sz.expandedTo(QSize(0, 18)); } }; DirectoryMergeWindow::DirectoryMergeWindow(QWidget* pParent, Options* pOptions) : 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; 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; } bool DirectoryMergeWindow::DirectoryMergeWindowPrivate::fastFileComparison( FileAccess& fi1, FileAccess& fi2, bool& bError, QString& status) { ProgressProxy pp; bool bEqual = false; status = ""; bError = true; if(!m_bFollowFileLinks) { if(fi1.isSymLink() != fi2.isSymLink()) { status = i18n("Mix of links and normal files."); return bEqual; } else if(fi1.isSymLink() && fi2.isSymLink()) { bError = false; bEqual = fi1.readLink() == fi2.readLink(); status = i18n("Link: "); return bEqual; } } if(fi1.size() != fi2.size()) { bError = false; bEqual = false; status = i18n("Size. "); return bEqual; } else if(m_pOptions->m_bDmTrustSize) { bEqual = true; bError = false; return bEqual; } if(m_pOptions->m_bDmTrustDate) { bEqual = (fi1.lastModified() == fi2.lastModified() && fi1.size() == fi2.size()); bError = false; status = i18n("Date & Size: "); return bEqual; } if(m_pOptions->m_bDmTrustDateFallbackToBinary) { bEqual = (fi1.lastModified() == fi2.lastModified() && fi1.size() == fi2.size()); if(bEqual) { bError = false; status = i18n("Date & Size: "); return bEqual; } } QString fileName1 = fi1.absoluteFilePath(); QString fileName2 = fi2.absoluteFilePath(); if(!fi1.createLocalCopy()) { status = i18n("Creating temp copy of %1 failed.", fileName1); return bEqual; } if(!fi2.createLocalCopy()) { status = i18n("Creating temp copy of %1 failed.", fileName2); return bEqual; } + //This is what the old TempRemover class would do. Should probally get path to temp file not just the name. + if(!fi1.isLocal()) + fileName1 = fi1.getTempName(); + if(!fi2.isLocal()) + fileName2 = fi2.getTempName(); + std::vector buf1(100000); std::vector buf2(buf1.size()); - QFile file1(fi1.fileName()); + QFile file1(fileName1); if(!file1.open(QIODevice::ReadOnly)) { status = i18n("Opening %1 failed. %2", fileName1, file1.errorString()); return bEqual; } - QFile file2(fi2.fileName()); + QFile file2(fileName2); if(!file2.open(QIODevice::ReadOnly)) { status = i18n("Opening %1 failed. %2", fileName2, file2.errorString()); return bEqual; } pp.setInformation(i18n("Comparing file..."), 0, false); typedef qint64 t_FileSize; t_FileSize fullSize = file1.size(); t_FileSize sizeLeft = fullSize; pp.setMaxNofSteps(fullSize / buf1.size()); while(sizeLeft > 0 && !pp.wasCancelled()) { qint64 len = std::min(sizeLeft, (t_FileSize)buf1.size()); if(len != file1.read(&buf1[0], len)) { status = i18n("Error reading from %1", fileName1); return bEqual; } if(len != file2.read(&buf2[0], len)) { status = i18n("Error reading from %1", fileName2); return bEqual; } if(memcmp(&buf1[0], &buf2[0], len) != 0) { bError = false; return bEqual; } sizeLeft -= len; //pp.setCurrent(double(fullSize-sizeLeft)/fullSize, false ); pp.step(); } // If the program really arrives here, then the files are really equal. bError = false; bEqual = true; return bEqual; } int DirectoryMergeWindow::totalColumnWidth() { int w = 0; for(int i = 0; i < s_OpStatusCol; ++i) { w += columnWidth(i); } return w; } void DirectoryMergeWindow::reload() { if(isDirectoryMergeInProgress()) { int result = KMessageBox::warningYesNo(this, i18n("You are currently doing a directory merge. Are you sure, you want to abort the merge and rescan the directory?"), i18n("Warning"), KGuiItem(i18n("Rescan")), KGuiItem(i18n("Continue Merging"))); if(result != KMessageBox::Yes) return; } init(d->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->dirA() || pMFI->dirB() || pMFI->dirC()) { ++nofDirs; } else { ++nofFiles; if(pMFI->m_bEqualAB && (!bThreeDirs || pMFI->m_bEqualAC)) { ++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 = m_dirListA.begin(); dirIterator != m_dirListA.end(); ++dirIterator) { MergeFileInfos& mfi = m_fileMergeMap[FileKey(*dirIterator)]; mfi.setFileInfoA(&(*dirIterator)); mfi.setDirectoryInfo(dirInfo); } } if(dirInfo->dirB().isValid()) { for(dirIterator = m_dirListB.begin(); dirIterator != m_dirListB.end(); ++dirIterator) { MergeFileInfos& mfi = m_fileMergeMap[FileKey(*dirIterator)]; mfi.setFileInfoB(&(*dirIterator)); mfi.setDirectoryInfo(dirInfo); } } if(dirInfo->dirC().isValid()) { for(dirIterator = m_dirListC.begin(); dirIterator != m_dirListC.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) { if(m_pOptions->m_bDmFullAnalysis) { // A full analysis uses the same resources that a normal text-diff/merge uses. // So make sure that the user saves his data first. bool bCanContinue = false; emit q->checkIfCanContinue(&bCanContinue); if(!bCanContinue) return false; emit q->startDiffMerge("", "", "", "", "", "", "", nullptr); // hide main window } q->show(); q->setUpdatesEnabled(true); std::map expandedDirsMap; if(bReload) { // Remember expanded items TODO //QTreeWidgetItemIterator it( this ); //while ( *it ) //{ // DirMergeItem* pDMI = static_cast( *it ); // t_ItemInfo& ii = expandedDirsMap[ pDMI->m_pMFI->subPath() ]; // ii.bExpanded = pDMI->isExpanded(); // ii.bOperationComplete = pDMI->m_pMFI->m_bOperationComplete; // ii.status = pDMI->text( s_OpStatusCol ); // ii.eMergeOperation = pDMI->m_pMFI->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); } 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(q, text, i18n("Directory Open Error")); return false; } if(dirC.isValid() && (dirDest.prettyAbsPath() == dirA.prettyAbsPath() || dirDest.prettyAbsPath() == dirB.prettyAbsPath())) { KMessageBox::error(q, i18n("The destination directory must not be the same as A or B when " "three directories are merged.\nCheck again before continuing."), i18n("Parameter Warning")); return false; } m_bScanning = true; emit q->statusBarMessage(i18n("Scanning directories...")); m_bSyncMode = m_pOptions->m_bDmSyncMode && !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); q->setColumnHidden(s_CCol, !dirC.isValid()); q->setColumnHidden(s_WhiteCol, !m_pOptions->m_bDmFullAnalysis); q->setColumnHidden(s_NonWhiteCol, !m_pOptions->m_bDmFullAnalysis); q->setColumnHidden(s_UnsolvedCol, !m_pOptions->m_bDmFullAnalysis); q->setColumnHidden(s_SolvedCol, !(m_pOptions->m_bDmFullAnalysis && dirC.isValid())); bool bListDirSuccessA = true; bool bListDirSuccessB = true; bool bListDirSuccessC = true; m_dirListA.clear(); m_dirListB.clear(); m_dirListC.clear(); if(dirA.isValid()) { pp.setInformation(i18n("Reading Directory A")); pp.setSubRangeTransformation(currentScan / nofScans, (currentScan + 1) / nofScans); ++currentScan; bListDirSuccessA = dirA.listDir(&m_dirListA, m_pOptions->m_bDmRecursiveDirs, m_pOptions->m_bDmFindHidden, m_pOptions->m_DmFilePattern, m_pOptions->m_DmFileAntiPattern, m_pOptions->m_DmDirAntiPattern, m_pOptions->m_bDmFollowDirLinks, m_pOptions->m_bDmUseCvsIgnore); } if(dirB.isValid()) { pp.setInformation(i18n("Reading Directory B")); pp.setSubRangeTransformation(currentScan / nofScans, (currentScan + 1) / nofScans); ++currentScan; bListDirSuccessB = dirB.listDir(&m_dirListB, m_pOptions->m_bDmRecursiveDirs, m_pOptions->m_bDmFindHidden, m_pOptions->m_DmFilePattern, m_pOptions->m_DmFileAntiPattern, m_pOptions->m_DmDirAntiPattern, m_pOptions->m_bDmFollowDirLinks, m_pOptions->m_bDmUseCvsIgnore); } e_MergeOperation eDefaultMergeOp; if(dirC.isValid()) { pp.setInformation(i18n("Reading Directory C")); pp.setSubRangeTransformation(currentScan / nofScans, (currentScan + 1) / nofScans); ++currentScan; bListDirSuccessC = dirC.listDir(&m_dirListC, m_pOptions->m_bDmRecursiveDirs, m_pOptions->m_bDmFindHidden, m_pOptions->m_DmFilePattern, m_pOptions->m_DmFileAntiPattern, m_pOptions->m_DmDirAntiPattern, m_pOptions->m_bDmFollowDirLinks, m_pOptions->m_bDmUseCvsIgnore); eDefaultMergeOp = eMergeABCToDest; } else eDefaultMergeOp = m_bSyncMode ? eMergeToAB : eMergeABToDest; buildMergeMap(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(q, s); } if(bContinue) { prepareListView(pp); q->updateFileVisibilities(); for(int childIdx = 0; childIdx < rowCount(); ++childIdx) { QModelIndex mi = index(childIdx, 0, QModelIndex()); calcSuggestedOperation(mi, eDefaultMergeOp); } } q->sortByColumn(0, Qt::AscendingOrder); for(int column = 0; column < columnCount(QModelIndex()); ++column) q->resizeColumnToContents(column); // Try to improve the view a little bit. QWidget* pParent = q->parentWidget(); QSplitter* pSplitter = static_cast(pParent); if(pSplitter != nullptr) { QList sizes = pSplitter->sizes(); int total = sizes[0] + sizes[1]; if(total < 10) total = 100; sizes[0] = total * 6 / 10; sizes[1] = total - sizes[0]; pSplitter->setSizes(sizes); } m_bScanning = false; emit q->statusBarMessage(i18n("Ready.")); if(bContinue && !m_bSkipDirStatus) { // Generate a status report int nofFiles = 0; int nofDirs = 0; int nofEqualFiles = 0; int nofManualMerges = 0; //TODO for(int childIdx = 0; childIdx < rowCount(); ++childIdx) calcDirStatus(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(q, s); // //TODO //if ( topLevelItemCount()>0 ) //{ // topLevelItem(0)->setSelected(true); // setCurrentItem( topLevelItem(0) ); //} } if(bReload) { // Remember expanded items //TODO //QTreeWidgetItemIterator it( this ); //while ( *it ) //{ // DirMergeItem* pDMI = static_cast( *it ); // std::map::iterator i = expandedDirsMap.find( pDMI->m_pMFI->subPath() ); // if ( i!=expandedDirsMap.end() ) // { // t_ItemInfo& ii = i->second; // pDMI->setExpanded( ii.bExpanded ); // //pDMI->m_pMFI->setMergeOperation( ii.eMergeOperation, false ); unsafe, might have changed // pDMI->m_pMFI->m_bOperationComplete = ii.bOperationComplete; // pDMI->setText( s_OpStatusCol, ii.status ); // } // ++it; //} } else if(m_bUnfoldSubdirs) { m_pDirUnfoldAll->trigger(); } return true; } 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(q, i18n("This affects all merge operations."), i18n("Changing All Merge Operations"), KStandardGuiItem::cont(), KStandardGuiItem::cancel())) { for(int i = 0; i < rowCount(); ++i) { calcSuggestedOperation(index(i, 0, QModelIndex()), eDefaultOperation); } } } bool DirectoryMergeWindow::DirectoryMergeWindowPrivate::compareFilesAndCalcAges(MergeFileInfos& mfi, QStringList& errors) { std::map dateMap; if(mfi.existsInA()) { dateMap[mfi.getFileInfoA()->lastModified()] = 0; } if(mfi.existsInB()) { dateMap[mfi.getFileInfoB()->lastModified()] = 1; } if(mfi.existsInC()) { dateMap[mfi.getFileInfoC()->lastModified()] = 2; } if(m_pOptions->m_bDmFullAnalysis) { if((mfi.existsInA() && mfi.dirA()) || (mfi.existsInB() && mfi.dirB()) || (mfi.existsInC() && mfi.dirC())) { // If any input is a directory, don't start any comparison. mfi.m_bEqualAB = mfi.existsInA() && mfi.existsInB(); mfi.m_bEqualAC = mfi.existsInA() && mfi.existsInC(); mfi.m_bEqualBC = mfi.existsInB() && mfi.existsInC(); } else { emit q->startDiffMerge( mfi.existsInA() ? mfi.getFileInfoA()->absoluteFilePath() : QString(""), mfi.existsInB() ? mfi.getFileInfoB()->absoluteFilePath() : QString(""), mfi.existsInC() ? mfi.getFileInfoC()->absoluteFilePath() : QString(""), "", "", "", "", mfi.diffStatus()); int nofNonwhiteConflicts = mfi.diffStatus()->getUnsolvedConflicts() + mfi.diffStatus()->getSolvedConflicts() - mfi.diffStatus()->getWhitespaceConflicts(); if(m_pOptions->m_bDmWhiteSpaceEqual && nofNonwhiteConflicts == 0) { mfi.m_bEqualAB = mfi.existsInA() && mfi.existsInB(); mfi.m_bEqualAC = mfi.existsInA() && mfi.existsInC(); mfi.m_bEqualBC = mfi.existsInB() && mfi.existsInC(); } else { mfi.m_bEqualAB = mfi.diffStatus()->isBinaryEqualAB(); mfi.m_bEqualBC = mfi.diffStatus()->isBinaryEqualBC(); mfi.m_bEqualAC = mfi.diffStatus()->isBinaryEqualAC(); } } } else { bool bError = false; QString eqStatus; if(mfi.existsInA() && mfi.existsInB()) { if(mfi.dirA()) mfi.m_bEqualAB = true; else mfi.m_bEqualAB = fastFileComparison(*mfi.getFileInfoA(), *mfi.getFileInfoB(), bError, eqStatus); } if(mfi.existsInA() && mfi.existsInC()) { if(mfi.dirA()) mfi.m_bEqualAC = true; else mfi.m_bEqualAC = fastFileComparison(*mfi.getFileInfoA(), *mfi.getFileInfoC(), bError, eqStatus); } if(mfi.existsInB() && mfi.existsInC()) { if(mfi.m_bEqualAB && mfi.m_bEqualAC) mfi.m_bEqualBC = true; else { if(mfi.dirB()) mfi.m_bEqualBC = true; else mfi.m_bEqualBC = fastFileComparison(*mfi.getFileInfoB(), *mfi.getFileInfoC(), bError, eqStatus); } } if(bError) { //Limit size of error list in memmory. if(errors.size() < 30) errors.append(eqStatus); return false; } } if(mfi.isLinkA() != mfi.isLinkB()) mfi.m_bEqualAB = false; if(mfi.isLinkA() != mfi.isLinkC()) mfi.m_bEqualAC = false; if(mfi.isLinkB() != mfi.isLinkC()) mfi.m_bEqualBC = false; if(mfi.dirA() != mfi.dirB()) mfi.m_bEqualAB = false; if(mfi.dirA() != mfi.dirC()) mfi.m_bEqualAC = false; if(mfi.dirB() != mfi.dirC()) mfi.m_bEqualBC = false; Q_ASSERT(eNew == 0 && eMiddle == 1 && eOld == 2); // The map automatically sorts the keys. int age = eNew; std::map::reverse_iterator i; for(i = dateMap.rbegin(); i != dateMap.rend(); ++i) { int n = i->second; if(n == 0 && mfi.m_ageA == eNotThere) { mfi.m_ageA = (e_Age)age; ++age; if(mfi.m_bEqualAB) { mfi.m_ageB = mfi.m_ageA; ++age; } if(mfi.m_bEqualAC) { mfi.m_ageC = mfi.m_ageA; ++age; } } else if(n == 1 && mfi.m_ageB == eNotThere) { mfi.m_ageB = (e_Age)age; ++age; if(mfi.m_bEqualAB) { mfi.m_ageA = mfi.m_ageB; ++age; } if(mfi.m_bEqualBC) { mfi.m_ageC = mfi.m_ageB; ++age; } } else if(n == 2 && mfi.m_ageC == eNotThere) { mfi.m_ageC = (e_Age)age; ++age; if(mfi.m_bEqualAC) { mfi.m_ageA = mfi.m_ageC; ++age; } if(mfi.m_bEqualBC) { mfi.m_ageB = mfi.m_ageC; ++age; } } } // The checks below are necessary when the dates of the file are equal but the // files are not. One wouldn't expect this to happen, yet it happens sometimes. if(mfi.existsInC() && mfi.m_ageC == eNotThere) { mfi.m_ageC = (e_Age)age; ++age; mfi.m_bConflictingAges = true; } if(mfi.existsInB() && mfi.m_ageB == eNotThere) { mfi.m_ageB = (e_Age)age; ++age; mfi.m_bConflictingAges = true; } if(mfi.existsInA() && mfi.m_ageA == eNotThere) { mfi.m_ageA = (e_Age)age; ++age; mfi.m_bConflictingAges = true; } if(mfi.m_ageA != eOld && mfi.m_ageB != eOld && mfi.m_ageC != eOld) { if(mfi.m_ageA == eMiddle) mfi.m_ageA = eOld; if(mfi.m_ageB == eMiddle) mfi.m_ageB = eOld; if(mfi.m_ageC == eMiddle) mfi.m_ageC = eOld; } return true; } void DirectoryMergeWindow::DirectoryMergeWindowPrivate::setPixmaps(MergeFileInfos& mfi, bool) { if(mfi.dirA() || mfi.dirB() || mfi.dirC()) { mfi.m_ageA = eNotThere; mfi.m_ageB = eNotThere; mfi.m_ageC = eNotThere; int age = eNew; if(mfi.existsInC()) { mfi.m_ageC = (e_Age)age; if(mfi.m_bEqualAC) mfi.m_ageA = (e_Age)age; if(mfi.m_bEqualBC) mfi.m_ageB = (e_Age)age; ++age; } if(mfi.existsInB() && mfi.m_ageB == eNotThere) { mfi.m_ageB = (e_Age)age; if(mfi.m_bEqualAB) mfi.m_ageA = (e_Age)age; ++age; } if(mfi.existsInA() && mfi.m_ageA == eNotThere) { mfi.m_ageA = (e_Age)age; } if(mfi.m_ageA != eOld && mfi.m_ageB != eOld && mfi.m_ageC != eOld) { if(mfi.m_ageA == eMiddle) mfi.m_ageA = eOld; if(mfi.m_ageB == eMiddle) mfi.m_ageB = eOld; if(mfi.m_ageC == eMiddle) mfi.m_ageC = eOld; } } } 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() && q->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); q->setRootIsDecorated(true); bool bCheckC = isThreeWay(); t_fileMergeMap::iterator j; int nrOfFiles = m_fileMergeMap.size(); int currentIdx = 1; QTime t; t.start(); pp.setMaxNofSteps(nrOfFiles); for(j = m_fileMergeMap.begin(); j != m_fileMergeMap.end(); ++j) { MergeFileInfos& mfi = j.value(); // const QString& fileName = j->first; const QString& fileName = mfi.subPath(); pp.setInformation( i18n("Processing %1 / %2\n%3", currentIdx, nrOfFiles, fileName), currentIdx, false); if(pp.wasCancelled()) break; ++currentIdx; // The comparisons and calculations for each file take place here. compareFilesAndCalcAges(mfi, errors); // 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() } setPixmaps(mfi, bCheckC); } if(errors.size() > 0) { if(errors.size() < 15) { KMessageBox::errorList(q, i18n("Some files could not be processed."), errors); } else { KMessageBox::error(q, 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->m_bEqualAB) { setMergeOperation(mi, bOtherDest ? eCopyBToDest : eNoOperation); } else if(pMFI->existsInA() && pMFI->existsInB()) { if(!bCopyNewer || pMFI->dirA()) setMergeOperation(mi, eDefaultMergeOp); else if(bCopyNewer && pMFI->m_bConflictingAges) { setMergeOperation(mi, eConflictingAges); } else { if(pMFI->m_ageA == 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->m_bEqualAB && pMFI->m_bEqualAC) { setMergeOperation(mi, bOtherDest ? eCopyCToDest : eNoOperation); } else if(pMFI->existsInA() && pMFI->existsInB() && pMFI->existsInC()) { if(pMFI->m_bEqualAB) setMergeOperation(mi, eCopyCToDest); else if(pMFI->m_bEqualAC) setMergeOperation(mi, eCopyBToDest); else if(pMFI->m_bEqualBC) setMergeOperation(mi, eCopyCToDest); else setMergeOperation(mi, eMergeABCToDest); } else if(pMFI->existsInA() && pMFI->existsInB() && !pMFI->existsInC()) { if(pMFI->m_bEqualAB) setMergeOperation(mi, eDeleteFromDest); else setMergeOperation(mi, eChangedAndDeleted); } else if(pMFI->existsInA() && !pMFI->existsInB() && pMFI->existsInC()) { if(pMFI->m_bEqualAC) setMergeOperation(mi, eDeleteFromDest); else setMergeOperation(mi, eChangedAndDeleted); } else if(!pMFI->existsInA() && pMFI->existsInB() && pMFI->existsInC()) { if(pMFI->m_bEqualBC) 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->dirA() : mi.column() == s_BCol ? pMFI->dirB() : pMFI->dirC(); } 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 q->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->dirC() ) // { // } // 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->m_bOperationComplete = false; 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->dirA() || pMFI->dirB() || pMFI->dirC())) { 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->dirA() || pMFI->dirB() || pMFI->dirC() || 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->m_bOperationComplete = true; if(d->m_mergeItemList.size() == 1) { d->m_mergeItemList.clear(); d->m_bRealMergeStarted = false; } } emit updateAvailabilities(); } bool DirectoryMergeWindow::DirectoryMergeWindowPrivate::canContinue() { bool bCanContinue = false; emit q->checkIfCanContinue(&bCanContinue); if(bCanContinue && !m_bError) { QModelIndex mi = (m_mergeItemList.empty() || m_currentIndexForOperation == m_mergeItemList.end()) ? QModelIndex() : *m_currentIndexForOperation; MergeFileInfos* pMFI = getMFI(mi); if(pMFI && !pMFI->m_bOperationComplete) { setOpStatus(mi, eOpStatusNotSaved); pMFI->m_bOperationComplete = true; if(m_mergeItemList.size() == 1) { m_mergeItemList.clear(); m_bRealMergeStarted = false; } } } return bCanContinue; } bool DirectoryMergeWindow::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(q, 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(q, i18n("Unknown merge operation.")); } return bSuccess; } // Check if the merge can start, and prepare the m_mergeItemList which then contains all // items that must be merged. void DirectoryMergeWindow::DirectoryMergeWindowPrivate::prepareMergeStart(const QModelIndex& miBegin, const QModelIndex& miEnd, bool bVerbose) { if(bVerbose) { int status = KMessageBox::warningYesNoCancel(q, i18n("The merge is about to begin.\n\n" "Choose \"Do it\" if you have read the instructions and know what you are doing.\n" "Choosing \"Simulate it\" will tell you what would happen.\n\n" "Be aware that this program still has beta status " "and there is NO WARRANTY whatsoever! Make backups of your vital data!"), i18n("Starting Merge"), KGuiItem(i18n("Do It")), KGuiItem(i18n("Simulate It"))); if(status == KMessageBox::Yes) m_bRealMergeStarted = true; else if(status == KMessageBox::No) m_bSimulatedMergeStarted = true; else return; } else { m_bRealMergeStarted = true; } m_mergeItemList.clear(); if(!miBegin.isValid()) return; for(QModelIndex mi = miBegin; mi != miEnd; mi = treeIterator(mi)) { MergeFileInfos* pMFI = getMFI(mi); if(pMFI && !pMFI->m_bOperationComplete) { m_mergeItemList.push_back(mi); QString errorText; if(pMFI->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()) { q->scrollTo(mi, QAbstractItemView::EnsureVisible); q->setCurrentIndex(mi); KMessageBox::error(q, errorText); m_mergeItemList.clear(); m_bRealMergeStarted = false; return; } } } m_currentIndexForOperation = m_mergeItemList.begin(); return; } void DirectoryMergeWindow::slotRunOperationForCurrentItem() { if(!d->canContinue()) return; bool bVerbose = false; if(d->m_mergeItemList.empty()) { QModelIndex miBegin = currentIndex(); QModelIndex miEnd = d->treeIterator(miBegin, false, false); // find next visible sibling (no children) d->prepareMergeStart(miBegin, miEnd, bVerbose); d->mergeContinue(true, bVerbose); } else d->mergeContinue(false, bVerbose); } void DirectoryMergeWindow::slotRunOperationForAllItems() { if(!d->canContinue()) return; bool bVerbose = true; if(d->m_mergeItemList.empty()) { QModelIndex miBegin = d->rowCount() > 0 ? d->index(0, 0, QModelIndex()) : QModelIndex(); d->prepareMergeStart(miBegin, QModelIndex(), bVerbose); d->mergeContinue(true, bVerbose); } else d->mergeContinue(false, bVerbose); } void DirectoryMergeWindow::mergeCurrentFile() { if(!d->canContinue()) return; if(d->m_bRealMergeStarted) { KMessageBox::sorry(this, i18n("This operation is currently not possible because directory merge is currently running."), i18n("Operation Not Possible")); return; } if(isFileSelected()) { MergeFileInfos* pMFI = d->getMFI(currentIndex()); if(pMFI != nullptr) { 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->m_bOperationComplete) ++nrOfCompletedItems; if(pMFI->m_bSimOpComplete) ++nrOfCompletedSimItems; } m_pStatusInfo->hide(); m_pStatusInfo->clear(); QModelIndex miCurrent = m_currentIndexForOperation == m_mergeItemList.end() ? QModelIndex() : *m_currentIndexForOperation; bool bContinueWithCurrentItem = bStart; // true for first item, else false bool bSkipItem = false; if(!bStart && m_bError && miCurrent.isValid()) { int status = KMessageBox::warningYesNoCancel(q, i18n("There was an error in the last step.\n" "Do you want to continue with the item that caused the error or do you want to skip this item?"), i18n("Continue merge after an error"), KGuiItem(i18n("Continue With Last Item")), KGuiItem(i18n("Skip Item"))); if(status == KMessageBox::Yes) bContinueWithCurrentItem = true; else if(status == KMessageBox::No) bSkipItem = true; else return; m_bError = false; } pp.setMaxNofSteps(nrOfItems); bool bSuccess = true; bool bSingleFileMerge = false; bool bSim = m_bSimulatedMergeStarted; while(bSuccess) { MergeFileInfos* pMFI = getMFI(miCurrent); if(pMFI == nullptr) { m_mergeItemList.clear(); m_bRealMergeStarted = false; break; } if(pMFI != nullptr && !bContinueWithCurrentItem) { if(bSim) { if(rowCount(miCurrent) == 0) { pMFI->m_bSimOpComplete = true; } } else { if(rowCount(miCurrent) == 0) { if(!pMFI->m_bOperationComplete) { setOpStatus(miCurrent, bSkipItem ? eOpStatusSkipped : eOpStatusDone); pMFI->m_bOperationComplete = true; bSkipItem = false; } } else { setOpStatus(miCurrent, eOpStatusInProgress); } } } if(!bContinueWithCurrentItem) { // Depth first QModelIndex miPrev = miCurrent; ++m_currentIndexForOperation; miCurrent = m_currentIndexForOperation == m_mergeItemList.end() ? QModelIndex() : *m_currentIndexForOperation; if((!miCurrent.isValid() || miCurrent.parent() != miPrev.parent()) && miPrev.parent().isValid()) { // Check if the parent may be set to "Done" QModelIndex miParent = miPrev.parent(); bool bDone = true; while(bDone && miParent.isValid()) { for(int childIdx = 0; childIdx < rowCount(miParent); ++childIdx) { pMFI = getMFI(index(childIdx, 0, miParent)); if((!bSim && !pMFI->m_bOperationComplete) || (bSim && pMFI->m_bSimOpComplete)) { bDone = false; break; } } if(bDone) { pMFI = getMFI(miParent); if(bSim) pMFI->m_bSimOpComplete = bDone; else { setOpStatus(miParent, eOpStatusDone); pMFI->m_bOperationComplete = bDone; } } miParent = miParent.parent(); } } } if(!miCurrent.isValid()) // end? { if(m_bRealMergeStarted) { if(bVerbose) { KMessageBox::information(q, i18n("Merge operation complete."), i18n("Merge Complete")); } m_bRealMergeStarted = false; m_pStatusInfo->setWindowTitle(i18n("Merge Complete")); } if(m_bSimulatedMergeStarted) { m_bSimulatedMergeStarted = false; QModelIndex mi = rowCount() > 0 ? index(0, 0, QModelIndex()) : QModelIndex(); for(; mi.isValid(); mi = treeIterator(mi)) { getMFI(mi)->m_bSimOpComplete = false; } m_pStatusInfo->setWindowTitle(i18n("Simulated merge complete: Check if you agree with the proposed operations.")); m_pStatusInfo->exec(); } m_mergeItemList.clear(); m_bRealMergeStarted = false; return; } 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(); q->setCurrentIndex(miCurrent); q->scrollTo(miCurrent, EnsureVisible); if(!bSuccess && !bSingleFileMerge) { KMessageBox::error(q, i18n("An error occurred. Press OK to see detailed information.")); m_pStatusInfo->setWindowTitle(i18n("Merge Error")); m_pStatusInfo->exec(); //if ( m_pStatusInfo->firstChild()!=0 ) // m_pStatusInfo->ensureItemVisible( m_pStatusInfo->last() ); m_bError = true; setOpStatus(miCurrent, eOpStatusError); } else { m_bError = false; } emit q->updateAvailabilities(); if(m_currentIndexForOperation == m_mergeItemList.end()) { m_mergeItemList.clear(); m_bRealMergeStarted = false; } } bool DirectoryMergeWindow::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; if(fi2.fileName() == "." || fi2.fileName() == "..") continue; bSuccess = deleteFLD(fi2.absoluteFilePath(), false); if(!bSuccess) break; } if(bSuccess) { bSuccess = FileAccess::removeDir(name); if(!bSuccess) { m_pStatusInfo->addText(i18n("Error: rmdir( %1 ) operation failed.", name)); // krazy:exclude=syscalls return false; } } } else { bool bSuccess = FileAccess::removeFile(name); 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); q->scrollTo(*m_currentIndexForOperation, EnsureVisible); emit q->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; if(FileAccess(destName, true).exists()) { bool bSuccess = deleteFLD(destName, false /*no backup*/); if(!bSuccess) { m_pStatusInfo->addText(i18n("Error during rename( %1 -> %2 ): " "Cannot delete existing destination.", srcName, destName)); return false; } } m_pStatusInfo->addText(i18n("rename( %1 -> %2 )", srcName, destName)); if(m_bSimulatedMergeStarted) { return true; } bool bSuccess = FileAccess(srcName).rename(destName); if(!bSuccess) { m_pStatusInfo->addText(i18n("Error: Rename failed.")); return false; } return true; } bool DirectoryMergeWindow::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() ? QString("A: ") : i18n("A (Base): ")); m_pInfoA->setText(dirA.prettyAbsPath()); if(dirB.absoluteFilePath() == dirDest.absoluteFilePath()) { m_pB->setText(i18n("B (Dest): ")); bHideDest = true; } else m_pB->setText("B: "); m_pInfoB->setText(dirB.prettyAbsPath()); if(dirC.absoluteFilePath() == dirDest.absoluteFilePath()) { m_pC->setText(i18n("C (Dest): ")); bHideDest = true; } else m_pC->setText("C: "); m_pInfoC->setText(dirC.prettyAbsPath()); m_pDest->setText(i18n("Dest: ")); m_pInfoDest->setText(dirDest.prettyAbsPath()); if(!dirC.isValid()) { m_pC->hide(); m_pInfoC->hide(); } else { m_pC->show(); m_pInfoC->show(); } if(!dirDest.isValid() || bHideDest) { m_pDest->hide(); m_pInfoDest->hide(); } else { m_pDest->show(); m_pInfoDest->show(); } m_pInfoList->clear(); addListViewItem(i18n("A"), dirA.prettyAbsPath(), mfi.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); } QTextStream& operator<<(QTextStream& ts, MergeFileInfos& mfi) { ts << "{\n"; ValueMap vm; vm.writeEntry("SubPath", mfi.subPath()); vm.writeEntry("ExistsInA", mfi.existsInA()); vm.writeEntry("ExistsInB", mfi.existsInB()); vm.writeEntry("ExistsInC", mfi.existsInC()); vm.writeEntry("EqualAB", mfi.m_bEqualAB); vm.writeEntry("EqualAC", mfi.m_bEqualAC); vm.writeEntry("EqualBC", mfi.m_bEqualBC); vm.writeEntry("MergeOperation", (int)mfi.getOperation()); vm.writeEntry("DirA", mfi.dirA()); vm.writeEntry("DirB", mfi.dirB()); vm.writeEntry("DirC", mfi.dirC()); vm.writeEntry("LinkA", mfi.isLinkA()); vm.writeEntry("LinkB", mfi.isLinkB()); vm.writeEntry("LinkC", mfi.isLinkC()); vm.writeEntry("OperationComplete", mfi.m_bOperationComplete); vm.writeEntry("AgeA", (int)mfi.m_ageA); vm.writeEntry("AgeB", (int)mfi.m_ageB); vm.writeEntry("AgeC", (int)mfi.m_ageC); vm.writeEntry("ConflictingAges", mfi.m_bConflictingAges); // Equal age but files are not! vm.save(ts); ts << "}\n"; return ts; } void DirectoryMergeWindow::slotSaveMergeState() { //slotStatusMsg(i18n("Saving Directory Merge State ...")); QString s = QFileDialog::getSaveFileName(this, i18n("Save Directory Merge State As..."), QDir::currentPath()); if(!s.isEmpty()) { d->m_dirMergeStateFilename = s; QFile file(d->m_dirMergeStateFilename); bool bSuccess = file.open(QIODevice::WriteOnly); if(bSuccess) { QTextStream ts(&file); QModelIndex mi(d->index(0, 0, QModelIndex())); while(mi.isValid()) { MergeFileInfos* pMFI = d->getMFI(mi); ts << *pMFI; mi = d->treeIterator(mi, true, true); } } } //slotStatusMsg(i18n("Ready.")); } void DirectoryMergeWindow::slotLoadMergeState() { } void DirectoryMergeWindow::updateFileVisibilities() { bool bShowIdentical = d->m_pDirShowIdenticalFiles->isChecked(); bool bShowDifferent = d->m_pDirShowDifferentFiles->isChecked(); bool bShowOnlyInA = d->m_pDirShowFilesOnlyInA->isChecked(); bool bShowOnlyInB = d->m_pDirShowFilesOnlyInB->isChecked(); bool bShowOnlyInC = d->m_pDirShowFilesOnlyInC->isChecked(); bool bThreeDirs = d->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->dirA() || pMFI->dirB() || pMFI->dirC(); if(loop == 0 && bDir) { bool bChange = false; if(!pMFI->m_bEqualAB && pMFI->dirA() == pMFI->dirB() && pMFI->isLinkA() == pMFI->isLinkB()) { pMFI->m_bEqualAB = true; bChange = true; } if(!pMFI->m_bEqualBC && pMFI->dirC() == pMFI->dirB() && pMFI->isLinkC() == pMFI->isLinkB()) { pMFI->m_bEqualBC = true; bChange = true; } if(!pMFI->m_bEqualAC && pMFI->dirA() == pMFI->dirC() && pMFI->isLinkA() == pMFI->isLinkC()) { pMFI->m_bEqualAC = true; bChange = true; } if(bChange) DirectoryMergeWindow::DirectoryMergeWindowPrivate::setPixmaps(*pMFI, bThreeDirs); } bool bExistsEverywhere = pMFI->existsInA() && pMFI->existsInB() && (pMFI->existsInC() || !bThreeDirs); int existCount = int(pMFI->existsInA()) + int(pMFI->existsInB()) + int(pMFI->existsInC()); bool bVisible = (bShowIdentical && bExistsEverywhere && pMFI->m_bEqualAB && (pMFI->m_bEqualAC || !bThreeDirs)) || ((bShowDifferent || bDir) && existCount >= 2 && (!pMFI->m_bEqualAB || !(pMFI->m_bEqualAC || !bThreeDirs))) || (bShowOnlyInA && pMFI->existsInA() && !pMFI->existsInB() && !pMFI->existsInC()) || (bShowOnlyInB && !pMFI->existsInA() && pMFI->existsInB() && !pMFI->existsInC()) || (bShowOnlyInC && !pMFI->existsInA() && !pMFI->existsInB() && pMFI->existsInC()); QString fileName = pMFI->fileName(); bVisible = bVisible && ((bDir && !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->m_bEqualAB && pMFI->m_bEqualAC : pMFI->m_bEqualAB; if(!bEqual && bVisible && loop == 0) // Set all parents to "not equal" { MergeFileInfos* p2 = pMFI->parent(); while(p2 != nullptr) { bool bChange = false; if(!pMFI->m_bEqualAB && p2->m_bEqualAB) { p2->m_bEqualAB = false; bChange = true; } if(!pMFI->m_bEqualAC && p2->m_bEqualAC) { p2->m_bEqualAC = false; bChange = true; } if(!pMFI->m_bEqualBC && p2->m_bEqualBC) { p2->m_bEqualBC = false; bChange = true; } if(bChange) DirectoryMergeWindow::DirectoryMergeWindowPrivate::setPixmaps(*p2, bThreeDirs); 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); d->m_pDirAutoChoiceEverywhere->setEnabled(bDirCompare && isVisible()); d->m_pDirDoNothingEverywhere->setEnabled(bDirCompare && isVisible()); d->m_pDirChooseAEverywhere->setEnabled(bDirCompare && isVisible()); d->m_pDirChooseBEverywhere->setEnabled(bDirCompare && isVisible()); d->m_pDirChooseCEverywhere->setEnabled(bDirCompare && isVisible()); bool bThreeDirs = d->isThreeWay(); 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/optiondialog.cpp b/src/optiondialog.cpp index 8f7d655..231fefe 100644 --- a/src/optiondialog.cpp +++ b/src/optiondialog.cpp @@ -1,1710 +1,1711 @@ /* * kdiff3 - Text Diff And Merge Tool * Copyright (C) 2002-2009 Joachim Eibl, joachim.eibl at gmx.de + * 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. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "optiondialog.h" #include "OptionItems.h" #include "ConfigValueMap.h" #include "diff.h" #include "smalldialogs.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define KDIFF3_CONFIG_GROUP "KDiff3 Options" QString s_historyEntryStartRegExpToolTip; QString s_historyEntryStartSortKeyOrderToolTip; QString s_autoMergeRegExpToolTip; QString s_historyStartRegExpToolTip; class OptionCheckBox : public QCheckBox, public OptionBool { public: OptionCheckBox(const QString& text, bool bDefaultVal, const QString& saveName, bool* pbVar, QWidget* pParent) : QCheckBox(text, pParent), OptionBool(pbVar, bDefaultVal, saveName) {} void setToDefault() override { setChecked(getDefault()); } void setToCurrent() override { setChecked(getCurrent()); } void apply() override { OptionBool::apply(isChecked()); } private: Q_DISABLE_COPY(OptionCheckBox) }; class OptionRadioButton : public QRadioButton, public OptionBool { public: OptionRadioButton(const QString& text, bool bDefaultVal, const QString& saveName, bool* pbVar, QWidget* pParent) : QRadioButton(text, pParent), OptionBool(pbVar, bDefaultVal, saveName) {} void setToDefault() override { setChecked(getDefault()); } void setToCurrent() override { setChecked(getCurrent()); } void apply() override { OptionBool::apply(isChecked()); } private: Q_DISABLE_COPY(OptionRadioButton) }; FontChooser::FontChooser(QWidget* pParent) : QGroupBox(pParent) { QVBoxLayout* pLayout = new QVBoxLayout(this); m_pLabel = new QLabel(QString()); pLayout->addWidget(m_pLabel); QChar visualTab(0x2192); QChar visualSpace((ushort)0xb7); m_pExampleTextEdit = new QPlainTextEdit(QString("The quick brown fox jumps over the river\n" "but the little red hen escapes with a shiver.\n" ":-)") + visualTab + visualSpace, this); m_pExampleTextEdit->setFont(m_font); m_pExampleTextEdit->setReadOnly(true); pLayout->addWidget(m_pExampleTextEdit); m_pSelectFont = new QPushButton(i18n("Change Font")); m_pSelectFont->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); connect(m_pSelectFont, &QPushButton::clicked, this, &FontChooser::slotSelectFont); pLayout->addWidget(m_pSelectFont); pLayout->setAlignment(m_pSelectFont, Qt::AlignRight); } QFont FontChooser::font() { return m_font; //QFont("courier",10); } void FontChooser::setFont(const QFont& font, bool) { m_font = font; m_pExampleTextEdit->setFont(m_font); m_pLabel->setText(i18n("Font: %1, %2, %3\n\nExample:", m_font.family(), m_font.styleName(), m_font.pointSize())); //update(); } void FontChooser::slotSelectFont() { bool bOk; m_font = QFontDialog::getFont(&bOk, m_font); m_pExampleTextEdit->setFont(m_font); m_pLabel->setText(i18n("Font: %1, %2, %3\n\nExample:", m_font.family(), m_font.styleName(), m_font.pointSize())); } class OptionFontChooser : public FontChooser, public OptionFont { public: OptionFontChooser(const QFont& defaultVal, const QString& saveName, QFont* pVar, QWidget* pParent) : FontChooser(pParent), OptionFont(pVar, defaultVal, saveName) {} void setToDefault() override { setFont(getDefault(), false); } void setToCurrent() override { setFont(getCurrent(), false); } void apply() override { OptionFont::apply(font()); } private: Q_DISABLE_COPY(OptionFontChooser) }; class OptionColorButton : public KColorButton, public OptionColor { public: OptionColorButton(const QColor &defaultVal, const QString& saveName, QColor* pVar, QWidget* pParent) : KColorButton(pParent), OptionColor(pVar, defaultVal, saveName) {} void setToDefault() override { setColor(getDefault()); } void setToCurrent() override { setColor(getCurrent()); } void apply() override { OptionColor::apply(color()); } private: Q_DISABLE_COPY(OptionColorButton) }; class OptionLineEdit : public QComboBox, public OptionString { public: OptionLineEdit(const QString& defaultVal, const QString& saveName, QString* pVar, QWidget* pParent) : QComboBox(pParent), OptionString(pVar, defaultVal, saveName) { setMinimumWidth(50); setEditable(true); m_list.push_back(defaultVal); insertText(); } void setToDefault() override { setEditText(getDefault()); } void setToCurrent() override { setEditText(getCurrent()); } void apply() override { OptionString::apply(currentText()); insertText(); } void write(ValueMap* config) override { config->writeEntry(m_saveName, m_list); } void read(ValueMap* config) override { m_list = config->readEntry(m_saveName, QStringList(m_defaultVal)); if(!m_list.empty()) setCurrent(m_list.front()); clear(); insertItems(0, m_list); } private: void insertText() { // Check if the text exists. If yes remove it and push it in as first element QString current = currentText(); m_list.removeAll(current); m_list.push_front(current); clear(); if(m_list.size() > 10) m_list.erase(m_list.begin() + 10, m_list.end()); insertItems(0, m_list); } Q_DISABLE_COPY(OptionLineEdit) QStringList m_list; }; class OptionIntEdit : public QLineEdit, public OptionNum { public: OptionIntEdit(int defaultVal, const QString& saveName, int* pVar, int rangeMin, int rangeMax, QWidget* pParent) : QLineEdit(pParent), OptionNum(pVar, defaultVal, saveName) { QIntValidator* v = new QIntValidator(this); v->setRange(rangeMin, rangeMax); setValidator(v); } void setToDefault() override { //QString::setNum does not account for locale settings setText(OptionNum::toString(getDefault())); } void setToCurrent() override { setText(getString()); } void apply() override { const QIntValidator* v = static_cast(validator()); setCurrent( minMaxLimiter(text().toInt(), v->bottom(), v->top()) ); setText(getString()); } private: Q_DISABLE_COPY(OptionIntEdit) }; class OptionComboBox : public QComboBox, public OptionItemBase { public: OptionComboBox(int defaultVal, const QString& saveName, int* pVarNum, QWidget* pParent) : QComboBox(pParent), OptionItemBase(saveName) { setMinimumWidth(50); m_pVarNum = pVarNum; m_pVarStr = nullptr; m_defaultVal = defaultVal; setEditable(false); } OptionComboBox(int defaultVal, const QString& saveName, QString* pVarStr, QWidget* pParent) : QComboBox(pParent), OptionItemBase(saveName) { m_pVarNum = nullptr; m_pVarStr = pVarStr; m_defaultVal = defaultVal; setEditable(false); } void setToDefault() override { setCurrentIndex(m_defaultVal); if(m_pVarStr != nullptr) { *m_pVarStr = currentText(); } } void setToCurrent() override { if(m_pVarNum != nullptr) setCurrentIndex(*m_pVarNum); else setText(*m_pVarStr); } void apply() override { if(m_pVarNum != nullptr) { *m_pVarNum = currentIndex(); } else { *m_pVarStr = currentText(); } } void write(ValueMap* config) override { if(m_pVarStr != nullptr) config->writeEntry(m_saveName, *m_pVarStr); else config->writeEntry(m_saveName, *m_pVarNum); } void read(ValueMap* config) override { if(m_pVarStr != nullptr) setText(config->readEntry(m_saveName, currentText())); else *m_pVarNum = config->readEntry(m_saveName, *m_pVarNum); } void preserve() override { if(m_pVarStr != nullptr) { m_preservedStrVal = *m_pVarStr; } else { m_preservedNumVal = *m_pVarNum; } } void unpreserve() override { if(m_pVarStr != nullptr) { *m_pVarStr = m_preservedStrVal; } else { *m_pVarNum = m_preservedNumVal; } } private: Q_DISABLE_COPY(OptionComboBox) int* m_pVarNum; int m_preservedNumVal = 0; QString* m_pVarStr; QString m_preservedStrVal; int m_defaultVal; void setText(const QString& s) { // Find the string in the combobox-list, don't change the value if nothing fits. for(int i = 0; i < count(); ++i) { if(itemText(i) == s) { if(m_pVarNum != nullptr) *m_pVarNum = i; if(m_pVarStr != nullptr) *m_pVarStr = s; setCurrentIndex(i); return; } } } }; class OptionEncodingComboBox : public QComboBox, public OptionCodec { Q_OBJECT QVector m_codecVec; QTextCodec** m_ppVarCodec; public: OptionEncodingComboBox(const QString& saveName, QTextCodec** ppVarCodec, QWidget* pParent) : QComboBox(pParent), OptionCodec(saveName) { m_ppVarCodec = ppVarCodec; insertCodec(i18n("Unicode, 8 bit"), QTextCodec::codecForName("UTF-8")); insertCodec(i18n("Unicode"), QTextCodec::codecForName("iso-10646-UCS-2")); insertCodec(i18n("Latin1"), QTextCodec::codecForName("iso 8859-1")); // First sort codec names: std::map names; QList mibs = QTextCodec::availableMibs(); foreach(int i, mibs) { QTextCodec* c = QTextCodec::codecForMib(i); if(c != nullptr) names[QString(QLatin1String(c->name())).toUpper()] = c; } std::map::iterator it; for(it = names.begin(); it != names.end(); ++it) { insertCodec("", it->second); } this->setToolTip(i18n( "Change this if non-ASCII characters are not displayed correctly.")); } void insertCodec(const QString& visibleCodecName, QTextCodec* c) { if(c != nullptr) { QString codecName = QLatin1String(c->name()); for(int i = 0; i < m_codecVec.size(); ++i) { if(c == m_codecVec[i]) return; // don't insert any codec twice } // The m_codecVec.size will at this point return the value we need for the index. if(codecName == defaultName()) saveDefaultIndex(m_codecVec.size()); QString itemText = visibleCodecName.isEmpty() ? codecName : visibleCodecName + QStringLiteral(" (") + codecName + QStringLiteral(")"); addItem(itemText, (int)m_codecVec.size()); m_codecVec.push_back(c); } } void setToDefault() override { int index = getDefaultIndex(); setCurrentIndex(index); if(m_ppVarCodec != nullptr) { *m_ppVarCodec = m_codecVec[index]; } } void setToCurrent() override { if(m_ppVarCodec != nullptr) { for(int i = 0; i < m_codecVec.size(); ++i) { if(*m_ppVarCodec == m_codecVec[i]) { setCurrentIndex(i); break; } } } } void apply() override { if(m_ppVarCodec != nullptr) { *m_ppVarCodec = m_codecVec[currentIndex()]; } } void write(ValueMap* config) override { if(m_ppVarCodec != nullptr) config->writeEntry(m_saveName, (const char*)(*m_ppVarCodec)->name()); } void read(ValueMap* config) override { QString codecName = config->readEntry(m_saveName, (const char*)m_codecVec[currentIndex()]->name()); for(int i = 0; i < m_codecVec.size(); ++i) { if(codecName == QLatin1String(m_codecVec[i]->name())) { setCurrentIndex(i); if(m_ppVarCodec != nullptr) *m_ppVarCodec = m_codecVec[i]; break; } } } protected: void preserve() override { m_preservedVal = currentIndex(); } void unpreserve() override { setCurrentIndex(m_preservedVal); } int m_preservedVal; }; void OptionDialog::addOptionItem(OptionItemBase* p) { m_optionItemList.push_back(p); } OptionDialog::OptionDialog(bool bShowDirMergeSettings, QWidget* parent) : KPageDialog(parent) { setFaceType(List); setWindowTitle(i18n("Configure")); setStandardButtons(QDialogButtonBox::Help | QDialogButtonBox::RestoreDefaults | QDialogButtonBox::Apply | QDialogButtonBox::Ok | QDialogButtonBox::Cancel); setModal(true); //showButtonSeparator( true ); //setHelp( "kdiff3/index.html", QString::null ); setupFontPage(); setupColorPage(); setupEditPage(); setupDiffPage(); setupMergePage(); setupOtherOptions(); if(bShowDirMergeSettings) setupDirectoryMergePage(); setupRegionalPage(); setupIntegrationPage(); //setupKeysPage(); // Initialize all values in the dialog resetToDefaults(); slotApply(); connect(button(QDialogButtonBox::Apply), &QPushButton::clicked, this, &OptionDialog::slotApply); connect(button(QDialogButtonBox::Ok), &QPushButton::clicked, this, &OptionDialog::slotOk); connect(button(QDialogButtonBox::RestoreDefaults), &QPushButton::clicked, this, &OptionDialog::slotDefault); connect(button(QDialogButtonBox::Cancel), &QPushButton::clicked, this, &QDialog::reject); connect(button(QDialogButtonBox::Help), &QPushButton::clicked, this, &OptionDialog::helpRequested); //connect(this, &OptionDialog::applyClicked, this, &OptionDialog::slotApply); //helpClicked() is connected in KDiff3App::KDiff3App -- Really where? //connect(this, &OptionDialog::defaultClicked, this, &OptionDialog::slotDefault); } void OptionDialog::helpRequested() { - KHelpClient::invokeHelp(QStringLiteral("kdiff3/index.html"), QString()); + KHelpClient::invokeHelp(QStringLiteral("kdiff3/index.html")); } OptionDialog::~OptionDialog() { } void OptionDialog::setupOtherOptions() { //TODO move to Options class addOptionItem(new OptionToggleAction(false, "AutoAdvance", &m_options.m_bAutoAdvance)); addOptionItem(new OptionToggleAction(true, "ShowWhiteSpaceCharacters", &m_options.m_bShowWhiteSpaceCharacters)); addOptionItem(new OptionToggleAction(true, "ShowWhiteSpace", &m_options.m_bShowWhiteSpace)); addOptionItem(new OptionToggleAction(false, "ShowLineNumbers", &m_options.m_bShowLineNumbers)); addOptionItem(new OptionToggleAction(true, "HorizDiffWindowSplitting", &m_options.m_bHorizDiffWindowSplitting)); addOptionItem(new OptionToggleAction(false, "WordWrap", &m_options.m_bWordWrap)); addOptionItem(new OptionToggleAction(true, "ShowIdenticalFiles", &m_options.m_bDmShowIdenticalFiles)); addOptionItem(new OptionToggleAction(true, "Show Toolbar", &m_options.m_bShowToolBar)); addOptionItem(new OptionToggleAction(true, "Show Statusbar", &m_options.m_bShowStatusBar)); /* - TODO manage toolbar positioning + TODO manage toolbar positioning */ addOptionItem(new OptionNum( Qt::TopToolBarArea, "ToolBarPos", (int*)&m_options.m_toolBarPos)); addOptionItem(new OptionSize(QSize(600, 400), "Geometry", &m_options.m_geometry)); addOptionItem(new OptionPoint(QPoint(0, 22), "Position", &m_options.m_position)); addOptionItem(new OptionToggleAction(false, "WindowStateMaximised", &m_options.m_bMaximised)); addOptionItem(new OptionStringList(&m_options.m_recentAFiles, "RecentAFiles")); addOptionItem(new OptionStringList(&m_options.m_recentBFiles, "RecentBFiles")); addOptionItem(new OptionStringList(&m_options.m_recentCFiles, "RecentCFiles")); addOptionItem(new OptionStringList(&m_options.m_recentOutputFiles, "RecentOutputFiles")); addOptionItem(new OptionStringList(&m_options.m_recentEncodings, "RecentEncodings")); } void OptionDialog::setupFontPage() { QFrame* page = new QFrame(); KPageWidgetItem* pageItem = new KPageWidgetItem(page, i18n("Font")); pageItem->setHeader(i18n("Editor & Diff Output Font")); //not all themes have this icon if(QIcon::hasThemeIcon(QStringLiteral("font-select-symbolic"))) pageItem->setIcon(QIcon::fromTheme(QStringLiteral("font-select-symbolic"))); else pageItem->setIcon(QIcon::fromTheme(QStringLiteral("preferences-desktop-font"))); addPage(pageItem); QVBoxLayout* topLayout = new QVBoxLayout(page); topLayout->setMargin(5); //requires QT 5.2 or later. static const QFont defaultFont = QFontDatabase::systemFont(QFontDatabase::FixedFont); ; static QFont defaultAppFont = QApplication::font(); OptionFontChooser* pAppFontChooser = new OptionFontChooser(defaultAppFont, "ApplicationFont", &m_options.m_appFont, page); addOptionItem(pAppFontChooser); topLayout->addWidget(pAppFontChooser); pAppFontChooser->setTitle(i18n("Application font")); OptionFontChooser* pFontChooser = new OptionFontChooser(defaultFont, "Font", &m_options.m_font, page); addOptionItem(pFontChooser); topLayout->addWidget(pFontChooser); pFontChooser->setTitle(i18n("File view font")); QGridLayout* gbox = new QGridLayout(); topLayout->addLayout(gbox); //int line=0; // This currently does not work (see rendering in class DiffTextWindow) //OptionCheckBox* pItalicDeltas = new OptionCheckBox( i18n("Italic font for deltas"), false, "ItalicForDeltas", &m_options.m_bItalicForDeltas, page, this ); //addOptionItem(pItalicDeltas); //gbox->addWidget( pItalicDeltas, line, 0, 1, 2 ); //pItalicDeltas->setToolTip( i18n( // "Selects the italic version of the font for differences.\n" // "If the font doesn't support italic characters, then this does nothing.") // ); } void OptionDialog::setupColorPage() { QFrame* page = new QFrame(); KPageWidgetItem* pageItem = new KPageWidgetItem(page, i18nc("Title for color settings page","Color")); pageItem->setHeader(i18n("Colors Settings")); pageItem->setIcon(QIcon::fromTheme(QStringLiteral("colormanagement"))); addPage(pageItem); QVBoxLayout* topLayout = new QVBoxLayout(page); topLayout->setMargin(5); QGridLayout* gbox = new QGridLayout(); gbox->setColumnStretch(1, 5); topLayout->addLayout(gbox); QLabel* label; int line = 0; int depth = QPixmap::defaultDepth(); bool bLowColor = depth <= 8; label = new QLabel(i18n("Editor and Diff Views:"), page); gbox->addWidget(label, line, 0); QFont f(label->font()); f.setBold(true); label->setFont(f); ++line; OptionColorButton* pFgColor = new OptionColorButton(Qt::black, "FgColor", &m_options.m_fgColor, page); label = new QLabel(i18n("Foreground color:"), page); label->setBuddy(pFgColor); addOptionItem(pFgColor); gbox->addWidget(label, line, 0); gbox->addWidget(pFgColor, line, 1); ++line; OptionColorButton* pBgColor = new OptionColorButton(Qt::white, "BgColor", &m_options.m_bgColor, page); label = new QLabel(i18n("Background color:"), page); label->setBuddy(pBgColor); addOptionItem(pBgColor); gbox->addWidget(label, line, 0); gbox->addWidget(pBgColor, line, 1); ++line; OptionColorButton* pDiffBgColor = new OptionColorButton( bLowColor ? QColor(Qt::lightGray) : qRgb(224, 224, 224), "DiffBgColor", &m_options.m_diffBgColor, page); label = new QLabel(i18n("Diff background color:"), page); label->setBuddy(pDiffBgColor); addOptionItem(pDiffBgColor); gbox->addWidget(label, line, 0); gbox->addWidget(pDiffBgColor, line, 1); ++line; OptionColorButton* pColorA = new OptionColorButton( bLowColor ? qRgb(0, 0, 255) : qRgb(0, 0, 200) /*blue*/, "ColorA", &m_options.m_colorA, page); label = new QLabel(i18n("Color A:"), page); label->setBuddy(pColorA); addOptionItem(pColorA); gbox->addWidget(label, line, 0); gbox->addWidget(pColorA, line, 1); ++line; OptionColorButton* pColorB = new OptionColorButton( bLowColor ? qRgb(0, 128, 0) : qRgb(0, 150, 0) /*green*/, "ColorB", &m_options.m_colorB, page); label = new QLabel(i18n("Color B:"), page); label->setBuddy(pColorB); addOptionItem(pColorB); gbox->addWidget(label, line, 0); gbox->addWidget(pColorB, line, 1); ++line; OptionColorButton* pColorC = new OptionColorButton( bLowColor ? qRgb(128, 0, 128) : qRgb(150, 0, 150) /*magenta*/, "ColorC", &m_options.m_colorC, page); label = new QLabel(i18n("Color C:"), page); label->setBuddy(pColorC); addOptionItem(pColorC); gbox->addWidget(label, line, 0); gbox->addWidget(pColorC, line, 1); ++line; OptionColorButton* pColorForConflict = new OptionColorButton(Qt::red, "ColorForConflict", &m_options.m_colorForConflict, page); label = new QLabel(i18n("Conflict color:"), page); label->setBuddy(pColorForConflict); addOptionItem(pColorForConflict); gbox->addWidget(label, line, 0); gbox->addWidget(pColorForConflict, line, 1); ++line; OptionColorButton* pColor = new OptionColorButton( bLowColor ? qRgb(192, 192, 192) : qRgb(220, 220, 100), "CurrentRangeBgColor", &m_options.m_currentRangeBgColor, page); label = new QLabel(i18n("Current range background color:"), page); label->setBuddy(pColor); addOptionItem(pColor); gbox->addWidget(label, line, 0); gbox->addWidget(pColor, line, 1); ++line; pColor = new OptionColorButton( bLowColor ? qRgb(255, 255, 0) : qRgb(255, 255, 150), "CurrentRangeDiffBgColor", &m_options.m_currentRangeDiffBgColor, page); label = new QLabel(i18n("Current range diff background color:"), page); label->setBuddy(pColor); addOptionItem(pColor); gbox->addWidget(label, line, 0); gbox->addWidget(pColor, line, 1); ++line; pColor = new OptionColorButton(qRgb(0xff, 0xd0, 0x80), "ManualAlignmentRangeColor", &m_options.m_manualHelpRangeColor, page); label = new QLabel(i18n("Color for manually aligned difference ranges:"), page); label->setBuddy(pColor); addOptionItem(pColor); gbox->addWidget(label, line, 0); gbox->addWidget(pColor, line, 1); ++line; label = new QLabel(i18n("Directory Comparison View:"), page); gbox->addWidget(label, line, 0); label->setFont(f); ++line; pColor = new OptionColorButton(qRgb(0, 0xd0, 0), "NewestFileColor", &m_options.m_newestFileColor, page); label = new QLabel(i18n("Newest file color:"), page); label->setBuddy(pColor); addOptionItem(pColor); gbox->addWidget(label, line, 0); gbox->addWidget(pColor, line, 1); QString dirColorTip = i18n("Changing this color will only be effective when starting the next directory comparison."); label->setToolTip(dirColorTip); ++line; pColor = new OptionColorButton(qRgb(0xf0, 0, 0), "OldestFileColor", &m_options.m_oldestFileColor, page); label = new QLabel(i18n("Oldest file color:"), page); label->setBuddy(pColor); addOptionItem(pColor); gbox->addWidget(label, line, 0); gbox->addWidget(pColor, line, 1); label->setToolTip(dirColorTip); ++line; pColor = new OptionColorButton(qRgb(0xc0, 0xc0, 0), "MidAgeFileColor", &m_options.m_midAgeFileColor, page); label = new QLabel(i18n("Middle age file color:"), page); label->setBuddy(pColor); addOptionItem(pColor); gbox->addWidget(label, line, 0); gbox->addWidget(pColor, line, 1); label->setToolTip(dirColorTip); ++line; pColor = new OptionColorButton(qRgb(0, 0, 0), "MissingFileColor", &m_options.m_missingFileColor, page); label = new QLabel(i18n("Color for missing files:"), page); label->setBuddy(pColor); addOptionItem(pColor); gbox->addWidget(label, line, 0); gbox->addWidget(pColor, line, 1); label->setToolTip(dirColorTip); ++line; topLayout->addStretch(10); } void OptionDialog::setupEditPage() { QFrame* page = new QFrame(); KPageWidgetItem* pageItem = new KPageWidgetItem(page, i18n("Editor")); pageItem->setHeader(i18n("Editor Behavior")); pageItem->setIcon(QIcon::fromTheme(QStringLiteral("accessories-text-editor"))); addPage(pageItem); QVBoxLayout* topLayout = new QVBoxLayout(page); topLayout->setMargin(5); QGridLayout* gbox = new QGridLayout(); gbox->setColumnStretch(1, 5); topLayout->addLayout(gbox); QLabel* label; int line = 0; OptionCheckBox* pReplaceTabs = new OptionCheckBox(i18n("Tab inserts spaces"), false, "ReplaceTabs", &m_options.m_bReplaceTabs, page); addOptionItem(pReplaceTabs); gbox->addWidget(pReplaceTabs, line, 0, 1, 2); pReplaceTabs->setToolTip(i18n( "On: Pressing tab generates the appropriate number of spaces.\n" "Off: A tab character will be inserted.")); ++line; OptionIntEdit* pTabSize = new OptionIntEdit(8, "TabSize", &m_options.m_tabSize, 1, 100, page); label = new QLabel(i18n("Tab size:"), page); label->setBuddy(pTabSize); addOptionItem(pTabSize); gbox->addWidget(label, line, 0); gbox->addWidget(pTabSize, line, 1); ++line; OptionCheckBox* pAutoIndentation = new OptionCheckBox(i18n("Auto indentation"), true, "AutoIndentation", &m_options.m_bAutoIndentation, page); gbox->addWidget(pAutoIndentation, line, 0, 1, 2); addOptionItem(pAutoIndentation); pAutoIndentation->setToolTip(i18n( "On: The indentation of the previous line is used for a new line.\n")); ++line; OptionCheckBox* pAutoCopySelection = new OptionCheckBox(i18n("Auto copy selection"), false, "AutoCopySelection", &m_options.m_bAutoCopySelection, page); gbox->addWidget(pAutoCopySelection, line, 0, 1, 2); addOptionItem(pAutoCopySelection); pAutoCopySelection->setToolTip(i18n( "On: Any selection is immediately written to the clipboard.\n" "Off: You must explicitly copy e.g. via Ctrl-C.")); ++line; label = new QLabel(i18n("Line end style:"), page); gbox->addWidget(label, line, 0); OptionComboBox* pLineEndStyle = new OptionComboBox(eLineEndStyleAutoDetect, "LineEndStyle", (int*)&m_options.m_lineEndStyle, page); gbox->addWidget(pLineEndStyle, line, 1); addOptionItem(pLineEndStyle); pLineEndStyle->insertItem(eLineEndStyleUnix, "Unix"); pLineEndStyle->insertItem(eLineEndStyleDos, "Dos/Windows"); pLineEndStyle->insertItem(eLineEndStyleAutoDetect, "Autodetect"); label->setToolTip(i18n( "Sets the line endings for when an edited file is saved.\n" "DOS/Windows: CR+LF; UNIX: LF; with CR=0D, LF=0A")); ++line; topLayout->addStretch(10); } void OptionDialog::setupDiffPage() { QFrame* page = new QFrame(); KPageWidgetItem* pageItem = new KPageWidgetItem(page, i18n("Diff")); pageItem->setHeader(i18n("Diff Settings")); pageItem->setIcon(QIcon::fromTheme(QStringLiteral("text-x-patch"))); addPage(pageItem); QVBoxLayout* topLayout = new QVBoxLayout(page); topLayout->setMargin(5); QGridLayout* gbox = new QGridLayout(); gbox->setColumnStretch(1, 5); topLayout->addLayout(gbox); int line = 0; QLabel* label = nullptr; m_options.m_bPreserveCarriageReturn = false; /* OptionCheckBox* pPreserveCarriageReturn = new OptionCheckBox( i18n("Preserve carriage return"), false, "PreserveCarriageReturn", &m_options.m_bPreserveCarriageReturn, page, this ); addOptionItem(pPreserveCarriageReturn); gbox->addWidget( pPreserveCarriageReturn, line, 0, 1, 2 ); pPreserveCarriageReturn->setToolTip( i18n( "Show carriage return characters '\\r' if they exist.\n" "Helps to compare files that were modified under different operating systems.") ); ++line; */ OptionCheckBox* pIgnoreNumbers = new OptionCheckBox(i18n("Ignore numbers (treat as white space)"), false, "IgnoreNumbers", &m_options.m_bIgnoreNumbers, page); gbox->addWidget(pIgnoreNumbers, line, 0, 1, 2); addOptionItem(pIgnoreNumbers); pIgnoreNumbers->setToolTip(i18n( "Ignore number characters during line matching phase. (Similar to Ignore white space.)\n" "Might help to compare files with numeric data.")); ++line; OptionCheckBox* pIgnoreComments = new OptionCheckBox(i18n("Ignore C/C++ comments (treat as white space)"), false, "IgnoreComments", &m_options.m_bIgnoreComments, page); gbox->addWidget(pIgnoreComments, line, 0, 1, 2); addOptionItem(pIgnoreComments); pIgnoreComments->setToolTip(i18n("Treat C/C++ comments like white space.")); ++line; OptionCheckBox* pIgnoreCase = new OptionCheckBox(i18n("Ignore case (treat as white space)"), false, "IgnoreCase", &m_options.m_bIgnoreCase, page); gbox->addWidget(pIgnoreCase, line, 0, 1, 2); addOptionItem(pIgnoreCase); pIgnoreCase->setToolTip(i18n( "Treat case differences like white space changes. ('a'<=>'A')")); ++line; label = new QLabel(i18n("Preprocessor command:"), page); gbox->addWidget(label, line, 0); OptionLineEdit* pLE = new OptionLineEdit("", "PreProcessorCmd", &m_options.m_PreProcessorCmd, page); gbox->addWidget(pLE, line, 1); addOptionItem(pLE); label->setToolTip(i18n("User defined pre-processing. (See the docs for details.)")); ++line; label = new QLabel(i18n("Line-matching preprocessor command:"), page); gbox->addWidget(label, line, 0); pLE = new OptionLineEdit("", "LineMatchingPreProcessorCmd", &m_options.m_LineMatchingPreProcessorCmd, page); gbox->addWidget(pLE, line, 1); addOptionItem(pLE); label->setToolTip(i18n("This pre-processor is only used during line matching.\n(See the docs for details.)")); ++line; OptionCheckBox* pTryHard = new OptionCheckBox(i18n("Try hard (slower)"), true, "TryHard", &m_options.m_bTryHard, page); gbox->addWidget(pTryHard, line, 0, 1, 2); addOptionItem(pTryHard); pTryHard->setToolTip(i18n( "Enables the --minimal option for the external diff.\n" "The analysis of big files will be much slower.")); ++line; OptionCheckBox* pDiff3AlignBC = new OptionCheckBox(i18n("Align B and C for 3 input files"), false, "Diff3AlignBC", &m_options.m_bDiff3AlignBC, page); gbox->addWidget(pDiff3AlignBC, line, 0, 1, 2); addOptionItem(pDiff3AlignBC); pDiff3AlignBC->setToolTip(i18n( "Try to align B and C when comparing or merging three input files.\n" "Not recommended for merging because merge might get more complicated.\n" "(Default is off.)")); ++line; topLayout->addStretch(10); } void OptionDialog::setupMergePage() { QFrame* page = new QFrame(); KPageWidgetItem* pageItem = new KPageWidgetItem(page, i18n("Merge")); pageItem->setHeader(i18n("Merge Settings")); pageItem->setIcon(QIcon::fromTheme(QStringLiteral("merge"))); addPage(pageItem); QVBoxLayout* topLayout = new QVBoxLayout(page); topLayout->setMargin(5); QGridLayout* gbox = new QGridLayout(); gbox->setColumnStretch(1, 5); topLayout->addLayout(gbox); int line = 0; QLabel* label = nullptr; label = new QLabel(i18n("Auto advance delay (ms):"), page); gbox->addWidget(label, line, 0); OptionIntEdit* pAutoAdvanceDelay = new OptionIntEdit(500, "AutoAdvanceDelay", &m_options.m_autoAdvanceDelay, 0, 2000, page); gbox->addWidget(pAutoAdvanceDelay, line, 1); addOptionItem(pAutoAdvanceDelay); label->setToolTip(i18n( "When in Auto-Advance mode the result of the current selection is shown \n" "for the specified time, before jumping to the next conflict. Range: 0-2000 ms")); ++line; OptionCheckBox* pShowInfoDialogs = new OptionCheckBox(i18n("Show info dialogs"), true, "ShowInfoDialogs", &m_options.m_bShowInfoDialogs, page); gbox->addWidget(pShowInfoDialogs, line, 0, 1, 2); addOptionItem(pShowInfoDialogs); pShowInfoDialogs->setToolTip(i18n("Show a dialog with information about the number of conflicts.")); ++line; label = new QLabel(i18n("White space 2-file merge default:"), page); gbox->addWidget(label, line, 0); OptionComboBox* pWhiteSpace2FileMergeDefault = new OptionComboBox(0, "WhiteSpace2FileMergeDefault", &m_options.m_whiteSpace2FileMergeDefault, page); gbox->addWidget(pWhiteSpace2FileMergeDefault, line, 1); addOptionItem(pWhiteSpace2FileMergeDefault); pWhiteSpace2FileMergeDefault->insertItem(0, i18n("Manual Choice")); pWhiteSpace2FileMergeDefault->insertItem(1, i18n("A")); pWhiteSpace2FileMergeDefault->insertItem(2, i18n("B")); label->setToolTip(i18n( "Allow the merge algorithm to automatically select an input for " "white-space-only changes.")); ++line; label = new QLabel(i18n("White space 3-file merge default:"), page); gbox->addWidget(label, line, 0); OptionComboBox* pWhiteSpace3FileMergeDefault = new OptionComboBox(0, "WhiteSpace3FileMergeDefault", &m_options.m_whiteSpace3FileMergeDefault, page); gbox->addWidget(pWhiteSpace3FileMergeDefault, line, 1); addOptionItem(pWhiteSpace3FileMergeDefault); pWhiteSpace3FileMergeDefault->insertItem(0, i18n("Manual Choice")); pWhiteSpace3FileMergeDefault->insertItem(1, i18n("A")); pWhiteSpace3FileMergeDefault->insertItem(2, i18n("B")); pWhiteSpace3FileMergeDefault->insertItem(3, i18n("C")); label->setToolTip(i18n( "Allow the merge algorithm to automatically select an input for " "white-space-only changes.")); ++line; QGroupBox* pGroupBox = new QGroupBox(i18n("Automatic Merge Regular Expression")); gbox->addWidget(pGroupBox, line, 0, 1, 2); ++line; { gbox = new QGridLayout(pGroupBox); gbox->setColumnStretch(1, 10); line = 0; label = new QLabel(i18n("Auto merge regular expression:"), page); gbox->addWidget(label, line, 0); m_pAutoMergeRegExpLineEdit = new OptionLineEdit(".*\\$(Version|Header|Date|Author).*\\$.*", "AutoMergeRegExp", &m_options.m_autoMergeRegExp, page); gbox->addWidget(m_pAutoMergeRegExpLineEdit, line, 1); addOptionItem(m_pAutoMergeRegExpLineEdit); s_autoMergeRegExpToolTip = i18n("Regular expression for lines where KDiff3 should automatically choose one source.\n" "When a line with a conflict matches the regular expression then\n" "- if available - C, otherwise B will be chosen."); label->setToolTip(s_autoMergeRegExpToolTip); ++line; OptionCheckBox* pAutoMergeRegExp = new OptionCheckBox(i18n("Run regular expression auto merge on merge start"), false, "RunRegExpAutoMergeOnMergeStart", &m_options.m_bRunRegExpAutoMergeOnMergeStart, page); addOptionItem(pAutoMergeRegExp); gbox->addWidget(pAutoMergeRegExp, line, 0, 1, 2); pAutoMergeRegExp->setToolTip(i18n("Run the merge for auto merge regular expressions\n" "immediately when a merge starts.\n")); ++line; } pGroupBox = new QGroupBox(i18n("Version Control History Merging")); gbox->addWidget(pGroupBox, line, 0, 1, 2); ++line; { gbox = new QGridLayout(pGroupBox); gbox->setColumnStretch(1, 10); line = 0; label = new QLabel(i18n("History start regular expression:"), page); gbox->addWidget(label, line, 0); m_pHistoryStartRegExpLineEdit = new OptionLineEdit(".*\\$Log.*\\$.*", "HistoryStartRegExp", &m_options.m_historyStartRegExp, page); gbox->addWidget(m_pHistoryStartRegExpLineEdit, line, 1); addOptionItem(m_pHistoryStartRegExpLineEdit); s_historyStartRegExpToolTip = i18n("Regular expression for the start of the version control history entry.\n" "Usually this line contains the \"$Log$\" keyword.\n" "Default value: \".*\\$Log.*\\$.*\""); label->setToolTip(s_historyStartRegExpToolTip); ++line; label = new QLabel(i18n("History entry start regular expression:"), page); gbox->addWidget(label, line, 0); // Example line: "** \main\rolle_fsp_dev_008\1 17 Aug 2001 10:45:44 rolle" QString historyEntryStartDefault = "\\s*\\\\main\\\\(\\S+)\\s+" // Start with "\main\" "([0-9]+) " // day "(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) " //month "([0-9][0-9][0-9][0-9]) " // year "([0-9][0-9]:[0-9][0-9]:[0-9][0-9])\\s+(.*)"; // time, name m_pHistoryEntryStartRegExpLineEdit = new OptionLineEdit(historyEntryStartDefault, "HistoryEntryStartRegExp", &m_options.m_historyEntryStartRegExp, page); gbox->addWidget(m_pHistoryEntryStartRegExpLineEdit, line, 1); addOptionItem(m_pHistoryEntryStartRegExpLineEdit); s_historyEntryStartRegExpToolTip = i18n("A version control history entry consists of several lines.\n" "Specify the regular expression to detect the first line (without the leading comment).\n" "Use parentheses to group the keys you want to use for sorting.\n" "If left empty, then KDiff3 assumes that empty lines separate history entries.\n" "See the documentation for details."); label->setToolTip(s_historyEntryStartRegExpToolTip); ++line; m_pHistoryMergeSorting = new OptionCheckBox(i18n("History merge sorting"), false, "HistoryMergeSorting", &m_options.m_bHistoryMergeSorting, page); gbox->addWidget(m_pHistoryMergeSorting, line, 0, 1, 2); addOptionItem(m_pHistoryMergeSorting); m_pHistoryMergeSorting->setToolTip(i18n("Sort version control history by a key.")); ++line; //QString branch = newHistoryEntry.cap(1); //int day = newHistoryEntry.cap(2).toInt(); //int month = QString("Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec").find(newHistoryEntry.cap(3))/4 + 1; //int year = newHistoryEntry.cap(4).toInt(); //QString time = newHistoryEntry.cap(5); //QString name = newHistoryEntry.cap(6); QString defaultSortKeyOrder = "4,3,2,5,1,6"; //QDate(year,month,day).toString(Qt::ISODate) +" "+ time + " " + branch + " " + name; label = new QLabel(i18n("History entry start sort key order:"), page); gbox->addWidget(label, line, 0); m_pHistorySortKeyOrderLineEdit = new OptionLineEdit(defaultSortKeyOrder, "HistoryEntryStartSortKeyOrder", &m_options.m_historyEntryStartSortKeyOrder, page); gbox->addWidget(m_pHistorySortKeyOrderLineEdit, line, 1); addOptionItem(m_pHistorySortKeyOrderLineEdit); s_historyEntryStartSortKeyOrderToolTip = i18n("Each pair of parentheses used in the regular expression for the history start entry\n" "groups a key that can be used for sorting.\n" "Specify the list of keys (that are numbered in order of occurrence\n" "starting with 1) using ',' as separator (e.g. \"4,5,6,1,2,3,7\").\n" "If left empty, then no sorting will be done.\n" "See the documentation for details."); label->setToolTip(s_historyEntryStartSortKeyOrderToolTip); m_pHistorySortKeyOrderLineEdit->setEnabled(false); connect(m_pHistoryMergeSorting, &OptionCheckBox::toggled, m_pHistorySortKeyOrderLineEdit, &OptionLineEdit::setEnabled); ++line; m_pHistoryAutoMerge = new OptionCheckBox(i18n("Merge version control history on merge start"), false, "RunHistoryAutoMergeOnMergeStart", &m_options.m_bRunHistoryAutoMergeOnMergeStart, page); addOptionItem(m_pHistoryAutoMerge); gbox->addWidget(m_pHistoryAutoMerge, line, 0, 1, 2); m_pHistoryAutoMerge->setToolTip(i18n("Run version control history automerge on merge start.")); ++line; OptionIntEdit* pMaxNofHistoryEntries = new OptionIntEdit(-1, "MaxNofHistoryEntries", &m_options.m_maxNofHistoryEntries, -1, 1000, page); label = new QLabel(i18n("Max number of history entries:"), page); gbox->addWidget(label, line, 0); gbox->addWidget(pMaxNofHistoryEntries, line, 1); addOptionItem(pMaxNofHistoryEntries); pMaxNofHistoryEntries->setToolTip(i18n("Cut off after specified number. Use -1 for infinite number of entries.")); ++line; } QPushButton* pButton = new QPushButton(i18n("Test your regular expressions"), page); gbox->addWidget(pButton, line, 0); connect(pButton, &QPushButton::clicked, this, &OptionDialog::slotHistoryMergeRegExpTester); ++line; label = new QLabel(i18n("Irrelevant merge command:"), page); gbox->addWidget(label, line, 0); OptionLineEdit* pLE = new OptionLineEdit("", "IrrelevantMergeCmd", &m_options.m_IrrelevantMergeCmd, page); gbox->addWidget(pLE, line, 1); addOptionItem(pLE); label->setToolTip(i18n("If specified this script is run after automerge\n" "when no other relevant changes were detected.\n" "Called with the parameters: filename1 filename2 filename3")); ++line; OptionCheckBox* pAutoSaveAndQuit = new OptionCheckBox(i18n("Auto save and quit on merge without conflicts"), false, "AutoSaveAndQuitOnMergeWithoutConflicts", &m_options.m_bAutoSaveAndQuitOnMergeWithoutConflicts, page); gbox->addWidget(pAutoSaveAndQuit, line, 0, 1, 2); addOptionItem(pAutoSaveAndQuit); pAutoSaveAndQuit->setToolTip(i18n("If KDiff3 was started for a file-merge from the command line and all\n" "conflicts are solvable without user interaction then automatically save and quit.\n" "(Similar to command line option \"--auto\".)")); ++line; topLayout->addStretch(10); } void OptionDialog::setupDirectoryMergePage() { QFrame* page = new QFrame(); KPageWidgetItem* pageItem = new KPageWidgetItem(page, i18n("Directory")); pageItem->setHeader(i18n("Directory")); pageItem->setIcon(QIcon::fromTheme(QStringLiteral("inode-directory"))); addPage(pageItem); QVBoxLayout* topLayout = new QVBoxLayout(page); topLayout->setMargin(5); QGridLayout* gbox = new QGridLayout(); gbox->setColumnStretch(1, 5); topLayout->addLayout(gbox); int line = 0; OptionCheckBox* pRecursiveDirs = new OptionCheckBox(i18n("Recursive directories"), true, "RecursiveDirs", &m_options.m_bDmRecursiveDirs, page); gbox->addWidget(pRecursiveDirs, line, 0, 1, 2); addOptionItem(pRecursiveDirs); pRecursiveDirs->setToolTip(i18n("Whether to analyze subdirectories or not.")); ++line; QLabel* label = new QLabel(i18n("File pattern(s):"), page); gbox->addWidget(label, line, 0); OptionLineEdit* pFilePattern = new OptionLineEdit("*", "FilePattern", &m_options.m_DmFilePattern, page); gbox->addWidget(pFilePattern, line, 1); addOptionItem(pFilePattern); label->setToolTip(i18n( "Pattern(s) of files to be analyzed. \n" "Wildcards: '*' and '?'\n" "Several Patterns can be specified by using the separator: ';'")); ++line; label = new QLabel(i18n("File-anti-pattern(s):"), page); gbox->addWidget(label, line, 0); OptionLineEdit* pFileAntiPattern = new OptionLineEdit("*.orig;*.o;*.obj;*.rej;*.bak", "FileAntiPattern", &m_options.m_DmFileAntiPattern, page); gbox->addWidget(pFileAntiPattern, line, 1); addOptionItem(pFileAntiPattern); label->setToolTip(i18n( "Pattern(s) of files to be excluded from analysis. \n" "Wildcards: '*' and '?'\n" "Several Patterns can be specified by using the separator: ';'")); ++line; label = new QLabel(i18n("Dir-anti-pattern(s):"), page); gbox->addWidget(label, line, 0); OptionLineEdit* pDirAntiPattern = new OptionLineEdit("CVS;.deps;.svn;.hg;.git", "DirAntiPattern", &m_options.m_DmDirAntiPattern, page); gbox->addWidget(pDirAntiPattern, line, 1); addOptionItem(pDirAntiPattern); label->setToolTip(i18n( "Pattern(s) of directories to be excluded from analysis. \n" "Wildcards: '*' and '?'\n" "Several Patterns can be specified by using the separator: ';'")); ++line; OptionCheckBox* pUseCvsIgnore = new OptionCheckBox(i18n("Use .cvsignore"), false, "UseCvsIgnore", &m_options.m_bDmUseCvsIgnore, page); gbox->addWidget(pUseCvsIgnore, line, 0, 1, 2); addOptionItem(pUseCvsIgnore); pUseCvsIgnore->setToolTip(i18n( "Extends the antipattern to anything that would be ignored by CVS.\n" "Via local \".cvsignore\" files this can be directory specific.")); ++line; OptionCheckBox* pFindHidden = new OptionCheckBox(i18n("Find hidden files and directories"), true, "FindHidden", &m_options.m_bDmFindHidden, page); gbox->addWidget(pFindHidden, line, 0, 1, 2); addOptionItem(pFindHidden); pFindHidden->setToolTip(i18n("Finds hidden files and directories.")); ++line; OptionCheckBox* pFollowFileLinks = new OptionCheckBox(i18n("Follow file links"), false, "FollowFileLinks", &m_options.m_bDmFollowFileLinks, page); gbox->addWidget(pFollowFileLinks, line, 0, 1, 2); addOptionItem(pFollowFileLinks); pFollowFileLinks->setToolTip(i18n( "On: Compare the file the link points to.\n" "Off: Compare the links.")); ++line; OptionCheckBox* pFollowDirLinks = new OptionCheckBox(i18n("Follow directory links"), false, "FollowDirLinks", &m_options.m_bDmFollowDirLinks, page); gbox->addWidget(pFollowDirLinks, line, 0, 1, 2); addOptionItem(pFollowDirLinks); pFollowDirLinks->setToolTip(i18n( "On: Compare the directory the link points to.\n" "Off: Compare the links.")); ++line; #if defined(Q_OS_WIN) bool bCaseSensitiveFilenameComparison = false; #else bool bCaseSensitiveFilenameComparison = true; #endif OptionCheckBox* pCaseSensitiveFileNames = new OptionCheckBox(i18n("Case sensitive filename comparison"), bCaseSensitiveFilenameComparison, "CaseSensitiveFilenameComparison", &m_options.m_bDmCaseSensitiveFilenameComparison, page); gbox->addWidget(pCaseSensitiveFileNames, line, 0, 1, 2); addOptionItem(pCaseSensitiveFileNames); pCaseSensitiveFileNames->setToolTip(i18n( "The directory comparison will compare files or directories when their names match.\n" "Set this option if the case of the names must match. (Default for Windows is off, otherwise on.)")); ++line; OptionCheckBox* pUnfoldSubdirs = new OptionCheckBox(i18n("Unfold all subdirectories on load"), false, "UnfoldSubdirs", &m_options.m_bDmUnfoldSubdirs, page); gbox->addWidget(pUnfoldSubdirs, line, 0, 1, 2); addOptionItem(pUnfoldSubdirs); pUnfoldSubdirs->setToolTip(i18n( "On: Unfold all subdirectories when starting a directory diff.\n" "Off: Leave subdirectories folded.")); ++line; OptionCheckBox* pSkipDirStatus = new OptionCheckBox(i18n("Skip directory status report"), false, "SkipDirStatus", &m_options.m_bDmSkipDirStatus, page); gbox->addWidget(pSkipDirStatus, line, 0, 1, 2); addOptionItem(pSkipDirStatus); pSkipDirStatus->setToolTip(i18n( "On: Do not show the Directory Comparison Status.\n" "Off: Show the status dialog on start.")); ++line; QGroupBox* pBG = new QGroupBox(i18n("File Comparison Mode")); gbox->addWidget(pBG, line, 0, 1, 2); QVBoxLayout* pBGLayout = new QVBoxLayout(pBG); OptionRadioButton* pBinaryComparison = new OptionRadioButton(i18n("Binary comparison"), true, "BinaryComparison", &m_options.m_bDmBinaryComparison, pBG); addOptionItem(pBinaryComparison); pBinaryComparison->setToolTip(i18n("Binary comparison of each file. (Default)")); pBGLayout->addWidget(pBinaryComparison); OptionRadioButton* pFullAnalysis = new OptionRadioButton(i18n("Full analysis"), false, "FullAnalysis", &m_options.m_bDmFullAnalysis, pBG); addOptionItem(pFullAnalysis); pFullAnalysis->setToolTip(i18n("Do a full analysis and show statistics information in extra columns.\n" "(Slower than a binary comparison, much slower for binary files.)")); pBGLayout->addWidget(pFullAnalysis); OptionRadioButton* pTrustDate = new OptionRadioButton(i18n("Trust the size and modification date (unsafe)"), false, "TrustDate", &m_options.m_bDmTrustDate, pBG); addOptionItem(pTrustDate); pTrustDate->setToolTip(i18n("Assume that files are equal if the modification date and file length are equal.\n" "Files with equal contents but different modification dates will appear as different.\n" "Useful for big directories or slow networks.")); pBGLayout->addWidget(pTrustDate); OptionRadioButton* pTrustDateFallbackToBinary = new OptionRadioButton(i18n("Trust the size and date, but use binary comparison if date does not match (unsafe)"), false, "TrustDateFallbackToBinary", &m_options.m_bDmTrustDateFallbackToBinary, pBG); addOptionItem(pTrustDateFallbackToBinary); pTrustDateFallbackToBinary->setToolTip(i18n("Assume that files are equal if the modification date and file length are equal.\n" "If the dates are not equal but the sizes are, use binary comparison.\n" "Useful for big directories or slow networks.")); pBGLayout->addWidget(pTrustDateFallbackToBinary); OptionRadioButton* pTrustSize = new OptionRadioButton(i18n("Trust the size (unsafe)"), false, "TrustSize", &m_options.m_bDmTrustSize, pBG); addOptionItem(pTrustSize); pTrustSize->setToolTip(i18n("Assume that files are equal if their file lengths are equal.\n" "Useful for big directories or slow networks when the date is modified during download.")); pBGLayout->addWidget(pTrustSize); ++line; // Some two Dir-options: Affects only the default actions. OptionCheckBox* pSyncMode = new OptionCheckBox(i18n("Synchronize directories"), false, "SyncMode", &m_options.m_bDmSyncMode, page); addOptionItem(pSyncMode); gbox->addWidget(pSyncMode, line, 0, 1, 2); pSyncMode->setToolTip(i18n( "Offers to store files in both directories so that\n" "both directories are the same afterwards.\n" "Works only when comparing two directories without specifying a destination.")); ++line; // Allow white-space only differences to be considered equal OptionCheckBox* pWhiteSpaceDiffsEqual = new OptionCheckBox(i18n("White space differences considered equal"), true, "WhiteSpaceEqual", &m_options.m_bDmWhiteSpaceEqual, page); addOptionItem(pWhiteSpaceDiffsEqual); gbox->addWidget(pWhiteSpaceDiffsEqual, line, 0, 1, 2); pWhiteSpaceDiffsEqual->setToolTip(i18n( "If files differ only by white space consider them equal.\n" "This is only active when full analysis is chosen.")); connect(pFullAnalysis, &OptionRadioButton::toggled, pWhiteSpaceDiffsEqual, &OptionCheckBox::setEnabled); pWhiteSpaceDiffsEqual->setEnabled(false); ++line; OptionCheckBox* pCopyNewer = new OptionCheckBox(i18n("Copy newer instead of merging (unsafe)"), false, "CopyNewer", &m_options.m_bDmCopyNewer, page); addOptionItem(pCopyNewer); gbox->addWidget(pCopyNewer, line, 0, 1, 2); pCopyNewer->setToolTip(i18n( "Do not look inside, just take the newer file.\n" "(Use this only if you know what you are doing!)\n" "Only effective when comparing two directories.")); ++line; OptionCheckBox* pCreateBakFiles = new OptionCheckBox(i18n("Backup files (.orig)"), true, "CreateBakFiles", &m_options.m_bDmCreateBakFiles, page); gbox->addWidget(pCreateBakFiles, line, 0, 1, 2); addOptionItem(pCreateBakFiles); pCreateBakFiles->setToolTip(i18n( "If a file would be saved over an old file, then the old file\n" "will be renamed with a '.orig' extension instead of being deleted.")); ++line; topLayout->addStretch(10); } /* static void insertCodecs(OptionComboBox* p) { std::multimap m; // Using the multimap for case-insensitive sorting. int i; for(i=0;;++i) { QTextCodec* pCodec = QTextCodec::codecForIndex ( i ); if ( pCodec != 0 ) m.insert( std::make_pair( QString(pCodec->mimeName()).toUpper(), pCodec->mimeName()) ); else break; } p->insertItem( i18n("Auto"), 0 ); std::multimap::iterator mi; for(mi=m.begin(), i=0; mi!=m.end(); ++mi, ++i) p->insertItem(mi->second, i+1); } */ /* // UTF8-Codec that saves a BOM // UTF8-Codec that saves a BOM class Utf8BOMCodec : public QTextCodec { QTextCodec* m_pUtf8Codec; class PublicTextCodec : public QTextCodec { public: QString publicConvertToUnicode ( const char * p, int len, ConverterState* pState ) const { return convertToUnicode( p, len, pState ); } QByteArray publicConvertFromUnicode ( const QChar * input, int number, ConverterState * pState ) const { return convertFromUnicode( input, number, pState ); } }; public: Utf8BOMCodec() { m_pUtf8Codec = QTextCodec::codecForName("UTF-8"); } QByteArray name () const { return "UTF-8-BOM"; } int mibEnum () const { return 2123; } QByteArray convertFromUnicode ( const QChar * input, int number, ConverterState * pState ) const { QByteArray r; if ( pState && pState->state_data[2]==0) // state_data[2] not used by QUtf8::convertFromUnicode (see qutfcodec.cpp) { r += "\xEF\xBB\xBF"; pState->state_data[2]=1; pState->flags |= QTextCodec::IgnoreHeader; } r += ((PublicTextCodec*)m_pUtf8Codec)->publicConvertFromUnicode( input, number, pState ); return r; } QString convertToUnicode ( const char * p, int len, ConverterState* pState ) const { return ((PublicTextCodec*)m_pUtf8Codec)->publicConvertToUnicode( p, len, pState ); } }; */ void OptionDialog::setupRegionalPage() { /* TODO: What is this line supposed to do besides leak memory? Introduced as is in .91 no explanation new Utf8BOMCodec(); */ QFrame* page = new QFrame(); KPageWidgetItem* pageItem = new KPageWidgetItem(page, i18n("Regional Settings")); pageItem->setHeader(i18n("Regional Settings")); pageItem->setIcon(QIcon::fromTheme(QStringLiteral("preferences-desktop-locale"))); addPage(pageItem); QVBoxLayout* topLayout = new QVBoxLayout(page); topLayout->setMargin(5); QGridLayout* gbox = new QGridLayout(); gbox->setColumnStretch(1, 5); topLayout->addLayout(gbox); int line = 0; QLabel* label; m_pSameEncoding = new OptionCheckBox(i18n("Use the same encoding for everything:"), true, "SameEncoding", &m_options.m_bSameEncoding, page); addOptionItem(m_pSameEncoding); gbox->addWidget(m_pSameEncoding, line, 0, 1, 2); m_pSameEncoding->setToolTip(i18n( "Enable this allows to change all encodings by changing the first only.\n" "Disable this if different individual settings are needed.")); ++line; label = new QLabel(i18n("Note: Local Encoding is \"%1\"", QLatin1String(QTextCodec::codecForLocale()->name())), page); gbox->addWidget(label, line, 0); ++line; label = new QLabel(i18n("File Encoding for A:"), page); gbox->addWidget(label, line, 0); m_pEncodingAComboBox = new OptionEncodingComboBox("EncodingForA", &m_options.m_pEncodingA, page); addOptionItem(m_pEncodingAComboBox); gbox->addWidget(m_pEncodingAComboBox, line, 1); QString autoDetectToolTip = i18n( "If enabled then Unicode (UTF-16 or UTF-8) encoding will be detected.\n" "If the file is not Unicode then the selected encoding will be used as fallback.\n" "(Unicode detection depends on the first bytes of a file.)"); m_pAutoDetectUnicodeA = new OptionCheckBox(i18n("Auto Detect Unicode"), true, "AutoDetectUnicodeA", &m_options.m_bAutoDetectUnicodeA, page); gbox->addWidget(m_pAutoDetectUnicodeA, line, 2); addOptionItem(m_pAutoDetectUnicodeA); m_pAutoDetectUnicodeA->setToolTip(autoDetectToolTip); ++line; label = new QLabel(i18n("File Encoding for B:"), page); gbox->addWidget(label, line, 0); m_pEncodingBComboBox = new OptionEncodingComboBox("EncodingForB", &m_options.m_pEncodingB, page); addOptionItem(m_pEncodingBComboBox); gbox->addWidget(m_pEncodingBComboBox, line, 1); m_pAutoDetectUnicodeB = new OptionCheckBox(i18n("Auto Detect Unicode"), true, "AutoDetectUnicodeB", &m_options.m_bAutoDetectUnicodeB, page); addOptionItem(m_pAutoDetectUnicodeB); gbox->addWidget(m_pAutoDetectUnicodeB, line, 2); m_pAutoDetectUnicodeB->setToolTip(autoDetectToolTip); ++line; label = new QLabel(i18n("File Encoding for C:"), page); gbox->addWidget(label, line, 0); m_pEncodingCComboBox = new OptionEncodingComboBox("EncodingForC", &m_options.m_pEncodingC, page); addOptionItem(m_pEncodingCComboBox); gbox->addWidget(m_pEncodingCComboBox, line, 1); m_pAutoDetectUnicodeC = new OptionCheckBox(i18n("Auto Detect Unicode"), true, "AutoDetectUnicodeC", &m_options.m_bAutoDetectUnicodeC, page); addOptionItem(m_pAutoDetectUnicodeC); gbox->addWidget(m_pAutoDetectUnicodeC, line, 2); m_pAutoDetectUnicodeC->setToolTip(autoDetectToolTip); ++line; label = new QLabel(i18n("File Encoding for Merge Output and Saving:"), page); gbox->addWidget(label, line, 0); m_pEncodingOutComboBox = new OptionEncodingComboBox("EncodingForOutput", &m_options.m_pEncodingOut, page); addOptionItem(m_pEncodingOutComboBox); gbox->addWidget(m_pEncodingOutComboBox, line, 1); m_pAutoSelectOutEncoding = new OptionCheckBox(i18n("Auto Select"), true, "AutoSelectOutEncoding", &m_options.m_bAutoSelectOutEncoding, page); addOptionItem(m_pAutoSelectOutEncoding); gbox->addWidget(m_pAutoSelectOutEncoding, line, 2); m_pAutoSelectOutEncoding->setToolTip(i18n( "If enabled then the encoding from the input files is used.\n" "In ambiguous cases a dialog will ask the user to choose the encoding for saving.")); ++line; label = new QLabel(i18n("File Encoding for Preprocessor Files:"), page); gbox->addWidget(label, line, 0); m_pEncodingPPComboBox = new OptionEncodingComboBox("EncodingForPP", &m_options.m_pEncodingPP, page); addOptionItem(m_pEncodingPPComboBox); gbox->addWidget(m_pEncodingPPComboBox, line, 1); ++line; connect(m_pSameEncoding, &OptionCheckBox::toggled, this, &OptionDialog::slotEncodingChanged); connect(m_pEncodingAComboBox, static_cast(&OptionEncodingComboBox::activated), this, &OptionDialog::slotEncodingChanged); connect(m_pAutoDetectUnicodeA, &OptionCheckBox::toggled, this, &OptionDialog::slotEncodingChanged); connect(m_pAutoSelectOutEncoding, &OptionCheckBox::toggled, this, &OptionDialog::slotEncodingChanged); OptionCheckBox* pRightToLeftLanguage = new OptionCheckBox(i18n("Right To Left Language"), false, "RightToLeftLanguage", &m_options.m_bRightToLeftLanguage, page); addOptionItem(pRightToLeftLanguage); gbox->addWidget(pRightToLeftLanguage, line, 0, 1, 2); pRightToLeftLanguage->setToolTip(i18n( "Some languages are read from right to left.\n" "This setting will change the viewer and editor accordingly.")); ++line; topLayout->addStretch(10); } void OptionDialog::setupIntegrationPage() { QFrame* page = new QFrame(); KPageWidgetItem* pageItem = new KPageWidgetItem(page, i18n("Integration")); pageItem->setHeader(i18n("Integration Settings")); pageItem->setIcon(QIcon::fromTheme(QStringLiteral("utilities-terminal"))); addPage(pageItem); QVBoxLayout* topLayout = new QVBoxLayout(page); topLayout->setMargin(5); QGridLayout* gbox = new QGridLayout(); gbox->setColumnStretch(2, 5); topLayout->addLayout(gbox); int line = 0; QLabel* label; label = new QLabel(i18n("Command line options to ignore:"), page); gbox->addWidget(label, line, 0); OptionLineEdit* pIgnorableCmdLineOptions = new OptionLineEdit("-u;-query;-html;-abort", "IgnorableCmdLineOptions", &m_options.m_ignorableCmdLineOptions, page); gbox->addWidget(pIgnorableCmdLineOptions, line, 1, 1, 2); addOptionItem(pIgnorableCmdLineOptions); label->setToolTip(i18n( "List of command line options that should be ignored when KDiff3 is used by other tools.\n" "Several values can be specified if separated via ';'\n" "This will suppress the \"Unknown option\" error.")); ++line; OptionCheckBox* pEscapeKeyQuits = new OptionCheckBox(i18n("Quit also via Escape key"), false, "EscapeKeyQuits", &m_options.m_bEscapeKeyQuits, page); gbox->addWidget(pEscapeKeyQuits, line, 0, 1, 2); addOptionItem(pEscapeKeyQuits); pEscapeKeyQuits->setToolTip(i18n( "Fast method to exit.\n" "For those who are used to using the Escape key.")); ++line; topLayout->addStretch(10); } void OptionDialog::slotEncodingChanged() { if(m_pSameEncoding->isChecked()) { m_pEncodingBComboBox->setEnabled(false); m_pEncodingBComboBox->setCurrentIndex(m_pEncodingAComboBox->currentIndex()); m_pEncodingCComboBox->setEnabled(false); m_pEncodingCComboBox->setCurrentIndex(m_pEncodingAComboBox->currentIndex()); m_pEncodingOutComboBox->setEnabled(false); m_pEncodingOutComboBox->setCurrentIndex(m_pEncodingAComboBox->currentIndex()); m_pEncodingPPComboBox->setEnabled(false); m_pEncodingPPComboBox->setCurrentIndex(m_pEncodingAComboBox->currentIndex()); m_pAutoDetectUnicodeB->setEnabled(false); m_pAutoDetectUnicodeB->setCheckState(m_pAutoDetectUnicodeA->checkState()); m_pAutoDetectUnicodeC->setEnabled(false); m_pAutoDetectUnicodeC->setCheckState(m_pAutoDetectUnicodeA->checkState()); m_pAutoSelectOutEncoding->setEnabled(false); m_pAutoSelectOutEncoding->setCheckState(m_pAutoDetectUnicodeA->checkState()); } else { m_pEncodingBComboBox->setEnabled(true); m_pEncodingCComboBox->setEnabled(true); m_pEncodingOutComboBox->setEnabled(true); m_pEncodingPPComboBox->setEnabled(true); m_pAutoDetectUnicodeB->setEnabled(true); m_pAutoDetectUnicodeC->setEnabled(true); m_pAutoSelectOutEncoding->setEnabled(true); m_pEncodingOutComboBox->setEnabled(m_pAutoSelectOutEncoding->checkState() == Qt::Unchecked); } } void OptionDialog::setupKeysPage() { //QVBox *page = addVBoxPage( i18n("Keys"), i18n("KeyDialog" ), // BarIcon("fonts", KIconLoader::SizeMedium ) ); //QVBoxLayout *topLayout = new QVBoxLayout( page, 0, spacingHint() ); // new KFontChooser( page,"font",false/*onlyFixed*/,QStringList(),false,6 ); //m_pKeyDialog=new KKeyDialog( false, 0 ); //topLayout->addWidget( m_pKeyDialog ); } void OptionDialog::slotOk() { slotApply(); accept(); } /** Copy the values from the widgets to the public variables.*/ void OptionDialog::slotApply() { std::list::iterator i; for(i = m_optionItemList.begin(); i != m_optionItemList.end(); ++i) { (*i)->apply(); } emit applyDone(); #ifdef Q_OS_WIN QString locale = m_options.m_language; if(locale == "Auto" || locale.isEmpty()) locale = QLocale::system().name().left(2); int spacePos = locale.indexOf(' '); if(spacePos > 0) locale = locale.left(spacePos); QSettings settings(QLatin1String("HKEY_CURRENT_USER\\Software\\KDiff3\\diff-ext"), QSettings::NativeFormat); settings.setValue(QLatin1String("Language"), locale); #endif } /** Set the default values in the widgets only, while the public variables remain unchanged. */ void OptionDialog::slotDefault() { int result = KMessageBox::warningContinueCancel(this, i18n("This resets all options. Not only those of the current topic.")); if(result == KMessageBox::Cancel) return; else resetToDefaults(); } void OptionDialog::resetToDefaults() { std::list::iterator i; for(i = m_optionItemList.begin(); i != m_optionItemList.end(); ++i) { (*i)->setToDefault(); } slotEncodingChanged(); } /** Initialise the widgets using the values in the public varibles. */ void OptionDialog::setState() { std::list::iterator i; for(i = m_optionItemList.begin(); i != m_optionItemList.end(); ++i) { (*i)->setToCurrent(); } slotEncodingChanged(); } void OptionDialog::saveOptions(KSharedConfigPtr config) { // No i18n()-Translations here! ConfigValueMap cvm(config->group(KDIFF3_CONFIG_GROUP)); std::list::iterator i; for(i = m_optionItemList.begin(); i != m_optionItemList.end(); ++i) { (*i)->doUnpreserve(); (*i)->write(&cvm); } } void OptionDialog::readOptions(KSharedConfigPtr config) { // No i18n()-Translations here! ConfigValueMap cvm(config->group(KDIFF3_CONFIG_GROUP)); std::list::iterator i; for(i = m_optionItemList.begin(); i != m_optionItemList.end(); ++i) { (*i)->read(&cvm); } setState(); } QString OptionDialog::parseOptions(const QStringList& optionList) { QString result; QStringList::const_iterator i; for(i = optionList.begin(); i != optionList.end(); ++i) { QString s = *i; int pos = s.indexOf('='); if(pos > 0) // seems not to have a tag { QString key = s.left(pos); QString val = s.mid(pos + 1); std::list::iterator j; bool bFound = false; for(j = m_optionItemList.begin(); j != m_optionItemList.end(); ++j) { if((*j)->getSaveName() == key) { (*j)->doPreserve(); ValueMap config; config.writeEntry(key, val); // Write the value as a string and (*j)->read(&config); // use the internal conversion from string to the needed value. bFound = true; break; } } if(!bFound) { result += "No config item named \"" + key + "\"\n"; } } else { result += "No '=' found in \"" + s + "\"\n"; } } return result; } QString OptionDialog::calcOptionHelp() { ValueMap config; std::list::iterator j; for(j = m_optionItemList.begin(); j != m_optionItemList.end(); ++j) { (*j)->write(&config); } return config.getAsString(); } void OptionDialog::slotHistoryMergeRegExpTester() { QPointer dlg=QPointer(new RegExpTester(this, s_autoMergeRegExpToolTip, s_historyStartRegExpToolTip, s_historyEntryStartRegExpToolTip, s_historyEntryStartSortKeyOrderToolTip)); dlg->init(m_pAutoMergeRegExpLineEdit->currentText(), m_pHistoryStartRegExpLineEdit->currentText(), m_pHistoryEntryStartRegExpLineEdit->currentText(), m_pHistorySortKeyOrderLineEdit->currentText()); if(dlg->exec()) { m_pAutoMergeRegExpLineEdit->setEditText(dlg->autoMergeRegExp()); m_pHistoryStartRegExpLineEdit->setEditText(dlg->historyStartRegExp()); m_pHistoryEntryStartRegExpLineEdit->setEditText(dlg->historyEntryStartRegExp()); m_pHistorySortKeyOrderLineEdit->setEditText(dlg->historySortKeyOrder()); } } #include "optiondialog.moc"