- Search for artists/albums using wikipedia search API.

- Add configurable languages
This commit is contained in:
craig.p.drummond
2013-05-21 19:03:07 +00:00
committed by craig.p.drummond
parent 15449d9999
commit 6114afbe5e
20 changed files with 1203 additions and 340 deletions

View File

@@ -103,7 +103,8 @@ SET( CANTATA_SRCS gui/application.cpp gui/main.cpp gui/initialsettingswizard.cpp
network/networkaccessmanager.cpp
devices/deviceoptions.cpp
context/lyricsettings.cpp context/ultimatelyricsprovider.cpp context/ultimatelyrics.cpp context/lyricsdialog.cpp
context/contextpage.cpp context/view.cpp context/artistview.cpp context/albumview.cpp context/songview.cpp)
context/contextpage.cpp context/view.cpp context/artistview.cpp context/albumview.cpp context/songview.cpp context/contextengine.cpp
context/wikipediaengine.cpp context/wikipediasettings.cpp)
SET( CANTATA_MOC_HDRS
gui/initialsettingswizard.h gui/mainwindow.h gui/settings.h gui/covers.h gui/folderpage.h gui/librarypage.h gui/albumspage.h
gui/playlistspage.h gui/serverplaybacksettings.h gui/serversettings.h gui/preferencesdialog.h gui/filesettings.h
@@ -119,14 +120,15 @@ SET( CANTATA_MOC_HDRS
widgets/genrecombo.h widgets/toolbar.h
network/networkaccessmanager.h
context/lyricsettings.h context/ultimatelyrics.h context/ultimatelyricsprovider.h context/lyricsdialog.h
context/contextpage.h context/artistview.h context/albumview.h context/songview.h context/view.h)
context/contextpage.h context/artistview.h context/albumview.h context/songview.h context/view.h context/contextengine.h
context/wikipediaengine.h context/wikipediasettings.h)
SET( CANTATA_UIS
gui/initialsettingswizard.ui gui/mainwindow.ui gui/folderpage.ui gui/librarypage.ui gui/albumspage.ui gui/playlistspage.ui
gui/filesettings.ui gui/interfacesettings.ui gui/playbacksettings.ui gui/serverplaybacksettings.ui gui/serversettings.ui
gui/coverdialog.ui
streams/streamspage.ui
dynamic/dynamicpage.ui dynamic/dynamicrule.ui dynamic/dynamicrules.ui
context/lyricsettings.ui
context/lyricsettings.ui context/wikipediasettings.ui
widgets/itemview.ui)
if (ENABLE_ONLINE_SERVICES)

View File

