File indexing completed on 2024-05-05 17:19:05
0001 /*************************************************************************** 0002 * SPDX-FileCopyrightText: 2022 S. MANKOWSKI stephane@mankowski.fr 0003 * SPDX-FileCopyrightText: 2022 G. DE BURE support@mankowski.fr 0004 * SPDX-License-Identifier: GPL-3.0-or-later 0005 ***************************************************************************/ 0006 /** @file 0007 * This file is a proxy model with better filter mechanism. 0008 * 0009 * @author Stephane MANKOWSKI / Guillaume DE BURE 0010 */ 0011 #include "skgsortfilterproxymodel.h" 0012 0013 #include <qregularexpression.h> 0014 0015 #include "skgobjectmodelbase.h" 0016 #include "skgservices.h" 0017 #include "skgtraces.h" 0018 0019 /** 0020 * This private class of SKGObjectBase 0021 */ 0022 class SKGSortFilterProxyModelPrivate 0023 { 0024 public: 0025 /** 0026 * the previous sort column 0027 */ 0028 int m_previous; 0029 0030 /** 0031 * the current sort column 0032 */ 0033 int m_current; 0034 0035 /** 0036 * to know if we are in a sub sort 0037 */ 0038 bool m_insubsort; 0039 }; 0040 0041 0042 SKGSortFilterProxyModel::SKGSortFilterProxyModel(QObject* iParent) 0043 : QSortFilterProxyModel(iParent), d(new SKGSortFilterProxyModelPrivate) 0044 { 0045 _SKGTRACEINFUNC(10) 0046 0047 setSortCaseSensitivity(Qt::CaseInsensitive); 0048 setSortLocaleAware(true); 0049 setFilterKeyColumn(0); 0050 0051 d->m_previous = -1; 0052 d->m_current = -1; 0053 d->m_insubsort = false; 0054 } 0055 0056 SKGSortFilterProxyModel::~SKGSortFilterProxyModel() 0057 { 0058 _SKGTRACEINFUNC(10) 0059 delete d; 0060 } 0061 0062 int SKGSortFilterProxyModel::getPreviousSortColumn() const 0063 { 0064 return d->m_previous; 0065 } 0066 0067 void SKGSortFilterProxyModel::setPreviousSortColumn(int iCol) 0068 { 0069 if (d->m_previous != iCol) { 0070 d->m_previous = iCol; 0071 Q_EMIT previousSortColumnModified(); 0072 } 0073 } 0074 0075 bool SKGSortFilterProxyModel::lessThan(const QModelIndex& left, 0076 const QModelIndex& right) const 0077 { 0078 // Get source 0079 auto* model = qobject_cast<SKGObjectModelBase*>(this->sourceModel()); 0080 if ((model != nullptr) && (d != nullptr)) { 0081 // Set previous column 0082 if (!d->m_insubsort) { 0083 int newCol = left.column(); 0084 if (newCol != d->m_current && d->m_current != -1) { 0085 d->m_previous = d->m_current; 0086 } 0087 0088 d->m_current = newCol; 0089 } 0090 0091 // Special sort order 0092 if (d->m_current != -1) { 0093 auto columname = model->getAttribute(d->m_current); 0094 if (columname.startsWith(QStringLiteral("f_BALANCE"))) { 0095 auto d_date_index = model->getIndexAttribute(QStringLiteral("d_date")); 0096 if (d_date_index != -1) { 0097 d->m_current = d_date_index; 0098 d->m_previous = -1; 0099 SKGTRACEL(5) << "SKGSortFilterProxyModel::lessThan-Special sort order on " << columname << SKGENDL; 0100 } 0101 } 0102 } 0103 0104 // Comparison 0105 QVariant leftData = model->data(model->index(left.row(), d->m_current, left.parent()), Qt::UserRole); 0106 QVariant rightData = model->data(model->index(right.row(), d->m_current, right.parent()), Qt::UserRole); 0107 SKGObjectBase* leftObj = model->getObjectPointer(left); 0108 if (leftData == rightData && (leftObj != nullptr)) { 0109 if (leftObj->getTable().isEmpty() && d->m_current != 0 && !d->m_insubsort) { 0110 // Groups 0111 d->m_insubsort = true; 0112 bool test = SKGSortFilterProxyModel::lessThan(model->index(left.row(), 0), model->index(right.row(), 0)); 0113 d->m_insubsort = false; 0114 return test; 0115 } 0116 // Compare on previous column 0117 if (!d->m_insubsort && d->m_previous != -1 && !d->m_insubsort) { 0118 d->m_insubsort = true; 0119 auto vl = model->index(left.row(), d->m_previous, left.parent()); 0120 auto vr = model->index(right.row(), d->m_previous, right.parent()); 0121 bool test = QSortFilterProxyModel::lessThan(vl, vr); 0122 d->m_insubsort = false; 0123 return test; 0124 } 0125 0126 // Compare on ID for stability 0127 SKGObjectBase* rightObj = model->getObjectPointer(right); 0128 return ((rightObj != nullptr) && leftObj->getID() < rightObj->getID()); 0129 } 0130 // For better performances, and avoid too many calls to QAbstractItemModel::data 0131 // return QSortFilterProxyModel::lessThan(left, right); 0132 return lessThan(leftData, rightData); 0133 } 0134 return false; 0135 } 0136 0137 bool SKGSortFilterProxyModel::lessThan(const QVariant& iLeftData, const QVariant& iRightData) const 0138 { 0139 switch (iLeftData.userType()) { 0140 case QVariant::Invalid: 0141 return (iRightData.type() != QVariant::Invalid); 0142 case QVariant::Int: 0143 return iLeftData.toInt() < iRightData.toInt(); 0144 case QVariant::UInt: 0145 return iLeftData.toUInt() < iRightData.toUInt(); 0146 case QVariant::LongLong: 0147 return iLeftData.toLongLong() < iRightData.toLongLong(); 0148 case QVariant::ULongLong: 0149 return iLeftData.toULongLong() < iRightData.toULongLong(); 0150 case QMetaType::Float: 0151 return iLeftData.toFloat() < iRightData.toFloat(); 0152 case QVariant::Double: 0153 return iLeftData.toDouble() < iRightData.toDouble(); 0154 case QVariant::Char: 0155 return iLeftData.toChar() < iRightData.toChar(); 0156 case QVariant::Date: 0157 return iLeftData.toDate() < iRightData.toDate(); 0158 case QVariant::Time: 0159 return iLeftData.toTime() < iRightData.toTime(); 0160 case QVariant::DateTime: 0161 return iLeftData.toDateTime() < iRightData.toDateTime(); 0162 case QVariant::String: 0163 default: 0164 if (this->isSortLocaleAware()) { 0165 return iLeftData.toString().localeAwareCompare(iRightData.toString()) < 0; 0166 } else { 0167 return iLeftData.toString().compare(iRightData.toString(), this->sortCaseSensitivity()) < 0; 0168 } 0169 } 0170 } 0171 0172 bool SKGSortFilterProxyModel::moreThan(const QVariant& iLeftData, const QVariant& iRightData) const 0173 { 0174 switch (iLeftData.userType()) { 0175 case QVariant::Invalid: 0176 return (iRightData.type() != QVariant::Invalid); 0177 case QVariant::Int: 0178 return iLeftData.toInt() > iRightData.toInt(); 0179 case QVariant::UInt: 0180 return iLeftData.toUInt() > iRightData.toUInt(); 0181 case QVariant::LongLong: 0182 return iLeftData.toLongLong() > iRightData.toLongLong(); 0183 case QVariant::ULongLong: 0184 return iLeftData.toULongLong() > iRightData.toULongLong(); 0185 case QMetaType::Float: 0186 return iLeftData.toFloat() > iRightData.toFloat(); 0187 case QVariant::Double: 0188 return iLeftData.toDouble() > iRightData.toDouble(); 0189 case QVariant::Char: 0190 return iLeftData.toChar() > iRightData.toChar(); 0191 case QVariant::Date: 0192 return iLeftData.toDate() > iRightData.toDate(); 0193 case QVariant::Time: 0194 return iLeftData.toTime() > iRightData.toTime(); 0195 case QVariant::DateTime: 0196 return iLeftData.toDateTime() > iRightData.toDateTime(); 0197 case QVariant::String: 0198 default: 0199 if (this->isSortLocaleAware()) { 0200 return iLeftData.toString().localeAwareCompare(iRightData.toString()) > 0; 0201 } else { 0202 return iLeftData.toString().compare(iRightData.toString(), this->sortCaseSensitivity()) > 0; 0203 } 0204 } 0205 } 0206 0207 bool SKGSortFilterProxyModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const 0208 { 0209 _SKGTRACEINFUNC(10) 0210 0211 // Initialisation 0212 bool output = (filterRegExp().isEmpty()); 0213 if (!output) { 0214 // Build list of criterias 0215 SKGServices::SKGSearchCriteriaList criterias = SKGServices::stringToSearchCriterias(filterRegExp().pattern()); 0216 0217 // Check if at least one group validate the line 0218 int nbList = criterias.count(); 0219 output = false; 0220 for (int i = 0; i < nbList; ++i) { 0221 QChar mode = criterias.at(i).mode; 0222 0223 bool validateAllWords = filterAcceptsRowWords(source_row, source_parent, criterias.at(i).words); 0224 if (mode == '+') { 0225 output |= validateAllWords; 0226 } else if (mode == '-' && validateAllWords) { 0227 output = false; 0228 } 0229 } 0230 0231 if (!output) { 0232 QAbstractItemModel* model = this->sourceModel(); 0233 if (model != nullptr) { 0234 QModelIndex index0 = model->index(source_row, 0, source_parent); 0235 0236 int nb = model->rowCount(index0); 0237 for (int i = 0; !output && i < nb; ++i) { 0238 output = filterAcceptsRow(i, index0); 0239 } 0240 } 0241 } 0242 } 0243 return output; 0244 } 0245 0246 bool SKGSortFilterProxyModel::filterAcceptsRowWords(int source_row, const QModelIndex& source_parent, const QStringList& iWords) const 0247 { 0248 _SKGTRACEINFUNC(10) 0249 0250 // Initialisation 0251 bool output = true; 0252 0253 // Get source 0254 QAbstractItemModel* model = this->sourceModel(); 0255 if (model != nullptr) { 0256 int nbwords = iWords.count(); 0257 for (int w = 0; output && w < nbwords; ++w) { 0258 QString word = iWords.at(w).toLower(); 0259 QString att; 0260 QString op(':'); 0261 bool modeStartWith = true; 0262 0263 int pos = word.indexOf(QStringLiteral(":")); 0264 int pos2 = word.indexOf(QStringLiteral("<=")); 0265 int pos3 = word.indexOf(QStringLiteral(">=")); 0266 int pos4 = word.indexOf(QStringLiteral("=")); 0267 int pos5 = word.indexOf(QStringLiteral("<")); 0268 int pos6 = word.indexOf(QStringLiteral(">")); 0269 int pos7 = word.indexOf(QStringLiteral("#")); 0270 int opLength = 1; 0271 if (pos2 != -1 && (pos2 < pos || pos == -1)) { 0272 pos = pos2; 0273 opLength = 2; 0274 } 0275 if (pos3 != -1 && (pos3 < pos || pos == -1)) { 0276 pos = pos3; 0277 opLength = 2; 0278 } 0279 if (pos4 != -1 && (pos4 < pos || pos == -1)) { 0280 pos = pos4; 0281 } 0282 if (pos5 != -1 && (pos5 < pos || pos == -1)) { 0283 pos = pos5; 0284 } 0285 if (pos6 != -1 && (pos6 < pos || pos == -1)) { 0286 pos = pos6; 0287 } 0288 if (pos7 != -1 && (pos7 < pos || pos == -1)) { 0289 pos = pos7; 0290 } 0291 0292 if (pos != -1) { 0293 att = word.left(pos); 0294 if (att.endsWith(QStringLiteral("."))) { 0295 modeStartWith = false; 0296 att = att.left(att.count() - 1); 0297 } 0298 op = word.mid(pos, opLength); 0299 word = word.right(word.count() - pos - op.count()); 0300 } 0301 0302 // Is this word validated by at least one column ? 0303 output = false; 0304 int nbcol = model->columnCount(); 0305 for (int i = 0; !output && i < nbcol; ++i) { 0306 QModelIndex index0 = model->index(source_row, i, source_parent); 0307 if (index0.isValid()) { 0308 // Check if the header validates the attribute 0309 if (att.isEmpty() || 0310 (modeStartWith && model->headerData(i, Qt::Horizontal).toString().startsWith(att, Qt::CaseInsensitive)) || 0311 (!modeStartWith && model->headerData(i, Qt::Horizontal).toString().compare(att, Qt::CaseInsensitive) == 0)) { 0312 // Check if the value validate the attribute 0313 if (op == QStringLiteral(":")) { 0314 output = model->data(index0).toString().contains(word, Qt::CaseInsensitive); 0315 if (!output) { 0316 output = model->data(index0, Qt::UserRole).toString().contains(word, Qt::CaseInsensitive); 0317 } 0318 } else if (op == QStringLiteral("<")) { 0319 QVariant var = model->data(index0, Qt::UserRole); 0320 output = lessThan(var, QVariant(word)); 0321 } else if (op == QStringLiteral(">")) { 0322 QVariant var = model->data(index0, Qt::UserRole); 0323 output = moreThan(var, QVariant(word)); 0324 } else if (op == QStringLiteral("<=")) { 0325 QVariant var = model->data(index0, Qt::UserRole); 0326 output = (var == QVariant(word)) || lessThan(var, QVariant(word)); 0327 } else if (op == QStringLiteral(">=")) { 0328 QVariant var = model->data(index0, Qt::UserRole); 0329 output = (var == QVariant(word)) || moreThan(var, QVariant(word)); 0330 } else if (op == QStringLiteral("=")) { 0331 QVariant var = model->data(index0, Qt::UserRole); 0332 output = (var == QVariant(word)); 0333 } else if (op == QStringLiteral("#")) { 0334 QVariant var = model->data(index0, Qt::UserRole); 0335 QRegularExpression pattern(QRegularExpression::anchoredPattern(word), QRegularExpression::CaseInsensitiveOption); 0336 output = pattern.match(var.toString()).hasMatch(); 0337 } 0338 } 0339 } 0340 } 0341 } 0342 } 0343 return output; 0344 }