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: