Add genre filtering to streams pages.

This commit is contained in:
craig
2012-02-09 19:13:04 +00:00
committed by craig
parent 4862e89218
commit 25232ecbea
11 changed files with 186 additions and 39 deletions

View File

@@ -25,8 +25,8 @@
18. Disable playback buttons depending upon size of play queue.
19. When sending track change notification, also include cover (if we have one)
20. Optionally fade-out track when stopping.
21. Add genre filtering to playlists page.
22. When pressing play after pressing stop, start playnig at current song -
21. Add genre filtering to playlists and streams pages.
22. When pressing play after pressing stop, start playing at current song -
don't start at begining of play queue.
23. Better repeat and consume icons.

View File

@@ -36,7 +36,7 @@
#include <QtGui/QPushButton>
#endif
StreamDialog::StreamDialog(const QStringList &categories, QWidget *parent)
StreamDialog::StreamDialog(const QStringList &categories, const QStringList &genres, QWidget *parent)
#ifdef ENABLE_KDE_SUPPORT
: KDialog(parent)
#else
@@ -50,11 +50,14 @@ StreamDialog::StreamDialog(const QStringList &categories, QWidget *parent)
urlEntry = new LineEdit(wid);
catCombo = new CompletionCombo(wid);
catCombo->setEditable(true);
genreCombo = new CompletionCombo(wid);
QSizePolicy sizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
sizePolicy.setHorizontalStretch(0);
sizePolicy.setVerticalStretch(0);
sizePolicy.setHeightForWidth(catCombo->sizePolicy().hasHeightForWidth());
catCombo->setSizePolicy(sizePolicy);
genreCombo->setSizePolicy(sizePolicy);
int row=0;
#ifdef ENABLE_KDE_SUPPORT
@@ -83,6 +86,12 @@ StreamDialog::StreamDialog(const QStringList &categories, QWidget *parent)
#endif
layout->setWidget(row++, QFormLayout::FieldRole, catCombo);
#ifdef ENABLE_KDE_SUPPORT
layout->setWidget(row, QFormLayout::LabelRole, new QLabel(i18n("Genre:"), wid));
#else
layout->setWidget(row, QFormLayout::LabelRole, new QLabel(tr("Genre:"), wid));
#endif
layout->setWidget(row++, QFormLayout::FieldRole, genreCombo);
#ifdef ENABLE_KDE_SUPPORT
setMainWidget(wid);
setButtons(KDialog::Ok|KDialog::Cancel);
setCaption(i18n("Add Stream"));
@@ -99,17 +108,19 @@ StreamDialog::StreamDialog(const QStringList &categories, QWidget *parent)
#endif
catCombo->clear();
catCombo->insertItems(0, categories);
genreCombo->clear();
genreCombo->insertItems(0, genres);
connect(nameEntry, SIGNAL(textChanged(const QString &)), SLOT(changed()));
connect(urlEntry, SIGNAL(textChanged(const QString &)), SLOT(changed()));
connect(catCombo, SIGNAL(editTextChanged(const QString &)), SLOT(changed()));
connect(genreCombo, SIGNAL(editTextChanged(const QString &)), SLOT(changed()));
#ifdef ENABLE_KDE_SUPPORT
connect(iconButton, SIGNAL(clicked()), SLOT(setIcon()));
#endif
nameEntry->setFocus();
}
void StreamDialog::setEdit(const QString &cat, const QString &editName, const QString &editIconName, const QString &editUrl)
void StreamDialog::setEdit(const QString &cat, const QString &editName, const QString &editGenre, const QString &editIconName, const QString &editUrl)
{
#ifdef ENABLE_KDE_SUPPORT
setCaption(i18n("Edit Stream"));
@@ -124,9 +135,11 @@ void StreamDialog::setEdit(const QString &cat, const QString &editName, const QS
prevName=editName;
prevUrl=editUrl;
prevCat=cat;
prevGenre=editGenre;
nameEntry->setText(editName);
urlEntry->setText(editUrl);
catCombo->setEditText(cat);
genreCombo->setEditText(editGenre);
}
void StreamDialog::changed()
@@ -134,7 +147,8 @@ void StreamDialog::changed()
QString n=name();
QString u=url();
QString c=category();
bool enableOk=!n.isEmpty() && !u.isEmpty() && !c.isEmpty() && (n!=prevName || u!=prevUrl || c!=prevCat);
QString g=genre();
bool enableOk=!n.isEmpty() && !u.isEmpty() && !c.isEmpty() && (n!=prevName || u!=prevUrl || c!=prevCat || g!=prevGenre);
#ifdef ENABLE_KDE_SUPPORT
enableOk=enableOk || icon()!=prevIconName;

View File

@@ -44,13 +44,14 @@ class StreamDialog : public QDialog
Q_OBJECT
public:
StreamDialog(const QStringList &categories, QWidget *parent);
StreamDialog(const QStringList &categories, const QStringList &genres, QWidget *parent);
void setEdit(const QString &cat, const QString &editName, const QString &editIconName, const QString &editUrl);
void setEdit(const QString &cat, const QString &editName, const QString &editGenre, const QString &editIconName, const QString &editUrl);
QString name() const { return nameEntry->text().trimmed(); }
QString url() const { return urlEntry->text().trimmed(); }
QString category() const { return catCombo->currentText().trimmed(); }
QString genre() const { return genreCombo->currentText().trimmed(); }
#ifdef ENABLE_KDE_SUPPORT
QString icon() const { return iconName; }
#endif
@@ -70,6 +71,7 @@ private:
QString prevName;
QString prevUrl;
QString prevCat;
QString prevGenre;
#ifdef ENABLE_KDE_SUPPORT
QString prevIconName;
QString iconName;
@@ -80,6 +82,7 @@ private:
LineEdit *nameEntry;
LineEdit *urlEntry;
CompletionCombo *catCombo;
CompletionCombo *genreCombo;
};
#endif

View File

@@ -81,6 +81,8 @@ StreamsPage::StreamsPage(MainWindow *p)
connect(editAction, SIGNAL(triggered(bool)), this, SLOT(edit()));
connect(importAction, SIGNAL(triggered(bool)), this, SLOT(importXml()));
connect(exportAction, SIGNAL(triggered(bool)), this, SLOT(exportXml()));
connect(genreCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(searchItems()));
connect(&model, SIGNAL(genresUpdated(const QSet<QString> &)), SLOT(genresUpdated(const QSet<QString> &)));
importStreams->setAutoRaise(true);
exportStreams->setAutoRaise(true);
addStream->setAutoRaise(true);
@@ -169,6 +171,43 @@ void StreamsPage::itemDoubleClicked(const QModelIndex &index)
addItemsToPlayQueue(indexes);
}
void StreamsPage::genresUpdated(const QSet<QString> &g)
{
if (genreCombo->count() && g==genres) {
return;
}
genres=g;
QStringList entries=g.toList();
qSort(entries);
#ifdef ENABLE_KDE_SUPPORT
entries.prepend(i18n("All Genres"));
#else
entries.prepend(tr("All Genres"));
#endif
QString currentFilter = genreCombo->currentIndex() ? genreCombo->currentText() : QString();
genreCombo->clear();
genreCombo->addItems(entries);
if (0==genres.count()) {
genreCombo->setCurrentIndex(0);
} else {
if (!currentFilter.isEmpty()) {
bool found=false;
for (int i=1; i<genreCombo->count() && !found; ++i) {
if (genreCombo->itemText(i) == currentFilter) {
genreCombo->setCurrentIndex(i);
found=true;
}
}
if (!found) {
genreCombo->setCurrentIndex(0);
}
}
}
}
void StreamsPage::importXml()
{
#ifdef ENABLE_KDE_SUPPORT
@@ -229,12 +268,13 @@ void StreamsPage::exportXml()
void StreamsPage::add()
{
StreamDialog dlg(getCategories(), this);
StreamDialog dlg(getCategories(), getGenres(), this);
if (QDialog::Accepted==dlg.exec()) {
QString name=dlg.name();
QString url=dlg.url();
QString cat=dlg.category();
QString genre=dlg.genre();
QString existing=model.name(cat, url);
if (!existing.isEmpty()) {
@@ -246,7 +286,7 @@ void StreamsPage::add()
return;
}
if (!model.add(cat, name, QString(), url)) {
if (!model.add(cat, name, genre, QString(), url)) {
#ifdef ENABLE_KDE_SUPPORT
KMessageBox::error(this, i18n("A stream named <b>%1</b> already exists!", name));
#else
@@ -327,12 +367,13 @@ void StreamsPage::edit()
return;
}
StreamDialog dlg(getCategories(), this);
StreamDialog dlg(getCategories(), getGenres(), this);
StreamsModel::StreamItem *stream=static_cast<StreamsModel::StreamItem *>(item);
QString url=stream->url.toString();
QString cat=stream->parent->name;
QString genre=stream->genre;
dlg.setEdit(cat, name, icon, url);
dlg.setEdit(cat, name, genre, icon, url);
if (QDialog::Accepted==dlg.exec()) {
QString newName=dlg.name();
@@ -343,6 +384,7 @@ void StreamsPage::edit()
#endif
QString newUrl=dlg.url();
QString newCat=dlg.category();
QString newGenre=dlg.genre();
QString existingNameForUrl=newUrl!=url ? model.name(newCat, newUrl) : QString();
//
if (!existingNameForUrl.isEmpty()) {
@@ -358,7 +400,7 @@ void StreamsPage::edit()
QMessageBox::critical(this, tr("Error"), tr("A stream named <b>%1 (%2)</b> already exists!").arg(newName).arg(newCat));
#endif
} else {
model.editStream(index, cat, newCat, newName, newIcon, newUrl);
model.editStream(index, cat, newCat, newName, newGenre, newIcon, newUrl);
}
}
}
@@ -386,16 +428,25 @@ void StreamsPage::controlActions()
void StreamsPage::searchItems()
{
QString genre=0==genreCombo->currentIndex() ? QString() : genreCombo->currentText();
QString filter=view->searchText().trimmed();
if (filter.isEmpty() ) {
if (filter.isEmpty() && genre.isEmpty()) {
proxy.setFilterEnabled(false);
proxy.setFilterGenre(genre);
if (!proxy.filterRegExp().isEmpty()) {
proxy.setFilterRegExp(QString());
} else {
proxy.invalidate();
}
} else if (filter!=proxy.filterRegExp().pattern()) {
} else {
proxy.setFilterEnabled(true);
proxy.setFilterRegExp(filter);
proxy.setFilterGenre(genre);
if (filter!=proxy.filterRegExp().pattern()) {
proxy.setFilterRegExp(filter);
} else {
proxy.invalidate();
}
}
}
@@ -420,3 +471,10 @@ QStringList StreamsPage::getCategories()
qSort(categories);
return categories;
}
QStringList StreamsPage::getGenres()
{
QStringList g=genres.toList();
qSort(g);
return g;
}

View File

@@ -58,10 +58,12 @@ private Q_SLOTS:
void controlActions();
void searchItems();
void itemDoubleClicked(const QModelIndex &index);
void genresUpdated(const QSet<QString> &g);
private:
void addItemsToPlayQueue(const QModelIndexList &indexes);
QStringList getCategories();
QStringList getGenres();
private:
bool enabled;
@@ -71,6 +73,7 @@ private:
Action *editAction;
StreamsModel model;
StreamsProxyModel proxy;
QSet<QString> genres;
};
#endif

View File

@@ -21,17 +21,7 @@
<widget class="ItemView" name="view"/>
</item>
<item row="1" column="0">
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>130</width>
<height>20</height>
</size>
</property>
</spacer>
<widget class="QComboBox" name="genreCombo"/>
</item>
<item row="1" column="1">
<widget class="QToolButton" name="importStreams"/>

View File

@@ -666,7 +666,9 @@ void PlaylistsModel::PlaylistItem::updateGenres()
{
genres.clear();
foreach (const SongItem *s, songs) {
genres.insert(s->genre);
if (!s->genre.isEmpty()) {
genres.insert(s->genre);
}
}
}

View File

@@ -238,6 +238,7 @@ bool StreamsModel::load(const QString &filename, bool isInternal)
if (stream.hasAttribute("name") && stream.hasAttribute("url")) {
QString name=stream.attribute("name");
QString icon=stream.attribute("icon");
QString genre=stream.attribute("genre");
QString origName=name;
QUrl url=QUrl(stream.attribute("url"));
@@ -254,7 +255,7 @@ bool StreamsModel::load(const QString &filename, bool isInternal)
if (!isInternal) {
beginInsertRows(createIndex(items.indexOf(cat), 0, cat), cat->streams.count(), cat->streams.count());
}
StreamItem *stream=new StreamItem(name, iconIsValid(icon) ? icon : QString(), url, cat);
StreamItem *stream=new StreamItem(name, genre, iconIsValid(icon) ? icon : QString(), url, cat);
cat->itemMap.insert(url.toString(), stream);
cat->streams.append(stream);
if (!isInternal) {
@@ -270,6 +271,9 @@ bool StreamsModel::load(const QString &filename, bool isInternal)
}
}
if (haveInserted) {
updateGenres();
}
if (haveInserted && !isInternal) {
modified=true;
save();
@@ -306,7 +310,10 @@ bool StreamsModel::save(const QString &filename, const QModelIndexList &selectio
stream.setAttribute("name", s->name);
stream.setAttribute("url", s->url.toString());
if (!s->icon.isEmpty() && s->icon!=constDefaultStreamIcon) {
stream.setAttribute("icon", c->icon);
stream.setAttribute("icon", s->icon);
}
if (!s->genre.isEmpty()) {
stream.setAttribute("genre", s->genre);
}
cat.appendChild(stream);
}
@@ -317,7 +324,7 @@ bool StreamsModel::save(const QString &filename, const QModelIndexList &selectio
return true;
}
bool StreamsModel::add(const QString &cat, const QString &name, const QString &icon, const QString &url)
bool StreamsModel::add(const QString &cat, const QString &name, const QString &genre, const QString &icon, const QString &url)
{
QUrl u(url);
CategoryItem *c=getCategory(cat, true, true);
@@ -327,7 +334,7 @@ bool StreamsModel::add(const QString &cat, const QString &name, const QString &i
}
beginInsertRows(createIndex(items.indexOf(c), 0, c), c->streams.count(), c->streams.count());
StreamItem *stream=new StreamItem(name, icon.isEmpty() || icon==constDefaultStreamIcon ? QString() : icon, QUrl(url), c);
StreamItem *stream=new StreamItem(name, genre, icon.isEmpty() || icon==constDefaultStreamIcon ? QString() : icon, QUrl(url), c);
c->itemMap.insert(url, stream);
c->streams.append(stream);
endInsertRows();
@@ -353,7 +360,7 @@ void StreamsModel::editCategory(const QModelIndex &index, const QString &name, c
}
}
void StreamsModel::editStream(const QModelIndex &index, const QString &oldCat, const QString &newCat, const QString &name, const QString &icon, const QString &url)
void StreamsModel::editStream(const QModelIndex &index, const QString &oldCat, const QString &newCat, const QString &name, const QString &genre, const QString &icon, const QString &url)
{
if (!index.isValid()) {
return;
@@ -366,7 +373,8 @@ void StreamsModel::editStream(const QModelIndex &index, const QString &oldCat, c
}
if (!newCat.isEmpty() && oldCat!=newCat) {
if(add(newCat, name, icon.isEmpty() || icon==constDefaultStreamIcon ? QString() : icon, url)) {
if(add(newCat, name, genre, icon.isEmpty() || icon==constDefaultStreamIcon ? QString() : icon, url)) {
updateGenres();
remove(index);
}
return;
@@ -384,6 +392,10 @@ void StreamsModel::editStream(const QModelIndex &index, const QString &oldCat, c
cat->itemMap.remove(oldUrl);
cat->itemMap.insert(url, stream);
}
if (stream->genre!=genre) {
stream->genre=genre;
updateGenres();
}
emit dataChanged(index, index);
modified=true;
save();
@@ -406,6 +418,7 @@ void StreamsModel::remove(const QModelIndex &index)
delete old;
endRemoveRows();
modified=true;
updateGenres();
}
} else {
StreamItem *stream=static_cast<StreamItem *>(item);
@@ -425,6 +438,7 @@ void StreamsModel::remove(const QModelIndex &index)
cat->itemMap.remove(old->url.toString());
endRemoveRows();
delete old;
updateGenres();
}
modified=true;
}
@@ -528,6 +542,22 @@ void StreamsModel::persist()
}
}
void StreamsModel::updateGenres()
{
QSet<QString> genres;
foreach (CategoryItem *c, items) {
c->genres.clear();
foreach (const StreamItem *s, c->streams) {
if (!s->genre.isEmpty()) {
c->genres.insert(s->genre);
genres.insert(s->genre);
}
}
}
emit genresUpdated(genres);
}
void StreamsModel::clearCategories()
{
qDeleteAll(items);

View File

@@ -27,6 +27,7 @@
#include <QtCore/QList>
#include <QtCore/QUrl>
#include <QtCore/QHash>
#include <QtCore/QSet>
class QTimer;
@@ -51,8 +52,9 @@ public:
struct CategoryItem;
struct StreamItem : public Item
{
StreamItem(const QString &n, const QString &i, const QUrl &u, CategoryItem *p=0) : Item(n, i), url(u), parent(p) { }
StreamItem(const QString &n, const QString &g, const QString &i, const QUrl &u, CategoryItem *p=0) : Item(n, i), genre(g), url(u), parent(p) { }
bool isCategory() { return false; }
QString genre;
QUrl url;
CategoryItem *parent;
};
@@ -65,6 +67,7 @@ public:
void clearStreams();
QHash<QString, StreamItem *> itemMap;
QList<StreamItem *> streams;
QSet<QString> genres;
};
StreamsModel();
@@ -80,9 +83,9 @@ public:
void save(bool force=false);
bool save(const QString &filename, const QModelIndexList &selection=QModelIndexList());
bool import(const QString &filename) { return load(filename, false); }
bool add(const QString &cat, const QString &name, const QString &icon, const QString &url);
bool add(const QString &cat, const QString &name, const QString &genre, const QString &icon, const QString &url);
void editCategory(const QModelIndex &index, const QString &name, const QString &icon);
void editStream(const QModelIndex &index, const QString &oldCat, const QString &newCat, const QString &name, const QString &icon, const QString &url);
void editStream(const QModelIndex &index, const QString &oldCat, const QString &newCat, const QString &name, const QString &genre, const QString &icon, const QString &url);
void remove(const QModelIndex &index);
QString name(const QString &cat, const QString &url) { return name(getCategory(cat), url); }
bool entryExists(const QString &cat, const QString &name, const QUrl &url=QUrl()) { return entryExists(getCategory(cat), name, url); }
@@ -91,7 +94,11 @@ public:
QMimeData * mimeData(const QModelIndexList &indexes) const;
void mark(const QList<int> &rows, bool f);
Q_SIGNALS:
void genresUpdated(const QSet<QString> &genres);
private:
void updateGenres();
void clearCategories();
bool load(const QString &filename, bool isInternal);
CategoryItem * getCategory(const QString &name, bool create=false, bool signal=false);

View File

@@ -33,13 +33,49 @@ StreamsProxyModel::StreamsProxyModel(QObject *parent)
sort(0);
}
void StreamsProxyModel::setFilterGenre(const QString &genre)
{
if (filterGenre!=genre) {
invalidate();
}
filterGenre=genre;
}
bool StreamsProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
{
if (filterRegExp().isEmpty()) {
if (filterGenre.isEmpty() && filterRegExp().isEmpty()) {
return true;
}
if (!isChildOfRoot(sourceParent)) {
return true;
}
return QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent);
const QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
StreamsModel::Item *item = static_cast<StreamsModel::Item *>(index.internalPointer());
if (item->isCategory()) {
StreamsModel::CategoryItem *cat = static_cast<StreamsModel::CategoryItem *>(item);
if (!filterGenre.isEmpty() && !cat->genres.contains(filterGenre)) {
return false;
}
if (cat->name.contains(filterRegExp())) {
return true;
}
foreach (StreamsModel::StreamItem *s, cat->streams) {
if (s->name.contains(filterRegExp())) {
return true;
}
}
} else {
StreamsModel::StreamItem *s = static_cast<StreamsModel::StreamItem *>(item);
if (!filterGenre.isEmpty() && s->genre!=filterGenre) {
return false;
}
return s->name.contains(filterRegExp());
}
return false;
}

View File

@@ -30,7 +30,11 @@ class StreamsProxyModel : public ProxyModel
{
public:
StreamsProxyModel(QObject *parent = 0);
void setFilterGenre(const QString &genre);
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const;
private:
QString filterGenre;
};
#endif