File indexing completed on 2022-09-27 20:25:33

0001 /**
0002  * Copyright (C) 2003-2004 Scott Wheeler <wheeler@kde.org>
0003  *
0004  * This program is free software; you can redistribute it and/or modify it under
0005  * the terms of the GNU General Public License as published by the Free Software
0006  * Foundation; either version 2 of the License, or (at your option) any later
0007  * version.
0008  *
0009  * This program is distributed in the hope that it will be useful, but WITHOUT ANY
0010  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
0011  * PARTICULAR PURPOSE. See the GNU General Public License for more details.
0012  *
0013  * You should have received a copy of the GNU General Public License along with
0014  * this program.  If not, see <http://www.gnu.org/licenses/>.
0015  */
0016 #include <QtGlobal>
0017 
0018 #include <algorithm>
0019 #if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)
0020 #include <QConcatenateTablesProxyModel>
0021 #else
0022 #include <KConcatenateRowsProxyModel>
0023 typedef KConcatenateRowsProxyModel QConcatenateTablesProxyModel;
0024 #endif
0025 
0026 #include "playlistsearch.h"
0027 #include "playlist.h"
0028 #include "playlistitem.h"
0029 #include "collectionlist.h"
0030 #include "juk-exception.h"
0031 
0032 #include "juk_debug.h"
0033 
0034 ////////////////////////////////////////////////////////////////////////////////
0035 // public methods
0036 ////////////////////////////////////////////////////////////////////////////////
0037 
0038 PlaylistSearch::PlaylistSearch(QObject* parent) :
0039     QSortFilterProxyModel(parent),
0040     m_mode(MatchAny)
0041 {
0042 
0043 }
0044 
0045 PlaylistSearch::PlaylistSearch(const PlaylistList &playlists,
0046                                const ComponentList &components,
0047                                SearchMode mode,
0048                                QObject* parent) :
0049     QSortFilterProxyModel(parent),
0050     m_playlists(playlists),
0051     m_components(components),
0052     m_mode(mode)
0053 {
0054     QConcatenateTablesProxyModel* const model = new QConcatenateTablesProxyModel(this);
0055     for(Playlist* playlist : playlists)
0056         model->addSourceModel(playlist->model());
0057     setSourceModel(model);
0058 }
0059 
0060 bool PlaylistSearch::checkItem(QModelIndex *item)
0061 {
0062     return mapFromSource(static_cast<QConcatenateTablesProxyModel*>(sourceModel())->mapFromSource(*item)).isValid();
0063 }
0064 
0065 QModelIndexList PlaylistSearch::matchedItems() const{
0066     QModelIndexList res;
0067     for(int row = 0; row < rowCount(); ++row)
0068         res.append(mapToSource(index(row, 0)));
0069     return res;
0070 }
0071 
0072 void PlaylistSearch::addPlaylist(Playlist* p)
0073 {
0074     static_cast<QConcatenateTablesProxyModel*>(sourceModel())->addSourceModel(p->model());
0075     m_playlists.append(p);
0076 }
0077 
0078 void PlaylistSearch::clearPlaylists()
0079 {
0080     setSourceModel(new QConcatenateTablesProxyModel(this));
0081     m_playlists.clear();
0082 }
0083 
0084 
0085 void PlaylistSearch::addComponent(const Component &c)
0086 {
0087     m_components.append(c);
0088     invalidateFilter();
0089 }
0090 
0091 void PlaylistSearch::clearComponents()
0092 {
0093     m_components.clear();
0094     invalidateFilter();
0095 }
0096 
0097 PlaylistSearch::ComponentList PlaylistSearch::components() const
0098 {
0099     return m_components;
0100 }
0101 
0102 bool PlaylistSearch::isNull() const
0103 {
0104     return m_components.isEmpty();
0105 }
0106 
0107 bool PlaylistSearch::isEmpty() const
0108 {
0109     if(isNull())
0110         return true;
0111 
0112     ComponentList::ConstIterator it = m_components.begin();
0113     for(; it != m_components.end(); ++it) {
0114         if(!(*it).query().isEmpty() || !(*it).pattern().isEmpty())
0115             return false;
0116     }
0117 
0118     return true;
0119 }
0120 
0121 bool PlaylistSearch::filterAcceptsRow(int source_row, const QModelIndex & source_parent) const{
0122     QAbstractItemModel* const model = sourceModel();
0123     auto matcher = [&](Component c){
0124         return c.matches(source_row, source_parent, model);
0125     };
0126     return m_mode == MatchAny? std::any_of(m_components.begin(), m_components.end(), matcher) :
0127         std::all_of(m_components.begin(), m_components.end(), matcher);
0128 }
0129 
0130 ////////////////////////////////////////////////////////////////////////////////
0131 // Component public methods
0132 ////////////////////////////////////////////////////////////////////////////////
0133 
0134 PlaylistSearch::Component::Component() :
0135     m_mode(Contains),
0136     m_searchAllVisible(true),
0137     m_caseSensitive(false)
0138 {
0139 
0140 }
0141 
0142 PlaylistSearch::Component::Component(const QString &query,
0143                                      bool caseSensitive,
0144                                      const ColumnList &columns,
0145                                      MatchMode mode) :
0146     m_query(query),
0147     m_columns(columns),
0148     m_mode(mode),
0149     m_searchAllVisible(columns.isEmpty()),
0150     m_caseSensitive(caseSensitive),
0151     m_re(false)
0152 {
0153 
0154 }
0155 
0156 PlaylistSearch::Component::Component(const QRegExp &query, const ColumnList& columns) :
0157     m_queryRe(query),
0158     m_columns(columns),
0159     m_mode(Exact),
0160     m_searchAllVisible(columns.isEmpty()),
0161     m_caseSensitive(false),
0162     m_re(true)
0163 {
0164 
0165 }
0166 
0167 bool PlaylistSearch::Component::matches(int row, QModelIndex parent, QAbstractItemModel* model) const
0168 {
0169     for(int column : qAsConst(m_columns)) {
0170         const QString str = model->index(row, column, parent).data().toString();
0171         if(m_re) {
0172             return str.contains(m_queryRe);
0173         }
0174 
0175         switch(m_mode) {
0176         case Contains:
0177             if(str.contains(m_query, m_caseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive))
0178                 return true;
0179             break;
0180         case Exact:
0181             // If lengths match, move on to check strings themselves
0182             if(str.length() == m_query.length() &&
0183                (( m_caseSensitive && str == m_query) ||
0184                 (!m_caseSensitive && str.toLower() == m_query.toLower()))
0185                )
0186             {
0187                 return true;
0188             }
0189             break;
0190         case ContainsWord:
0191         {
0192             int i = str.indexOf(m_query, 0, m_caseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive);
0193 
0194             if(i >= 0) {
0195 
0196                 // If we found the pattern and the lengths are the same, then
0197                 // this is a match.
0198 
0199                 if(str.length() == m_query.length())
0200                     return true;
0201 
0202                 // First: If the match starts at the beginning of the text or the
0203                 // character before the match is not a word character
0204 
0205                 // AND
0206 
0207                 // Second: Either the pattern was found at the end of the text,
0208                 // or the text following the match is a non-word character
0209 
0210                 // ...then we have a match
0211 
0212                 if((i == 0 || !str.at(i - 1).isLetterOrNumber()) &&
0213                     (i + m_query.length() == str.length() || !str.at(i + m_query.length()).isLetterOrNumber()))
0214                     return true;
0215             }
0216         }
0217         }
0218     };
0219     return false;
0220 }
0221 
0222 bool PlaylistSearch::Component::operator==(const Component &v) const
0223 {
0224     return m_query == v.m_query &&
0225         m_queryRe == v.m_queryRe &&
0226         m_columns == v.m_columns &&
0227         m_mode == v.m_mode &&
0228         m_searchAllVisible == v.m_searchAllVisible &&
0229         m_caseSensitive == v.m_caseSensitive &&
0230         m_re == v.m_re;
0231 }
0232 
0233 ////////////////////////////////////////////////////////////////////////////////
0234 // helper functions
0235 ////////////////////////////////////////////////////////////////////////////////
0236 
0237 QDataStream &operator<<(QDataStream &s, const PlaylistSearch &search)
0238 {
0239     s << search.components()
0240       << qint32(search.searchMode());
0241 
0242     return s;
0243 }
0244 
0245 QDataStream &operator>>(QDataStream &s, PlaylistSearch &search)
0246 {
0247     search.clearPlaylists();
0248     search.addPlaylist(CollectionList::instance());
0249 
0250     search.clearComponents();
0251     PlaylistSearch::ComponentList components;
0252     s >> components;
0253     PlaylistSearch::ComponentList::ConstIterator it = components.constBegin();
0254     for(; it != components.constEnd(); ++it)
0255         search.addComponent(*it);
0256 
0257     qint32 mode;
0258     s >> mode;
0259     search.setSearchMode(PlaylistSearch::SearchMode(mode));
0260 
0261     return s;
0262 }
0263 
0264 QDataStream &operator<<(QDataStream &s, const PlaylistSearch::Component &c)
0265 {
0266     s << c.isPatternSearch()
0267       << (c.isPatternSearch() ? c.pattern().pattern() : c.query())
0268       << c.isCaseSensitive()
0269       << c.columns()
0270       << qint32(c.matchMode());
0271 
0272     return s;
0273 }
0274 
0275 QDataStream &operator>>(QDataStream &s, PlaylistSearch::Component &c)
0276 {
0277     bool patternSearch;
0278     QString pattern;
0279     bool caseSensitive;
0280     ColumnList columns;
0281     qint32 mode;
0282 
0283     s >> patternSearch
0284       >> pattern
0285       >> caseSensitive
0286       >> columns
0287       >> mode;
0288 
0289     if(patternSearch)
0290         c = PlaylistSearch::Component(QRegExp(pattern), columns);
0291     else
0292         c = PlaylistSearch::Component(pattern, caseSensitive, columns, PlaylistSearch::Component::MatchMode(mode));
0293 
0294     return s;
0295 }
0296 
0297 // vim: set et sw=4 tw=0 sta: