Allow streams to have multiple genres.

This commit is contained in:
craig.p.drummond
2013-02-11 19:33:58 +00:00
parent 9e31dd9b50
commit d92dca64d4
6 changed files with 78 additions and 38 deletions

View File

@@ -85,6 +85,7 @@
delete the item. This helps remobing the entry from the Unity menubar.
49. If all streams (and categories) have been removed, then remove streams xml
file as well.
50. Allow streams to have multiple genres.
0.9.2
-----

View File

@@ -70,12 +70,13 @@ const QLatin1String StreamsModel::constDefaultCategoryIcon("inode-directory");
static const QString constStreamCategoryMimeType("cantata/streams-category");
static const QString constStreamMimeType("cantata/stream");
static const QLatin1String constSeparator("##Cantata##");
const QLatin1String StreamsModel::constGenreSeparator("|");
static QString encodeStreamItem(StreamsModel::StreamItem *i)
{
return i->name.replace(constSeparator, " ")+constSeparator+
i->url.toString()+constSeparator+
i->genre+constSeparator+
i->genreString()+constSeparator+
i->icon+constSeparator+
i->parent->name;
}
@@ -469,8 +470,10 @@ bool StreamsModel::save(const QString &filename, const QSet<StreamsModel::Item *
if (!s->icon.isEmpty() && s->icon!=Icons::streamIcon.name()) {
doc.writeAttribute("icon", s->icon);
}
if (!s->genre.isEmpty() && s->genre!=unknown) {
doc.writeAttribute("genre", s->genre);
QSet<QString> genres=s->genres;
genres.remove(unknown);
if (!genres.isEmpty()) {
doc.writeAttribute("genre", QStringList(genres.toList()).join(constGenreSeparator));
}
doc.writeEndElement();
}
@@ -492,7 +495,7 @@ bool StreamsModel::add(const QString &cat, const QString &name, const QString &g
}
beginInsertRows(createIndex(items.indexOf(c), 0, c), c->streams.count(), c->streams.count());
StreamItem *stream=new StreamItem(name, genre, icon.isEmpty() || icon==Icons::streamIcon.name() ? QString() : icon, QUrl(url), c);
StreamItem *stream=new StreamItem(name, genreSet(genre), icon.isEmpty() || icon==Icons::streamIcon.name() ? QString() : icon, QUrl(url), c);
c->itemMap.insert(url, stream);
c->streams.append(stream);
endInsertRows();
@@ -561,7 +564,6 @@ void StreamsModel::editStream(const QModelIndex &index, const QString &oldCat, c
return;
}
QUrl u(url);
int row=index.row();
if (row<cat->streams.count()) {
@@ -574,8 +576,9 @@ 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;
QSet<QString> genres=genreSet(genre);
if (stream->genres!=genres) {
stream->genres=genres;
updateGenres();
}
emit dataChanged(index, index);
@@ -902,10 +905,8 @@ void StreamsModel::updateGenres()
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);
}
c->genres+=s->genres;
genres+=s->genres;
}
}

View File

@@ -28,6 +28,7 @@
#include <QUrl>
#include <QHash>
#include <QSet>
#include <QStringList>
class QTimer;
class QIODevice;
@@ -40,6 +41,11 @@ class StreamsModel : public QAbstractItemModel
public:
static const QLatin1String constDefaultCategoryIcon;
static QString prefixUrl(const QString &n, bool addPrefix=true);
static QString dir();
static const QLatin1String constGenreSeparator;
static QSet<QString> genreSet(const QString &str) { return str.split(constGenreSeparator, QString::SkipEmptyParts).toSet(); }
struct Item
{
@@ -53,9 +59,11 @@ public:
struct CategoryItem;
struct StreamItem : public Item
{
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) { }
StreamItem(const QString &n, const QString &g, const QString &i, const QUrl &u, CategoryItem *p=0) : Item(n, i), genres(genreSet(g)), url(u), parent(p) { }
StreamItem(const QString &n, const QSet<QString> &g, const QString &i, const QUrl &u, CategoryItem *p=0) : Item(n, i), genres(g), url(u), parent(p) { }
bool isCategory() { return false; }
QString genre;
QString genreString() const { return QStringList(genres.toList()).join(constGenreSeparator); }
QSet<QString> genres;
QUrl url;
CategoryItem *parent;
};
@@ -71,9 +79,6 @@ public:
QSet<QString> genres;
};
static QString prefixUrl(const QString &n, bool addPrefix=true);
static QString dir();
static StreamsModel * self();
StreamsModel();

