From 110ca299c162c096e3353eb730bda8d9ee8462fb Mon Sep 17 00:00:00 2001 From: "craig.p.drummond" Date: Mon, 9 Jun 2014 18:43:49 +0000 Subject: [PATCH] Show track info in context-view as well as artist and album info. Track info is stacked behind lyrics. --- ChangeLog | 2 + context/albumview.cpp | 2 +- context/albumview.h | 2 +- context/artistview.cpp | 2 +- context/artistview.h | 2 +- context/contextengine.h | 3 +- context/contextwidget.cpp | 2 +- context/lastfmengine.cpp | 65 ++++++----- context/lastfmengine.h | 3 +- context/songview.cpp | 172 ++++++++++++++++++++++++--- context/songview.h | 23 +++- context/view.cpp | 225 ++++++++++++++++++++++++++++-------- context/view.h | 52 +++++++-- context/wikipediaengine.cpp | 55 ++++++--- gui/cachesettings.cpp | 1 + gui/settings.cpp | 10 ++ gui/settings.h | 2 + 17 files changed, 492 insertions(+), 131 deletions(-) diff --git a/ChangeLog b/ChangeLog index 71acfb04c..796cd9be0 100644 --- a/ChangeLog +++ b/ChangeLog @@ -141,6 +141,8 @@ 88. Open search-widget as soon as user starts typing in view. 89. If artist is different to album-artist, then show track title as "title - artist" +90. Show track info in context-view as well as artist and album info. Track + info is stacked behind lyrics. 1.3.4 ----- diff --git a/context/albumview.cpp b/context/albumview.cpp index 7475e5512..2fb0a9d31 100644 --- a/context/albumview.cpp +++ b/context/albumview.cpp @@ -68,7 +68,7 @@ AlbumView::AlbumView(QWidget *p) connect(text, SIGNAL(anchorClicked(QUrl)), SLOT(playSong(QUrl))); text->setContextMenuPolicy(Qt::CustomContextMenu); connect(text, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showContextMenu(QPoint))); - setStandardHeader(i18n("Album Information")); + setStandardHeader(i18n("Album")); int imageSize=fontMetrics().height()*18; setPicSize(QSize(imageSize, imageSize)); clear(); diff --git a/context/albumview.h b/context/albumview.h index c1c5d89fa..b7f502aac 100644 --- a/context/albumview.h +++ b/context/albumview.h @@ -57,12 +57,12 @@ private Q_SLOTS: void showContextMenu(const QPoint &pos); void refresh(); void clearCache(); + void searchResponse(const QString &resp, const QString &lang); private: void clearDetails(); void getTrackListing(); void getDetails(); - void searchResponse(const QString &resp, const QString &lang); void updateDetails(bool preservePos=false); void abort(); diff --git a/context/artistview.cpp b/context/artistview.cpp index 4c7223360..ba212815a 100644 --- a/context/artistview.cpp +++ b/context/artistview.cpp @@ -90,7 +90,7 @@ ArtistView::ArtistView(QWidget *parent) connect(text, SIGNAL(anchorClicked(QUrl)), SLOT(show(QUrl))); text->setContextMenuPolicy(Qt::CustomContextMenu); connect(text, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showContextMenu(QPoint))); - setStandardHeader(i18n("Artist Information")); + setStandardHeader(i18n("Artist")); int imageHeight=fontMetrics().height()*14; int imageWidth=imageHeight*1.5; diff --git a/context/artistview.h b/context/artistview.h index a981db598..e20c36e27 100644 --- a/context/artistview.h +++ b/context/artistview.h @@ -68,10 +68,10 @@ private Q_SLOTS: void handleSimilarReply(); void show(const QUrl &url); void clearCache(); + void searchResponse(const QString &resp, const QString &lang); private: void loadBio(); - void searchResponse(const QString &resp, const QString &lang); void loadSimilar(); void requestSimilar(); QStringList parseSimilarResponse(const QByteArray &resp); diff --git a/context/contextengine.h b/context/contextengine.h index c90001986..164f282db 100644 --- a/context/contextengine.h +++ b/context/contextengine.h @@ -36,7 +36,8 @@ class ContextEngine : public QObject public: enum Mode { Artist, - Album + Album, + Track }; static ContextEngine * create(QObject *parent); diff --git a/context/contextwidget.cpp b/context/contextwidget.cpp index 6d49e777d..453de7e4a 100644 --- a/context/contextwidget.cpp +++ b/context/contextwidget.cpp @@ -366,7 +366,7 @@ void ContextWidget::setWide(bool w) viewSelector=new ViewSelector(standardContext); viewSelector->addItem(i18n("&Artist"), "artist"); viewSelector->addItem(i18n("Al&bum"), "album"); - viewSelector->addItem(i18n("&Lyrics"), "song"); + viewSelector->addItem(i18n("&Track"), "song"); viewSelector->setPalette(palette()); connect(viewSelector, SIGNAL(activated(int)), stack, SLOT(setCurrentIndex(int))); } diff --git a/context/lastfmengine.cpp b/context/lastfmengine.cpp index cc74ce875..633590fc9 100644 --- a/context/lastfmengine.cpp +++ b/context/lastfmengine.cpp @@ -74,13 +74,24 @@ void LastFmEngine::search(const QStringList &query, Mode mode) QUrlQuery urlQuery; #endif - urlQuery.addQueryItem("method", Artist==mode ? "artist.getInfo" : "album.getInfo"); + switch (mode) { + case Artist: + urlQuery.addQueryItem("method", "artist.getInfo"); + break; + case Album: + urlQuery.addQueryItem("method", "album.getInfo"); + urlQuery.addQueryItem("album", fixedQuery.at(1)); + break; + case Track: + urlQuery.addQueryItem("method", "track.getInfo"); + urlQuery.addQueryItem("track", fixedQuery.at(1)); + break; + } + urlQuery.addQueryItem("api_key", Covers::constLastFmApiKey); urlQuery.addQueryItem("autocorrect", "1"); urlQuery.addQueryItem("artist", Covers::fixArtist(fixedQuery.at(0))); - if (Album==mode) { - urlQuery.addQueryItem("album", fixedQuery.at(1)); - } + #if QT_VERSION >= 0x050000 url.setQuery(urlQuery); #endif @@ -115,7 +126,20 @@ void LastFmEngine::parseResponse() } Mode mode=(Mode)reply->property(constModeProperty).toInt(); - QString text=Artist==mode ? parseArtistResponse(data) : parseAlbumResponse(data); + QString text; + + switch (mode) { + case Artist: + text=parseResponse(data, QLatin1String("artist"), QLatin1String("bio")); + break; + case Album: + text=parseResponse(data, QLatin1String("album"), QLatin1String("wiki")); + break; + case Track: + text=parseResponse(data, QLatin1String("track"), QLatin1String("wiki")); + break; + } + if (!text.isEmpty()) { static const QRegExp constLicense("User-contributed text is available.*"); text.remove(constLicense); @@ -141,37 +165,14 @@ void LastFmEngine::parseResponse() emit searchResult(text, text.isEmpty() ? QString() : constLang); } -QString LastFmEngine::parseArtistResponse(const QByteArray &data) +QString LastFmEngine::parseResponse(const QByteArray &data, const QString &firstTag, const QString &secondTag) { + DBUG << __FUNCTION__ << data; QXmlStreamReader xml(data); while (xml.readNextStartElement()) { - if (QLatin1String("artist")==xml.name()) { + if (firstTag==xml.name()) { while (xml.readNextStartElement()) { - if (QLatin1String("bio")==xml.name()) { - while (xml.readNextStartElement()) { - if (QLatin1String("content")==xml.name()) { - return xml.readElementText().trimmed(); - } else { - xml.skipCurrentElement(); - } - } - } else { - xml.skipCurrentElement(); - } - } - } - } - - return QString(); -} - -QString LastFmEngine::parseAlbumResponse(const QByteArray &data) -{ - QXmlStreamReader xml(data); - while (xml.readNextStartElement()) { - if (QLatin1String("album")==xml.name()) { - while (xml.readNextStartElement()) { - if (QLatin1String("wiki")==xml.name()) { + if (secondTag==xml.name()) { while (xml.readNextStartElement()) { if (QLatin1String("content")==xml.name()) { return xml.readElementText().trimmed(); diff --git a/context/lastfmengine.h b/context/lastfmengine.h index 323100525..635228d87 100644 --- a/context/lastfmengine.h +++ b/context/lastfmengine.h @@ -50,8 +50,7 @@ private Q_SLOTS: void parseResponse(); private: - QString parseArtistResponse(const QByteArray &data); - QString parseAlbumResponse(const QByteArray &data); + QString parseResponse(const QByteArray &data, const QString &firstTag, const QString &secondTag); }; #endif diff --git a/context/songview.cpp b/context/songview.cpp index 60f1d06ba..6a52bb5f9 100644 --- a/context/songview.cpp +++ b/context/songview.cpp @@ -25,7 +25,9 @@ #include "lyricsdialog.h" #include "ultimatelyricsprovider.h" #include "ultimatelyrics.h" +#include "contextengine.h" #include "gui/settings.h" +#include "gui/covers.h" #include "support/squeezedtextlabel.h" #include "support/utils.h" #include "support/messagebox.h" @@ -41,6 +43,7 @@ #include "widgets/textbrowser.h" #include "gui/stdactions.h" #include "mpd/mpdstatus.h" +#include "qtiocompressor/qtiocompressor.h" #include #include #include @@ -54,17 +57,35 @@ const QLatin1String SongView::constLyricsDir("lyrics/"); const QLatin1String SongView::constExtension(".lyrics"); +const QLatin1String SongView::constCacheDir("tracks/"); +const QLatin1String SongView::constInfoExt(".html.gz"); -static QString cacheFile(QString artist, QString title, bool createDir=false) +static QString infoCacheFileName(const Song &song, const QString &lang, bool createDir) { + QString artist=song.artist; + QString title=song.title; title.replace("/", "_"); artist.replace("/", "_"); - QString dir=Utils::cacheDir(SongView::constLyricsDir+artist+Utils::constDirSep, createDir); + QString dir=Utils::cacheDir(SongView::constCacheDir+Covers::encodeName(artist)+Utils::constDirSep, createDir); if (dir.isEmpty()) { return QString(); } - return dir+title+SongView::constExtension; + return dir+Covers::encodeName(title)+"."+lang+SongView::constInfoExt; +} + +static QString lyricsCacheFileName(const Song &song, bool createDir=false) +{ + QString artist=song.artist; + QString title=song.title; + title.replace("/", "_"); + artist.replace("/", "_"); + QString dir=Utils::cacheDir(SongView::constLyricsDir+Covers::encodeName(artist)+Utils::constDirSep, createDir); + + if (dir.isEmpty()) { + return QString(); + } + return dir+Covers::encodeName(title)+SongView::constExtension; } static inline QString mpdFilePath(const QString &songFile) @@ -83,7 +104,7 @@ static inline QString fixNewLines(const QString &o) } SongView::SongView(QWidget *p) - : View(p) + : View(p, QStringList() << i18n("Lyrics:") << i18n("Information:")) , scrollTimer(0) , songPos(0) , currentProvider(-1) @@ -91,12 +112,13 @@ SongView::SongView(QWidget *p) , mode(Mode_Display) , job(0) , currentProv(0) + , infoNeedsUpdating(true) { scrollAction = ActionCollection::get()->createAction("scrolllyrics", i18n("Scroll Lyrics"), "go-down"); refreshAction = ActionCollection::get()->createAction("refreshlyrics", i18n("Refresh Lyrics"), "view-refresh"); editAction = ActionCollection::get()->createAction("editlyrics", i18n("Edit Lyrics"), Icons::self()->editIcon); saveAction = ActionCollection::get()->createAction("savelyrics", i18n("Save Lyrics"), "document-save"); - cancelAction = ActionCollection::get()->createAction("canceleditlyrics", i18n("Cancel Editing Lyrics"), Icons::self()->cancelIcon); + cancelEditAction = ActionCollection::get()->createAction("canceleditlyrics", i18n("Cancel Editing Lyrics"), Icons::self()->cancelIcon); delAction = ActionCollection::get()->createAction("dellyrics", i18n("Delete Lyrics File"), "edit-delete"); scrollAction->setCheckable(true); @@ -105,16 +127,28 @@ SongView::SongView(QWidget *p) connect(refreshAction, SIGNAL(triggered()), SLOT(update())); connect(editAction, SIGNAL(triggered()), SLOT(edit())); connect(saveAction, SIGNAL(triggered()), SLOT(save())); - connect(cancelAction, SIGNAL(triggered()), SLOT(cancel())); + connect(cancelEditAction, SIGNAL(triggered()), SLOT(cancel())); connect(delAction, SIGNAL(triggered()), SLOT(del())); connect(UltimateLyrics::self(), SIGNAL(lyricsReady(int, QString)), SLOT(lyricsReady(int, QString))); + engine=ContextEngine::create(this); + refreshInfoAction = ActionCollection::get()->createAction("refreshtrack", i18n("Refresh Track Information"), "view-refresh"); + cancelInfoJobAction=new Action(Icons::self()->cancelIcon, i18n("Cancel"), this); + cancelInfoJobAction->setEnabled(false); + connect(refreshInfoAction, SIGNAL(triggered()), SLOT(refreshInfo())); + connect(cancelInfoJobAction, SIGNAL(triggered()), SLOT(abortInfoSearch())); + connect(engine, SIGNAL(searchResult(QString,QString)), this, SLOT(infoSearchResponse(QString,QString))); + text->setContextMenuPolicy(Qt::CustomContextMenu); connect(text, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showContextMenu(QPoint))); + texts.at(Page_Information)->setContextMenuPolicy(Qt::CustomContextMenu); + connect(texts.at(Page_Information), SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showInfoContextMenu(QPoint))); + connect(this, SIGNAL(viewChanged()), this, SLOT(curentViewChanged())); setMode(Mode_Blank); - setStandardHeader(i18n("Lyrics")); + setStandardHeader(i18n("Track")); clear(); toggleScroll(); + setCurrentView(Settings::self()->contextTrackView()); } SongView::~SongView() @@ -146,6 +180,7 @@ void SongView::update() void SongView::saveConfig() { Settings::self()->saveContextAutoScroll(scrollAction->isChecked()); + Settings::self()->saveContextTrackView(currentView()); } void SongView::search() @@ -253,7 +288,7 @@ void SongView::showContextMenu(const QPoint &pos) case Mode_Edit: menu->addSeparator(); menu->addAction(saveAction); - menu->addAction(cancelAction); + menu->addAction(cancelEditAction); break; } @@ -266,6 +301,19 @@ void SongView::showContextMenu(const QPoint &pos) delete menu; } +void SongView::showInfoContextMenu(const QPoint &pos) +{ + QMenu *menu = texts.at(Page_Information)->createStandardContextMenu(); + menu->addSeparator(); + if (cancelInfoJobAction->isEnabled()) { + menu->addAction(cancelInfoJobAction); + } else { + menu->addAction(refreshInfoAction); + } + menu->exec(texts.at(Page_Information)->mapToGlobal(pos)); + delete menu; +} + void SongView::toggleScroll() { if (scrollAction->isChecked()) { @@ -329,6 +377,90 @@ void SongView::scroll() } } +void SongView::curentViewChanged() +{ + if (infoNeedsUpdating) { + loadInfo(); + } +} + +void SongView::loadInfo() +{ + infoNeedsUpdating=false; + foreach (const QString &lang, engine->getLangs()) { + QString prefix=engine->getPrefix(lang); + QString cachedFile=infoCacheFileName(currentSong, prefix, false); + if (QFile::exists(cachedFile)) { + QFile f(cachedFile); + QtIOCompressor compressor(&f); + compressor.setStreamFormat(QtIOCompressor::GzipFormat); + if (compressor.open(QIODevice::ReadOnly)) { + QByteArray data=compressor.readAll(); + + if (!data.isEmpty()) { + infoSearchResponse(QString::fromUtf8(data), QString()); + Utils::touchFile(cachedFile); + return; + } + } + } + } + searchForInfo(); +} + +void SongView::refreshInfo() +{ + if (currentSong.isEmpty()) { + return; + } + foreach (const QString &lang, engine->getLangs()) { + QFile::remove(infoCacheFileName(currentSong, engine->getPrefix(lang), false)); + } + searchForInfo(); +} + +void SongView::searchForInfo() +{ + cancelInfoJobAction->setEnabled(true); + engine->search(QStringList() << currentSong.artist << currentSong.title, ContextEngine::Track); + showSpinner(false); +} + +void SongView::infoSearchResponse(const QString &resp, const QString &lang) +{ + cancelInfoJobAction->setEnabled(false); + hideSpinner(); + + if (!resp.isEmpty()) { + QString str=engine->translateLinks(resp); + if (!lang.isEmpty()) { + QFile f(infoCacheFileName(currentSong, lang, true)); + QtIOCompressor compressor(&f); + compressor.setStreamFormat(QtIOCompressor::GzipFormat); + if (compressor.open(QIODevice::WriteOnly)) { + compressor.write(resp.toUtf8().constData()); + } + } + setHtml(str, Page_Information); + } +} + +void SongView::abortInfoSearch() +{ + if (cancelInfoJobAction->isEnabled()) { + cancelInfoJobAction->setEnabled(false); + engine->cancel(); + hideSpinner(); + } +} + +void SongView::hideSpinner() +{ + if (!cancelInfoJobAction->isEnabled() && !cancelJobAction->isEnabled()) { + View::hideSpinner(); + } +} + void SongView::abort() { if (job) { @@ -344,9 +476,10 @@ void SongView::abort() // Set lyrics file anyway - so that editing is enabled! lyricsFile=Settings::self()->storeLyricsInMpdDir() && !currentSong.isNonMPD() ? mpdFilePath(currentSong) - : cacheFile(currentSong.artist, currentSong.title); + : lyricsCacheFileName(currentSong); setMode(Mode_Display); } + cancelJobAction->setEnabled(false); hideSpinner(); } @@ -358,6 +491,7 @@ void SongView::update(const Song &s, bool force) if (s.isEmpty() || s.title.isEmpty() || s.artist.isEmpty()) { currentSong=s; + infoNeedsUpdating=false; clear(); abort(); return; @@ -389,6 +523,12 @@ void SongView::update(const Song &s, bool force) } setHeader(song.title); + if (Page_Information==currentView()) { + loadInfo(); + } else { + infoNeedsUpdating=true; + } + // Only reset the provider if the refresh was an automatic one or if the song has // changed. Otherwise we'll keep the provider so the user can cycle through the lyrics // offered by the various providers. @@ -438,7 +578,7 @@ void SongView::update(const Song &s, bool force) } // Check for cached file... - QString file=cacheFile(song.artist, song.title); + QString file=lyricsCacheFileName(song); /*if (force && QFile::exists(file)) { // Delete the cached lyrics file when the user is force-fully re-fetching the lyrics. @@ -469,6 +609,7 @@ void SongView::downloadFinished() QString lyrics=str.readAll(); if (!lyrics.isEmpty()) { text->setText(fixNewLines(lyrics)); + cancelJobAction->setEnabled(false); hideSpinner(); return; } @@ -490,6 +631,7 @@ void SongView::lyricsReady(int id, QString lyrics) if (lyrics.isEmpty()) { getLyrics(); } else { + cancelJobAction->setEnabled(false); hideSpinner(); QString before=text->toHtml(); text->setText(fixNewLines(lyrics)); @@ -504,7 +646,7 @@ void SongView::lyricsReady(int id, QString lyrics) lyricsFile=QString(); if (! ( Settings::self()->storeLyricsInMpdDir() && !currentSong.isNonMPD() && saveFile(mpdFilePath(currentSong))) ) { - saveFile(cacheFile(currentSong.artist, currentSong.title, true)); + saveFile(lyricsCacheFileName(currentSong, true)); } setMode(Mode_Display); } @@ -533,7 +675,7 @@ QString SongView::mpdFileName() const QString SongView::cacheFileName() const { - return currentSong.artist.isEmpty() || currentSong.title.isEmpty() ? QString() : cacheFile(currentSong.artist, currentSong.title); + return currentSong.artist.isEmpty() || currentSong.title.isEmpty() ? QString() : lyricsCacheFileName(currentSong); } void SongView::getLyrics() @@ -549,7 +691,7 @@ void SongView::getLyrics() // Set lyrics file anyway - so that editing is enabled! lyricsFile=Settings::self()->storeLyricsInMpdDir() && !currentSong.isNonMPD() ? mpdFilePath(currentSong) - : cacheFile(currentSong.artist, currentSong.title); + : lyricsCacheFileName(currentSong); setMode(Mode_Display); } } @@ -557,6 +699,7 @@ void SongView::getLyrics() void SongView::setMode(Mode m) { if (Mode_Display==m) { + cancelJobAction->setEnabled(false); hideSpinner(); } if (mode==m) { @@ -565,7 +708,7 @@ void SongView::setMode(Mode m) mode=m; bool editable=Mode_Display==m && !lyricsFile.isEmpty() && (!QFile::exists(lyricsFile) || QFileInfo(lyricsFile).isWritable()); saveAction->setEnabled(Mode_Edit==m); - cancelAction->setEnabled(Mode_Edit==m); + cancelEditAction->setEnabled(Mode_Edit==m); editAction->setEnabled(editable); delAction->setEnabled(editable && !MPDConnection::self()->getDetails().dir.isEmpty() && QFile::exists(mpdFilePath(currentSong))); setEditable(Mode_Edit==m); @@ -587,6 +730,7 @@ bool SongView::setLyricsFromFile(const QString &filePath) QTextStream inputStream(&f); text->setText(fixNewLines(inputStream.readAll())); + cancelJobAction->setEnabled(false); hideSpinner(); f.close(); diff --git a/context/songview.h b/context/songview.h index a35c4d846..d3cc1d5f5 100644 --- a/context/songview.h +++ b/context/songview.h @@ -32,6 +32,7 @@ class QImage; class Action; class NetworkJob; class QTimer; +class ContextEngine; class SongView : public View { @@ -43,9 +44,16 @@ class SongView : public View Mode_Edit }; + enum Pages { + Page_Lyrics, + Page_Information + }; + public: static const QLatin1String constLyricsDir; static const QLatin1String constExtension; + static const QLatin1String constCacheDir; + static const QLatin1String constInfoExt; SongView(QWidget *p); ~SongView(); @@ -66,13 +74,21 @@ public Q_SLOTS: void cancel(); void del(); void showContextMenu(const QPoint &pos); + void showInfoContextMenu(const QPoint &pos); private Q_SLOTS: void toggleScroll(); void songPosition(); void scroll(); + void curentViewChanged(); + void refreshInfo(); + void infoSearchResponse(const QString &resp, const QString &lang); + void abortInfoSearch(); private: + void loadInfo(); + void searchForInfo(); + void hideSpinner(); void abort(); QString mpdFileName() const; QString cacheFileName() const; @@ -100,13 +116,18 @@ private: Action *searchAction; Action *editAction; Action *saveAction; - Action *cancelAction; + Action *cancelEditAction; Action *delAction; Mode mode; QString lyricsFile; QString preEdit; NetworkJob *job; UltimateLyricsProvider *currentProv; + + bool infoNeedsUpdating; + Action *refreshInfoAction; + Action *cancelInfoJobAction; + ContextEngine *engine; }; #endif diff --git a/context/view.cpp b/context/view.cpp index a5a3ead1f..4862ccf8a 100644 --- a/context/view.cpp +++ b/context/view.cpp @@ -39,6 +39,11 @@ #include #include #include +#include +#include +#include +#include +#include static QString headerTag; QString View::subTag; @@ -59,31 +64,136 @@ void View::initHeaderTags() subTag=small ? "h3" : "h2"; } -View::View(QWidget *parent) - : QWidget(parent) - , needToUpdate(false) - , spinner(0) +static TextBrowser * createView(QWidget *parent) { - QVBoxLayout *layout=new QVBoxLayout(this); - layout->setAlignment(Qt::AlignTop | Qt::AlignLeft); - header=new QLabel(this); - text=new TextBrowser(this); - - layout->setMargin(0); - header->setWordWrap(true); - header->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); - text->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding); - text->setFrameShape(QFrame::NoFrame); - - layout->addItem(new QSpacerItem(1, layout->spacing(), QSizePolicy::Fixed, QSizePolicy::Fixed)); - layout->addWidget(header); - layout->addWidget(text); - layout->addItem(new QSpacerItem(1, fontMetrics().height()/4, QSizePolicy::Fixed, QSizePolicy::Fixed)); - text->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + TextBrowser *text=new TextBrowser(parent); if (GtkStyle::isActive()) { text->verticalScrollBar()->setAttribute(Qt::WA_OpaquePaintEvent, false); } text->setOpenLinks(false); + text->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + text->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding); + text->setFrameShape(QFrame::NoFrame); + return text; +} + +ViewTextSelector::ViewTextSelector(QWidget *p) + : QLabel(p) + , current(0) +{ + setAttribute(Qt::WA_Hover, true); + menu=new QMenu(this); +} + +void ViewTextSelector::addItem(const QString &t) +{ + menu->addAction(t.endsWith(":") ? t.left(t.count()-1) : t, this, SLOT(itemSelected()))->setData(items.count()); + if (text().isEmpty()) { + setText(""+t+""); + current=items.count(); + } + items.append(t); +} + +void ViewTextSelector::itemSelected() +{ + QAction *act=qobject_cast(sender()); + if (act) { + setCurrentIndex(act->data().toInt()); + } +} + +bool ViewTextSelector::event(QEvent *e) +{ + switch (e->type()) { + case QEvent::MouseButtonPress: + if (Qt::NoModifier==static_cast(e)->modifiers() && Qt::LeftButton==static_cast(e)->button()) { + menu->exec(mapToGlobal(static_cast(e)->pos())); + } + case QEvent::HoverLeave: + setStyleSheet(QString()); + break; + case QEvent::HoverEnter: + setStyleSheet(QLatin1String("QLabel{color:palette(highlight);}")); + break; + case QEvent::Wheel: { + int numDegrees = static_cast(e)->delta() / 8; + int numSteps = numDegrees / 15; + int newIndex = current; + if (numSteps > 0) { + for (int i = 0; i < numSteps; ++i) { + newIndex++; + if (newIndex>=items.count()) { + newIndex=0; + } + } + } else { + for (int i = 0; i > numSteps; --i) { + newIndex--; + if (newIndex<0) { + newIndex=items.count()-1; + } + } + } + setCurrentIndex(newIndex); + break; + } + default: + break; + } + return QLabel::event(e); +} + +void ViewTextSelector::setCurrentIndex(int v) +{ + if (v<0 || v>=items.count() || v==current) { + return; + } + current=v; + setText(""+items.at(current)+""); + emit activated(current); +} + +View::View(QWidget *parent, const QStringList &views) + : QWidget(parent) + , needToUpdate(false) + , spinner(0) + , selector(0) + , stack(0) +{ + QVBoxLayout *layout=new QVBoxLayout(this); + layout->setAlignment(Qt::AlignTop | Qt::AlignLeft); + header=new QLabel(this); + + if (views.isEmpty()) { + TextBrowser *t=createView(this); + texts.append(t); + } else { + stack=new QStackedWidget(this); + selector=new ViewTextSelector(this); + foreach (const QString &v, views) { + TextBrowser *t=createView(stack); + selector->addItem(v); + stack->addWidget(t); + texts.append(t); + } + connect(selector, SIGNAL(activated(int)), stack, SLOT(setCurrentIndex(int))); + connect(selector, SIGNAL(activated(int)), this, SIGNAL(viewChanged())); + } + + layout->setMargin(0); + header->setWordWrap(true); + header->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + + layout->addItem(new QSpacerItem(1, layout->spacing(), QSizePolicy::Fixed, QSizePolicy::Fixed)); + layout->addWidget(header); + if (views.isEmpty()) { + layout->addWidget(texts.at(0)); + } else { + layout->addWidget(selector); + layout->addWidget(stack); + } + layout->addItem(new QSpacerItem(1, fontMetrics().height()/4, QSizePolicy::Fixed, QSizePolicy::Fixed)); setEditable(false); if (headerTag.isEmpty()) { initHeaderTags(); @@ -92,6 +202,7 @@ View::View(QWidget *parent) cancelJobAction=new Action(Icons::self()->cancelIcon, i18n("Cancel"), this); cancelJobAction->setEnabled(false); connect(cancelJobAction, SIGNAL(triggered()), SLOT(abort())); + text=texts.at(0); } View::~View() @@ -102,7 +213,9 @@ View::~View() void View::clear() { setHeader(stdHeader); - text->clear(); + foreach (TextBrowser *t, texts) { + t->clear(); + } } void View::setHeader(const QString &str) @@ -112,7 +225,9 @@ void View::setHeader(const QString &str) void View::setPicSize(const QSize &sz) { - text->setPicSize(sz); + foreach (TextBrowser *t, texts) { + t->setPicSize(sz); + } } QSize View::picSize() const @@ -129,7 +244,7 @@ QString View::createPicTag(const QImage &img, const QString &file) return QString(); } // No filename given, or file does not exist - therefore encode & scale image. - return encode(img.scaled(text->picSize(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); + return encode(img.scaled(texts.at(0)->picSize(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); } void View::showEvent(QShowEvent *e) @@ -141,7 +256,7 @@ void View::showEvent(QShowEvent *e) QWidget::showEvent(e); } -void View::showSpinner() +void View::showSpinner(bool enableCancel) { if (!spinner) { spinner=new Spinner(this); @@ -149,53 +264,73 @@ void View::showSpinner() } if (!spinner->isActive()) { spinner->start(); - cancelJobAction->setEnabled(true); + if (enableCancel) { + cancelJobAction->setEnabled(true); + } } } -void View::hideSpinner() +void View::hideSpinner(bool disableCancel) { if (spinner) { spinner->stop(); - cancelJobAction->setEnabled(false); + if (disableCancel) { + cancelJobAction->setEnabled(false); + } } } -void View::setEditable(bool e) +void View::setEditable(bool e, int index) { - text->setReadOnly(!e); - text->viewport()->setAutoFillBackground(e); + TextBrowser *t=texts.at(index); + t->setReadOnly(!e); + t->viewport()->setAutoFillBackground(e); } void View::setPal(const QPalette &pal, const QColor &linkColor, const QColor &prevLinkColor) { - // QTextBrowser seems to save link colour within the HTML, so we need to manually - // update this when the palette changes! - QString old=text->toHtml(); - text->setPal(pal); + foreach (TextBrowser *t, texts) { + // QTextBrowser seems to save link colour within the HTML, so we need to manually + // update this when the palette changes! + QString old=t->toHtml(); + t->setPal(pal); + old=old.replace("color:"+prevLinkColor.name()+";", "color:"+linkColor.name()+";"); + t->setHtml(old); + } // header uses window/button text - so need to set these now... QPalette hdrPal=pal; hdrPal.setColor(QPalette::WindowText, pal.color(QPalette::Text)); hdrPal.setColor(QPalette::ButtonText, pal.color(QPalette::Text)); header->setPalette(hdrPal); - old=old.replace("color:"+prevLinkColor.name()+";", "color:"+linkColor.name()+";"); - text->setHtml(old); + if (selector) { + selector->setPalette(hdrPal); + } } void View::addEventFilter(QObject *obj) { installEventFilter(obj); - text->installEventFilter(obj); - text->viewport()->installEventFilter(obj); + foreach (TextBrowser *t, texts) { + t->installEventFilter(obj); + t->viewport()->installEventFilter(obj); + } header->installEventFilter(obj); } void View::setZoom(int z) { - text->setZoom(z); + foreach (TextBrowser *t, texts) { + t->setZoom(z); + } QFont f=header->font(); f.setPointSize(f.pointSize()+z); header->setFont(f); + + if (selector) { + QFont f=selector->font(); + f.setPointSize(f.pointSize()+z); + selector->setFont(f); + } } int View::getZoom() @@ -203,16 +338,10 @@ int View::getZoom() return text->zoom(); } -void View::setHtml(const QString &h) +void View::setHtml(const QString &h, int index) { - text->setText(QLatin1String("")+h+QLatin1String("")); -} - -void View::searchResponse(const QString &r, const QString &l) -{ - Q_UNUSED(l) - text->setText(r); + texts.at(index)->setText(QLatin1String("")+h+QLatin1String("")); } void View::abort() diff --git a/context/view.h b/context/view.h index 472204c2c..d32693459 100644 --- a/context/view.h +++ b/context/view.h @@ -26,21 +26,47 @@ #include #include +#include #include "mpd/song.h" class QImage; -class QLabel; class Spinner; class QNetworkReply; class QLayoutItem; class TextBrowser; class Action; +class QStackedWidget; +class QMenu; + +class ViewTextSelector : public QLabel +{ + Q_OBJECT +public: + ViewTextSelector(QWidget *p); + void addItem(const QString &t); + bool event(QEvent *e); + int currentIndex() const { return current; } + void setCurrentIndex(int v); + +Q_SIGNALS: + void activated(int); + +private Q_SLOTS: + void itemSelected(); + +private: + int current; + QStringList items; + QMenu *menu; +}; class View : public QWidget { Q_OBJECT public: - View(QWidget *p); + static QString subTag; + + View(QWidget *p, const QStringList &views=QStringList()); virtual ~View(); static QString subHeader(const QString &str) { return "<"+subTag+">"+str+""; } @@ -53,30 +79,36 @@ public: QSize picSize() const; QString createPicTag(const QImage &img, const QString &file); void showEvent(QShowEvent *e); - void showSpinner(); - void hideSpinner(); - void setEditable(bool e); + void showSpinner(bool enableCancel=true); + void hideSpinner(bool disableCancel=true); + void setEditable(bool e, int index=0); void setPal(const QPalette &pal, const QColor &linkColor, const QColor &prevLinkColor); void addEventFilter(QObject *obj); void setZoom(int z); int getZoom(); virtual void update(const Song &s, bool force)=0; - void setHtml(const QString &h); + void setHtml(const QString &h, int index=0); + int currentView() const { return selector ? selector->currentIndex() : -1; } + void setCurrentView(int v) { selector->setCurrentIndex(v); } + +Q_SIGNALS: + void viewChanged(); protected Q_SLOTS: - virtual void searchResponse(const QString &r, const QString &l); virtual void abort(); protected: - static QString subTag; Song currentSong; QString stdHeader; QLabel *header; - TextBrowser *text; bool needToUpdate; Spinner *spinner; Action *cancelJobAction; + + ViewTextSelector *selector; + QStackedWidget *stack; + TextBrowser *text; // short-cut to first text item... + QList texts; }; #endif - diff --git a/context/wikipediaengine.cpp b/context/wikipediaengine.cpp index 9536b854d..4f32a8590 100644 --- a/context/wikipediaengine.cpp +++ b/context/wikipediaengine.cpp @@ -25,6 +25,7 @@ #include "network/networkaccessmanager.h" #include "support/localize.h" #include "gui/settings.h" +#include "gui/covers.h" #include "config.h" #if QT_VERSION >= 0x050000 #include @@ -306,6 +307,10 @@ QString WikipediaEngine::translateLinks(QString text) const void WikipediaEngine::search(const QStringList &query, Mode mode) { titles.clear(); +// if (Track==mode) { +// emit searchResult(QString(), QString()); +// return; +// } requestTitles(fixQuery(query), mode, getPrefix(preferredLangs.first())); } @@ -389,6 +394,7 @@ void WikipediaEngine::parseTitles() return; } + DBUG << titles; getPage(query, mode, hostLang); } @@ -413,20 +419,21 @@ void WikipediaEngine::getPage(const QStringList &query, Mode mode, const QString simplifiedTitles.append(t.simplified()); } - while(!queryCopy.isEmpty()) { - QString q=queryCopy.join(" "); - QString q2=q; - q2.remove("."); // A.S.A.P. -> ASAP - queries.append(q); - if (q2!=q) { - queries.append(q2); - } + QMap replacements; + replacements.insert(QLatin1String("."), QString()); // A.S.A.P. -> ASAP + replacements.insert(QLatin1String("-"), QLatin1String("/")); // AC-DC -> AC/DC + QMap::ConstIterator repEnd=replacements.constEnd(); - q2=q; - q2.replace("-", "/"); // AC-DC -> AC/DC + while (!queryCopy.isEmpty()) { + QString q=queryCopy.join(" "); queries.append(q); - if (q2!=q) { - queries.append(q2); + + for (QMap::ConstIterator rep=replacements.constBegin(); rep!=repEnd; ++rep) { + QString q2=q; + q2.replace(rep.key(), rep.value()); + if (q2!=q) { + queries.append(q2); + } } queryCopy.takeFirst(); @@ -445,6 +452,10 @@ void WikipediaEngine::getPage(const QStringList &query, Mode mode, const QString patterns=i18nc("Search pattern for an album, separated by |", "album|score|soundtrack").split("|", QString::SkipEmptyParts); englishPatterns=QString(QLatin1String("album|score|soundtrack")).split("|"); break; + case Track: +// patterns=i18nc("Search pattern for a song, separated by |", "song|track").split("|", QString::SkipEmptyParts); +// englishPatterns=QString(QLatin1String("song|track")).split("|"); + break; } foreach (const QString &eng, englishPatterns) { @@ -456,18 +467,19 @@ void WikipediaEngine::getPage(const QStringList &query, Mode mode, const QString DBUG << "Titles" << titles; int index=-1; - if (mode==Album && 2==query.count()) { - DBUG << "Check album"; + if ((mode==Album || mode==Track) && 2==query.count()) { + DBUG << "Check track/album"; foreach (const QString &pattern, patterns) { QString q=query.at(1)+" ("+query.at(0)+" "+pattern+")"; DBUG << "Try" << q; index=indexOf(simplifiedTitles, q); if (-1!=index) { - DBUG << "Matched with '$album ($artist pattern)" << index << q; + DBUG << "Matched with '$album/$track ($artist pattern)" << index << q; break; } } } + if (-1==index) { foreach (const QString &q, queries) { DBUG << "Query" << q; @@ -539,9 +551,10 @@ void WikipediaEngine::parsePage() QUrl url=reply->url(); QString hostLang=getLang(url); + QStringList query=reply->property(constQueryProperty).toStringList(); + Mode mode=(Mode)reply->property(constModeProperty).toInt(); if (answer.contains(QLatin1String("{{disambiguation}}")) || answer.contains(QLatin1String("{{disambig}}"))) { // i18n??? - getPage(reply->property(constQueryProperty).toStringList(), (Mode)reply->property(constModeProperty).toInt(), - hostLang); + getPage(query, mode, hostLang); return; } @@ -553,5 +566,11 @@ void WikipediaEngine::parsePage() if (introOnly && resp.isEmpty()) { resp=wikiToHtml(answer, false, reply->url()); } - emit searchResult(resp, hostLang); + + // For track results, ensure response contains artist name! + if (Track==mode && !resp.contains(query.at(0), Qt::CaseInsensitive) && !resp.contains(Covers::fixArtist(query.at(0)), Qt::CaseInsensitive)) { + getPage(query, mode, hostLang); + } else { + emit searchResult(resp, hostLang); + } } diff --git a/gui/cachesettings.cpp b/gui/cachesettings.cpp index cf9460a93..05abd0be6 100644 --- a/gui/cachesettings.cpp +++ b/gui/cachesettings.cpp @@ -268,6 +268,7 @@ CacheSettings::CacheSettings(QWidget *parent) new CacheItem(i18n("Artist Information"), Utils::cacheDir(ArtistView::constCacheDir, false), QStringList() << "*"+ArtistView::constInfoExt << "*"+ArtistView::constSimilarInfoExt << "*.json.gz" << "*.jpg" << "*.png", tree); new CacheItem(i18n("Album Information"), Utils::cacheDir(AlbumView::constCacheDir, false), QStringList() << "*"+AlbumView::constInfoExt << "*.jpg" << "*.png", tree); + new CacheItem(i18n("Track Information"), Utils::cacheDir(SongView::constCacheDir, false), QStringList() << "*"+AlbumView::constInfoExt, tree); #ifdef ENABLE_STREAMS new CacheItem(i18n("Stream Listings"), Utils::cacheDir(StreamsModel::constSubDir, false), QStringList() << "*"+StreamsModel::constCacheExt, tree); #endif diff --git a/gui/settings.cpp b/gui/settings.cpp index 1e8c2e9b9..7e54b05b5 100644 --- a/gui/settings.cpp +++ b/gui/settings.cpp @@ -569,6 +569,11 @@ bool Settings::contextAutoScroll() return cfg.get("contextAutoScroll", false); } +int Settings::contextTrackView() +{ + return cfg.get("contextTrackView", 0); +} + QString Settings::page() { return cfg.get("page", QString()); @@ -1184,6 +1189,11 @@ void Settings::saveContextAutoScroll(bool v) cfg.set("contextAutoScroll", v); } +void Settings::saveContextTrackView(int v) +{ + cfg.set("contextTrackView", v); +} + void Settings::savePage(const QString &v) { cfg.set("page", v); diff --git a/gui/settings.h b/gui/settings.h index 549db479b..03156bb6e 100644 --- a/gui/settings.h +++ b/gui/settings.h @@ -111,6 +111,7 @@ public: bool contextAlwaysCollapsed(); int contextSwitchTime(); bool contextAutoScroll(); + int contextTrackView(); QString page(); QStringList hiddenPages(); #if !defined ENABLE_KDE_SUPPORT && !defined ENABLE_UBUNTU @@ -234,6 +235,7 @@ public: void saveContextAlwaysCollapsed(bool v); void saveContextSwitchTime(int v); void saveContextAutoScroll(bool v); + void saveContextTrackView(int v); void savePage(const QString &v); void saveHiddenPages(const QStringList &v); #if !defined ENABLE_KDE_SUPPORT && !defined ENABLE_UBUNTU