File indexing completed on 2024-05-12 04:33:31

0001 /*
0002     SPDX-FileCopyrightText: 2008 Pino Toscano <pino@kde.org>
0003     SPDX-FileCopyrightText: 2008 Harri Porten <porten@kde.org>
0004 
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 #include "js_app_p.h"
0009 
0010 #include <QApplication>
0011 
0012 #include <QLocale>
0013 #include <QTimer>
0014 
0015 #include <KLocalizedString>
0016 #include <QCheckBox>
0017 #include <QJSEngine>
0018 #include <QMenu>
0019 #include <QMessageBox>
0020 
0021 #include "../document_p.h"
0022 #include "../scripter.h"
0023 #include "config-okular.h"
0024 #include "js_fullscreen_p.h"
0025 
0026 using namespace Okular;
0027 
0028 #define OKULAR_TIMERID QStringLiteral("okular_timerID")
0029 
0030 typedef QHash<int, QTimer *> TimerCache;
0031 Q_GLOBAL_STATIC(TimerCache, g_timerCache)
0032 
0033 // the acrobat version we fake
0034 static const double fake_acroversion = 8.00;
0035 
0036 // The property used to hold the value to return from app.popUpMenuEx
0037 static const char *kResultProperty = "result";
0038 
0039 static const struct FakePluginInfo {
0040     const char *name;
0041     bool certified;
0042     bool loaded;
0043     const char *path;
0044 } s_fake_plugins[] = {{"Annots", true, true, ""}, {"EFS", true, true, ""}, {"EScript", true, true, ""}, {"Forms", true, true, ""}, {"ReadOutLoud", true, true, ""}, {"WebLink", true, true, ""}};
0045 static const int s_num_fake_plugins = sizeof(s_fake_plugins) / sizeof(s_fake_plugins[0]);
0046 
0047 int JSApp::formsVersion() const
0048 {
0049     // faking a bit...
0050     return fake_acroversion;
0051 }
0052 
0053 QString JSApp::language() const
0054 {
0055     QLocale locale;
0056     QString lang = QLocale::languageToString(locale.language());
0057     QString country = QLocale::territoryToString(locale.territory());
0058     QString acroLang = QStringLiteral("ENU");
0059     if (lang == QLatin1String("da")) {
0060         acroLang = QStringLiteral("DAN"); // Danish
0061     } else if (lang == QLatin1String("de")) {
0062         acroLang = QStringLiteral("DEU"); // German
0063     } else if (lang == QLatin1String("en")) {
0064         acroLang = QStringLiteral("ENU"); // English
0065     } else if (lang == QLatin1String("es")) {
0066         acroLang = QStringLiteral("ESP"); // Spanish
0067     } else if (lang == QLatin1String("fr")) {
0068         acroLang = QStringLiteral("FRA"); // French
0069     } else if (lang == QLatin1String("it")) {
0070         acroLang = QStringLiteral("ITA"); // Italian
0071     } else if (lang == QLatin1String("ko")) {
0072         acroLang = QStringLiteral("KOR"); // Korean
0073     } else if (lang == QLatin1String("ja")) {
0074         acroLang = QStringLiteral("JPN"); // Japanese
0075     } else if (lang == QLatin1String("nl")) {
0076         acroLang = QStringLiteral("NLD"); // Dutch
0077     } else if (lang == QLatin1String("pt") && country == QLatin1String("BR")) {
0078         acroLang = QStringLiteral("PTB"); // Brazilian Portuguese
0079     } else if (lang == QLatin1String("fi")) {
0080         acroLang = QStringLiteral("SUO"); // Finnish
0081     } else if (lang == QLatin1String("sv")) {
0082         acroLang = QStringLiteral("SVE"); // Swedish
0083     } else if (lang == QLatin1String("zh") && country == QLatin1String("CN")) {
0084         acroLang = QStringLiteral("CHS"); // Chinese Simplified
0085     } else if (lang == QLatin1String("zh") && country == QLatin1String("TW")) {
0086         acroLang = QStringLiteral("CHT"); // Chinese Traditional
0087     }
0088     return acroLang;
0089 }
0090 
0091 int JSApp::numPlugIns() const
0092 {
0093     return s_num_fake_plugins;
0094 }
0095 
0096 QString JSApp::platform() const
0097 {
0098 #if defined(Q_OS_WIN)
0099     return QString::fromLatin1("WIN");
0100 #elif defined(Q_OS_MAC)
0101     return QString::fromLatin1("MAC");
0102 #else
0103     return QStringLiteral("UNIX");
0104 #endif
0105 }
0106 
0107 QJSValue JSApp::plugIns() const
0108 {
0109     QJSValue plugins = qjsEngine(this)->newArray(s_num_fake_plugins);
0110     for (int i = 0; i < s_num_fake_plugins; ++i) {
0111         const FakePluginInfo &info = s_fake_plugins[i];
0112         QJSValue plugin = qjsEngine(this)->newObject();
0113         plugin.setProperty(QStringLiteral("certified"), info.certified);
0114         plugin.setProperty(QStringLiteral("loaded"), info.loaded);
0115         plugin.setProperty(QStringLiteral("name"), info.name);
0116         plugin.setProperty(QStringLiteral("path"), info.path);
0117         plugin.setProperty(QStringLiteral("version"), fake_acroversion);
0118         plugins.setProperty(i, plugin);
0119     }
0120     return plugins;
0121 }
0122 
0123 QStringList JSApp::printColorProfiles() const
0124 {
0125     return QStringList();
0126 }
0127 
0128 QStringList JSApp::printerNames() const
0129 {
0130     return QStringList();
0131 }
0132 
0133 QString JSApp::viewerType() const
0134 {
0135     // faking a bit...
0136     return QStringLiteral("Reader");
0137 }
0138 
0139 QString JSApp::viewerVariation() const
0140 {
0141     // faking a bit...
0142     return QStringLiteral("Reader");
0143 }
0144 
0145 int JSApp::viewerVersion() const
0146 {
0147     // faking a bit...
0148     return fake_acroversion;
0149 }
0150 
0151 /*
0152     Alert function defined in the reference, it shows a Dialog Box with options.
0153     app.alert()
0154 */
0155 int JSApp::alert(const QJSValue &arguments)
0156 {
0157     const auto cMsg = arguments.property(QStringLiteral("cMsg")).toString();
0158     const auto nIcon = arguments.property(QStringLiteral("nIcon")).toInt();
0159     const auto nType = arguments.property(QStringLiteral("nType")).toInt();
0160     const auto cTitle = arguments.property(QStringLiteral("cTitle")).toString();
0161     const auto oCheckbox = arguments.property(QStringLiteral("oCheckbox"));
0162     return alert(cMsg, nIcon, nType, cTitle, QJSValue(), oCheckbox);
0163 }
0164 
0165 int JSApp::alert(const QString &cMsg, int nIcon, int nType, const QString &cTitle, [[maybe_unused]] const QJSValue &oDoc, const QJSValue &oCheckbox)
0166 {
0167     QMessageBox::Icon icon = QMessageBox::Critical;
0168     switch (nIcon) {
0169     case 0:
0170         icon = QMessageBox::Critical;
0171         break;
0172     case 1:
0173         icon = QMessageBox::Warning;
0174         break;
0175     case 2:
0176         icon = QMessageBox::Question;
0177         break;
0178     case 3:
0179         icon = QMessageBox::Information;
0180         break;
0181     }
0182 
0183     const QString title = !cTitle.isEmpty() ? cTitle : QStringLiteral("Okular");
0184     QMessageBox box(icon, title, cMsg);
0185 
0186     QMessageBox::StandardButtons buttons = QMessageBox::Ok;
0187     switch (nType) {
0188     case 0:
0189         buttons = QMessageBox::Ok;
0190         break;
0191     case 1:
0192         buttons = QMessageBox::Ok | QMessageBox::Cancel;
0193         break;
0194     case 2:
0195         buttons = QMessageBox::Yes | QMessageBox::No;
0196         break;
0197     case 3:
0198         buttons = QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel;
0199         break;
0200     }
0201     box.setStandardButtons(buttons);
0202 
0203     QCheckBox *checkBox = nullptr;
0204     if (oCheckbox.isObject()) {
0205         const auto oMsg = oCheckbox.property(QStringLiteral("cMsg"));
0206         QString msg = i18n("Do not show this message again");
0207 
0208         if (oMsg.isString()) {
0209             msg = oMsg.toString();
0210         }
0211 
0212         bool bInitialValue = false;
0213         const auto value = oCheckbox.property(QStringLiteral("bInitialValue"));
0214         if (value.isBool()) {
0215             bInitialValue = value.toBool();
0216         }
0217         checkBox = new QCheckBox(msg);
0218         checkBox->setChecked(bInitialValue);
0219         box.setCheckBox(checkBox);
0220     }
0221 
0222     // halt timeout until the user has responded
0223     QMetaObject::invokeMethod(m_watchdogTimer, qOverload<>(&QTimer::stop));
0224 
0225     int button = box.exec();
0226 
0227     // restart max allowed time
0228     QMetaObject::invokeMethod(m_watchdogTimer, qOverload<>(&QTimer::start));
0229 
0230     int ret = 0;
0231 
0232     switch (button) {
0233     case QMessageBox::Ok:
0234         ret = 1;
0235         break;
0236     case QMessageBox::Cancel:
0237         ret = 2;
0238         break;
0239     case QMessageBox::No:
0240         ret = 3;
0241         break;
0242     case QMessageBox::Yes:
0243         ret = 4;
0244         break;
0245     }
0246 
0247     if (checkBox) {
0248         QJSValue(oCheckbox).setProperty(QStringLiteral("bAfterValue"), checkBox->isChecked());
0249     }
0250 
0251     delete checkBox;
0252 
0253     return ret;
0254 }
0255 
0256 void JSApp::beep([[maybe_unused]] int nType)
0257 {
0258     QApplication::beep();
0259 }
0260 
0261 QJSValue JSApp::getNthPlugInName(int nIndex) const
0262 {
0263     if (nIndex < 0 || nIndex >= s_num_fake_plugins) {
0264         return qjsEngine(this)->newErrorObject(QJSValue::TypeError, QStringLiteral("PlugIn index out of bounds"));
0265     }
0266 
0267     const FakePluginInfo &info = s_fake_plugins[nIndex];
0268     return info.name;
0269 }
0270 
0271 void JSApp::goBack()
0272 {
0273     if (!m_doc->m_parent->historyAtBegin()) {
0274         m_doc->m_parent->setPrevViewport();
0275     }
0276 }
0277 
0278 void JSApp::goForward()
0279 {
0280     if (!m_doc->m_parent->historyAtEnd()) {
0281         m_doc->m_parent->setNextViewport();
0282     }
0283 }
0284 
0285 // app.setInterval()
0286 QJSValue JSApp::setInterval(const QString &cExpr, int nMilliseconds)
0287 {
0288     QTimer *timer = new QTimer();
0289 
0290     QObject::connect(timer, &QTimer::timeout, m_doc->m_parent, [=]() { m_doc->executeScript(cExpr); });
0291 
0292     timer->start(nMilliseconds);
0293 
0294     return JSApp::wrapTimer(timer);
0295 }
0296 
0297 // app.clearInterval()
0298 void JSApp::clearInterval(const QJSValue &oInterval)
0299 {
0300     const int timerId = oInterval.property(OKULAR_TIMERID).toInt();
0301     QTimer *timer = g_timerCache->value(timerId);
0302     if (timer != nullptr) {
0303         timer->stop();
0304         g_timerCache->remove(timerId);
0305         delete timer;
0306     }
0307 }
0308 
0309 // app.setTimeOut()
0310 QJSValue JSApp::setTimeOut(const QString &cExpr, int nMilliseconds)
0311 {
0312     QTimer *timer = new QTimer();
0313     timer->setSingleShot(true);
0314 
0315     QObject::connect(timer, &QTimer::timeout, m_doc->m_parent, [=]() { m_doc->executeScript(cExpr); });
0316 
0317     timer->start(nMilliseconds);
0318 
0319     return JSApp::wrapTimer(timer);
0320 }
0321 
0322 // app.clearTimeOut()
0323 void JSApp::clearTimeOut(const QJSValue &oTime)
0324 {
0325     const int timerId = oTime.property(OKULAR_TIMERID).toInt();
0326     QTimer *timer = g_timerCache->value(timerId);
0327 
0328     if (timer != nullptr) {
0329         timer->stop();
0330         g_timerCache->remove(timerId);
0331         delete timer;
0332     }
0333 }
0334 
0335 // app.popUpMenuEx()
0336 
0337 bool JSApp::createPopUpMenuTree(int depth, QMenu *rootMenu, const QJSValue &arguments)
0338 {
0339     const int nArgs = arguments.property(QStringLiteral("length")).toInt();
0340 
0341     // If no menu to add or if we got too deep in recursion
0342     if (nArgs == 0 || depth > 20) {
0343         return false;
0344     }
0345 
0346     for (int i = 0; i < nArgs; ++i) {
0347         const QJSValue item = arguments.property(i);
0348         const QString cName = item.property(QStringLiteral("cName")).toString();
0349         const QJSValue cResultProperty = item.property(QStringLiteral("cResult"));
0350         const QJSValue oSubMenu = item.property(QStringLiteral("oSubMenu"));
0351 
0352         if (oSubMenu.isArray()) {
0353             QMenu *subMenu = rootMenu->addMenu(cName);
0354             createPopUpMenuTree(depth + 1, subMenu, oSubMenu);
0355         } else {
0356             QAction *a = rootMenu->addAction(cName);
0357             if (cResultProperty.isUndefined()) {
0358                 a->setProperty(kResultProperty, cName);
0359             } else {
0360                 a->setProperty(kResultProperty, cResultProperty.toString());
0361             }
0362         }
0363     }
0364 
0365     return true;
0366 }
0367 
0368 QJSValue JSApp::okular_popUpMenuEx(const QJSValue &arguments)
0369 {
0370     QMenu m;
0371 
0372     // Object name is used for tests.
0373     m.setObjectName(QStringLiteral("popUpMenuEx"));
0374 
0375     if (!createPopUpMenuTree(0, &m, arguments)) {
0376         return {};
0377     }
0378 
0379     QAction *result = m.exec(QCursor::pos());
0380     return result ? result->property(kResultProperty).toString() : QString();
0381 }
0382 
0383 JSApp::JSApp(DocumentPrivate *doc, QTimer *watchdogTimer, QObject *parent)
0384     : QObject(parent)
0385     , m_doc(doc)
0386     , m_watchdogTimer(watchdogTimer)
0387 {
0388 }
0389 
0390 JSApp::~JSApp() = default;
0391 
0392 QJSValue JSApp::wrapTimer(QTimer *timer) const
0393 {
0394     QJSValue timerObject = qjsEngine(this)->newObject();
0395     timerObject.setProperty(OKULAR_TIMERID, timer->timerId());
0396 
0397     g_timerCache->insert(timer->timerId(), timer);
0398 
0399     return timerObject;
0400 }
0401 
0402 void JSApp::clearCachedFields()
0403 {
0404     if (g_timerCache) {
0405         qDeleteAll(g_timerCache->begin(), g_timerCache->end());
0406         g_timerCache->clear();
0407     }
0408 }