Show track info in context-view as well as artist and album info. Track info is stacked behind lyrics.

This commit is contained in:
craig.p.drummond
2014-06-09 18:43:49 +00:00
committed by craig.p.drummond
parent f333fd6399
commit 110ca299c1
17 changed files with 492 additions and 131 deletions

View File

@@ -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
-----

View File

@@ -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();

View File

@@ -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();

View File

@@ -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;

View File

@@ -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);

View File

@@ -36,7 +36,8 @@ class ContextEngine : public QObject
public:
enum Mode {
Artist,
Album
Album,
Track
};
static ContextEngine * create(QObject *parent);

View File

@@ -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)));
}

View File

@@ -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();

View File

@@ -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

View File

@@ -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 <QFile>
#include <QDir>
#include <QFileInfo>
@@ -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();

View File

@@ -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

View File

@@ -39,6 +39,11 @@
#include <QLocale>
#include <QBuffer>
#include <QFile>
#include <QStackedWidget>
#include <QComboBox>
#include <QMenu>
#include <QMouseEvent>
#include <QWheelEvent>
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("<b>"+t+"</b>");
current=items.count();
}
items.append(t);
}
void ViewTextSelector::itemSelected()
{
QAction *act=qobject_cast<QAction *>(sender());
if (act) {
setCurrentIndex(act->data().toInt());
}
}
bool ViewTextSelector::event(QEvent *e)
{
switch (e->type()) {
case QEvent::MouseButtonPress:
if (Qt::NoModifier==static_cast<QMouseEvent *>(e)->modifiers() && Qt::LeftButton==static_cast<QMouseEvent *>(e)->button()) {
menu->exec(mapToGlobal(static_cast<QMouseEvent *>(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<QWheelEvent *>(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("<b>"+items.at(current)+"</b>");
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("<html><head><style type=text/css>a:link {color:")+text->palette().link().color().name()+
QLatin1String("; text-decoration:underline;}</style></head><body>")+h+QLatin1String("</body></html>"));
}
void View::searchResponse(const QString &r, const QString &l)
{
Q_UNUSED(l)
text->setText(r);
texts.at(index)->setText(QLatin1String("<html><head><style type=text/css>a:link {color:")+text->palette().link().color().name()+
QLatin1String("; text-decoration:underline;}</style></head><body>")+h+QLatin1String("</body></html>"));
}
void View::abort()

View File

@@ -26,21 +26,47 @@
#include <QWidget>
#include <QSize>
#include <QLabel>
#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+"</"+subTag+">"; }
@@ -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<TextBrowser *> texts;
};
#endif

View File

@@ -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 <QUrlQuery>
@@ -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<QString, QString> replacements;
replacements.insert(QLatin1String("."), QString()); // A.S.A.P. -> ASAP
replacements.insert(QLatin1String("-"), QLatin1String("/")); // AC-DC -> AC/DC
QMap<QString, QString>::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<QString, QString>::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);
}
}

View File

@@ -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

View File

@@ -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);

View File

@@ -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