File indexing completed on 2024-04-21 16:30:28

0001 // SPDX-FileCopyrightText: 2003 Scott Wheeler <wheeler@kde.org>
0002 // SPDX-FileCopyrightText: 2004 Max Howell <max.howell@methylblue.com>
0003 // SPDX-FileCopyrightText: 2004 Mark Kretschmann <markey@web.de>
0004 // SPDX-FileCopyrightText: 2008 Seb Ruiz <ruiz@kde.org>
0005 // SPDX-FileCopyrightText: 2008 Sebastian Trueg <trueg@kde.org>
0006 // SPDX-FileCopyrightText: 2020 Simon Persson <simon.persson@mykolab.com>
0007 //
0008 // SPDX-License-Identifier: GPL-2.0-only
0009 
0010 #include "folderselectionmodel.h"
0011 #include "kuputils.h"
0012 
0013 #include <QBrush>
0014 #include <QColor>
0015 #include <QDir>
0016 #include <QFileInfo>
0017 #include <QIcon>
0018 #include <QPalette>
0019 
0020 #include <KLocalizedString>
0021 
0022 namespace {
0023 
0024 bool setContainsSubdir(const QSet<QString> &pSet, const QString &pParentDir) {
0025     // we need the trailing slash to be able to use the startsWith() function to check for parent dirs.
0026     QString lPathWithSlash = pParentDir;
0027     ensureTrailingSlash(lPathWithSlash);
0028     foreach(QString lTestedPath, pSet) {
0029         if(lTestedPath.startsWith(lPathWithSlash)) {
0030             return true;
0031         }
0032     }
0033     return false;
0034 }
0035 
0036 }
0037 
0038 
0039 FolderSelectionModel::FolderSelectionModel(bool pHiddenFoldersVisible, QObject *pParent)
0040    : QFileSystemModel(pParent)
0041 {
0042     setHiddenFoldersVisible(pHiddenFoldersVisible);
0043 }
0044 
0045 Qt::ItemFlags FolderSelectionModel::flags(const QModelIndex &pIndex) const {
0046     Qt::ItemFlags lFlags = QFileSystemModel::flags(pIndex);
0047     lFlags |= Qt::ItemIsUserCheckable;
0048     return lFlags;
0049 }
0050 
0051 QVariant FolderSelectionModel::data(const QModelIndex& pIndex, int pRole) const {
0052     if(!pIndex.isValid() || pIndex.column() != 0) {
0053         return QFileSystemModel::data(pIndex, pRole);
0054     }
0055     const QString lPath = filePath(pIndex);
0056     const InclusionState lState = inclusionState(lPath);
0057     switch(pRole) {
0058     case Qt::CheckStateRole: {
0059         switch(lState) {
0060         case StateIncluded:
0061         case StateIncludeInherited:
0062             if(setContainsSubdir(mExcludedPaths, lPath)) {
0063                 return Qt::PartiallyChecked;
0064             }
0065             return Qt::Checked;
0066         default:
0067             return Qt::Unchecked;
0068         }
0069     }
0070     case IncludeStateRole:
0071         return inclusionState(pIndex);
0072     case Qt::ForegroundRole: {
0073         switch(lState) {
0074         case StateIncluded:
0075         case StateIncludeInherited:
0076             return QVariant::fromValue(QPalette().brush(QPalette::Active, QPalette::Text));
0077         default:
0078             if(setContainsSubdir(mIncludedPaths, lPath)) {
0079                 return QVariant::fromValue(QPalette().brush( QPalette::Active, QPalette::Text));
0080             }
0081             return QVariant::fromValue(QPalette().brush( QPalette::Disabled, QPalette::Text));
0082         }
0083     }
0084     case Qt::ToolTipRole: {
0085         switch(lState) {
0086         case StateIncluded:
0087         case StateIncludeInherited:
0088             if(setContainsSubdir(mExcludedPaths, lPath)) {
0089                 return xi18nc("@info:tooltip %1 is the path of the folder in a listview",
0090                              "<filename>%1</filename><nl/>will be included in the backup, except "
0091                              "for unchecked subfolders", filePath(pIndex));
0092             }
0093             return xi18nc("@info:tooltip %1 is the path of the folder in a listview",
0094                          "<filename>%1</filename><nl/>will be included in the backup", filePath(pIndex));
0095         default:
0096             if(setContainsSubdir(mIncludedPaths, lPath)) {
0097                 return xi18nc("@info:tooltip %1 is the path of the folder in a listview",
0098                              "<filename>%1</filename><nl/> will <emphasis>not</emphasis> be included "
0099                              "in the backup but contains folders that will", filePath(pIndex));
0100             }
0101             return xi18nc("@info:tooltip %1 is the path of the folder in a listview",
0102                          "<filename>%1</filename><nl/> will <emphasis>not</emphasis> be included "
0103                          "in the backup", filePath(pIndex));
0104         }
0105     }
0106     case Qt::DecorationRole:
0107         if(lPath == QDir::homePath()) {
0108             return QIcon::fromTheme(QStringLiteral("user-home"));
0109         }
0110         break;
0111     }
0112 
0113     return QFileSystemModel::data(pIndex, pRole);
0114 }
0115 
0116 bool FolderSelectionModel::setData(const QModelIndex& pIndex, const QVariant& pValue, int pRole) {
0117     if(!pIndex.isValid() || pIndex.column() != 0 || pRole != Qt::CheckStateRole) {
0118         return QFileSystemModel::setData(pIndex, pValue, pRole);
0119     }
0120 
0121     // here we ignore the check value, we treat it as a toggle
0122     // This is due to our using the Qt checking system in a virtual way
0123     const QString lPath = filePath(pIndex);
0124     const InclusionState lState = inclusionState(lPath);
0125     switch(lState) {
0126     case StateIncluded:
0127     case StateIncludeInherited:
0128         excludePath(lPath);
0129         break;
0130     default:
0131         includePath(lPath);
0132     }
0133     QModelIndex lRecurseIndex = pIndex;
0134     while(lRecurseIndex.isValid()) {
0135         emit dataChanged(lRecurseIndex, lRecurseIndex);
0136         lRecurseIndex = lRecurseIndex.parent();
0137     }
0138     return true;
0139 }
0140 
0141 void FolderSelectionModel::includePath(const QString &pPath) {
0142     const InclusionState lState = inclusionState(pPath);
0143     if(lState == StateIncluded) {
0144         return;
0145     }
0146     removeSubDirs(pPath);
0147     if(lState == StateNone || lState == StateExcludeInherited) {
0148         mIncludedPaths.insert(pPath);
0149         emit includedPathAdded(pPath);
0150     }
0151     emit dataChanged(index(pPath), findLastLeaf(index(pPath)));
0152 }
0153 
0154 void FolderSelectionModel::excludePath(const QString& pPath) {
0155     const InclusionState lState = inclusionState(pPath);
0156     if(lState == StateExcluded) {
0157         return;
0158     }
0159     removeSubDirs(pPath);
0160     if(lState == StateIncludeInherited) {
0161         mExcludedPaths.insert(pPath);
0162         emit excludedPathAdded(pPath);
0163     }
0164     emit dataChanged(index(pPath), findLastLeaf(index(pPath)));
0165 }
0166 
0167 void FolderSelectionModel::setIncludedPaths(const QSet<QString> &pIncludedPaths) {
0168     QSet<QString> lRemoved = mIncludedPaths - pIncludedPaths;
0169     QSet<QString> lAdded = pIncludedPaths - mIncludedPaths;
0170     if(lRemoved.count() + lAdded.count() == 0) return;
0171 
0172     beginResetModel();
0173     mIncludedPaths = pIncludedPaths;
0174     foreach(const QString &lRemovedPath, lRemoved) {
0175         emit includedPathRemoved(lRemovedPath);
0176     }
0177     foreach(const QString &lAddedPath, lAdded) {
0178         emit includedPathAdded(lAddedPath);
0179     }
0180     endResetModel();
0181 }
0182 
0183 void FolderSelectionModel::setExcludedPaths(const QSet<QString> &pExcludedPaths) {
0184     QSet<QString> lRemoved = mExcludedPaths - pExcludedPaths;
0185     QSet<QString> lAdded = pExcludedPaths - mExcludedPaths;
0186     if(lRemoved.count() + lAdded.count() == 0)return;
0187 
0188     beginResetModel();
0189     mExcludedPaths = pExcludedPaths;
0190     foreach(const QString &lRemovedPath, lRemoved) {
0191         emit excludedPathRemoved(lRemovedPath);
0192     }
0193     foreach(const QString &lAddedPath, lAdded) {
0194         emit excludedPathAdded(lAddedPath);
0195     }
0196     endResetModel();
0197 }
0198 
0199 QSet<QString> FolderSelectionModel::includedPaths() const {
0200     return mIncludedPaths;
0201 }
0202 
0203 QSet<QString> FolderSelectionModel::excludedPaths() const {
0204     return mExcludedPaths;
0205 }
0206 
0207 FolderSelectionModel::InclusionState FolderSelectionModel::inclusionState(const QModelIndex& pIndex) const {
0208     return inclusionState(filePath(pIndex));
0209 }
0210 
0211 FolderSelectionModel::InclusionState FolderSelectionModel::inclusionState(const QString& pPath) const {
0212     if(mIncludedPaths.contains(pPath)) {
0213         return StateIncluded;
0214     }
0215     if(mExcludedPaths.contains(pPath)) {
0216         return StateExcluded;
0217     }
0218     QString lParent = pPath.section(QDir::separator(), 0, -2, QString::SectionSkipEmpty|QString::SectionIncludeLeadingSep);
0219     if(lParent.isEmpty()) {
0220         return StateNone;
0221     }
0222     InclusionState state = inclusionState(lParent);
0223     if(state == StateNone) {
0224         return StateNone;
0225     }
0226     if(state == StateIncluded || state == StateIncludeInherited) {
0227         return StateIncludeInherited;
0228     }
0229     return StateExcludeInherited;
0230 }
0231 
0232 bool FolderSelectionModel::hiddenFoldersVisible() const {
0233     return filter() & QDir::Hidden;
0234 }
0235 
0236 void FolderSelectionModel::setHiddenFoldersVisible(bool pVisible){
0237     if(pVisible) {
0238         setFilter(QDir::AllDirs | QDir::NoDotAndDotDot | QDir::Hidden);
0239     } else {
0240         setFilter(QDir::AllDirs | QDir::NoDotAndDotDot);
0241     }
0242 }
0243 
0244 QModelIndex FolderSelectionModel::findLastLeaf(const QModelIndex &pIndex) {
0245     QModelIndex lIndex = pIndex;
0246     forever {
0247         int lRowCount = rowCount(lIndex);
0248         if(lRowCount > 0) {
0249             lIndex = index(lRowCount-1, 0, lIndex);
0250         } else {
0251             return lIndex;
0252         }
0253     }
0254 }
0255 
0256 void FolderSelectionModel::removeSubDirs(const QString& pPath) {
0257     QSet<QString>::iterator it = mExcludedPaths.begin();
0258     QString lPath = pPath + QStringLiteral("/");
0259     while(it != mExcludedPaths.end()) {
0260         if(*it == pPath || it->startsWith(lPath)) {
0261             QString lPathCopy = *it;
0262             it = mExcludedPaths.erase(it);
0263             emit excludedPathRemoved(lPathCopy);
0264         } else {
0265             ++it;
0266         }
0267     }
0268     it = mIncludedPaths.begin();
0269     while(it != mIncludedPaths.end()) {
0270         if(*it == pPath || it->startsWith(lPath)) {
0271             QString lPathCopy = *it;
0272             it = mIncludedPaths.erase(it);
0273             emit includedPathRemoved(lPathCopy);
0274         } else {
0275             ++it;
0276         }
0277     }
0278 }
0279