playlistsearch.cpp 8.86 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
/**
 * Copyright (C) 2003-2004 Scott Wheeler <wheeler@kde.org>
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation; either version 2 of the License, or (at your option) any later
 * version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE. See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program.  If not, see <http://www.gnu.org/licenses/>.
 */
16 17

#include "playlistsearch.h"
18 19
#include "playlist.h"
#include "playlistitem.h"
20
#include "collectionlist.h"
21
#include "juk-exception.h"
22

23
#include "juk_debug.h"
24

25 26 27 28
////////////////////////////////////////////////////////////////////////////////
// public methods
////////////////////////////////////////////////////////////////////////////////

29 30 31 32 33 34
PlaylistSearch::PlaylistSearch() :
    m_mode(MatchAny)
{

}

35
PlaylistSearch::PlaylistSearch(const PlaylistList &playlists,
36 37 38
                               const ComponentList &components,
                               SearchMode mode,
                               bool searchNow) :
39
    m_playlists(playlists),
40
    m_components(components),
41 42
    m_mode(mode)
{
43
    if(searchNow)
44
        search();
45 46 47 48
}

void PlaylistSearch::search()
{
49 50 51
    m_items.clear();
    m_matchedItems.clear();
    m_unmatchedItems.clear();
52 53

    // This really isn't as bad as it looks.  Despite the four nexted loops
54
    // most of the time this will be searching one playlist for one search
55 56 57 58 59 60 61
    // component -- possibly for one column.

    // Also there should be some caching of previous searches in here and
    // allowance for appending and removing chars.  If one is added it
    // should only search the current list.  If one is removed it should
    // pop the previous search results off of a stack.

62
    foreach(Playlist *playlist, m_playlists) {
63
        if(!isEmpty()) {
64
            for(QTreeWidgetItemIterator it(playlist); *it; ++it)
65 66 67
                checkItem(static_cast<PlaylistItem *>(*it));
        }
        else {
68 69
            m_items += playlist->items();
            m_matchedItems += playlist->items();
70
        }
71 72
    }
}
73

74 75 76
bool PlaylistSearch::checkItem(PlaylistItem *item)
{
    m_items.append(item);
77

78 79
    // set our default
    bool match = bool(m_mode);
80

81 82
    ComponentList::Iterator componentIt = m_components.begin();
    for(; componentIt != m_components.end(); ++componentIt) {
83

84
        bool componentMatches = (*componentIt).matches(item);
85

86 87 88 89
        if(componentMatches && m_mode == MatchAny) {
            match = true;
            break;
        }
90

91 92 93 94
        if(!componentMatches && m_mode == MatchAll) {
            match = false;
            break;
        }
95
    }
96

97
    if(match)
98
        m_matchedItems.append(item);
99
    else
100
        m_unmatchedItems.append(item);
101

102 103
    return match;
}
104

105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
void PlaylistSearch::addComponent(const Component &c)
{
    m_components.append(c);
}

void PlaylistSearch::clearComponents()
{
    m_components.clear();
}

PlaylistSearch::ComponentList PlaylistSearch::components() const
{
    return m_components;
}

bool PlaylistSearch::isNull() const
{
    return m_components.isEmpty();
}

125 126 127
bool PlaylistSearch::isEmpty() const
{
    if(isNull())
128
        return true;
129 130 131

    ComponentList::ConstIterator it = m_components.begin();
    for(; it != m_components.end(); ++it) {
132 133
        if(!(*it).query().isEmpty() || !(*it).pattern().isEmpty())
            return false;
134
    }
135 136

    return true;
137 138
}

139 140
void PlaylistSearch::clearItem(PlaylistItem *item)
{
141 142 143
    m_items.removeAll(item);
    m_matchedItems.removeAll(item);
    m_unmatchedItems.removeAll(item);
144 145
}

146 147 148 149
////////////////////////////////////////////////////////////////////////////////
// Component public methods
////////////////////////////////////////////////////////////////////////////////

150
PlaylistSearch::Component::Component() :
151
    m_mode(Contains),
152 153 154
    m_searchAllVisible(true),
    m_caseSensitive(false)
{
155

156 157
}

158
PlaylistSearch::Component::Component(const QString &query,
159 160 161
                                     bool caseSensitive,
                                     const ColumnList &columns,
                                     MatchMode mode) :
162 163
    m_query(query),
    m_columns(columns),
164
    m_mode(mode),
165
    m_searchAllVisible(columns.isEmpty()),
166 167
    m_caseSensitive(caseSensitive),
    m_re(false)
168 169 170 171
{

}

172 173 174
PlaylistSearch::Component::Component(const QRegExp &query, const ColumnList& columns) :
    m_queryRe(query),
    m_columns(columns),
175
    m_mode(Exact),
176 177 178 179
    m_searchAllVisible(columns.isEmpty()),
    m_caseSensitive(false),
    m_re(true)
{
180

181 182
}