@@ -28,6 +28,7 @@
#include "utils.h"
#include "qtiocompressor/qtiocompressor.h"
#include "musiclibrarymodel.h"
#include "contextengine.h"
#include <QTextBrowser>
#include <QScrollBar>
#include <QFile>
@@ -53,10 +54,10 @@ enum Parts {
AlbumView::AlbumView(QWidget *p)
: View(p)
, triedWithFilter(false)
, detailsReceived(0)
, job(0)
{
engine=ContextEngine::create(this);
connect(engine, SIGNAL(searchResult(QString,QString)), this, SLOT(searchResponse(QString,QString)));
connect(Covers::self(), SIGNAL(cover(const Song &, const QImage &, const QString &)), SLOT(coverRetreived(const Song &, const QImage &, const QString &)));
connect(Covers::self(), SIGNAL(coverUpdated(const Song &, const QImage &, const QString &)), SLOT(coverRetreived(const Song &, const QImage &, const QString &)));
connect(text, SIGNAL(anchorClicked(QUrl)), SLOT(playSong(QUrl)));
@@ -71,7 +72,7 @@ void AlbumView::update(const Song &song, bool force)
{
if (song.isEmpty()) {
currentSong=song;
cancel();
engine->cancel();
clear();
return;
}
@@ -84,7 +85,7 @@ void AlbumView::update(const Song &song, bool force)
details.clear();
trackList.clear();
bio.clear();
triedWithFilter=false;
clear();
detailsReceived=bioArtist==currentSong.artist ? ArtistBio : 0;
setHeader(song.album.isEmpty() ? stdHeader : song.album);
Covers::Image cImg=Covers::self()->requestImage(song);
@@ -143,23 +144,25 @@ void AlbumView::getTrackListing()
void AlbumView::getDetails()
{
cancel();
QString cachedFile=cacheFileName(Covers::fixArtist(currentSong.albumArtist()), currentSong.album, locale, false);
if (QFile::exists(cachedFile)) {
QFile f(cachedFile);
QtIOCompressor compressor(&f);
compressor.setStreamFormat(QtIOCompressor::GzipFormat);
if (compressor.open(QIODevice::ReadOnly)) {
QByteArray data=compressor.readAll();
engine->cancel();
foreach (const QString &lang, engine->getLangs()) {
QString cachedFile=cacheFileName(Covers::fixArtist(currentSong.albumArtist()), currentSong.album, lang, 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()) {
searchResponse(QString::fromUtf8(data));
Utils::touchFile(cachedFile);
return;
if (!data.isEmpty()) {
searchResponse(QString::fromUtf8(data), QString());
Utils::touchFile(cachedFile);
return;
}
}
}
}
search(currentSong.albumArtist()+" "+currentSong.album);
engine->search(QStringList() << currentSong.albumArtist() << currentSong.album, ContextEngine::Album);
}
void AlbumView::coverRetreived(const Song &s, const QImage &img, const QString &file)
@@ -174,26 +177,22 @@ void AlbumView::coverRetreived(const Song &s, const QImage &img, const QString &
}
}
void AlbumView::searchResponse(const QString &resp)
void AlbumView::searchResponse(const QString &resp, const QString &lang)
{
if (View::constAmbiguous==resp && !triedWithFilter) {
triedWithFilter=true;
search(currentSong.albumArtist()+" "+currentSong.album+" "+i18nc("Search pattern for an album", "(album|score|soundtrack)"));
return;
}
detailsReceived|=Details;
if (All==detailsReceived) {
hideSpinner();
}
if (!resp.isEmpty() && View::constAmbiguous!=resp) {
if (!resp.isEmpty()) {
details=resp;
QFile f(cacheFileName(Covers::fixArtist(currentSong.albumArtist()), currentSong.album, locale, true));
QtIOCompressor compressor(&f);
compressor.setStreamFormat(QtIOCompressor::GzipFormat);
if (compressor.open(QIODevice::WriteOnly)) {
compressor.write(resp.toUtf8().constData());
if (!lang.isEmpty()) {
QFile f(cacheFileName(Covers::fixArtist(currentSong.albumArtist()), currentSong.album, lang, true));
QtIOCompressor compressor(&f);
compressor.setStreamFormat(QtIOCompressor::GzipFormat);
if (compressor.open(QIODevice::WriteOnly)) {
compressor.write(resp.toUtf8().constData());
}
}
updateDetails();
}

View File

@@ -30,6 +30,7 @@ class QImage;
class QNetworkReply;
class QByteArray;
class QUrl;
class ContextEngine;
class AlbumView : public View
{
@@ -53,18 +54,17 @@ public Q_SLOTS:
private:
void getTrackListing();
void getDetails();
void searchResponse(const QString &resp);
void searchResponse(const QString &resp, const QString &lang);
void updateDetails(bool preservePos=false);
void abort();
private:
bool triedWithFilter;
ContextEngine *engine;
int detailsReceived;
QString details;
QString trackList;
QString bioArtist;
QString bio;
QNetworkReply *job;
};
#endif

View File

@@ -29,6 +29,7 @@
#include "musiclibrarymodel.h"
#include "networkaccessmanager.h"
#include "qtiocompressor/qtiocompressor.h"
#include "contextengine.h"
#include <QNetworkReply>
#include <QApplication>
#include <QTextBrowser>
@@ -51,17 +52,18 @@ const QLatin1String ArtistView::constCacheDir("artists/");
const QLatin1String ArtistView::constInfoExt(".html.gz");
const QLatin1String ArtistView::constSimilarInfoExt(".txt");
static QString cacheFileName(const QString &artist, const QString &locale, bool similar, bool createDir)
static QString cacheFileName(const QString &artist, const QString &lang, bool similar, bool createDir)
{
return Utils::cacheDir(ArtistView::constCacheDir, createDir)+
Covers::encodeName(artist)+(similar ? "-similar" : ("."+locale))+(similar ? ArtistView::constSimilarInfoExt : ArtistView::constInfoExt);
Covers::encodeName(artist)+(similar ? "-similar" : ("."+lang))+(similar ? ArtistView::constSimilarInfoExt : ArtistView::constInfoExt);
}
ArtistView::ArtistView(QWidget *parent)
: View(parent)
, triedWithFilter(false)
, currentSimilarJob(0)
{
engine=ContextEngine::create(this);
connect(engine, SIGNAL(searchResult(QString,QString)), this, SLOT(searchResponse(QString,QString)));
connect(Covers::self(), SIGNAL(artistImage(Song,QImage,QString)), SLOT(artistImage(Song,QImage,QString)));
connect(text, SIGNAL(anchorClicked(QUrl)), SLOT(showArtist(QUrl)));
Utils::clearOldCache(constCacheDir, constCacheAge);
@@ -77,7 +79,7 @@ void ArtistView::update(const Song &s, bool force)
{
if (s.isEmpty()) {
currentSong=s;
cancel();
engine->cancel();
clear();
return;
}
@@ -106,7 +108,6 @@ void ArtistView::update(const Song &s, bool force)
clear();
biography.clear();
similarArtists=QString();
triedWithFilter=false;
if (!currentSong.isEmpty()) {
setHeader(currentSong.artist);
@@ -132,31 +133,33 @@ void ArtistView::artistImage(const Song &song, const QImage &i, const QString &f
void ArtistView::loadBio()
{
QString cachedFile=cacheFileName(currentSong.artist, locale, false, false);
if (QFile::exists(cachedFile)) {
QFile f(cachedFile);
QtIOCompressor compressor(&f);
compressor.setStreamFormat(QtIOCompressor::GzipFormat);
if (compressor.open(QIODevice::ReadOnly)) {
QString data=QString::fromUtf8(compressor.readAll());
foreach (const QString &lang, engine->getLangs()) {
QString cachedFile=cacheFileName(currentSong.artist, lang, false, false);
if (QFile::exists(cachedFile)) {
QFile f(cachedFile);
QtIOCompressor compressor(&f);
compressor.setStreamFormat(QtIOCompressor::GzipFormat);
if (compressor.open(QIODevice::ReadOnly)) {
QString data=QString::fromUtf8(compressor.readAll());
if (!data.isEmpty() && View::constAmbiguous!=data) {
searchResponse(data);
loadSimilar();
setBio();
Utils::touchFile(cachedFile);
return;
if (!data.isEmpty()) {
searchResponse(data, QString());
loadSimilar();
setBio();
Utils::touchFile(cachedFile);
return;
}
}
}
}
showSpinner();
search(currentSong.artist);
engine->search(QStringList() << currentSong.artist, ContextEngine::Artist);
}
void ArtistView::loadSimilar()
{
QString cachedFile=cacheFileName(currentSong.artist, locale, true, false);
QString cachedFile=cacheFileName(currentSong.artist, QString(), true, false);
if (QFile::exists(cachedFile)) {
QFile f(cachedFile);
if (f.open(QIODevice::ReadOnly|QIODevice::Text)) {
@@ -193,7 +196,7 @@ void ArtistView::handleSimilarReply()
if (!artists.isEmpty()) {
buildSimilar(artists);
setBio();
QFile f(cacheFileName(reply->property(constNameKey).toString(), locale, true, true));
QFile f(cacheFileName(reply->property(constNameKey).toString(), QString(), true, true));
if (f.open(QIODevice::WriteOnly|QIODevice::Text)) {
QTextStream stream(&f);
foreach (const QString &artist, artists) {
@@ -266,7 +269,7 @@ void ArtistView::requestSimilar()
void ArtistView::abort()
{
cancel();
engine->cancel();
if (currentSimilarJob) {
disconnect(currentSimilarJob, SIGNAL(finished()), this, SLOT(handleSimilarArtistsReply()));
currentSimilarJob->abort();
@@ -274,24 +277,20 @@ void ArtistView::abort()
}
}
void ArtistView::searchResponse(const QString &resp)
void ArtistView::searchResponse(const QString &resp, const QString &lang)
{
if (View::constAmbiguous==resp && !triedWithFilter) {
triedWithFilter=true;
search(currentSong.artist+" "+i18nc("Search pattern for an artist or band", "(artist|band)"));
return;
}
biography=View::constAmbiguous==resp ? QString() : resp;
biography=resp;
emit haveBio(currentSong.artist, resp);
hideSpinner();
if (!resp.isEmpty()) {
QFile f(cacheFileName(currentSong.artist, locale, false, false));
QtIOCompressor compressor(&f);
compressor.setStreamFormat(QtIOCompressor::GzipFormat);
if (compressor.open(QIODevice::WriteOnly)) {
compressor.write(resp.toUtf8().constData());
if (!lang.isEmpty()) {
QFile f(cacheFileName(currentSong.artist, lang, false, false));
QtIOCompressor compressor(&f);
compressor.setStreamFormat(QtIOCompressor::GzipFormat);
if (compressor.open(QIODevice::WriteOnly)) {
compressor.write(resp.toUtf8().constData());
}
}
loadSimilar();
setBio();

View File

@@ -33,6 +33,7 @@ class QNetworkReply;
class QIODevice;
class QImage;
class QUrl;
class ContextEngine;
class ArtistView : public View
{
@@ -62,7 +63,7 @@ private Q_SLOTS:
private:
void loadBio();
void searchResponse(const QString &resp);
void searchResponse(const QString &resp, const QString &lang);
void loadSimilar();
void requestSimilar();
QStringList parseSimilarResponse(const QByteArray &resp);
@@ -70,7 +71,7 @@ private:
void abort();
private:
bool triedWithFilter;
ContextEngine *engine;
QString biography;
QString similarArtists;
QNetworkReply *currentSimilarJob;

71
context/contextengine.cpp Normal file
View File

@@ -0,0 +1,71 @@
/*
* Cantata
*
* Copyright (c) 2011-2013 Craig Drummond <craig.p.drummond@gmail.com>
*
* ----
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "contextengine.h"
#include "wikipediaengine.h"
#include "wikipediasettings.h"
#include <QNetworkReply>
ContextEngine * ContextEngine::create(QObject *parent)
{
return new WikipediaEngine(parent);
}
ContextSettings * ContextEngine::settings(QWidget *parent)
{
return new WikipediaSettings(parent);
}
ContextEngine::ContextEngine(QObject *p)
: QObject(p)
, job(0)
{
}
ContextEngine::~ContextEngine()
{
cancel();
}
void ContextEngine::cancel()
{
if (job) {
job->deleteLater();
job=0;
}
}
QNetworkReply * ContextEngine::getReply(QObject *obj)
{
QNetworkReply *reply = qobject_cast<QNetworkReply*>(obj);
if (!reply) {
return 0;
}
reply->deleteLater();
if (reply!=job) {
return 0;
}
job=0;
return reply;
}

77
context/contextengine.h Normal file
View File

@@ -0,0 +1,77 @@
/*
* Cantata
*
* Copyright (c) 2011-2013 Craig Drummond <craig.p.drummond@gmail.com>
*
* ----
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef CONTEXT_ENGINE_H
#define CONTEXT_ENGINE_H
#include <QObject>
#include <QStringList>
#include <QWidget>
class QNetworkReply;
class ContextSettings : public QWidget {
public:
ContextSettings(QWidget *p=0) : QWidget(p) { }
virtual ~ContextSettings() { }
virtual void load()=0;
virtual void save()=0;
};
class ContextEngine : public QObject
{
Q_OBJECT
public:
enum Mode {
Artist,
Album
};
static void setPreferedLangs(const QStringList &l);
static const QStringList & getPreferedLangs() { return preferredLangs; }
static ContextEngine * create(QObject *parent);
static ContextSettings * settings(QWidget *parent);
ContextEngine(QObject *p);
virtual ~ContextEngine();
virtual const QStringList & getLangs()=0;
void cancel();
public Q_SLOTS:
virtual void search(const QStringList &query, Mode mode)=0;
Q_SIGNALS:
void searchResult(const QString &html, const QString &lang);
protected:
QNetworkReply * getReply(QObject *obj);
protected:
static QStringList preferredLangs;
QNetworkReply *job;
};
#endif

View File

@@ -231,7 +231,6 @@ void SongView::update(const Song &s, bool force)
if (s.isEmpty()) {
currentSong=s;
View::cancel();
clear();
return;
}

View File

@@ -24,7 +24,6 @@
#include "view.h"
#include "spinner.h"
#include "networkaccessmanager.h"
#include "settings.h"
#include <QLabel>
#include <QTextBrowser>
#include <QImage>
@@ -33,13 +32,10 @@
#include <QNetworkReply>
#include <QLocale>
const QLatin1String View::constAmbiguous("-");
View::View(QWidget *parent)
: QWidget(parent)
, needToUpdate(false)
, spinner(0)
, job(0)
{
QVBoxLayout *layout=new QVBoxLayout(this);
layout->setAlignment(Qt::AlignTop | Qt::AlignLeft);
@@ -60,7 +56,6 @@ View::View(QWidget *parent)
layout->addItem(new QSpacerItem(1, fontMetrics().height()/4, QSizePolicy::Fixed, QSizePolicy::Fixed));
text->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
setEditable(false);
locale=Settings::self()->wikipediaLocale();
}
void View::clear()
@@ -134,248 +129,9 @@ void View::setEditable(bool e)
text->viewport()->setAutoFillBackground(e);
}
void View::searchResponse(const QString &r)
void View::searchResponse(const QString &r, const QString &l)
{
Q_UNUSED(l)
text->setText(r);
}
void View::cancel()
{
if (job) {
disconnect(job, SIGNAL(finished()), this, SLOT(googleAnswer()));
job->deleteLater();
job=0;
}
}
void View::search(const QString &query)
{
cancel();
if (query.isEmpty()) {
return;
}
QUrl url("http://www.google.com/search?as_q="+query+"&ft=i&num=1&as_qdr=all&pws=0&as_sitesearch="+locale+".wikipedia.org");
QNetworkRequest request(url);
QString lang(QLocale::languageToString(QLocale::system().language()));
request.setRawHeader("User-Agent", "Mozilla/5.0 (X11; Linux i686; rv:6.0) Gecko/20100101 Firefox/6.0");
if (!lang.isEmpty()) {
request.setRawHeader("Accept-Language", lang.toAscii());
}
job = NetworkAccessManager::self()->get(request);
connect(job, SIGNAL(finished()), this, SLOT(googleAnswer()));
}
void View::googleAnswer()
{
QNetworkReply *reply=qobject_cast<QNetworkReply *>(sender());
if (!reply) {
return;
}
reply->deleteLater();
if (reply!=job) {
return;
}
job=0;
if (reply->error()) {
searchResponse(QString());
return;
}
QString answer(QString::fromUtf8(reply->readAll()));
int start = answer.indexOf("<h3");
if (start > -1) {
start = answer.indexOf("href=\"", start) + 6;
start = answer.indexOf("http", start);
int end = answer.indexOf("\"", start);
end = qMin(end, answer.indexOf("&amp", start));
answer = answer.mid(start, end - start);
// For "Queensrÿche" google returns http://en.wikipedia.org/wiki/Queensr%25C3%25BFche
// ...but (I think) this should be http://en.wikipedia.org/wiki/Queensr%C3%BFche
// ...so, replace %25 with % :-)
answer.replace("%25", "%");
}
else {
answer.clear();
}
if (!answer.contains("wikipedia.org")) {
searchResponse(QString());
cancel();
return;
}
QUrl wiki;
wiki.setEncodedUrl(answer.replace("/wiki/", "/wiki/Special:Export/").toUtf8());
job = NetworkAccessManager::self()->get(wiki);
job->setProperty("redirect", 0);
connect(job, SIGNAL(finished()), this, SLOT(wikiAnswer()));
}
static QString strip(const QString &string, QString open, QString close, QString inner=QString())
{
QString result;
int next, /*lastLeft, */left = 0;
int pos = string.indexOf(open);
if (pos < 0) {
return string;
}
if (inner.isEmpty()) {
inner = open;
}
while (pos > -1) {
result += string.mid(left, pos - left);
// lastLeft = left;
left = string.indexOf(close, pos);
if (left < 0) { // opens, but doesn't close
break;
} else {
next = pos;
while (next > -1 && left > -1) {
// search for inner iterations
int count = 0;
int lastNext = next;
while ((next = string.indexOf(inner, next+inner.length())) < left && next > -1) {
// count inner section openers
lastNext = next;
++count;
}
next = lastNext; // set back next to last inside opener for next iteration
if (!count) { // no inner sections, skip
break;
}
for (int i = 0; i < count; ++i) { // shift section closers by inside section amount
left = string.indexOf(close, left+close.length());
}
// "continue" - search for next inner section
}
if (left < 0) { // section does not close, skip here
break;
}
left += close.length(); // extend close to next search start
}
if (left < 0) { // section does not close, skip here
break;
}
pos = string.indexOf(open, left); // search next 1st level section opener
}
if (left > -1) { // append last part
result += string.mid(left);
}
return result;
}
void View::wikiAnswer()
{
static const int constMaxRedirects=3;
QNetworkReply *reply=qobject_cast<QNetworkReply *>(sender());
if (!reply) {
return;
}
reply->deleteLater();
if (reply!=job) {
return;
}
job=0;
QVariant redirect = reply->header(QNetworkRequest::LocationHeader);
int numRirects=reply->property("redirect").toInt();
if (redirect.isValid() && ++numRirects<constMaxRedirects) {
job=NetworkAccessManager::self()->get(redirect.toString());
job->setProperty("redirect", numRirects);
connect(job, SIGNAL(finished()), this, SLOT(wikiAnswer()));
return;
}
if (reply->error()) {
searchResponse(QString());
return;
}
QString answer(QString::fromUtf8(reply->readAll()));
if (answer.contains(QLatin1String("{{disambiguation}}"))) {
searchResponse(constAmbiguous);
return;
}
int start = answer.indexOf('>', answer.indexOf("<text")) + 1;
int end = answer.lastIndexOf(QRegExp("\\n[^\\n]*\\n\\{\\{reflist", Qt::CaseInsensitive));
if (end < start) {
end = INT_MAX;
}
int e = answer.lastIndexOf(QRegExp("\\n[^\\n]*\\n&lt;references", Qt::CaseInsensitive));
if (e > start && e < end) {
end = e;
}
e = answer.lastIndexOf(QRegExp("\n==\\s*Sources\\s*=="));
if (e > start && e < end) {
end = e;
}
e = answer.lastIndexOf(QRegExp("\n==\\s*Notes\\s*=="));
if (e > start && e < end) {
end = e;
}
e = answer.lastIndexOf(QRegExp("\n==\\s*References\\s*=="));
if (e > start && e < end) {
end = e;
}
e = answer.lastIndexOf(QRegExp("\n==\\s*External links\\s*=="));
if (e > start && e < end) {
end = e;
}
if (end < start) {
end = answer.lastIndexOf("</text");
}
answer = answer.mid(start, end - start); // strip header/footer
answer = strip(answer, "{{", "}}"); // strip wiki internal stuff
answer.replace("&lt;", "<").replace("&gt;", ">");
answer = strip(answer, "<!--", "-->"); // strip comments
answer.remove(QRegExp("<ref[^>]*/>")); // strip inline refereces
answer = strip(answer, "<ref", "</ref>", "<ref"); // strip refereces
// answer = strip(answer, "<ref ", "</ref>", "<ref"); // strip argumented refereces
answer = strip(answer, "[[File:", "]]", "[["); // strip images etc
answer = strip(answer, "[[Image:", "]]", "[["); // strip images etc
answer.replace(QRegExp("\\[\\[[^\\[\\]]*\\|([^\\[\\]\\|]*)\\]\\]"), "\\1"); // collapse commented links
answer.replace("[['", "[["); // Fixes '74 (e.g. 1974) causing errors!
answer.remove("[[").remove("]]"); // remove wiki link "tags"
answer = answer.trimmed();
// answer.replace(QRegExp("\\n\\{\\|[^\\n]*wikitable[^\\n]*\\n!"), "\n<table><th>");
answer.replace("\n\n", "<br>");
// answer.replace("\n\n", "</p><p align=\"justify\">");
answer.replace(QRegExp("\\n'''([^\\n]*)'''\\n"), "<hr><b>\\1</b>\n");
answer.replace(QRegExp("\\n\\{\\|[^\\n]*\\n"), "\n");
answer.replace(QRegExp("\\n\\|[^\\n]*\\n"), "\n");
answer.replace("\n*", "<br>");
answer.replace("\n", "");
answer.replace("'''", "¬").replace(QRegExp("¬([^¬]*)¬"), "<b>\\1</b>");
answer.replace("''", "¬").replace(QRegExp("¬([^¬]*)¬"), "<i>\\1</i>");
answer.replace("===", "¬").replace(QRegExp("¬([^¬]*)¬"), "<h3>\\1</h3>");
answer.replace("==", "¬").replace(QRegExp("¬([^¬]*)¬"), "<h2>\\1</h2>");
answer.replace("&amp;nbsp;", " ");
answer.replace("<br><h", "<h");
answer.replace("</h2><br>", "</h2>");
answer.replace("</h3><br>", "</h3>");
answer.replace("<h3>=", "<h4>");
answer.replace("</h3>=", "</h4>");
answer.replace("br>;", "br>");
answer.replace("h2>;", "h2>");
answer.replace("h3>;", "h3>");
searchResponse(answer);
}

View File

@@ -39,8 +39,6 @@ class View : public QWidget
{
Q_OBJECT
public:
static const QLatin1String constAmbiguous;
View(QWidget *p);
void clear();
@@ -52,20 +50,12 @@ public:
void showSpinner();
void hideSpinner();
void setEditable(bool e);
virtual void update(const Song &s, bool force)=0;
virtual void searchResponse(const QString &r);
void search(const QString &query);
protected Q_SLOTS:
virtual void searchResponse(const QString &r, const QString &l);
protected:
void cancel();
private Q_SLOTS:
void googleAnswer();
void wikiAnswer();
protected:
QString locale;
Song currentSong;
QString stdHeader;
QLabel *header;
@@ -74,7 +64,6 @@ protected:
bool needToUpdate;
QSize picSize;
Spinner *spinner;
QNetworkReply *job;
};
#endif

447
context/wikipediaengine.cpp Normal file
View File

@@ -0,0 +1,447 @@
/*
* Cantata
*
* Copyright (c) 2011-2013 Craig Drummond <craig.p.drummond@gmail.com>
*
* ----
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "wikipediaengine.h"
#include "networkaccessmanager.h"
#include "localize.h"
#include "settings.h"
#include <QNetworkReply>
#if QT_VERSION >= 0x050000
#include <QUrlQuery>
#endif
#include <QXmlStreamReader>
#include <QRegExp>
#include <QDebug>
static const char * constModeProperty="mode";
static const char * constLangProperty="lang";
static const char * constRedirectsProperty="redirects";
static const char * constQueryProperty="query";
static const int constMaxRedirects=3;
static QString strip(const QString &string, QString open, QString close, QString inner=QString())
{
QString result;
int next, /*lastLeft, */left = 0;
int pos = string.indexOf(open);
if (pos < 0) {
return string;
}
if (inner.isEmpty()) {
inner = open;
}
while (pos > -1) {
result += string.mid(left, pos - left);
// lastLeft = left;
left = string.indexOf(close, pos);
if (left < 0) { // opens, but doesn't close
break;
} else {
next = pos;
while (next > -1 && left > -1) {
// search for inner iterations
int count = 0;
int lastNext = next;
while ((next = string.indexOf(inner, next+inner.length())) < left && next > -1) {
// count inner section openers
lastNext = next;
++count;
}
next = lastNext; // set back next to last inside opener for next iteration
if (!count) { // no inner sections, skip
break;
}
for (int i = 0; i < count; ++i) { // shift section closers by inside section amount
left = string.indexOf(close, left+close.length());
}
// "continue" - search for next inner section
}
if (left < 0) { // section does not close, skip here
break;
}
left += close.length(); // extend close to next search start
}
if (left < 0) { // section does not close, skip here
break;
}
pos = string.indexOf(open, left); // search next 1st level section opener
}
if (left > -1) { // append last part
result += string.mid(left);
}
return result;
}
static QString wikiToHtml(QString answer)
{
int start = answer.indexOf('>', answer.indexOf("<text")) + 1;
int end = answer.lastIndexOf(QRegExp("\\n[^\\n]*\\n\\{\\{reflist", Qt::CaseInsensitive));
if (end < start) {
end = INT_MAX;
}
int e = answer.lastIndexOf(QRegExp("\\n[^\\n]*\\n&lt;references", Qt::CaseInsensitive));
if (e > start && e < end) {
end = e;
}
e = answer.lastIndexOf(QRegExp("\n==\\s*Sources\\s*=="));
if (e > start && e < end) {
end = e;
}
e = answer.lastIndexOf(QRegExp("\n==\\s*Notes\\s*=="));
if (e > start && e < end) {
end = e;
}
e = answer.lastIndexOf(QRegExp("\n==\\s*References\\s*=="));
if (e > start && e < end) {
end = e;
}
e = answer.lastIndexOf(QRegExp("\n==\\s*External links\\s*=="));
if (e > start && e < end) {
end = e;
}
if (end < start) {
end = answer.lastIndexOf("</text");
}
answer = answer.mid(start, end - start); // strip header/footer
answer = strip(answer, "{{", "}}"); // strip wiki internal stuff
answer.replace("&lt;", "<").replace("&gt;", ">");
answer = strip(answer, "<!--", "-->"); // strip comments
answer.remove(QRegExp("<ref[^>]*/>")); // strip inline refereces
answer = strip(answer, "<ref", "</ref>", "<ref"); // strip refereces
// answer = strip(answer, "<ref ", "</ref>", "<ref"); // strip argumented refereces
answer = strip(answer, "[[File:", "]]", "[["); // strip images etc
answer = strip(answer, "[[Image:", "]]", "[["); // strip images etc
answer.replace(QRegExp("\\[\\[[^\\[\\]]*\\|([^\\[\\]\\|]*)\\]\\]"), "\\1"); // collapse commented links
answer.replace("[['", "[["); // Fixes '74 (e.g. 1974) causing errors!
answer.remove("[[").remove("]]"); // remove wiki link "tags"
answer = answer.trimmed();
// answer.replace(QRegExp("\\n\\{\\|[^\\n]*wikitable[^\\n]*\\n!"), "\n<table><th>");
answer.replace("\n\n", "<br>");
// answer.replace("\n\n", "</p><p align=\"justify\">");
answer.replace(QRegExp("\\n'''([^\\n]*)'''\\n"), "<hr><b>\\1</b>\n");
answer.replace(QRegExp("\\n\\{\\|[^\\n]*\\n"), "\n");
answer.replace(QRegExp("\\n\\|[^\\n]*\\n"), "\n");
answer.replace("\n*", "<br>");
answer.replace("\n", "");
answer.replace("'''", "¬").replace(QRegExp("¬([^¬]*)¬"), "<b>\\1</b>");
answer.replace("''", "¬").replace(QRegExp("¬([^¬]*)¬"), "<i>\\1</i>");
answer.replace("===", "¬").replace(QRegExp("¬([^¬]*)¬"), "<h3>\\1</h3>");
answer.replace("==", "¬").replace(QRegExp("¬([^¬]*)¬"), "<h2>\\1</h2>");
answer.replace("&amp;nbsp;", " ");
answer.replace("<br><h", "<h");
answer.replace("</h2><br>", "</h2>");
answer.replace("</h3><br>", "</h3>");
answer.replace("<h3>=", "<h4>");
answer.replace("</h3>=", "</h4>");
answer.replace("br>;", "br>");
answer.replace("h2>;", "h2>");
answer.replace("h3>;", "h3>");
return answer;
}
QStringList WikipediaEngine::preferredLangs;
WikipediaEngine::WikipediaEngine(QObject *p)
: ContextEngine(p)
{
if (preferredLangs.isEmpty()) {
setPreferedLangs(Settings::self()->wikipediaLangs());
}
}
void WikipediaEngine::setPreferedLangs(const QStringList &l)
{
preferredLangs=l;
if (preferredLangs.isEmpty()) {
preferredLangs.append("en");
}
}
void WikipediaEngine::search(const QStringList &query, Mode mode)
{
titles.clear();
requestTitles(query, mode, preferredLangs.first());
}
void WikipediaEngine::requestTitles(const QStringList &query, Mode mode, const QString &lang)
{
cancel();
QUrl url("https://"+lang+".wikipedia.org/w/api.php");
#if QT_VERSION < 0x050000
QUrl &q=url;
#else
QUrlQuery q;
#endif
q.addQueryItem(QLatin1String("action"), QLatin1String("query"));
q.addQueryItem(QLatin1String("list"), QLatin1String("search"));
q.addQueryItem(QLatin1String("srsearch"), query.join(" "));
q.addQueryItem(QLatin1String("srprop"), QLatin1String("size") );
q.addQueryItem(QLatin1String("srredirects"), QString::number(1));
q.addQueryItem(QLatin1String("srlimit"), QString::number(20));
q.addQueryItem(QLatin1String("format"), QLatin1String("xml"));
#if QT_VERSION >= 0x050000
url.setQuery(q);
#endif
job=NetworkAccessManager::self()->get(url);
job->setProperty(constModeProperty, (int)mode);
job->setProperty(constLangProperty, lang);
job->setProperty(constRedirectsProperty, 0);
job->setProperty(constQueryProperty, query);
qWarning() << "XXX requestTitles:" << url.toString();
connect(job, SIGNAL(finished()), this, SLOT(parseTitles()));
}
void WikipediaEngine::parseTitles()
{
QNetworkReply *reply = getReply(sender());
if (!reply) {
return;
}
QByteArray data=reply->readAll();
if (QNetworkReply::NoError!=reply->error() || data.isEmpty()) {
emit searchResult(QString(), QString());
return;
}
QString hostLang = reply->property(constLangProperty).toString();
QStringList query = reply->property(constQueryProperty).toStringList();
Mode mode=(Mode)reply->property(constModeProperty).toInt();
QXmlStreamReader xml(data);
while (!xml.atEnd() && !xml.hasError()) {
xml.readNext();
if (xml.isStartElement() && QLatin1String("search")==xml.name()) {
while (xml.readNextStartElement()) {
if (QLatin1String("p")==xml.name()) {
if (xml.attributes().hasAttribute(QLatin1String("title"))) {
titles << xml.attributes().value(QLatin1String("title")).toString();
}
xml.skipCurrentElement();
} else {
xml.skipCurrentElement();
}
}
}
}
if (titles.isEmpty()) {
QStringList refinePossibleLangs = preferredLangs.filter(QRegExp("^(en|fr|de|pl).*$") );
int index = refinePossibleLangs.indexOf(hostLang);
if (-1!=index && index<refinePossibleLangs.count()-1) {
// Try next language!
requestTitles(query, mode, refinePossibleLangs.value(index + 1).split(QLatin1Char(':')).back());
} else {
emit searchResult(QString(), QString());
}
return;
}
getPage(query, mode, hostLang);
}
static int indexOf(const QStringList &l, const QString &s)
{
for (int i=0; i<l.length(); ++i){
qWarning() << "COMPARE" << l.at(i) << s << l.at(i).compare(s, Qt::CaseInsensitive);
if (0==l.at(i).compare(s, Qt::CaseInsensitive)) {
return i;
}
}
return -1;
}
void WikipediaEngine::getPage(const QStringList &query, Mode mode, const QString &lang)
{
QStringList queryCopy(query);
QStringList queries;
while(!queryCopy.isEmpty()) {
queries.append(queryCopy.join(" "));
queryCopy.takeFirst();
}
#if 0
// Amarok original - not working for me :-(
qWarning() << "XXX Titles:" << titles;
QString basePattern;
switch (mode)
{
default:
case Artist:
basePattern=i18nc("Search pattern for an artist or band", ".*\\(.*(artist|band).*\\))").toLatin1();
break;
case Album:
basePattern=i18nc("Search pattern for an album", ".*\\(.*(album|score|soundtrack).*\\)").toLatin1();
break;
}
int index=-1;
foreach (const QString &q, queries) {
QString pattern=q+basePattern;
index = titles.indexOf(QRegExp(pattern, Qt::CaseInsensitive));
qWarning() << "XXX Query[a]:" << q << pattern << index;
if (-1==index) {
QString q2=q;
q2.remove(".");
pattern=q2+basePattern;
index = titles.indexOf(QRegExp(pattern, Qt::CaseInsensitive));
qWarning() << "XXX Query[b]:" << q2 << pattern << index;
}
if (-1!=index) {
break;
}
}
#else
QStringList patterns;
switch (mode)
{
default:
case Artist:
patterns=i18nc("Search pattern for an artist or band, separated by |", "artist|band").split("|", QString::SkipEmptyParts);
break;
case Album:
patterns=i18nc("Search pattern for an album, separated by |", "album|score|soundtrack").split("|", QString::SkipEmptyParts);
break;
}
qWarning() << "XXX Titles:" << titles;
int index=-1;
foreach (const QString &q, queries) {
qWarning() << "XXX Query:" << q;
qWarning() << "XXX - patterns";
// First check original query with one of the patterns...
foreach (const QString &pattern, patterns) {
index=indexOf(titles, q+" ("+pattern+")");
if (-1!=index) {
qWarning() << "XXX match[a]" << index << q;
break;
}
}
if (-1==index && q.contains(".")) {
qWarning() << "XXX - patterns (no dots)";
// Now try by removing all dots (A.S.A.P. -> ASAP)
QString query2=q;
query2.remove(".");
foreach (const QString &pattern, patterns) {
index=indexOf(titles, query2+" ("+pattern+")");
if (-1!=index) {
qWarning() << "XXX match[b]" << index << q;
break;
}
}
}
if (-1==index) {
// Try without any pattern...
qWarning() << "XXX - no pattern";
index=indexOf(titles, q);
if (-1!=index) {
qWarning() << "XXX match[c]" << index << q;
}
}
if (-1==index && q.contains(".")) {
// Try without any pattern, and no dots..
qWarning() << "XXX - no pattern no dots";
QString query2=q;
query2.remove(".");
index=indexOf(titles, query2);
if (-1!=index) {
qWarning() << "XXX match[d]" << index << q;
}
}
if (-1!=index) {
break;
}
}
#endif
// TODO: If we fail to find a match, prompt user???
const QString title=titles.takeAt(-1==index ? 0 : index);
QUrl url;
url.setScheme(QLatin1String("https"));
url.setHost(lang+".wikipedia.org");
url.setPath("/wiki/Special:Export/"+title);
job=NetworkAccessManager::self()->get(url);
job->setProperty(constModeProperty, (int)mode);
job->setProperty(constLangProperty, lang);
job->setProperty(constQueryProperty, query);
job->setProperty(constRedirectsProperty, 0);
qWarning() << "XXX getPage:" << url.toString();
connect(job, SIGNAL(finished()), this, SLOT(parsePage()));
}
void WikipediaEngine::parsePage()
{
QNetworkReply *reply = getReply(sender());
if (!reply) {
return;
}
QVariant redirect = reply->header(QNetworkRequest::LocationHeader);
int numRirects=reply->property(constRedirectsProperty).toInt();
if (redirect.isValid() && ++numRirects<constMaxRedirects) {
job=NetworkAccessManager::self()->get(redirect.toString());
job->setProperty(constRedirectsProperty, numRirects);
job->setProperty(constLangProperty, reply->property(constLangProperty));
job->setProperty(constModeProperty, reply->property(constModeProperty));
job->setProperty(constQueryProperty, reply->property(constQueryProperty));
connect(job, SIGNAL(finished()), this, SLOT(parsePage()));
return;
}
QByteArray data=reply->readAll();
if (QNetworkReply::NoError!=reply->error() || data.isEmpty()) {
emit searchResult(QString(), QString());
return;
}
QString answer(QString::fromUtf8(data));
qWarning() << "XXX ANS:" << answer;
if (answer.contains(QLatin1String("{{disambiguation}}")) || answer.contains(QLatin1String("{{disambig}}"))) { // i18n???
getPage(reply->property(constQueryProperty).toStringList(), (Mode)reply->property(constModeProperty).toInt(),
reply->property(constLangProperty).toString());
return;
}
emit searchResult(wikiToHtml(answer), reply->property(constLangProperty).toString());
}

View File

@@ -0,0 +1,273 @@
/*
* Cantata
*
* Copyright (c) 2011-2013 Craig Drummond <craig.p.drummond@gmail.com>
*
* ----
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "wikipediasettings.h"
#include "wikipediaengine.h"
#include "networkaccessmanager.h"
#include "localize.h"
#include "icon.h"
#include "spinner.h"
#include "settings.h"
#include "qtiocompressor/qtiocompressor.h"
#include "utils.h"
#include <QNetworkReply>
#if QT_VERSION >= 0x050000
#include <QUrlQuery>
#endif
#include <QXmlStreamReader>
#include <QFile>
static QString localeFile() {
return Utils::configDir(QString(), true)+"wikipedia-langs.xml.gz";
}
WikipediaSettings::WikipediaSettings(QWidget *p)
: ContextSettings(p)
, needToUpdate(true)
, job(0)
, spinner(0)
{
setupUi(this);
connect(upButton, SIGNAL(clicked()), SLOT(moveUp()));
connect(downButton, SIGNAL(clicked()), SLOT(moveDown()));
connect(reload, SIGNAL(clicked()), SLOT(getLangs()));
connect(addButton, SIGNAL(clicked()), SLOT(add()));
connect(removeButton, SIGNAL(clicked()), SLOT(remove()));
connect(langs, SIGNAL(currentItemChanged(QListWidgetItem*,QListWidgetItem*)), SLOT(currentLangChanged(QListWidgetItem*)));
connect(preferredLangs, SIGNAL(currentItemChanged(QListWidgetItem*,QListWidgetItem*)), SLOT(currentPreferredLangChanged(QListWidgetItem*)));
upButton->setIcon(Icon("go-up"));
downButton->setIcon(Icon("go-down"));
addButton->setIcon(Icon("list-add"));
removeButton->setIcon(Icon("list-remove"));
upButton->setEnabled(false);
downButton->setEnabled(false);
addButton->setEnabled(false);
removeButton->setEnabled(false);
}
void WikipediaSettings::showEvent(QShowEvent *e)
{
if (needToUpdate) {
needToUpdate=false;
QByteArray data;
QString fileName=localeFile();
if (QFile::exists(fileName)) {
QFile f(fileName);
QtIOCompressor compressor(&f);
compressor.setStreamFormat(QtIOCompressor::GzipFormat);
if (compressor.open(QIODevice::ReadOnly)) {
data=compressor.readAll();
}
}
if (data.isEmpty()) {
getLangs();
} else {
parseLangs(data);
}
}
QWidget::showEvent(e);
}
void WikipediaSettings::load()
{
}
void WikipediaSettings::save()
{
QStringList pref;
for (int i=0; i<preferredLangs->count(); ++i) {
pref.append(preferredLangs->item(i)->data(Qt::UserRole).toString());
}
if (pref.isEmpty()) {
pref.append("en");
}
Settings::self()->saveWikipediaLangs(pref);
}
void WikipediaSettings::cancel()
{
if (job) {
disconnect(job, SIGNAL(finished()), this, SLOT(parseLangs()));
job->deleteLater();
job=0;
}
}
void WikipediaSettings::getLangs()
{
if (!spinner) {
spinner=new Spinner(langs);
spinner->setWidget(langs);
}
spinner->start();
langs->clear();
preferredLangs->clear();
reload->setEnabled(false);
cancel();
QUrl url("https://en.wikipedia.org/w/api.php");
#if QT_VERSION < 0x050000
QUrl &q=url;
#else
QUrlQuery q;
#endif
q.addQueryItem(QLatin1String("action"), QLatin1String("query"));
q.addQueryItem(QLatin1String("meta"), QLatin1String("siteinfo"));
q.addQueryItem(QLatin1String("siprop"), QLatin1String("interwikimap"));
q.addQueryItem(QLatin1String("sifilteriw"), QLatin1String("local"));
q.addQueryItem(QLatin1String("format"), QLatin1String("xml"));
#if QT_VERSION >= 0x050000
url.setQuery(q);
#endif
job=NetworkAccessManager::self()->get(url);
connect(job, SIGNAL(finished()), this, SLOT(parseLangs()));
}
void WikipediaSettings::parseLangs()
{
QNetworkReply *reply = qobject_cast<QNetworkReply*>(sender());
if (!reply) {
return;
}
reply->deleteLater();
if (reply!=job) {
return;
}
job=0;
parseLangs(reply->readAll());
}
void WikipediaSettings::parseLangs(const QByteArray &data)
{
QStringList preferred=WikipediaEngine::getPreferedLangs();
QXmlStreamReader xml(data);
QMap<int, QListWidgetItem *> prefMap;
while (!xml.atEnd() && !xml.hasError()) {
xml.readNext();
if( xml.isStartElement() && QLatin1String("iw")==xml.name()) {
const QXmlStreamAttributes &a = xml.attributes();
if (a.hasAttribute(QLatin1String("prefix")) && a.hasAttribute(QLatin1String("language")) && a.hasAttribute(QLatin1String("url"))) {
// The urlPrefix is the lang code infront of the wikipedia host
// url. It is mostly the same as the "prefix" attribute but in
// some weird cases they differ, so we can't just use "prefix".
QString prefix=QUrl(a.value(QLatin1String("url")).toString()).host().remove(QLatin1String(".wikipedia.org"));
int index=preferred.indexOf(prefix);
QListWidgetItem *item = new QListWidgetItem(-1==index ? langs : preferredLangs);
item->setText(QString("[%1] %2").arg(a.value(QLatin1String("prefix")).toString()).arg(a.value(QLatin1String("language")).toString()));
item->setData(Qt::UserRole, prefix);
if (-1!=index) {
prefMap[index]=item;
}
}
}
}
QMap<int, QListWidgetItem *>::ConstIterator it(prefMap.constBegin());
QMap<int, QListWidgetItem *>::ConstIterator end(prefMap.constEnd());
for (; it!=end; ++it) {
int row=preferredLangs->row(it.value());
if (row!=it.key()) {
preferredLangs->insertItem(it.key(), preferredLangs->takeItem(row));
}
}
QFile f(localeFile());
QtIOCompressor compressor(&f);
compressor.setStreamFormat(QtIOCompressor::GzipFormat);
if (compressor.open(QIODevice::WriteOnly)) {
compressor.write(data);
}
reload->setEnabled(true);
if (spinner) {
spinner->stop();
}
}
void WikipediaSettings::currentLangChanged(QListWidgetItem *item)
{
addButton->setEnabled(item);
}
void WikipediaSettings::currentPreferredLangChanged(QListWidgetItem *item)
{
if (!item) {
upButton->setEnabled(false);
downButton->setEnabled(false);
removeButton->setEnabled(false);
} else {
const int row = langs->row(item);
upButton->setEnabled(row != 0);
downButton->setEnabled(row != langs->count() - 1);
}
removeButton->setEnabled(item);
}
void WikipediaSettings::moveUp()
{
move(-1);
}
void WikipediaSettings::moveDown()
{
move(+1);
}
void WikipediaSettings::add()
{
int index=langs->currentRow();
if (index<0 || index>langs->count()) {
return;
}
QListWidgetItem *item = langs->takeItem(index);
QListWidgetItem *newItem=new QListWidgetItem(preferredLangs);
newItem->setText(item->text());
newItem->setData(Qt::UserRole, item->data(Qt::UserRole));
delete item;
}
void WikipediaSettings::remove()
{
int index=preferredLangs->currentRow();
if (index<0 || index>preferredLangs->count()) {
return;
}
QListWidgetItem *item = preferredLangs->takeItem(index);
QListWidgetItem *newItem=new QListWidgetItem(langs);
newItem->setText(item->text());
newItem->setData(Qt::UserRole, item->data(Qt::UserRole));
delete item;
}
void WikipediaSettings::move(int d)
{
const int row = preferredLangs->currentRow();
QListWidgetItem *item = preferredLangs->takeItem(row);
preferredLangs->insertItem(row + d, item);
preferredLangs->setCurrentRow(row + d);
}

View File

@@ -0,0 +1,66 @@
/*
* Cantata
*
* Copyright (c) 2011-2013 Craig Drummond <craig.p.drummond@gmail.com>
*
* ----
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef WIKIPEDIA_SETTINGS_H
#define WIKIPEDIA_SETTINGS_H
#include "contextengine.h"
#include "ui_wikipediasettings.h"
class QNetworkReply;
class QShowEvent;
class Spinner;
class WikipediaSettings : public ContextSettings, private Ui::WikipediaSettings
{
Q_OBJECT
public:
WikipediaSettings(QWidget *p);
void load();
void save();
void cancel();
void showEvent(QShowEvent *e);
private Q_SLOTS:
void getLangs();
void parseLangs();
void moveUp();
void moveDown();
void move(int d);
void add();
void remove();
void currentLangChanged(QListWidgetItem *item);
void currentPreferredLangChanged(QListWidgetItem *item);
private:
void parseLangs(const QByteArray &data);
private:
bool needToUpdate;
QNetworkReply *job;
Spinner *spinner;
};
#endif

View File

@@ -0,0 +1,164 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>WikipediaSettings</class>
<widget class="QWidget" name="WikipediaSettings">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>437</width>
<height>362</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayout">
<property name="margin">
<number>0</number>
</property>
<item row="0" column="0" colspan="4">
<widget class="QLabel" name="label">
<property name="text">
<string>Choose the wikipedia languages you want to use when searching for artist and album information.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Available:</string>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Selected:</string>
</property>
</widget>
</item>
<item row="2" column="0" rowspan="6">
<widget class="QListWidget" name="langs">
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="sortingEnabled">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="1">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>74</height>
</size>
</property>
</spacer>
</item>
<item row="2" column="2" rowspan="6">
<widget class="QListWidget" name="preferredLangs">
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="sortingEnabled">
<bool>false</bool>
</property>
</widget>
</item>
<item row="2" column="3" rowspan="2">
<spacer name="verticalSpacer_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>89</height>
</size>
</property>
</spacer>
</item>
<item row="3" column="1" rowspan="2">
<widget class="QToolButton" name="addButton">
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item row="4" column="3" rowspan="2">
<widget class="QToolButton" name="upButton">
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item row="5" column="1" rowspan="2">
<widget class="QToolButton" name="removeButton">
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item row="7" column="3">
<spacer name="verticalSpacer_4">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>73</height>
</size>
</property>
</spacer>
</item>
<item row="6" column="3">
<widget class="QToolButton" name="downButton">
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item row="7" column="1">
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>73</height>
</size>
</property>
</spacer>
</item>
<item row="8" column="0">
<widget class="QPushButton" name="reload">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>Reload</string>
</property>
</widget>
</item>
</layout>
</widget>
<tabstops>
<tabstop>langs</tabstop>
<tabstop>addButton</tabstop>
<tabstop>removeButton</tabstop>
<tabstop>preferredLangs</tabstop>
<tabstop>upButton</tabstop>
<tabstop>downButton</tabstop>
<tabstop>reload</tabstop>
</tabstops>
<resources/>
<connections/>
</ui>

View File

@@ -33,6 +33,7 @@
#ifdef TAGLIB_FOUND
#include "httpserversettings.h"
#endif
#include "contextengine.h"
#include "lyricsettings.h"
#include "cachesettings.h"
#include "localize.h"
@@ -66,6 +67,7 @@ PreferencesDialog::PreferencesDialog(QWidget *parent)
playback = new PlaybackSettings(widget);
files = new FileSettings(widget);
interface = new InterfaceSettings(widget);
context = ContextEngine::settings(widget);
lyrics = new LyricSettings(widget);
cache = new CacheSettings(widget);
server->load();
@@ -89,6 +91,8 @@ PreferencesDialog::PreferencesDialog(QWidget *parent)
http=0;
}
#endif
widget->addPage(context, i18n("Context"), Icons::contextIcon, i18n("Context View Settings"));
context->load();
widget->addPage(lyrics, i18n("Lyrics"), Icons::lyricsIcon, i18n("Lyrics Settings"));
#if defined CDDB_FOUND || defined MUSICBRAINZ5_FOUND
audiocd = new AudioCdSettings(widget);
@@ -140,6 +144,7 @@ void PreferencesDialog::writeSettings()
#if defined CDDB_FOUND || defined MUSICBRAINZ5_FOUND
audiocd->save();
#endif
context->save();
lyrics->save();
Settings::self()->save();
emit settingsSaved();

View File

@@ -36,6 +36,7 @@ class ServerPlaybackSettings;
class PlaybackSettings;
class FileSettings;
class InterfaceSettings;
class ContextSettings;
class LyricSettings;
#ifdef TAGLIB_FOUND
class HttpServerSettings;
@@ -73,6 +74,7 @@ private:
PlaybackSettings *playback;
FileSettings *files;
InterfaceSettings *interface;
ContextSettings *context;
LyricSettings *lyrics;
#ifdef TAGLIB_FOUND
HttpServerSettings *http;

View File

@@ -452,9 +452,10 @@ QStringList Settings::lyricProviders()
return GET_STRINGLIST("lyricProviders", def);
}
QString Settings::wikipediaLocale()
QStringList Settings::wikipediaLangs()
{
return GET_STRING("wikipediaLocale", "en");
QStringList def=QStringList() << "en";
return GET_STRINGLIST("wikipediaLangs", def);
}
@@ -857,9 +858,9 @@ void Settings::saveLyricProviders(const QStringList &v)
SET_VALUE_MOD(lyricProviders)
}
void Settings::saveWikipediaLocale(const QString &v)
void Settings::saveWikipediaLangs(const QStringList &v)
{
SET_VALUE_MOD(wikipediaLocale)
SET_VALUE_MOD(wikipediaLangs)
}
void Settings::savePage(const QString &v)

View File

@@ -125,7 +125,7 @@ public:
bool groupSingle();
bool groupMultiple();
QStringList lyricProviders();
QString wikipediaLocale();
QStringList wikipediaLangs();
QString page();
QStringList hiddenPages();
bool gnomeMediaKeys();
@@ -204,7 +204,7 @@ public:
void saveGroupSingle(bool v);
void saveGroupMultiple(bool v);
void saveLyricProviders(const QStringList &v);
void saveWikipediaLocale(const QString &v);
void saveWikipediaLangs(const QStringList &v);
void savePage(const QString &v);
void saveHiddenPages(const QStringList &v);
void saveGnomeMediaKeys(bool v);

View File

@@ -331,6 +331,7 @@ Icon Icons::streamsIcon;
#ifdef ENABLE_ONLINE_SERVICES
Icon Icons::onlineIcon;
#endif
Icon Icons::contextIcon;
Icon Icons::lyricsIcon;
Icon Icons::infoIcon;
#ifdef ENABLE_DEVICES_SUPPORT
@@ -626,9 +627,19 @@ void Icons::initToolbarIcons(const QColor &color, bool forceLight)
QColor col=QApplication::palette().color(QPalette::Active, QPalette::ButtonText);
infoIcon=loadSidebarIcon("info", col, col);
}
if (infoIcon.isNull()) {
infoIcon=Icon("dialog-information");
}
#if !defined ENABLE_KDE_SUPPORT && !defined Q_OS_WIN
if (QIcon::themeName()==QLatin1String("gnome")) {
QColor col=QApplication::palette().color(QPalette::Active, QPalette::ButtonText);
contextIcon=loadSidebarIcon("info", col, col);
} else
#endif
contextIcon=Icon("dialog-information");
if (toolbarPrevIcon.isNull()) {
toolbarPrevIcon=Icon::getMediaIcon("media-skip-backward");
} else {

View File

@@ -77,6 +77,7 @@ namespace Icons
#ifdef ENABLE_ONLINE_SERVICES
extern Icon onlineIcon;
#endif
extern Icon contextIcon;
extern Icon lyricsIcon;
extern Icon infoIcon;
#ifdef ENABLE_DEVICES_SUPPORT