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 }