File indexing completed on 2024-05-12 05:09:52

0001 /***************************************************************************
0002     Copyright (C) 2008-2020 Robby Stephenson <robby@periapsis.org>
0003  ***************************************************************************/
0004 
0005 /***************************************************************************
0006  *                                                                         *
0007  *   This program is free software; you can redistribute it and/or         *
0008  *   modify it under the terms of the GNU General Public License as        *
0009  *   published by the Free Software Foundation; either version 2 of        *
0010  *   the License or (at your option) version 3 or any later version        *
0011  *   accepted by the membership of KDE e.V. (or its successor approved     *
0012  *   by the membership of KDE e.V.), which shall act as a proxy            *
0013  *   defined in Section 14 of version 3 of the license.                    *
0014  *                                                                         *
0015  *   This program is distributed in the hope that it will be useful,       *
0016  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
0017  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
0018  *   GNU General Public License for more details.                          *
0019  *                                                                         *
0020  *   You should have received a copy of the GNU General Public License     *
0021  *   along with this program.  If not, see <http://www.gnu.org/licenses/>. *
0022  *                                                                         *
0023  ***************************************************************************/
0024 
0025 #include "groupsortmodel.h"
0026 #include "models.h"
0027 #include "stringcomparison.h"
0028 #include "fieldcomparison.h"
0029 #include "../field.h"
0030 #include "../entrygroup.h"
0031 #include "../tellico_debug.h"
0032 
0033 using namespace Tellico;
0034 using Tellico::GroupSortModel;
0035 
0036 GroupSortModel::GroupSortModel(QObject* parent) : AbstractSortModel(parent)
0037      , m_entryComparison(nullptr)
0038      , m_groupComparison(nullptr) {
0039 }
0040 
0041 GroupSortModel::~GroupSortModel() {
0042   clearComparisons();
0043 }
0044 
0045 void GroupSortModel::setSourceModel(QAbstractItemModel* sourceModel_) {
0046   AbstractSortModel::setSourceModel(sourceModel_);
0047   if(sourceModel_) {
0048     clearComparisons();
0049     connect(sourceModel_, &QAbstractItemModel::modelReset,
0050             this, &GroupSortModel::clearComparisons);
0051   }
0052 }
0053 
0054 QString GroupSortModel::entrySortField() const {
0055   return m_entryComparison ? m_entryComparison->field()->name() : QString();
0056 }
0057 
0058 void GroupSortModel::setEntrySortField(const QString& fieldName_) {
0059   // only have to update if the field name is different than existing
0060   if(m_entryComparison && m_entryComparison->field() &&
0061      m_entryComparison->field()->name() == fieldName_) {
0062     return;
0063   }
0064   // can only update the sort field if the model is not empty
0065   QModelIndex groupIndex = index(0, 0);
0066   if(!groupIndex.isValid()) {
0067     return;
0068   }
0069   QModelIndex entryIndex = index(0, 0, groupIndex);
0070   if(!entryIndex.isValid()) {
0071     return;
0072   }
0073   // possible that field name does not exist in this collection
0074   Tellico::FieldComparison* newComp = getEntryComparison(entryIndex, fieldName_);
0075   if(!newComp) {
0076     return;
0077   }
0078   emit layoutAboutToBeChanged(QList<QPersistentModelIndex>(), QAbstractItemModel::VerticalSortHint);
0079   delete m_entryComparison;
0080   m_entryComparison = newComp;
0081   emit layoutChanged(QList<QPersistentModelIndex>(), QAbstractItemModel::VerticalSortHint);
0082   // emitting layoutChanged does not cause the sorting to be refreshed. I can't figure out why
0083   // but calling invalidate() does. <shrug>
0084   invalidate();
0085 }
0086 
0087 bool GroupSortModel::lessThan(const QModelIndex& left_, const QModelIndex& right_) const {
0088   // if the index have parents, then they represent entries, compare by title
0089   // calling index.parent() is expensive for the EntryGroupModel
0090   /// all we really need to know is whether the parent is valid
0091   const bool leftParentValid = sourceModel()->data(left_, ValidParentRole).toBool();
0092   const bool rightParentValid = sourceModel()->data(right_, ValidParentRole).toBool();
0093   const bool reverseOrder = (sortOrder() == Qt::DescendingOrder);
0094   if(!leftParentValid || !rightParentValid) {
0095     // now if we're using count, just pass up the line
0096     if(sortRole() == RowCountRole) {
0097       return AbstractSortModel::lessThan(left_, right_);
0098     }
0099 
0100     // we're dealing with groups
0101     Data::EntryGroup* leftGroup = sourceModel()->data(left_, GroupPtrRole).value<Data::EntryGroup*>();
0102     Data::EntryGroup* rightGroup = sourceModel()->data(right_, GroupPtrRole).value<Data::EntryGroup*>();
0103 
0104     // no matter what the sortRole is, the empty group is always first
0105     const bool emptyLeft = (!leftGroup || leftGroup->hasEmptyGroupName());
0106     const bool emptyRight = (!rightGroup || rightGroup->hasEmptyGroupName());
0107 
0108     // yeah, I should figure out some bit-wise operations...whatever
0109     if(emptyLeft && !emptyRight) {
0110       return reverseOrder ? false : true;
0111     } else if(!emptyLeft && emptyRight) {
0112       return reverseOrder ? true : false;
0113     } else if(emptyLeft && emptyRight) {
0114       return reverseOrder ? true : false;
0115     }
0116 
0117    // if we can get the fields' type, then for certain non-text-only
0118     // types use the sort defined for that type.
0119     if(!m_groupComparison) {
0120       m_groupComparison = getGroupComparison(leftGroup);
0121     }
0122     if(m_groupComparison) {
0123       return m_groupComparison->compare(leftGroup->groupName(), rightGroup->groupName()) < 0;
0124     }
0125     // couldn't determine the type or it's a type we want to sort
0126     // alphabetically, so sort by locale
0127     return left_.data().toString().localeAwareCompare(right_.data().toString()) < 0;
0128   }
0129 
0130   if(!m_entryComparison) {
0131     // default to using the title field for sorting
0132     m_entryComparison = getEntryComparison(left_, QStringLiteral("title"));
0133   }
0134   Q_ASSERT(m_entryComparison);
0135   if(!m_entryComparison) {
0136     return left_.data().toString().localeAwareCompare(right_.data().toString()) < 0;
0137   }
0138   // entries always sort ascending, despite whatever the group order is
0139   const bool res =  m_entryComparison->compare( left_.data(EntryPtrRole).value<Data::EntryPtr>(),
0140                                                 right_.data(EntryPtrRole).value<Data::EntryPtr>()) < 0;
0141   return reverseOrder ? !res : res;
0142 }
0143 
0144 void GroupSortModel::clearComparisons() {
0145   delete m_entryComparison;
0146   m_entryComparison = nullptr;
0147   delete m_groupComparison;
0148   m_groupComparison = nullptr;
0149 }
0150 
0151 Tellico::FieldComparison* GroupSortModel::getEntryComparison(const QModelIndex& index_, const QString& fieldName_) const {
0152   FieldComparison* comp = nullptr;
0153   // depend on the index pointing to an entry from which we can get a collection
0154   Data::EntryPtr entry = index_.data(EntryPtrRole).value<Data::EntryPtr>();
0155   if(entry) {
0156     Data::CollPtr coll = entry->collection();
0157     // by default, always sort by title
0158     if(coll) {
0159       comp = FieldComparison::create(coll->fieldByName(fieldName_));
0160     }
0161   }
0162   return comp;
0163 }
0164 
0165 // if 'group_' contains a type of field that merits a non-alphabetic
0166 // sort, return a pointer to the proper sort function.
0167 Tellico::StringComparison* GroupSortModel::getGroupComparison(Data::EntryGroup* group_) const {
0168   StringComparison* comp = nullptr;
0169   if(group_ && !group_->isEmpty()) {
0170     // depend on the group NOT being empty which allows access to the first entry
0171     Data::CollPtr coll = group_->first()->collection();
0172     if(coll && coll->hasField(group_->fieldName())) {
0173       comp = StringComparison::create(coll->fieldByName(group_->fieldName()));
0174     }
0175   }
0176   return comp;
0177 }