You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdemultimedia/juk/playlistsearch.cpp

329 lines
8.2 KiB

/***************************************************************************
begin : Sun Mar 6 2003
copyright : (C) 2003 - 2004 by Scott Wheeler
email : 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. *
* *
***************************************************************************/
#include <kdatastream.h>
#include <kdebug.h>
#include "playlistsearch.h"
#include "playlist.h"
#include "playlistitem.h"
#include "collectionlist.h"
////////////////////////////////////////////////////////////////////////////////
// public methods
////////////////////////////////////////////////////////////////////////////////
PlaylistSearch::PlaylistSearch() :
m_mode(MatchAny)
{
}
PlaylistSearch::PlaylistSearch(const PlaylistList &playlists,
const ComponentList &components,
SearchMode mode,
bool searchNow) :
m_playlists(playlists),
m_components(components),
m_mode(mode)
{
if(searchNow)
search();
}
void PlaylistSearch::search()
{
m_items.clear();
m_matchedItems.clear();
m_unmatchedItems.clear();
// This really isn't as bad as it looks. Despite the four nexted loops
// most of the time this will be searching one playlist for one search
// 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.
PlaylistList::Iterator playlistIt = m_playlists.begin();
for(; playlistIt != m_playlists.end(); ++playlistIt) {
if(!isEmpty()) {
for(TQListViewItemIterator it(*playlistIt); it.current(); ++it)
checkItem(static_cast<PlaylistItem *>(*it));
}
else {
m_items += (*playlistIt)->items();
m_matchedItems += (*playlistIt)->items();
}
}
}
bool PlaylistSearch::checkItem(PlaylistItem *item)
{
m_items.append(item);
// set our default
bool match = bool(m_mode);
ComponentList::Iterator componentIt = m_components.begin();
for(; componentIt != m_components.end(); ++componentIt) {
bool componentMatches = (*componentIt).matches(item);
if(componentMatches && m_mode == MatchAny) {
match = true;
break;
}
if(!componentMatches && m_mode == MatchAll) {
match = false;
break;
}
}
if(match)
m_matchedItems.append(item);
else
m_unmatchedItems.append(item);
return match;
}
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();
}
bool PlaylistSearch::isEmpty() const
{
if(isNull())
return true;
ComponentList::ConstIterator it = m_components.begin();
for(; it != m_components.end(); ++it) {
if(!(*it).query().isEmpty() || !(*it).pattern().isEmpty())
return false;
}
return true;
}
void PlaylistSearch::clearItem(PlaylistItem *item)
{
m_items.remove(item);
m_matchedItems.remove(item);
m_unmatchedItems.remove(item);
}
////////////////////////////////////////////////////////////////////////////////
// Component public methods
////////////////////////////////////////////////////////////////////////////////
PlaylistSearch::Component::Component() :
m_mode(Contains),
m_searchAllVisible(true),
m_caseSensitive(false)
{
}
PlaylistSearch::Component::Component(const TQString &query,
bool caseSensitive,
const ColumnList &columns,
MatchMode mode) :
m_query(query),
m_columns(columns),
m_mode(mode),
m_searchAllVisible(columns.isEmpty()),
m_caseSensitive(caseSensitive),
m_re(false)
{
}
PlaylistSearch::Component::Component(const TQRegExp &query, const ColumnList& columns) :
m_queryRe(query),
m_columns(columns),
m_mode(Exact),
m_searchAllVisible(columns.isEmpty()),
m_caseSensitive(false),
m_re(true)
{
}
bool PlaylistSearch::Component::matches(PlaylistItem *item) const
{
if((m_re && m_queryRe.isEmpty()) || (!m_re && m_query.isEmpty()))
return false;
if(m_columns.isEmpty()) {
Playlist *p = static_cast<Playlist *>(item->listView());
for(int i = 0; i < p->columns(); i++) {
if(p->isColumnVisible(i))
m_columns.append(i);
}
}
for(ColumnList::Iterator it = m_columns.begin(); it != m_columns.end(); ++it) {
if(m_re) {
if(item->text(*it).find(m_queryRe) > -1)
return true;
else
break;
}
switch(m_mode) {
case Contains:
if(item->text(*it).find(m_query, 0, m_caseSensitive) > -1)
return true;
break;
case Exact:
if(item->text(*it).length() == m_query.length()) {
if(m_caseSensitive) {
if(item->text(*it) == m_query)
return true;
}
else if(item->text(*it).lower() == m_query.lower())
return true;
}
break;
case ContainsWord:
{
TQString s = item->text(*it);
int i = s.find(m_query, 0, m_caseSensitive);
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;
}
}
}
}
return false;
}
bool PlaylistSearch::Component::operator==(const Component &v) const
{
return m_query == v.m_query &&
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;
}
////////////////////////////////////////////////////////////////////////////////
// helper functions
////////////////////////////////////////////////////////////////////////////////
TQDataStream &operator<<(TQDataStream &s, const PlaylistSearch &search)
{
s << search.components()
<< TQ_INT32(search.searchMode());
return s;
}
TQDataStream &operator>>(TQDataStream &s, PlaylistSearch &search)
{
search.clearPlaylists();
search.addPlaylist(CollectionList::instance());
search.clearComponents();
PlaylistSearch::ComponentList components;
s >> components;
PlaylistSearch::ComponentList::ConstIterator it = components.begin();
for(; it != components.end(); ++it)
search.addComponent(*it);
TQ_INT32 mode;
s >> mode;
search.setSearchMode(PlaylistSearch::SearchMode(mode));
return s;
}
TQDataStream &operator<<(TQDataStream &s, const PlaylistSearch::Component &c)
{
s << c.isPatternSearch();
s << (c.isPatternSearch() ? c.pattern().pattern() : c.query());
s << c.isCaseSensitive();
s << c.columns();
s << TQ_INT32(c.matchMode());
return s;
}
TQDataStream &operator>>(TQDataStream &s, PlaylistSearch::Component &c)
{
bool patternSearch;
TQString pattern;
bool caseSensitive;
ColumnList columns;
TQ_INT32 mode;
s >> patternSearch
>> pattern
>> caseSensitive
>> columns
>> mode;
if(patternSearch)
c = PlaylistSearch::Component(TQRegExp(pattern), columns);
else
c = PlaylistSearch::Component(pattern, caseSensitive, columns, PlaylistSearch::Component::MatchMode(mode));
return s;
}