View File

@@ -63,7 +63,7 @@ bool StreamsProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourc
} else {
StreamsModel::StreamItem *s = static_cast<StreamsModel::StreamItem *>(item);
if (!filterGenre.isEmpty() && s->genre!=filterGenre) {
if (!filterGenre.isEmpty() && !s->genres.contains(filterGenre)) {
return false;
}
return matchesFilter(QStringList() << s->name);

View File

@@ -103,6 +103,7 @@ StreamDialog::StreamDialog(const QStringList &categories, const QStringList &gen
layout->setWidget(row++, QFormLayout::FieldRole, catCombo);
layout->setWidget(row, QFormLayout::LabelRole, new BuddyLabel(i18n("Genre:"), wid, genreCombo));
layout->setWidget(row++, QFormLayout::FieldRole, genreCombo);
layout->setWidget(row++, QFormLayout::SpanningRole, new QLabel(i18n("<i><b>NOTE:</b> Use '|' to split mutliple genres - e.g. 'Current|Classic'</i>"), this));
layout->setWidget(row++, QFormLayout::SpanningRole, statusText);
setCaption(i18n("Add Stream"));
setMainWidget(wid);
@@ -174,7 +175,7 @@ void StreamDialog::changed()
// #endif
);
statusText->setText(validProtocol ? QString() : i18n("<i>Invalid protocol</i>"));
statusText->setText(validProtocol ? QString() : i18n("<i><b>ERROR:</b> Invalid protocol</i>"));
}
enableOk=enableOk && validProtocol;
enableButton(Ok, enableOk);

View File

@@ -68,18 +68,13 @@ static QUrl webStreamUrl(StreamsPage::WebStream type)
}
}
static QString fixGenre(const QString &g)
static QString fixSingleGenre(const QString &g)
{
if (g.length()) {
QString genre=g.toLower();
QString genre=Song::capitalize(g);
genre[0]=genre[0].toUpper();
int pos=genre.indexOf('|');
if (pos>0) {
genre=genre.left(pos);
}
genre=genre.trimmed();
genre=genre.replace(QLatin1String("Afro Caribbean"), QLatin1String("Afrocaribbean"));
if (genre.length() < 3 ||
QLatin1String("The")==genre || QLatin1String("All")==genre ||
QLatin1String("Various")==genre || QLatin1String("Unknown")==genre ||
@@ -89,7 +84,7 @@ static QString fixGenre(const QString &g)
return QString();
}
if (genre==QLatin1String("R b") || genre==QLatin1String("R b")|| genre==QLatin1String("Rnb")) {
if (genre==QLatin1String("R&B") || genre==QLatin1String("R B") || genre==QLatin1String("Rnb") || genre==QLatin1String("RnB")) {
return QLatin1String("R&B");
}
if (genre==QLatin1String("Classic") || genre==QLatin1String("Classical")) {
@@ -104,22 +99,33 @@ static QString fixGenre(const QString &g)
if (genre==QLatin1String("Electronic") || genre==QLatin1String("Electronica") || genre==QLatin1String("Electric")) {
return QLatin1String("Electronic");
}
if (genre==QLatin1String("Easy") || genre==QLatin1String("Easy listening")) {
return QLatin1String("Easy listening");
if (genre==QLatin1String("Easy") || genre==QLatin1String("Easy Listening")) {
return QLatin1String("Easy Listening");
}
if (genre==QLatin1String("Hit") || genre==QLatin1String("Hits") || genre==QLatin1String("Easy listening")) {
return QLatin1String("Hits");
}
if (genre==QLatin1String("Hip") || genre==QLatin1String("Hiphop") || genre==QLatin1String("Hip hop") || genre==QLatin1String("Hop hip")) {
return QLatin1String("Hip hop");
if (genre==QLatin1String("Hip") || genre==QLatin1String("Hiphop") || genre==QLatin1String("Hip Hop") || genre==QLatin1String("Hop Hip")) {
return QLatin1String("Hip Hop");
}
if (genre==QLatin1String("News") || genre==QLatin1String("News talk")) {
return QLatin1String("News");
}
if (genre==QLatin1String("Top40") || genre==QLatin1String("Top 40") || genre==QLatin1String("40top") || genre==QLatin1String("40 top")) {
if (genre==QLatin1String("Top40") || genre==QLatin1String("Top 40") || genre==QLatin1String("40Top") || genre==QLatin1String("40 Top")) {
return QLatin1String("Top 40");
}
QStringList small=QStringList() << QLatin1String("Adult Contemporary") << QLatin1String("Alternative")
<< QLatin1String("Community Radio") << QLatin1String("Local Service")
<< QLatin1String("Multiultural") << QLatin1String("News")
<< QLatin1String("Student") << QLatin1String("Urban");
foreach (const QString &s, small) {
if (genre==s || genre.startsWith(s+" ") || genre.endsWith(" "+s)) {
return s;
}
}
// Convert XX's to XXs
if (genre.contains(QRegExp("^[0-9]0's$"))) {
genre=genre.remove('\'');
@@ -136,6 +142,28 @@ static QString fixGenre(const QString &g)
return g;
}
static QString fixGenres(const QString &genre)
{
QString g(genre);
int pos=g.indexOf("<br");
if (pos>3) {
g=g.left(pos);
}
g=Song::capitalize(g);
QStringList genres=g.split('|', QString::SkipEmptyParts);
QStringList allGenres;
foreach (const QString &genre, genres) {
allGenres+=genre.split('/', QString::SkipEmptyParts);
}
QStringList fixed;
foreach (const QString &genre, allGenres) {
fixed.append(fixSingleGenre(genre));
}
return fixed.join(StreamsModel::constGenreSeparator);
}
static void trimGenres(QMultiHash<QString, StreamsModel::StreamItem *> &genres)
{
QSet<QString> genreSet = genres.keys().toSet();
@@ -143,7 +171,7 @@ static void trimGenres(QMultiHash<QString, StreamsModel::StreamItem *> &genres)
if (genres.count(genre) < 3) {
const QList<StreamsModel::StreamItem *> &smallGnre = genres.values(genre);
foreach (StreamsModel::StreamItem* s, smallGnre) {
s->genre = QString();
s->genres.remove(genre);
}
}
}
@@ -174,7 +202,7 @@ static QList<StreamsModel::StreamItem *> parseIceCast(const QByteArray &data)
name=doc.readElementText();
--level;
} else if (QLatin1String("genre")==doc.name()) {
genre=fixGenre(doc.readElementText());
genre=fixGenres(doc.readElementText());
--level;
} else if (QLatin1String("listen_url")==doc.name()) {
url=QUrl(doc.readElementText());
@@ -185,7 +213,9 @@ static QList<StreamsModel::StreamItem *> parseIceCast(const QByteArray &data)
if (2==level && QLatin1String("entry")==doc.name() && !name.isEmpty() && url.isValid() && !names.contains(name)) {
StreamsModel::StreamItem *item=new StreamsModel::StreamItem(name, genre, QString(), url);
streams.append(item);
genres.insert(item->genre, item);
foreach (const QString &genre, item->genres) {
genres.insert(genre, item);
}
names.insert(item->name);
}
--level;
@@ -222,7 +252,7 @@ static QList<StreamsModel::StreamItem *> parseSomaFm(const QByteArray &data)
name=doc.readElementText();
--level;
} else if (QLatin1String("genre")==doc.name()) {
genre=fixGenre(doc.readElementText());
genre=fixGenres(doc.readElementText());
--level;
} else if (QLatin1String("fastpls")==doc.name()) {
if (streamFormat.isEmpty() || QLatin1String("mp3")!=streamFormat) {
@@ -236,7 +266,9 @@ static QList<StreamsModel::StreamItem *> parseSomaFm(const QByteArray &data)
if (2==level && QLatin1String("channel")==doc.name() && !name.isEmpty() && url.isValid() && !names.contains(name)) {
StreamsModel::StreamItem *item=new StreamsModel::StreamItem(name, genre, QString(), url);
streams.append(item);
genres.insert(item->genre, item);
foreach (const QString &genre, item->genres) {
genres.insert(genre, item);
}
names.insert(item->name);
}
--level;
@@ -674,7 +706,7 @@ void StreamsPage::edit()
StreamsModel::StreamItem *stream=static_cast<StreamsModel::StreamItem *>(item);
QString url=stream->url.toString();
QString cat=stream->parent->name;
QString genre=stream->genre;
QString genre=stream->genreString();
dlg.setEdit(cat, name, genre, icon, url);