Commit 8dc85041 authored by Abdel-Rahman Abdel-Rahman's avatar Abdel-Rahman Abdel-Rahman Committed by Michael Pyne
Browse files

Proxify PlaylistSearch

parent c5259a6e
......@@ -205,6 +205,10 @@ feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAG
install(TARGETS juk ${INSTALL_TARGETS_DEFAULT_ARGS} )
if(Qt5Widgets_VERSION VERSION_LESS "5.13.0")
find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS ItemModels)
target_link_libraries(juk KF5::ItemModels)
endif(Qt5Widgets_VERSION VERSION_LESS "5.13.0")
########### install files ###############
......
......@@ -133,16 +133,16 @@ AdvancedSearchDialog::AdvancedSearchDialog(const QString &defaultName,
void AdvancedSearchDialog::accept()
{
m_search.clearPlaylists();
m_search.clearComponents();
m_search->clearPlaylists();
m_search->clearComponents();
m_search.addPlaylist(CollectionList::instance());
m_search->addPlaylist(CollectionList::instance());
for(const auto &searchLine : m_searchLines)
m_search.addComponent(searchLine->searchComponent());
m_search->addComponent(searchLine->searchComponent());
PlaylistSearch::SearchMode m = PlaylistSearch::SearchMode(!m_matchAnyButton->isChecked());
m_search.setSearchMode(m);
m_search->setSearchMode(m);
m_playlistName = m_playlistNameLineEdit->text();
......
......@@ -38,7 +38,7 @@ public:
const PlaylistSearch& defaultSearch = PlaylistSearch(),
QWidget* parent = nullptr);
PlaylistSearch resultSearch() const
PlaylistSearch* resultSearch() const
{
return m_search;
}
......@@ -58,7 +58,7 @@ private:
void updateButtons();
QBoxLayout *m_criteriaLayout;
PlaylistSearch m_search;
PlaylistSearch* m_search;
QString m_playlistName;
QList<SearchLine *> m_searchLines;
QLineEdit *m_playlistNameLineEdit;
......
......@@ -197,7 +197,9 @@ void CoverInfo::applyCoverToWholeAlbum(bool overwriteExistingCovers) const
// Search done, iterate through results.
PlaylistItemList results = search.matchedItems();
PlaylistItemList results;
for(QModelIndex i : search.matchedItems())
results.append(static_cast<PlaylistItem*>(CollectionList::instance()->itemAt(i.row(), i.column())));
PlaylistItemList::ConstIterator it = results.constBegin();
for(; it != results.constEnd(); ++it) {
......
......@@ -298,7 +298,6 @@ void Playlist::saveAs()
void Playlist::updateDeletedItem(PlaylistItem *item)
{
m_members.remove(item->file().absFilePath());
m_search.clearItem(item);
m_history.removeAll(item);
}
......@@ -364,23 +363,24 @@ void Playlist::updateLeftColumn()
}
}
void Playlist::setItemsVisible(const PlaylistItemList &items, bool visible) // static
void Playlist::setItemsVisible(const QModelIndexList &indexes, bool visible) // static
{
m_visibleChanged = true;
foreach(PlaylistItem *playlistItem, items)
playlistItem->setHidden(!visible);
for(QModelIndex index : indexes)
itemFromIndex(index)->setHidden(!visible);
}
void Playlist::setSearch(const PlaylistSearch &s)
void Playlist::setSearch(PlaylistSearch* s)
{
m_search = s;
if(!m_searchEnabled)
return;
setItemsVisible(s.matchedItems(), true);
setItemsVisible(s.unmatchedItems(), false);
for(int row = 0; row < topLevelItemCount(); ++row)
topLevelItem(row)->setHidden(true);
setItemsVisible(s->matchedItems(), true);
TrackSequenceManager::instance()->iterator()->playlistChanged();
}
......@@ -393,11 +393,14 @@ void Playlist::setSearchEnabled(bool enabled)
m_searchEnabled = enabled;
if(enabled) {
setItemsVisible(m_search.matchedItems(), true);
setItemsVisible(m_search.unmatchedItems(), false);
for(int row = 0; row < topLevelItemCount(); ++row)
topLevelItem(row)->setHidden(true);
setItemsVisible(m_search->matchedItems(), true);
}
else
setItemsVisible(items(), true);
for(PlaylistItem* item : items())
item->setHidden(false);
}
// Mostly seems to be for DynamicPlaylist
......@@ -1112,10 +1115,10 @@ void Playlist::refreshAlbum(const QString &artist, const QString &album)
playlists.append(CollectionList::instance());
PlaylistSearch search(playlists, components);
const PlaylistItemList matches = search.matchedItems();
const QModelIndexList matches = search.matchedItems();
foreach(PlaylistItem *item, matches)
item->refresh();
for(QModelIndex index: matches)
static_cast<PlaylistItem*>(itemFromIndex(index))->refresh();
}
void Playlist::hideColumn(int c, bool updateSearch)
......@@ -1273,8 +1276,9 @@ void Playlist::setupItem(PlaylistItem *item)
item->setTrackId(g_trackID);
g_trackID++;
if(!m_search.isEmpty())
item->setHidden(!m_search.checkItem(item));
QModelIndex index = indexFromItem(item);
if(!m_search->isEmpty())
item->setHidden(!m_search->checkItem(&index));
if(topLevelItemCount() <= 2 && !manualResize()) {
slotWeightDirty();
......@@ -1350,6 +1354,8 @@ void Playlist::slotPlayFromBackMenu(QAction *backAction) const
void Playlist::setup()
{
m_search = new PlaylistSearch(this);
setAlternatingRowColors(true);
setRootIsDecorated(false);
setContextMenuPolicy(Qt::CustomContextMenu);
......
......@@ -256,18 +256,18 @@ public:
* Sets the items in the list to be either visible based on the value of
* visible. This is useful for search operations and such.
*/
static void setItemsVisible(const PlaylistItemList &items, bool visible = true);
void setItemsVisible(const QModelIndexList &indexes, bool visible = true);
/**
* Returns the search associated with this list, or an empty search if one
* has not yet been set.
*/
PlaylistSearch search() const { return m_search; }
PlaylistSearch* search() const { return m_search; }
/**
* Set the search associated with this playlist.
*/
void setSearch(const PlaylistSearch &s);
void setSearch(PlaylistSearch* s);
/**
* If the search is disabled then all items will be shown, not just those that
......@@ -678,7 +678,7 @@ private:
bool m_widthsDirty = true;
bool m_applySharedSettings = true;
PlaylistSearch m_search;
PlaylistSearch* m_search;
bool m_searchEnabled = true;
int m_itemsLoading = 0; /// Count of pending file loads outstanding
......
......@@ -244,7 +244,7 @@ void PlaylistCollection::showMore(const QString &artist, const QString &album)
PlaylistSearch search(playlists, components, PlaylistSearch::MatchAll);
if(m_showMorePlaylist)
m_showMorePlaylist->setPlaylistSearch(search);
m_showMorePlaylist->setPlaylistSearch(&search);
else
m_showMorePlaylist = new SearchPlaylist(this, search, i18n("Now Playing"), false, true);
......@@ -455,7 +455,7 @@ void PlaylistCollection::editSearch()
return;
auto searchDialog = new AdvancedSearchDialog(
p->name(), p->playlistSearch(), JuK::JuKInstance());
p->name(), *(p->playlistSearch()), JuK::JuKInstance());
QObject::connect(searchDialog, &QDialog::finished, [searchDialog, p](int result)
{
if (result) {
......@@ -538,7 +538,7 @@ void PlaylistCollection::createSearchPlaylist()
if (result) {
raise(new SearchPlaylist(
this,
searchDialog->resultSearch(),
*searchDialog->resultSearch(),
searchDialog->resultPlaylistName()));
}
searchDialog->deleteLater();
......
......@@ -13,6 +13,15 @@
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <QtGlobal>
#include <algorithm>
#if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)
#include <QConcatenateTablesProxyModel>
#else
#include <KConcatenateRowsProxyModel>
typedef KConcatenateRowsProxyModel QConcatenateTablesProxyModel;
#endif
#include "playlistsearch.h"
#include "playlist.h"
......@@ -26,7 +35,8 @@
// public methods
////////////////////////////////////////////////////////////////////////////////
PlaylistSearch::PlaylistSearch() :
PlaylistSearch::PlaylistSearch(QObject* parent) :
QSortFilterProxyModel(parent),
m_mode(MatchAny)
{
......@@ -35,81 +45,55 @@ PlaylistSearch::PlaylistSearch() :
PlaylistSearch::PlaylistSearch(const PlaylistList &playlists,
const ComponentList &components,
SearchMode mode,
bool searchNow) :
QObject* parent) :
QSortFilterProxyModel(parent),
m_playlists(playlists),
m_components(components),
m_mode(mode)
{
if(searchNow)
search();
QConcatenateTablesProxyModel* const model = new QConcatenateTablesProxyModel(this);
for(Playlist* playlist : playlists)
model->addSourceModel(playlist->model());
setSourceModel(model);
}
void PlaylistSearch::search()
bool PlaylistSearch::checkItem(QModelIndex *item)
{
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.
foreach(Playlist *playlist, m_playlists) {
if(!isEmpty()) {
for(QTreeWidgetItemIterator it(playlist); *it; ++it)
checkItem(static_cast<PlaylistItem *>(*it));
}
else {
m_items += playlist->items();
m_matchedItems += playlist->items();
}
}
return mapFromSource(static_cast<QConcatenateTablesProxyModel*>(sourceModel())->mapFromSource(*item)).isValid();
}
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;
}
}
QModelIndexList PlaylistSearch::matchedItems() const{
QModelIndexList res;
for(int row = 0; row < rowCount(); ++row)
res.append(mapToSource(index(row, 0)));
return res;
}
if(match)
m_matchedItems.append(item);
else
m_unmatchedItems.append(item);
void PlaylistSearch::addPlaylist(Playlist* p)
{
static_cast<QConcatenateTablesProxyModel*>(sourceModel())->addSourceModel(p->model());
m_playlists.append(p);
}
return match;
void PlaylistSearch::clearPlaylists()
{
beginResetModel();
setSourceModel(new QConcatenateTablesProxyModel(this));
endResetModel();
m_playlists.clear();
}
void PlaylistSearch::addComponent(const Component &c)
{
m_components.append(c);
invalidateFilter();
}
void PlaylistSearch::clearComponents()
{
m_components.clear();
invalidateFilter();
}
PlaylistSearch::ComponentList PlaylistSearch::components() const
......@@ -136,11 +120,13 @@ bool PlaylistSearch::isEmpty() const
return true;
}
void PlaylistSearch::clearItem(PlaylistItem *item)
{
m_items.removeAll(item);
m_matchedItems.removeAll(item);
m_unmatchedItems.removeAll(item);
bool PlaylistSearch::filterAcceptsRow(int source_row, const QModelIndex & source_parent) const{
QAbstractItemModel* const model = sourceModel();
auto matcher = [&](Component c){
return c.matches(source_row, source_parent, model);
};
return m_mode == MatchAny? std::any_of(m_components.begin(), m_components.end(), matcher) :
std::all_of(m_components.begin(), m_components.end(), matcher);
}
////////////////////////////////////////////////////////////////////////////////
......@@ -180,55 +166,39 @@ PlaylistSearch::Component::Component(const QRegExp &query, const ColumnList& col
}
bool PlaylistSearch::Component::matches(PlaylistItem *item) const
bool PlaylistSearch::Component::matches(int row, QModelIndex parent, QAbstractItemModel* model) const
{
if((m_re && m_queryRe.isEmpty()) || (!m_re && m_query.isEmpty()))
return false;
if(m_columns.isEmpty()) {
Playlist *p = static_cast<Playlist *>(item->treeWidget());
for(int i = 0; i < p->columnCount(); i++) {
if(!p->isColumnHidden(i))
m_columns.append(i);
}
}
for(ColumnList::Iterator it = m_columns.begin(); it != m_columns.end(); ++it) {
if(m_re) {
if(item->text(*it).contains(m_queryRe))
return true;
else
break;
for(int column : m_columns){
const QString str = model->index(row, column, parent).data().toString();
if(m_re){
return str.contains(m_queryRe);
}
switch(m_mode) {
case Contains:
if(item->text(*it).contains(m_query, m_caseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive))
if(str.contains(m_query, m_caseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive))
return true;
break;
case Exact:
if(item->text(*it).length() == m_query.length()) {
if(str.length() == m_query.length()) {
if(m_caseSensitive) {
if(item->text(*it) == m_query)
if(str == m_query)
return true;
}
else if(item->text(*it).toLower() == m_query.toLower())
else if(str.toLower() == m_query.toLower())
return true;
}
break;
case ContainsWord:
{
QString s = item->text(*it);
int i = s.indexOf(m_query, 0, m_caseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive );
int i = str.indexOf(m_query, 0, m_caseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive );
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())
if(str.length() == m_query.length())
return true;
// First: If the match starts at the beginning of the text or the
......@@ -241,14 +211,13 @@ bool PlaylistSearch::Component::matches(PlaylistItem *item) const
// ...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()))
if((i == 0 || !str.at(i - 1).isLetterOrNumber()) &&
(i + m_query.length() == str.length() || !str.at(i + m_query.length()).isLetterOrNumber()))
return true;
break;
}
}
}
}
};
return false;
}
......
......@@ -19,6 +19,7 @@
#include <QRegExp>
#include <QVector>
#include <QSortFilterProxyModel>
class Playlist;
class PlaylistItem;
......@@ -27,7 +28,7 @@ typedef QVector<int> ColumnList;
typedef QVector<PlaylistItem *> PlaylistItemList;
typedef QVector<Playlist *> PlaylistList;
class PlaylistSearch
class PlaylistSearch : QSortFilterProxyModel
{
public:
class Component;
......@@ -35,21 +36,19 @@ public:
enum SearchMode { MatchAny = 0, MatchAll = 1 };
PlaylistSearch();
PlaylistSearch(QObject* parent = nullptr);
PlaylistSearch(const PlaylistList &playlists,
const ComponentList &components,
SearchMode mode = MatchAny,
bool searchNow = true);
QObject* parent = nullptr);
void search();
bool checkItem(PlaylistItem *item);
bool checkItem(QModelIndex *item);
PlaylistItemList searchedItems() const { return m_items; }
PlaylistItemList matchedItems() const { return m_matchedItems; }
PlaylistItemList unmatchedItems() const { return m_unmatchedItems; }
QModelIndexList matchedItems() const;
void addPlaylist(Playlist *p) { m_playlists.append(p); }
void clearPlaylists() { m_playlists.clear(); }
void addPlaylist(Playlist *p);
void clearPlaylists();
PlaylistList playlists() const { return m_playlists; }
void addComponent(const Component &c);
......@@ -62,6 +61,8 @@ public:
bool isNull() const;
bool isEmpty() const;
bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const override;
/**
* This is used to clear an item from the matched and unmatched lists. This
* is useful because it can prevent keeping a dangling pointer around without
......@@ -70,13 +71,10 @@ public:
void clearItem(PlaylistItem *item);
private:
PlaylistList m_playlists;
ComponentList m_components;
SearchMode m_mode;
PlaylistItemList m_items;
PlaylistItemList m_matchedItems;
PlaylistItemList m_unmatchedItems;
};
/**
......@@ -112,7 +110,7 @@ public:
QRegExp pattern() const { return m_queryRe; }
ColumnList columns() const { return m_columns; }
bool matches(PlaylistItem *item) const;
bool matches(int row, QModelIndex parent, QAbstractItemModel* model) const;
bool isPatternSearch() const { return m_re; }
bool isCaseSensitive() const { return m_caseSensitive; }
MatchMode matchMode() const { return m_mode; }
......
......@@ -297,8 +297,7 @@ void PlaylistSplitter::slotShowSearchResults()
{
PlaylistList playlists;
playlists.append(visiblePlaylist());
PlaylistSearch search = m_searchWidget->search(playlists);
visiblePlaylist()->setSearch(search);
visiblePlaylist()->setSearch(m_searchWidget->search(playlists));
}
void PlaylistSplitter::slotPlaylistSelectionChanged()
......
......@@ -28,22 +28,22 @@
////////////////////////////////////////////////////////////////////////////////
SearchPlaylist::SearchPlaylist(PlaylistCollection *collection,
const PlaylistSearch &search,
const PlaylistSearch& search,
const QString &name,
bool setupPlaylist,
bool synchronizePlaying) :
DynamicPlaylist(search.playlists(), collection, name, "edit-find",
setupPlaylist, synchronizePlaying),
m_search(search)
m_search(&search)
{
}
void SearchPlaylist::setPlaylistSearch(const PlaylistSearch &s, bool update)
void SearchPlaylist::setPlaylistSearch(const PlaylistSearch* s, bool update)
{
m_search = s;
if(update)
setPlaylists(s.playlists());
setPlaylists(s->playlists());
}
////////////////////////////////////////////////////////////////////////////////
......@@ -55,12 +55,14 @@ void SearchPlaylist::updateItems()
// Here we don't simply use "clear" since that would involve a call to
// items() which would in turn call this method...
m_search.search();
synchronizeItemsTo(m_search.matchedItems());
PlaylistItemList items;
for(const QModelIndex index: m_search->matchedItems())
items.push_back(static_cast<PlaylistItem*>(itemFromIndex(index)));
synchronizeItemsTo(items);
if(synchronizePlaying()) {
qCDebug(JUK_LOG) << "synchronizing playing";
synchronizePlayingItems(m_search.playlists(), true);
synchronizePlayingItems(m_search->playlists(), true);
}
}
......@@ -89,7 +91,7 @@ QDataStream &operator>>(QDataStream &s, SearchPlaylist &p)
throw BICStreamException();
p.setName(name);
p.setPlaylistSearch(search, false);
p.setPlaylistSearch(&search, false);
return s;
}
......
......@@ -24,13 +24,13 @@ class SearchPlaylist : public DynamicPlaylist
Q_OBJECT
public:
explicit SearchPlaylist(PlaylistCollection *collection,
const PlaylistSearch &search = PlaylistSearch(),
const PlaylistSearch& search = PlaylistSearch(),
const QString &name = QString(),
bool setupPlaylist = true,
bool synchronizePlaying = false);
PlaylistSearch playlistSearch() const { return m_search; }
void setPlaylistSearch(const PlaylistSearch &s, bool update = true);
const PlaylistSearch* playlistSearch() const { return m_search; }
void setPlaylistSearch ( const PlaylistSearch* s, bool update = true );
virtual bool searchIsEditable() const override { return true; }
protected:
......@@ -40,7 +40,7 @@ protected:
virtual void updateItems() override;
private:
PlaylistSearch m_search;
const PlaylistSearch* m_search;
};
QDataStream &operator<<(QDataStream &s, const SearchPlaylist &p);
......
......@@ -207,9 +207,9 @@ SearchWidget::SearchWidget(QWidget *parent)