File indexing completed on 2024-04-28 16:01:21
0001 /* 0002 SPDX-FileCopyrightText: 2007 Trolltech ASA 0003 SPDX-FileCopyrightText: 2008-2010 Urs Wolfer <uwolfer@kde.org> 0004 SPDX-FileCopyrightText: 2008 Laurent Montel <montel@kde.org> 0005 SPDX-FileCopyrightText: 2009 Dawit Alemayehu <adawit@kde.org> 0006 0007 SPDX-License-Identifier: LGPL-2.1-or-later 0008 */ 0009 0010 #include "webview.h" 0011 #include "webpage.h" 0012 #include "kwebkitpart_debug.h" 0013 #include "kwebkitpart.h" 0014 #include "settings/webkitsettings.h" 0015 0016 #include <KIO/Global> 0017 #include <KAboutData> 0018 #include <KActionCollection> 0019 #include <KConfigGroup> 0020 #include <KUriFilter> 0021 0022 #include <KActionMenu> 0023 #include <KIO/AccessManager> 0024 #include <KStringHandler> 0025 #include <KLocalizedString> 0026 0027 #include <QDropEvent> 0028 #include <QLabel> 0029 #include <QWebElement> 0030 #include <QWebInspector> 0031 #include <QToolTip> 0032 #include <QCoreApplication> 0033 #include <QMimeType> 0034 #include <QMimeDatabase> 0035 0036 #include <unistd.h> 0037 0038 #define QL1S(x) QLatin1String(x) 0039 #define QL1C(x) QLatin1Char(x) 0040 0041 #define ALTERNATE_DEFAULT_WEB_SHORTCUT QL1S("google") 0042 #define ALTERNATE_WEB_SHORTCUTS QStringList() << QL1S("google") << QL1S("wikipedia") << QL1S("webster") << QL1S("dmoz") 0043 0044 WebView::WebView(KWebKitPart* part, QWidget* parent) 0045 :KWebView(parent, false), 0046 m_actionCollection(new KActionCollection(this)), 0047 m_part(part), 0048 m_webInspector(nullptr), 0049 m_autoScrollTimerId(-1), 0050 m_verticalAutoScrollSpeed(0), 0051 m_horizontalAutoScrollSpeed(0), 0052 m_accessKeyActivated(NotActivated) 0053 { 0054 setAcceptDrops(true); 0055 0056 // Create the custom page... 0057 setPage(new WebPage(part, this)); 0058 0059 connect(this, SIGNAL(loadStarted()), this, SLOT(slotStopAutoScroll())); 0060 connect(this, SIGNAL(loadStarted()), this, SLOT(hideAccessKeys())); 0061 connect(page(), SIGNAL(scrollRequested(int,int,QRect)), this, SLOT(hideAccessKeys())); 0062 0063 if (WebKitSettings::self()->zoomToDPI()) 0064 setZoomFactor(logicalDpiY() / 96.0f); 0065 } 0066 0067 WebView::~WebView() 0068 { 0069 //qCDebug(KWEBKITPART_LOG); 0070 } 0071 0072 void WebView::loadUrl(const QUrl& url, const KParts::OpenUrlArguments& args, const KParts::BrowserArguments& bargs) 0073 { 0074 page()->setProperty("NavigationTypeUrlEntered", true); 0075 0076 if (args.reload() && url == this->url()) { 0077 reload(); 0078 return; 0079 } 0080 0081 QNetworkRequest request(url); 0082 if (args.reload()) { 0083 request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork); 0084 } 0085 0086 if (bargs.postData.isEmpty()) { 0087 KWebView::load(request); 0088 } else { 0089 KWebView::load(request, QNetworkAccessManager::PostOperation, bargs.postData); 0090 } 0091 } 0092 0093 QWebHitTestResult WebView::contextMenuResult() const 0094 { 0095 return m_result; 0096 } 0097 0098 static bool isMultimediaElement(const QWebElement& element) 0099 { 0100 if (element.tagName().compare(QL1S("video"), Qt::CaseInsensitive) == 0) 0101 return true; 0102 0103 if (element.tagName().compare(QL1S("audio"), Qt::CaseInsensitive) == 0) 0104 return true; 0105 0106 return false; 0107 } 0108 0109 static void extractMimeTypeFor(const QUrl& url, QString& mimeType) 0110 { 0111 if (url.fileName().isEmpty() || url.hasFragment() || url.hasQuery()) 0112 return; 0113 0114 QMimeDatabase db; 0115 QMimeType pmt = db.mimeTypeForUrl(url); 0116 0117 // Further check for mime types guessed from the extension which, 0118 // on a web page, are more likely to be a script delivering content 0119 // of undecidable type. If the mime type from the extension is one 0120 // of these, don't use it. Retain the original type 'text/html'. 0121 if (pmt.isDefault() || 0122 pmt.inherits(QL1S("application/x-perl")) || 0123 pmt.inherits(QL1S("application/x-perl-module")) || 0124 pmt.inherits(QL1S("application/x-php")) || 0125 pmt.inherits(QL1S("application/x-python-bytecode")) || 0126 pmt.inherits(QL1S("application/x-python")) || 0127 pmt.inherits(QL1S("application/x-shellscript"))) 0128 return; 0129 0130 mimeType = pmt.name(); 0131 } 0132 0133 void WebView::contextMenuEvent(QContextMenuEvent* e) 0134 { 0135 m_result = page()->mainFrame()->hitTestContent(e->pos()); 0136 0137 // Clear the previous collection entries first... 0138 m_actionCollection->clear(); 0139 0140 KParts::BrowserExtension::PopupFlags flags = KParts::BrowserExtension::DefaultPopupItems; 0141 KParts::BrowserExtension::ActionGroupMap mapAction; 0142 QString mimeType (QL1S("text/html")); 0143 bool forcesNewWindow = false; 0144 0145 QUrl emitUrl; 0146 if (m_result.isContentEditable()) { 0147 if (m_result.element().hasAttribute(QL1S("disabled"))) { 0148 e->accept(); 0149 return; 0150 } 0151 flags |= KParts::BrowserExtension::ShowTextSelectionItems; 0152 editableContentActionPopupMenu(mapAction); 0153 } else if (isMultimediaElement(m_result.element())) { 0154 multimediaActionPopupMenu(mapAction); 0155 } else if (!m_result.linkUrl().isValid()) { 0156 if (m_result.imageUrl().isValid()) { 0157 emitUrl = m_result.imageUrl(); 0158 extractMimeTypeFor(emitUrl, mimeType); 0159 } else { 0160 flags |= KParts::BrowserExtension::ShowBookmark; 0161 flags |= KParts::BrowserExtension::ShowReload; 0162 emitUrl = m_part->url(); 0163 0164 if (m_result.isContentSelected()) { 0165 flags |= KParts::BrowserExtension::ShowTextSelectionItems; 0166 selectActionPopupMenu(mapAction); 0167 } else { 0168 flags |= KParts::BrowserExtension::ShowNavigationItems; 0169 } 0170 } 0171 partActionPopupMenu(mapAction); 0172 } else { 0173 flags |= KParts::BrowserExtension::ShowBookmark; 0174 flags |= KParts::BrowserExtension::ShowReload; 0175 flags |= KParts::BrowserExtension::IsLink; 0176 emitUrl = m_result.linkUrl(); 0177 linkActionPopupMenu(mapAction); 0178 if (emitUrl.isLocalFile()) { 0179 QMimeDatabase db; 0180 mimeType = db.mimeTypeForUrl(emitUrl).name(); 0181 } 0182 else { 0183 extractMimeTypeFor(emitUrl, mimeType); 0184 } 0185 partActionPopupMenu(mapAction); 0186 0187 // Show the OpenInThisWindow context menu item 0188 forcesNewWindow = (page()->currentFrame() != m_result.linkTargetFrame()); 0189 } 0190 0191 if (!mapAction.isEmpty()) { 0192 KParts::OpenUrlArguments args; 0193 KParts::BrowserArguments bargs; 0194 args.setMimeType(mimeType); 0195 bargs.setForcesNewWindow(forcesNewWindow); 0196 e->accept(); 0197 emit m_part->browserExtension()->popupMenu(e->globalPos(), emitUrl, static_cast<mode_t>(-1), args, bargs, flags, mapAction); 0198 return; 0199 } 0200 0201 KWebView::contextMenuEvent(e); 0202 } 0203 0204 static bool isEditableElement(QWebPage* page) 0205 { 0206 const QWebFrame* frame = (page ? page->currentFrame() : nullptr); 0207 QWebElement element = (frame ? frame->findFirstElement(QL1S(":focus")) : QWebElement()); 0208 if (!element.isNull()) { 0209 const QString tagName(element.tagName()); 0210 if (tagName.compare(QL1S("textarea"), Qt::CaseInsensitive) == 0) { 0211 return true; 0212 } 0213 const QString type(element.attribute(QL1S("type")).toLower()); 0214 if (tagName.compare(QL1S("input"), Qt::CaseInsensitive) == 0 0215 && (type.isEmpty() || type == QL1S("text") || type == QL1S("password"))) { 0216 return true; 0217 } 0218 if (element.evaluateJavaScript("this.isContentEditable").toBool()) { 0219 return true; 0220 } 0221 } 0222 return false; 0223 } 0224 0225 void WebView::keyPressEvent(QKeyEvent* e) 0226 { 0227 if (e && hasFocus()) { 0228 const int key = e->key(); 0229 if (WebKitSettings::self()->accessKeysEnabled()) { 0230 if (m_accessKeyActivated == Activated) { 0231 if (checkForAccessKey(e)) { 0232 hideAccessKeys(); 0233 e->accept(); 0234 return; 0235 } 0236 hideAccessKeys(); 0237 } else if (e->key() == Qt::Key_Control && e->modifiers() == Qt::ControlModifier && !isEditableElement(page())) { 0238 m_accessKeyActivated = PreActivated; // Only preactive here, it will be actually activated in key release. 0239 } 0240 } 0241 0242 if (e->modifiers() & Qt::ShiftModifier) { 0243 switch (key) { 0244 case Qt::Key_Up: 0245 if (!isEditableElement(page())) { 0246 m_verticalAutoScrollSpeed--; 0247 if (m_autoScrollTimerId == -1) 0248 m_autoScrollTimerId = startTimer(100); 0249 e->accept(); 0250 return; 0251 } 0252 break; 0253 case Qt::Key_Down: 0254 if (!isEditableElement(page())) { 0255 m_verticalAutoScrollSpeed++; 0256 if (m_autoScrollTimerId == -1) 0257 m_autoScrollTimerId = startTimer(100); 0258 e->accept(); 0259 return; 0260 } 0261 break; 0262 case Qt::Key_Left: 0263 if (!isEditableElement(page())) { 0264 m_horizontalAutoScrollSpeed--; 0265 if (m_autoScrollTimerId == -1) 0266 m_autoScrollTimerId = startTimer(100); 0267 e->accept(); 0268 return; 0269 } 0270 break; 0271 case Qt::Key_Right: 0272 if (!isEditableElement(page())) { 0273 m_horizontalAutoScrollSpeed--; 0274 if (m_autoScrollTimerId == -1) 0275 m_autoScrollTimerId = startTimer(100); 0276 e->accept(); 0277 return; 0278 } 0279 break; 0280 default: 0281 break; 0282 } 0283 } else if (m_autoScrollTimerId != -1) { 0284 // qCDebug(KWEBKITPART_LOG) << "scroll timer id:" << m_autoScrollTimerId; 0285 slotStopAutoScroll(); 0286 e->accept(); 0287 return; 0288 } 0289 } 0290 KWebView::keyPressEvent(e); 0291 } 0292 0293 void WebView::keyReleaseEvent(QKeyEvent *e) 0294 { 0295 if (WebKitSettings::self()->accessKeysEnabled() && m_accessKeyActivated == PreActivated) { 0296 // Activate only when the CTRL key is pressed and released by itself. 0297 if (e->key() == Qt::Key_Control && e->modifiers() == Qt::NoModifier) { 0298 showAccessKeys(); 0299 emit statusBarMessage(i18n("Access keys activated")); 0300 m_accessKeyActivated = Activated; 0301 } else { 0302 m_accessKeyActivated = NotActivated; 0303 } 0304 } 0305 KWebView::keyReleaseEvent(e); 0306 } 0307 0308 void WebView::mouseReleaseEvent(QMouseEvent* e) 0309 { 0310 if (WebKitSettings::self()->accessKeysEnabled() && m_accessKeyActivated == PreActivated 0311 && e->button() != Qt::NoButton && (e->modifiers() & Qt::ControlModifier)) { 0312 m_accessKeyActivated = NotActivated; 0313 } 0314 KWebView::mouseReleaseEvent(e); 0315 } 0316 0317 void WebView::wheelEvent (QWheelEvent* e) 0318 { 0319 if (WebKitSettings::self()->accessKeysEnabled() && 0320 m_accessKeyActivated == PreActivated && 0321 (e->modifiers() & Qt::ControlModifier)) { 0322 m_accessKeyActivated = NotActivated; 0323 } 0324 KWebView::wheelEvent(e); 0325 } 0326 0327 0328 void WebView::timerEvent(QTimerEvent* e) 0329 { 0330 if (e && e->timerId() == m_autoScrollTimerId) { 0331 // do the scrolling 0332 page()->currentFrame()->scroll(m_horizontalAutoScrollSpeed, m_verticalAutoScrollSpeed); 0333 0334 // check if we reached the end 0335 const int y = page()->currentFrame()->scrollPosition().y(); 0336 if (y == page()->currentFrame()->scrollBarMinimum(Qt::Vertical) || 0337 y == page()->currentFrame()->scrollBarMaximum(Qt::Vertical)) { 0338 m_verticalAutoScrollSpeed = 0; 0339 } 0340 0341 const int x = page()->currentFrame()->scrollPosition().x(); 0342 if (x == page()->currentFrame()->scrollBarMinimum(Qt::Horizontal) || 0343 x == page()->currentFrame()->scrollBarMaximum(Qt::Horizontal)) { 0344 m_horizontalAutoScrollSpeed = 0; 0345 } 0346 0347 // Kill the timer once the max/min scroll limit is reached. 0348 if (m_horizontalAutoScrollSpeed == 0 && m_verticalAutoScrollSpeed == 0) { 0349 killTimer(m_autoScrollTimerId); 0350 m_autoScrollTimerId = -1; 0351 } 0352 0353 e->accept(); 0354 return; 0355 } 0356 KWebView::timerEvent(e); 0357 } 0358 0359 static bool showSpellCheckAction(const QWebElement& element) 0360 { 0361 if (element.hasAttribute(QL1S("readonly"))) 0362 return false; 0363 0364 if (element.attribute(QL1S("spellcheck"), QL1S("true")).compare(QL1S("false"), Qt::CaseInsensitive) == 0) 0365 return false; 0366 0367 if (element.hasAttribute(QL1S("type")) 0368 && element.attribute(QL1S("type")).compare(QL1S("text"), Qt::CaseInsensitive) != 0) 0369 return false; 0370 0371 return true; 0372 } 0373 0374 void WebView::editableContentActionPopupMenu(KParts::BrowserExtension::ActionGroupMap& partGroupMap) 0375 { 0376 QList<QAction*> editableContentActions; 0377 0378 KActionMenu* menu = new KActionMenu(i18nc("Text direction", "Direction"), this); 0379 QActionGroup* group = new QActionGroup(this); 0380 group->setExclusive(true); 0381 0382 QAction *action = m_actionCollection->addAction(QL1S("text-direction-default"), m_part->browserExtension(), SLOT(slotTextDirectionChanged())); 0383 action->setText(i18n("Default")); 0384 action->setCheckable(true); 0385 action->setData(QWebPage::SetTextDirectionDefault); 0386 action->setEnabled(pageAction(QWebPage::SetTextDirectionDefault)->isEnabled()); 0387 action->setCheckable(pageAction(QWebPage::SetTextDirectionDefault)->isChecked()); 0388 action->setActionGroup(group); 0389 menu->addAction(action); 0390 0391 action = m_actionCollection->addAction(QL1S("text-direction-left-to-right"), m_part->browserExtension(), SLOT(slotTextDirectionChanged())); 0392 action->setText(i18n("Left to right")); 0393 action->setCheckable(true); 0394 action->setData(QWebPage::SetTextDirectionLeftToRight); 0395 action->setEnabled(pageAction(QWebPage::SetTextDirectionLeftToRight)->isEnabled()); 0396 action->setChecked(pageAction(QWebPage::SetTextDirectionLeftToRight)->isChecked()); 0397 action->setActionGroup(group); 0398 menu->addAction(action); 0399 0400 action = m_actionCollection->addAction(QL1S("text-direction-right-to-left"), m_part->browserExtension(), SLOT(slotTextDirectionChanged())); 0401 action->setText(i18n("Right to left")); 0402 action->setCheckable(true); 0403 action->setData(QWebPage::SetTextDirectionRightToLeft); 0404 action->setEnabled(pageAction(QWebPage::SetTextDirectionRightToLeft)->isEnabled()); 0405 action->setChecked(pageAction(QWebPage::SetTextDirectionRightToLeft)->isChecked()); 0406 action->setActionGroup(group); 0407 menu->addAction(action); 0408 0409 editableContentActions.append(menu); 0410 0411 action = new QAction(m_actionCollection); 0412 action->setSeparator(true); 0413 editableContentActions.append(action); 0414 0415 action = m_actionCollection->addAction(KStandardAction::Copy, QL1S("copy"), m_part->browserExtension(), SLOT(copy())); 0416 action->setEnabled(pageAction(QWebPage::Copy)->isEnabled()); 0417 editableContentActions.append(action); 0418 0419 action = m_actionCollection->addAction(KStandardAction::Cut, QL1S("cut"), m_part->browserExtension(), SLOT(cut())); 0420 action->setEnabled(pageAction(QWebPage::Cut)->isEnabled()); 0421 editableContentActions.append(action); 0422 0423 action = m_actionCollection->addAction(KStandardAction::Paste, QL1S("paste"), m_part->browserExtension(), SLOT(paste())); 0424 action->setEnabled(pageAction(QWebPage::Paste)->isEnabled()); 0425 editableContentActions.append(action); 0426 0427 action = new QAction(m_actionCollection); 0428 action->setSeparator(true); 0429 editableContentActions.append(action); 0430 0431 const bool hasContent = (!m_result.element().evaluateJavaScript(QL1S("this.value")).toString().isEmpty()); 0432 action = m_actionCollection->addAction(KStandardAction::SelectAll, QL1S("selectall"), m_part->browserExtension(), SLOT(slotSelectAll())); 0433 action->setEnabled((pageAction(QWebPage::SelectAll)->isEnabled() && hasContent)); 0434 editableContentActions.append(action); 0435 0436 if (showSpellCheckAction(m_result.element())) { 0437 action = new QAction(m_actionCollection); 0438 action->setSeparator(true); 0439 editableContentActions.append(action); 0440 action = m_actionCollection->addAction(KStandardAction::Spelling, QL1S("spelling"), m_part->browserExtension(), SLOT(slotCheckSpelling())); 0441 action->setText(i18n("Check Spelling...")); 0442 action->setEnabled(hasContent); 0443 editableContentActions.append(action); 0444 0445 const bool hasSelection = (hasContent && m_result.isContentSelected()); 0446 action = m_actionCollection->addAction(KStandardAction::Spelling, QL1S("spellcheckSelection"), m_part->browserExtension(), SLOT(slotSpellCheckSelection())); 0447 action->setText(i18n("Spellcheck selection...")); 0448 action->setEnabled(hasSelection); 0449 editableContentActions.append(action); 0450 } 0451 0452 if (settings()->testAttribute(QWebSettings::DeveloperExtrasEnabled)) { 0453 if (!m_webInspector) { 0454 m_webInspector = new QWebInspector; 0455 m_webInspector->setPage(page()); 0456 connect(page(), SIGNAL(destroyed()), m_webInspector, SLOT(deleteLater())); 0457 } 0458 action = new QAction(m_actionCollection); 0459 action->setSeparator(true); 0460 editableContentActions.append(action); 0461 editableContentActions.append(pageAction(QWebPage::InspectElement)); 0462 } 0463 0464 partGroupMap.insert("editactions" , editableContentActions); 0465 } 0466 0467 0468 void WebView::partActionPopupMenu(KParts::BrowserExtension::ActionGroupMap& partGroupMap) 0469 { 0470 QList<QAction*> partActions; 0471 0472 if (m_result.imageUrl().isValid()) { 0473 QAction *action; 0474 action = new QAction(i18n("Save Image As..."), this); 0475 m_actionCollection->addAction(QL1S("saveimageas"), action); 0476 connect(action, SIGNAL(triggered(bool)), m_part->browserExtension(), SLOT(slotSaveImageAs())); 0477 partActions.append(action); 0478 0479 action = new QAction(i18n("Send Image..."), this); 0480 m_actionCollection->addAction(QL1S("sendimage"), action); 0481 connect(action, SIGNAL(triggered(bool)), m_part->browserExtension(), SLOT(slotSendImage())); 0482 partActions.append(action); 0483 0484 action = new QAction(i18n("Copy Image URL"), this); 0485 m_actionCollection->addAction(QL1S("copyimageurl"), action); 0486 connect(action, SIGNAL(triggered(bool)), m_part->browserExtension(), SLOT(slotCopyImageURL())); 0487 partActions.append(action); 0488 0489 action = new QAction(i18n("Copy Image"), this); 0490 m_actionCollection->addAction(QL1S("copyimage"), action); 0491 connect(action, SIGNAL(triggered(bool)), m_part->browserExtension(), SLOT(slotCopyImage())); 0492 action->setEnabled(!m_result.pixmap().isNull()); 0493 partActions.append(action); 0494 0495 action = new QAction(i18n("View Image (%1)", QUrl(m_result.imageUrl()).fileName()), this); 0496 m_actionCollection->addAction(QL1S("viewimage"), action); 0497 connect(action, SIGNAL(triggered(bool)), m_part->browserExtension(), SLOT(slotViewImage())); 0498 partActions.append(action); 0499 0500 if (WebKitSettings::self()->isAdFilterEnabled()) { 0501 action = new QAction(i18n("Block Image..."), this); 0502 m_actionCollection->addAction(QL1S("blockimage"), action); 0503 connect(action, SIGNAL(triggered(bool)), m_part->browserExtension(), SLOT(slotBlockImage())); 0504 partActions.append(action); 0505 0506 if (!m_result.imageUrl().host().isEmpty() && 0507 !m_result.imageUrl().scheme().isEmpty()) 0508 { 0509 action = new QAction(i18n("Block Images From %1" , m_result.imageUrl().host()), this); 0510 m_actionCollection->addAction(QL1S("blockhost"), action); 0511 connect(action, SIGNAL(triggered(bool)), m_part->browserExtension(), SLOT(slotBlockHost())); 0512 partActions.append(action); 0513 } 0514 } 0515 } else if (m_result.frame() && m_result.frame()->parentFrame() && !m_result.isContentSelected() && m_result.linkUrl().isEmpty()) { 0516 KActionMenu * menu = new KActionMenu(i18nc("@title:menu HTML frame/iframe", "Frame"), this); 0517 0518 QAction* action = new QAction(QIcon::fromTheme(QStringLiteral("window-new")), i18n("Open in New &Window"), this); 0519 m_actionCollection->addAction(QL1S("frameinwindow"), action); 0520 connect(action, SIGNAL(triggered(bool)), m_part->browserExtension(), SLOT(slotFrameInWindow())); 0521 menu->addAction(action); 0522 0523 action = new QAction(i18n("Open in &This Window"), this); 0524 m_actionCollection->addAction(QL1S("frameintop"), action); 0525 connect(action, SIGNAL(triggered(bool)), m_part->browserExtension(), SLOT(slotFrameInTop())); 0526 menu->addAction(action); 0527 0528 action = new QAction(QIcon::fromTheme(QStringLiteral("tab-new")), i18n("Open in &New Tab"), this); 0529 m_actionCollection->addAction(QL1S("frameintab"), action); 0530 connect(action, SIGNAL(triggered(bool)), m_part->browserExtension(), SLOT(slotFrameInTab())); 0531 menu->addAction(action); 0532 0533 action = new QAction(m_actionCollection); 0534 action->setSeparator(true); 0535 menu->addAction(action); 0536 0537 action = new QAction(i18n("Reload Frame"), this); 0538 m_actionCollection->addAction(QL1S("reloadframe"), action); 0539 connect(action, SIGNAL(triggered(bool)), m_part->browserExtension(), SLOT(slotReloadFrame())); 0540 menu->addAction(action); 0541 0542 action = new QAction(QIcon::fromTheme(QStringLiteral("document-print-frame")), i18n("Print Frame..."), this); 0543 m_actionCollection->addAction(QL1S("printFrame"), action); 0544 connect(action, SIGNAL(triggered(bool)), m_part->browserExtension(), SLOT(print())); 0545 menu->addAction(action); 0546 0547 action = new QAction(i18n("Save &Frame As..."), this); 0548 m_actionCollection->addAction(QL1S("saveFrame"), action); 0549 connect(action, SIGNAL(triggered(bool)), m_part->browserExtension(), SLOT(slotSaveFrame())); 0550 menu->addAction(action); 0551 0552 action = new QAction(i18n("View Frame Source"), this); 0553 m_actionCollection->addAction(QL1S("viewFrameSource"), action); 0554 connect(action, SIGNAL(triggered(bool)), m_part->browserExtension(), SLOT(slotViewFrameSource())); 0555 menu->addAction(action); 0556 ///TODO Slot not implemented yet 0557 // action = new QAction(i18n("View Frame Information"), this); 0558 // m_actionCollection->addAction(QL1S("viewFrameInfo"), action); 0559 // connect(action, SIGNAL(triggered(bool)), m_part->browserExtension(), SLOT(slotViewPageInfo())); 0560 0561 action = new QAction(m_actionCollection); 0562 action->setSeparator(true); 0563 menu->addAction(action); 0564 0565 if (WebKitSettings::self()->isAdFilterEnabled()) { 0566 action = new QAction(i18n("Block IFrame..."), this); 0567 m_actionCollection->addAction(QL1S("blockiframe"), action); 0568 connect(action, SIGNAL(triggered(bool)), m_part->browserExtension(), SLOT(slotBlockIFrame())); 0569 menu->addAction(action); 0570 } 0571 0572 partActions.append(menu); 0573 } 0574 0575 const bool showDocSourceAction = (!m_result.linkUrl().isValid() && 0576 !m_result.imageUrl().isValid() && 0577 !m_result.isContentSelected()); 0578 const bool showInspectorAction = settings()->testAttribute(QWebSettings::DeveloperExtrasEnabled); 0579 0580 if (showDocSourceAction || showInspectorAction) { 0581 QAction *separatorAction = new QAction(m_actionCollection); 0582 separatorAction->setSeparator(true); 0583 partActions.append(separatorAction); 0584 } 0585 0586 if (showDocSourceAction) 0587 partActions.append(m_part->actionCollection()->action("viewDocumentSource")); 0588 0589 if (showInspectorAction) { 0590 if (!m_webInspector) { 0591 m_webInspector = new QWebInspector; 0592 m_webInspector->setPage(page()); 0593 connect(page(), SIGNAL(destroyed()), m_webInspector, SLOT(deleteLater())); 0594 } 0595 partActions.append(pageAction(QWebPage::InspectElement)); 0596 } else { 0597 if (m_webInspector) { 0598 delete m_webInspector; 0599 m_webInspector = nullptr; 0600 } 0601 } 0602 0603 partGroupMap.insert("partactions", partActions); 0604 } 0605 0606 void WebView::selectActionPopupMenu(KParts::BrowserExtension::ActionGroupMap& selectGroupMap) 0607 { 0608 QList<QAction*> selectActions; 0609 0610 QAction* copyAction = m_actionCollection->addAction(KStandardAction::Copy, QL1S("copy"), m_part->browserExtension(), SLOT(copy())); 0611 copyAction->setText(i18n("&Copy Text")); 0612 copyAction->setEnabled(m_part->browserExtension()->isActionEnabled("copy")); 0613 selectActions.append(copyAction); 0614 0615 addSearchActions(selectActions, this); 0616 0617 KUriFilterData data (selectedText().simplified().left(256)); 0618 data.setCheckForExecutables(false); 0619 if (KUriFilter::self()->filterUri(data, QStringList() << "kshorturifilter" << "fixhosturifilter") && 0620 data.uri().isValid() && data.uriType() == KUriFilterData::NetProtocol) { 0621 QAction *action = new QAction(QIcon::fromTheme(QStringLiteral("window-new")), i18nc("open selected url", "Open '%1'", 0622 KStringHandler::rsqueeze(data.uri().url(), 18)), this); 0623 m_actionCollection->addAction(QL1S("openSelection"), action); 0624 action->setData(QUrl(data.uri())); 0625 connect(action, SIGNAL(triggered(bool)), m_part->browserExtension(), SLOT(slotOpenSelection())); 0626 selectActions.append(action); 0627 } 0628 0629 selectGroupMap.insert("editactions", selectActions); 0630 } 0631 0632 void WebView::linkActionPopupMenu(KParts::BrowserExtension::ActionGroupMap& linkGroupMap) 0633 { 0634 Q_ASSERT(!m_result.linkUrl().isEmpty()); 0635 0636 const QUrl url(m_result.linkUrl()); 0637 0638 QList<QAction*> linkActions; 0639 0640 QAction* action; 0641 0642 if (m_result.isContentSelected()) { 0643 action = m_actionCollection->addAction(KStandardAction::Copy, QL1S("copy"), m_part->browserExtension(), SLOT(copy())); 0644 action->setText(i18n("&Copy Text")); 0645 action->setEnabled(m_part->browserExtension()->isActionEnabled("copy")); 0646 linkActions.append(action); 0647 } 0648 0649 if (url.scheme() == "mailto") { 0650 action = new QAction(i18n("&Copy Email Address"), this); 0651 m_actionCollection->addAction(QL1S("copylinklocation"), action); 0652 connect(action, SIGNAL(triggered(bool)), m_part->browserExtension(), SLOT(slotCopyEmailAddress())); 0653 linkActions.append(action); 0654 } else { 0655 if (!m_result.isContentSelected()) { 0656 action = new QAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18n("Copy Link &Text"), this); 0657 m_actionCollection->addAction(QL1S("copylinktext"), action); 0658 connect(action, SIGNAL(triggered(bool)), m_part->browserExtension(), SLOT(slotCopyLinkText())); 0659 linkActions.append(action); 0660 } 0661 0662 action = new QAction(i18n("Copy Link &URL"), this); 0663 m_actionCollection->addAction(QL1S("copylinkurl"), action); 0664 connect(action, SIGNAL(triggered(bool)), m_part->browserExtension(), SLOT(slotCopyLinkURL())); 0665 linkActions.append(action); 0666 0667 action = new QAction(i18n("&Save Link As..."), this); 0668 m_actionCollection->addAction(QL1S("savelinkas"), action); 0669 connect(action, SIGNAL(triggered(bool)), m_part->browserExtension(), SLOT(slotSaveLinkAs())); 0670 linkActions.append(action); 0671 } 0672 0673 linkGroupMap.insert("linkactions", linkActions); 0674 } 0675 0676 void WebView::multimediaActionPopupMenu(KParts::BrowserExtension::ActionGroupMap& mmGroupMap) 0677 { 0678 QList<QAction*> multimediaActions; 0679 0680 QWebElement element (m_result.element()); 0681 const bool isPaused = element.evaluateJavaScript(QL1S("this.paused")).toBool(); 0682 const bool isMuted = element.evaluateJavaScript(QL1S("this.muted")).toBool(); 0683 const bool isLoopOn = element.evaluateJavaScript(QL1S("this.loop")).toBool(); 0684 const bool areControlsOn = element.evaluateJavaScript(QL1S("this.controls")).toBool(); 0685 const bool isVideoElement = (element.tagName().compare(QL1S("video"), Qt::CaseInsensitive) == 0); 0686 const bool isAudioElement = (element.tagName().compare(QL1S("audio"), Qt::CaseInsensitive) == 0); 0687 0688 QAction* action = new QAction((isPaused ? i18n("&Play") : i18n("&Pause")), this); 0689 m_actionCollection->addAction(QL1S("playmultimedia"), action); 0690 connect(action, SIGNAL(triggered()), m_part->browserExtension(), SLOT(slotPlayMedia())); 0691 multimediaActions.append(action); 0692 0693 action = new QAction((isMuted ? i18n("Un&mute") : i18n("&Mute")), this); 0694 m_actionCollection->addAction(QL1S("mutemultimedia"), action); 0695 connect(action, SIGNAL(triggered()), m_part->browserExtension(), SLOT(slotMuteMedia())); 0696 multimediaActions.append(action); 0697 0698 action = new QAction(i18n("&Loop"), this); 0699 action->setCheckable(true); 0700 action->setChecked(isLoopOn); 0701 m_actionCollection->addAction(QL1S("loopmultimedia"), action); 0702 connect(action, SIGNAL(triggered()), m_part->browserExtension(), SLOT(slotLoopMedia())); 0703 multimediaActions.append(action); 0704 0705 action = new QAction(i18n("Show &Controls"), this); 0706 action->setCheckable(true); 0707 action->setChecked(areControlsOn); 0708 m_actionCollection->addAction(QL1S("showmultimediacontrols"), action); 0709 connect(action, SIGNAL(triggered()), m_part->browserExtension(), SLOT(slotShowMediaControls())); 0710 multimediaActions.append(action); 0711 0712 action = new QAction(m_actionCollection); 0713 action->setSeparator(true); 0714 multimediaActions.append(action); 0715 0716 QString saveMediaText, copyMediaText; 0717 if (isVideoElement) { 0718 saveMediaText = i18n("Sa&ve Video As..."); 0719 copyMediaText = i18n("C&opy Video URL"); 0720 } else if (isAudioElement) { 0721 saveMediaText = i18n("Sa&ve Audio As..."); 0722 copyMediaText = i18n("C&opy Audio URL"); 0723 } else { 0724 saveMediaText = i18n("Sa&ve Media As..."); 0725 copyMediaText = i18n("C&opy Media URL"); 0726 } 0727 0728 action = new QAction(saveMediaText, this); 0729 m_actionCollection->addAction(QL1S("savemultimedia"), action); 0730 connect(action, SIGNAL(triggered()), m_part->browserExtension(), SLOT(slotSaveMedia())); 0731 multimediaActions.append(action); 0732 0733 action = new QAction(copyMediaText, this); 0734 m_actionCollection->addAction(QL1S("copymultimediaurl"), action); 0735 connect(action, SIGNAL(triggered()), m_part->browserExtension(), SLOT(slotCopyMedia())); 0736 multimediaActions.append(action); 0737 0738 mmGroupMap.insert("partactions", multimediaActions); 0739 } 0740 0741 void WebView::slotStopAutoScroll() 0742 { 0743 if (m_autoScrollTimerId == -1) { 0744 return; 0745 } 0746 0747 killTimer(m_autoScrollTimerId); 0748 m_autoScrollTimerId = -1; 0749 m_verticalAutoScrollSpeed = 0; 0750 m_horizontalAutoScrollSpeed = 0; 0751 0752 } 0753 0754 void WebView::addSearchActions(QList<QAction*>& selectActions, QWebView* view) 0755 { 0756 // search text 0757 const QString selectedText = view->selectedText().simplified(); 0758 if (selectedText.isEmpty()) 0759 return; 0760 0761 KUriFilterData data; 0762 data.setData(selectedText); 0763 data.setAlternateDefaultSearchProvider(ALTERNATE_DEFAULT_WEB_SHORTCUT); 0764 data.setAlternateSearchProviders(ALTERNATE_WEB_SHORTCUTS); 0765 0766 if (KUriFilter::self()->filterSearchUri(data, KUriFilter::NormalTextFilter)) { 0767 const QString squeezedText = KStringHandler::rsqueeze(selectedText, 20); 0768 QAction *action = new QAction(QIcon::fromTheme(data.iconName()), 0769 i18nc("Search \"search provider\" for \"text\"", "Search %1 for '%2'", 0770 data.searchProvider(), squeezedText), view); 0771 action->setData(QUrl(data.uri())); 0772 connect(action, SIGNAL(triggered(bool)), m_part->browserExtension(), SLOT(searchProvider())); 0773 m_actionCollection->addAction(QL1S("defaultSearchProvider"), action); 0774 selectActions.append(action); 0775 0776 0777 const QStringList preferredSearchProviders = data.preferredSearchProviders(); 0778 if (!preferredSearchProviders.isEmpty()) { 0779 KActionMenu* providerList = new KActionMenu(i18nc("Search for \"text\" with", 0780 "Search for '%1' with", squeezedText), view); 0781 Q_FOREACH(const QString &searchProvider, preferredSearchProviders) { 0782 if (searchProvider == data.searchProvider()) 0783 continue; 0784 0785 QAction *action = new QAction(QIcon::fromTheme(data.iconNameForPreferredSearchProvider(searchProvider)), searchProvider, view); 0786 action->setData(data.queryForPreferredSearchProvider(searchProvider)); 0787 m_actionCollection->addAction(searchProvider, action); 0788 connect(action, SIGNAL(triggered(bool)), m_part->browserExtension(), SLOT(searchProvider())); 0789 0790 providerList->addAction(action); 0791 } 0792 m_actionCollection->addAction(QL1S("searchProviderList"), providerList); 0793 selectActions.append(providerList); 0794 } 0795 } 0796 } 0797 0798 bool WebView::checkForAccessKey(QKeyEvent *event) 0799 { 0800 if (m_accessKeyLabels.isEmpty()) 0801 return false; 0802 0803 QString text = event->text(); 0804 if (text.isEmpty()) 0805 return false; 0806 QChar key = text.at(0).toUpper(); 0807 bool handled = false; 0808 if (m_accessKeyNodes.contains(key)) { 0809 QWebElement element = m_accessKeyNodes[key]; 0810 QPoint p = element.geometry().center(); 0811 QWebFrame *frame = element.webFrame(); 0812 Q_ASSERT(frame); 0813 do { 0814 p -= frame->scrollPosition(); 0815 frame = frame->parentFrame(); 0816 } while (frame && frame != page()->mainFrame()); 0817 QMouseEvent pevent(QEvent::MouseButtonPress, p, Qt::LeftButton, {}, {}); 0818 QCoreApplication::sendEvent(this, &pevent); 0819 QMouseEvent revent(QEvent::MouseButtonRelease, p, Qt::LeftButton, {}, {}); 0820 QCoreApplication::sendEvent(this, &revent); 0821 handled = true; 0822 } 0823 return handled; 0824 } 0825 0826 void WebView::hideAccessKeys() 0827 { 0828 if (!m_accessKeyLabels.isEmpty()) { 0829 for (int i = 0, count = m_accessKeyLabels.count(); i < count; ++i) { 0830 QLabel *label = m_accessKeyLabels[i]; 0831 label->hide(); 0832 label->deleteLater(); 0833 } 0834 m_accessKeyLabels.clear(); 0835 m_accessKeyNodes.clear(); 0836 m_duplicateLinkElements.clear(); 0837 m_accessKeyActivated = NotActivated; 0838 emit statusBarMessage(QString()); 0839 update(); 0840 } 0841 } 0842 0843 static QString linkElementKey(const QWebElement& element) 0844 { 0845 if (element.hasAttribute(QL1S("href"))) { 0846 const QUrl url = element.webFrame()->baseUrl().resolved(QUrl(element.attribute(QL1S("href")))); 0847 QString linkKey (url.toString()); 0848 if (element.hasAttribute(QL1S("target"))) { 0849 linkKey += QL1C('+'); 0850 linkKey += element.attribute(QL1S("target")); 0851 } 0852 return linkKey; 0853 } 0854 return QString(); 0855 } 0856 0857 static void handleDuplicateLinkElements(const QWebElement& element, QHash<QString, QChar>* dupLinkList, QChar* accessKey) 0858 { 0859 if (element.tagName().compare(QL1S("A"), Qt::CaseInsensitive) == 0) { 0860 const QString linkKey (linkElementKey(element)); 0861 // qCDebug(KWEBKITPART_LOG) << "LINK KEY:" << linkKey; 0862 if (dupLinkList->contains(linkKey)) { 0863 // qCDebug(KWEBKITPART_LOG) << "***** Found duplicate link element:" << linkKey << endl; 0864 *accessKey = dupLinkList->value(linkKey); 0865 } else if (!linkKey.isEmpty()) { 0866 dupLinkList->insert(linkKey, *accessKey); 0867 } 0868 if (linkKey.isEmpty()) 0869 *accessKey = QChar(); 0870 } 0871 } 0872 0873 static bool isHiddenElement(const QWebElement& element) 0874 { 0875 // width property set to less than zero 0876 if (element.hasAttribute(QL1S("width")) && element.attribute(QL1S("width")).toInt() < 1) { 0877 return true; 0878 } 0879 0880 // height property set to less than zero 0881 if (element.hasAttribute(QL1S("height")) && element.attribute(QL1S("height")).toInt() < 1) { 0882 return true; 0883 } 0884 0885 // visibility set to 'hidden' in the element itself or its parent elements. 0886 if (element.styleProperty(QL1S("visibility"),QWebElement::ComputedStyle).compare(QL1S("hidden"), Qt::CaseInsensitive) == 0) { 0887 return true; 0888 } 0889 0890 // display set to 'none' in the element itself or its parent elements. 0891 if (element.styleProperty(QL1S("display"),QWebElement::ComputedStyle).compare(QL1S("none"), Qt::CaseInsensitive) == 0) { 0892 return true; 0893 } 0894 0895 return false; 0896 } 0897 0898 void WebView::showAccessKeys() 0899 { 0900 QList<QChar> unusedKeys; 0901 for (char c = 'A'; c <= 'Z'; ++c) 0902 unusedKeys << QLatin1Char(c); 0903 for (char c = '0'; c <= '9'; ++c) 0904 unusedKeys << QLatin1Char(c); 0905 0906 QList<QWebElement> unLabeledElements; 0907 QRect viewport = QRect(page()->mainFrame()->scrollPosition(), page()->viewportSize()); 0908 const QString selectorQuery (QLatin1String("a[href]," 0909 "area," 0910 "button:not([disabled])," 0911 "input:not([disabled]):not([hidden])," 0912 "label[for]," 0913 "legend," 0914 "select:not([disabled])," 0915 "textarea:not([disabled])")); 0916 QList<QWebElement> result = page()->mainFrame()->findAllElements(selectorQuery).toList(); 0917 0918 // Priority first goes to elements with accesskey attributes 0919 Q_FOREACH (const QWebElement& element, result) { 0920 const QRect geometry = element.geometry(); 0921 if (geometry.size().isEmpty() || !viewport.contains(geometry.topLeft())) { 0922 continue; 0923 } 0924 if (isHiddenElement(element)) { 0925 continue; // Do not show access key for hidden elements... 0926 } 0927 const QString accessKeyAttribute (element.attribute(QLatin1String("accesskey")).toUpper()); 0928 if (accessKeyAttribute.isEmpty()) { 0929 unLabeledElements.append(element); 0930 continue; 0931 } 0932 QChar accessKey; 0933 for (int i = 0; i < accessKeyAttribute.count(); i+=2) { 0934 const QChar &possibleAccessKey = accessKeyAttribute[i]; 0935 if (unusedKeys.contains(possibleAccessKey)) { 0936 accessKey = possibleAccessKey; 0937 break; 0938 } 0939 } 0940 if (accessKey.isNull()) { 0941 unLabeledElements.append(element); 0942 continue; 0943 } 0944 0945 handleDuplicateLinkElements(element, &m_duplicateLinkElements, &accessKey); 0946 if (!accessKey.isNull()) { 0947 unusedKeys.removeOne(accessKey); 0948 makeAccessKeyLabel(accessKey, element); 0949 } 0950 } 0951 0952 0953 // Pick an access key first from the letters in the text and then from the 0954 // list of unused access keys 0955 Q_FOREACH (const QWebElement &element, unLabeledElements) { 0956 const QRect geometry = element.geometry(); 0957 if (unusedKeys.isEmpty() 0958 || geometry.size().isEmpty() 0959 || !viewport.contains(geometry.topLeft())) 0960 continue; 0961 QChar accessKey; 0962 QString text = element.toPlainText().toUpper(); 0963 for (int i = 0; i < text.count(); ++i) { 0964 const QChar &c = text.at(i); 0965 if (unusedKeys.contains(c)) { 0966 accessKey = c; 0967 break; 0968 } 0969 } 0970 if (accessKey.isNull()) 0971 accessKey = unusedKeys.takeFirst(); 0972 0973 handleDuplicateLinkElements(element, &m_duplicateLinkElements, &accessKey); 0974 if (!accessKey.isNull()) { 0975 unusedKeys.removeOne(accessKey); 0976 makeAccessKeyLabel(accessKey, element); 0977 } 0978 } 0979 0980 m_accessKeyActivated = (m_accessKeyLabels.isEmpty() ? Activated : NotActivated); 0981 } 0982 0983 void WebView::makeAccessKeyLabel(const QChar &accessKey, const QWebElement &element) 0984 { 0985 QLabel *label = new QLabel(this); 0986 QFont font (label->font()); 0987 font.setBold(true); 0988 label->setFont(font); 0989 label->setText(accessKey); 0990 label->setPalette(QToolTip::palette()); 0991 label->setAutoFillBackground(true); 0992 label->setFrameStyle(QFrame::Box | QFrame::Plain); 0993 QPoint point = element.geometry().center(); 0994 point -= page()->mainFrame()->scrollPosition(); 0995 label->move(point); 0996 label->show(); 0997 point.setX(point.x() - label->width() / 2); 0998 label->move(point); 0999 m_accessKeyLabels.append(label); 1000 m_accessKeyNodes.insertMulti(accessKey, element); 1001 }