diff --git a/ChangeLog b/ChangeLog index df8f605aa..57a416104 100644 --- a/ChangeLog +++ b/ChangeLog @@ -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 ----- diff --git a/models/streamsmodel.cpp b/models/streamsmodel.cpp index 29b2d3dce..4e40b1014 100644 --- a/models/streamsmodel.cpp +++ b/models/streamsmodel.cpp @@ -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 QSeticon.isEmpty() && s->icon!=Icons::streamIcon.name()) { doc.writeAttribute("icon", s->icon); } - if (!s->genre.isEmpty() && s->genre!=unknown) { - doc.writeAttribute("genre", s->genre); + QSet 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 (rowstreams.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 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; } } diff --git a/models/streamsmodel.h b/models/streamsmodel.h index c8a36afb6..5431c6b87 100644 --- a/models/streamsmodel.h +++ b/models/streamsmodel.h @@ -28,6 +28,7 @@ #include #include #include +#include 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 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 &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 genres; QUrl url; CategoryItem *parent; }; @@ -71,9 +79,6 @@ public: QSet genres; }; - static QString prefixUrl(const QString &n, bool addPrefix=true); - static QString dir(); - static StreamsModel * self(); StreamsModel(); diff --git a/models/streamsproxymodel.cpp b/models/streamsproxymodel.cpp index ea02c7041..ce31968f0 100644 --- a/models/streamsproxymodel.cpp +++ b/models/streamsproxymodel.cpp @@ -63,7 +63,7 @@ bool StreamsProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourc } else { StreamsModel::StreamItem *s = static_cast(item); - if (!filterGenre.isEmpty() && s->genre!=filterGenre) { + if (!filterGenre.isEmpty() && !s->genres.contains(filterGenre)) { return false; } return matchesFilter(QStringList() << s->name); diff --git a/streams/streamdialog.cpp b/streams/streamdialog.cpp index ac2ef214b..cb42e8f5b 100644 --- a/streams/streamdialog.cpp +++ b/streams/streamdialog.cpp @@ -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("NOTE: Use '|' to split mutliple genres - e.g. 'Current|Classic'"), 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("Invalid protocol")); + statusText->setText(validProtocol ? QString() : i18n("ERROR: Invalid protocol")); } enableOk=enableOk && validProtocol; enableButton(Ok, enableOk); diff --git a/streams/streamspage.cpp b/streams/streamspage.cpp index 4da7f4a54..67484427f 100644 --- a/streams/streamspage.cpp +++ b/streams/streamspage.cpp @@ -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("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 &genres) { QSet genreSet = genres.keys().toSet(); @@ -143,7 +171,7 @@ static void trimGenres(QMultiHash &genres) if (genres.count(genre) < 3) { const QList &smallGnre = genres.values(genre); foreach (StreamsModel::StreamItem* s, smallGnre) { - s->genre = QString(); + s->genres.remove(genre); } } } @@ -174,7 +202,7 @@ static QList 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 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 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 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(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);