/* * Cantata * * Copyright (c) 2011-2014 Craig Drummond * * ---- * * 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 "contextwidget.h" #include "artistview.h" #include "albumview.h" #include "songview.h" #include "song.h" #include "utils.h" #include "covers.h" #include "networkaccessmanager.h" #include "settings.h" #include "wikipediaengine.h" #include "localize.h" #include "backdropcreator.h" #include "qjson/parser.h" #include #include #include #include #if QT_VERSION >= 0x050000 #include #endif #include #include #include #include #ifndef SCALE_CONTEXT_BGND #include #endif #include #include #include #include #include #include #include static bool debugEnabled=false; #define DBUG if (debugEnabled) qWarning() << metaObject()->className() << __FUNCTION__ void ContextWidget::enableDebug() { debugEnabled=true; } static const QString constBackdropName=QLatin1String("backdrop"); //const QLatin1String ContextWidget::constHtbApiKey(0); // API key required const QLatin1String ContextWidget::constFanArtApiKey("ee86404cb429fa27ac32a1a3c117b006"); const QLatin1String ContextWidget::constCacheDir("backdrops/"); static const double constBgndOpacity=0.15; static QString cacheFileName(const QString &artist, bool createDir) { return Utils::cacheDir(ContextWidget::constCacheDir, createDir)+Covers::encodeName(artist)+".jpg"; } static QImage setOpacity(const QImage &orig) { QImage img=QImage::Format_ARGB32==orig.format() ? orig : orig.convertToFormat(QImage::Format_ARGB32); uchar *bits = img.bits(); for (int i = 0; i < img.height()*img.bytesPerLine(); i+=4) { bits[i+3] = constBgndOpacity*255; } return img; } static QColor splitterColor; class ThinSplitterHandle : public QSplitterHandle { public: ThinSplitterHandle(Qt::Orientation orientation, ThinSplitter *parent) : QSplitterHandle(orientation, parent) , underMouse(false) { setMask(QRegion(contentsRect())); setAttribute(Qt::WA_MouseNoMask, true); setAttribute(Qt::WA_OpaquePaintEvent, false); setAttribute(Qt::WA_MouseTracking, true); QAction *act=new QAction(i18n("Reset Spacing"), this); addAction(act); connect(act, SIGNAL(triggered(bool)), parent, SLOT(reset())); setContextMenuPolicy(Qt::ActionsContextMenu); size=Utils::isHighDpi() ? 4 : 2; } void resizeEvent(QResizeEvent *event) { if (Qt::Horizontal==orientation()) { setContentsMargins(size, 0, size, 0); } else { setContentsMargins(0, size, 0, size); } setMask(QRegion(contentsRect())); QSplitterHandle::resizeEvent(event); } void paintEvent(QPaintEvent *event) { if (underMouse) { QColor col(splitterColor); QPainter p(this); col.setAlphaF(0.75); p.fillRect(event->rect().adjusted(1, 0, -1, 0), col); col.setAlphaF(0.25); p.fillRect(event->rect(), col); } } bool event(QEvent *event) { switch(event->type()) { case QEvent::Enter: case QEvent::HoverEnter: underMouse = true; update(); break; case QEvent::ContextMenu: case QEvent::Leave: case QEvent::HoverLeave: underMouse = false; update(); break; default: break; } return QWidget::event(event); } bool underMouse; int size; }; ThinSplitter::ThinSplitter(QWidget *parent) : QSplitter(parent) { setHandleWidth(3); setChildrenCollapsible(false); setOrientation(Qt::Horizontal); } QSplitterHandle * ThinSplitter::createHandle() { return new ThinSplitterHandle(orientation(), this); } void ThinSplitter::reset() { int totalSize=0; foreach (int s, sizes()) { totalSize+=s; } QList newSizes; int size=totalSize/count(); for (int i=0; ipicSize().width()*2.5; artist->addEventFilter(this); album->addEventFilter(this); song->addEventFilter(this); connect(artist, SIGNAL(findArtist(QString)), this, SIGNAL(findArtist(QString))); connect(artist, SIGNAL(findAlbum(QString,QString)), this, SIGNAL(findAlbum(QString,QString))); connect(album, SIGNAL(playSong(QString)), this, SIGNAL(playSong(QString))); readConfig(); setZoom(); setWide(true); splitterColor=palette().text().color(); #ifndef SCALE_CONTEXT_BGND QDesktopWidget *dw=QApplication::desktop(); if (dw) { QSize geo=dw->availableGeometry(this).size()-QSize(32, 64); minBackdropSize=geo; minBackdropSize.setWidth(((int)(minBackdropSize.width()/32))*32); minBackdropSize.setHeight(((int)(minBackdropSize.height()/32))*32); maxBackdropSize=QSize(geo.width()*1.25, geo.height()*1.25); } else if (Utils::isHighDpi()) { minBackdropSize=QSize(1024*3, 768*3); maxBackdropSize=QSize(minBackdropSize.width()*2, minBackdropSize.height()*2); } else { minBackdropSize=QSize(1024, 768); maxBackdropSize=QSize(minBackdropSize.width()*2, minBackdropSize.height()*2); } #endif } void ContextWidget::setZoom() { int zoom=Settings::self()->contextZoom(); if (zoom) { artist->setZoom(zoom); album->setZoom(zoom); song->setZoom(zoom); } } void ContextWidget::setWide(bool w) { if (w==isWide) { return; } isWide=w; if (w) { if (layout()) { delete layout(); } QHBoxLayout *l=new QHBoxLayout(this); setLayout(l); int m=l->margin()/2; l->setMargin(0); if (stack) { stack->setVisible(false); viewCombo->setVisible(false); stack->removeWidget(artist); stack->removeWidget(album); stack->removeWidget(song); artist->setVisible(true); album->setVisible(true); song->setVisible(true); } l->addItem(new QSpacerItem(m, m, QSizePolicy::Fixed, QSizePolicy::Fixed)); QByteArray state; bool resetSplitter=splitter; if (!splitter) { splitter=new ThinSplitter(this); state=Settings::self()->contextSplitterState(); } l->addWidget(splitter); artist->setParent(splitter); album->setParent(splitter); song->setParent(splitter); splitter->addWidget(artist); splitter->addWidget(album); splitter->setVisible(true); splitter->addWidget(song); if (resetSplitter) { splitter->reset(); } else if (!state.isEmpty()) { splitter->restoreState(state); } // l->addWidget(album); // l->addWidget(song); // layout->addItem(new QSpacerItem(m, m, QSizePolicy::Fixed, QSizePolicy::Fixed)); // l->setStretch(1, 1); // l->setStretch(2, 1); // l->setStretch(3, 1); } else { if (layout()) { delete layout(); } QGridLayout *l=new QGridLayout(this); setLayout(l); int m=l->margin()/2; l->setMargin(0); l->setSpacing(0); if (!stack) { stack=new QStackedWidget(this); } if (!viewCombo) { viewCombo=new QComboBox(this); viewCombo->addItem(i18n("Artist Information"), "artist"); viewCombo->addItem(i18n("Album Information"), "album"); viewCombo->addItem(i18n("Lyrics"), "song"); connect(viewCombo, SIGNAL(activated(int)), stack, SLOT(setCurrentIndex(int))); } if (splitter) { splitter->setVisible(false); } stack->setVisible(true); viewCombo->setVisible(true); artist->setParent(stack); album->setParent(stack); song->setParent(stack); stack->addWidget(artist); stack->addWidget(album); stack->addWidget(song); l->addItem(new QSpacerItem(m, m, QSizePolicy::Fixed, QSizePolicy::Fixed), 0, 0, 1, 1); l->addWidget(stack, 0, 1, 1, 1); l->addWidget(viewCombo, 1, 0, 1, 2); QString lastSaved=Settings::self()->contextSlimPage(); if (!lastSaved.isEmpty()) { for (int i=0; icount(); ++i) { if (viewCombo->itemData(i).toString()==lastSaved) { viewCombo->setCurrentIndex(i); stack->setCurrentIndex(i); break; } } } } } void ContextWidget::resizeEvent(QResizeEvent *e) { if (isVisible()) { setWide(width()>minWidth && !alwaysCollapsed); } #ifdef SCALE_CONTEXT_BGND resizeBackdrop(); #endif QWidget::resizeEvent(e); } void ContextWidget::readConfig() { useBackdrop(Settings::self()->contextBackdrop()); useDarkBackground(Settings::self()->contextDarkBackground()); WikipediaEngine::setIntroOnly(Settings::self()->wikipediaIntroOnly()); bool wasCollpased=stack && stack->isVisible(); alwaysCollapsed=Settings::self()->contextAlwaysCollapsed(); if (alwaysCollapsed && !wasCollpased) { setWide(false); } } void ContextWidget::saveConfig() { Settings::self()->saveContextZoom(artist->getZoom()); if (viewCombo) { Settings::self()->saveContextSlimPage(viewCombo->itemData(viewCombo->currentIndex()).toString()); } if (splitter) { Settings::self()->saveContextSplitterState(splitter->saveState()); } } void ContextWidget::useBackdrop(bool u) { if (u!=drawBackdrop) { drawBackdrop=u; if (isVisible() && !currentArtist.isEmpty()) { updateArtist=currentArtist; currentArtist.clear(); updateBackdrop(); QWidget::update(); } } } void ContextWidget::useDarkBackground(bool u) { if (u!=darkBackground) { darkBackground=u; QPalette pal=darkBackground ? palette() : parentWidget()->palette(); QColor prevLinkColor; QColor linkCol; if (darkBackground) { QColor dark(32, 32, 32); QColor light(240, 240, 240); QColor linkVisited(164, 164, 164); pal.setColor(QPalette::Window, dark); pal.setColor(QPalette::Base, dark); // Dont globally change window/button text - else this can mess up scrollbar buttons // with some styles (e.g. plastique) // pal.setColor(QPalette::WindowText, light); // pal.setColor(QPalette::ButtonText, light); pal.setColor(QPalette::Text, light); pal.setColor(QPalette::Link, light); pal.setColor(QPalette::LinkVisited, linkVisited); prevLinkColor=appLinkColor; linkCol=pal.color(QPalette::Link); splitterColor=light; } else { linkCol=appLinkColor; prevLinkColor=QColor(240, 240, 240); splitterColor=pal.text().color(); } setPalette(pal); artist->setPal(pal, linkCol, prevLinkColor); album->setPal(pal, linkCol, prevLinkColor); song->setPal(pal, linkCol, prevLinkColor); QWidget::update(); } } void ContextWidget::showEvent(QShowEvent *e) { setWide(width()>minWidth && !alwaysCollapsed); if (drawBackdrop) { updateBackdrop(); } QWidget::showEvent(e); } void ContextWidget::paintEvent(QPaintEvent *e) { QPainter p(this); QRect r(rect()); if (!isWide && viewCombo) { int space=2; // fontMetrics().height()/4; r.adjust(0, 0, 0, -(viewCombo->rect().height()+space)); } if (darkBackground) { p.fillRect(r, palette().background().color()); } if (drawBackdrop) { if (!oldBackdrop.isNull()) { if (!qFuzzyCompare(fadeValue, qreal(0.0))) { p.setOpacity(1.0-fadeValue); } p.fillRect(r, QBrush(oldBackdrop)); } if (!currentBackdrop.isNull()) { p.setOpacity(fadeValue); #ifdef SCALE_CONTEXT_BGND if (!albumCoverBackdrop && currentBackdrop.height()isVisible()) { song->search(); } } void ContextWidget::update(const Song &s) { Song sng=s; if (sng.isVariousArtists()) { sng.revertVariousArtists(); } if (sng.isStream() && !sng.isCantataStream() && !sng.isCdda() && sng.artist.isEmpty() && sng.albumartist.isEmpty() && sng.album.isEmpty()) { int pos=sng.title.indexOf(QLatin1String(" - ")); if (pos>3) { sng.artist=sng.title.left(pos); sng.title=sng.title.mid(pos+3); } } artist->update(sng); album->update(sng); song->update(sng); currentSong=s; updateArtist=Covers::fixArtist(sng.basicArtist()); if (isVisible() && drawBackdrop) { updateBackdrop(); } } bool ContextWidget::eventFilter(QObject *o, QEvent *e) { if (QEvent::Wheel==e->type()) { QWheelEvent *we=static_cast(e); if (Qt::ControlModifier==we->modifiers()) { int numDegrees = static_cast(e)->delta() / 8; int numSteps = numDegrees / 15; artist->setZoom(numSteps); album->setZoom(numSteps); song->setZoom(numSteps); return true; } } return QObject::eventFilter(o, e); } void ContextWidget::cancel() { if (job) { job->deleteLater(); job=0; } } void ContextWidget::updateBackdrop() { DBUG << updateArtist << currentArtist << currentSong.file; if (updateArtist==currentArtist) { return; } currentArtist=updateArtist; backdropAlbums.clear(); if (currentArtist.isEmpty()) { updateImage(QImage()); QWidget::update(); return; } if (!currentSong.isStream() && MPDConnection::self()->getDetails().dirReadable) { bool localNonMpd=currentSong.file.startsWith(Utils::constDirSep); QString dirName=localNonMpd ? QString() : MPDConnection::self()->getDetails().dir; if (localNonMpd || (!dirName.isEmpty() && !dirName.startsWith(QLatin1String("http:/")))) { dirName+=Utils::getDir(currentSong.file); QString encoded=Covers::encodeName(currentArtist); QStringList names=QStringList() << encoded+"-"+constBackdropName+".jpg" << encoded+"-"+constBackdropName+".png" << constBackdropName+".jpg" << constBackdropName+".png"; for (int level=0; level<2; ++level) { foreach (const QString &fileName, names) { DBUG << "Checking file" << QString(dirName+fileName); if (QFile::exists(dirName+fileName)) { QImage img(dirName+fileName); if (!img.isNull()) { DBUG << "Got backdrop from" << QString(dirName+fileName); updateImage(img); QWidget::update(); return; } } } QDir d(dirName); d.cdUp(); dirName=Utils::fixPath(d.absolutePath()); } } } QString cacheName=cacheFileName(currentArtist, false); QImage img(cacheName); if (img.isNull()) { getBackdrop(); } else { DBUG << "Use cache file:" << cacheName; updateImage(img); QWidget::update(); } } static QString fixArtist(const QString &artist) { QString fixed(artist.trimmed()); fixed.remove(QChar('?')); return fixed; } void ContextWidget::getBackdrop() { cancel(); if (artistsCreatedBackdropsFor.contains(currentArtist)) { createBackdrop(); } else if (useFanArt) { getFanArtBackdrop(); } else { getDiscoGsImage(); } } void ContextWidget::getFanArtBackdrop() { // First we need to query musicbrainz to get id getMusicbrainzId(fixArtist(currentArtist)); } static const char * constArtistProp="artist-name"; void ContextWidget::getMusicbrainzId(const QString &artist) { QUrl url("http://www.musicbrainz.org/ws/2/artist/"); #if QT_VERSION < 0x050000 QUrl &query=url; #else QUrlQuery query; #endif query.addQueryItem("query", "artist:"+artist); #if QT_VERSION >= 0x050000 url.setQuery(query); #endif job = NetworkAccessManager::self()->get(url); DBUG << url.toString(); job->setProperty(constArtistProp, artist); connect(job, SIGNAL(finished()), this, SLOT(musicbrainzResponse())); } void ContextWidget::getDiscoGsImage() { cancel(); QUrl url; #if QT_VERSION < 0x050000 QUrl &query=url; #else QUrlQuery query; #endif url.setScheme("http"); url.setHost("api.discogs.com"); url.setPath("/search"); query.addQueryItem("per_page", QString::number(5)); query.addQueryItem("type", "artist"); query.addQueryItem("q", fixArtist(currentArtist)); query.addQueryItem("f", "json"); #if QT_VERSION >= 0x050000 url.setQuery(query); #endif job=NetworkAccessManager::self()->get(url, 5000); DBUG << url.toString(); connect(job, SIGNAL(finished()), this, SLOT(discoGsResponse())); } void ContextWidget::musicbrainzResponse() { NetworkJob *reply = getReply(sender()); if (!reply) { return; } DBUG << "status" << reply->error() << reply->errorString(); QString id; if (reply->ok()) { bool inSection=false; QXmlStreamReader doc(reply->actualJob()); while (!doc.atEnd()) { doc.readNext(); if (doc.isStartElement()) { if (!inSection && QLatin1String("artist-list")==doc.name()) { inSection=true; } if (inSection && QLatin1String("artist")==doc.name()) { id=doc.attributes().value("id").toString(); break; } } else if (doc.isEndElement() && inSection && QLatin1String("artist")==doc.name()) { break; } } } if (id.isEmpty()) { QString artist=reply->property(constArtistProp).toString(); // MusicBrainz does not seem to like AC/DC, but AC DC works - so if we fail with an artist // containing /, then try with space... if (!artist.isEmpty() && artist.contains("/")) { artist=artist.replace("/", " "); getMusicbrainzId(artist); } else { getDiscoGsImage(); } } else { QUrl url("http://api.fanart.tv/webservice/artist/"+constFanArtApiKey+"/"+id+"/json/artistbackground/1"); job=NetworkAccessManager::self()->get(url); DBUG << url.toString(); connect(job, SIGNAL(finished()), this, SLOT(fanArtResponse())); } } void ContextWidget::fanArtResponse() { NetworkJob *reply = getReply(sender()); if (!reply) { return; } DBUG << "status" << reply->error() << reply->errorString(); QString url; if (reply->ok()) { QJson::Parser parser; bool ok=false; #ifdef Q_OS_WIN QVariantMap parsed=parser.parse(reply->readAll(), &ok).toMap(); #else QVariantMap parsed=parser.parse(reply->actualJob(), &ok).toMap(); #endif if (ok && !parsed.isEmpty()) { QVariantMap artist=parsed[parsed.keys().first()].toMap(); if (artist.contains("artistbackground")) { QVariantList artistbackgrounds=artist["artistbackground"].toList(); if (!artistbackgrounds.isEmpty()) { QVariantMap artistbackground=artistbackgrounds.first().toMap(); if (artistbackground.contains("url")) { url=artistbackground["url"].toString(); } } } } } if (url.isEmpty()) { getDiscoGsImage(); } else { job=NetworkAccessManager::self()->get(QUrl(url)); DBUG << url; connect(job, SIGNAL(finished()), this, SLOT(downloadResponse())); } } static bool matchesArtist(const QString &titleOrig, const QString &artistOrig) { QString title=titleOrig.toLower(); QString artist=artistOrig.toLower(); if (title==artist) { return true; } if (artist.startsWith(QLatin1String("the ")) && title.endsWith(QLatin1String(", the"))) { QString theArtist=artist.mid(4)+QLatin1String(", the"); if (title==theArtist) { return true; } } typedef QPair ChPair; QList replacements = QList() << ChPair('-', '/') << ChPair('.', 0) << ChPair(QChar(0x00ff /* ΓΏ */), 'y'); foreach (const ChPair &r, replacements) { QString a=artist; QString t=title; if (r.second.isNull()) { a=a.replace(QString()+r.first, ""); t=t.replace(QString()+r.first, ""); } else { a=a.replace(r.first, r.second); t=t.replace(r.first, r.second); } if (t==a) { return true; } } return false; } void ContextWidget::discoGsResponse() { NetworkJob *reply = getReply(sender()); if (!reply) { return; } DBUG << "status" << reply->error() << reply->errorString(); QString url; if (reply->ok()) { QJson::Parser parser; bool ok=false; #ifdef Q_OS_WIN QVariantMap parsed=parser.parse(reply->readAll(), &ok).toMap(); #else QVariantMap parsed=parser.parse(reply->actualJob(), &ok).toMap(); #endif if (ok && parsed.contains("resp")) { QVariantMap response=parsed["resp"].toMap(); if (response.contains("search")) { QVariantMap search=response["search"].toMap(); if (search.contains("exactresults")) { QVariantList results=search["exactresults"].toList(); foreach (const QVariant &r, results) { QVariantMap rm=r.toMap(); if (rm.contains("thumb") && rm.contains("title")) { QString thumbUrl=rm["thumb"].toString(); QString title=rm["title"].toString(); if (thumbUrl.contains("/image/A-150-") && matchesArtist(title, currentArtist)) { url=thumbUrl.replace("image/A-150-", "/image/A-"); break; } } } } } } } if (url.isEmpty()) { createBackdrop(); } else { job=NetworkAccessManager::self()->get(QUrl(url)); DBUG << url; connect(job, SIGNAL(finished()), this, SLOT(downloadResponse())); } } void ContextWidget::downloadResponse() { NetworkJob *reply = getReply(sender()); if (!reply) { return; } DBUG << "status" << reply->error() << reply->errorString(); QImage img; QByteArray data; if (reply->ok()) { data=reply->readAll(); img=QImage::fromData(data); } if (img.isNull()) { createBackdrop(); } else { updateImage(img); bool saved=false; if (Settings::self()->storeBackdropsInMpdDir() && !currentSong.isVariousArtists() && !currentSong.isNonMPD() && MPDConnection::self()->getDetails().dirReadable) { QString mpdDir=MPDConnection::self()->getDetails().dir; QString songDir=Utils::getDir(currentSong.file); if (!mpdDir.isEmpty() && 2==songDir.split(Utils::constDirSep, QString::SkipEmptyParts).count()) { QDir d(mpdDir+songDir); d.cdUp(); QString fileName=Utils::fixPath(d.absolutePath())+constBackdropName+".jpg"; QFile f(fileName); if (f.open(QIODevice::WriteOnly)) { f.write(data); f.close(); DBUG << "Saved backdrop to" << fileName << "for artist" << currentArtist << ", current song" << currentSong.file; saved=true; } } else { DBUG << "Not saving to mpd folder, mpd dir:" << mpdDir << "num parts:" << songDir.split(Utils::constDirSep, QString::SkipEmptyParts).count(); } } else { DBUG << "Not saving to mpd folder - set to save in mpd?" << Settings::self()->storeBackdropsInMpdDir() << "isVa:" << currentSong.isVariousArtists() << "isNonMPD:" << currentSong.isNonMPD() << "mpd readable:" << MPDConnection::self()->getDetails().dirReadable; } if (!saved) { QString cacheName=cacheFileName(currentArtist, true); QFile f(cacheName); if (f.open(QIODevice::WriteOnly)) { DBUG << "Saved backdrop to (cache)" << cacheName << "for artist" << currentArtist << ", current song" << currentSong.file; f.write(data); f.close(); } } QWidget::update(); } } void ContextWidget::createBackdrop() { DBUG << currentArtist; if (!creator) { creator = new BackdropCreator(); connect(creator, SIGNAL(created(QString,QImage)), SLOT(backdropCreated(QString,QImage))); connect(this, SIGNAL(createBackdrop(QString,QList)), creator, SLOT(create(QString,QList))); } QList artistAlbumsFirstTracks=artist->getArtistAlbumsFirstTracks(); QSet albumNames; foreach (const Song &s, artistAlbumsFirstTracks) { albumNames.insert(s.albumArtist()+" - "+s.album); } if (backdropAlbums!=albumNames) { backdropAlbums=albumNames; emit createBackdrop(currentArtist, artistAlbumsFirstTracks); } } void ContextWidget::resizeBackdrop() { #ifdef SCALE_CONTEXT_BGND if (!currentBackdrop.isNull() && !albumCoverBackdrop && currentBackdrop.width()!=width()) { QSize sz(width(), width()*currentBackdrop.height()/currentBackdrop.width()); currentBackdrop=currentBackdrop.scaled(sz, Qt::KeepAspectRatio, Qt::SmoothTransformation); } #else if (!currentBackdrop.isNull() && !albumCoverBackdrop) { if (currentBackdrop.width()1024 && maxBackdropSize.height()>768 && (currentBackdrop.width()>maxBackdropSize.width() || currentBackdrop.height()>maxBackdropSize.height())) { currentBackdrop=currentBackdrop.scaled(maxBackdropSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); } } #endif } void ContextWidget::backdropCreated(const QString &artist, const QImage &img) { DBUG << artist << img.isNull() << currentArtist; if (artist==currentArtist) { artistsCreatedBackdropsFor.removeAll(artist); artistsCreatedBackdropsFor.append(artist); if (artistsCreatedBackdropsFor.count()>20) { artistsCreatedBackdropsFor.removeFirst(); } updateImage(img, true); QWidget::update(); } } NetworkJob * ContextWidget::getReply(QObject *obj) { NetworkJob *reply = qobject_cast(obj); if (!reply) { return 0; } reply->deleteLater(); if (reply!=job) { return 0; } job=0; return reply; }