183
bool PlaylistSearch::Component::matches(PlaylistItem *item) const
184
{
185
    if((m_re && m_queryRe.isEmpty()) || (!m_re && m_query.isEmpty()))
186
        return false;
187 188

    if(m_columns.isEmpty()) {
189 190
        Playlist *p = static_cast<Playlist *>(item->treeWidget());
        for(int i = 0; i < p->columnCount(); i++) {
Kacper Kasper's avatar
Kacper Kasper committed
191
            if(!p->isColumnHidden(i))
192 193
                m_columns.append(i);
        }
194 195 196
    }


197 198
    for(ColumnList::Iterator it = m_columns.begin(); it != m_columns.end(); ++it) {

199
        if(m_re) {
Stephan Kulow's avatar
Stephan Kulow committed
200
            if(item->text(*it).contains(m_queryRe))
201 202 203 204 205 206 207
                return true;
            else
                break;
        }

        switch(m_mode) {
        case Contains:
Stephan Kulow's avatar
Stephan Kulow committed
208
            if(item->text(*it).contains(m_query, m_caseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive))
209 210 211 212 213 214 215 216
                return true;
            break;
        case Exact:
            if(item->text(*it).length() == m_query.length()) {
                if(m_caseSensitive) {
                    if(item->text(*it) == m_query)
                        return true;
                }
Dirk Mueller's avatar
Dirk Mueller committed
217
                else if(item->text(*it).toLower() == m_query.toLower())
218 219 220 221 222 223
                    return true;
            }
            break;
        case ContainsWord:
        {
            QString s = item->text(*it);
Stephan Kulow's avatar
Stephan Kulow committed
224
            int i = s.indexOf(m_query, 0, m_caseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive );
225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250

            if(i >= 0) {

                // If we found the pattern and the lengths are the same, then
                // this is a match.

                if(s.length() == m_query.length())
                    return true;

                // First: If the match starts at the beginning of the text or the
                // character before the match is not a word character

                // AND

                // Second: Either the pattern was found at the end of the text,
                // or the text following the match is a non-word character

                // ...then we have a match

                if((i == 0 || !s.at(i - 1).isLetterOrNumber()) &&
                   (i + m_query.length() == s.length() || !s.at(i + m_query.length()).isLetterOrNumber()))
                    return true;
                break;
            }
        }
        }
251
    }
252
    return false;
253
}
254

255 256 257
bool PlaylistSearch::Component::operator==(const Component &v) const
{
    return m_query == v.m_query &&
258 259 260 261 262 263
        m_queryRe == v.m_queryRe &&
        m_columns == v.m_columns &&
        m_mode == v.m_mode &&
        m_searchAllVisible == v.m_searchAllVisible &&
        m_caseSensitive == v.m_caseSensitive &&
        m_re == v.m_re;
264 265 266 267 268 269
}

////////////////////////////////////////////////////////////////////////////////
// helper functions
////////////////////////////////////////////////////////////////////////////////

270 271 272
QDataStream &operator<<(QDataStream &s, const PlaylistSearch &search)
{
    s << search.components()
Laurent Montel's avatar
Laurent Montel committed
273
      << qint32(search.searchMode());
274 275 276 277 278 279 280 281 282 283 284 285

    return s;
}

QDataStream &operator>>(QDataStream &s, PlaylistSearch &search)
{
    search.clearPlaylists();
    search.addPlaylist(CollectionList::instance());

    search.clearComponents();
    PlaylistSearch::ComponentList components;
    s >> components;
Laurent Montel's avatar
Laurent Montel committed
286 287
    PlaylistSearch::ComponentList::ConstIterator it = components.constBegin();
    for(; it != components.constEnd(); ++it)
288 289
        search.addComponent(*it);

Laurent Montel's avatar
Laurent Montel committed
290
    qint32 mode;
291 292
    s >> mode;
    search.setSearchMode(PlaylistSearch::SearchMode(mode));
293

294 295 296 297 298
    return s;
}

QDataStream &operator<<(QDataStream &s, const PlaylistSearch::Component &c)
{
299 300 301
    s << c.isPatternSearch()
      << (c.isPatternSearch() ? c.pattern().pattern() : c.query())
      << c.isCaseSensitive()
302
      << c.columns()
Laurent Montel's avatar
Laurent Montel committed
303
      << qint32(c.matchMode());
304 305 306 307 308 309 310 311 312 313

    return s;
}

QDataStream &operator>>(QDataStream &s, PlaylistSearch::Component &c)
{
    bool patternSearch;
    QString pattern;
    bool caseSensitive;
    ColumnList columns;
Laurent Montel's avatar
Laurent Montel committed
314
    qint32 mode;
315 316 317 318 319 320 321 322 323 324 325 326 327 328

    s >> patternSearch
      >> pattern
      >> caseSensitive
      >> columns
      >> mode;

    if(patternSearch)
        c = PlaylistSearch::Component(QRegExp(pattern), columns);
    else
        c = PlaylistSearch::Component(pattern, caseSensitive, columns, PlaylistSearch::Component::MatchMode(mode));

    return s;
}
329 330

// vim: set et sw=4 tw=0 sta: