File indexing completed on 2021-12-21 13:27:59
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: