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 }