File indexing completed on 2024-05-12 05:09:36
0001 /*************************************************************************** 0002 Copyright (C) 2004-2009 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 "imdbfetcher.h" 0026 #include "../collections/videocollection.h" 0027 #include "../entry.h" 0028 #include "../field.h" 0029 #include "../fieldformat.h" 0030 #include "../images/imagefactory.h" 0031 #include "../utils/mapvalue.h" 0032 #include "../utils/guiproxy.h" 0033 #include "../gui/combobox.h" 0034 #include "../gui/lineedit.h" 0035 #include "../tellico_debug.h" 0036 0037 #include <KLocalizedString> 0038 #include <KConfigGroup> 0039 #include <KIO/Job> 0040 #include <KJobUiDelegate> 0041 #include <KAcceleratorManager> 0042 #include <KJobWidgets/KJobWidgets> 0043 0044 #include <QSpinBox> 0045 #include <QFile> 0046 #include <QMap> 0047 #include <QLabel> 0048 #include <QRadioButton> 0049 #include <QGroupBox> 0050 #include <QButtonGroup> 0051 #include <QGridLayout> 0052 #include <QJsonDocument> 0053 #include <QJsonParseError> 0054 #include <QJsonObject> 0055 #include <QRegularExpression> 0056 0057 namespace { 0058 static const uint IMDB_DEFAULT_CAST_SIZE = 10; 0059 } 0060 0061 using namespace Tellico; 0062 using Tellico::Fetch::IMDBFetcher; 0063 0064 IMDBFetcher::IMDBFetcher(QObject* parent_) : Fetcher(parent_), 0065 m_job(nullptr), m_started(false), m_imageSize(MediumImage), 0066 m_numCast(IMDB_DEFAULT_CAST_SIZE), m_useSystemLocale(true) { 0067 } 0068 0069 IMDBFetcher::~IMDBFetcher() = default; 0070 0071 QString IMDBFetcher::source() const { 0072 return m_name.isEmpty() ? defaultName() : m_name; 0073 } 0074 0075 bool IMDBFetcher::canFetch(int type) const { 0076 return type == Data::Collection::Video; 0077 } 0078 0079 // imdb can search title only 0080 bool IMDBFetcher::canSearch(Fetch::FetchKey k) const { 0081 // Raw searches are intended to be the imdb url 0082 return k == Title || k == Raw; 0083 } 0084 0085 void IMDBFetcher::readConfigHook(const KConfigGroup& config_) { 0086 m_numCast = config_.readEntry("Max Cast", IMDB_DEFAULT_CAST_SIZE); 0087 const int imageSize = config_.readEntry("Image Size", -1); 0088 if(imageSize > -1) { 0089 m_imageSize = static_cast<ImageSize>(imageSize); 0090 } 0091 m_useSystemLocale = config_.readEntry("System Locale", true); 0092 m_customLocale = config_.readEntry("Custom Locale"); 0093 } 0094 0095 // multiple values not supported 0096 void IMDBFetcher::search() { 0097 m_started = true; 0098 m_matches.clear(); 0099 0100 QString operationName, query; 0101 QJsonObject vars; 0102 switch(request().key()) { 0103 case Title: 0104 operationName = QLatin1String("Search"); 0105 query = searchQuery(); 0106 vars.insert(QLatin1String("searchTerms"), request().value()); 0107 break; 0108 0109 case Raw: 0110 { 0111 // expect a url that ends with the tt id 0112 QRegularExpression ttEndRx(QStringLiteral("/(tt\\d+)/?$")); 0113 auto match = ttEndRx.match(request().value()); 0114 if(match.hasMatch()) { 0115 operationName = QLatin1String("TitleFull"); 0116 query = titleQuery(); 0117 vars.insert(QLatin1String("id"), match.captured(1)); 0118 } else { 0119 // fallback to a general search 0120 myDebug() << "bad url"; 0121 operationName = QLatin1String("Search"); 0122 query = searchQuery(); 0123 vars.insert(QLatin1String("searchTerms"), request().value()); 0124 } 0125 } 0126 break; 0127 0128 default: 0129 myWarning() << source() << "- key not recognized:" << request().key(); 0130 stop(); 0131 return; 0132 } 0133 0134 QJsonObject payload; 0135 payload.insert(QLatin1String("operationName"), operationName); 0136 payload.insert(QLatin1String("query"), query); 0137 payload.insert(QLatin1String("variables"), vars); 0138 0139 m_job = KIO::storedHttpPost(QJsonDocument(payload).toJson(), 0140 QUrl(QLatin1String("https://api.graphql.imdb.com")), 0141 KIO::HideProgressInfo); 0142 configureJob(m_job); 0143 connect(m_job.data(), &KJob::result, 0144 this, &IMDBFetcher::slotComplete); 0145 } 0146 0147 void IMDBFetcher::stop() { 0148 if(!m_started) { 0149 return; 0150 } 0151 if(m_job) { 0152 m_job->kill(); 0153 m_job = nullptr; 0154 } 0155 0156 m_started = false; 0157 0158 emit signalDone(this); 0159 } 0160 0161 void IMDBFetcher::slotComplete(KJob*) { 0162 if(m_job->error()) { 0163 myDebug() << m_job->errorString(); 0164 m_job->uiDelegate()->showErrorMessage(); 0165 stop(); 0166 return; 0167 } 0168 0169 const auto data = m_job->data(); 0170 if(data.isEmpty()) { 0171 myDebug() << "IMDB - no data"; 0172 stop(); 0173 return; 0174 } 0175 m_job = nullptr; 0176 0177 #if 0 0178 myWarning() << "Remove JSON debug from imdbfetcher.cpp"; 0179 QFile f(QString::fromLatin1("/tmp/imdb-graphql-search.json")); 0180 if(f.open(QIODevice::WriteOnly)) { 0181 QTextStream t(&f); 0182 t.setCodec("UTF-8"); 0183 t << QString::fromUtf8(data.constData(), data.size()); 0184 } 0185 f.close(); 0186 #endif 0187 0188 // for Raw searches, the result should be a single title 0189 if(request().key() == Raw) { 0190 auto entry = parseResult(data); 0191 if(entry) { 0192 FetchResult* r = new FetchResult(this, entry); 0193 m_entries.insert(r->uid, entry); 0194 emit signalResultFound(r); 0195 } 0196 stop(); 0197 return; 0198 } 0199 0200 QJsonParseError jsonError; 0201 QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError); 0202 if(doc.isNull()) { 0203 myDebug() << "null JSON document:" << jsonError.errorString(); 0204 message(jsonError.errorString(), MessageHandler::Error); 0205 } 0206 0207 const auto objectMap = doc.object() 0208 .value(QLatin1String("data")).toObject() 0209 .value(QLatin1String("mainSearch")).toObject().toVariantMap(); 0210 auto list = objectMap.value(QLatin1String("edges")).toList(); 0211 for(const auto& edge: qAsConst(list)) { 0212 const auto map = edge.toMap().value(QLatin1String("node")) 0213 .toMap().value(QLatin1String("entity")).toMap(); 0214 const auto id = mapValue(map, "id"); 0215 const auto title = mapValue(map, "titleText", "text"); 0216 const auto year = mapValue(map, "releaseYear", "year"); 0217 0218 FetchResult* r = new FetchResult(this, title, year); 0219 m_matches.insert(r->uid, id); 0220 m_titleTypes.insert(r->uid, mapValue(map, "titleType", "text")); 0221 emit signalResultFound(r); 0222 } 0223 0224 stop(); 0225 } 0226 0227 Tellico::Data::EntryPtr IMDBFetcher::fetchEntryHook(uint uid_) { 0228 // if we already grabbed this one, then just pull it out of the dict 0229 Data::EntryPtr entry = m_entries[uid_]; 0230 if(entry) { 0231 return entry; 0232 } 0233 if(!m_matches.contains(uid_)) { 0234 myDebug() << "no id match for" << uid_; 0235 return entry; 0236 } 0237 0238 entry = readGraphQL(m_matches.value(uid_), m_titleTypes.value(uid_)); 0239 if(entry) { 0240 m_entries.insert(uid_, entry); // keep for later 0241 } 0242 0243 return entry; 0244 } 0245 0246 Tellico::Fetch::FetchRequest IMDBFetcher::updateRequest(Data::EntryPtr entry_) { 0247 QUrl link = QUrl::fromUserInput(entry_->field(QStringLiteral("imdb"))); 0248 0249 if(!link.isEmpty() && link.isValid()) { 0250 return FetchRequest(Fetch::Raw, link.url()); 0251 } 0252 0253 // optimistically try searching for title and rely on Collection::sameEntry() to figure things out 0254 const QString t = entry_->field(QStringLiteral("title")); 0255 if(!t.isEmpty()) { 0256 return FetchRequest(Fetch::Title, t); 0257 } 0258 return FetchRequest(); 0259 } 0260 0261 void IMDBFetcher::configureJob(QPointer<KIO::StoredTransferJob> job_) { 0262 KJobWidgets::setWindow(job_, GUI::Proxy::widget()); 0263 job_->addMetaData(QStringLiteral("content-type"), QStringLiteral("Content-Type: application/json")); 0264 job_->addMetaData(QStringLiteral("accept"), QStringLiteral("application/json")); 0265 job_->addMetaData(QStringLiteral("origin"), QLatin1String("https://www.imdb.com")); 0266 QStringList headers; 0267 headers += QStringLiteral("x-imdb-client-name: imdb-web-next-localized"); 0268 0269 QString localeName; 0270 if(m_useSystemLocale || m_customLocale.isEmpty()) { 0271 // use default locale instead of system in case it was changed 0272 localeName = QLocale().name(); 0273 myLog() << "Using system locale:" << localeName; 0274 } else { 0275 localeName = m_customLocale; 0276 myLog() << "Using custom locale:" << localeName; 0277 } 0278 localeName.replace(QLatin1Char('_'), QLatin1Char('-')); 0279 0280 job_->addMetaData(QStringLiteral("Languages"), localeName); 0281 headers += QStringLiteral("x-imdb-user-country: %1").arg(localeName.section(QLatin1Char('-'), 1, 1)); 0282 0283 job_->addMetaData(QStringLiteral("customHTTPHeader"), headers.join(QLatin1String("\r\n"))); 0284 } 0285 0286 Tellico::Data::EntryPtr IMDBFetcher::readGraphQL(const QString& imdbId_, const QString& titleType_) { 0287 const auto query = titleType_ == QLatin1String("TV Series") ? episodeQuery() : titleQuery(); 0288 QJsonObject vars; 0289 vars.insert(QLatin1String("id"), imdbId_); 0290 0291 QJsonObject payload; 0292 payload.insert(QLatin1String("operationName"), QLatin1String("TitleFull")); 0293 payload.insert(QLatin1String("query"), query); 0294 payload.insert(QLatin1String("variables"), vars); 0295 0296 QPointer<KIO::StoredTransferJob> job = KIO::storedHttpPost(QJsonDocument(payload).toJson(), 0297 QUrl(QLatin1String("https://api.graphql.imdb.com")), 0298 KIO::HideProgressInfo); 0299 configureJob(job); 0300 0301 if(!job->exec()) { 0302 myDebug() << "IMDB: graphql failure"; 0303 myDebug() << job->errorString(); 0304 return Data::EntryPtr(); 0305 } 0306 0307 const auto data = job->data(); 0308 #if 0 0309 myWarning() << "Remove JSON debug from imdbfetcher.cpp"; 0310 QFile f(QString::fromLatin1("/tmp/imdb-graphql-title.json")); 0311 if(f.open(QIODevice::WriteOnly)) { 0312 QTextStream t(&f); 0313 t.setCodec("UTF-8"); 0314 t << QString::fromUtf8(data.constData(), data.size()); 0315 } 0316 f.close(); 0317 #endif 0318 return parseResult(data); 0319 } 0320 0321 Tellico::Data::EntryPtr IMDBFetcher::parseResult(const QByteArray& data_) { 0322 QJsonParseError jsonError; 0323 QJsonDocument doc = QJsonDocument::fromJson(data_, &jsonError); 0324 if(doc.isNull()) { 0325 myDebug() << "null JSON document:" << jsonError.errorString(); 0326 message(jsonError.errorString(), MessageHandler::Error); 0327 return Data::EntryPtr(); 0328 } 0329 Data::CollPtr coll(new Data::VideoCollection(true)); 0330 Data::EntryPtr entry(new Data::Entry(coll)); 0331 const auto objectMap = doc.object() 0332 .value(QLatin1String("data")).toObject() 0333 .value(QLatin1String("title")).toObject().toVariantMap(); 0334 entry->setField(QStringLiteral("title"), mapValue(objectMap, "titleText", "text")); 0335 entry->setField(QStringLiteral("year"), mapValue(objectMap, "releaseYear", "year")); 0336 entry->setField(QStringLiteral("language"), mapValue(objectMap, "spokenLanguages", "spokenLanguages")); 0337 entry->setField(QStringLiteral("plot"), mapValue(objectMap, "plot", "plotText", "plainText")); 0338 entry->setField(QStringLiteral("genre"), mapValue(objectMap, "genres", "genres", "text")); 0339 entry->setField(QStringLiteral("nationality"), mapValue(objectMap, "countriesOfOrigin", "countries", "text")); 0340 entry->setField(QStringLiteral("audio-track"), mapValue(objectMap, "technicalSpecifications", "soundMixes", "items", "text")); 0341 entry->setField(QStringLiteral("aspect-ratio"), mapValue(objectMap, "technicalSpecifications", "aspectRatios", "items", "aspectRatio")); 0342 entry->setField(QStringLiteral("color"), mapValue(objectMap, "technicalSpecifications", "colorations", "items", "text")); 0343 const int runTime = mapValue(objectMap, "runtime", "seconds").toInt(); 0344 if(runTime > 0) { 0345 entry->setField(QStringLiteral("running-time"), QString::number(runTime/60)); 0346 } 0347 entry->setField(QStringLiteral("language"), mapValue(objectMap, "spokenLanguages", "spokenLanguages", "text")); 0348 entry->setField(QStringLiteral("plot"), mapValue(objectMap, "plot", "plotText", "plainText")); 0349 0350 if(m_imageSize != NoImage) { 0351 QUrl imageUrl(mapValue(objectMap, "primaryImage", "url")); 0352 // LargeImage just means use default available size 0353 if(m_imageSize != LargeImage) { 0354 // limit to 256 for small and 640 for medium 0355 const int maxDim = m_imageSize == SmallImage ? 256 : 640; 0356 const auto imageWidth = mapValue(objectMap, "primaryImage", "width").toFloat(); 0357 const auto imageHeight = mapValue(objectMap, "primaryImage", "height").toFloat(); 0358 const auto ratio = imageWidth/imageHeight; 0359 int newWidth, newHeight; 0360 if(ratio < 1) { 0361 newWidth = ratio*maxDim; 0362 newHeight = maxDim; 0363 } else { 0364 newWidth = maxDim; 0365 newHeight = ratio*maxDim; 0366 } 0367 auto param = QStringLiteral("QL75_SX%1_CR0,0,%1,%2_.jpg").arg(newWidth).arg(newHeight); 0368 imageUrl.setPath(imageUrl.path().replace(QLatin1String(".jpg"), param)); 0369 } 0370 entry->setField(QStringLiteral("cover"), ImageFactory::addImage(imageUrl, true)); 0371 } 0372 0373 QStringList studios; 0374 auto list = objectMap.value(QLatin1String("companyCredits")).toMap().value(QLatin1String("edges")).toList(); 0375 for(const auto& edge: qAsConst(list)) { 0376 studios += mapValue(edge.toMap(), "node", "company", "companyText", "text"); 0377 } 0378 entry->setField(QStringLiteral("studio"), studios.join(FieldFormat::delimiterString())); 0379 0380 const QString certification(QStringLiteral("certification")); 0381 QString cert = mapValue(objectMap, "certificate", "rating"); 0382 if(!cert.isEmpty()) { 0383 // set default certification, assuming US for now 0384 if(cert == QLatin1String("Not Rated")) { 0385 cert = QLatin1Char('U'); 0386 } 0387 const QString certCountry = mapValue(objectMap, "certificate", "country", "text"); 0388 if(certCountry == QLatin1String("United States")) { 0389 cert += QStringLiteral(" (USA)"); 0390 } else if(!certCountry.isEmpty()) { 0391 cert += QStringLiteral(" (%1)").arg(certCountry); 0392 } 0393 const QStringList& certsAllowed = coll->fieldByName(certification)->allowed(); 0394 if(certsAllowed.contains(cert)) { 0395 entry->setField(certification, cert); 0396 } else { 0397 myLog() << "Skipping certification as not allowed:" << cert; 0398 } 0399 } 0400 0401 QStringList directors; 0402 list = objectMap.value(QLatin1String("principalDirectors")).toList(); 0403 for(const auto& director: qAsConst(list)) { 0404 directors += mapValue(director.toMap(), "credits", "name", "nameText", "text"); 0405 } 0406 // favor principalDirectors over all the directors, but episodes may be directors only 0407 if(list.isEmpty()) { 0408 list = objectMap.value(QLatin1String("directors")).toMap().value(QLatin1String("edges")).toList(); 0409 for(const auto& edge: qAsConst(list)) { 0410 directors += mapValue(edge.toMap(), "node", "name", "nameText", "text"); 0411 } 0412 } 0413 entry->setField(QStringLiteral("director"), directors.join(FieldFormat::delimiterString())); 0414 0415 QStringList producers; 0416 list = objectMap.value(QLatin1String("producers")).toMap().value(QLatin1String("edges")).toList(); 0417 for(const auto& edge: qAsConst(list)) { 0418 producers += mapValue(edge.toMap(), "node", "name", "nameText", "text"); 0419 } 0420 entry->setField(QStringLiteral("producer"), producers.join(FieldFormat::delimiterString())); 0421 0422 QStringList composers; 0423 list = objectMap.value(QLatin1String("composers")).toMap().value(QLatin1String("edges")).toList(); 0424 for(const auto& edge: qAsConst(list)) { 0425 composers += mapValue(edge.toMap(), "node", "name", "nameText", "text"); 0426 } 0427 entry->setField(QStringLiteral("composer"), composers.join(FieldFormat::delimiterString())); 0428 0429 QStringList writers; 0430 list = objectMap.value(QLatin1String("writers")).toMap().value(QLatin1String("edges")).toList(); 0431 for(const auto& edge: qAsConst(list)) { 0432 writers += mapValue(edge.toMap(), "node", "name", "nameText", "text"); 0433 } 0434 entry->setField(QStringLiteral("writer"), writers.join(FieldFormat::delimiterString())); 0435 0436 QStringList cast; 0437 list = objectMap.value(QLatin1String("cast")).toMap().value(QLatin1String("edges")).toList(); 0438 for(const auto& edge: qAsConst(list)) { 0439 const auto map = edge.toMap().value(QLatin1String("node")).toMap(); 0440 cast += mapValue(map, "name", "nameText", "text") 0441 + FieldFormat::columnDelimiterString() 0442 + mapValue(map, "characters", "name"); 0443 if(cast.count() >= m_numCast) { 0444 break; 0445 } 0446 } 0447 entry->setField(QStringLiteral("cast"), cast.join(FieldFormat::rowDelimiterString())); 0448 0449 const QString imdb(QStringLiteral("imdb")); 0450 if(!coll->hasField(imdb) && optionalFields().contains(imdb)) { 0451 coll->addField(Data::Field::createDefaultField(Data::Field::ImdbField)); 0452 } 0453 if(coll->hasField(imdb) && coll->fieldByName(imdb)->type() == Data::Field::URL) { 0454 entry->setField(imdb, mapValue(objectMap, "canonicalUrl")); 0455 } 0456 0457 const QString imdbRating(QStringLiteral("imdb-rating")); 0458 if(optionalFields().contains(imdbRating)) { 0459 if(!coll->hasField(imdbRating)) { 0460 Data::FieldPtr f(new Data::Field(imdbRating, i18n("IMDb Rating"), Data::Field::Rating)); 0461 f->setCategory(i18n("General")); 0462 f->setProperty(QStringLiteral("maximum"), QStringLiteral("10")); 0463 coll->addField(f); 0464 } 0465 const auto value = objectMap.value(QLatin1String("ratingsSummary")).toMap() 0466 .value(QLatin1String("aggregateRating")).toFloat(); 0467 entry->setField(imdbRating, QString::number(value)); 0468 } 0469 0470 const QString origtitle(QStringLiteral("origtitle")); 0471 if(optionalFields().contains(origtitle)) { 0472 Data::FieldPtr f(new Data::Field(origtitle, i18n("Original Title"))); 0473 f->setFormatType(FieldFormat::FormatTitle); 0474 coll->addField(f); 0475 entry->setField(origtitle, mapValue(objectMap, "originalTitleText", "text")); 0476 } 0477 0478 const QString alttitle(QStringLiteral("alttitle")); 0479 if(optionalFields().contains(alttitle)) { 0480 Data::FieldPtr f(new Data::Field(alttitle, i18n("Alternative Titles"), Data::Field::Table)); 0481 f->setFormatType(FieldFormat::FormatTitle); 0482 coll->addField(f); 0483 QStringList akas; 0484 list = objectMap.value(QLatin1String("akas")).toMap().value(QLatin1String("edges")).toList(); 0485 for(const auto& edge: qAsConst(list)) { 0486 akas += mapValue(edge.toMap(), "node", "text"); 0487 } 0488 akas.removeDuplicates(); 0489 entry->setField(alttitle, akas.join(FieldFormat::rowDelimiterString())); 0490 } 0491 0492 const QString episode(QStringLiteral("episode")); 0493 if(mapValue(objectMap, "titleType", "text") == QLatin1String("TV Series") && 0494 optionalFields().contains(episode)) { 0495 coll->addField(Data::Field::createDefaultField(Data::Field::EpisodeField)); 0496 QStringList episodes; 0497 list = objectMap.value(QLatin1String("episodes")).toMap() 0498 .value(QLatin1String("episodes")).toMap() 0499 .value(QLatin1String("edges")).toList(); 0500 for(const auto& edge: qAsConst(list)) { 0501 const auto nodeMap = edge.toMap().value(QLatin1String("node")).toMap(); 0502 QString row = mapValue(nodeMap, "titleText", "text"); 0503 const auto seriesMap = nodeMap.value(QLatin1String("series")).toMap(); 0504 // future episodes have a "Episode #" start 0505 if(!row.startsWith(QLatin1String("Episode #")) && 0506 seriesMap.contains(QLatin1String("displayableEpisodeNumber"))) { 0507 row += FieldFormat::columnDelimiterString() + mapValue(seriesMap, "displayableEpisodeNumber", "displayableSeason", "text") 0508 + FieldFormat::columnDelimiterString() + mapValue(seriesMap, "displayableEpisodeNumber", "episodeNumber", "text"); 0509 } 0510 episodes += row; 0511 } 0512 entry->setField(episode, episodes.join(FieldFormat::rowDelimiterString())); 0513 } 0514 0515 return entry; 0516 } 0517 0518 QString IMDBFetcher::defaultName() { 0519 return i18n("Internet Movie Database"); 0520 } 0521 0522 QString IMDBFetcher::defaultIcon() { 0523 return favIcon(QUrl(QLatin1String("https://www.imdb.com")), 0524 QUrl(QLatin1String("https://m.media-amazon.com/images/G/01/imdb/images-ANDW73HA/favicon_desktop_32x32._CB1582158068_.png"))); 0525 } 0526 0527 //static 0528 Tellico::StringHash IMDBFetcher::allOptionalFields() { 0529 StringHash hash; 0530 hash[QStringLiteral("imdb")] = i18n("IMDb Link"); 0531 hash[QStringLiteral("imdb-rating")] = i18n("IMDb Rating"); 0532 hash[QStringLiteral("alttitle")] = i18n("Alternative Titles"); 0533 hash[QStringLiteral("allcertification")] = i18n("Certifications"); 0534 hash[QStringLiteral("origtitle")] = i18n("Original Title"); 0535 hash[QStringLiteral("episode")] = i18n("Episodes"); 0536 return hash; 0537 } 0538 0539 Tellico::Fetch::ConfigWidget* IMDBFetcher::configWidget(QWidget* parent_) const { 0540 return new IMDBFetcher::ConfigWidget(parent_, this); 0541 } 0542 0543 IMDBFetcher::ConfigWidget::ConfigWidget(QWidget* parent_, const IMDBFetcher* fetcher_/*=0*/) 0544 : Fetch::ConfigWidget(parent_) { 0545 QGridLayout* l = new QGridLayout(optionsWidget()); 0546 l->setSpacing(4); 0547 l->setColumnStretch(1, 10); 0548 0549 int row = -1; 0550 0551 QLabel* label = new QLabel(i18n("&Maximum cast: "), optionsWidget()); 0552 l->addWidget(label, ++row, 0); 0553 m_numCast = new QSpinBox(optionsWidget()); 0554 m_numCast->setMaximum(99); 0555 m_numCast->setMinimum(0); 0556 m_numCast->setValue(IMDB_DEFAULT_CAST_SIZE); 0557 #if (QT_VERSION < QT_VERSION_CHECK(5, 14, 0)) 0558 void (QSpinBox::* textChanged)(const QString&) = &QSpinBox::valueChanged; 0559 #else 0560 void (QSpinBox::* textChanged)(const QString&) = &QSpinBox::textChanged; 0561 #endif 0562 connect(m_numCast, textChanged, this, &ConfigWidget::slotSetModified); 0563 l->addWidget(m_numCast, row, 1); 0564 QString w = i18n("The list of cast members may include many people. Set the maximum number returned from the search."); 0565 label->setWhatsThis(w); 0566 m_numCast->setWhatsThis(w); 0567 label->setBuddy(m_numCast); 0568 0569 label = new QLabel(i18n("&Image size: "), optionsWidget()); 0570 l->addWidget(label, ++row, 0); 0571 m_imageCombo = new GUI::ComboBox(optionsWidget()); 0572 m_imageCombo->addItem(i18n("Small Image"), SmallImage); 0573 m_imageCombo->addItem(i18n("Medium Image"), MediumImage); 0574 m_imageCombo->addItem(i18n("Large Image"), LargeImage); 0575 m_imageCombo->addItem(i18n("No Image"), NoImage); 0576 void (GUI::ComboBox::* activatedInt)(int) = &GUI::ComboBox::activated; 0577 connect(m_imageCombo, activatedInt, this, &ConfigWidget::slotSetModified); 0578 l->addWidget(m_imageCombo, row, 1); 0579 w = i18n("The cover image may be downloaded as well. However, too many large images in the " 0580 "collection may degrade performance."); 0581 label->setWhatsThis(w); 0582 m_imageCombo->setWhatsThis(w); 0583 label->setBuddy(m_imageCombo); 0584 0585 auto localeGroupBox = new QGroupBox(i18n("Locale"), optionsWidget()); 0586 l->addWidget(localeGroupBox, ++row, 0, 1, -1); 0587 0588 m_systemLocaleRadioButton = new QRadioButton(i18n("Use system locale"), localeGroupBox); 0589 m_customLocaleRadioButton = new QRadioButton(i18n("Use custom locale"), localeGroupBox); 0590 m_customLocaleEdit = new GUI::LineEdit(localeGroupBox); 0591 m_customLocaleEdit->setEnabled(false); 0592 0593 auto localeGroupLayout = new QGridLayout(localeGroupBox); 0594 localeGroupLayout->addWidget(m_systemLocaleRadioButton, 0, 0); 0595 localeGroupLayout->addWidget(m_customLocaleRadioButton, 1, 0); 0596 localeGroupLayout->addWidget(m_customLocaleEdit, 1, 1); 0597 localeGroupBox->setLayout(localeGroupLayout); 0598 0599 auto localeGroup = new QButtonGroup(localeGroupBox); 0600 localeGroup->addButton(m_systemLocaleRadioButton, 0 /* id */); 0601 localeGroup->addButton(m_customLocaleRadioButton, 1 /* id */); 0602 #if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0)) 0603 void (QButtonGroup::* buttonClicked)(int) = &QButtonGroup::buttonClicked; 0604 connect(localeGroup, buttonClicked, this, &ConfigWidget::slotSetModified); 0605 connect(localeGroup, buttonClicked, this, &ConfigWidget::slotLocaleChanged); 0606 #else 0607 connect(localeGroup, &QButtonGroup::idClicked, this, &ConfigWidget::slotSetModified); 0608 connect(localeGroup, &QButtonGroup::idClicked, this, &ConfigWidget::slotLocaleChanged); 0609 #endif 0610 connect(m_customLocaleEdit, &QLineEdit::textChanged, this, &ConfigWidget::slotSetModified); 0611 0612 l->setRowStretch(++row, 10); 0613 0614 // now add additional fields widget 0615 addFieldsWidget(IMDBFetcher::allOptionalFields(), fetcher_ ? fetcher_->optionalFields() : QStringList()); 0616 KAcceleratorManager::manage(optionsWidget()); 0617 0618 if(fetcher_) { 0619 m_numCast->setValue(fetcher_->m_numCast); 0620 m_imageCombo->setCurrentData(fetcher_->m_imageSize); 0621 if(fetcher_->m_useSystemLocale) { 0622 m_systemLocaleRadioButton->setChecked(true); 0623 m_customLocaleEdit->setText(QLocale().name()); 0624 } else { 0625 m_customLocaleRadioButton->setChecked(true); 0626 m_customLocaleEdit->setEnabled(true); 0627 m_customLocaleEdit->setText(fetcher_->m_customLocale); 0628 } 0629 } else { //defaults 0630 m_imageCombo->setCurrentData(MediumImage); 0631 } 0632 } 0633 0634 void IMDBFetcher::ConfigWidget::saveConfigHook(KConfigGroup& config_) { 0635 config_.deleteEntry("Host"); // clear old host entry 0636 config_.writeEntry("Max Cast", m_numCast->value()); 0637 config_.deleteEntry("Fetch Images"); // no longer used 0638 const int n = m_imageCombo->currentData().toInt(); 0639 config_.writeEntry("Image Size", n); 0640 config_.deleteEntry("Lang"); // no longer used 0641 config_.writeEntry("System Locale", m_systemLocaleRadioButton->isChecked()); 0642 config_.writeEntry("Custom Locale", m_customLocaleRadioButton->isChecked() ? 0643 m_customLocaleEdit->text().trimmed() : 0644 QString()); 0645 } 0646 0647 QString IMDBFetcher::ConfigWidget::preferredName() const { 0648 return i18n("Internet Movie Database"); 0649 } 0650 0651 void IMDBFetcher::ConfigWidget::slotLocaleChanged(int id_) { 0652 // id 0 is system locale, 1 is custom locale 0653 m_customLocaleEdit->setEnabled(id_ == 1); 0654 }