From a66db09c6efe0d4dcc6d6a3cc93a000207da2175 Mon Sep 17 00:00:00 2001 From: Valentin Fokin Date: Tue, 21 Nov 2017 13:39:26 +0100 Subject: Make default context menus look more like chrome's one MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Implement EditFlags in ContextMenuData - Unify Quick and Widget default context menus - Add workaround for QTBUG-65044 - Update the SimpleBrowser example and its documentation [ChangeLog][QtWebEngine][QtWebEngineWidgets] Unify Quick and Widget default context menus Task-number: QTBUG-62414 Change-Id: I16a380f9f17e160497dfb8ac9c172341eb28c6c8 Reviewed-by: Jüri Valdmann Reviewed-by: Allan Sandfeld Jensen --- .../simplebrowser/doc/src/simplebrowser.qdoc | 16 +- .../webenginewidgets/simplebrowser/webview.cpp | 26 +- src/core/core_chromium.pri | 2 + src/core/render_view_context_menu_qt.cpp | 224 ++++++++++++++ src/core/render_view_context_menu_qt.h | 120 ++++++++ src/core/web_contents_adapter_client.h | 24 ++ src/core/web_contents_view_qt.cpp | 12 + src/webengine/api/qquickwebengineview.cpp | 325 +++++++++++++-------- src/webengine/api/qquickwebengineview_p.h | 2 + src/webengine/api/qquickwebengineview_p_p.h | 19 ++ src/webenginewidgets/api/qwebenginepage.cpp | 298 ++++++++++++------- src/webenginewidgets/api/qwebenginepage.h | 2 + src/webenginewidgets/api/qwebenginepage_p.h | 17 ++ 13 files changed, 844 insertions(+), 243 deletions(-) create mode 100644 src/core/render_view_context_menu_qt.cpp create mode 100644 src/core/render_view_context_menu_qt.h diff --git a/examples/webenginewidgets/simplebrowser/doc/src/simplebrowser.qdoc b/examples/webenginewidgets/simplebrowser/doc/src/simplebrowser.qdoc index 4f44d9f6c..4b39463b6 100644 --- a/examples/webenginewidgets/simplebrowser/doc/src/simplebrowser.qdoc +++ b/examples/webenginewidgets/simplebrowser/doc/src/simplebrowser.qdoc @@ -184,16 +184,18 @@ \section2 Adding Context Menu Items - We add menu items to the context menu, so that users can right-click a link - to have it opened in the same tab, a new window, or a new tab. We override - QWebEngineView::contextMenuEvent and use + We add a menu item to the context menu, so that users can right-click to have an inspector + opened in a new window. We override QWebEngineView::contextMenuEvent and use QWebEnginePage::createStandardContextMenu to create a default QMenu with a default list of QWebEnginePage::WebAction actions. - The default name for QWebEnginePage::OpenLinkInThisWindow action is - \uicontrol Follow. For clarity, we rename it - \uicontrol {Open Link in This Tab}. Also, we add the actions for opening - links in a separate window or in a new tab: + The default name for QWebEnginePage::InspectElement action is + \uicontrol Inspect. For clarity, we rename it to \uicontrol {Open Inspector In New Window} when + there is no Inspector present yet, + and \uicontrol {Inspect Element} when it's already created. + + We also check if the QWebEnginePage::ViewSource action is in the menu, because if it's not + we have to add a separator as well. \quotefromfile webenginewidgets/simplebrowser/webview.cpp \skipto WebView::contextMenuEvent( diff --git a/examples/webenginewidgets/simplebrowser/webview.cpp b/examples/webenginewidgets/simplebrowser/webview.cpp index ab42c4a0a..fcbb543f2 100644 --- a/examples/webenginewidgets/simplebrowser/webview.cpp +++ b/examples/webenginewidgets/simplebrowser/webview.cpp @@ -177,21 +177,21 @@ QWebEngineView *WebView::createWindow(QWebEnginePage::WebWindowType type) void WebView::contextMenuEvent(QContextMenuEvent *event) { QMenu *menu = page()->createStandardContextMenu(); - const QList actions = menu->actions(); - auto it = std::find(actions.cbegin(), actions.cend(), page()->action(QWebEnginePage::OpenLinkInThisWindow)); - if (it != actions.cend()) { - (*it)->setText(tr("Open Link in This Tab")); - ++it; - QAction *before(it == actions.cend() ? nullptr : *it); - menu->insertAction(before, page()->action(QWebEnginePage::OpenLinkInNewWindow)); - menu->insertAction(before, page()->action(QWebEnginePage::OpenLinkInNewTab)); - } - it = std::find(actions.cbegin(), actions.cend(), page()->action(QWebEnginePage::InspectElement)); - if (it == actions.cend()) { + const QList actions = menu->actions(); + auto inspectElement = std::find(actions.cbegin(), actions.cend(), page()->action(QWebEnginePage::InspectElement)); + if (inspectElement == actions.cend()) { + auto viewSource = std::find(actions.cbegin(), actions.cend(), page()->action(QWebEnginePage::ViewSource)); + if (viewSource == actions.cend()) + menu->addSeparator(); + QAction *action = new QAction(menu); - action->setText("Inspect Element"); + action->setText("Open inspector in new window"); connect(action, &QAction::triggered, [this]() { emit devToolsRequested(page()); }); - menu->addAction(action); + + QAction *before(inspectElement == actions.cend() ? nullptr : *inspectElement); + menu->insertAction(before, action); + } else { + (*inspectElement)->setText(tr("Inspect element")); } menu->popup(event->globalPos()); } diff --git a/src/core/core_chromium.pri b/src/core/core_chromium.pri index 7a7e3ab02..08552800d 100644 --- a/src/core/core_chromium.pri +++ b/src/core/core_chromium.pri @@ -79,6 +79,7 @@ SOURCES = \ qrc_protocol_handler_qt.cpp \ quota_permission_context_qt.cpp \ quota_permission_controller.cpp \ + render_view_context_menu_qt.cpp \ render_view_observer_host_qt.cpp \ render_widget_host_view_qt.cpp \ renderer/content_renderer_client_qt.cpp \ @@ -161,6 +162,7 @@ HEADERS = \ quota_permission_context_qt.h \ quota_permission_controller.h \ quota_permission_controller_p.h \ + render_view_context_menu_qt.h \ render_view_observer_host_qt.h \ render_widget_host_view_qt.h \ render_widget_host_view_qt_delegate.h \ diff --git a/src/core/render_view_context_menu_qt.cpp b/src/core/render_view_context_menu_qt.cpp new file mode 100644 index 000000000..4e182973c --- /dev/null +++ b/src/core/render_view_context_menu_qt.cpp @@ -0,0 +1,224 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtWebEngine module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include "render_view_context_menu_qt.h" + +namespace QtWebEngineCore { + + const QString RenderViewContextMenuQt::getMenuItemName(RenderViewContextMenuQt::ContextMenuItem menuItem) { + Q_ASSERT(menuItem <= ContextMenuItem::ViewSource); + static const char *names[ContextMenuItem::ViewSource + 1] = { + QT_TRANSLATE_NOOP("RenderViewContextMenuQt", "Back"), + QT_TRANSLATE_NOOP("RenderViewContextMenuQt", "Forward"), + QT_TRANSLATE_NOOP("RenderViewContextMenuQt", "Reload"), + QT_TRANSLATE_NOOP("RenderViewContextMenuQt", "Cut"), + QT_TRANSLATE_NOOP("RenderViewContextMenuQt", "Copy"), + QT_TRANSLATE_NOOP("RenderViewContextMenuQt", "Paste"), + QT_TRANSLATE_NOOP("RenderViewContextMenuQt", "Undo"), + QT_TRANSLATE_NOOP("RenderViewContextMenuQt", "Redo"), + QT_TRANSLATE_NOOP("RenderViewContextMenuQt", "Select all"), + QT_TRANSLATE_NOOP("RenderViewContextMenuQt", "Paste and match style"), + QT_TRANSLATE_NOOP("RenderViewContextMenuQt", "Open link in new window"), + QT_TRANSLATE_NOOP("RenderViewContextMenuQt", "Open link in new tab"), + QT_TRANSLATE_NOOP("RenderViewContextMenuQt", "Copy link address"), + QT_TRANSLATE_NOOP("RenderViewContextMenuQt", "Save link"), + QT_TRANSLATE_NOOP("RenderViewContextMenuQt", "Copy image"), + QT_TRANSLATE_NOOP("RenderViewContextMenuQt", "Copy image address"), + QT_TRANSLATE_NOOP("RenderViewContextMenuQt", "Save image"), + QT_TRANSLATE_NOOP("RenderViewContextMenuQt", "Copy media address"), + QT_TRANSLATE_NOOP("RenderViewContextMenuQt", "Show controls"), + QT_TRANSLATE_NOOP("RenderViewContextMenuQt", "Loop"), + QT_TRANSLATE_NOOP("RenderViewContextMenuQt", "Save media"), + QT_TRANSLATE_NOOP("RenderViewContextMenuQt", "Inspect"), + QT_TRANSLATE_NOOP("RenderViewContextMenuQt", "Exit full screen"), + QT_TRANSLATE_NOOP("RenderViewContextMenuQt", "Save page"), + QT_TRANSLATE_NOOP("RenderViewContextMenuQt", "View page source") + }; + return QCoreApplication::translate("RenderViewContextMenuQt", qUtf8Printable(names[menuItem])); + } + + RenderViewContextMenuQt::RenderViewContextMenuQt(const WebEngineContextMenuData &data) + : m_contextData(data) + { + } + + void RenderViewContextMenuQt::initMenu() + { + if (isFullScreenMode()) { + appendExitFullscreenItem(); + appendSeparatorItem(); + } + + if (m_contextData.isEditable() && !m_contextData.spellCheckerSuggestions().isEmpty()) { + appendSpellingSuggestionItems(); + appendSeparatorItem(); + } + + if (m_contextData.linkText().isEmpty() && !m_contextData.linkUrl().isValid() && !m_contextData.mediaUrl().isValid()) { + if (m_contextData.isEditable()) + appendEditableItems(); + else if (!m_contextData.selectedText().isEmpty()) + appendCopyItem(); + else + appendPageItems(); + } + + if (m_contextData.linkUrl().isValid() || !m_contextData.unfilteredLinkUrl().isEmpty() || !m_contextData.linkUrl().isEmpty()) + appendLinkItems(); + + if (m_contextData.mediaUrl().isValid()) { + switch (m_contextData.mediaType()) { + case WebEngineContextMenuData::MediaTypeImage: + appendSeparatorItem(); + appendImageItems(); + break; + case WebEngineContextMenuData::MediaTypeCanvas: + Q_UNREACHABLE(); // mediaUrl is invalid for canvases + break; + case WebEngineContextMenuData::MediaTypeAudio: + case WebEngineContextMenuData::MediaTypeVideo: + appendSeparatorItem(); + appendMediaItems(); + break; + default: + break; + } + } else if (m_contextData.mediaType() == WebEngineContextMenuData::MediaTypeCanvas) { + appendSeparatorItem(); + appendCanvasItems(); + } + + if (canViewSource() || hasInspector()) { + appendSeparatorItem(); + appendDeveloperItems(); + } + } + + void RenderViewContextMenuQt::appendCanvasItems() + { + addMenuItem(RenderViewContextMenuQt::DownloadImageToDisk); + addMenuItem(RenderViewContextMenuQt::CopyImageToClipboard); + } + + void RenderViewContextMenuQt::appendCopyItem() + { + addMenuItem(RenderViewContextMenuQt::Copy); + } + + void RenderViewContextMenuQt::appendDeveloperItems() + { + if (canViewSource()) + addMenuItem(RenderViewContextMenuQt::ViewSource); + if (hasInspector()) + addMenuItem(RenderViewContextMenuQt::InspectElement); + } + + void RenderViewContextMenuQt::appendEditableItems() + { + addMenuItem(RenderViewContextMenuQt::Undo); + addMenuItem(RenderViewContextMenuQt::Redo); + appendSeparatorItem(); + addMenuItem(RenderViewContextMenuQt::Cut); + addMenuItem(RenderViewContextMenuQt::Copy); + addMenuItem(RenderViewContextMenuQt::Paste); + if (m_contextData.misspelledWord().isEmpty()) { + addMenuItem(RenderViewContextMenuQt::PasteAndMatchStyle); + addMenuItem(RenderViewContextMenuQt::SelectAll); + } + } + + void RenderViewContextMenuQt::appendExitFullscreenItem() + { + addMenuItem(RenderViewContextMenuQt::ExitFullScreen); + } + + void RenderViewContextMenuQt::appendImageItems() + { + addMenuItem(RenderViewContextMenuQt::DownloadImageToDisk); + addMenuItem(RenderViewContextMenuQt::CopyImageToClipboard); + addMenuItem(RenderViewContextMenuQt::CopyImageUrlToClipboard); + } + + void RenderViewContextMenuQt::appendLinkItems() + { + addMenuItem(RenderViewContextMenuQt::OpenLinkInNewTab); + addMenuItem(RenderViewContextMenuQt::OpenLinkInNewWindow); + appendSeparatorItem(); + addMenuItem(RenderViewContextMenuQt::DownloadLinkToDisk); + addMenuItem(RenderViewContextMenuQt::CopyLinkToClipboard); + } + + void RenderViewContextMenuQt::appendMediaItems() + { + addMenuItem(RenderViewContextMenuQt::ToggleMediaLoop); + if (m_contextData.mediaFlags() & QtWebEngineCore::WebEngineContextMenuData::MediaCanToggleControls) + addMenuItem(RenderViewContextMenuQt::ToggleMediaControls); + addMenuItem(RenderViewContextMenuQt::DownloadMediaToDisk); + addMenuItem(RenderViewContextMenuQt::CopyMediaUrlToClipboard); + } + + void RenderViewContextMenuQt::appendPageItems() + { + addMenuItem(RenderViewContextMenuQt::Back); + addMenuItem(RenderViewContextMenuQt::Forward); + addMenuItem(RenderViewContextMenuQt::Reload); + appendSeparatorItem(); + addMenuItem(RenderViewContextMenuQt::SavePage); + } + + void RenderViewContextMenuQt::appendSpellingSuggestionItems() + { + addMenuItem(RenderViewContextMenuQt::SpellingSuggestions); + } + + void RenderViewContextMenuQt::appendSeparatorItem() + { + addMenuItem(RenderViewContextMenuQt::Separator); + } + + bool RenderViewContextMenuQt::canViewSource() + { + return m_contextData.linkText().isEmpty() + && !m_contextData.linkUrl().isValid() + && !m_contextData.mediaUrl().isValid() + && !m_contextData.isEditable() + && m_contextData.selectedText().isEmpty(); + } +} diff --git a/src/core/render_view_context_menu_qt.h b/src/core/render_view_context_menu_qt.h new file mode 100644 index 000000000..1694f66e6 --- /dev/null +++ b/src/core/render_view_context_menu_qt.h @@ -0,0 +1,120 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtWebEngine module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef RENDER_VIEW_CONTEXT_MENU_QT_H +#define RENDER_VIEW_CONTEXT_MENU_QT_H + +#include "web_contents_adapter_client.h" + +namespace QtWebEngineCore { + +class QWEBENGINE_EXPORT RenderViewContextMenuQt +{ +public: + enum ContextMenuItem { + Back = 0, + Forward, + Reload, + + Cut, + Copy, + Paste, + + Undo, + Redo, + SelectAll, + + PasteAndMatchStyle, + + OpenLinkInNewWindow, + OpenLinkInNewTab, + CopyLinkToClipboard, + DownloadLinkToDisk, + + CopyImageToClipboard, + CopyImageUrlToClipboard, + DownloadImageToDisk, + + CopyMediaUrlToClipboard, + ToggleMediaControls, + ToggleMediaLoop, + DownloadMediaToDisk, + + InspectElement, + ExitFullScreen, + SavePage, + ViewSource, + + SpellingSuggestions, + + Separator + }; + + static const QString getMenuItemName(RenderViewContextMenuQt::ContextMenuItem menuItem); + + RenderViewContextMenuQt(const WebEngineContextMenuData &data); + void initMenu(); + +protected: + virtual bool hasInspector() = 0; + virtual bool isFullScreenMode() = 0; + + virtual void addMenuItem(ContextMenuItem menuItem) = 0; + virtual bool isMenuItemEnabled(ContextMenuItem menuItem) = 0; + + const WebEngineContextMenuData &m_contextData; + +private: + void appendCanvasItems(); + void appendCopyItem(); + void appendEditableItems(); + void appendExitFullscreenItem(); + void appendDeveloperItems(); + void appendImageItems(); + void appendLinkItems(); + void appendMediaItems(); + void appendPageItems(); + void appendSpellingSuggestionItems(); + void appendSeparatorItem(); + bool canViewSource(); +}; + +} + +#endif // RENDER_VIEW_CONTEXT_MENU_QT_H diff --git a/src/core/web_contents_adapter_client.h b/src/core/web_contents_adapter_client.h index d6de9dfe6..dfb2dddcc 100644 --- a/src/core/web_contents_adapter_client.h +++ b/src/core/web_contents_adapter_client.h @@ -95,6 +95,7 @@ public: , isSpellCheckerEnabled(false) , mediaType(0) , mediaFlags(0) + , editFlags(0) { } bool hasImageContent; @@ -102,6 +103,7 @@ public: bool isSpellCheckerEnabled; uint mediaType; uint mediaFlags; + uint editFlags; QPoint pos; QUrl linkUrl; QUrl unfilteredLinkUrl; @@ -155,6 +157,20 @@ public: MediaCanRotate = 0x200, }; + // Must match blink::WebContextMenuData::EditFlags: + enum EditFlags { + CanDoNone = 0x0, + CanUndo = 0x1, + CanRedo = 0x2, + CanCut = 0x4, + CanCopy = 0x8, + CanPaste = 0x10, + CanDelete = 0x20, + CanSelectAll = 0x40, + CanTranslate = 0x80, + CanEditRichly = 0x100, + }; + WebEngineContextMenuData():d(new WebEngineContextMenuSharedData) { } @@ -230,6 +246,14 @@ public: return MediaFlags(d->mediaFlags); } + void setEditFlags(EditFlags flags) { + d->editFlags = flags; + } + + EditFlags editFlags() const { + return EditFlags(d->editFlags); + } + void setSuggestedFileName(const QString &filename) { d->suggestedFileName = filename; } diff --git a/src/core/web_contents_view_qt.cpp b/src/core/web_contents_view_qt.cpp index 28f202e24..0ad3c1c38 100644 --- a/src/core/web_contents_view_qt.cpp +++ b/src/core/web_contents_view_qt.cpp @@ -158,6 +158,17 @@ ASSERT_ENUMS_MATCH(WebEngineContextMenuData::MediaControls, blink::WebContextMen ASSERT_ENUMS_MATCH(WebEngineContextMenuData::MediaCanPrint, blink::WebContextMenuData::kMediaCanPrint) ASSERT_ENUMS_MATCH(WebEngineContextMenuData::MediaCanRotate, blink::WebContextMenuData::kMediaCanRotate) +ASSERT_ENUMS_MATCH(WebEngineContextMenuData::CanDoNone, blink::WebContextMenuData::kCanDoNone) +ASSERT_ENUMS_MATCH(WebEngineContextMenuData::CanUndo, blink::WebContextMenuData::kCanUndo) +ASSERT_ENUMS_MATCH(WebEngineContextMenuData::CanRedo, blink::WebContextMenuData::kCanRedo) +ASSERT_ENUMS_MATCH(WebEngineContextMenuData::CanCut, blink::WebContextMenuData::kCanCut) +ASSERT_ENUMS_MATCH(WebEngineContextMenuData::CanCopy, blink::WebContextMenuData::kCanCopy) +ASSERT_ENUMS_MATCH(WebEngineContextMenuData::CanPaste, blink::WebContextMenuData::kCanPaste) +ASSERT_ENUMS_MATCH(WebEngineContextMenuData::CanDelete, blink::WebContextMenuData::kCanDelete) +ASSERT_ENUMS_MATCH(WebEngineContextMenuData::CanSelectAll, blink::WebContextMenuData::kCanSelectAll) +ASSERT_ENUMS_MATCH(WebEngineContextMenuData::CanTranslate, blink::WebContextMenuData::kCanTranslate) +ASSERT_ENUMS_MATCH(WebEngineContextMenuData::CanEditRichly, blink::WebContextMenuData::kCanEditRichly) + static inline WebEngineContextMenuData fromParams(const content::ContextMenuParams ¶ms) { WebEngineContextMenuData ret; @@ -170,6 +181,7 @@ static inline WebEngineContextMenuData fromParams(const content::ContextMenuPara ret.setMediaType((WebEngineContextMenuData::MediaType)params.media_type); ret.setHasImageContent(params.has_image_contents); ret.setMediaFlags((WebEngineContextMenuData::MediaFlags)params.media_flags); + ret.setEditFlags((WebEngineContextMenuData::EditFlags)params.edit_flags); ret.setSuggestedFileName(toQt(params.suggested_filename.data())); ret.setIsEditable(params.is_editable); #if BUILDFLAG(ENABLE_SPELLCHECK) diff --git a/src/webengine/api/qquickwebengineview.cpp b/src/webengine/api/qquickwebengineview.cpp index 2c30627ad..5bb9fa23a 100644 --- a/src/webengine/api/qquickwebengineview.cpp +++ b/src/webengine/api/qquickwebengineview.cpp @@ -190,8 +190,14 @@ void QQuickWebEngineViewPrivate::contextMenuRequested(const WebEngineContextMenu m_contextMenuData = data; QQuickWebEngineContextMenuRequest *request = new QQuickWebEngineContextMenuRequest(data); + QQmlEngine *engine = qmlEngine(q); + + // TODO: this is a workaround for QTBUG-65044 + if (!engine) + return; + // mark the object for gc by creating temporary jsvalue - qmlEngine(q)->newQObject(request); + engine->newQObject(request); Q_EMIT q->contextMenuRequested(request); if (request->isAccepted()) @@ -203,129 +209,14 @@ void QQuickWebEngineViewPrivate::contextMenuRequested(const WebEngineContextMenu if (!menu) return; - // Populate our menu - MenuItemHandler *item = 0; - if (data.isEditable() && !data.spellCheckerSuggestions().isEmpty()) { - const QPointer qRef(q); - for (int i=0; i < data.spellCheckerSuggestions().count() && i < 4; i++) { - item = new MenuItemHandler(menu); - QString replacement = data.spellCheckerSuggestions().at(i); - QObject::connect(item, &MenuItemHandler::triggered, [qRef, replacement] { qRef->replaceMisspelledWord(replacement); }); - ui()->addMenuItem(item, replacement); - } - ui()->addMenuSeparator(menu); - } - if (data.linkUrl().isValid()) { - item = new MenuItemHandler(menu); - QObject::connect(item, &MenuItemHandler::triggered, [q] { q->triggerWebAction(QQuickWebEngineView::OpenLinkInThisWindow); }); - ui()->addMenuItem(item, QQuickWebEngineView::tr("Follow Link")); - } - - if (data.selectedText().isEmpty()) { - item = new MenuItemHandler(menu); - QObject::connect(item, &MenuItemHandler::triggered, q, &QQuickWebEngineView::goBack); - ui()->addMenuItem(item, QQuickWebEngineView::tr("Back"), QStringLiteral("go-previous"), q->canGoBack()); - - item = new MenuItemHandler(menu); - QObject::connect(item, &MenuItemHandler::triggered, q, &QQuickWebEngineView::goForward); - ui()->addMenuItem(item, QQuickWebEngineView::tr("Forward"), QStringLiteral("go-next"), q->canGoForward()); + QQuickContextMenuBuilder contextMenuBuilder(data, q, menu); - item = new MenuItemHandler(menu); - QObject::connect(item, &MenuItemHandler::triggered, q, &QQuickWebEngineView::reload); - ui()->addMenuItem(item, QQuickWebEngineView::tr("Reload"), QStringLiteral("view-refresh")); - - item = new MenuItemHandler(menu); - QObject::connect(item, &MenuItemHandler::triggered, [q] { q->triggerWebAction(QQuickWebEngineView::ViewSource); }); - ui()->addMenuItem(item, QQuickWebEngineView::tr("View Page Source"), QStringLiteral("view-source"), adapter->canViewSource()); - } else { - item = new MenuItemHandler(menu); - QObject::connect(item, &MenuItemHandler::triggered, [q] { q->triggerWebAction(QQuickWebEngineView::Copy); }); - ui()->addMenuItem(item, QQuickWebEngineView::tr("Copy")); - item = new MenuItemHandler(menu); - QObject::connect(item, &MenuItemHandler::triggered, [q] { q->triggerWebAction(QQuickWebEngineView::Unselect); }); - ui()->addMenuItem(item, QQuickWebEngineView::tr("Unselect")); - } - - if (!data.linkText().isEmpty() && !data.unfilteredLinkUrl().isEmpty()) { - item = new MenuItemHandler(menu); - QObject::connect(item, &MenuItemHandler::triggered, [q] { q->triggerWebAction(QQuickWebEngineView::CopyLinkToClipboard); }); - ui()->addMenuItem(item, QQuickWebEngineView::tr("Copy Link URL")); - } - if (!data.linkText().isEmpty() && data.linkUrl().isValid()) { - item = new MenuItemHandler(menu); - QObject::connect(item, &MenuItemHandler::triggered, [q] { q->triggerWebAction(QQuickWebEngineView::DownloadLinkToDisk); }); - ui()->addMenuItem(item, QQuickWebEngineView::tr("Save Link")); - } - if (data.mediaUrl().isValid()) { - switch (data.mediaType()) { - case WebEngineContextMenuData::MediaTypeImage: - item = new MenuItemHandler(menu); - QObject::connect(item, &MenuItemHandler::triggered, [q] { q->triggerWebAction(QQuickWebEngineView::CopyImageUrlToClipboard); }); - ui()->addMenuItem(item, QQuickWebEngineView::tr("Copy Image URL")); - item = new MenuItemHandler(menu); - QObject::connect(item, &MenuItemHandler::triggered, [q] { q->triggerWebAction(QQuickWebEngineView::CopyImageToClipboard); }); - ui()->addMenuItem(item, QQuickWebEngineView::tr("Copy Image")); - item = new MenuItemHandler(menu); - QObject::connect(item, &MenuItemHandler::triggered, [q] { q->triggerWebAction(QQuickWebEngineView::DownloadImageToDisk); }); - ui()->addMenuItem(item, QQuickWebEngineView::tr("Save Image")); - break; - case WebEngineContextMenuData::MediaTypeCanvas: - Q_UNREACHABLE(); // mediaUrl is invalid for canvases - break; - case WebEngineContextMenuData::MediaTypeAudio: - case WebEngineContextMenuData::MediaTypeVideo: - item = new MenuItemHandler(menu); - QObject::connect(item, &MenuItemHandler::triggered, [q] { q->triggerWebAction(QQuickWebEngineView::CopyMediaUrlToClipboard); }); - ui()->addMenuItem(item, QQuickWebEngineView::tr("Copy Media URL")); - item = new MenuItemHandler(menu); - QObject::connect(item, &MenuItemHandler::triggered, [q] { q->triggerWebAction(QQuickWebEngineView::DownloadMediaToDisk); }); - ui()->addMenuItem(item, QQuickWebEngineView::tr("Save Media")); - item = new MenuItemHandler(menu); - QObject::connect(item, &MenuItemHandler::triggered, [q] { q->triggerWebAction(QQuickWebEngineView::ToggleMediaPlayPause); }); - ui()->addMenuItem(item, QQuickWebEngineView::tr("Toggle Play/Pause")); - item = new MenuItemHandler(menu); - QObject::connect(item, &MenuItemHandler::triggered, [q] { q->triggerWebAction(QQuickWebEngineView::ToggleMediaLoop); }); - ui()->addMenuItem(item, QQuickWebEngineView::tr("Toggle Looping")); - if (data.mediaFlags() & WebEngineContextMenuData::MediaHasAudio) { - item = new MenuItemHandler(menu); - QObject::connect(item, &MenuItemHandler::triggered, [q] { q->triggerWebAction(QQuickWebEngineView::ToggleMediaMute); }); - ui()->addMenuItem(item, QQuickWebEngineView::tr("Toggle Mute")); - } - if (data.mediaFlags() & WebEngineContextMenuData::MediaCanToggleControls) { - item = new MenuItemHandler(menu); - QObject::connect(item, &MenuItemHandler::triggered, [q] { q->triggerWebAction(QQuickWebEngineView::ToggleMediaControls); }); - ui()->addMenuItem(item, QQuickWebEngineView::tr("Toggle Media Controls")); - } - break; - default: - break; - } - } else if (data.mediaType() == WebEngineContextMenuData::MediaTypeCanvas) { - item = new MenuItemHandler(menu); - QObject::connect(item, &MenuItemHandler::triggered, [q] { q->triggerWebAction(QQuickWebEngineView::CopyImageToClipboard); }); - ui()->addMenuItem(item, QQuickWebEngineView::tr("Copy Image")); - } - if (adapter->hasInspector()) { - item = new MenuItemHandler(menu); - QObject::connect(item, &MenuItemHandler::triggered, [q] { q->triggerWebAction(QQuickWebEngineView::InspectElement); }); - ui()->addMenuItem(item, QQuickWebEngineView::tr("Inspect Element")); - } - if (isFullScreenMode()) { - item = new MenuItemHandler(menu); - QObject::connect(item, &MenuItemHandler::triggered, [q] { q->triggerWebAction(QQuickWebEngineView::ExitFullScreen); }); - ui()->addMenuItem(item, QQuickWebEngineView::tr("Exit Full Screen Mode")); - } + // Populate our menu + contextMenuBuilder.initMenu(); // FIXME: expose the context menu data as an attached property to make this more useful - if (contextMenuExtraItems) { - ui()->addMenuSeparator(menu); - if (QObject* menuExtras = contextMenuExtraItems->create(qmlContext(q))) { - menuExtras->setParent(menu); - QQmlListReference entries(menu, defaultPropertyName(menu), qmlEngine(q)); - if (entries.isValid()) - entries.append(menuExtras); - } - } + if (contextMenuExtraItems) + contextMenuBuilder.appendExtraItems(engine); // Now fire the popup() method on the top level menu ui()->showMenu(menu); @@ -427,6 +318,11 @@ void QQuickWebEngineViewPrivate::iconChanged(const QUrl &url) if (!faviconProvider) { QQmlEngine *engine = qmlEngine(q); + + // TODO: this is a workaround for QTBUG-65044 + if (!engine) + return; + Q_ASSERT(engine); faviconProvider = static_cast( engine->imageProvider(QQuickWebEngineFaviconProvider::identifier())); @@ -1926,5 +1822,192 @@ qint64 QQuickWebEngineQuotaPermissionRequest::requestedSize() const return d_ptr->requestedSize(); } +QQuickContextMenuBuilder::QQuickContextMenuBuilder(const QtWebEngineCore::WebEngineContextMenuData &data, + QQuickWebEngineView *view, + QObject *menu) + : QtWebEngineCore::RenderViewContextMenuQt(data) + , m_view(view) + , m_menu(menu) +{ +} + +void QQuickContextMenuBuilder::appendExtraItems(QQmlEngine *engine) +{ + m_view->d_ptr->ui()->addMenuSeparator(m_menu); + if (QObject *menuExtras = m_view->d_ptr->contextMenuExtraItems->create(qmlContext(m_view))) { + menuExtras->setParent(m_menu); + QQmlListReference entries(m_menu, defaultPropertyName(m_menu), engine); + if (entries.isValid()) + entries.append(menuExtras); + } +} + +bool QQuickContextMenuBuilder::hasInspector() +{ + return m_view->d_ptr->adapter->hasInspector(); +} + +bool QQuickContextMenuBuilder::isFullScreenMode() +{ + return m_view->d_ptr->isFullScreenMode(); +} + +void QQuickContextMenuBuilder::addMenuItem(ContextMenuItem menuItem) +{ + MenuItemHandler *item = new MenuItemHandler(m_menu); + QString menuItemIcon; + QPointer thisRef(m_view); + + switch (menuItem) { + case ContextMenuItem::Back: + QObject::connect(item, &MenuItemHandler::triggered, thisRef, &QQuickWebEngineView::goBack); + menuItemIcon = QStringLiteral("go-previous"); + break; + case ContextMenuItem::Forward: + QObject::connect(item, &MenuItemHandler::triggered, thisRef, &QQuickWebEngineView::goForward); + menuItemIcon = QStringLiteral("go-next"); + break; + case ContextMenuItem::Reload: + QObject::connect(item, &MenuItemHandler::triggered, thisRef, &QQuickWebEngineView::reload); + menuItemIcon = QStringLiteral("view-refresh"); + break; + case ContextMenuItem::Cut: + QObject::connect(item, &MenuItemHandler::triggered, [thisRef] { thisRef->triggerWebAction(QQuickWebEngineView::Cut); }); + menuItemIcon = QStringLiteral("Cut"); + break; + case ContextMenuItem::Copy: + QObject::connect(item, &MenuItemHandler::triggered, [thisRef] { thisRef->triggerWebAction(QQuickWebEngineView::Copy); }); + menuItemIcon = QStringLiteral("Copy"); + break; + + case ContextMenuItem::Paste: + QObject::connect(item, &MenuItemHandler::triggered, [thisRef] { thisRef->triggerWebAction(QQuickWebEngineView::Paste); }); + menuItemIcon = QStringLiteral("Paste"); + break; + case ContextMenuItem::Undo: + QObject::connect(item, &MenuItemHandler::triggered, [thisRef] { thisRef->triggerWebAction(QQuickWebEngineView::Undo); }); + menuItemIcon = QStringLiteral("Undo"); + break; + case ContextMenuItem::Redo: + QObject::connect(item, &MenuItemHandler::triggered, [thisRef] { thisRef->triggerWebAction(QQuickWebEngineView::Redo); }); + menuItemIcon = QStringLiteral("Redo"); + break; + case ContextMenuItem::SelectAll: + QObject::connect(item, &MenuItemHandler::triggered, [thisRef] { thisRef->triggerWebAction(QQuickWebEngineView::SelectAll); }); + menuItemIcon = QStringLiteral("Select All"); + break; + case ContextMenuItem::PasteAndMatchStyle: + QObject::connect(item, &MenuItemHandler::triggered, [thisRef] { thisRef->triggerWebAction(QQuickWebEngineView::PasteAndMatchStyle); }); + menuItemIcon = QStringLiteral("Paste And Match Style"); + break; + case ContextMenuItem::OpenLinkInNewWindow: + QObject::connect(item, &MenuItemHandler::triggered, [thisRef] { thisRef->triggerWebAction(QQuickWebEngineView::OpenLinkInNewWindow); }); + break; + case ContextMenuItem::OpenLinkInNewTab: + QObject::connect(item, &MenuItemHandler::triggered, [thisRef] { thisRef->triggerWebAction(QQuickWebEngineView::OpenLinkInNewTab); }); + break; + case ContextMenuItem::CopyLinkToClipboard: + QObject::connect(item, &MenuItemHandler::triggered, [thisRef] { thisRef->triggerWebAction(QQuickWebEngineView::CopyLinkToClipboard); }); + break; + case ContextMenuItem::DownloadLinkToDisk: + QObject::connect(item, &MenuItemHandler::triggered, [thisRef] { thisRef->triggerWebAction(QQuickWebEngineView::DownloadLinkToDisk); }); + break; + case ContextMenuItem::CopyImageToClipboard: + QObject::connect(item, &MenuItemHandler::triggered, [thisRef] { thisRef->triggerWebAction(QQuickWebEngineView::CopyImageToClipboard); }); + break; + case ContextMenuItem::CopyImageUrlToClipboard: + QObject::connect(item, &MenuItemHandler::triggered, [thisRef] { thisRef->triggerWebAction(QQuickWebEngineView::CopyImageUrlToClipboard); }); + break; + case ContextMenuItem::DownloadImageToDisk: + QObject::connect(item, &MenuItemHandler::triggered, [thisRef] { thisRef->triggerWebAction(QQuickWebEngineView::DownloadImageToDisk); }); + break; + case ContextMenuItem::CopyMediaUrlToClipboard: + QObject::connect(item, &MenuItemHandler::triggered, [thisRef] { thisRef->triggerWebAction(QQuickWebEngineView::CopyMediaUrlToClipboard); }); + break; + case ContextMenuItem::ToggleMediaControls: + QObject::connect(item, &MenuItemHandler::triggered, [thisRef] { thisRef->triggerWebAction(QQuickWebEngineView::ToggleMediaControls); }); + break; + case ContextMenuItem::ToggleMediaLoop: + QObject::connect(item, &MenuItemHandler::triggered, [thisRef] { thisRef->triggerWebAction(QQuickWebEngineView::ToggleMediaLoop); }); + break; + case ContextMenuItem::DownloadMediaToDisk: + QObject::connect(item, &MenuItemHandler::triggered, [thisRef] { thisRef->triggerWebAction(QQuickWebEngineView::DownloadMediaToDisk); }); + break; + case ContextMenuItem::InspectElement: + QObject::connect(item, &MenuItemHandler::triggered, [thisRef] { thisRef->triggerWebAction(QQuickWebEngineView::InspectElement); }); + break; + case ContextMenuItem::ExitFullScreen: + QObject::connect(item, &MenuItemHandler::triggered, [thisRef] { thisRef->triggerWebAction(QQuickWebEngineView::ExitFullScreen); }); + break; + case ContextMenuItem::SavePage: + QObject::connect(item, &MenuItemHandler::triggered, [thisRef] { thisRef->triggerWebAction(QQuickWebEngineView::SavePage); }); + break; + case ContextMenuItem::ViewSource: + QObject::connect(item, &MenuItemHandler::triggered, [thisRef] { thisRef->triggerWebAction(QQuickWebEngineView::ViewSource); }); + menuItemIcon = QStringLiteral("view-source"); + break; + case ContextMenuItem::SpellingSuggestions: + for (int i=0; i < m_contextData.spellCheckerSuggestions().count() && i < 4; i++) { + item = new MenuItemHandler(m_menu); + QString replacement = m_contextData.spellCheckerSuggestions().at(i); + QObject::connect(item, &MenuItemHandler::triggered, [thisRef, replacement] { thisRef->replaceMisspelledWord(replacement); }); + m_view->d_ptr->ui()->addMenuItem(item, replacement); + } + return; + case ContextMenuItem::Separator: + thisRef->d_ptr->ui()->addMenuSeparator(m_menu); + return; + } + QString menuItemName = RenderViewContextMenuQt::getMenuItemName(menuItem); + thisRef->d_ptr->ui()->addMenuItem(item, menuItemName, menuItemIcon, isMenuItemEnabled(menuItem)); +} + +bool QQuickContextMenuBuilder::isMenuItemEnabled(ContextMenuItem menuItem) +{ + switch (menuItem) { + case ContextMenuItem::Back: + return m_view->canGoBack(); + case ContextMenuItem::Forward: + return m_view->canGoForward(); + case ContextMenuItem::Reload: + return true; + case ContextMenuItem::Cut: + return m_contextData.editFlags() & QtWebEngineCore::WebEngineContextMenuData::CanCut; + case ContextMenuItem::Copy: + return m_contextData.editFlags() & QtWebEngineCore::WebEngineContextMenuData::CanCopy; + case ContextMenuItem::Paste: + return m_contextData.editFlags() & QtWebEngineCore::WebEngineContextMenuData::CanPaste; + case ContextMenuItem::Undo: + return m_contextData.editFlags() & QtWebEngineCore::WebEngineContextMenuData::CanUndo; + case ContextMenuItem::Redo: + return m_contextData.editFlags() & QtWebEngineCore::WebEngineContextMenuData::CanRedo; + case ContextMenuItem::SelectAll: + return m_contextData.editFlags() & QtWebEngineCore::WebEngineContextMenuData::CanSelectAll; + case ContextMenuItem::PasteAndMatchStyle: + return m_contextData.editFlags() & QtWebEngineCore::WebEngineContextMenuData::CanPaste; + case ContextMenuItem::OpenLinkInNewWindow: + case ContextMenuItem::OpenLinkInNewTab: + case ContextMenuItem::CopyLinkToClipboard: + case ContextMenuItem::DownloadLinkToDisk: + case ContextMenuItem::CopyImageToClipboard: + case ContextMenuItem::CopyImageUrlToClipboard: + case ContextMenuItem::DownloadImageToDisk: + case ContextMenuItem::CopyMediaUrlToClipboard: + case ContextMenuItem::ToggleMediaControls: + case ContextMenuItem::ToggleMediaLoop: + case ContextMenuItem::DownloadMediaToDisk: + case ContextMenuItem::InspectElement: + case ContextMenuItem::ExitFullScreen: + case ContextMenuItem::SavePage: + return true; + case ContextMenuItem::ViewSource: + return m_view->d_ptr->adapter->canViewSource(); + case ContextMenuItem::SpellingSuggestions: + case ContextMenuItem::Separator: + return true; + } + Q_UNREACHABLE(); +} + QT_END_NAMESPACE diff --git a/src/webengine/api/qquickwebengineview_p.h b/src/webengine/api/qquickwebengineview_p.h index 275503d14..8bda609c0 100644 --- a/src/webengine/api/qquickwebengineview_p.h +++ b/src/webengine/api/qquickwebengineview_p.h @@ -65,6 +65,7 @@ namespace QtWebEngineCore { QT_BEGIN_NAMESPACE class QQmlWebChannel; +class QQuickContextMenuBuilder; class QQuickWebEngineAuthenticationDialogRequest; class QQuickWebEngineCertificateError; class QQuickWebEngineColorDialogRequest; @@ -583,6 +584,7 @@ private: Q_DECLARE_PRIVATE(QQuickWebEngineView) QScopedPointer d_ptr; + friend class QQuickContextMenuBuilder; friend class QQuickWebEngineNewViewRequest; friend class QQuickWebEngineFaviconProvider; #ifndef QT_NO_ACCESSIBILITY diff --git a/src/webengine/api/qquickwebengineview_p_p.h b/src/webengine/api/qquickwebengineview_p_p.h index baa89718d..cfe99a0d4 100644 --- a/src/webengine/api/qquickwebengineview_p_p.h +++ b/src/webengine/api/qquickwebengineview_p_p.h @@ -52,6 +52,7 @@ // #include "qquickwebengineview_p.h" +#include "render_view_context_menu_qt.h" #include "web_contents_adapter_client.h" #include @@ -215,6 +216,24 @@ private: QQuickWebEngineView *engineView() const { return static_cast(object()); } }; #endif // QT_NO_ACCESSIBILITY + +class QQuickContextMenuBuilder : public QtWebEngineCore::RenderViewContextMenuQt +{ +public: + QQuickContextMenuBuilder(const QtWebEngineCore::WebEngineContextMenuData &data, QQuickWebEngineView *view, QObject *menu); + void appendExtraItems(QQmlEngine *engine); + +private: + virtual bool hasInspector() override; + virtual bool isFullScreenMode() override; + + virtual void addMenuItem(ContextMenuItem menuItem) override; + virtual bool isMenuItemEnabled(ContextMenuItem menuItem) override; + + QQuickWebEngineView *m_view; + QObject *m_menu; +}; + QT_END_NAMESPACE #endif // QQUICKWEBENGINEVIEW_P_P_H diff --git a/src/webenginewidgets/api/qwebenginepage.cpp b/src/webenginewidgets/api/qwebenginepage.cpp index a8fa98bea..ddd016329 100644 --- a/src/webenginewidgets/api/qwebenginepage.cpp +++ b/src/webenginewidgets/api/qwebenginepage.cpp @@ -1042,11 +1042,11 @@ QAction *QWebEnginePage::action(WebAction action) const switch (action) { case Back: - text = tr("Back"); + text = RenderViewContextMenuQt::getMenuItemName(RenderViewContextMenuQt::ContextMenuItem::Back); icon = style->standardIcon(QStyle::SP_ArrowBack); break; case Forward: - text = tr("Forward"); + text = RenderViewContextMenuQt::getMenuItemName(RenderViewContextMenuQt::ContextMenuItem::Forward); icon = style->standardIcon(QStyle::SP_ArrowForward); break; case Stop: @@ -1054,7 +1054,7 @@ QAction *QWebEnginePage::action(WebAction action) const icon = style->standardIcon(QStyle::SP_BrowserStop); break; case Reload: - text = tr("Reload"); + text = RenderViewContextMenuQt::getMenuItemName(RenderViewContextMenuQt::ContextMenuItem::Reload); icon = style->standardIcon(QStyle::SP_BrowserReload); break; case ReloadAndBypassCache: @@ -1062,61 +1062,61 @@ QAction *QWebEnginePage::action(WebAction action) const icon = style->standardIcon(QStyle::SP_BrowserReload); break; case Cut: - text = tr("Cut"); + text = RenderViewContextMenuQt::getMenuItemName(RenderViewContextMenuQt::ContextMenuItem::Cut); break; case Copy: - text = tr("Copy"); + text = RenderViewContextMenuQt::getMenuItemName(RenderViewContextMenuQt::ContextMenuItem::Copy); break; case Paste: - text = tr("Paste"); + text = RenderViewContextMenuQt::getMenuItemName(RenderViewContextMenuQt::ContextMenuItem::Paste); break; case Undo: - text = tr("Undo"); + text = RenderViewContextMenuQt::getMenuItemName(RenderViewContextMenuQt::ContextMenuItem::Undo); break; case Redo: - text = tr("Redo"); + text = RenderViewContextMenuQt::getMenuItemName(RenderViewContextMenuQt::ContextMenuItem::Redo); break; case SelectAll: - text = tr("Select All"); + text = RenderViewContextMenuQt::getMenuItemName(RenderViewContextMenuQt::ContextMenuItem::SelectAll); break; case PasteAndMatchStyle: - text = tr("Paste and Match Style"); + text = RenderViewContextMenuQt::getMenuItemName(RenderViewContextMenuQt::ContextMenuItem::PasteAndMatchStyle); break; case OpenLinkInThisWindow: - text = tr("Open Link in This Window"); + text = tr("Open link in this window"); break; case OpenLinkInNewWindow: - text = tr("Open Link in New Window"); + text = RenderViewContextMenuQt::getMenuItemName(RenderViewContextMenuQt::ContextMenuItem::OpenLinkInNewWindow); break; case OpenLinkInNewTab: - text = tr("Open Link in New Tab"); + text = RenderViewContextMenuQt::getMenuItemName(RenderViewContextMenuQt::ContextMenuItem::OpenLinkInNewTab); break; case OpenLinkInNewBackgroundTab: - text = tr("Open Link in New Background Tab"); + text = tr("Open link in new background tab"); break; case CopyLinkToClipboard: - text = tr("Copy Link URL"); + text = RenderViewContextMenuQt::getMenuItemName(RenderViewContextMenuQt::ContextMenuItem::CopyLinkToClipboard); break; case DownloadLinkToDisk: - text = tr("Save Link"); + text = RenderViewContextMenuQt::getMenuItemName(RenderViewContextMenuQt::ContextMenuItem::DownloadLinkToDisk); break; case CopyImageToClipboard: - text = tr("Copy Image"); + text = RenderViewContextMenuQt::getMenuItemName(RenderViewContextMenuQt::ContextMenuItem::CopyImageToClipboard); break; case CopyImageUrlToClipboard: - text = tr("Copy Image URL"); + text = RenderViewContextMenuQt::getMenuItemName(RenderViewContextMenuQt::ContextMenuItem::CopyImageUrlToClipboard); break; case DownloadImageToDisk: - text = tr("Save Image"); + text = RenderViewContextMenuQt::getMenuItemName(RenderViewContextMenuQt::ContextMenuItem::DownloadImageToDisk); break; case CopyMediaUrlToClipboard: - text = tr("Copy Media URL"); + text = RenderViewContextMenuQt::getMenuItemName(RenderViewContextMenuQt::ContextMenuItem::CopyMediaUrlToClipboard); break; case ToggleMediaControls: - text = tr("Toggle Media Controls"); + text = RenderViewContextMenuQt::getMenuItemName(RenderViewContextMenuQt::ContextMenuItem::ToggleMediaControls); break; case ToggleMediaLoop: - text = tr("Toggle Looping"); + text = RenderViewContextMenuQt::getMenuItemName(RenderViewContextMenuQt::ContextMenuItem::ToggleMediaLoop); break; case ToggleMediaPlayPause: text = tr("Toggle Play/Pause"); @@ -1125,13 +1125,13 @@ QAction *QWebEnginePage::action(WebAction action) const text = tr("Toggle Mute"); break; case DownloadMediaToDisk: - text = tr("Save Media"); + text = RenderViewContextMenuQt::getMenuItemName(RenderViewContextMenuQt::ContextMenuItem::DownloadMediaToDisk); break; case InspectElement: - text = tr("Inspect Element"); + text = RenderViewContextMenuQt::getMenuItemName(RenderViewContextMenuQt::ContextMenuItem::InspectElement); break; case ExitFullScreen: - text = tr("Exit Full Screen Mode"); + text = RenderViewContextMenuQt::getMenuItemName(RenderViewContextMenuQt::ContextMenuItem::ExitFullScreen); break; case RequestClose: text = tr("Close Page"); @@ -1140,10 +1140,10 @@ QAction *QWebEnginePage::action(WebAction action) const text = tr("Unselect"); break; case SavePage: - text = tr("Save &Page"); + text = RenderViewContextMenuQt::getMenuItemName(RenderViewContextMenuQt::ContextMenuItem::SavePage); break; case ViewSource: - text = tr("&View Page Source"); + text = RenderViewContextMenuQt::getMenuItemName(RenderViewContextMenuQt::ContextMenuItem::ViewSource); break; case ToggleBold: text = tr("&Bold"); @@ -1656,84 +1656,11 @@ QMenu *QWebEnginePage::createStandardContextMenu() return nullptr; QMenu *menu = new QMenu(d->view); - QAction *action = 0; const WebEngineContextMenuData &contextMenuData = *d->contextData.d; - if (contextMenuData.isEditable() && !contextMenuData.spellCheckerSuggestions().isEmpty()) { - QPointer thisRef(this); - for (int i=0; i < contextMenuData.spellCheckerSuggestions().count() && i < 4; i++) { - QAction *action = new QAction(menu); - QString replacement = contextMenuData.spellCheckerSuggestions().at(i); - QObject::connect(action, &QAction::triggered, [thisRef, replacement] { if (thisRef) thisRef->replaceMisspelledWord(replacement); }); - action->setText(replacement); - menu->addAction(action); - } - menu->addSeparator(); - } - - if (contextMenuData.linkUrl().isValid()) { - action = QWebEnginePage::action(OpenLinkInThisWindow); - action->setText(tr("Follow Link")); - menu->addAction(action); - menu->addAction(QWebEnginePage::action(DownloadLinkToDisk)); - } - if (contextMenuData.selectedText().isEmpty()) { - action = new QAction(QIcon::fromTheme(QStringLiteral("go-previous")), tr("&Back"), menu); - connect(action, &QAction::triggered, d->view, &QWebEngineView::back); - action->setEnabled(d->adapter->canGoBack()); - menu->addAction(action); - - action = new QAction(QIcon::fromTheme(QStringLiteral("go-next")), tr("&Forward"), menu); - connect(action, &QAction::triggered, d->view, &QWebEngineView::forward); - action->setEnabled(d->adapter->canGoForward()); - menu->addAction(action); - - action = new QAction(QIcon::fromTheme(QStringLiteral("view-refresh")), tr("&Reload"), menu); - connect(action, &QAction::triggered, d->view, &QWebEngineView::reload); - menu->addAction(action); - - menu->addAction(QWebEnginePage::action(ViewSource)); - } else { - menu->addAction(QWebEnginePage::action(Copy)); - menu->addAction(QWebEnginePage::action(Unselect)); - } - - if (!contextMenuData.linkText().isEmpty() && !contextMenuData.unfilteredLinkUrl().isEmpty()) { - menu->addAction(QWebEnginePage::action(CopyLinkToClipboard)); - } - if (contextMenuData.mediaUrl().isValid()) { - switch (contextMenuData.mediaType()) { - case WebEngineContextMenuData::MediaTypeImage: - menu->addAction(QWebEnginePage::action(DownloadImageToDisk)); - menu->addAction(QWebEnginePage::action(CopyImageUrlToClipboard)); - menu->addAction(QWebEnginePage::action(CopyImageToClipboard)); - break; - case WebEngineContextMenuData::MediaTypeCanvas: - Q_UNREACHABLE(); // mediaUrl is invalid for canvases - break; - case WebEngineContextMenuData::MediaTypeAudio: - case WebEngineContextMenuData::MediaTypeVideo: - menu->addAction(QWebEnginePage::action(DownloadMediaToDisk)); - menu->addAction(QWebEnginePage::action(CopyMediaUrlToClipboard)); - menu->addAction(QWebEnginePage::action(ToggleMediaPlayPause)); - menu->addAction(QWebEnginePage::action(ToggleMediaLoop)); - if (contextMenuData.mediaFlags() & WebEngineContextMenuData::MediaHasAudio) - menu->addAction(QWebEnginePage::action(ToggleMediaMute)); - if (contextMenuData.mediaFlags() & WebEngineContextMenuData::MediaCanToggleControls) - menu->addAction(QWebEnginePage::action(ToggleMediaControls)); - break; - default: - break; - } - } else if (contextMenuData.mediaType() == WebEngineContextMenuData::MediaTypeCanvas) { - menu->addAction(QWebEnginePage::action(CopyImageToClipboard)); - } + QContextMenuBuilder contextMenuBuilder(contextMenuData, this, menu); - if (d->adapter->hasInspector()) - menu->addAction(QWebEnginePage::action(InspectElement)); - - if (d->isFullScreenMode()) - menu->addAction(QWebEnginePage::action(ExitFullScreen)); + contextMenuBuilder.initMenu(); menu->setAttribute(Qt::WA_DeleteOnClose, true); @@ -2309,6 +2236,173 @@ const QWebEngineContextMenuData &QWebEnginePage::contextMenuData() const return d->contextData; } +QContextMenuBuilder::QContextMenuBuilder(const QtWebEngineCore::WebEngineContextMenuData &data, + QWebEnginePage *page, + QMenu *menu) + : QtWebEngineCore::RenderViewContextMenuQt(data) + , m_page(page) + , m_menu(menu) +{ +} + +bool QContextMenuBuilder::hasInspector() +{ + return m_page->d_ptr->adapter->hasInspector(); +} + +bool QContextMenuBuilder::isFullScreenMode() +{ + return m_page->d_ptr->isFullScreenMode(); +} + +void QContextMenuBuilder::addMenuItem(ContextMenuItem menuItem) +{ + QPointer thisRef(m_page); + QAction *action = 0; + + switch (menuItem) { + case ContextMenuItem::Back: + action = new QAction(QIcon::fromTheme(QStringLiteral("go-previous")), QWebEnginePage::tr("&Back"), m_menu); + QObject::connect(action, &QAction::triggered, thisRef->d_ptr->view, &QWebEngineView::back); + break; + case ContextMenuItem::Forward: + action = new QAction(QIcon::fromTheme(QStringLiteral("go-next")), QWebEnginePage::tr("&Forward"), m_menu); + QObject::connect(action, &QAction::triggered, thisRef->d_ptr->view, &QWebEngineView::forward); + break; + case ContextMenuItem::Reload: + action = new QAction(QIcon::fromTheme(QStringLiteral("view-refresh")), QWebEnginePage::tr("&Reload"), m_menu); + QObject::connect(action, &QAction::triggered, thisRef->d_ptr->view, &QWebEngineView::reload); + break; + case ContextMenuItem::Cut: + action = thisRef->action(QWebEnginePage::Cut); + break; + case ContextMenuItem::Copy: + action = thisRef->action(QWebEnginePage::Copy); + break; + case ContextMenuItem::Paste: + action = thisRef->action(QWebEnginePage::Paste); + break; + case ContextMenuItem::Undo: + action = thisRef->action(QWebEnginePage::Undo); + break; + case ContextMenuItem::Redo: + action = thisRef->action(QWebEnginePage::Redo); + break; + case ContextMenuItem::SelectAll: + action = thisRef->action(QWebEnginePage::SelectAll); + break; + case ContextMenuItem::PasteAndMatchStyle: + action = thisRef->action(QWebEnginePage::PasteAndMatchStyle); + break; + case ContextMenuItem::OpenLinkInNewWindow: + action = thisRef->action(QWebEnginePage::OpenLinkInNewWindow); + break; + case ContextMenuItem::OpenLinkInNewTab: + action = thisRef->action(QWebEnginePage::OpenLinkInNewTab); + break; + case ContextMenuItem::CopyLinkToClipboard: + action = thisRef->action(QWebEnginePage::CopyLinkToClipboard); + break; + case ContextMenuItem::DownloadLinkToDisk: + action = thisRef->action(QWebEnginePage::DownloadLinkToDisk); + break; + case ContextMenuItem::CopyImageToClipboard: + action = thisRef->action(QWebEnginePage::CopyImageToClipboard); + break; + case ContextMenuItem::CopyImageUrlToClipboard: + action = thisRef->action(QWebEnginePage::CopyImageUrlToClipboard); + break; + case ContextMenuItem::DownloadImageToDisk: + action = thisRef->action(QWebEnginePage::DownloadImageToDisk); + break; + case ContextMenuItem::CopyMediaUrlToClipboard: + action = thisRef->action(QWebEnginePage::CopyMediaUrlToClipboard); + break; + case ContextMenuItem::ToggleMediaControls: + action = thisRef->action(QWebEnginePage::ToggleMediaControls); + break; + case ContextMenuItem::ToggleMediaLoop: + action = thisRef->action(QWebEnginePage::ToggleMediaLoop); + break; + case ContextMenuItem::DownloadMediaToDisk: + action = thisRef->action(QWebEnginePage::DownloadMediaToDisk); + break; + case ContextMenuItem::InspectElement: + action = thisRef->action(QWebEnginePage::InspectElement); + break; + case ContextMenuItem::ExitFullScreen: + action = thisRef->action(QWebEnginePage::ExitFullScreen); + break; + case ContextMenuItem::SavePage: + action = thisRef->action(QWebEnginePage::SavePage); + break; + case ContextMenuItem::ViewSource: + action = thisRef->action(QWebEnginePage::ViewSource); + break; + case ContextMenuItem::SpellingSuggestions: + for (int i=0; i < m_contextData.spellCheckerSuggestions().count() && i < 4; i++) { + action = new QAction(m_menu); + QString replacement = m_contextData.spellCheckerSuggestions().at(i); + QObject::connect(action, &QAction::triggered, [thisRef, replacement] { if (thisRef) thisRef->replaceMisspelledWord(replacement); }); + action->setText(replacement); + m_menu->addAction(action); + } + return; + case ContextMenuItem::Separator: + m_menu->addSeparator(); + return; + } + action->setEnabled(isMenuItemEnabled(menuItem)); + m_menu->addAction(action); +} + +bool QContextMenuBuilder::isMenuItemEnabled(ContextMenuItem menuItem) +{ + switch (menuItem) { + case ContextMenuItem::Back: + return m_page->d_ptr->adapter->canGoBack(); + case ContextMenuItem::Forward: + return m_page->d_ptr->adapter->canGoForward(); + case ContextMenuItem::Reload: + return true; + case ContextMenuItem::Cut: + return m_contextData.editFlags() & QtWebEngineCore::WebEngineContextMenuData::CanCut; + case ContextMenuItem::Copy: + return m_contextData.editFlags() & QtWebEngineCore::WebEngineContextMenuData::CanCopy; + case ContextMenuItem::Paste: + return m_contextData.editFlags() & QtWebEngineCore::WebEngineContextMenuData::CanPaste; + case ContextMenuItem::Undo: + return m_contextData.editFlags() & QtWebEngineCore::WebEngineContextMenuData::CanUndo; + case ContextMenuItem::Redo: + return m_contextData.editFlags() & QtWebEngineCore::WebEngineContextMenuData::CanRedo; + case ContextMenuItem::SelectAll: + return m_contextData.editFlags() & QtWebEngineCore::WebEngineContextMenuData::CanSelectAll; + case ContextMenuItem::PasteAndMatchStyle: + return m_contextData.editFlags() & QtWebEngineCore::WebEngineContextMenuData::CanPaste; + case ContextMenuItem::OpenLinkInNewWindow: + case ContextMenuItem::OpenLinkInNewTab: + case ContextMenuItem::CopyLinkToClipboard: + case ContextMenuItem::DownloadLinkToDisk: + case ContextMenuItem::CopyImageToClipboard: + case ContextMenuItem::CopyImageUrlToClipboard: + case ContextMenuItem::DownloadImageToDisk: + case ContextMenuItem::CopyMediaUrlToClipboard: + case ContextMenuItem::ToggleMediaControls: + case ContextMenuItem::ToggleMediaLoop: + case ContextMenuItem::DownloadMediaToDisk: + case ContextMenuItem::InspectElement: + case ContextMenuItem::ExitFullScreen: + case ContextMenuItem::SavePage: + return true; + case ContextMenuItem::ViewSource: + return m_page->d_ptr->adapter->canViewSource(); + case ContextMenuItem::SpellingSuggestions: + case ContextMenuItem::Separator: + return true; + } + Q_UNREACHABLE(); +} + QT_END_NAMESPACE #include "moc_qwebenginepage.cpp" diff --git a/src/webenginewidgets/api/qwebenginepage.h b/src/webenginewidgets/api/qwebenginepage.h index 531eef6e9..7ce72258b 100644 --- a/src/webenginewidgets/api/qwebenginepage.h +++ b/src/webenginewidgets/api/qwebenginepage.h @@ -57,6 +57,7 @@ QT_BEGIN_NAMESPACE class QMenu; class QPrinter; +class QContextMenuBuilder; class QWebChannel; class QWebEngineContextMenuData; class QWebEngineFullScreenRequest; @@ -374,6 +375,7 @@ private: Q_PRIVATE_SLOT(d_func(), void _q_webActionTriggered(bool checked)) #endif + friend class QContextMenuBuilder; friend class QWebEngineFullScreenRequest; friend class QWebEngineView; friend class QWebEngineViewPrivate; diff --git a/src/webenginewidgets/api/qwebenginepage_p.h b/src/webenginewidgets/api/qwebenginepage_p.h index 270849aee..301028e39 100644 --- a/src/webenginewidgets/api/qwebenginepage_p.h +++ b/src/webenginewidgets/api/qwebenginepage_p.h @@ -56,6 +56,7 @@ #include "qwebenginecallback_p.h" #include "qwebenginecontextmenudata.h" #include "qwebenginescriptcollection.h" +#include "render_view_context_menu_qt.h" #include "web_contents_adapter_client.h" #include @@ -186,6 +187,22 @@ public: #endif }; +class QContextMenuBuilder : public QtWebEngineCore::RenderViewContextMenuQt +{ +public: + QContextMenuBuilder(const QtWebEngineCore::WebEngineContextMenuData &data, QWebEnginePage *page, QMenu *menu); + +private: + virtual bool hasInspector() override; + virtual bool isFullScreenMode() override; + + virtual void addMenuItem(ContextMenuItem entry) override; + virtual bool isMenuItemEnabled(ContextMenuItem entry) override; + + QWebEnginePage *m_page; + QMenu *m_menu; +}; + QT_END_NAMESPACE #endif // QWEBENGINEPAGE_P_H -- cgit v1.2.3