File indexing completed on 2024-05-12 05:09:37
0001 /*************************************************************************** 0002 Copyright (C) 2023 Robby Stephenson <robby@periapsis.org> 0003 ***************************************************************************/ 0004 0005 /*************************************************************************** 0006 * * 0007 * This program is free software; you can redistribute it and/or * 0008 * modify it under the terms of the GNU General Public License as * 0009 * published by the Free Software Foundation; either version 2 of * 0010 * the License or (at your option) version 3 or any later version * 0011 * accepted by the membership of KDE e.V. (or its successor approved * 0012 * by the membership of KDE e.V.), which shall act as a proxy * 0013 * defined in Section 14 of version 3 of the license. * 0014 * * 0015 * This program is distributed in the hope that it will be useful, * 0016 * but WITHOUT ANY WARRANTY; without even the implied warranty of * 0017 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 0018 * GNU General Public License for more details. * 0019 * * 0020 * You should have received a copy of the GNU General Public License * 0021 * along with this program. If not, see <http://www.gnu.org/licenses/>. * 0022 * * 0023 ***************************************************************************/ 0024 0025 #include "itunesfetcher.h" 0026 #include "../collections/bookcollection.h" 0027 #include "../collections/musiccollection.h" 0028 #include "../collections/videocollection.h" 0029 #include "../images/imagefactory.h" 0030 #include "../gui/combobox.h" 0031 #include "../utils/guiproxy.h" 0032 #include "../utils/isbnvalidator.h" 0033 #include "../utils/string_utils.h" 0034 #include "../utils/mapvalue.h" 0035 #include "../tellico_debug.h" 0036 0037 #include <KLocalizedString> 0038 #include <KConfigGroup> 0039 #include <KJob> 0040 #include <KJobUiDelegate> 0041 #include <KJobWidgets/KJobWidgets> 0042 #include <KIO/StoredTransferJob> 0043 0044 #include <QLabel> 0045 #include <QFile> 0046 #include <QTextStream> 0047 #include <QGridLayout> 0048 #include <QTextCodec> 0049 #include <QJsonDocument> 0050 #include <QJsonObject> 0051 #include <QJsonArray> 0052 #include <QUrlQuery> 0053 0054 namespace { 0055 static const int ITUNES_MAX_RETURNS_TOTAL = 20; 0056 static const char* ITUNES_API_URL = "https://itunes.apple.com"; 0057 } 0058 0059 using namespace Tellico; 0060 using Tellico::Fetch::ItunesFetcher; 0061 0062 ItunesFetcher::ItunesFetcher(QObject* parent_) 0063 : Fetcher(parent_) 0064 , m_started(false) 0065 , m_isTV(false) 0066 , m_imageSize(LargeImage) { 0067 } 0068 0069 ItunesFetcher::~ItunesFetcher() { 0070 } 0071 0072 QString ItunesFetcher::source() const { 0073 return m_name.isEmpty() ? defaultName() : m_name; 0074 } 0075 0076 bool ItunesFetcher::canSearch(Fetch::FetchKey k) const { 0077 return k == Keyword || k == ISBN || k == UPC; 0078 } 0079 0080 bool ItunesFetcher::canFetch(int type) const { 0081 return type == Data::Collection::Book 0082 || type == Data::Collection::Album 0083 || type == Data::Collection::Video; 0084 } 0085 0086 void ItunesFetcher::readConfigHook(const KConfigGroup& config_) { 0087 const int imageSize = config_.readEntry("Image Size", -1); 0088 if(imageSize > -1) { 0089 m_imageSize = static_cast<ImageSize>(imageSize); 0090 } 0091 } 0092 0093 void ItunesFetcher::saveConfigHook(KConfigGroup& config_) { 0094 Q_UNUSED(config_) 0095 } 0096 0097 void ItunesFetcher::search() { 0098 m_started = true; 0099 m_isTV = false; 0100 0101 QUrl u(QString::fromLatin1(ITUNES_API_URL)); 0102 u = u.adjusted(QUrl::StripTrailingSlash); 0103 QUrlQuery q; 0104 switch(request().key()) { 0105 case Keyword: 0106 u.setPath(u.path() + QLatin1String("/search")); 0107 if(collectionType() == Data::Collection::Book) { 0108 q.addQueryItem(QStringLiteral("media"), QLatin1String("audiobook")); 0109 q.addQueryItem(QStringLiteral("entity"), QLatin1String("audiobook")); 0110 } else if(collectionType() == Data::Collection::Album) { 0111 q.addQueryItem(QStringLiteral("media"), QLatin1String("music")); 0112 q.addQueryItem(QStringLiteral("entity"), QLatin1String("album")); 0113 } else if(collectionType() == Data::Collection::Video) { 0114 q.addQueryItem(QStringLiteral("media"), QLatin1String("movie,tvShow")); 0115 q.addQueryItem(QStringLiteral("entity"), QLatin1String("movie,tvSeason")); 0116 } 0117 q.addQueryItem(QStringLiteral("limit"), QString::number(ITUNES_MAX_RETURNS_TOTAL)); 0118 q.addQueryItem(QStringLiteral("term"), QString::fromLatin1(QUrl::toPercentEncoding(request().value()))); 0119 break; 0120 0121 case ISBN: 0122 u.setPath(u.path() + QLatin1String("/lookup")); 0123 { 0124 QString isbn = ISBNValidator::isbn13(request().value()); 0125 isbn.remove(QLatin1Char('-')); 0126 q.addQueryItem(QStringLiteral("isbn"), isbn); 0127 } 0128 break; 0129 0130 case UPC: 0131 u.setPath(u.path() + QLatin1String("/lookup")); 0132 if(collectionType() == Data::Collection::Album) { 0133 // include songs 0134 q.addQueryItem(QStringLiteral("entity"), QLatin1String("song")); 0135 } 0136 q.addQueryItem(QStringLiteral("upc"), request().value()); 0137 break; 0138 0139 default: 0140 myWarning() << source() << "- key not recognized:" << request().key(); 0141 stop(); 0142 return; 0143 } 0144 u.setQuery(q); 0145 0146 // myDebug() << u; 0147 m_job = KIO::storedGet(u, KIO::NoReload, KIO::HideProgressInfo); 0148 KJobWidgets::setWindow(m_job, GUI::Proxy::widget()); 0149 connect(m_job.data(), &KJob::result, this, &ItunesFetcher::slotComplete); 0150 } 0151 0152 void ItunesFetcher::stop() { 0153 if(!m_started) { 0154 return; 0155 } 0156 if(m_job) { 0157 m_job->kill(); 0158 m_job = nullptr; 0159 } 0160 m_started = false; 0161 emit signalDone(this); 0162 } 0163 0164 Tellico::Fetch::FetchRequest ItunesFetcher::updateRequest(Data::EntryPtr entry_) { 0165 const QString upc = entry_->field(QStringLiteral("upc")); 0166 if(!upc.isEmpty()) { 0167 return FetchRequest(UPC, upc); 0168 } 0169 0170 const QString title = entry_->field(QStringLiteral("title")); 0171 if(!title.isEmpty()) { 0172 return FetchRequest(Keyword, title); 0173 } 0174 0175 return FetchRequest(); 0176 } 0177 0178 void ItunesFetcher::slotComplete(KJob* job_) { 0179 KIO::StoredTransferJob* job = static_cast<KIO::StoredTransferJob*>(job_); 0180 0181 if(job->error()) { 0182 job->uiDelegate()->showErrorMessage(); 0183 stop(); 0184 return; 0185 } 0186 0187 const QByteArray data = job->data(); 0188 if(data.isEmpty()) { 0189 myDebug() << "iTunesFetcher: no data"; 0190 stop(); 0191 return; 0192 } 0193 // see bug 319662. If fetcher is cancelled, job is killed 0194 // if the pointer is retained, it gets double-deleted 0195 m_job = nullptr; 0196 0197 #if 0 0198 myWarning() << "Remove debug from itunesfetcher.cpp"; 0199 QFile f(QStringLiteral("/tmp/test-itunes.json")); 0200 if(f.open(QIODevice::WriteOnly)) { 0201 QTextStream t(&f); 0202 t.setCodec("UTF-8"); 0203 t << data; 0204 } 0205 f.close(); 0206 #endif 0207 0208 QJsonDocument doc = QJsonDocument::fromJson(data); 0209 if(doc.isNull()) { 0210 myDebug() << "null JSON document"; 0211 stop(); 0212 return; 0213 } 0214 0215 QJsonArray results = doc.object().value(QLatin1String("results")).toArray(); 0216 if(results.isEmpty()) { 0217 myDebug() << "iTunesFetcher: no results"; 0218 stop(); 0219 return; 0220 } 0221 0222 Data::CollPtr coll; 0223 if(collectionType() == Data::Collection::Book) { 0224 coll = new Data::BookCollection(true); 0225 } else if(collectionType() == Data::Collection::Album) { 0226 coll = new Data::MusicCollection(true); 0227 } else if(collectionType() == Data::Collection::Video) { 0228 coll = new Data::VideoCollection(true); 0229 } 0230 Q_ASSERT(coll); 0231 if(!coll) { 0232 stop(); 0233 return; 0234 } 0235 0236 // placeholder for collection id, to be removed later 0237 Data::FieldPtr f1(new Data::Field(QStringLiteral("collectionId"), QString(), Data::Field::Number)); 0238 coll->addField(f1); 0239 if(optionalFields().contains(QStringLiteral("itunes"))) { 0240 Data::FieldPtr field(new Data::Field(QStringLiteral("itunes"), i18n("iTunes Link"), Data::Field::URL)); 0241 field->setCategory(i18n("General")); 0242 coll->addField(field); 0243 } 0244 if(collectionType() == Data::Collection::Video && 0245 optionalFields().contains(QStringLiteral("episode"))) { 0246 coll->addField(Data::Field::createDefaultField(Data::Field::EpisodeField)); 0247 } 0248 0249 QList<FetchResult*> fetchResults; 0250 foreach(const QJsonValue& result, results) { 0251 auto obj = result.toObject(); 0252 if(obj.value(QLatin1String("kind")) == QLatin1String("song")) { 0253 readTrackInfo(obj.toVariantMap()); 0254 } else { 0255 Data::EntryPtr entry(new Data::Entry(coll)); 0256 populateEntry(entry, obj.toVariantMap()); 0257 0258 FetchResult* r = new FetchResult(this, entry); 0259 m_entries.insert(r->uid, entry); 0260 fetchResults.append(r); 0261 } 0262 } 0263 0264 // don't emit result until after adding tracks 0265 for(auto fetchResult : fetchResults) { 0266 emit signalResultFound(fetchResult); 0267 } 0268 stop(); 0269 } 0270 0271 Tellico::Data::EntryPtr ItunesFetcher::fetchEntryHook(uint uid_) { 0272 Data::EntryPtr entry = m_entries.value(uid_); 0273 if(!entry) { 0274 myWarning() << "no entry in dict"; 0275 return Data::EntryPtr(); 0276 } 0277 0278 // check if tracks need to be downloaded 0279 const QString collectionId = entry->field(QLatin1String("collectionId")); 0280 if(!collectionId.isEmpty() && collectionType() == Data::Collection::Album && 0281 entry->field(QLatin1String("track")).isEmpty()) { 0282 QUrl u(QString::fromLatin1(ITUNES_API_URL)); 0283 u = u.adjusted(QUrl::StripTrailingSlash); 0284 u.setPath(u.path() + QLatin1String("/lookup")); 0285 QUrlQuery q; 0286 q.addQueryItem(QStringLiteral("entity"), QLatin1String("song")); 0287 q.addQueryItem(QStringLiteral("id"), collectionId); 0288 u.setQuery(q); 0289 auto job = KIO::storedGet(u, KIO::NoReload, KIO::HideProgressInfo); 0290 if(job->exec()) { 0291 #if 0 0292 myWarning() << "Remove debug from itunesfetcher.cpp"; 0293 QFile f(QStringLiteral("/tmp/test-itunes-tracks.json")); 0294 if(f.open(QIODevice::WriteOnly)) { 0295 QTextStream t(&f); 0296 t.setCodec("UTF-8"); 0297 t << job->data(); 0298 } 0299 f.close(); 0300 #endif 0301 QJsonDocument doc = QJsonDocument::fromJson(job->data()); 0302 QJsonArray results = doc.object().value(QLatin1String("results")).toArray(); 0303 foreach(const QJsonValue& result, results) { 0304 auto obj = result.toObject(); 0305 if(obj.value(QLatin1String("wrapperType")) == QLatin1String("track")) { 0306 readTrackInfo(obj.toVariantMap()); 0307 } 0308 } 0309 auto discsInColl = m_trackList.value(collectionId.toInt()); 0310 bool changeTrackTitle = true; 0311 for(int disc = 0; disc < discsInColl.count(); ++disc) { 0312 QString trackField = QStringLiteral("track"); 0313 if(disc > 0) { 0314 trackField.append(QString::number(disc+1)); 0315 Data::FieldPtr f2(new Data::Field(trackField, 0316 i18n("Tracks (Disc %1)", disc+1), 0317 Data::Field::Table)); 0318 f2->setFormatType(FieldFormat::FormatTitle); 0319 f2->setProperty(QStringLiteral("columns"), QStringLiteral("3")); 0320 f2->setProperty(QStringLiteral("column1"), i18n("Title")); 0321 f2->setProperty(QStringLiteral("column2"), i18n("Artist")); 0322 f2->setProperty(QStringLiteral("column3"), i18n("Length")); 0323 entry->collection()->addField(f2); 0324 // also change the title of the first track field 0325 if(changeTrackTitle) { 0326 Data::FieldPtr f1 = entry->collection()->fieldByName(QStringLiteral("track")); 0327 f1->setTitle(i18n("Tracks (Disc %1)", 1)); 0328 entry->collection()->modifyField(f1); 0329 changeTrackTitle = false; 0330 } 0331 } 0332 entry->setField(trackField, discsInColl.at(disc).join(FieldFormat::rowDelimiterString())); 0333 } 0334 } 0335 } 0336 0337 if(m_isTV && optionalFields().contains(QStringLiteral("episode"))) { 0338 populateEpisodes(entry); 0339 } 0340 0341 // image might still be a URL 0342 const QString image_id = entry->field(QStringLiteral("cover")); 0343 if(image_id.contains(QLatin1Char('/'))) { 0344 // default image is the 100x100 size and considered 'Small' 0345 QString newImage = image_id; 0346 if(m_imageSize == LargeImage) { 0347 newImage.replace(QLatin1String("100x100"), QLatin1String("600x600")); 0348 } 0349 QString id = ImageFactory::addImage(QUrl::fromUserInput(newImage), true /* quiet */); 0350 if(id.isEmpty() && newImage != image_id) { 0351 // fallback to original 0352 id = ImageFactory::addImage(QUrl::fromUserInput(image_id), true /* quiet */); 0353 } 0354 if(id.isEmpty()) { 0355 message(i18n("The cover image could not be loaded."), MessageHandler::Warning); 0356 } 0357 // empty image ID is ok 0358 entry->setField(QStringLiteral("cover"), id); 0359 } 0360 0361 // clear the placeholder field 0362 entry->setField(QStringLiteral("collectionId"), QString()); 0363 0364 return entry; 0365 } 0366 0367 void ItunesFetcher::populateEntry(Data::EntryPtr entry_, const QVariantMap& resultMap_) { 0368 entry_->setField(QStringLiteral("collectionId"), mapValue(resultMap_, "collectionId")); 0369 if(collectionType() == Data::Collection::Book) { 0370 QString title = mapValue(resultMap_, "collectionName"); 0371 if(title.isEmpty()) title = mapValue(resultMap_, "trackName"); 0372 entry_->setField(QStringLiteral("title"), title); 0373 entry_->setField(QStringLiteral("author"), mapValue(resultMap_, "artistName")); 0374 entry_->setField(QStringLiteral("plot"), mapValue(resultMap_, "description")); 0375 static const QRegularExpression publisherRx(QStringLiteral("^© \\d{4} (.+)$")); 0376 auto publisherMatch = publisherRx.match(mapValue(resultMap_, "copyright")); 0377 if(publisherMatch.hasMatch()) { 0378 entry_->setField(QStringLiteral("publisher"), publisherMatch.captured(1)); 0379 } 0380 } else if(collectionType() == Data::Collection::Album) { 0381 entry_->setField(QStringLiteral("title"), mapValue(resultMap_, "collectionName")); 0382 entry_->setField(QStringLiteral("artist"), mapValue(resultMap_, "artistName")); 0383 static const QRegularExpression labelRx(QStringLiteral("^℗ \\d{4} ([^,]+)")); 0384 auto labelMatch = labelRx.match(mapValue(resultMap_, "copyright")); 0385 if(labelMatch.hasMatch()) { 0386 entry_->setField(QStringLiteral("label"), labelMatch.captured(1)); 0387 } 0388 } else if(collectionType() == Data::Collection::Video) { 0389 if(mapValue(resultMap_, "collectionType") == QLatin1String("TV Season")) { 0390 m_isTV = true; 0391 // collection Name includes season 0392 entry_->setField(QStringLiteral("title"), mapValue(resultMap_, "collectionName")); 0393 // artistName is TV Show title 0394 entry_->setField(QStringLiteral("keyword"), mapValue(resultMap_, "artistName")); 0395 } else { 0396 entry_->setField(QStringLiteral("title"), mapValue(resultMap_, "trackName")); 0397 entry_->setField(QStringLiteral("director"), mapValue(resultMap_, "artistName")); 0398 } 0399 entry_->setField(QStringLiteral("nationality"), mapValue(resultMap_, "country")); 0400 QString cert = mapValue(resultMap_, "contentAdvisoryRating"); 0401 if(cert == QStringLiteral("NR")) { 0402 cert = QLatin1Char('U'); 0403 } 0404 if(!cert.isEmpty()) { 0405 if(mapValue(resultMap_, "country") == QLatin1String("US")) { 0406 cert += QStringLiteral(" (USA)"); 0407 } else { 0408 cert += QLatin1String(" (") + mapValue(resultMap_, "country") + QLatin1Char(')'); 0409 } 0410 QStringList certsAllowed = entry_->collection()->fieldByName(QStringLiteral("certification"))->allowed(); 0411 if(!certsAllowed.contains(cert)) { 0412 certsAllowed << cert; 0413 Data::FieldPtr f = entry_->collection()->fieldByName(QStringLiteral("certification")); 0414 f->setAllowed(certsAllowed); 0415 } 0416 entry_->setField(QStringLiteral("certification"), cert); 0417 } 0418 entry_->setField(QStringLiteral("plot"), mapValue(resultMap_, "longDescription")); 0419 } 0420 if(collectionType() == Data::Collection::Book) { 0421 entry_->setField(QStringLiteral("binding"), i18n("E-Book")); 0422 entry_->setField(QStringLiteral("pub_year"), mapValue(resultMap_, "releaseDate").left(4)); 0423 } else { 0424 entry_->setField(QStringLiteral("year"), mapValue(resultMap_, "releaseDate").left(4)); 0425 } 0426 0427 QStringList genres; 0428 genres += mapValue(resultMap_, "primaryGenreName"); 0429 const auto genreList = resultMap_.value(QLatin1String("genres")).toList(); 0430 for(const auto& genre : genreList) { 0431 genres += genre.toString(); 0432 } 0433 genres.removeDuplicates(); 0434 genres.removeOne(QString()); // no empty genres 0435 genres.removeOne(QLatin1String("Books")); // too generic 0436 entry_->setField(QStringLiteral("genre"), genres.join(FieldFormat::delimiterString())); 0437 0438 if(m_imageSize != NoImage) { 0439 entry_->setField(QStringLiteral("cover"), mapValue(resultMap_, "artworkUrl100")); 0440 } 0441 0442 if(optionalFields().contains(QStringLiteral("itunes"))) { 0443 entry_->setField(QStringLiteral("itunes"), mapValue(resultMap_, "collectionViewUrl")); 0444 } 0445 0446 m_collectionHash.insert(resultMap_.value(QLatin1String("collectionId")).toInt(), entry_); 0447 } 0448 0449 void ItunesFetcher::populateEpisodes(Data::EntryPtr entry_) { 0450 const QString collectionId = entry_->field(QLatin1String("collectionId")); 0451 QUrl u(QString::fromLatin1(ITUNES_API_URL)); 0452 u = u.adjusted(QUrl::StripTrailingSlash); 0453 u.setPath(u.path() + QLatin1String("/lookup")); 0454 QUrlQuery q; 0455 q.addQueryItem(QStringLiteral("entity"), QLatin1String("tvEpisode")); 0456 q.addQueryItem(QStringLiteral("id"), collectionId); 0457 u.setQuery(q); 0458 0459 auto job = KIO::storedGet(u, KIO::NoReload, KIO::HideProgressInfo); 0460 if(!job->exec()) { 0461 myDebug() << "Failed downloa ditunes episodes"; 0462 return; 0463 } 0464 0465 #if 0 0466 myWarning() << "Remove debug2 from ItunesFetcher.cpp"; 0467 QFile f(QStringLiteral("/tmp/test-itunes-episodes.json")); 0468 if(f.open(QIODevice::WriteOnly)) { 0469 QTextStream t(&f); 0470 t.setCodec("UTF-8"); 0471 t << job->data(); 0472 } 0473 f.close(); 0474 #endif 0475 0476 static const QRegularExpression seasonRx(QStringLiteral("Season (\\d+)")); 0477 QMap<int, QString> episodeMap; // mapping episode number to episode string 0478 QJsonDocument doc = QJsonDocument::fromJson(job->data()); 0479 QJsonArray results = doc.object().value(QLatin1String("results")).toArray(); 0480 foreach(const QJsonValue& result, results) { 0481 auto map = result.toObject().toVariantMap(); 0482 if(mapValue(map, "kind") != QStringLiteral("tv-episode")) continue; 0483 int seasonNumber = 1; 0484 // the season number is in the collection title 0485 auto match = seasonRx.match(mapValue(map, "collectionName")); 0486 if(match.hasMatch()) { 0487 seasonNumber = match.captured(1).toInt(); 0488 } 0489 QString ep = mapValue(map, "trackName") + FieldFormat::columnDelimiterString() + 0490 QString::number(seasonNumber) + FieldFormat::columnDelimiterString() + 0491 mapValue(map, "trackNumber"); 0492 episodeMap.insert(seasonNumber*1000 + mapValue(map, "trackNumber").toInt(), ep); 0493 } 0494 // the QMap sorts the values in ascending order by key 0495 const auto episodes = episodeMap.values(); 0496 entry_->setField(QStringLiteral("episode"), episodes.join(FieldFormat::rowDelimiterString())); 0497 } 0498 0499 void ItunesFetcher::readTrackInfo(const QVariantMap& resultMap_) { 0500 QStringList trackInfo; 0501 trackInfo << mapValue(resultMap_, "trackName") 0502 << mapValue(resultMap_, "artistName") 0503 << Tellico::minutes(mapValue(resultMap_, "trackTimeMillis").toInt() / 1000); 0504 0505 const int collectionId = mapValue(resultMap_, "collectionId").toInt(); 0506 const int discNum = mapValue(resultMap_, "discNumber").toInt(); 0507 const int trackNum = mapValue(resultMap_, "trackNumber").toInt(); 0508 if(trackNum < 1) return; 0509 0510 auto discsInColl = m_trackList.value(collectionId); 0511 while(discsInColl.size() < discNum) discsInColl << QStringList(); 0512 0513 auto tracks = discsInColl.at(discNum-1); 0514 while(tracks.size() < trackNum) tracks << QString(); 0515 0516 tracks[trackNum-1] = trackInfo.join(FieldFormat::columnDelimiterString()); 0517 discsInColl[discNum-1] = tracks; 0518 m_trackList.insert(collectionId, discsInColl); 0519 } 0520 0521 Tellico::Fetch::ConfigWidget* ItunesFetcher::configWidget(QWidget* parent_) const { 0522 return new ItunesFetcher::ConfigWidget(parent_, this); 0523 } 0524 0525 QString ItunesFetcher::defaultName() { 0526 return QStringLiteral("iTunes"); // this is the capitalization they use on their site 0527 } 0528 0529 QString ItunesFetcher::defaultIcon() { 0530 return favIcon(ITUNES_API_URL); 0531 } 0532 0533 Tellico::StringHash ItunesFetcher::allOptionalFields() { 0534 StringHash hash; 0535 hash[QStringLiteral("itunes")] = i18n("iTunes Link"); 0536 hash[QStringLiteral("episode")] = i18n("Episodes"); 0537 return hash; 0538 } 0539 0540 ItunesFetcher::ConfigWidget::ConfigWidget(QWidget* parent_, const ItunesFetcher* fetcher_) 0541 : Fetch::ConfigWidget(parent_) { 0542 QGridLayout* l = new QGridLayout(optionsWidget()); 0543 l->setColumnStretch(1, 10); 0544 0545 int row = -1; 0546 0547 QLabel* label = new QLabel(i18n("&Image size: "), optionsWidget()); 0548 l->addWidget(label, ++row, 0); 0549 m_imageCombo = new GUI::ComboBox(optionsWidget()); 0550 m_imageCombo->addItem(i18n("No Image"), NoImage); 0551 m_imageCombo->addItem(i18n("Small Image"), SmallImage); 0552 m_imageCombo->addItem(i18n("Large Image"), LargeImage); 0553 void (GUI::ComboBox::* activatedInt)(int) = &GUI::ComboBox::activated; 0554 connect(m_imageCombo, activatedInt, this, &ConfigWidget::slotSetModified); 0555 l->addWidget(m_imageCombo, row, 1); 0556 label->setBuddy(m_imageCombo); 0557 0558 l->setRowStretch(++row, 10); 0559 0560 // now add additional fields widget 0561 addFieldsWidget(ItunesFetcher::allOptionalFields(), fetcher_ ? fetcher_->optionalFields() : QStringList()); 0562 0563 if(fetcher_) { 0564 m_imageCombo->setCurrentData(fetcher_->m_imageSize); 0565 } else { // defaults 0566 m_imageCombo->setCurrentData(SmallImage); 0567 } 0568 } 0569 0570 void ItunesFetcher::ConfigWidget::saveConfigHook(KConfigGroup& config_) { 0571 const int n = m_imageCombo->currentData().toInt(); 0572 config_.writeEntry("Image Size", n); 0573 } 0574 0575 QString ItunesFetcher::ConfigWidget::preferredName() const { 0576 return ItunesFetcher::defaultName(); 0577 }