File indexing completed on 2023-05-30 11:30:50

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