diff options
Diffstat (limited to 'examples')
210 files changed, 2567 insertions, 6891 deletions
diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index ccad90e11..2d3ccab7e 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,3 +1,6 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + cmake_minimum_required(VERSION 3.16) qt_examples_build_begin(EXTERNAL_BUILD) diff --git a/examples/pdf/CMakeLists.txt b/examples/pdf/CMakeLists.txt index 265d2ab8f..f1d45d477 100644 --- a/examples/pdf/CMakeLists.txt +++ b/examples/pdf/CMakeLists.txt @@ -1,3 +1,6 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + qt_internal_add_example(pdfviewer) qt_internal_add_example(multipage) if(NOT TARGET Qt::Svg) diff --git a/examples/pdf/multipage/CMakeLists.txt b/examples/pdf/multipage/CMakeLists.txt index 92c2bce22..25cc726ed 100644 --- a/examples/pdf/multipage/CMakeLists.txt +++ b/examples/pdf/multipage/CMakeLists.txt @@ -1,3 +1,6 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + cmake_minimum_required(VERSION 3.16) project(multipage LANGUAGES CXX) diff --git a/examples/pdf/multipage/viewer.qml b/examples/pdf/multipage/viewer.qml index fcf326869..d0b0a7263 100644 --- a/examples/pdf/multipage/viewer.qml +++ b/examples/pdf/multipage/viewer.qml @@ -412,7 +412,7 @@ ApplicationWindow { Layout.bottomMargin: 3 onAccepted: { sidebar.open() - sidebarTabs.setCurrentIndex(0) + sidebarTabs.setCurrentIndex(1) } Image { visible: searchField.text !== "" diff --git a/examples/pdf/pdfviewer/CMakeLists.txt b/examples/pdf/pdfviewer/CMakeLists.txt index 179734dc4..b664fb6f2 100644 --- a/examples/pdf/pdfviewer/CMakeLists.txt +++ b/examples/pdf/pdfviewer/CMakeLists.txt @@ -1,3 +1,6 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + cmake_minimum_required(VERSION 3.16) project(pdfviewer LANGUAGES CXX) diff --git a/examples/pdfwidgets/pdfviewer/CMakeLists.txt b/examples/pdfwidgets/pdfviewer/CMakeLists.txt index e1d2fb580..901509f62 100644 --- a/examples/pdfwidgets/pdfviewer/CMakeLists.txt +++ b/examples/pdfwidgets/pdfviewer/CMakeLists.txt @@ -1,3 +1,6 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + cmake_minimum_required(VERSION 3.16) project(pdfviewer LANGUAGES CXX) diff --git a/examples/pdfwidgets/pdfviewer/doc/src/pdfviewer.qdoc b/examples/pdfwidgets/pdfviewer/doc/src/pdfviewer.qdoc index 8a60e06d6..e585a84ef 100644 --- a/examples/pdfwidgets/pdfviewer/doc/src/pdfviewer.qdoc +++ b/examples/pdfwidgets/pdfviewer/doc/src/pdfviewer.qdoc @@ -6,12 +6,10 @@ \meta installpath pdfwidgets \ingroup qtpdf-examples - \title PDF Viewer Example + \title PDF Viewer Widget Example \brief A widget-based PDF viewer that allows scrolling through the pages. - \omit - //! TODO add thumbnail \image pdfviewer.png - \endomit + \image pdfviewer.png \e {PDF Viewer} demonstrates how to use the QPdfView class to render PDF documents and the QPdfPageNavigator class to navigate them. diff --git a/examples/webenginequick/CMakeLists.txt b/examples/webenginequick/CMakeLists.txt index dc42b3dfe..52ba7cb0b 100644 --- a/examples/webenginequick/CMakeLists.txt +++ b/examples/webenginequick/CMakeLists.txt @@ -1,9 +1,5 @@ -qt_internal_add_example(customdialogs) -qt_internal_add_example(customtouchhandle) +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + qt_internal_add_example(lifecycle) -qt_internal_add_example(minimal) qt_internal_add_example(quicknanobrowser) -qt_internal_add_example(webengineaction) -if(TARGET Qt::QuickControls2) - qt_internal_add_example(recipebrowser) -endif() diff --git a/examples/webenginequick/customdialogs/CMakeLists.txt b/examples/webenginequick/customdialogs/CMakeLists.txt deleted file mode 100644 index b1899fd66..000000000 --- a/examples/webenginequick/customdialogs/CMakeLists.txt +++ /dev/null @@ -1,73 +0,0 @@ -cmake_minimum_required(VERSION 3.16) -project(customdialogs LANGUAGES CXX) - -set(CMAKE_AUTOMOC ON) -set(CMAKE_AUTOUIC ON) - -if(NOT DEFINED INSTALL_EXAMPLESDIR) - set(INSTALL_EXAMPLESDIR "examples") -endif() - -set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/webenginequick/customdialogs") - -find_package(Qt6 REQUIRED COMPONENTS Core Gui WebEngineQuick) - -qt_add_executable(customdialogs - main.cpp - server.cpp server.h -) - -set_target_properties(customdialogs PROPERTIES - WIN32_EXECUTABLE TRUE - MACOSX_BUNDLE TRUE -) - -target_link_libraries(customdialogs PUBLIC - Qt::Core - Qt::Gui - Qt::WebEngineQuick -) - -set(customdialogs_resource_files - "MessageRectangle.qml" - "SwitchButton.qml" - "WebView.qml" - "forms/Authentication.qml" - "forms/AuthenticationForm.ui.qml" - "forms/ColorCell.qml" - "forms/ColorPicker.qml" - "forms/ColorPickerForm.ui.qml" - "forms/CustomButton.qml" - "forms/FilePicker.qml" - "forms/FilePickerForm.ui.qml" - "forms/FileRow.qml" - "forms/JavaScript.qml" - "forms/JavaScriptForm.ui.qml" - "forms/Menu.qml" - "forms/MenuForm.ui.qml" - "forms/TouchSelectionMenu.qml" - "forms/TouchSelectionMenuForm.ui.qml" - "icon.svg" - "index.html" - "main.qml" - "style.css" -) - -qt_add_resources(customdialogs "customdialogs" - PREFIX - "/" - FILES - ${customdialogs_resource_files} -) - -if(TARGET Qt::Widgets) - target_link_libraries(customdialogs PUBLIC - Qt::Widgets - ) -endif() - -install(TARGETS customdialogs - RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" - BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" - LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" -) diff --git a/examples/webenginequick/customdialogs/MessageRectangle.qml b/examples/webenginequick/customdialogs/MessageRectangle.qml deleted file mode 100644 index 09a202cf3..000000000 --- a/examples/webenginequick/customdialogs/MessageRectangle.qml +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -import QtQuick - -Rectangle { - property alias text: messageText.text - width: parent.width - height: 30 - visible: false - color: "#80c342" - Text { - id: messageText - anchors.horizontalCenter: parent.horizontalCenter - anchors.verticalCenter: parent.verticalCenter - font.pointSize: 12 - } -} diff --git a/examples/webenginequick/customdialogs/SwitchButton.qml b/examples/webenginequick/customdialogs/SwitchButton.qml deleted file mode 100644 index 69fc1427e..000000000 --- a/examples/webenginequick/customdialogs/SwitchButton.qml +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts - -Item { - width: parent.width - height: 40 - property alias checked: switcher.checked - RowLayout { - anchors.centerIn: parent - Text { - text: qsTr("Use default dialogs") - font.pointSize: 12 - } - Switch { - id: switcher - checked: true - } - } -} diff --git a/examples/webenginequick/customdialogs/WebView.qml b/examples/webenginequick/customdialogs/WebView.qml deleted file mode 100644 index 5c99ee7e7..000000000 --- a/examples/webenginequick/customdialogs/WebView.qml +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -import QtQuick -import QtWebEngine - -WebEngineView { - id: view - url: "qrc:/index.html" - property bool useDefaultDialogs: true - signal openForm(var form) - - Rectangle { - id: tooltip - width: 200 - height: 30 - z: 50 - visible: false - color: "gray" - border.color: "black" - border.width: 2 - radius: 3 - - property string text: "" - - Text { - x: 0 - y: 0 - color: "#ffffff" - text: parent.text - font.pointSize: 12 - anchors.horizontalCenter: parent.horizontalCenter - anchors.verticalCenter: parent.verticalCenter - font.bold: false - } - - } - - onContextMenuRequested: function(request) { - // we only show menu for links with #openMenu - if (!request.linkUrl.toString().endsWith("#openMenu")) { - request.accepted = true; - return; - } - // return early to show default menu - if (useDefaultDialogs) - return; - - request.accepted = true; - openForm({item: Qt.resolvedUrl("forms/Menu.qml"), - properties: {"request": request}}); - } - - onTooltipRequested: function(request) { - if (useDefaultDialogs) - return; - - if (request.type == TooltipRequest.Show) { - tooltip.visible = true; - tooltip.x = request.x; - tooltip.y = request.y; - tooltip.text = request.text; - } else { - tooltip.visible = false; - } - - request.accepted = true; - } - - onAuthenticationDialogRequested: function(request) { - if (useDefaultDialogs) { - // do not show proxy error page - view.url = "qrc:/index.html" - return; - } - request.accepted = true; - openForm({item: Qt.resolvedUrl("forms/Authentication.qml"), - properties: {"request": request}}); - } - - onJavaScriptDialogRequested: function(request) { - if (useDefaultDialogs) - return; - - request.accepted = true; - openForm({item: Qt.resolvedUrl("forms/JavaScript.qml"), - properties: {"request": request}}); - } - - onColorDialogRequested: function(request) { - if (useDefaultDialogs) - return; - - request.accepted = true; - openForm({item: Qt.resolvedUrl("forms/ColorPicker.qml"), - properties: {"request": request}}); - } - - onFileDialogRequested: function(request) { - if (useDefaultDialogs) - return; - - request.accepted = true; - openForm({item: Qt.resolvedUrl("forms/FilePicker.qml"), - properties: {"request": request}}); - - } - - onTouchSelectionMenuRequested: function(request) { - if (useDefaultDialogs) - return; - - request.accepted = true; - openForm({item: Qt.resolvedUrl("forms/TouchSelectionMenu.qml"), - properties: {"request": request}}); - } -} diff --git a/examples/webenginequick/customdialogs/customdialogs.pro b/examples/webenginequick/customdialogs/customdialogs.pro deleted file mode 100644 index 1b9a6778e..000000000 --- a/examples/webenginequick/customdialogs/customdialogs.pro +++ /dev/null @@ -1,18 +0,0 @@ -QT += webenginequick - -HEADERS += \ - server.h - -SOURCES += \ - main.cpp \ - server.cpp - -RESOURCES += \ - customdialogs.qrc - -target.path = $$[QT_INSTALL_EXAMPLES]/webenginequick/customdialogs -INSTALLS += target - -qtHaveModule(widgets) { - QT += widgets # QApplication is required to get native styling with QtQuickControls -} diff --git a/examples/webenginequick/customdialogs/customdialogs.qrc b/examples/webenginequick/customdialogs/customdialogs.qrc deleted file mode 100644 index bb2677198..000000000 --- a/examples/webenginequick/customdialogs/customdialogs.qrc +++ /dev/null @@ -1,24 +0,0 @@ -<RCC> - <qresource prefix="/"> - <file>forms/AuthenticationForm.ui.qml</file> - <file>forms/Authentication.qml</file> - <file>forms/ColorCell.qml</file> - <file>forms/ColorPickerForm.ui.qml</file> - <file>forms/ColorPicker.qml</file> - <file>forms/CustomButton.qml</file> - <file>forms/FilePickerForm.ui.qml</file> - <file>forms/FilePicker.qml</file> - <file>forms/FileRow.qml</file> - <file>forms/JavaScriptForm.ui.qml</file> - <file>forms/JavaScript.qml</file> - <file>forms/MenuForm.ui.qml</file> - <file>forms/Menu.qml</file> - <file>icon.svg</file> - <file>index.html</file> - <file>main.qml</file> - <file>MessageRectangle.qml</file> - <file>style.css</file> - <file>SwitchButton.qml</file> - <file>WebView.qml</file> - </qresource> -</RCC> diff --git a/examples/webenginequick/customdialogs/doc/images/customdialogs-auth1.png b/examples/webenginequick/customdialogs/doc/images/customdialogs-auth1.png Binary files differdeleted file mode 100644 index 042712f5c..000000000 --- a/examples/webenginequick/customdialogs/doc/images/customdialogs-auth1.png +++ /dev/null diff --git a/examples/webenginequick/customdialogs/doc/images/customdialogs-auth2.png b/examples/webenginequick/customdialogs/doc/images/customdialogs-auth2.png Binary files differdeleted file mode 100644 index 41828d36d..000000000 --- a/examples/webenginequick/customdialogs/doc/images/customdialogs-auth2.png +++ /dev/null diff --git a/examples/webenginequick/customdialogs/doc/images/customdialogs-color1.png b/examples/webenginequick/customdialogs/doc/images/customdialogs-color1.png Binary files differdeleted file mode 100644 index 7f0492f87..000000000 --- a/examples/webenginequick/customdialogs/doc/images/customdialogs-color1.png +++ /dev/null diff --git a/examples/webenginequick/customdialogs/doc/images/customdialogs-color2.png b/examples/webenginequick/customdialogs/doc/images/customdialogs-color2.png Binary files differdeleted file mode 100644 index 9087fdf14..000000000 --- a/examples/webenginequick/customdialogs/doc/images/customdialogs-color2.png +++ /dev/null diff --git a/examples/webenginequick/customdialogs/doc/images/customdialogs-file1.png b/examples/webenginequick/customdialogs/doc/images/customdialogs-file1.png Binary files differdeleted file mode 100644 index 5023ced6f..000000000 --- a/examples/webenginequick/customdialogs/doc/images/customdialogs-file1.png +++ /dev/null diff --git a/examples/webenginequick/customdialogs/doc/images/customdialogs-file2.png b/examples/webenginequick/customdialogs/doc/images/customdialogs-file2.png Binary files differdeleted file mode 100644 index aa25579d7..000000000 --- a/examples/webenginequick/customdialogs/doc/images/customdialogs-file2.png +++ /dev/null diff --git a/examples/webenginequick/customdialogs/doc/images/customdialogs-menu.png b/examples/webenginequick/customdialogs/doc/images/customdialogs-menu.png Binary files differdeleted file mode 100644 index 3409c951c..000000000 --- a/examples/webenginequick/customdialogs/doc/images/customdialogs-menu.png +++ /dev/null diff --git a/examples/webenginequick/customdialogs/doc/images/customdialogs-prompt1.png b/examples/webenginequick/customdialogs/doc/images/customdialogs-prompt1.png Binary files differdeleted file mode 100644 index c34080b4d..000000000 --- a/examples/webenginequick/customdialogs/doc/images/customdialogs-prompt1.png +++ /dev/null diff --git a/examples/webenginequick/customdialogs/doc/images/customdialogs-prompt2.png b/examples/webenginequick/customdialogs/doc/images/customdialogs-prompt2.png Binary files differdeleted file mode 100644 index 2c8d92649..000000000 --- a/examples/webenginequick/customdialogs/doc/images/customdialogs-prompt2.png +++ /dev/null diff --git a/examples/webenginequick/customdialogs/doc/images/customdialogs-tooltip.png b/examples/webenginequick/customdialogs/doc/images/customdialogs-tooltip.png Binary files differdeleted file mode 100644 index 498de9595..000000000 --- a/examples/webenginequick/customdialogs/doc/images/customdialogs-tooltip.png +++ /dev/null diff --git a/examples/webenginequick/customdialogs/doc/images/customdialogs.png b/examples/webenginequick/customdialogs/doc/images/customdialogs.png Binary files differdeleted file mode 100644 index c42114a16..000000000 --- a/examples/webenginequick/customdialogs/doc/images/customdialogs.png +++ /dev/null diff --git a/examples/webenginequick/customdialogs/doc/src/customdialogs.qdoc b/examples/webenginequick/customdialogs/doc/src/customdialogs.qdoc deleted file mode 100644 index d89d76f71..000000000 --- a/examples/webenginequick/customdialogs/doc/src/customdialogs.qdoc +++ /dev/null @@ -1,309 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only - -/*! - \example webenginequick/customdialogs - \title WebEngine Qt Quick Custom Dialogs Example - \ingroup webengine-examples - \brief Customizes UI elements of \QWE's dialogs. - - \image customdialogs.png - - A web page might request dialogs for various purposes, such as - authentication, picking colors, choosing files, and responding to JavaScript - alerts, confirmation requests, and prompts. - - \e {Custom Dialogs} demonstrates how to use WebEngine dialog request objects - to implement custom dialogs for use instead of the default dialogs. - - \include examples-run.qdocinc - - \section1 UI Elements of WebEngineView - - In this example, we create a simple \c index.html page that contains buttons - and text fields for triggering a context menu and the following dialogs: - - \list - \li HTTP Authentication Dialog - \li Proxy Authentication Dialog - \li JavaScript Alert, Confirm, and Prompt Dialogs - \li Color Picker Dialog - \li File Picker Dialog - \endlist - - \section1 Triggering Dialogs - - As mentioned, the \c index.html file is responsible for triggering the - dialogs from the side of HTML and JavaScript. Additionally, the example - program starts a localhost TCP server returning the mocked HTTP responses - needed to trigger proxy and HTTP authentication dialogs. - - \section1 Custom Dialogs - - The custom dialogs are just \e {Qt Quick Designer UI Forms} without any - business logic. The point here is to present the glue code that is required - to display the custom dialog for a particular web engine dialog or a menu - request. - - \section1 Creating the Main Window - - In \c main.cpp, we initialize the WebEngine the same way as in the - \l {WebEngine Qt Quick Minimal Example}: - - \quotefromfile webenginequick/customdialogs/main.cpp - \skipto main - \printuntil } - - In addition, we set up a proxy and a TCP server to be able to simulate proxy - and HTTP authetication requests. - - In \c main.qml, we create a top level window, which contains a StackView - with a SwitchButton and a WebView: - - \quotefromfile webenginequick/customdialogs/main.qml - \skipto Window - \printuntil Component - \printuntil } - \printline } - - \section1 Handling Web Engine Requests - - In this example, we implement the handling of the following web engine - requests: - - \list - \li ContextMenuRequest - \li AuthenticationDialogRequest - \li JavaScriptDialogRequest - \li ColorDialogRequest - \li FileDialogRequest - \endlist - - \section2 Context Menu Requests - - \l [QML]{ContextMenuRequest} is a request object that is passed as a - parameter of the WebEngineView::contextMenuRequested signal. We use the - \c onContextMenuRequested signal handler to handle requests for - \c #openMenu URL links: - - \quotefromfile webenginequick/customdialogs/WebView.qml - \skipto WebEngineView - \printuntil { - \dots 4 - \skipto onContextMenuRequested - \printuntil } - \printuntil } - \printuntil } - \dots 4 - \skipuntil onFileDialogRequested - \skipuntil }}); - \skipuntil } - \skipto } - \printline } - - The first text field from the top on our page triggers the request. Next, - we check whether we should use the default menu. If not, we accept the - request and switch the view to show the \c MenuForm: - - \image customdialogs-menu.png - - \quotefromfile webenginequick/customdialogs/forms/Menu.qml - \skipto MenuForm - \printuntil } - \printuntil } - - To keep things simple, we do not provide any logic on component completion, - and we simply close the form on any action. - - \section2 Tooltip Requests - - \l [QML]{TooltipRequest} is a request object that is passed as a - parameter of the WebEngineView::tooltipRequested signal. We use the - \c onTooltipRequested signal handler to handle requests for - custom tooltip menus at specific positions: - - \quotefromfile webenginequick/customdialogs/WebView.qml - \skipto WebEngineView - \printuntil { - \dots 4 - \skipto onTooltipRequested - \printuntil } - \printuntil } - \printuntil } - \dots 4 - \skipuntil onFileDialogRequested - \skipuntil }}); - \skipuntil } - \skipto } - \printline } - - The second text field from the top on our page triggers the request. Next, - we check whether we should use the default menu. If not, we accept the - request and show a custom QML element as tooltip: - - \image customdialogs-tooltip.png - - \quotefromfile webenginequick/customdialogs/WebView.qml - \skipto Rectangle - \printuntil } - - \section2 Authentication Dialog Requests - - \image customdialogs-auth1.png - - \l [QML]{AuthenticationDialogRequest} is a request object that is passed - as a parameter of the WebEngineView::authenticationDialogRequested signal: - - \quotefromfile webenginequick/customdialogs/WebView.qml - \skipto WebEngineView - \printuntil { - \dots 4 - \skipto onAuthenticationDialogRequested - \printuntil } - \printuntil } - \dots 4 - \skipuntil onFileDialogRequested - \skipuntil }}); - \skipuntil } - \skipto } - \printline } - - We use the \c onAuthenticationDialogRequested signal handler to check - whether we should use the default authentication dialog. If not, we accept - the request and switch the view to show the \c AuthenticationForm: - - \image customdialogs-auth2.png - - \quotefromfile webenginequick/customdialogs/forms/Authentication.qml - \skipto AuthenticationForm - \printuntil } - \printuntil } - \printuntil } - \printuntil } - \printuntil } - - On component completion, we log the request type. The user can fill in the - credentials and click \uicontrol Login. We provide \c onClicked handlers to - accept or reject the authentication dialog. The TCP server on localhost does - not handle real authentication, and therefore we call \c rejectDialog() - instead of \c acceptDialog() also for the login button \c clicked signal. - - \section2 JavaScript Dialog Requests - - \image customdialogs-prompt1.png - - \l [QML]{JavaScriptDialogRequest} is a request object that is passed as a - parameter of the WebEngineView::javaScriptDialogRequested signal: - - \quotefromfile webenginequick/customdialogs/WebView.qml - \skipto WebEngineView - \printuntil { - \dots 4 - \skipto onJavaScriptDialogRequested - \printuntil } - \printuntil } - \dots 4 - \skipuntil onFileDialogRequested - \skipuntil }}); - \skipuntil } - \skipto } - \printline } - - We use the \c onJavaScriptDialogRequested signal handler to check - whether we should use the default JavaScript dialog. If not, we accept the - request and switch the view to show the \c JavaScriptForm: - - \image customdialogs-prompt2.png - - \quotefromfile webenginequick/customdialogs/forms/JavaScript.qml - \skipto JavaScriptForm - \printuntil } - \printuntil } - \printuntil } - \printuntil } - \printuntil } - - On component completion, we customize the form based on the request type. - For a JavaScript prompt dialog we use \c dialogAccept() with the - \c prompt.text argument. - - \section2 Color Dialog Requests - - \image customdialogs-color1.png - - \l [QML]{ColorDialogRequest} is a request object that is passed as a - parameter of the WebEngineView::colorDialogRequested signal: - - \quotefromfile webenginequick/customdialogs/WebView.qml - \skipto WebEngineView - \printuntil { - \dots 4 - \skipto onColorDialogRequested - \printuntil } - \printuntil } - \dots 4 - \skipuntil onFileDialogRequested - \skipuntil }}); - \skipuntil } - \skipto } - \printline } - - We use the \c onColorDialogRequested signal handler to check whether - we should use the default color picker dialog. If not, we accept the request - and switch the view to show the \c ColorPickerForm: - - \image customdialogs-color2.png - - \quotefromfile webenginequick/customdialogs/forms/ColorPicker.qml - \skipto ColorPickerForm - \printuntil } - \printuntil } - \printuntil } - \printuntil } - \printuntil } - \printuntil } - \printuntil } - - On component completion, we create callbacks for all the color cells. When - the user selects the color and clicks \c OK, we pass the selected color to - the \c dialogAccept() method. - - \section2 File Dialog Requests - - \image customdialogs-file1.png - - \l [QML]{FileDialogRequest} is a request object that is passed as a - parameter of the WebEngineView::fileDialogRequested signal: - - \quotefromfile webenginequick/customdialogs/WebView.qml - \skipto WebEngineView - \printuntil { - \dots 4 - \skipto onFileDialogRequested - \printuntil } - \printuntil } - \printuntil } - - We use the \c onFileDialogRequested signal handler to check whether - we should use the default file picker dialog. If not, we accept the request - and switch the view to show the \c FilePickerForm: - - \image customdialogs-file2.png - - \quotefromfile webenginequick/customdialogs/forms/FilePicker.qml - \skipto FilePickerForm - \printuntil } - \printuntil } - \printuntil } - \printuntil } - \printuntil } - \printuntil } - \printuntil } - \printuntil } - \printuntil } - - On component completion, we create callbacks for selecting files. When the user selects a - file and clicks \c OK, we pass the selected file to the \c dialogAccept - method. - -*/ diff --git a/examples/webenginequick/customdialogs/forms/Authentication.qml b/examples/webenginequick/customdialogs/forms/Authentication.qml deleted file mode 100644 index 151a7c4aa..000000000 --- a/examples/webenginequick/customdialogs/forms/Authentication.qml +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -import QtQuick -import QtWebEngine - -AuthenticationForm { - property QtObject request - signal closeForm() - - cancelButton.onClicked: { - request.dialogReject(); - closeForm(); - } - - loginButton.onClicked: { - request.dialogReject(); - closeForm(); - } - - Component.onCompleted: { - switch (request.type) { - case AuthenticationDialogRequest.AuthenticationTypeHTTP: - console.log("HTTP Authentication Required. Host says: " + request.realm); - break; - case AuthenticationDialogRequest.AuthenticationTypeProxy: - console.log("Proxy Authentication Required for: " + request.proxyHost); - break; - } - } -} diff --git a/examples/webenginequick/customdialogs/forms/AuthenticationForm.ui.qml b/examples/webenginequick/customdialogs/forms/AuthenticationForm.ui.qml deleted file mode 100644 index f14986b20..000000000 --- a/examples/webenginequick/customdialogs/forms/AuthenticationForm.ui.qml +++ /dev/null @@ -1,137 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -import QtQuick -import QtQuick.Layouts -import QtQuick.Controls - -Item { - id: item1 - property alias cancelButton: cancelButton - property alias loginButton: loginButton - property alias userName: userName - property alias password: password - - ColumnLayout { - id: columnLayout - anchors.topMargin: 20 - anchors.top: parent.top - anchors.bottomMargin: 20 - anchors.bottom: parent.bottom - anchors.rightMargin: 20 - anchors.right: parent.right - anchors.leftMargin: 20 - anchors.left: parent.left - - Image { - id: image - Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter - source: "qrc:/icon.svg" - } - - Rectangle { - id: rectangle - width: parent.width - height: 30 - Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter - gradient: Gradient { - GradientStop { - position: 0 - color: "#25a6e2" - } - GradientStop { - color: "#188bd0" - } - } - - Text { - id: textArea - x: 54 - y: 5 - color: "#ffffff" - text: qsTr("Restricted Area") - font.pointSize: 12 - anchors.horizontalCenter: parent.horizontalCenter - anchors.verticalCenter: parent.verticalCenter - } - } - - Item { - width: 40 - height: 40 - } - - Text { - id: userNameText - text: qsTr("Username:") - font.pointSize: 12 - } - - TextField { - id: userName - width: 300 - height: 22 - Layout.fillWidth: true - font.pointSize: 12 - color: "black" - - background: Rectangle { - color: "white" - border.color: "black" - border.width: 1 - } - } - - Text { - id: passwordText - text: qsTr("Password:") - font.pointSize: 12 - } - - TextField { - id: password - width: 300 - height: 26 - Layout.fillWidth: true - font.pointSize: 12 - color: "black" - echoMode: TextInput.Password - - background: Rectangle { - color: "white" - border.color: "black" - border.width: 1 - } - } - - Item { - Layout.fillHeight: true - } - - RowLayout { - id: rowLayout - width: 100 - height: 100 - - Item { - Layout.fillWidth: true - } - - CustomButton { - id: cancelButton - width: 90 - height: 30 - btnText: qsTr("Cancel") - btnBlue: false - } - - CustomButton { - id: loginButton - width: 90 - height: 30 - btnText: qsTr("Login") - btnBlue: false - } - } - } -} diff --git a/examples/webenginequick/customdialogs/forms/ColorCell.qml b/examples/webenginequick/customdialogs/forms/ColorCell.qml deleted file mode 100644 index 57151780c..000000000 --- a/examples/webenginequick/customdialogs/forms/ColorCell.qml +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -import QtQuick - -Rectangle { - id: rectangle - width: 50 - height: 50 - signal clicked() - MouseArea { - id: mouseArea - anchors.fill: parent - onClicked: rectangle.clicked() - } -} diff --git a/examples/webenginequick/customdialogs/forms/ColorPicker.qml b/examples/webenginequick/customdialogs/forms/ColorPicker.qml deleted file mode 100644 index 63269ddff..000000000 --- a/examples/webenginequick/customdialogs/forms/ColorPicker.qml +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -import QtQuick - -ColorPickerForm { - property QtObject request - signal closeForm() - - okButton.onClicked: { - request.dialogAccept(colorPicker.color); - closeForm(); - } - - cancelButton.onClicked: { - request.dialogReject(); - closeForm(); - } - - function createCallback(color) { - return function() { colorPicker.color = color }; - } - - Component.onCompleted:{ - for (var i = 0; i < grid.children.length; i++) { - var cell = grid.children[i]; - cell.clicked.connect(createCallback(cell.color)); - } - colorPicker.color = request.color; - } -} diff --git a/examples/webenginequick/customdialogs/forms/ColorPickerForm.ui.qml b/examples/webenginequick/customdialogs/forms/ColorPickerForm.ui.qml deleted file mode 100644 index 060aeef7d..000000000 --- a/examples/webenginequick/customdialogs/forms/ColorPickerForm.ui.qml +++ /dev/null @@ -1,186 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -import QtQuick -import QtQuick.Layouts - -Item { - property alias cancelButton: cancelButton - property alias okButton: okButton - property string message: "Message" - property string title: "Title" - property alias blue1: blue1 - property alias grid: grid - property alias colorPicker: colorPicker - - ColumnLayout { - id: columnLayout - anchors.topMargin: 20 - anchors.top: parent.top - anchors.bottomMargin: 20 - anchors.bottom: parent.bottom - anchors.rightMargin: 20 - anchors.right: parent.right - anchors.leftMargin: 20 - anchors.left: parent.left - - Image { - Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter - source: "qrc:/icon.svg" - } - - Rectangle { - width: parent.width - height: 30 - Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter - gradient: Gradient { - GradientStop { - position: 0 - color: "#25a6e2" - } - - GradientStop { - color: "#188bd0" - } - } - - Text { - id: title - x: 54 - y: 5 - color: "#ffffff" - text: qsTr("Select Color") - font.pointSize: 12 - anchors.horizontalCenter: parent.horizontalCenter - anchors.verticalCenter: parent.verticalCenter - } - } - - Item { - width: 40 - height: 40 - } - - GridLayout { - id: grid - columns: 5 - Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter - - ColorCell { - id: blue1 - color: "#26d5f8" - } - ColorCell { - id: green1 - color: "#25f93d" - } - ColorCell { - id: red1 - color: "#f71111" - } - ColorCell { - id: yellow1 - color: "#faf23c" - } - ColorCell { - id: orange1 - color: "#ec8505" - } - ColorCell { - id: blue2 - color: "#037eaa" - } - ColorCell { - id: green2 - color: "#389a13" - } - ColorCell { - id: red2 - color: "#b2001b" - } - ColorCell { - id: yellow2 - color: "#caca03" - } - ColorCell { - id: orange2 - color: "#bb4900" - } - ColorCell { - id: blue3 - color: "#01506c" - } - ColorCell { - id: green3 - color: "#37592b" - } - ColorCell { - id: red3 - color: "#700113" - } - ColorCell { - id: yellow3 - color: "#848404" - } - - ColorCell { - id: orange3 - color: "#563100" - } - } - - Item { - width: 10 - height: 10 - } - - Rectangle { - width: 90 - height: 90 - color: "#000000" - radius: 4 - Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter - - Rectangle { - id: colorPicker - height: 80 - color: "#ffffff" - anchors.rightMargin: 5 - anchors.leftMargin: 5 - anchors.bottomMargin: 5 - anchors.topMargin: 5 - anchors.fill: parent - } - } - - Item { - Layout.fillHeight: true - } - - RowLayout { - id: rowLayout - width: 100 - height: 100 - - Item { - Layout.fillWidth: true - } - - CustomButton { - id: cancelButton - width: 90 - height: 30 - btnText: qsTr("Cancel") - btnBlue: false - } - - CustomButton { - id: okButton - width: 90 - height: 30 - btnText: qsTr("OK") - btnBlue: false - } - } - } -} diff --git a/examples/webenginequick/customdialogs/forms/CustomButton.qml b/examples/webenginequick/customdialogs/forms/CustomButton.qml deleted file mode 100644 index 00a06d558..000000000 --- a/examples/webenginequick/customdialogs/forms/CustomButton.qml +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -import QtQuick - -Rectangle { - id: root - width: 200 - height: 30 - radius: 5 - property string btnText: "Name" - property bool btnEnable: true - property bool btnBlue: true - opacity: btnEnable ? 1.0 : 0.5 - signal clicked() - gradient: btnBlue ? blueButton : greenButton - Text { - id: textArea - x: 54 - y: 5 - color: "#ffffff" - text: parent.btnText - font.pointSize: 12 - anchors.horizontalCenter: parent.horizontalCenter - anchors.verticalCenter: parent.verticalCenter - font.bold: false - } - - MouseArea { - id: mouseArea - anchors.fill: parent - onClicked: { - if (btnEnable) - root.clicked(); - } - } - - Gradient { - id: blueButton - GradientStop { - position: 0 - color: "#25a6e2" - } - GradientStop { - position: mouseArea.pressed && root.btnEnable ? 0.7 :1 - color: "#188bd0" - } - } - - Gradient { - id: greenButton - GradientStop { - position: 0 - color: "#80c342" - } - GradientStop { - position: mouseArea.pressed && root.btnEnable ? 0.7 :1 - color: "#5fac18" - } - } -} diff --git a/examples/webenginequick/customdialogs/forms/FilePicker.qml b/examples/webenginequick/customdialogs/forms/FilePicker.qml deleted file mode 100644 index 45ffefb3a..000000000 --- a/examples/webenginequick/customdialogs/forms/FilePicker.qml +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -import QtQuick - -FilePickerForm { - property QtObject request - property string selectedFile - signal closeForm() - - cancelButton.onClicked: { - request.dialogReject(); - closeForm(); - } - - okButton.onClicked: { - request.dialogAccept('/' + selectedFile); - closeForm(); - } - - function createCallback(fileIndex) { - return function() { - for (var i = 0; i < files.children.length; i++) { - var file = files.children[i]; - if (i === fileIndex) { - selectedFile = file.text; - file.selected = true; - } else { - file.selected = false; - } - } - } - } - - Component.onCompleted: { - selectedFile = request.defaultFileName; - for (var i = 0; i < files.children.length; i++) { - var file = files.children[i]; - file.clicked.connect(createCallback(i)); - if (file.text === selectedFile) - file.selected = true; - } - } -} diff --git a/examples/webenginequick/customdialogs/forms/FilePickerForm.ui.qml b/examples/webenginequick/customdialogs/forms/FilePickerForm.ui.qml deleted file mode 100644 index 1e99b1a91..000000000 --- a/examples/webenginequick/customdialogs/forms/FilePickerForm.ui.qml +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -import QtQuick -import QtQuick.Layouts - -Item { - property alias cancelButton: cancelButton - property alias okButton: okButton - property string message: "Message" - property string title: "Title" - property alias files: files - - ColumnLayout { - id: columnLayout - anchors.topMargin: 20 - anchors.top: parent.top - anchors.bottomMargin: 20 - anchors.bottom: parent.bottom - anchors.rightMargin: 20 - anchors.right: parent.right - anchors.leftMargin: 20 - anchors.left: parent.left - - Image { - id: image - Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter - source: "qrc:/icon.svg" - } - - Rectangle { - id: rectangle - width: parent.width - height: 30 - Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter - gradient: Gradient { - GradientStop { - position: 0 - color: "#25a6e2" - } - - GradientStop { - color: "#188bd0" - } - } - - Text { - id: title - x: 54 - y: 5 - color: "#ffffff" - text: qsTr("Select File") - font.pointSize: 12 - anchors.horizontalCenter: parent.horizontalCenter - anchors.verticalCenter: parent.verticalCenter - } - } - - Item { - width: 40 - height: 40 - } - - ColumnLayout { - id: files - - FileRow { - id: filename1 - text: "example.qdoc" - } - - FileRow { - id: filename2 - text: "factory.cpp" - } - - FileRow { - id: filename3 - text: "index.html" - } - - FileRow { - id: filename4 - text: "main.qml" - } - - FileRow { - id: filename5 - text: "qt-logo.png" - } - - FileRow { - id: filename6 - text: "window.h" - } - } - - Item { - Layout.fillHeight: true - } - - RowLayout { - id: rowLayout - width: 20 - height: 100 - - Item { - Layout.fillWidth: true - } - - CustomButton { - id: cancelButton - width: 90 - height: 30 - btnText: qsTr("Cancel") - btnBlue: false - } - - CustomButton { - id: okButton - width: 90 - height: 30 - btnText: qsTr("OK") - btnBlue: false - } - } - } -} diff --git a/examples/webenginequick/customdialogs/forms/FileRow.qml b/examples/webenginequick/customdialogs/forms/FileRow.qml deleted file mode 100644 index 1a0cfc0a0..000000000 --- a/examples/webenginequick/customdialogs/forms/FileRow.qml +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -import QtQuick -import QtQuick.Layouts - -Item { - id: root - height: 30 - property string text: "Filename" - property bool selected: false - signal clicked() - - RowLayout { - id: fileRow - width: 100 - - Item { - id: item5 - width: 10 - height: 10 - } - - Rectangle { - id: rectangle2 - width: 10 - height: 10 - color: selected ? "#80c342" : "#25a6e2" - } - - Text { - id: filename - text: root.text - font.pointSize: 12 - } - } - - MouseArea { - id: mouseArea - width: 200 - height: 30 - onClicked: root.clicked() - } -} diff --git a/examples/webenginequick/customdialogs/forms/JavaScript.qml b/examples/webenginequick/customdialogs/forms/JavaScript.qml deleted file mode 100644 index 132c95697..000000000 --- a/examples/webenginequick/customdialogs/forms/JavaScript.qml +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -import QtQuick -import QtWebEngine - -JavaScriptForm { - property QtObject request - signal closeForm() - - cancelButton.onClicked: { - request.dialogReject(); - closeForm(); - } - - okButton.onClicked: { - request.dialogAccept(prompt.text); - closeForm(); - } - - Component.onCompleted: { - switch (request.type) { - case JavaScriptDialogRequest.DialogTypeAlert: - cancelButton.visible = false; - title = qsTr("Alert"); - message = request.message; - prompt.text = ""; - prompt.visible = false; - break; - case JavaScriptDialogRequest.DialogTypeConfirm: - title = qsTr("Confirm"); - message = request.message; - prompt.text = ""; - prompt.visible = false; - break; - case JavaScriptDialogRequest.DialogTypePrompt: - title = qsTr("Prompt"); - message = request.message; - prompt.text = request.defaultText; - prompt.visible = true; - break; - } - } -} diff --git a/examples/webenginequick/customdialogs/forms/JavaScriptForm.ui.qml b/examples/webenginequick/customdialogs/forms/JavaScriptForm.ui.qml deleted file mode 100644 index b535e7ef9..000000000 --- a/examples/webenginequick/customdialogs/forms/JavaScriptForm.ui.qml +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -import QtQuick -import QtQuick.Layouts -import QtQuick.Controls - -Item { - id: root - property alias cancelButton: cancelButton - property alias okButton: okButton - property string message: "Message" - property string title: "Title" - property alias prompt: prompt - - ColumnLayout { - id: columnLayout - anchors.topMargin: 20 - anchors.top: parent.top - anchors.bottomMargin: 20 - anchors.bottom: parent.bottom - anchors.rightMargin: 20 - anchors.right: parent.right - anchors.leftMargin: 20 - anchors.left: parent.left - - Image { - id: image - Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter - source: "qrc:/icon.svg" - } - - Rectangle { - id: rectangle - width: parent.width - height: 30 - Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter - gradient: Gradient { - GradientStop { - position: 0 - color: "#25a6e2" - } - - GradientStop { - color: "#188bd0" - } - } - - Text { - id: title - x: 54 - y: 5 - color: "#ffffff" - text: qsTr("Title") - font.pointSize: 12 - anchors.horizontalCenter: parent.horizontalCenter - anchors.verticalCenter: parent.verticalCenter - } - } - - Item { - width: 40 - height: 40 - } - - Text { - id: message - text: root.message - font.pointSize: 12 - } - - TextField { - id: prompt - width: 300 - height: 22 - Layout.fillWidth: true - font.pointSize: 12 - color: "black" - - background: Rectangle { - color: "white" - border.color: "black" - border.width: 1 - } - } - - Item { - Layout.fillHeight: true - } - - RowLayout { - id: rowLayout - width: 100 - height: 100 - - Item { - Layout.fillWidth: true - } - - CustomButton { - id: cancelButton - width: 90 - height: 30 - btnText: qsTr("Cancel") - btnBlue: false - } - - CustomButton { - id: okButton - width: 90 - height: 30 - btnText: qsTr("OK") - btnBlue: false - } - } - } -} diff --git a/examples/webenginequick/customdialogs/forms/Menu.qml b/examples/webenginequick/customdialogs/forms/Menu.qml deleted file mode 100644 index b90802a0c..000000000 --- a/examples/webenginequick/customdialogs/forms/Menu.qml +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -import QtQuick - -MenuForm { - property QtObject request - signal closeForm() - - followLink.onClicked: closeForm() - back.onClicked: closeForm() - forward.onClicked: closeForm() - reload.onClicked: closeForm() - copyLinkUrl.onClicked: closeForm() - saveLink.onClicked: closeForm() - close.onClicked: closeForm() - - Component.onCompleted: { - back.btnEnable = false; - forward.btnEnable = false; - } -} diff --git a/examples/webenginequick/customdialogs/forms/MenuForm.ui.qml b/examples/webenginequick/customdialogs/forms/MenuForm.ui.qml deleted file mode 100644 index b4c06bb7d..000000000 --- a/examples/webenginequick/customdialogs/forms/MenuForm.ui.qml +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -import QtQuick -import QtQuick.Layouts - -Item { - property alias followLink: followLink - property alias back: back - property alias forward: forward - property alias reload: reload - property alias copyLinkUrl: copyLinkUrl - property alias saveLink: saveLink - property alias close: close - - ColumnLayout { - id: columnLayout - anchors.verticalCenter: parent.verticalCenter - anchors.horizontalCenter: parent.horizontalCenter - - Image { - id: image - width: 100 - height: 100 - Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter - source: "qrc:/icon.svg" - } - - CustomButton { - id: followLink - btnText: qsTr("Follow") - } - - CustomButton { - id: back - btnText: qsTr("Back") - } - - CustomButton { - id: forward - btnText: qsTr("Forward") - } - - CustomButton { - id: reload - btnText: qsTr("Reload") - } - - CustomButton { - id: copyLinkUrl - btnText: qsTr("Copy Link URL") - } - - CustomButton { - id: saveLink - btnText: qsTr("Save Link") - } - - CustomButton { - id: close - btnBlue: false - btnText: qsTr("Close") - } - } -} diff --git a/examples/webenginequick/customdialogs/forms/TouchSelectionMenu.qml b/examples/webenginequick/customdialogs/forms/TouchSelectionMenu.qml deleted file mode 100644 index 1b0c19789..000000000 --- a/examples/webenginequick/customdialogs/forms/TouchSelectionMenu.qml +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (C) 2021 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -import QtQuick - -TouchSelectionMenuForm { - property QtObject request - signal closeForm() - - cut.onClicked: closeForm() - copy.onClicked: closeForm() - paste.onClicked: closeForm() - contextMenu.onClicked: closeForm() -} diff --git a/examples/webenginequick/customdialogs/forms/TouchSelectionMenuForm.ui.qml b/examples/webenginequick/customdialogs/forms/TouchSelectionMenuForm.ui.qml deleted file mode 100644 index bed39566f..000000000 --- a/examples/webenginequick/customdialogs/forms/TouchSelectionMenuForm.ui.qml +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (C) 2021 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -import QtQuick -import QtQuick.Layouts - -Item { - property alias cut: cut - property alias copy: copy - property alias paste: paste - property alias contextMenu: contextMenu - - ColumnLayout { - id: columnLayout - anchors.verticalCenter: parent.verticalCenter - anchors.horizontalCenter: parent.horizontalCenter - - CustomButton { - id: cut - btnText: qsTr("Cut") - } - - CustomButton { - id: copy - btnText: qsTr("Copy") - } - - CustomButton { - id: paste - btnText: qsTr("Paste") - } - - CustomButton { - id: contextMenu - btnText: qsTr("...") - } - - } -} diff --git a/examples/webenginequick/customdialogs/forms/forms.qmlproject b/examples/webenginequick/customdialogs/forms/forms.qmlproject deleted file mode 100644 index b06afaaf1..000000000 --- a/examples/webenginequick/customdialogs/forms/forms.qmlproject +++ /dev/null @@ -1,45 +0,0 @@ -import QmlProject - -Project { - mainFile: "MenuForm.ui.qml" - - /* Include .qml, .js, and image files from current directory and subdirectories */ - QmlFiles { - directory: "." - } - - JavaScriptFiles { - directory: "." - } - - ImageFiles { - directory: "." - } - - Files { - filter: "*.conf" - files: ["qtquickcontrols2.conf"] - } - - Files { - filter: "qmldir" - directory: "." - } - - Files { - filter: "*.ttf;*.otf" - } - - Environment { - QT_QUICK_CONTROLS_CONF: "qtquickcontrols2.conf" - QT_AUTO_SCREEN_SCALE_FACTOR: "1" - } - - qt6Project: true - - /* List of plugin directories passed to QML runtime */ - importPaths: [ ".", "imports" ] - - /* Required for deployment */ - targetDirectory: "/opt/forms" -} diff --git a/examples/webenginequick/customdialogs/icon.svg b/examples/webenginequick/customdialogs/icon.svg deleted file mode 100644 index 48271180b..000000000 --- a/examples/webenginequick/customdialogs/icon.svg +++ /dev/null @@ -1,24 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> -<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" - width="94px" height="94px" viewBox="0 0 94 94" enable-background="new 0 0 94 94" xml:space="preserve"> -<g> - <circle fill="none" cx="47" cy="47" r="47"/> - <g> - <path fill="#46A2DA" d="M47,92.979c-11.779,0-23.559-4.484-32.526-13.451C-3.461,61.591-3.461,32.409,14.472,14.474 - C32.41-3.463,61.592-3.461,79.526,14.473c17.935,17.936,17.935,47.119,0.002,65.054l-0.002,0.001 - C70.559,88.495,58.779,92.979,47,92.979z"/> - </g> - <path fill="#80C342" d="M93,47C93,21.595,72.405,1,47,1C34.297,1,22.797,6.149,14.473,14.473l65.054,65.054 - C87.851,71.203,93,59.703,93,47z"/> - <g> - <path fill="#46A2DA" d="M47,65c-4.808,0-9.328-1.873-12.728-5.272c-7.018-7.019-7.018-18.438,0-25.456 - C37.672,30.873,42.192,29,47,29s9.328,1.873,12.728,5.272c7.018,7.019,7.018,18.438,0,25.456C56.328,63.127,51.808,65,47,65z"/> - <path fill="#FFFFFF" d="M62.248,59.919c6.671-7.858,6.312-19.644-1.105-27.061C57.237,28.953,52.118,27,47,27 - c-5.118,0-10.237,1.953-14.142,5.858c-7.81,7.81-7.81,20.474,0,28.284C36.763,65.047,41.882,67,47,67 - c4.379,0,8.752-1.441,12.372-4.3L77.88,81.209c0.989-0.895,1.935-1.837,2.843-2.814L62.248,59.919z M35.686,58.314 - c-6.238-6.238-6.238-16.389,0-22.627C38.708,32.664,42.726,31,47,31c4.274,0,8.292,1.664,11.314,4.686 - c6.238,6.238,6.238,16.389,0,22.627C55.292,61.336,51.274,63,47,63C42.726,63,38.708,61.336,35.686,58.314z"/> - </g> -</g> -</svg> diff --git a/examples/webenginequick/customdialogs/index.html b/examples/webenginequick/customdialogs/index.html deleted file mode 100644 index d5de2827c..000000000 --- a/examples/webenginequick/customdialogs/index.html +++ /dev/null @@ -1,51 +0,0 @@ -<!DOCTYPE html> -<html lang="en"> - <head> - <meta charset="utf-8"> - <title>Custom UI</title> - <link rel="stylesheet" type="text/css" href="style.css"> - </head> - <body> - <table align="center"> - <tr> - <td><div class="div"><a href="#openMenu" class="link">Right click on text to see link context menu</a></div></td> - </tr> - <tr> - <td><div class="div"><p title="I am a tooltip.">Hover this text to display a tooltip</a></div></td> - </tr> - <tr> - <td><div class="div"><p>Touch devices only: long press on this text to see the touch selection menu</p></div></td> - </tr> - <tr> - <td><button class="button" onclick="window.location = 'http://localhost.:5555/OPEN_AUTH'"> - Open Authentication Dialog</button></td> - </tr> - <tr> - <td><button class="button" onclick="window.location = 'http://www.qt.io'"> - Open Proxy Dialog</button></td> - </tr> - <tr> - <td><button class="button" onclick="alert('This is the Alert Dialog !')"> - Open Alert Dialog</button></td> - </tr> - <tr> - <td><button class="button" onclick="confirm('This is the Confirm Dialog.')"> - Open Confirm Dialog</button></td> - </tr> - <tr> - <td><button class="button" onclick="prompt('Is this the Prompt Dialog ?', 'Yes')"> - Open Prompt Dialog</button></td> - </tr> - <tr> - <td><button class="button" onclick="document.getElementById('colorpicker').click()"> - Open Color Dialog</button></td> - </tr> - <tr> - <td><button class="button" onclick="document.getElementById('filepicker').click()"> - Open File Dialog</button></td> - </tr> - </table> - <input type="color" id="colorpicker" value="#ff0000" style="visibility:hidden"/> - <input type="file" id="filepicker" accept=".cpp, .html, .h, .png, .qdoc, .qml" style="visibility:hidden"/> - </body> -</html> diff --git a/examples/webenginequick/customdialogs/main.cpp b/examples/webenginequick/customdialogs/main.cpp deleted file mode 100644 index c114ea935..000000000 --- a/examples/webenginequick/customdialogs/main.cpp +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -#include "server.h" -#include <QtWebEngineQuick/qtwebenginequickglobal.h> -#include <QNetworkProxy> -#include <QQmlApplicationEngine> -#include <QTimer> -#include <QtGui/QGuiApplication> - -int main(int argc, char *argv[]) -{ - QCoreApplication::setOrganizationName("QtExamples"); - QtWebEngineQuick::initialize(); - - QGuiApplication app(argc, argv); - - QQmlApplicationEngine engine; - Server *server = new Server(&engine); - - engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); - QTimer::singleShot(0, server, &Server::run); - - QNetworkProxy proxy; - proxy.setType(QNetworkProxy::HttpProxy); - proxy.setHostName("localhost"); - proxy.setPort(5555); - QNetworkProxy::setApplicationProxy(proxy); - - return app.exec(); -} - diff --git a/examples/webenginequick/customdialogs/main.qml b/examples/webenginequick/customdialogs/main.qml deleted file mode 100644 index d0cb6f324..000000000 --- a/examples/webenginequick/customdialogs/main.qml +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import QtQuick.Window - -Window { - id: mainWindow - width: 800 - height: 610 - visible: true - - StackView { - id: stackView - anchors.fill: parent - focus: true - initialItem: Item { - id: main - width: mainWindow.width - height: mainWindow.height - ColumnLayout { - anchors.fill: parent - SwitchButton { - id: switcher - Layout.fillWidth: true - } - WebView { - id: webView - useDefaultDialogs: switcher.checked - Layout.fillWidth: true - Layout.fillHeight: true - } - } - } - - function closeForm() - { - pop(main); - // reset url in case of proxy error - webView.url = "qrc:/index.html" - } - - function openForm(form) - { - push(form.item, form.properties); - currentItem.closeForm.connect(closeForm); - } - - } - - Component.onCompleted: { - webView.openForm.connect(stackView.openForm); - } -} diff --git a/examples/webenginequick/customdialogs/server.cpp b/examples/webenginequick/customdialogs/server.cpp deleted file mode 100644 index efb870618..000000000 --- a/examples/webenginequick/customdialogs/server.cpp +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -#include "server.h" -#include <QDataStream> -#include <QTcpSocket> - -Server::Server(QObject *parent) : QObject(parent) -{ - connect(&m_server, &QTcpServer::newConnection, this, &Server::handleNewConnection); -} - -void Server::run() -{ - if (!m_server.listen(QHostAddress::LocalHost, 5555)) - qWarning() << "Could not start the server -> http/proxy authentication dialog" - " will not work. Error:" << m_server.errorString(); -} - -void Server::handleNewConnection() -{ - QTcpSocket *socket = m_server.nextPendingConnection(); - connect(socket, &QAbstractSocket::disconnected, socket, &QObject::deleteLater); - connect(socket, &QAbstractSocket::readyRead, this, &Server::handleReadReady); -} - -void Server::handleReadReady() -{ - QTcpSocket *socket = qobject_cast<QTcpSocket*>(sender()); - Q_ASSERT(socket); - m_data.append(socket->readAll()); - - // simply wait for whole request - if (!m_data.endsWith("\r\n\r\n")) - return; - if (m_data.contains(QByteArrayLiteral("OPEN_AUTH"))) { - socket->write("HTTP/1.1 401 Unauthorized\nWWW-Authenticate: " - "Basic realm=\"Very Restricted Area\"\r\n\r\n"); - m_data.clear(); - return; - } - - socket->write("HTTP/1.1 407 Proxy Auth Required\nProxy-Authenticate: " - "Basic realm=\"Proxy requires authentication\"\r\n" - "content-length: 0\r\n\r\n"); - m_data.clear(); -} diff --git a/examples/webenginequick/customdialogs/server.h b/examples/webenginequick/customdialogs/server.h deleted file mode 100644 index 563465013..000000000 --- a/examples/webenginequick/customdialogs/server.h +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -#ifndef SERVER_H -#define SERVER_H - -#include <QObject> -#include <QTcpServer> - -class Server : public QObject -{ - Q_OBJECT - -public: - explicit Server(QObject *parent = nullptr); - -public slots: - void run(); - -private slots: - void handleNewConnection(); - void handleReadReady(); - -private: - QTcpServer m_server; - QByteArray m_data; -}; - -#endif // SERVER_H diff --git a/examples/webenginequick/customdialogs/style.css b/examples/webenginequick/customdialogs/style.css deleted file mode 100644 index e4c25e7eb..000000000 --- a/examples/webenginequick/customdialogs/style.css +++ /dev/null @@ -1,37 +0,0 @@ -.div { - padding:8px 4px; - border: 5px solid #188BD0; - width: 280px; - font-family: sans-serif; - font-size:10pt; -} -.link { - text-decoration: none; - color: #888888; -} -.button { - background: -webkit-linear-gradient(top,#25A6E2 0%,#188BD0 100%); - padding:8px 13px; - color:#fff; - font-family: sans-serif; - font-size:17px; - -webkit-border-radius:5px; - border:1px solid #1A87FF; - width: 300px; -} -.button:focus { - outline: none; -} -.button:active { - background: -webkit-linear-gradient(top,#25A6E2 0%,#188BD0 70%); -} -.input { - padding:8px 4px; - border: 5px solid #188BD0; - width: 280px; - font-family: sans-serif; - font-size:10pt; -} -.input:focus { - outline: none; -} diff --git a/examples/webenginequick/customtouchhandle/CMakeLists.txt b/examples/webenginequick/customtouchhandle/CMakeLists.txt deleted file mode 100644 index 3ec47e8bd..000000000 --- a/examples/webenginequick/customtouchhandle/CMakeLists.txt +++ /dev/null @@ -1,45 +0,0 @@ -cmake_minimum_required(VERSION 3.16) -project(customtouchhandle LANGUAGES CXX) - -set(CMAKE_AUTOMOC ON) - -if(NOT DEFINED INSTALL_EXAMPLESDIR) - set(INSTALL_EXAMPLESDIR "examples") -endif() - -set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/webenginequick/customtouchhandle") - -find_package(Qt6 REQUIRED COMPONENTS Core Gui WebEngineQuick) - -qt_add_executable(customtouchhandle - main.cpp -) - -set_target_properties(customtouchhandle PROPERTIES - WIN32_EXECUTABLE TRUE - MACOSX_BUNDLE TRUE -) - -target_link_libraries(customtouchhandle PUBLIC - Qt::Core - Qt::Gui - Qt::WebEngineQuick -) - -# Resources: -set(qml_resource_files - "main.qml" -) - -qt6_add_resources(customtouchhandle "qml" - PREFIX - "/" - FILES - ${qml_resource_files} -) - -install(TARGETS customtouchhandle - RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" - BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" - LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" -) diff --git a/examples/webenginequick/customtouchhandle/customtouchhandle.pro b/examples/webenginequick/customtouchhandle/customtouchhandle.pro deleted file mode 100644 index a74ef3146..000000000 --- a/examples/webenginequick/customtouchhandle/customtouchhandle.pro +++ /dev/null @@ -1,10 +0,0 @@ -TEMPLATE = app - -QT += webenginequick - -SOURCES += main.cpp - -RESOURCES += qml.qrc - -target.path = $$[QT_INSTALL_EXAMPLES]/webenginequick/customtouchhandle -INSTALLS += target diff --git a/examples/webenginequick/customtouchhandle/doc/images/customtouchhandle.jpg b/examples/webenginequick/customtouchhandle/doc/images/customtouchhandle.jpg Binary files differdeleted file mode 100644 index bd65c083d..000000000 --- a/examples/webenginequick/customtouchhandle/doc/images/customtouchhandle.jpg +++ /dev/null diff --git a/examples/webenginequick/customtouchhandle/doc/src/customtouchhandle.qdoc b/examples/webenginequick/customtouchhandle/doc/src/customtouchhandle.qdoc deleted file mode 100644 index 742f65b6b..000000000 --- a/examples/webenginequick/customtouchhandle/doc/src/customtouchhandle.qdoc +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (C) 2022 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only - -/*! - \example webenginequick/customtouchhandle - \title WebEngine Qt Quick Custom Touch Handle Example - \ingroup webengine-examples - \brief Shows custom touch handles upon touch selection events. - - \image customtouchhandle.jpg - - \e {WebEngine Qt Quick Touch Handle Example} demonstrates how to use - custom touch handles when a touch selection event happens. It shows the - minimum amount of code needed to use custom touch handle delegates, and - can be used as a basis for further experimentation. - - \section1 Custom Touch Handle - - In \c main.qml we create the custom touch handle delegate. - - \quotefromfile webenginequick/customtouchhandle/main.qml - \skipto WebEngineView - \printuntil /^\ {4}\}/ - - \section1 QML Code - - In \c main.qml we create the top level window filled by a - \l{WebEngineView} item loading the \l{Qt Homepage}. - To display custom touch handles, a QML item should be delegated to - \l{WebEngineView::touchHandleDelegate}. - - The touch handle's position, opacity, and visibility is automatically updated. - - \note If no delegate is provided, Chromium's default touch handles will appear. - - \section1 Requirements - - The example requires a working internet connection to render the - \l{Qt Homepage} and a touch-enabled input device to trigger touch - events. - An optional system proxy should be picked up automatically. -*/ diff --git a/examples/webenginequick/customtouchhandle/main.cpp b/examples/webenginequick/customtouchhandle/main.cpp deleted file mode 100644 index f1b70b024..000000000 --- a/examples/webenginequick/customtouchhandle/main.cpp +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (C) 2022 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -#include <QGuiApplication> -#include <QQmlApplicationEngine> -#include <QtWebEngineQuick/qtwebenginequickglobal.h> - -int main(int argc, char *argv[]) -{ - QCoreApplication::setOrganizationName("QtExamples"); - QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts); - QtWebEngineQuick::initialize(); - QGuiApplication app(argc, argv); - - QQmlApplicationEngine engine; - engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); - - return app.exec(); -} diff --git a/examples/webenginequick/customtouchhandle/main.qml b/examples/webenginequick/customtouchhandle/main.qml deleted file mode 100644 index c40b4c73b..000000000 --- a/examples/webenginequick/customtouchhandle/main.qml +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright (C) 2022 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -import QtQuick -import QtQuick.Window -import QtWebEngine -import QtQuick.Layouts -import QtQuick.Controls - -ApplicationWindow { - width: 1024 - height: 750 - visible: true - header: ToolBar { - RowLayout { - anchors.fill: parent - - ToolButton { - property int itemAction: WebEngineView.Back - text: webEngineView.action(itemAction).text - enabled: webEngineView.action(itemAction).enabled - onClicked: webEngineView.action(itemAction).trigger() - icon.name: webEngineView.action(itemAction).iconName - display: AbstractButton.TextUnderIcon - } - - ToolButton { - property int itemAction: WebEngineView.Forward - text: webEngineView.action(itemAction).text - enabled: webEngineView.action(itemAction).enabled - onClicked: webEngineView.action(itemAction).trigger() - icon.name: webEngineView.action(itemAction).iconName - display: AbstractButton.TextUnderIcon - } - - ToolButton { - property int itemAction: webEngineView.loading ? WebEngineView.Stop : WebEngineView.Reload - text: webEngineView.action(itemAction).text - enabled: webEngineView.action(itemAction).enabled - onClicked: webEngineView.action(itemAction).trigger() - icon.name: webEngineView.action(itemAction).iconName - display: AbstractButton.TextUnderIcon - } - - TextField { - Layout.fillWidth: true - text: webEngineView.url - selectByMouse: true - onEditingFinished: webEngineView.url = text - } - - Label { text: 'Handle: ' } - ComboBox { - model: [ 'Default', 'Circle', 'Square' ] - - onCurrentValueChanged: { - if (currentValue == 'Circle') - webEngineView.touchHandleDelegate = circleTouchHandle - else if (currentValue == 'Square') - webEngineView.touchHandleDelegate = rectTouchHandle - else - webEngineView.touchHandleDelegate = null - } - - Component.onCompleted: currentIndex = indexOfValue('Square') - } - } - } - - Component { - id: circleTouchHandle - Rectangle { - color: "blue" - border.color: "black" - border.width: 2 - radius: 50 - } - } - - Component { - id: rectTouchHandle - Rectangle { - border.color: "black" - border.width: 2 - radius: 2 - onVisibleChanged: if (visible) { color = 'yellow'; cAnim.restart(); } - ColorAnimation on color { id: cAnim; to: 'red'; duration: 1000 } - } - } - - WebEngineView { - anchors.fill: parent - id: webEngineView - url: "https://www.qt.io" - } -} diff --git a/examples/webenginequick/lifecycle/CMakeLists.txt b/examples/webenginequick/lifecycle/CMakeLists.txt index 556b50706..d0a378c13 100644 --- a/examples/webenginequick/lifecycle/CMakeLists.txt +++ b/examples/webenginequick/lifecycle/CMakeLists.txt @@ -1,3 +1,6 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + cmake_minimum_required(VERSION 3.16) project(lifecycle LANGUAGES CXX) @@ -13,6 +16,7 @@ find_package(Qt6 REQUIRED COMPONENTS Core Gui QuickControls2 WebEngineQuick) qt_add_executable(lifecycle main.cpp + utils.h ) set_target_properties(lifecycle PROPERTIES diff --git a/examples/webenginequick/lifecycle/WebTab.qml b/examples/webenginequick/lifecycle/WebTab.qml index d805628ad..6fb6cb386 100644 --- a/examples/webenginequick/lifecycle/WebTab.qml +++ b/examples/webenginequick/lifecycle/WebTab.qml @@ -132,7 +132,7 @@ ColumnLayout { text: view.url == "about:blank" ? "" : view.url selectByMouse: true - onAccepted: { view.url = text } + onAccepted: { view.url = utils.fromUserInput(text) } } WebToolButton { text: "â‹®" diff --git a/examples/webenginequick/lifecycle/doc/src/lifecycle.qdoc b/examples/webenginequick/lifecycle/doc/src/lifecycle.qdoc index b28e3e272..4d997b1c4 100644 --- a/examples/webenginequick/lifecycle/doc/src/lifecycle.qdoc +++ b/examples/webenginequick/lifecycle/doc/src/lifecycle.qdoc @@ -31,6 +31,9 @@ window also has a \l {Drawer} for changing settings. The drawer can be opened by clicking the "â‹®" button on the tool bar. + \note Note that \c {WebTab.qml} uses \l {QUrl::} + {fromUserInput} to handle incomplete URLs. + \section1 Lifecycle States in the Example The example implements two ways of changing the lifecycle state: manual and diff --git a/examples/webenginequick/lifecycle/lifecycle.pro b/examples/webenginequick/lifecycle/lifecycle.pro index ccbe801f3..044d025d7 100644 --- a/examples/webenginequick/lifecycle/lifecycle.pro +++ b/examples/webenginequick/lifecycle/lifecycle.pro @@ -2,6 +2,7 @@ TEMPLATE = app QT += quickcontrols2 webenginequick +HEADERS += utils.h SOURCES += main.cpp RESOURCES += resources.qrc diff --git a/examples/webenginequick/lifecycle/main.cpp b/examples/webenginequick/lifecycle/main.cpp index 3601bdf5c..1f45ad0ee 100644 --- a/examples/webenginequick/lifecycle/main.cpp +++ b/examples/webenginequick/lifecycle/main.cpp @@ -1,6 +1,8 @@ -// Copyright (C) 2019 The Qt Company Ltd. +// Copyright (C) 2022 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause +#include "utils.h" + #include <QGuiApplication> #include <QQmlApplicationEngine> #include <QQmlContext> @@ -12,6 +14,8 @@ int main(int argc, char *argv[]) QtWebEngineQuick::initialize(); QGuiApplication app(argc, argv); QQmlApplicationEngine engine; + Utils utils; + engine.rootContext()->setContextProperty("utils", &utils); engine.load(QUrl(QStringLiteral("qrc:/WebBrowser.qml"))); return app.exec(); } diff --git a/examples/webenginequick/lifecycle/utils.h b/examples/webenginequick/lifecycle/utils.h new file mode 100644 index 000000000..d9a803907 --- /dev/null +++ b/examples/webenginequick/lifecycle/utils.h @@ -0,0 +1,25 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef UTILS_H +#define UTILS_H + +#include <QtCore/QFileInfo> +#include <QtCore/QUrl> + +class Utils : public QObject +{ + Q_OBJECT +public: + Q_INVOKABLE static QUrl fromUserInput(const QString &userInput); +}; + +inline QUrl Utils::fromUserInput(const QString &userInput) +{ + QFileInfo fileInfo(userInput); + if (fileInfo.exists()) + return QUrl::fromLocalFile(fileInfo.absoluteFilePath()); + return QUrl::fromUserInput(userInput); +} + +#endif // UTILS_H diff --git a/examples/webenginequick/minimal/CMakeLists.txt b/examples/webenginequick/minimal/CMakeLists.txt deleted file mode 100644 index e0cb66733..000000000 --- a/examples/webenginequick/minimal/CMakeLists.txt +++ /dev/null @@ -1,45 +0,0 @@ -cmake_minimum_required(VERSION 3.16) -project(minimal LANGUAGES CXX) - -set(CMAKE_AUTOMOC ON) - -if(NOT DEFINED INSTALL_EXAMPLESDIR) - set(INSTALL_EXAMPLESDIR "examples") -endif() - -set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/webenginequick/minimal-qml") - -find_package(Qt6 REQUIRED COMPONENTS Core Gui WebEngineQuick) - -qt_add_executable(webengine-minimal-qml - main.cpp -) - -set_target_properties(webengine-minimal-qml PROPERTIES - WIN32_EXECUTABLE TRUE - MACOSX_BUNDLE TRUE -) - -target_link_libraries(webengine-minimal-qml PUBLIC - Qt::Core - Qt::Gui - Qt::WebEngineQuick -) - -# Resources: -set(qml_resource_files - "main.qml" -) - -qt_add_resources(webengine-minimal-qml "qml" - PREFIX - "/" - FILES - ${qml_resource_files} -) - -install(TARGETS webengine-minimal-qml - RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" - BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" - LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" -) diff --git a/examples/webenginequick/minimal/doc/src/minimal.qdoc b/examples/webenginequick/minimal/doc/src/minimal.qdoc deleted file mode 100644 index aed74a7f5..000000000 --- a/examples/webenginequick/minimal/doc/src/minimal.qdoc +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only - -/*! - \example webenginequick/minimal - \title WebEngine Qt Quick Minimal Example - \ingroup webengine-examples - \brief Displays a web page using the Qt Quick integration of \QWE. - - \image minimal-example.png - - \e {WebEngine Qt Quick Minimal Example} demonstrates how to use the - \l{WebEngineView} item to render a web page. It shows the minimum amount of - code needed to load and display an HTML page, and can be used as a basis for - further experimentation. - - \include examples-run.qdocinc - - \section1 C++ Code - - In \c main.cpp we use only the QGuiApplication and QQmlApplicationEngine - classes. We also include \c qtwebengineglobal.h to be able to use - \l{QtWebEngineQuick::initialize}. - - \quotefromfile webenginequick/minimal/main.cpp - \skipto #include - \printto main - - In the \c main function we first set the - \l{QCoreApplication::organizationName} property. This affects the locations - where \QWE stores persistent and cached data (see also - \l{WebEngineProfile::cachePath} and - \l{WebEngineProfile::persistentStoragePath}). - - Next, we call \l{QtWebEngineQuick::initialize}, which makes sure that OpenGL - contexts can be shared between the main process and the dedicated renderer - process (\c QtWebEngineProcess). This method needs to be called before - any OpenGL context is created. - - Then we create a QQmlApplicationEngine, and tell it to load \c main.qml - from the \l{The Qt Resource System}{Qt Resource System}. - - Finally, QGuiApplication::exec() launches the main event loop. - - \printuntil } - - \section1 QML Code - - In \c main.qml we create the top level window, set a sensible default size - and make it visible. The window will be filled by a WebEngineView item - loading the \l{Qt Homepage}. - - \quotefromfile webenginequick/minimal/main.qml - \skipto import - \printuntil } - \printline } - - \section1 Requirements - - The example requires a working internet connection to render the - \l{Qt Homepage}. - An optional system proxy should be picked up automatically. -*/ diff --git a/examples/webenginequick/minimal/main.cpp b/examples/webenginequick/minimal/main.cpp deleted file mode 100644 index 47b3e146a..000000000 --- a/examples/webenginequick/minimal/main.cpp +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -#include <QGuiApplication> -#include <QQmlApplicationEngine> -#include <QtWebEngineQuick/qtwebenginequickglobal.h> - -int main(int argc, char *argv[]) -{ - QCoreApplication::setOrganizationName("QtExamples"); - QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts); - QtWebEngineQuick::initialize(); - QGuiApplication app(argc, argv); - - QQmlApplicationEngine engine; - engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); - - return app.exec(); -} - diff --git a/examples/webenginequick/minimal/main.qml b/examples/webenginequick/minimal/main.qml deleted file mode 100644 index a8733a8c8..000000000 --- a/examples/webenginequick/minimal/main.qml +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -import QtQuick -import QtQuick.Window -import QtWebEngine - -Window { - width: 1024 - height: 750 - visible: true - WebEngineView { - anchors.fill: parent - url: "https://www.qt.io" - } -} diff --git a/examples/webenginequick/minimal/minimal.pro b/examples/webenginequick/minimal/minimal.pro deleted file mode 100644 index acca6477c..000000000 --- a/examples/webenginequick/minimal/minimal.pro +++ /dev/null @@ -1,10 +0,0 @@ -TEMPLATE = app - -QT += webenginequick - -SOURCES += main.cpp - -RESOURCES += qml.qrc - -target.path = $$[QT_INSTALL_EXAMPLES]/webenginequick/minimal -INSTALLS += target diff --git a/examples/webenginequick/minimal/qml.qrc b/examples/webenginequick/minimal/qml.qrc deleted file mode 100644 index 0ff3892d9..000000000 --- a/examples/webenginequick/minimal/qml.qrc +++ /dev/null @@ -1,6 +0,0 @@ -<RCC> - <qresource prefix="/"> - <file>main.qml</file> - </qresource> -</RCC> - diff --git a/examples/webenginequick/quicknanobrowser/BrowserWindow.qml b/examples/webenginequick/quicknanobrowser/BrowserWindow.qml index ef4af5d4f..9c257de3c 100644 --- a/examples/webenginequick/quicknanobrowser/BrowserWindow.qml +++ b/examples/webenginequick/quicknanobrowser/BrowserWindow.qml @@ -4,7 +4,7 @@ import Qt.labs.settings import QtQml import QtQuick -import QtQuick.Controls +import QtQuick.Controls.Fusion import QtQuick.Layouts import QtQuick.Window import QtWebEngine @@ -471,7 +471,6 @@ ApplicationWindow { } function removeView(index) { - tabBar.removeItem(index); if (tabBar.count > 1) { tabBar.removeItem(tabBar.itemAt(index)); tabLayout.children[index].destroy(); @@ -559,13 +558,6 @@ ApplicationWindow { request.accept(); } - onQuotaRequested: function(request) { - if (request.requestedSize <= 5 * 1024 * 1024) - request.accept(); - else - request.reject(); - } - onRegisterProtocolHandlerRequested: function(request) { console.log("accepting registerProtocolHandler request for " + request.scheme + " from " + request.origin); @@ -610,6 +602,12 @@ ApplicationWindow { findBar.reset(); } + onFeaturePermissionRequested: function(securityOrigin, feature) { + featurePermissionDialog.securityOrigin = securityOrigin; + featurePermissionDialog.feature = feature; + featurePermissionDialog.visible = true; + } + Timer { id: reloadTimer interval: 0 @@ -648,22 +646,21 @@ ApplicationWindow { Dialog { id: sslDialog anchors.centerIn: parent - contentWidth: Math.max(mainText.width, detailedText.width) - contentHeight: mainText.height + detailedText.height + contentWidth: Math.max(mainTextForSSLDialog.width, detailedTextForSSLDialog.width) + contentHeight: mainTextForSSLDialog.height + detailedTextForSSLDialog.height property var certErrors: [] // fixme: icon! // icon: StandardIcon.Warning standardButtons: Dialog.No | Dialog.Yes title: "Server's certificate not trusted" contentItem: Item { - id: textContentItem Label { - id: mainText + id: mainTextForSSLDialog text: "Do you wish to continue?" } Text { - id: detailedText - anchors.top: mainText.bottom + id: detailedTextForSSLDialog + anchors.top: mainTextForSSLDialog.bottom text: "If you wish so, you may continue with an unverified certificate.\n" + "Accepting an unverified certificate means\n" + "you may not be connected with the host you tried to connect to.\n" + @@ -689,6 +686,68 @@ ApplicationWindow { visible = certErrors.length > 0 } } + Dialog { + id: featurePermissionDialog + anchors.centerIn: parent + width: Math.min(browserWindow.width, browserWindow.height) / 3 * 2 + contentWidth: mainTextForPermissionDialog.width + contentHeight: mainTextForPermissionDialog.height + standardButtons: Dialog.No | Dialog.Yes + title: "Permission Request" + + property var feature; + property url securityOrigin; + + contentItem: Item { + Label { + id: mainTextForPermissionDialog + text: featurePermissionDialog.questionForFeature() + } + } + + onAccepted: currentWebView && currentWebView.grantFeaturePermission(securityOrigin, feature, true) + onRejected: currentWebView && currentWebView.grantFeaturePermission(securityOrigin, feature, false) + onVisibleChanged: { + if (visible) + width = contentWidth + 20; + } + + function questionForFeature() { + var question = "Allow " + securityOrigin + " to " + + switch (feature) { + case WebEngineView.Geolocation: + question += "access your location information?"; + break; + case WebEngineView.MediaAudioCapture: + question += "access your microphone?"; + break; + case WebEngineView.MediaVideoCapture: + question += "access your webcam?"; + break; + case WebEngineView.MediaVideoCapture: + question += "access your microphone and webcam?"; + break; + case WebEngineView.MouseLock: + question += "lock your mouse cursor?"; + break; + case WebEngineView.DesktopVideoCapture: + question += "capture video of your desktop?"; + break; + case WebEngineView.DesktopAudioVideoCapture: + question += "capture audio and video of your desktop?"; + break; + case WebEngineView.Notifications: + question += "show notification on your desktop?"; + break; + default: + question += "access unknown or unsupported feature [" + feature + "] ?"; + break; + } + + return question; + } + } FullScreenNotification { id: fullScreenNotification diff --git a/examples/webenginequick/quicknanobrowser/CMakeLists.txt b/examples/webenginequick/quicknanobrowser/CMakeLists.txt index a12570225..ac41569aa 100644 --- a/examples/webenginequick/quicknanobrowser/CMakeLists.txt +++ b/examples/webenginequick/quicknanobrowser/CMakeLists.txt @@ -1,3 +1,6 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + cmake_minimum_required(VERSION 3.16) project(quicknanobrowser LANGUAGES CXX) @@ -32,6 +35,7 @@ target_link_libraries(quicknanobrowser PUBLIC qt_add_qml_module(quicknanobrowser URI BrowserUtils VERSION 1.0 + RESOURCE_PREFIX / ) # Resources: diff --git a/examples/webenginequick/quicknanobrowser/DownloadView.qml b/examples/webenginequick/quicknanobrowser/DownloadView.qml index e16647cdb..421b4f55c 100644 --- a/examples/webenginequick/quicknanobrowser/DownloadView.qml +++ b/examples/webenginequick/quicknanobrowser/DownloadView.qml @@ -2,7 +2,7 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause import QtQuick -import QtQuick.Controls +import QtQuick.Controls.Fusion import QtWebEngine import QtQuick.Layouts diff --git a/examples/webenginequick/quicknanobrowser/FindBar.qml b/examples/webenginequick/quicknanobrowser/FindBar.qml index 4d130a22b..409d8dcff 100644 --- a/examples/webenginequick/quicknanobrowser/FindBar.qml +++ b/examples/webenginequick/quicknanobrowser/FindBar.qml @@ -2,7 +2,7 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause import QtQuick -import QtQuick.Controls +import QtQuick.Controls.Fusion import QtQuick.Layouts Rectangle { @@ -51,6 +51,7 @@ Rectangle { TextField { id: findTextField anchors.fill: parent + color: "black" background: Rectangle { color: "transparent" } @@ -64,6 +65,7 @@ Rectangle { Label { text: activeMatch + "/" + numberOfMatches visible: findTextField.text != "" + color: "black" } Rectangle { @@ -79,17 +81,29 @@ Rectangle { text: "<" enabled: numberOfMatches > 0 onClicked: root.findPrevious() + contentItem: Text { + color: "black" + text: parent.text + } } ToolButton { text: ">" enabled: numberOfMatches > 0 onClicked: root.findNext() + contentItem: Text { + color: "black" + text: parent.text + } } ToolButton { text: "x" onClicked: root.visible = false + contentItem: Text { + color: "black" + text: parent.text + } } } } diff --git a/examples/webenginequick/quicknanobrowser/doc/src/quicknanobrowser.qdoc b/examples/webenginequick/quicknanobrowser/doc/src/quicknanobrowser.qdoc index b1910df95..ee94e0fd7 100644 --- a/examples/webenginequick/quicknanobrowser/doc/src/quicknanobrowser.qdoc +++ b/examples/webenginequick/quicknanobrowser/doc/src/quicknanobrowser.qdoc @@ -91,6 +91,31 @@ \skipto Dialog { \printuntil /^\ {4}\}/ + \section1 Handling Feature Permission Requests + + We use the \c onFeaturePermissionRequested() signal handler to handle requests for + accessing a certain feature or device. The \c securityOrigin parameter identifies the + requester web site, and the \c feature parameter is the requested feature. We use these + to construct the message of the dialog: + + \quotefromfile webenginequick/quicknanobrowser/BrowserWindow.qml + \skipto onFeaturePermissionRequested + \printuntil } + + We show a dialog where the user is asked to grant or deny access. The custom + \c questionForFeature() JavaScript function generates a human-readable question about + the request. + If users select \uicontrol Yes, we call the \l{WebEngineView::}{grantFeaturePermission()} + method with a third \c true parameter to grant the \c securityOrigin web site the permission + to access the \c feature. + If users select \uicontrol No, we call the same method but with the \c false parameter to + deny access: + + \skipto id: sslDialog + \skipto Dialog { + \printuntil /^\ {4}\}/ + + \section1 Entering and Leaving Fullscreen Mode We create a menu item for allowing fullscreen mode in a settings menu that we place on the tool diff --git a/examples/webenginequick/quicknanobrowser/main.cpp b/examples/webenginequick/quicknanobrowser/main.cpp index 519cfd089..850b8c443 100644 --- a/examples/webenginequick/quicknanobrowser/main.cpp +++ b/examples/webenginequick/quicknanobrowser/main.cpp @@ -12,6 +12,7 @@ #include <QtCore/QCommandLineParser> #include <QtCore/QCommandLineOption> +#include <QtCore/QLoggingCategory> static QUrl startupUrl(const QCommandLineParser &parser) { @@ -31,6 +32,7 @@ int main(int argc, char **argv) QtWebEngineQuick::initialize(); QGuiApplication app(argc, argv); + QLoggingCategory::setFilterRules(QStringLiteral("qt.webenginecontext.debug=true")); QCommandLineParser parser; parser.addHelpOption(); diff --git a/examples/webenginequick/recipebrowser/CMakeLists.txt b/examples/webenginequick/recipebrowser/CMakeLists.txt deleted file mode 100644 index 57a73b4aa..000000000 --- a/examples/webenginequick/recipebrowser/CMakeLists.txt +++ /dev/null @@ -1,155 +0,0 @@ -cmake_minimum_required(VERSION 3.16) -project(recipebrowser LANGUAGES CXX) - -set(CMAKE_AUTOMOC ON) - -if(NOT DEFINED INSTALL_EXAMPLESDIR) - set(INSTALL_EXAMPLESDIR "examples") -endif() - -set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/webenginequick/recipebrowser") - -find_package(Qt6 REQUIRED COMPONENTS Core Gui Qml Quick QuickControls2 WebEngineQuick) - -qt_add_executable(recipebrowser - main.cpp -) - -set_target_properties(recipebrowser PROPERTIES - WIN32_EXECUTABLE TRUE - MACOSX_BUNDLE TRUE -) - -target_link_libraries(recipebrowser PUBLIC - Qt::Core - Qt::Gui - Qt::Qml - Qt::Quick - Qt::QuickControls2 - Qt::WebEngineQuick -) - -# Resources: -set_source_files_properties("resources/pages/assets/3rdparty/markdown.css" - PROPERTIES QT_SKIP_QUICKCOMPILER 1 -) - -set_source_files_properties("resources/pages/assets/3rdparty/marked.js" - PROPERTIES QT_SKIP_QUICKCOMPILER 1 -) - -set_source_files_properties("resources/pages/assets/custom.css" - PROPERTIES QT_SKIP_QUICKCOMPILER 1 -) - -set_source_files_properties("resources/pages/assets/custom.js" - PROPERTIES QT_SKIP_QUICKCOMPILER 1 -) - -set_source_files_properties("resources/pages/burger.html" - PROPERTIES QT_SKIP_QUICKCOMPILER 1 -) - -set_source_files_properties("resources/pages/cupcakes.html" - PROPERTIES QT_SKIP_QUICKCOMPILER 1 -) - -set_source_files_properties("resources/pages/images/burger.jpg" - PROPERTIES QT_SKIP_QUICKCOMPILER 1 -) - -set_source_files_properties("resources/pages/images/cupcakes.jpg" - PROPERTIES QT_SKIP_QUICKCOMPILER 1 -) - -set_source_files_properties("resources/pages/images/pasta.jpg" - PROPERTIES QT_SKIP_QUICKCOMPILER 1 -) - -set_source_files_properties("resources/pages/images/pizza.jpg" - PROPERTIES QT_SKIP_QUICKCOMPILER 1 -) - -set_source_files_properties("resources/pages/images/skewers.jpg" - PROPERTIES QT_SKIP_QUICKCOMPILER 1 -) - -set_source_files_properties("resources/pages/images/soup.jpg" - PROPERTIES QT_SKIP_QUICKCOMPILER 1 -) - -set_source_files_properties("resources/pages/images/steak.jpg" - PROPERTIES QT_SKIP_QUICKCOMPILER 1 -) - -set_source_files_properties("resources/pages/pasta.html" - PROPERTIES QT_SKIP_QUICKCOMPILER 1 -) - -set_source_files_properties("resources/pages/pizza.html" - PROPERTIES QT_SKIP_QUICKCOMPILER 1 -) - -set_source_files_properties("resources/pages/skewers.html" - PROPERTIES QT_SKIP_QUICKCOMPILER 1 -) - -set_source_files_properties("resources/pages/soup.html" - PROPERTIES QT_SKIP_QUICKCOMPILER 1 -) - -set_source_files_properties("resources/pages/steak.html" - PROPERTIES QT_SKIP_QUICKCOMPILER 1 -) - -set_source_files_properties("resources/qml/RecipeList.qml" - PROPERTIES QT_SKIP_QUICKCOMPILER 1 -) - -set_source_files_properties("resources/qml/main.qml" - PROPERTIES QT_SKIP_QUICKCOMPILER 1 -) - -set(resources_resource_files - "resources/pages/assets/3rdparty/markdown.css" - "resources/pages/assets/3rdparty/marked.js" - "resources/pages/assets/custom.css" - "resources/pages/assets/custom.js" - "resources/pages/burger.html" - "resources/pages/cupcakes.html" - "resources/pages/images/burger.jpg" - "resources/pages/images/cupcakes.jpg" - "resources/pages/images/pasta.jpg" - "resources/pages/images/pizza.jpg" - "resources/pages/images/skewers.jpg" - "resources/pages/images/soup.jpg" - "resources/pages/images/steak.jpg" - "resources/pages/pasta.html" - "resources/pages/pizza.html" - "resources/pages/skewers.html" - "resources/pages/soup.html" - "resources/pages/steak.html" - "resources/qml/main.qml" - "resources/qml/RecipeList.qml" -) - -qt_add_resources(recipebrowser "resources" - PREFIX - "/" - BASE - "resources" - FILES - ${resources_resource_files} -) - -if(CMAKE_CROSSCOMPILING AND (LINUX OR QNX OR posix)) - target_compile_definitions(recipebrowser PUBLIC - QTWEBENGINE_RECIPE_BROWSER_EMBEDDED - ) -endif() - -install(TARGETS recipebrowser - RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" - BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" - LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" -) diff --git a/examples/webenginequick/recipebrowser/doc/images/recipebrowser-demo.jpg b/examples/webenginequick/recipebrowser/doc/images/recipebrowser-demo.jpg Binary files differdeleted file mode 100644 index 761ad3576..000000000 --- a/examples/webenginequick/recipebrowser/doc/images/recipebrowser-demo.jpg +++ /dev/null diff --git a/examples/webenginequick/recipebrowser/doc/src/recipebrowser.qdoc b/examples/webenginequick/recipebrowser/doc/src/recipebrowser.qdoc deleted file mode 100644 index d2de5780c..000000000 --- a/examples/webenginequick/recipebrowser/doc/src/recipebrowser.qdoc +++ /dev/null @@ -1,209 +0,0 @@ -// Copyright (C) 2017 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only - -/*! - \example webenginequick/recipebrowser - \title WebEngine Recipe Browser - \ingroup webengine-examples - \brief A small hybrid application based on the WebEngineView QML type and Qt Quick Controls 2. - - \image recipebrowser-demo.jpg - - \e {Recipe Browser} demonstrates how to use the \l{WebEngineView} item, \l{Qt Quick} items, and - \l{Qt Quick Controls 2} items to develop a small hybrid web browser application. - A \l{ListView}-based item is used to display a list of recipe names. Clicking on - a name causes the web view to load the respective recipe page. The overall appearance - of the application is provided by the \l{Qt Quick Controls 2} items, which have their active - style set to the \l{Material Style}{Material} style. The web content is a mix of HTML and - Markdown source compiled to HTML, along with CSS and JavaScript. - - \include examples-run.qdocinc - - \section1 C++ Code - - In \c main.cpp, we use the \l{QGuiApplication} and \l{QQmlApplicationEngine} - classes to set up and load the main QML file. We call \l{QtWebEngineQuick::initialize} so we can use - \l{WebEngineView} in our QML code. We set the default Qt Quick Controls 2 style - to the Material style, so we do not have to specify it for each new item we add. Finally, we use - a C++ define to check whether the application is compiled for an embedded platform. - The value will be used in the main QML code to determine the window size. - - \quotefromfile webenginequick/recipebrowser/main.cpp - \skipto #include - \printuntil } - - \section1 QML Code - - In the \c main.qml file, we first create a top-level window and set a title for it. We also set - up the size of the window depending on its primary orientation as well as the platform, so that - the application is usable on both desktop and embedded platforms. On desktop, the size - is constrained by a minimum of 320x480 pixels up to the maximum size that the screen supports. - The default window size is 1024 pixels wide and 768 pixels high in landscape orientation. - On embedded devices, the window will occupy the whole screen. - - \quotefromfile webenginequick/recipebrowser/resources/qml/main.qml - \skipto ApplicationWindow - \printuntil minimumHeight - - Next, we add a \l{RowLayout} item so we can divide the window into two parts: one being a - custom \c RecipeList item containing the recipe titles, and the other being the - \l{WebEngineView}, which shows the recipe details. The spacing is set to zero so the items are - positioned directly next to each other. - - \printuntil RecipeList - \dots 16 - \skipuntil webView.showRecipe - \printline } - \printuntil WebEngineView - \dots 16 - \skipuntil busy.running = true - \skipline } - \skipline } - \printline } - \printline } - - The \c RecipeList item has a few \l{Layout}{attached Layout properties}, in order to scale the - item to a maximum of one third of the layout width. We give the item focus, so that the keyboard - can be used to navigate the recipes, in addition to using mouse and touch. We also add a handler - for the custom \c recipeSelected signal, to tell the WebEngineView to load the URL of the - selected recipe. - - \quotefromfile webenginequick/recipebrowser/resources/qml/main.qml - \skipto RecipeList - \printuntil } - - The WebEngineView has similar layout properties, to make it occupy two thirds of the layout - width. - - \skipto WebEngineView - \printuntil Layout.fillHeight - - We then disable the \l{WebEngineSettings::focusOnNavigationEnabled}{focusOnNavigationEnabled} - setting to make sure that the \l{WebEngineView} does not steal focus from the \c RecipeList - item every time its URL is changed. This allows the user to continue navigating through the - recipes using the keyboard. We also disable the default context menu by accepting the - ContextMenuRequest. - - \skipto focusOnNavigationEnabled - \printuntil } - - When the application starts, instead of directly showing the \l{WebEngineView}, we show a - placeholder \l{Rectangle} with a \l{BusyIndicator} to provide a nicer user experience while the - application is loading. - - \printuntil } - \dots 12 - \skipto Rectangle - \printuntil } - - Once the first page in the view is loaded, we start a \l{Timer} that - will hide the placeholder and show the actual page. The delay provides more time for the recipe - images to load, so that when the view is shown, the page is completely rendered. The timer also - shows a help \l{ToolTip} that informs the user on how to navigate the recipes. - - \quotefromfile webenginequick/recipebrowser/resources/qml/main.qml - \skipto Timer { - \printuntil } - - Let's see what the \c RecipeList item looks like from the inside. The root item is a - FocusScope to allow transferring focus to the child ListView whenever the root item receives - focus. We also declare a custom \c recipeSelected signal, which will be emitted when the current - item of the ListView changes. - - \quotefromfile webenginequick/recipebrowser/resources/qml/RecipeList.qml - \skipto FocusScope - \printuntil recipeSelected - - A ColumnLayout holds a header \l{Label} above the ListView, and the ListView itself. - Again, we set the spacing to zero and make sure the layout occupies the whole space of - the parent item. - - \skipto ColumnLayout - \printuntil anchors.fill - - Inside the layout there is a styled \l{ToolBar} item, with a \l{Label} inside of it serving as - the ListView header. - - \skipto ToolBar - \printuntil Label - \printuntil } - \printuntil } - - The second item inside the layout is a \l{ListView}, whose contents will fill the remaining - space in the layout. We set \l{Item::}{clip} to true, so that the delegates that are scrolled - up are not seen under the ToolBar item. We set \l{Item::}{focus} to true, so the ListView gains - focus when the FocusScope does. We add a vertical scroll bar, so the user can scroll through the - recipes if the window size is small. We also specify the recipe model to be used by the - ListView as described later in this topic. - - \skipto ListView - \printuntil model - - We have an \l{ItemDelegate} set as the ListView delegate, which displays the - recipe title. The contentItem is a \l{Text} item, customized with a few properties to adjust the - visual appearance and position of the text. We create a binding to the current delegate's model - URL, so we can access the respective URL outside the delegate itself. We set the - \l{ItemDelegate::}{highlighted} property to \c true whenever the item is the current one in the - ListView to provide visual feedback. And we set the focus on the ListView whenever a delegate - is clicked, so that keyboard navigation works in case the focus was previously in the - WebEngineView. - - \skipto delegate - \printuntil onClicked - \printuntil } - \printuntil } - - A handler is defined for the \c currentItemChanged signal to emit our own \c recipeSelected - signal with the URL that the WebEngineView should load. - - \skipto onCurrentItemChanged - \printuntil } - - We use a \l{ListModel} with seven \l{ListElement}s, each of which contains a recipe - title and the URL to an HTML page contained in a resource file. The model is used to populate - the ListView with the recipes and to show the recipe details in the WebEngineView. - - \skipto ListModel - \printuntil Cupcakes - \printuntil } - \printuntil } - - We use a \l{ToolTip} item that is displayed on application startup to inform the users - how they can navigate and view the details of each recipe. The ToolTip is shown using the - \c showHelp method, which is invoked by the \l{Timer} in the main.qml file. - - \skipto ToolTip - \printuntil help.open() - \printuntil } - \printuntil } - - An example of a recipe page can be seen below. The page uses two stylesheets and - two JavaScript files: - \list - \li \l{https://bitbucket.org/kevinburke/markdowncss/src/master/}{markdown.css} is - a markdown-friendly stylesheet created by Kevin Burke - \li \l{https://github.com/chjj/marked}{marked.min.js} is a markdown parser and - compiler designed for speed written by Christopher Jeffrey - \li custom.css makes some small styling adjustments to the final recipe page - \li custom.js is used to invoke the conversion of the recipe content (which is written in - markdown syntax) into HTML - \endlist - - The images on the pages are loaded from the compiled resource file. - - \quotefromfile webenginequick/recipebrowser/resources/pages/soup.html - \printuntil </html> - - \section1 Files and Attributions - - The example bundles the following code with third-party licenses: - - \table - \row - \li \l{recipebrowser-marked}{Marked} - \li MIT License - \row - \li \l{recipebrowser-markdowncss}{Markdown.css} - \li Apache License 2.0 - \endtable -*/ diff --git a/examples/webenginequick/recipebrowser/main.cpp b/examples/webenginequick/recipebrowser/main.cpp deleted file mode 100644 index 076a3be9a..000000000 --- a/examples/webenginequick/recipebrowser/main.cpp +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -#include <QGuiApplication> -#include <QQmlApplicationEngine> -#include <QQmlContext> -#include <QQuickStyle> -#include <QtWebEngineQuick/qtwebenginequickglobal.h> - -int main(int argc, char *argv[]) -{ - QCoreApplication::setOrganizationName("QtExamples"); - QtWebEngineQuick::initialize(); - - QGuiApplication app(argc, argv); - - QQuickStyle::setStyle(QStringLiteral("Material")); - - QQmlApplicationEngine engine; - - bool isEmbedded = false; -#ifdef QTWEBENGINE_RECIPE_BROWSER_EMBEDDED - isEmbedded = true; -#endif - engine.rootContext()->setContextProperty(QStringLiteral("isEmbedded"), isEmbedded); - - engine.load(QUrl(QStringLiteral("qrc:/qml/main.qml"))); - - return app.exec(); -} diff --git a/examples/webenginequick/recipebrowser/recipebrowser.pro b/examples/webenginequick/recipebrowser/recipebrowser.pro deleted file mode 100644 index e358d00a3..000000000 --- a/examples/webenginequick/recipebrowser/recipebrowser.pro +++ /dev/null @@ -1,21 +0,0 @@ -TEMPLATE = app - -QT += quick qml quickcontrols2 webenginequick - -cross_compile { - posix|qnx|linux: DEFINES += QTWEBENGINE_RECIPE_BROWSER_EMBEDDED -} - -SOURCES += main.cpp - -RESOURCES += resources/resources.qrc - -# Make sure Qt Quick compiler does not remove the source code of the .js files. -QTQUICK_COMPILER_SKIPPED_RESOURCES = resources/resources.qrc - -DISTFILES += \ - resources/pages/assets/3rdparty/MARKDOWN-LICENSE.txt \ - resources/pages/assets/3rdparty/MARKED-LICENSE.txt - -target.path = $$[QT_INSTALL_EXAMPLES]/webenginequick/recipebrowser -INSTALLS += target diff --git a/examples/webenginequick/recipebrowser/resources/pages/assets/3rdparty/qt_attribution.json b/examples/webenginequick/recipebrowser/resources/pages/assets/3rdparty/qt_attribution.json deleted file mode 100644 index 4dafa1acd..000000000 --- a/examples/webenginequick/recipebrowser/resources/pages/assets/3rdparty/qt_attribution.json +++ /dev/null @@ -1,34 +0,0 @@ -[ - { - "Id": "recipebrowser-marked", - "Name": "Marked (WebEngine RecipeBrowser example)", - "QDocModule": "qtwebengine", - "QtUsage": "Marked is used in the WebEngine RecipeBrowser example", - "QtParts": [ "examples" ], - "Files": "marked.js", - "Description": "A full-featured markdown parser and compiler, written in JavaScript. Built for speed.", - "Homepage": "https://github.com/chjj/marked", - "Version": "0.4.0", - "DownloadLocation": "https://github.com/markedjs/marked/blob/0.4.0/lib/marked.js", - "Copyright": "Copyright (c) 2011-2018, Christopher Jeffrey", - "License": "MIT License", - "LicenseId": "MIT", - "LicenseFile": "MARKED-LICENSE.txt" - }, - { - "Id": "recipebrowser-markdowncss", - "Name": "Markdown.css (WebEngine RecipeBrowser example)", - "QDocModule": "qtwebengine", - "QtUsage": "markdown.css is used in the WebEngine RecipeBrowser example", - "QtParts": [ "examples" ], - "Files": "markdown.css", - "Description": "Markdown.css is better default styling for your Markdown files.", - "Version": "188530e4b5d020d7e237fc6b26be13ebf4a8def3", - "DownloadLocation": "https://bitbucket.org/kevinburke/markdowncss/src/188530e4b5d020d7e237fc6b26be13ebf4a8def3/markdown.css", - "Copyright": "Copyright 2011 Kevin Burke - Copyright Twitter Inc.", - "License": "Apache License 2.0", - "LicenseId": "Apache-2.0", - "LicenseFile": "MARKDOWN-LICENSE.txt" - } -] diff --git a/examples/webenginequick/recipebrowser/resources/pages/assets/custom.js b/examples/webenginequick/recipebrowser/resources/pages/assets/custom.js deleted file mode 100644 index 2be2cf1ec..000000000 --- a/examples/webenginequick/recipebrowser/resources/pages/assets/custom.js +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -marked.setOptions({ - renderer: new marked.Renderer(), - gfm: true, - tables: true, - breaks: false, - pedantic: false, - sanitize: false, - smartLists: true, - smartypants: false -}); - -// Poor man document.ready(); -(function() { - var placeholder = document.getElementById('placeholder'); - var content = document.getElementById('content'); - placeholder.innerHTML = marked(content.innerHTML); -})(); diff --git a/examples/webenginequick/recipebrowser/resources/qml/RecipeList.qml b/examples/webenginequick/recipebrowser/resources/qml/RecipeList.qml deleted file mode 100644 index bfaf59112..000000000 --- a/examples/webenginequick/recipebrowser/resources/qml/RecipeList.qml +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -import QtQuick -import QtQuick.Controls -import QtQuick.Controls.Material -import QtQuick.Layouts - -FocusScope { - id: root - signal recipeSelected(url url) - - ColumnLayout { - spacing: 0 - anchors.fill: parent - - ToolBar { - id: headerBackground - Layout.fillWidth: true - implicitHeight: headerText.height + 20 - - Label { - id: headerText - width: parent.width - text: qsTr("Favorite recipes") - padding: 10 - anchors.centerIn: parent - } - } - - ListView { - id: listView - Layout.fillWidth: true - Layout.fillHeight: true - keyNavigationWraps: true - clip: true - focus: true - ScrollBar.vertical: ScrollBar { } - - model: recipeModel - - delegate: ItemDelegate { - width: parent.width - text: model.name - contentItem: Text { - text: parent.text - font: parent.font - color: parent.enabled ? parent.Material.primaryTextColor - : parent.Material.hintTextColor - elide: Text.ElideRight - horizontalAlignment: Text.AlignLeft - verticalAlignment: Text.AlignVCenter - wrapMode: Text.Wrap - } - - property url url: model.url - highlighted: ListView.isCurrentItem - - onClicked: { - listView.forceActiveFocus() - listView.currentIndex = model.index - } - } - - onCurrentItemChanged: { - root.recipeSelected(currentItem.url) - } - - ListModel { - id: recipeModel - - ListElement { - name: "Pizza Diavola" - url: "qrc:///pages/pizza.html" - } - ListElement { - name: "Steak" - url: "qrc:///pages/steak.html" - } - ListElement { - name: "Burger" - url: "qrc:///pages/burger.html" - } - ListElement { - name: "Soup" - url: "qrc:///pages/soup.html" - } - ListElement { - name: "Pasta" - url: "qrc:///pages/pasta.html" - } - ListElement { - name: "Grilled Skewers" - url: "qrc:///pages/skewers.html" - } - ListElement { - name: "Cupcakes" - url: "qrc:///pages/cupcakes.html" - } - } - - ToolTip { - id: help - implicitWidth: root.width - padding * 3 - y: root.y + root.height - delay: 1000 - timeout: 5000 - text: qsTr("Use keyboard, mouse, or touch controls to navigate through the\ - recipes.") - - contentItem: Text { - text: help.text - font: help.font - color: help.Material.primaryTextColor - wrapMode: Text.Wrap - } - } - } - } - - function showHelp() { - help.open() - } -} - diff --git a/examples/webenginequick/recipebrowser/resources/qml/main.qml b/examples/webenginequick/recipebrowser/resources/qml/main.qml deleted file mode 100644 index 7db43c871..000000000 --- a/examples/webenginequick/recipebrowser/resources/qml/main.qml +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -import QtQml -import QtQuick -import QtQuick.Controls -import QtQuick.Controls.Material -import QtQuick.Layouts -import QtQuick.Window -import QtWebEngine - -ApplicationWindow { - id: appWindow - title: qsTr("Recipe Browser") - visible: true - - property int shorterDesktop: 768 - property int longerDesktop: 1024 - property int shorterMin: 360 - property int longerMin: 480 - property bool isPortrait: Screen.primaryOrientation === Qt.PortraitOrientation - width: { - if (isEmbedded) - return Screen.width - var potentialWidth = shorterDesktop - if (!isPortrait) - potentialWidth = longerDesktop - return potentialWidth > Screen.width ? Screen.width : potentialWidth - } - height: { - if (isEmbedded) - return Screen.height - var potentialHeight = longerDesktop - if (!isPortrait) - potentialHeight = shorterDesktop - return potentialHeight > Screen.height ? Screen.height : potentialHeight - } - minimumWidth: isPortrait ? shorterMin : longerMin - minimumHeight: isPortrait ? longerMin : shorterMin - - RowLayout { - id: container - anchors.fill: parent - spacing: 0 - - RecipeList { - id: recipeList - Layout.minimumWidth: 124 - Layout.preferredWidth: parent.width / 3 - Layout.maximumWidth: 300 - Layout.fillWidth: true - Layout.fillHeight: true - focus: true - activeFocusOnTab: true - onRecipeSelected: function(url) { - webView.showRecipe(url) - } - } - - WebEngineView { - id: webView - Layout.preferredWidth: 2 * parent.width / 3 - Layout.fillWidth: true - Layout.fillHeight: true - // Make sure focus is not taken by the web view, so user can continue navigating - // recipes with the keyboard. - settings.focusOnNavigationEnabled: false - - onContextMenuRequested: function(request) { - request.accepted = true - } - - property bool firstLoadComplete: false - onLoadingChanged: function(loadRequest) { - if (loadRequest.status === WebEngineView.LoadSucceededStatus - && !firstLoadComplete) { - // Debounce the showing of the web content, so images are more likely - // to have loaded completely. - showTimer.start() - } - } - - Timer { - id: showTimer - interval: 500 - repeat: false - onTriggered: { - webView.show(true) - webView.firstLoadComplete = true - recipeList.showHelp() - } - } - - Rectangle { - id: webViewPlaceholder - anchors.fill: parent - z: 1 - color: "white" - - BusyIndicator { - id: busy - anchors.centerIn: parent - } - } - - function showRecipe(url) { - webView.url = url - } - - function show(show) { - if (show === true) { - busy.running = false - webViewPlaceholder.visible = false - } else { - webViewPlaceholder.visible = true - busy.running = true - } - } - } - } -} diff --git a/examples/webenginequick/recipebrowser/resources/resources.qrc b/examples/webenginequick/recipebrowser/resources/resources.qrc deleted file mode 100644 index bd13dcfae..000000000 --- a/examples/webenginequick/recipebrowser/resources/resources.qrc +++ /dev/null @@ -1,27 +0,0 @@ -<RCC> - <qresource prefix="/"> - <file>qml/main.qml</file> - <file>qml/RecipeList.qml</file> - - <file>pages/pizza.html</file> - <file>pages/burger.html</file> - <file>pages/steak.html</file> - <file>pages/soup.html</file> - <file>pages/pasta.html</file> - <file>pages/skewers.html</file> - <file>pages/cupcakes.html</file> - - <file>pages/assets/3rdparty/marked.js</file> - <file>pages/assets/3rdparty/markdown.css</file> - <file>pages/assets/custom.css</file> - <file>pages/assets/custom.js</file> - - <file>pages/images/burger.jpg</file> - <file>pages/images/pizza.jpg</file> - <file>pages/images/steak.jpg</file> - <file>pages/images/soup.jpg</file> - <file>pages/images/pasta.jpg</file> - <file>pages/images/skewers.jpg</file> - <file>pages/images/cupcakes.jpg</file> - </qresource> -</RCC> diff --git a/examples/webenginequick/webengineaction/CMakeLists.txt b/examples/webenginequick/webengineaction/CMakeLists.txt deleted file mode 100644 index 1d6ae3fe5..000000000 --- a/examples/webenginequick/webengineaction/CMakeLists.txt +++ /dev/null @@ -1,45 +0,0 @@ -cmake_minimum_required(VERSION 3.16) -project(webengineaction LANGUAGES CXX) - -set(CMAKE_AUTOMOC ON) - -if(NOT DEFINED INSTALL_EXAMPLESDIR) - set(INSTALL_EXAMPLESDIR "examples") -endif() - -set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/webenginequick/webengineaction") - -find_package(Qt6 REQUIRED COMPONENTS Core Gui WebEngineQuick) - -qt_add_executable(webengineaction - main.cpp -) - -set_target_properties(webengineaction PROPERTIES - WIN32_EXECUTABLE TRUE - MACOSX_BUNDLE TRUE -) - -target_link_libraries(webengineaction PUBLIC - Qt::Core - Qt::Gui - Qt::WebEngineQuick -) - -# Resources: -set(qml_resource_files - "main.qml" -) - -qt_add_resources(webengineaction "qml" - PREFIX - "/" - FILES - ${qml_resource_files} -) - -install(TARGETS webengineaction - RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" - BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" - LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" -) diff --git a/examples/webenginequick/webengineaction/doc/images/webengineaction-example.png b/examples/webenginequick/webengineaction/doc/images/webengineaction-example.png Binary files differdeleted file mode 100644 index 2e34bbf63..000000000 --- a/examples/webenginequick/webengineaction/doc/images/webengineaction-example.png +++ /dev/null diff --git a/examples/webenginequick/webengineaction/doc/src/webengineaction.qdoc b/examples/webenginequick/webengineaction/doc/src/webengineaction.qdoc deleted file mode 100644 index 24394ad04..000000000 --- a/examples/webenginequick/webengineaction/doc/src/webengineaction.qdoc +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (C) 2018 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only - -/*! - \example webenginequick/webengineaction - \title WebEngine Action Example - \ingroup webengine-examples - \brief A simple browser implemented using WebEngineActions. - - \image webengineaction-example.png - - \e {WebEngine Action Example} demonstrates how to perform actions on a web page - using the \l{WebEngineAction} type. It shows the minimum amount of code needed - to bind browser functionalities to input elements and build up a custom context - menu. - - \include examples-run.qdocinc - - \section1 Working With Web Engine Actions - - An intended use of \l{WebEngineAction} is building a connection between UI - elements and browser commands. It can be added to menus and toolbars via - assigning its properties to the corresponding ones of the element. - - The \l{ToolButton} relies on the properties provided by a - \l{WebEngineAction}. Clicking the button triggers backwards navigation on the - originating \l{WebEngineView} of the action. - - \quotefromfile webenginequick/webengineaction/main.qml - \skipto ToolButton { - \printuntil } - - The simplest way to create custom context menus is enumerating the required - \l{WebEngineAction} types in a data model and instantiating \l{MenuItem} types - for them, for example using a \l{Repeater}. - - \quotefromfile webenginequick/webengineaction/main.qml - \skipto property Menu contextMenu: Menu { - \printuntil /^ {8}\}/ - - Assigning a \l{WebEngineAction} to multiple UI elements will keep them in sync. - As it can be seen in the picture above, if the browser engine disables a - navigation action, both corresponding menu items will be disabled. -*/ diff --git a/examples/webenginequick/webengineaction/main.cpp b/examples/webenginequick/webengineaction/main.cpp deleted file mode 100644 index e685a715c..000000000 --- a/examples/webenginequick/webengineaction/main.cpp +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (C) 2018 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -#include <QGuiApplication> -#include <QQmlApplicationEngine> -#include <QtWebEngineQuick/qtwebenginequickglobal.h> - -int main(int argc, char *argv[]) -{ - QCoreApplication::setOrganizationName("QtExamples"); - QtWebEngineQuick::initialize(); - - QGuiApplication app(argc, argv); - - QQmlApplicationEngine engine; - engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); - - return app.exec(); -} diff --git a/examples/webenginequick/webengineaction/main.qml b/examples/webenginequick/webengineaction/main.qml deleted file mode 100644 index 149484340..000000000 --- a/examples/webenginequick/webengineaction/main.qml +++ /dev/null @@ -1,119 +0,0 @@ -// Copyright (C) 2018 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -import QtQuick -import QtQuick.Window -import QtWebEngine -import QtQuick.Controls -import QtQuick.Layouts - -ApplicationWindow { - id: window - visible: true - width: 800 - height: 600 - title: qsTr("WebEngineAction Example") - - header: ToolBar { - RowLayout { - anchors.fill: parent - - ToolButton { - property int itemAction: WebEngineView.Back - text: webEngineView.action(itemAction).text - enabled: webEngineView.action(itemAction).enabled - onClicked: webEngineView.action(itemAction).trigger() - icon.name: webEngineView.action(itemAction).iconName - display: AbstractButton.TextUnderIcon - } - - ToolButton { - property int itemAction: WebEngineView.Forward - text: webEngineView.action(itemAction).text - enabled: webEngineView.action(itemAction).enabled - onClicked: webEngineView.action(itemAction).trigger() - icon.name: webEngineView.action(itemAction).iconName - display: AbstractButton.TextUnderIcon - } - - ToolButton { - property int itemAction: webEngineView.loading ? WebEngineView.Stop : WebEngineView.Reload - text: webEngineView.action(itemAction).text - enabled: webEngineView.action(itemAction).enabled - onClicked: webEngineView.action(itemAction).trigger() - icon.name: webEngineView.action(itemAction).iconName - display: AbstractButton.TextUnderIcon - } - - TextField { - Layout.fillWidth: true - - text: webEngineView.url - selectByMouse: true - onEditingFinished: webEngineView.url = text - } - - ToolButton { - id: settingsButton - text: "Settings" - icon.name: "settings-configure" - display: AbstractButton.TextUnderIcon - onClicked: settingsMenu.open() - checked: settingsMenu.visible - - Menu { - id: settingsMenu - y: settingsButton.height - - MenuItem { - id: customContextMenuOption - checkable: true - checked: true - - text: "Custom context menu" - } - } - } - } - } - - WebEngineView { - id: webEngineView - url: "https://qt.io" - anchors.fill: parent - - Component.onCompleted: { - profile.downloadRequested.connect(function(download){ - download.accept(); - }) - } - - property Menu contextMenu: Menu { - Repeater { - model: [ - WebEngineView.Back, - WebEngineView.Forward, - WebEngineView.Reload, - WebEngineView.SavePage, - WebEngineView.Copy, - WebEngineView.Paste, - WebEngineView.Cut - ] - MenuItem { - text: webEngineView.action(modelData).text - enabled: webEngineView.action(modelData).enabled - onClicked: webEngineView.action(modelData).trigger() - icon.name: webEngineView.action(modelData).iconName - display: MenuItem.TextBesideIcon - } - } - } - - onContextMenuRequested: function(request) { - if (customContextMenuOption.checked) { - request.accepted = true; - contextMenu.popup(); - } - } - } -} diff --git a/examples/webenginequick/webengineaction/qml.qrc b/examples/webenginequick/webengineaction/qml.qrc deleted file mode 100644 index 5f6483ac3..000000000 --- a/examples/webenginequick/webengineaction/qml.qrc +++ /dev/null @@ -1,5 +0,0 @@ -<RCC> - <qresource prefix="/"> - <file>main.qml</file> - </qresource> -</RCC> diff --git a/examples/webenginequick/webengineaction/webengineaction.pro b/examples/webenginequick/webengineaction/webengineaction.pro deleted file mode 100644 index 7ef0a8bcf..000000000 --- a/examples/webenginequick/webengineaction/webengineaction.pro +++ /dev/null @@ -1,10 +0,0 @@ -TEMPLATE = app - -QT += webenginequick - -SOURCES += main.cpp - -RESOURCES += qml.qrc - -target.path = $$[QT_INSTALL_EXAMPLES]/webenginequick/webengineaction -INSTALLS += target diff --git a/examples/webenginequick/webenginequick.pro b/examples/webenginequick/webenginequick.pro index edf4315a0..fb44f2c54 100644 --- a/examples/webenginequick/webenginequick.pro +++ b/examples/webenginequick/webenginequick.pro @@ -1,14 +1,9 @@ TEMPLATE=subdirs SUBDIRS += \ - customdialogs \ - customtouchhandle \ - minimal \ - quicknanobrowser \ - webengineaction + quicknanobrowser qtHaveModule(quickcontrols2) { SUBDIRS += \ - lifecycle \ - recipebrowser + lifecycle } diff --git a/examples/webenginewidgets/CMakeLists.txt b/examples/webenginewidgets/CMakeLists.txt index d9b12607f..07ffa9bc4 100644 --- a/examples/webenginewidgets/CMakeLists.txt +++ b/examples/webenginewidgets/CMakeLists.txt @@ -1,16 +1,17 @@ -qt_internal_add_example(minimal) +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + qt_internal_add_example(contentmanipulation) qt_internal_add_example(cookiebrowser) qt_internal_add_example(notifications) qt_internal_add_example(simplebrowser) -qt_internal_add_example(stylesheetbrowser) +qt_internal_add_example(push-notifications) qt_internal_add_example(videoplayer) -qt_internal_add_example(webui) if(QT_FEATURE_webengine_geolocation) qt_internal_add_example(maps) endif() if(QT_FEATURE_webengine_webchannel) - qt_internal_add_example(markdowneditor) + qt_internal_add_example(recipebrowser) endif() if(QT_FEATURE_webengine_printing_and_pdf) qt_internal_add_example(printme) diff --git a/examples/webenginewidgets/contentmanipulation/CMakeLists.txt b/examples/webenginewidgets/contentmanipulation/CMakeLists.txt index b53274507..7edf62044 100644 --- a/examples/webenginewidgets/contentmanipulation/CMakeLists.txt +++ b/examples/webenginewidgets/contentmanipulation/CMakeLists.txt @@ -1,3 +1,6 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + cmake_minimum_required(VERSION 3.16) project(contentmanipulation LANGUAGES CXX) diff --git a/examples/webenginewidgets/cookiebrowser/CMakeLists.txt b/examples/webenginewidgets/cookiebrowser/CMakeLists.txt index c5c6b41d3..e606e848d 100644 --- a/examples/webenginewidgets/cookiebrowser/CMakeLists.txt +++ b/examples/webenginewidgets/cookiebrowser/CMakeLists.txt @@ -1,3 +1,6 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + cmake_minimum_required(VERSION 3.16) project(cookiebrowser LANGUAGES CXX) diff --git a/examples/webenginewidgets/html2pdf/CMakeLists.txt b/examples/webenginewidgets/html2pdf/CMakeLists.txt index 7e3758d1a..034b89102 100644 --- a/examples/webenginewidgets/html2pdf/CMakeLists.txt +++ b/examples/webenginewidgets/html2pdf/CMakeLists.txt @@ -1,3 +1,6 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + cmake_minimum_required(VERSION 3.16) project(html2pdf LANGUAGES CXX) diff --git a/examples/webenginewidgets/maps/CMakeLists.txt b/examples/webenginewidgets/maps/CMakeLists.txt index 62eb04e42..f6e6b94ea 100644 --- a/examples/webenginewidgets/maps/CMakeLists.txt +++ b/examples/webenginewidgets/maps/CMakeLists.txt @@ -1,3 +1,6 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + cmake_minimum_required(VERSION 3.16) project(maps LANGUAGES CXX) diff --git a/examples/webenginewidgets/maps/doc/src/maps.qdoc b/examples/webenginewidgets/maps/doc/src/maps.qdoc index 547572750..ad727e165 100644 --- a/examples/webenginewidgets/maps/doc/src/maps.qdoc +++ b/examples/webenginewidgets/maps/doc/src/maps.qdoc @@ -22,6 +22,12 @@ geolocation requests are denied by default. This example demonstrates the steps an application must take in order to start accepting these requests. + \note On Windows 11, enable settings to grant the application access to + Windows location services. In the Settings App under + \uicontrol {Privacy & Security} > \uicontrol {Location}, enable \uicontrol + {Location services}, \uicontrol {Let apps access your location} and \uicontrol + {Let desktop apps access your location}. + \include examples-run.qdocinc \section1 The Code diff --git a/examples/webenginewidgets/markdowneditor/CMakeLists.txt b/examples/webenginewidgets/markdowneditor/CMakeLists.txt deleted file mode 100644 index c1a0974a0..000000000 --- a/examples/webenginewidgets/markdowneditor/CMakeLists.txt +++ /dev/null @@ -1,55 +0,0 @@ -cmake_minimum_required(VERSION 3.16) -project(markdowneditor LANGUAGES CXX) - -set(CMAKE_AUTOMOC ON) -set(CMAKE_AUTOUIC ON) - -if(NOT DEFINED INSTALL_EXAMPLESDIR) - set(INSTALL_EXAMPLESDIR "examples") -endif() - -set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/webenginewidgets/markdowneditor") - -find_package(Qt6 REQUIRED COMPONENTS Core Gui WebChannel WebEngineWidgets) - -qt_add_executable(markdowneditor - document.cpp document.h - main.cpp - mainwindow.cpp mainwindow.h mainwindow.ui - previewpage.cpp previewpage.h -) - -set_target_properties(markdowneditor PROPERTIES - WIN32_EXECUTABLE TRUE - MACOSX_BUNDLE TRUE -) - -target_link_libraries(markdowneditor PUBLIC - Qt::Core - Qt::Gui - Qt::WebChannel - Qt::WebEngineWidgets -) - -# Resources: -set(markdowneditor_resource_files - "resources/3rdparty/markdown.css" - "resources/3rdparty/marked.js" - "resources/default.md" - "resources/index.html" -) - -qt_add_resources(markdowneditor "markdowneditor" - PREFIX - "/" - BASE - "resources" - FILES - ${markdowneditor_resource_files} -) - -install(TARGETS markdowneditor - RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" - BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" - LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" -) diff --git a/examples/webenginewidgets/markdowneditor/doc/images/markdowneditor-example.png b/examples/webenginewidgets/markdowneditor/doc/images/markdowneditor-example.png Binary files differdeleted file mode 100644 index 9f456c4db..000000000 --- a/examples/webenginewidgets/markdowneditor/doc/images/markdowneditor-example.png +++ /dev/null diff --git a/examples/webenginewidgets/markdowneditor/doc/src/markdowneditor.qdoc b/examples/webenginewidgets/markdowneditor/doc/src/markdowneditor.qdoc deleted file mode 100644 index f8c67fd63..000000000 --- a/examples/webenginewidgets/markdowneditor/doc/src/markdowneditor.qdoc +++ /dev/null @@ -1,159 +0,0 @@ -// Copyright (C) 2017 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only - -/*! - \example webenginewidgets/markdowneditor - \title WebEngine Markdown Editor Example - \ingroup webengine-widgetexamples - \brief Demonstrates how to integrate a web engine in a hybrid desktop - application. - - \image markdowneditor-example.png - - \e {Markdown Editor} demonstrates how to use QWebChannel and JavaScript - libraries to provide a rich text preview tool for a custom markup language. - - \l{http://daringfireball.net/projects/markdown/}{Markdown} is a lightweight - markup language with a plain text formatting syntax. - Some services, such as \l{http://github.com}{github}, acknowledge the - format, and render the content as rich text when viewed in a browser. - - The Markdown Editor main window is split into an editor and a preview area. - The editor supports the Markdown syntax and is implemented by using - QPlainTextEdit. The document is rendered as rich text in the preview area, - which is implemented by using QWebEngineView. To render the text, the - Markdown text is converted to HTML format with the help of a JavaScript - library inside the web engine. The preview is updated from the editor - through QWebChannel. - - \include examples-run.qdocinc - - \section1 Exposing Document Text - - Because we expose the current Markdown text to be rendered to the web engine - through QWebChannel, we need to somehow make the current text available - through the Qt metatype system. This is done by using a dedicated - \c Document class that exposes the document text as a \c{Q_PROPERTY}: - - \quotefromfile webenginewidgets/markdowneditor/document.h - \skipto class Document - \printto #endif - - The \c Document class wraps a QString to be set on the C++ side with - the \c setText() method and exposes it at runtime as a \c text property - with a \c textChanged signal. - - We define the \c setText method as follows: - - \quotefromfile webenginewidgets/markdowneditor/document.cpp - \skipto Document::setText - \printuntil - - \section1 Previewing Text - - We implement our own \c PreviewPage class that publicly inherits from - \c QWebEnginePage: - - \quotefromfile webenginewidgets/markdowneditor/previewpage.h - \skipto class PreviewPage - \printto #endif - - We reimplement the virtual \c acceptNavigationRequest method to - stop the page from navigating away from the current document. Instead, - we redirect external links to the system browser: - - \quotefromfile webenginewidgets/markdowneditor/previewpage.cpp - \skipto acceptNavigationRequest - \printuntil - - \section1 Creating the Main Window - - The \c MainWindow class inherits the QMainWindow class: - - \quotefromfile webenginewidgets/markdowneditor/mainwindow.h - \skipto class MainWindow : - \printto endif - - The class declares private slots that match the actions in the menu, - as well as the \c isModified() helper method. - - The actual layout of the main window is specified in a \c .ui file. - The widgets and actions are available at runtime in the \c ui member - variable. - - \c m_filePath holds the file path to the currently loaded document. - \c m_content is an instance of the \c Document class. - - The actual setup of the different objects is done in the \c MainWindow - constructor: - - \quotefromfile webenginewidgets/markdowneditor/mainwindow.cpp - \skipto MainWindow::MainWindow - \printto PreviewPage - - The constructor first calls \c setupUi to construct the widgets and menu - actions according to the UI file. The text editor font is set to one - with a fixed character width, and the QWebEngineView widget is told not - to show a context menu. - - \printto connect - - Here the constructor makes sure our custom - \c PreviewPage is used by the QWebEngineView instance in \c{ui->preview}. - - \printto ui->preview - - Here the \c textChanged signal of the editor is connected to a lambda that - updates the text in \c m_content. This object is then exposed to the JS side - by \c QWebChannel under the name \c{content}. - - \printto connect - - Now we can actually load the \e index.html file from the - resources. For more information about the file, see - \l{Creating an Index File}. - - \printto defaultTextFile - - The menu items are connected to the corresponding member slots. The - \uicontrol Save item is activated or deactivated depending on whether - the user has edited the content. - - \printuntil } - - Finally, we load a default document \e default.md from the resources. - - \section1 Creating an Index File - - \quotefile webenginewidgets/markdowneditor/resources/index.html - - In the \e index.html, we load a custom stylesheet and two JavaScript - libraries. \l{https://bitbucket.org/kevinburke/markdowncss/src/master/}{markdown.css} is - a markdown-friendly stylesheet created by Kevin Burke. - \l{https://github.com/chjj/marked}{marked.js} is a markdown parser and - compiler designed for speed written by Christopher Jeffrey and - \e qwebchannel.js is part of the \l{QWebChannel} module. - - In the \c <body> element we first define a \c placeholder element, and - make it available as a JavaScript variable. We then define the \c updateText - helper method that updates the content of \c placeholder with the HTML - that the JavaScript method \c marked() returns. - - Finally, we set up the web channel to access the \c content proxy object - and make sure that \c updateText() is called whenever \c content.text - changes. - - \section1 Files and Attributions - - The example bundles the following code with third-party licenses: - - \table - \row - \li \l{markdowneditor-marked}{Marked} - \li MIT License - \row - \li \l{markdowneditor-markdowncss}{Markdown.css} - \li Apache License 2.0 - \endtable -*/ - diff --git a/examples/webenginewidgets/markdowneditor/document.cpp b/examples/webenginewidgets/markdowneditor/document.cpp deleted file mode 100644 index 8ece76509..000000000 --- a/examples/webenginewidgets/markdowneditor/document.cpp +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -#include "document.h" - -void Document::setText(const QString &text) -{ - if (text == m_text) - return; - m_text = text; - emit textChanged(m_text); -} diff --git a/examples/webenginewidgets/markdowneditor/document.h b/examples/webenginewidgets/markdowneditor/document.h deleted file mode 100644 index f4eabbdaa..000000000 --- a/examples/webenginewidgets/markdowneditor/document.h +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -#ifndef DOCUMENT_H -#define DOCUMENT_H - -#include <QObject> -#include <QString> - -class Document : public QObject -{ - Q_OBJECT - Q_PROPERTY(QString text MEMBER m_text NOTIFY textChanged FINAL) -public: - explicit Document(QObject *parent = nullptr) : QObject(parent) {} - - void setText(const QString &text); - -signals: - void textChanged(const QString &text); - -private: - QString m_text; -}; - -#endif // DOCUMENT_H diff --git a/examples/webenginewidgets/markdowneditor/main.cpp b/examples/webenginewidgets/markdowneditor/main.cpp deleted file mode 100644 index 98e76bfba..000000000 --- a/examples/webenginewidgets/markdowneditor/main.cpp +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -#include "document.h" -#include "mainwindow.h" - -#include <QApplication> -#include <QFile> - -int main(int argc, char *argv[]) -{ - QCoreApplication::setOrganizationName("QtExamples"); - QApplication a(argc, argv); - - MainWindow window; - window.show(); - - return a.exec(); -} diff --git a/examples/webenginewidgets/markdowneditor/mainwindow.cpp b/examples/webenginewidgets/markdowneditor/mainwindow.cpp deleted file mode 100644 index a4ef50a31..000000000 --- a/examples/webenginewidgets/markdowneditor/mainwindow.cpp +++ /dev/null @@ -1,147 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -#include "mainwindow.h" -#include "previewpage.h" -#include "ui_mainwindow.h" - -#include <QFile> -#include <QFileDialog> -#include <QFontDatabase> -#include <QMessageBox> -#include <QStatusBar> -#include <QTextStream> -#include <QWebChannel> - -MainWindow::MainWindow(QWidget *parent) : - QMainWindow(parent), - ui(new Ui::MainWindow) -{ - ui->setupUi(this); - ui->editor->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); - ui->preview->setContextMenuPolicy(Qt::NoContextMenu); - - PreviewPage *page = new PreviewPage(this); - ui->preview->setPage(page); - - connect(ui->editor, &QPlainTextEdit::textChanged, - [this]() { m_content.setText(ui->editor->toPlainText()); }); - - QWebChannel *channel = new QWebChannel(this); - channel->registerObject(QStringLiteral("content"), &m_content); - page->setWebChannel(channel); - - ui->preview->setUrl(QUrl("qrc:/index.html")); - - connect(ui->actionNew, &QAction::triggered, this, &MainWindow::onFileNew); - connect(ui->actionOpen, &QAction::triggered, this, &MainWindow::onFileOpen); - connect(ui->actionSave, &QAction::triggered, this, &MainWindow::onFileSave); - connect(ui->actionSaveAs, &QAction::triggered, this, &MainWindow::onFileSaveAs); - connect(ui->actionExit, &QAction::triggered, this, &QWidget::close); - - connect(ui->editor->document(), &QTextDocument::modificationChanged, - ui->actionSave, &QAction::setEnabled); - - QFile defaultTextFile(":/default.md"); - defaultTextFile.open(QIODevice::ReadOnly); - ui->editor->setPlainText(defaultTextFile.readAll()); -} - -MainWindow::~MainWindow() -{ - delete ui; -} - -void MainWindow::openFile(const QString &path) -{ - QFile f(path); - if (!f.open(QIODevice::ReadOnly)) { - QMessageBox::warning(this, windowTitle(), - tr("Could not open file %1: %2").arg( - QDir::toNativeSeparators(path), f.errorString())); - return; - } - m_filePath = path; - ui->editor->setPlainText(f.readAll()); - statusBar()->showMessage(tr("Opened %1").arg(QDir::toNativeSeparators(path))); -} - -bool MainWindow::isModified() const -{ - return ui->editor->document()->isModified(); -} - -void MainWindow::onFileNew() -{ - if (isModified()) { - QMessageBox::StandardButton button = QMessageBox::question(this, windowTitle(), - tr("You have unsaved changes. Do you want to create a new document anyway?")); - if (button != QMessageBox::Yes) - return; - } - - m_filePath.clear(); - ui->editor->setPlainText(tr("## New document")); - ui->editor->document()->setModified(false); -} - -void MainWindow::onFileOpen() -{ - if (isModified()) { - QMessageBox::StandardButton button = QMessageBox::question(this, windowTitle(), - tr("You have unsaved changes. Do you want to open a new document anyway?")); - if (button != QMessageBox::Yes) - return; - } - - QFileDialog dialog(this, tr("Open MarkDown File")); - dialog.setMimeTypeFilters({"text/markdown"}); - dialog.setAcceptMode(QFileDialog::AcceptOpen); - if (dialog.exec() == QDialog::Accepted) - openFile(dialog.selectedFiles().constFirst()); -} - -void MainWindow::onFileSave() -{ - if (m_filePath.isEmpty()) { - onFileSaveAs(); - return; - } - - QFile f(m_filePath); - if (!f.open(QIODevice::WriteOnly | QIODevice::Text)) { - QMessageBox::warning(this, windowTitle(), - tr("Could not write to file %1: %2").arg( - QDir::toNativeSeparators(m_filePath), f.errorString())); - return; - } - QTextStream str(&f); - str << ui->editor->toPlainText(); - - ui->editor->document()->setModified(false); - - statusBar()->showMessage(tr("Wrote %1").arg(QDir::toNativeSeparators(m_filePath))); -} - -void MainWindow::onFileSaveAs() -{ - QFileDialog dialog(this, tr("Save MarkDown File")); - dialog.setMimeTypeFilters({"text/markdown"}); - dialog.setAcceptMode(QFileDialog::AcceptSave); - dialog.setDefaultSuffix("md"); - if (dialog.exec() != QDialog::Accepted) - return; - - m_filePath = dialog.selectedFiles().constFirst(); - onFileSave(); -} - -void MainWindow::closeEvent(QCloseEvent *e) -{ - if (isModified()) { - QMessageBox::StandardButton button = QMessageBox::question(this, windowTitle(), - tr("You have unsaved changes. Do you want to exit anyway?")); - if (button != QMessageBox::Yes) - e->ignore(); - } -} diff --git a/examples/webenginewidgets/markdowneditor/mainwindow.h b/examples/webenginewidgets/markdowneditor/mainwindow.h deleted file mode 100644 index 271664852..000000000 --- a/examples/webenginewidgets/markdowneditor/mainwindow.h +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -#ifndef MAINWINDOW_H -#define MAINWINDOW_H - -#include "document.h" - -#include <QMainWindow> -#include <QString> - -QT_BEGIN_NAMESPACE -namespace Ui { -class MainWindow; -} -QT_END_NAMESPACE - -class MainWindow : public QMainWindow -{ - Q_OBJECT - -public: - explicit MainWindow(QWidget *parent = nullptr); - ~MainWindow(); - - void openFile(const QString &path); - -protected: - void closeEvent(QCloseEvent *e) override; - -private slots: - void onFileNew(); - void onFileOpen(); - void onFileSave(); - void onFileSaveAs(); - -private: - bool isModified() const; - - Ui::MainWindow *ui; - QString m_filePath; - Document m_content; -}; - -#endif // MAINWINDOW_H diff --git a/examples/webenginewidgets/markdowneditor/mainwindow.ui b/examples/webenginewidgets/markdowneditor/mainwindow.ui deleted file mode 100644 index 36ab352b7..000000000 --- a/examples/webenginewidgets/markdowneditor/mainwindow.ui +++ /dev/null @@ -1,115 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<ui version="4.0"> - <class>MainWindow</class> - <widget class="QMainWindow" name="MainWindow"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>800</width> - <height>600</height> - </rect> - </property> - <property name="windowTitle"> - <string>MarkDown Editor</string> - </property> - <widget class="QWidget" name="centralwidget"> - <layout class="QHBoxLayout" name="horizontalLayout"> - <item> - <widget class="QSplitter" name="splitter"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <widget class="QPlainTextEdit" name="editor"/> - <widget class="QWebEngineView" name="preview" native="true"/> - </widget> - </item> - </layout> - </widget> - <widget class="QMenuBar" name="menubar"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>800</width> - <height>26</height> - </rect> - </property> - <widget class="QMenu" name="menu_File"> - <property name="title"> - <string>&File</string> - </property> - <addaction name="actionNew"/> - <addaction name="actionOpen"/> - <addaction name="actionSave"/> - <addaction name="actionSaveAs"/> - <addaction name="separator"/> - <addaction name="actionExit"/> - </widget> - <addaction name="menu_File"/> - </widget> - <widget class="QStatusBar" name="statusbar"/> - <action name="actionOpen"> - <property name="text"> - <string>&Open...</string> - </property> - <property name="toolTip"> - <string>Open document</string> - </property> - <property name="shortcut"> - <string>Ctrl+O</string> - </property> - </action> - <action name="actionSave"> - <property name="text"> - <string>&Save</string> - </property> - <property name="toolTip"> - <string>Save current document</string> - </property> - <property name="shortcut"> - <string>Ctrl+S</string> - </property> - </action> - <action name="actionExit"> - <property name="text"> - <string>E&xit</string> - </property> - <property name="toolTip"> - <string>Exit editor</string> - </property> - <property name="shortcut"> - <string>Ctrl+Q</string> - </property> - </action> - <action name="actionSaveAs"> - <property name="text"> - <string>Save &As...</string> - </property> - <property name="toolTip"> - <string>Save document under different name</string> - </property> - </action> - <action name="actionNew"> - <property name="text"> - <string>&New</string> - </property> - <property name="toolTip"> - <string>Create new document</string> - </property> - <property name="shortcut"> - <string>Ctrl+N</string> - </property> - </action> - </widget> - <customwidgets> - <customwidget> - <class>QWebEngineView</class> - <extends>QWidget</extends> - <header>qwebengineview.h</header> - <container>1</container> - </customwidget> - </customwidgets> - <resources/> - <connections/> -</ui> diff --git a/examples/webenginewidgets/markdowneditor/markdowneditor.pro b/examples/webenginewidgets/markdowneditor/markdowneditor.pro deleted file mode 100644 index 099edf4b5..000000000 --- a/examples/webenginewidgets/markdowneditor/markdowneditor.pro +++ /dev/null @@ -1,32 +0,0 @@ -TEMPLATE = app - -QT += webenginewidgets webchannel - -HEADERS += \ - mainwindow.h \ - previewpage.h \ - document.h - -SOURCES = \ - main.cpp \ - mainwindow.cpp \ - previewpage.cpp \ - document.cpp - -RESOURCES = \ - resources/markdowneditor.qrc - -# Disable Qt Quick compiler because the example doesn't use QML, but more importantly so that -# the source code of the .js files is not removed from the embedded qrc file. -CONFIG -= qtquickcompiler - -FORMS += \ - mainwindow.ui - -DISTFILES += \ - resources/3rdparty/MARKDOWN-LICENSE.txt \ - resources/3rdparty/MARKED-LICENSE.txt - -# install -target.path = $$[QT_INSTALL_EXAMPLES]/webenginewidgets/markdowneditor -INSTALLS += target diff --git a/examples/webenginewidgets/markdowneditor/previewpage.cpp b/examples/webenginewidgets/markdowneditor/previewpage.cpp deleted file mode 100644 index 17249fdb0..000000000 --- a/examples/webenginewidgets/markdowneditor/previewpage.cpp +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -#include "previewpage.h" - -#include <QDesktopServices> - -bool PreviewPage::acceptNavigationRequest(const QUrl &url, - QWebEnginePage::NavigationType /*type*/, - bool /*isMainFrame*/) -{ - // Only allow qrc:/index.html. - if (url.scheme() == QString("qrc")) - return true; - QDesktopServices::openUrl(url); - return false; -} diff --git a/examples/webenginewidgets/markdowneditor/previewpage.h b/examples/webenginewidgets/markdowneditor/previewpage.h deleted file mode 100644 index 4a5d98c7c..000000000 --- a/examples/webenginewidgets/markdowneditor/previewpage.h +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -#ifndef PREVIEWPAGE_H -#define PREVIEWPAGE_H - -#include <QWebEnginePage> - -class PreviewPage : public QWebEnginePage -{ - Q_OBJECT -public: - using QWebEnginePage::QWebEnginePage; - -protected: - bool acceptNavigationRequest(const QUrl &url, NavigationType type, bool isMainFrame) override; -}; - -#endif // PREVIEWPAGE_H diff --git a/examples/webenginewidgets/markdowneditor/resources/3rdparty/MARKDOWN-LICENSE.txt b/examples/webenginewidgets/markdowneditor/resources/3rdparty/MARKDOWN-LICENSE.txt deleted file mode 100644 index 23c52cc43..000000000 --- a/examples/webenginewidgets/markdowneditor/resources/3rdparty/MARKDOWN-LICENSE.txt +++ /dev/null @@ -1,16 +0,0 @@ -Copyright 2011 Kevin Burke unless otherwise noted. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. - -Some content is copyrighted by Twitter, Inc., and also released under an -Apache License; these sections are noted in the source. diff --git a/examples/webenginewidgets/markdowneditor/resources/3rdparty/MARKED-LICENSE.txt b/examples/webenginewidgets/markdowneditor/resources/3rdparty/MARKED-LICENSE.txt deleted file mode 100644 index 8e3ba0e0a..000000000 --- a/examples/webenginewidgets/markdowneditor/resources/3rdparty/MARKED-LICENSE.txt +++ /dev/null @@ -1,19 +0,0 @@ -Copyright (c) 2011-2018, Christopher Jeffrey (https://github.com/chjj/) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/examples/webenginewidgets/markdowneditor/resources/3rdparty/markdown.css b/examples/webenginewidgets/markdowneditor/resources/3rdparty/markdown.css deleted file mode 100644 index 24fc2ffe2..000000000 --- a/examples/webenginewidgets/markdowneditor/resources/3rdparty/markdown.css +++ /dev/null @@ -1,260 +0,0 @@ -body{ - margin: 0 auto; - font-family: Georgia, Palatino, serif; - color: #444444; - line-height: 1; - max-width: 960px; - padding: 30px; -} -h1, h2, h3, h4 { - color: #111111; - font-weight: 400; -} -h1, h2, h3, h4, h5, p { - margin-bottom: 24px; - padding: 0; -} -h1 { - font-size: 48px; -} -h2 { - font-size: 36px; - /* The bottom margin is small. It's designed to be used with gray meta text - * below a post title. */ - margin: 24px 0 6px; -} -h3 { - font-size: 24px; -} -h4 { - font-size: 21px; -} -h5 { - font-size: 18px; -} -a { - color: #0099ff; - margin: 0; - padding: 0; - vertical-align: baseline; -} -a:hover { - text-decoration: none; - color: #ff6600; -} -a:visited { - color: purple; -} -ul, ol { - padding: 0; - margin: 0; -} -li { - line-height: 24px; -} -li ul, li ul { - margin-left: 24px; -} -p, ul, ol { - font-size: 16px; - line-height: 24px; - max-width: 540px; -} -pre { - padding: 0px 24px; - max-width: 800px; - white-space: pre-wrap; -} -code { - font-family: Consolas, Monaco, Andale Mono, monospace; - line-height: 1.5; - font-size: 13px; -} -aside { - display: block; - float: right; - width: 390px; -} -blockquote { - border-left:.5em solid #eee; - padding: 0 2em; - margin-left:0; - max-width: 476px; -} -blockquote cite { - font-size:14px; - line-height:20px; - color:#bfbfbf; -} -blockquote cite:before { - content: '\2014 \00A0'; -} - -blockquote p { - color: #666; - max-width: 460px; -} -hr { - width: 540px; - text-align: left; - margin: 0 auto 0 0; - color: #999; -} - -/* Code below this line is copyright Twitter Inc. */ - -button, -input, -select, -textarea { - font-size: 100%; - margin: 0; - vertical-align: baseline; - *vertical-align: middle; -} -button, input { - line-height: normal; - *overflow: visible; -} -button::-moz-focus-inner, input::-moz-focus-inner { - border: 0; - padding: 0; -} -button, -input[type="button"], -input[type="reset"], -input[type="submit"] { - cursor: pointer; - -webkit-appearance: button; -} -input[type=checkbox], input[type=radio] { - cursor: pointer; -} -/* override default chrome & firefox settings */ -input:not([type="image"]), textarea { - -webkit-box-sizing: content-box; - -moz-box-sizing: content-box; - box-sizing: content-box; -} - -input[type="search"] { - -webkit-appearance: textfield; - -webkit-box-sizing: content-box; - -moz-box-sizing: content-box; - box-sizing: content-box; -} -input[type="search"]::-webkit-search-decoration { - -webkit-appearance: none; -} -label, -input, -select, -textarea { - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - font-size: 13px; - font-weight: normal; - line-height: normal; - margin-bottom: 18px; -} -input[type=checkbox], input[type=radio] { - cursor: pointer; - margin-bottom: 0; -} -input[type=text], -input[type=password], -textarea, -select { - display: inline-block; - width: 210px; - padding: 4px; - font-size: 13px; - font-weight: normal; - line-height: 18px; - height: 18px; - color: #808080; - border: 1px solid #ccc; - -webkit-border-radius: 3px; - -moz-border-radius: 3px; - border-radius: 3px; -} -select, input[type=file] { - height: 27px; - line-height: 27px; -} -textarea { - height: auto; -} - -/* grey out placeholders */ -:-moz-placeholder { - color: #bfbfbf; -} -::-webkit-input-placeholder { - color: #bfbfbf; -} - -input[type=text], -input[type=password], -select, -textarea { - -webkit-transition: border linear 0.2s, box-shadow linear 0.2s; - -moz-transition: border linear 0.2s, box-shadow linear 0.2s; - transition: border linear 0.2s, box-shadow linear 0.2s; - -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1); - -moz-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1); - box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1); -} -input[type=text]:focus, input[type=password]:focus, textarea:focus { - outline: none; - border-color: rgba(82, 168, 236, 0.8); - -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1), 0 0 8px rgba(82, 168, 236, 0.6); - -moz-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1), 0 0 8px rgba(82, 168, 236, 0.6); - box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1), 0 0 8px rgba(82, 168, 236, 0.6); -} - -/* buttons */ -button { - display: inline-block; - padding: 4px 14px; - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - font-size: 13px; - line-height: 18px; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; - -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); - -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); - box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); - background-color: #0064cd; - background-repeat: repeat-x; - background-image: -khtml-gradient(linear, left top, left bottom, from(#049cdb), to(#0064cd)); - background-image: -moz-linear-gradient(top, #049cdb, #0064cd); - background-image: -ms-linear-gradient(top, #049cdb, #0064cd); - background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #049cdb), color-stop(100%, #0064cd)); - background-image: -webkit-linear-gradient(top, #049cdb, #0064cd); - background-image: -o-linear-gradient(top, #049cdb, #0064cd); - background-image: linear-gradient(top, #049cdb, #0064cd); - color: #fff; - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); - border: 1px solid #004b9a; - border-bottom-color: #003f81; - -webkit-transition: 0.1s linear all; - -moz-transition: 0.1s linear all; - transition: 0.1s linear all; - border-color: #0064cd #0064cd #003f81; - border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); -} -button:hover { - color: #fff; - background-position: 0 -15px; - text-decoration: none; -} -button:active { - -webkit-box-shadow: inset 0 3px 7px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05); - -moz-box-shadow: inset 0 3px 7px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05); - box-shadow: inset 0 3px 7px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05); -} -button::-moz-focus-inner { - padding: 0; - border: 0; -} diff --git a/examples/webenginewidgets/markdowneditor/resources/3rdparty/marked.js b/examples/webenginewidgets/markdowneditor/resources/3rdparty/marked.js deleted file mode 100644 index 33c02d9cf..000000000 --- a/examples/webenginewidgets/markdowneditor/resources/3rdparty/marked.js +++ /dev/null @@ -1,1514 +0,0 @@ -/** - * marked - a markdown parser - * Copyright (c) 2011-2014, Christopher Jeffrey. (MIT Licensed) - * https://github.com/markedjs/marked - */ - -;(function(root) { -'use strict'; - -/** - * Block-Level Grammar - */ - -var block = { - newline: /^\n+/, - code: /^( {4}[^\n]+\n*)+/, - fences: noop, - hr: /^ {0,3}((?:- *){3,}|(?:_ *){3,}|(?:\* *){3,})(?:\n+|$)/, - heading: /^ *(#{1,6}) *([^\n]+?) *(?:#+ *)?(?:\n+|$)/, - nptable: noop, - blockquote: /^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/, - list: /^( *)(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?!\1bull )\n*|\s*$)/, - html: '^ {0,3}(?:' // optional indentation - + '<(script|pre|style)[\\s>][\\s\\S]*?(?:</\\1>[^\\n]*\\n+|$)' // (1) - + '|comment[^\\n]*(\\n+|$)' // (2) - + '|<\\?[\\s\\S]*?\\?>\\n*' // (3) - + '|<![A-Z][\\s\\S]*?>\\n*' // (4) - + '|<!\\[CDATA\\[[\\s\\S]*?\\]\\]>\\n*' // (5) - + '|</?(tag)(?: +|\\n|/?>)[\\s\\S]*?(?:\\n{2,}|$)' // (6) - + '|<(?!script|pre|style)([a-z][\\w-]*)(?:attribute)*? */?>(?=\\h*\\n)[\\s\\S]*?(?:\\n{2,}|$)' // (7) open tag - + '|</(?!script|pre|style)[a-z][\\w-]*\\s*>(?=\\h*\\n)[\\s\\S]*?(?:\\n{2,}|$)' // (7) closing tag - + ')', - def: /^ {0,3}\[(label)\]: *\n? *<?([^\s>]+)>?(?:(?: +\n? *| *\n *)(title))? *(?:\n+|$)/, - table: noop, - lheading: /^([^\n]+)\n *(=|-){2,} *(?:\n+|$)/, - paragraph: /^([^\n]+(?:\n(?!hr|heading|lheading| {0,3}>|<\/?(?:tag)(?: +|\n|\/?>)|<(?:script|pre|style|!--))[^\n]+)*)/, - text: /^[^\n]+/ -}; - -block._label = /(?!\s*\])(?:\\[\[\]]|[^\[\]])+/; -block._title = /(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/; -block.def = edit(block.def) - .replace('label', block._label) - .replace('title', block._title) - .getRegex(); - -block.bullet = /(?:[*+-]|\d+\.)/; -block.item = /^( *)(bull) [^\n]*(?:\n(?!\1bull )[^\n]*)*/; -block.item = edit(block.item, 'gm') - .replace(/bull/g, block.bullet) - .getRegex(); - -block.list = edit(block.list) - .replace(/bull/g, block.bullet) - .replace('hr', '\\n+(?=\\1?(?:(?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$))') - .replace('def', '\\n+(?=' + block.def.source + ')') - .getRegex(); - -block._tag = 'address|article|aside|base|basefont|blockquote|body|caption' - + '|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption' - + '|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe' - + '|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option' - + '|p|param|section|source|summary|table|tbody|td|tfoot|th|thead|title|tr' - + '|track|ul'; -block._comment = /<!--(?!-?>)[\s\S]*?-->/; -block.html = edit(block.html, 'i') - .replace('comment', block._comment) - .replace('tag', block._tag) - .replace('attribute', / +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/) - .getRegex(); - -block.paragraph = edit(block.paragraph) - .replace('hr', block.hr) - .replace('heading', block.heading) - .replace('lheading', block.lheading) - .replace('tag', block._tag) // pars can be interrupted by type (6) html blocks - .getRegex(); - -block.blockquote = edit(block.blockquote) - .replace('paragraph', block.paragraph) - .getRegex(); - -/** - * Normal Block Grammar - */ - -block.normal = merge({}, block); - -/** - * GFM Block Grammar - */ - -block.gfm = merge({}, block.normal, { - fences: /^ *(`{3,}|~{3,})[ \.]*(\S+)? *\n([\s\S]*?)\n? *\1 *(?:\n+|$)/, - paragraph: /^/, - heading: /^ *(#{1,6}) +([^\n]+?) *#* *(?:\n+|$)/ -}); - -block.gfm.paragraph = edit(block.paragraph) - .replace('(?!', '(?!' - + block.gfm.fences.source.replace('\\1', '\\2') + '|' - + block.list.source.replace('\\1', '\\3') + '|') - .getRegex(); - -/** - * GFM + Tables Block Grammar - */ - -block.tables = merge({}, block.gfm, { - nptable: /^ *([^|\n ].*\|.*)\n *([-:]+ *\|[-| :]*)(?:\n((?:.*[^>\n ].*(?:\n|$))*)\n*|$)/, - table: /^ *\|(.+)\n *\|?( *[-:]+[-| :]*)(?:\n((?: *[^>\n ].*(?:\n|$))*)\n*|$)/ -}); - -/** - * Pedantic grammar - */ - -block.pedantic = merge({}, block.normal, { - html: edit( - '^ *(?:comment *(?:\\n|\\s*$)' - + '|<(tag)[\\s\\S]+?</\\1> *(?:\\n{2,}|\\s*$)' // closed tag - + '|<tag(?:"[^"]*"|\'[^\']*\'|\\s[^\'"/>\\s]*)*?/?> *(?:\\n{2,}|\\s*$))') - .replace('comment', block._comment) - .replace(/tag/g, '(?!(?:' - + 'a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub' - + '|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)' - + '\\b)\\w+(?!:|[^\\w\\s@]*@)\\b') - .getRegex(), - def: /^ *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/ -}); - -/** - * Block Lexer - */ - -function Lexer(options) { - this.tokens = []; - this.tokens.links = {}; - this.options = options || marked.defaults; - this.rules = block.normal; - - if (this.options.pedantic) { - this.rules = block.pedantic; - } else if (this.options.gfm) { - if (this.options.tables) { - this.rules = block.tables; - } else { - this.rules = block.gfm; - } - } -} - -/** - * Expose Block Rules - */ - -Lexer.rules = block; - -/** - * Static Lex Method - */ - -Lexer.lex = function(src, options) { - var lexer = new Lexer(options); - return lexer.lex(src); -}; - -/** - * Preprocessing - */ - -Lexer.prototype.lex = function(src) { - src = src - .replace(/\r\n|\r/g, '\n') - .replace(/\t/g, ' ') - .replace(/\u00a0/g, ' ') - .replace(/\u2424/g, '\n'); - - return this.token(src, true); -}; - -/** - * Lexing - */ - -Lexer.prototype.token = function(src, top) { - src = src.replace(/^ +$/gm, ''); - var next, - loose, - cap, - bull, - b, - item, - space, - i, - tag, - l, - isordered, - istask, - ischecked; - - while (src) { - // newline - if (cap = this.rules.newline.exec(src)) { - src = src.substring(cap[0].length); - if (cap[0].length > 1) { - this.tokens.push({ - type: 'space' - }); - } - } - - // code - if (cap = this.rules.code.exec(src)) { - src = src.substring(cap[0].length); - cap = cap[0].replace(/^ {4}/gm, ''); - this.tokens.push({ - type: 'code', - text: !this.options.pedantic - ? cap.replace(/\n+$/, '') - : cap - }); - continue; - } - - // fences (gfm) - if (cap = this.rules.fences.exec(src)) { - src = src.substring(cap[0].length); - this.tokens.push({ - type: 'code', - lang: cap[2], - text: cap[3] || '' - }); - continue; - } - - // heading - if (cap = this.rules.heading.exec(src)) { - src = src.substring(cap[0].length); - this.tokens.push({ - type: 'heading', - depth: cap[1].length, - text: cap[2] - }); - continue; - } - - // table no leading pipe (gfm) - if (top && (cap = this.rules.nptable.exec(src))) { - item = { - type: 'table', - header: splitCells(cap[1].replace(/^ *| *\| *$/g, '')), - align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */), - cells: cap[3] ? cap[3].replace(/\n$/, '').split('\n') : [] - }; - - if (item.header.length === item.align.length) { - src = src.substring(cap[0].length); - - for (i = 0; i < item.align.length; i++) { - if (/^ *-+: *$/.test(item.align[i])) { - item.align[i] = 'right'; - } else if (/^ *:-+: *$/.test(item.align[i])) { - item.align[i] = 'center'; - } else if (/^ *:-+ *$/.test(item.align[i])) { - item.align[i] = 'left'; - } else { - item.align[i] = null; - } - } - - for (i = 0; i < item.cells.length; i++) { - item.cells[i] = splitCells(item.cells[i], item.header.length); - } - - this.tokens.push(item); - - continue; - } - } - - // hr - if (cap = this.rules.hr.exec(src)) { - src = src.substring(cap[0].length); - this.tokens.push({ - type: 'hr' - }); - continue; - } - - // blockquote - if (cap = this.rules.blockquote.exec(src)) { - src = src.substring(cap[0].length); - - this.tokens.push({ - type: 'blockquote_start' - }); - - cap = cap[0].replace(/^ *> ?/gm, ''); - - // Pass `top` to keep the current - // "toplevel" state. This is exactly - // how markdown.pl works. - this.token(cap, top); - - this.tokens.push({ - type: 'blockquote_end' - }); - - continue; - } - - // list - if (cap = this.rules.list.exec(src)) { - src = src.substring(cap[0].length); - bull = cap[2]; - isordered = bull.length > 1; - - this.tokens.push({ - type: 'list_start', - ordered: isordered, - start: isordered ? +bull : '' - }); - - // Get each top-level item. - cap = cap[0].match(this.rules.item); - - next = false; - l = cap.length; - i = 0; - - for (; i < l; i++) { - item = cap[i]; - - // Remove the list item's bullet - // so it is seen as the next token. - space = item.length; - item = item.replace(/^ *([*+-]|\d+\.) +/, ''); - - // Outdent whatever the - // list item contains. Hacky. - if (~item.indexOf('\n ')) { - space -= item.length; - item = !this.options.pedantic - ? item.replace(new RegExp('^ {1,' + space + '}', 'gm'), '') - : item.replace(/^ {1,4}/gm, ''); - } - - // Determine whether the next list item belongs here. - // Backpedal if it does not belong in this list. - if (this.options.smartLists && i !== l - 1) { - b = block.bullet.exec(cap[i + 1])[0]; - if (bull !== b && !(bull.length > 1 && b.length > 1)) { - src = cap.slice(i + 1).join('\n') + src; - i = l - 1; - } - } - - // Determine whether item is loose or not. - // Use: /(^|\n)(?! )[^\n]+\n\n(?!\s*$)/ - // for discount behavior. - loose = next || /\n\n(?!\s*$)/.test(item); - if (i !== l - 1) { - next = item.charAt(item.length - 1) === '\n'; - if (!loose) loose = next; - } - - // Check for task list items - istask = /^\[[ xX]\] /.test(item); - ischecked = undefined; - if (istask) { - ischecked = item[1] !== ' '; - item = item.replace(/^\[[ xX]\] +/, ''); - } - - this.tokens.push({ - type: loose - ? 'loose_item_start' - : 'list_item_start', - task: istask, - checked: ischecked - }); - - // Recurse. - this.token(item, false); - - this.tokens.push({ - type: 'list_item_end' - }); - } - - this.tokens.push({ - type: 'list_end' - }); - - continue; - } - - // html - if (cap = this.rules.html.exec(src)) { - src = src.substring(cap[0].length); - this.tokens.push({ - type: this.options.sanitize - ? 'paragraph' - : 'html', - pre: !this.options.sanitizer - && (cap[1] === 'pre' || cap[1] === 'script' || cap[1] === 'style'), - text: cap[0] - }); - continue; - } - - // def - if (top && (cap = this.rules.def.exec(src))) { - src = src.substring(cap[0].length); - if (cap[3]) cap[3] = cap[3].substring(1, cap[3].length - 1); - tag = cap[1].toLowerCase().replace(/\s+/g, ' '); - if (!this.tokens.links[tag]) { - this.tokens.links[tag] = { - href: cap[2], - title: cap[3] - }; - } - continue; - } - - // table (gfm) - if (top && (cap = this.rules.table.exec(src))) { - item = { - type: 'table', - header: splitCells(cap[1].replace(/^ *| *\| *$/g, '')), - align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */), - cells: cap[3] ? cap[3].replace(/(?: *\| *)?\n$/, '').split('\n') : [] - }; - - if (item.header.length === item.align.length) { - src = src.substring(cap[0].length); - - for (i = 0; i < item.align.length; i++) { - if (/^ *-+: *$/.test(item.align[i])) { - item.align[i] = 'right'; - } else if (/^ *:-+: *$/.test(item.align[i])) { - item.align[i] = 'center'; - } else if (/^ *:-+ *$/.test(item.align[i])) { - item.align[i] = 'left'; - } else { - item.align[i] = null; - } - } - - for (i = 0; i < item.cells.length; i++) { - item.cells[i] = splitCells( - item.cells[i].replace(/^ *\| *| *\| *$/g, ''), - item.header.length); - } - - this.tokens.push(item); - - continue; - } - } - - // lheading - if (cap = this.rules.lheading.exec(src)) { - src = src.substring(cap[0].length); - this.tokens.push({ - type: 'heading', - depth: cap[2] === '=' ? 1 : 2, - text: cap[1] - }); - continue; - } - - // top-level paragraph - if (top && (cap = this.rules.paragraph.exec(src))) { - src = src.substring(cap[0].length); - this.tokens.push({ - type: 'paragraph', - text: cap[1].charAt(cap[1].length - 1) === '\n' - ? cap[1].slice(0, -1) - : cap[1] - }); - continue; - } - - // text - if (cap = this.rules.text.exec(src)) { - // Top-level should never reach here. - src = src.substring(cap[0].length); - this.tokens.push({ - type: 'text', - text: cap[0] - }); - continue; - } - - if (src) { - throw new Error('Infinite loop on byte: ' + src.charCodeAt(0)); - } - } - - return this.tokens; -}; - -/** - * Inline-Level Grammar - */ - -var inline = { - escape: /^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/, - autolink: /^<(scheme:[^\s\x00-\x1f<>]*|email)>/, - url: noop, - tag: '^comment' - + '|^</[a-zA-Z][\\w:-]*\\s*>' // self-closing tag - + '|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>' // open tag - + '|^<\\?[\\s\\S]*?\\?>' // processing instruction, e.g. <?php ?> - + '|^<![a-zA-Z]+\\s[\\s\\S]*?>' // declaration, e.g. <!DOCTYPE html> - + '|^<!\\[CDATA\\[[\\s\\S]*?\\]\\]>', // CDATA section - link: /^!?\[(label)\]\(href(?:\s+(title))?\s*\)/, - reflink: /^!?\[(label)\]\[(?!\s*\])((?:\\[\[\]]?|[^\[\]\\])+)\]/, - nolink: /^!?\[(?!\s*\])((?:\[[^\[\]]*\]|\\[\[\]]|[^\[\]])*)\](?:\[\])?/, - strong: /^__([^\s][\s\S]*?[^\s])__(?!_)|^\*\*([^\s][\s\S]*?[^\s])\*\*(?!\*)|^__([^\s])__(?!_)|^\*\*([^\s])\*\*(?!\*)/, - em: /^_([^\s][\s\S]*?[^\s_])_(?!_)|^_([^\s_][\s\S]*?[^\s])_(?!_)|^\*([^\s][\s\S]*?[^\s*])\*(?!\*)|^\*([^\s*][\s\S]*?[^\s])\*(?!\*)|^_([^\s_])_(?!_)|^\*([^\s*])\*(?!\*)/, - code: /^(`+)\s*([\s\S]*?[^`]?)\s*\1(?!`)/, - br: /^ {2,}\n(?!\s*$)/, - del: noop, - text: /^[\s\S]+?(?=[\\<!\[`*]|\b_| {2,}\n|$)/ -}; - -inline._escapes = /\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/g; - -inline._scheme = /[a-zA-Z][a-zA-Z0-9+.-]{1,31}/; -inline._email = /[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/; -inline.autolink = edit(inline.autolink) - .replace('scheme', inline._scheme) - .replace('email', inline._email) - .getRegex(); - -inline._attribute = /\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/; - -inline.tag = edit(inline.tag) - .replace('comment', block._comment) - .replace('attribute', inline._attribute) - .getRegex(); - -inline._label = /(?:\[[^\[\]]*\]|\\[\[\]]?|`[^`]*`|[^\[\]\\])*?/; -inline._href = /\s*(<(?:\\[<>]?|[^\s<>\\])*>|(?:\\[()]?|\([^\s\x00-\x1f()\\]*\)|[^\s\x00-\x1f()\\])*?)/; -inline._title = /"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/; - -inline.link = edit(inline.link) - .replace('label', inline._label) - .replace('href', inline._href) - .replace('title', inline._title) - .getRegex(); - -inline.reflink = edit(inline.reflink) - .replace('label', inline._label) - .getRegex(); - -/** - * Normal Inline Grammar - */ - -inline.normal = merge({}, inline); - -/** - * Pedantic Inline Grammar - */ - -inline.pedantic = merge({}, inline.normal, { - strong: /^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/, - em: /^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/, - link: edit(/^!?\[(label)\]\((.*?)\)/) - .replace('label', inline._label) - .getRegex(), - reflink: edit(/^!?\[(label)\]\s*\[([^\]]*)\]/) - .replace('label', inline._label) - .getRegex() -}); - -/** - * GFM Inline Grammar - */ - -inline.gfm = merge({}, inline.normal, { - escape: edit(inline.escape).replace('])', '~|])').getRegex(), - url: edit(/^((?:ftp|https?):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/) - .replace('email', inline._email) - .getRegex(), - _backpedal: /(?:[^?!.,:;*_~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_~)]+(?!$))+/, - del: /^~~(?=\S)([\s\S]*?\S)~~/, - text: edit(inline.text) - .replace(']|', '~]|') - .replace('|', '|https?://|ftp://|www\\.|[a-zA-Z0-9.!#$%&\'*+/=?^_`{\\|}~-]+@|') - .getRegex() -}); - -/** - * GFM + Line Breaks Inline Grammar - */ - -inline.breaks = merge({}, inline.gfm, { - br: edit(inline.br).replace('{2,}', '*').getRegex(), - text: edit(inline.gfm.text).replace('{2,}', '*').getRegex() -}); - -/** - * Inline Lexer & Compiler - */ - -function InlineLexer(links, options) { - this.options = options || marked.defaults; - this.links = links; - this.rules = inline.normal; - this.renderer = this.options.renderer || new Renderer(); - this.renderer.options = this.options; - - if (!this.links) { - throw new Error('Tokens array requires a `links` property.'); - } - - if (this.options.pedantic) { - this.rules = inline.pedantic; - } else if (this.options.gfm) { - if (this.options.breaks) { - this.rules = inline.breaks; - } else { - this.rules = inline.gfm; - } - } -} - -/** - * Expose Inline Rules - */ - -InlineLexer.rules = inline; - -/** - * Static Lexing/Compiling Method - */ - -InlineLexer.output = function(src, links, options) { - var inline = new InlineLexer(links, options); - return inline.output(src); -}; - -/** - * Lexing/Compiling - */ - -InlineLexer.prototype.output = function(src) { - var out = '', - link, - text, - href, - title, - cap; - - while (src) { - // escape - if (cap = this.rules.escape.exec(src)) { - src = src.substring(cap[0].length); - out += cap[1]; - continue; - } - - // autolink - if (cap = this.rules.autolink.exec(src)) { - src = src.substring(cap[0].length); - if (cap[2] === '@') { - text = escape(this.mangle(cap[1])); - href = 'mailto:' + text; - } else { - text = escape(cap[1]); - href = text; - } - out += this.renderer.link(href, null, text); - continue; - } - - // url (gfm) - if (!this.inLink && (cap = this.rules.url.exec(src))) { - cap[0] = this.rules._backpedal.exec(cap[0])[0]; - src = src.substring(cap[0].length); - if (cap[2] === '@') { - text = escape(cap[0]); - href = 'mailto:' + text; - } else { - text = escape(cap[0]); - if (cap[1] === 'www.') { - href = 'http://' + text; - } else { - href = text; - } - } - out += this.renderer.link(href, null, text); - continue; - } - - // tag - if (cap = this.rules.tag.exec(src)) { - if (!this.inLink && /^<a /i.test(cap[0])) { - this.inLink = true; - } else if (this.inLink && /^<\/a>/i.test(cap[0])) { - this.inLink = false; - } - src = src.substring(cap[0].length); - out += this.options.sanitize - ? this.options.sanitizer - ? this.options.sanitizer(cap[0]) - : escape(cap[0]) - : cap[0] - continue; - } - - // link - if (cap = this.rules.link.exec(src)) { - src = src.substring(cap[0].length); - this.inLink = true; - href = cap[2]; - if (this.options.pedantic) { - link = /^([^'"]*[^\s])\s+(['"])(.*)\2/.exec(href); - - if (link) { - href = link[1]; - title = link[3]; - } else { - title = ''; - } - } else { - title = cap[3] ? cap[3].slice(1, -1) : ''; - } - href = href.trim().replace(/^<([\s\S]*)>$/, '$1'); - out += this.outputLink(cap, { - href: InlineLexer.escapes(href), - title: InlineLexer.escapes(title) - }); - this.inLink = false; - continue; - } - - // reflink, nolink - if ((cap = this.rules.reflink.exec(src)) - || (cap = this.rules.nolink.exec(src))) { - src = src.substring(cap[0].length); - link = (cap[2] || cap[1]).replace(/\s+/g, ' '); - link = this.links[link.toLowerCase()]; - if (!link || !link.href) { - out += cap[0].charAt(0); - src = cap[0].substring(1) + src; - continue; - } - this.inLink = true; - out += this.outputLink(cap, link); - this.inLink = false; - continue; - } - - // strong - if (cap = this.rules.strong.exec(src)) { - src = src.substring(cap[0].length); - out += this.renderer.strong(this.output(cap[4] || cap[3] || cap[2] || cap[1])); - continue; - } - - // em - if (cap = this.rules.em.exec(src)) { - src = src.substring(cap[0].length); - out += this.renderer.em(this.output(cap[6] || cap[5] || cap[4] || cap[3] || cap[2] || cap[1])); - continue; - } - - // code - if (cap = this.rules.code.exec(src)) { - src = src.substring(cap[0].length); - out += this.renderer.codespan(escape(cap[2].trim(), true)); - continue; - } - - // br - if (cap = this.rules.br.exec(src)) { - src = src.substring(cap[0].length); - out += this.renderer.br(); - continue; - } - - // del (gfm) - if (cap = this.rules.del.exec(src)) { - src = src.substring(cap[0].length); - out += this.renderer.del(this.output(cap[1])); - continue; - } - - // text - if (cap = this.rules.text.exec(src)) { - src = src.substring(cap[0].length); - out += this.renderer.text(escape(this.smartypants(cap[0]))); - continue; - } - - if (src) { - throw new Error('Infinite loop on byte: ' + src.charCodeAt(0)); - } - } - - return out; -}; - -InlineLexer.escapes = function(text) { - return text ? text.replace(InlineLexer.rules._escapes, '$1') : text; -} - -/** - * Compile Link - */ - -InlineLexer.prototype.outputLink = function(cap, link) { - var href = link.href, - title = link.title ? escape(link.title) : null; - - return cap[0].charAt(0) !== '!' - ? this.renderer.link(href, title, this.output(cap[1])) - : this.renderer.image(href, title, escape(cap[1])); -}; - -/** - * Smartypants Transformations - */ - -InlineLexer.prototype.smartypants = function(text) { - if (!this.options.smartypants) return text; - return text - // em-dashes - .replace(/---/g, '\u2014') - // en-dashes - .replace(/--/g, '\u2013') - // opening singles - .replace(/(^|[-\u2014/(\[{"\s])'/g, '$1\u2018') - // closing singles & apostrophes - .replace(/'/g, '\u2019') - // opening doubles - .replace(/(^|[-\u2014/(\[{\u2018\s])"/g, '$1\u201c') - // closing doubles - .replace(/"/g, '\u201d') - // ellipses - .replace(/\.{3}/g, '\u2026'); -}; - -/** - * Mangle Links - */ - -InlineLexer.prototype.mangle = function(text) { - if (!this.options.mangle) return text; - var out = '', - l = text.length, - i = 0, - ch; - - for (; i < l; i++) { - ch = text.charCodeAt(i); - if (Math.random() > 0.5) { - ch = 'x' + ch.toString(16); - } - out += '&#' + ch + ';'; - } - - return out; -}; - -/** - * Renderer - */ - -function Renderer(options) { - this.options = options || marked.defaults; -} - -Renderer.prototype.code = function(code, lang, escaped) { - if (this.options.highlight) { - var out = this.options.highlight(code, lang); - if (out != null && out !== code) { - escaped = true; - code = out; - } - } - - if (!lang) { - return '<pre><code>' - + (escaped ? code : escape(code, true)) - + '</code></pre>'; - } - - return '<pre><code class="' - + this.options.langPrefix - + escape(lang, true) - + '">' - + (escaped ? code : escape(code, true)) - + '</code></pre>\n'; -}; - -Renderer.prototype.blockquote = function(quote) { - return '<blockquote>\n' + quote + '</blockquote>\n'; -}; - -Renderer.prototype.html = function(html) { - return html; -}; - -Renderer.prototype.heading = function(text, level, raw) { - if (this.options.headerIds) { - return '<h' - + level - + ' id="' - + this.options.headerPrefix - + raw.toLowerCase().replace(/[^\w]+/g, '-') - + '">' - + text - + '</h' - + level - + '>\n'; - } - // ignore IDs - return '<h' + level + '>' + text + '</h' + level + '>\n'; -}; - -Renderer.prototype.hr = function() { - return this.options.xhtml ? '<hr/>\n' : '<hr>\n'; -}; - -Renderer.prototype.list = function(body, ordered, start) { - var type = ordered ? 'ol' : 'ul', - startatt = (ordered && start !== 1) ? (' start="' + start + '"') : ''; - return '<' + type + startatt + '>\n' + body + '</' + type + '>\n'; -}; - -Renderer.prototype.listitem = function(text) { - return '<li>' + text + '</li>\n'; -}; - -Renderer.prototype.checkbox = function(checked) { - return '<input ' - + (checked ? 'checked="" ' : '') - + 'disabled="" type="checkbox"' - + (this.options.xhtml ? ' /' : '') - + '> '; -} - -Renderer.prototype.paragraph = function(text) { - return '<p>' + text + '</p>\n'; -}; - -Renderer.prototype.table = function(header, body) { - if (body) body = '<tbody>' + body + '</tbody>'; - - return '<table>\n' - + '<thead>\n' - + header - + '</thead>\n' - + body - + '</table>\n'; -}; - -Renderer.prototype.tablerow = function(content) { - return '<tr>\n' + content + '</tr>\n'; -}; - -Renderer.prototype.tablecell = function(content, flags) { - var type = flags.header ? 'th' : 'td'; - var tag = flags.align - ? '<' + type + ' align="' + flags.align + '">' - : '<' + type + '>'; - return tag + content + '</' + type + '>\n'; -}; - -// span level renderer -Renderer.prototype.strong = function(text) { - return '<strong>' + text + '</strong>'; -}; - -Renderer.prototype.em = function(text) { - return '<em>' + text + '</em>'; -}; - -Renderer.prototype.codespan = function(text) { - return '<code>' + text + '</code>'; -}; - -Renderer.prototype.br = function() { - return this.options.xhtml ? '<br/>' : '<br>'; -}; - -Renderer.prototype.del = function(text) { - return '<del>' + text + '</del>'; -}; - -Renderer.prototype.link = function(href, title, text) { - if (this.options.sanitize) { - try { - var prot = decodeURIComponent(unescape(href)) - .replace(/[^\w:]/g, '') - .toLowerCase(); - } catch (e) { - return text; - } - if (prot.indexOf('javascript:') === 0 || prot.indexOf('vbscript:') === 0 || prot.indexOf('data:') === 0) { - return text; - } - } - if (this.options.baseUrl && !originIndependentUrl.test(href)) { - href = resolveUrl(this.options.baseUrl, href); - } - try { - href = encodeURI(href).replace(/%25/g, '%'); - } catch (e) { - return text; - } - var out = '<a href="' + escape(href) + '"'; - if (title) { - out += ' title="' + title + '"'; - } - out += '>' + text + '</a>'; - return out; -}; - -Renderer.prototype.image = function(href, title, text) { - if (this.options.baseUrl && !originIndependentUrl.test(href)) { - href = resolveUrl(this.options.baseUrl, href); - } - var out = '<img src="' + href + '" alt="' + text + '"'; - if (title) { - out += ' title="' + title + '"'; - } - out += this.options.xhtml ? '/>' : '>'; - return out; -}; - -Renderer.prototype.text = function(text) { - return text; -}; - -/** - * TextRenderer - * returns only the textual part of the token - */ - -function TextRenderer() {} - -// no need for block level renderers - -TextRenderer.prototype.strong = -TextRenderer.prototype.em = -TextRenderer.prototype.codespan = -TextRenderer.prototype.del = -TextRenderer.prototype.text = function (text) { - return text; -} - -TextRenderer.prototype.link = -TextRenderer.prototype.image = function(href, title, text) { - return '' + text; -} - -TextRenderer.prototype.br = function() { - return ''; -} - -/** - * Parsing & Compiling - */ - -function Parser(options) { - this.tokens = []; - this.token = null; - this.options = options || marked.defaults; - this.options.renderer = this.options.renderer || new Renderer(); - this.renderer = this.options.renderer; - this.renderer.options = this.options; -} - -/** - * Static Parse Method - */ - -Parser.parse = function(src, options) { - var parser = new Parser(options); - return parser.parse(src); -}; - -/** - * Parse Loop - */ - -Parser.prototype.parse = function(src) { - this.inline = new InlineLexer(src.links, this.options); - // use an InlineLexer with a TextRenderer to extract pure text - this.inlineText = new InlineLexer( - src.links, - merge({}, this.options, {renderer: new TextRenderer()}) - ); - this.tokens = src.reverse(); - - var out = ''; - while (this.next()) { - out += this.tok(); - } - - return out; -}; - -/** - * Next Token - */ - -Parser.prototype.next = function() { - return this.token = this.tokens.pop(); -}; - -/** - * Preview Next Token - */ - -Parser.prototype.peek = function() { - return this.tokens[this.tokens.length - 1] || 0; -}; - -/** - * Parse Text Tokens - */ - -Parser.prototype.parseText = function() { - var body = this.token.text; - - while (this.peek().type === 'text') { - body += '\n' + this.next().text; - } - - return this.inline.output(body); -}; - -/** - * Parse Current Token - */ - -Parser.prototype.tok = function() { - switch (this.token.type) { - case 'space': { - return ''; - } - case 'hr': { - return this.renderer.hr(); - } - case 'heading': { - return this.renderer.heading( - this.inline.output(this.token.text), - this.token.depth, - unescape(this.inlineText.output(this.token.text))); - } - case 'code': { - return this.renderer.code(this.token.text, - this.token.lang, - this.token.escaped); - } - case 'table': { - var header = '', - body = '', - i, - row, - cell, - j; - - // header - cell = ''; - for (i = 0; i < this.token.header.length; i++) { - cell += this.renderer.tablecell( - this.inline.output(this.token.header[i]), - { header: true, align: this.token.align[i] } - ); - } - header += this.renderer.tablerow(cell); - - for (i = 0; i < this.token.cells.length; i++) { - row = this.token.cells[i]; - - cell = ''; - for (j = 0; j < row.length; j++) { - cell += this.renderer.tablecell( - this.inline.output(row[j]), - { header: false, align: this.token.align[j] } - ); - } - - body += this.renderer.tablerow(cell); - } - return this.renderer.table(header, body); - } - case 'blockquote_start': { - body = ''; - - while (this.next().type !== 'blockquote_end') { - body += this.tok(); - } - - return this.renderer.blockquote(body); - } - case 'list_start': { - body = ''; - var ordered = this.token.ordered, - start = this.token.start; - - while (this.next().type !== 'list_end') { - body += this.tok(); - } - - return this.renderer.list(body, ordered, start); - } - case 'list_item_start': { - body = ''; - - if (this.token.task) { - body += this.renderer.checkbox(this.token.checked); - } - - while (this.next().type !== 'list_item_end') { - body += this.token.type === 'text' - ? this.parseText() - : this.tok(); - } - - return this.renderer.listitem(body); - } - case 'loose_item_start': { - body = ''; - - while (this.next().type !== 'list_item_end') { - body += this.tok(); - } - - return this.renderer.listitem(body); - } - case 'html': { - // TODO parse inline content if parameter markdown=1 - return this.renderer.html(this.token.text); - } - case 'paragraph': { - return this.renderer.paragraph(this.inline.output(this.token.text)); - } - case 'text': { - return this.renderer.paragraph(this.parseText()); - } - } -}; - -/** - * Helpers - */ - -function escape(html, encode) { - return html - .replace(!encode ? /&(?!#?\w+;)/g : /&/g, '&') - .replace(/</g, '<') - .replace(/>/g, '>') - .replace(/"/g, '"') - .replace(/'/g, '''); -} - -function unescape(html) { - // explicitly match decimal, hex, and named HTML entities - return html.replace(/&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/ig, function(_, n) { - n = n.toLowerCase(); - if (n === 'colon') return ':'; - if (n.charAt(0) === '#') { - return n.charAt(1) === 'x' - ? String.fromCharCode(parseInt(n.substring(2), 16)) - : String.fromCharCode(+n.substring(1)); - } - return ''; - }); -} - -function edit(regex, opt) { - regex = regex.source || regex; - opt = opt || ''; - return { - replace: function(name, val) { - val = val.source || val; - val = val.replace(/(^|[^\[])\^/g, '$1'); - regex = regex.replace(name, val); - return this; - }, - getRegex: function() { - return new RegExp(regex, opt); - } - }; -} - -function resolveUrl(base, href) { - if (!baseUrls[' ' + base]) { - // we can ignore everything in base after the last slash of its path component, - // but we might need to add _that_ - // https://tools.ietf.org/html/rfc3986#section-3 - if (/^[^:]+:\/*[^/]*$/.test(base)) { - baseUrls[' ' + base] = base + '/'; - } else { - baseUrls[' ' + base] = base.replace(/[^/]*$/, ''); - } - } - base = baseUrls[' ' + base]; - - if (href.slice(0, 2) === '//') { - return base.replace(/:[\s\S]*/, ':') + href; - } else if (href.charAt(0) === '/') { - return base.replace(/(:\/*[^/]*)[\s\S]*/, '$1') + href; - } else { - return base + href; - } -} -var baseUrls = {}; -var originIndependentUrl = /^$|^[a-z][a-z0-9+.-]*:|^[?#]/i; - -function noop() {} -noop.exec = noop; - -function merge(obj) { - var i = 1, - target, - key; - - for (; i < arguments.length; i++) { - target = arguments[i]; - for (key in target) { - if (Object.prototype.hasOwnProperty.call(target, key)) { - obj[key] = target[key]; - } - } - } - - return obj; -} - -function splitCells(tableRow, count) { - var cells = tableRow.replace(/([^\\])\|/g, '$1 |').split(/ +\| */), - i = 0; - - if (cells.length > count) { - cells.splice(count); - } else { - while (cells.length < count) cells.push(''); - } - - for (; i < cells.length; i++) { - cells[i] = cells[i].replace(/\\\|/g, '|'); - } - return cells; -} - -/** - * Marked - */ - -function marked(src, opt, callback) { - // throw error in case of non string input - if (typeof src === 'undefined' || src === null) { - throw new Error('marked(): input parameter is undefined or null'); - } - if (typeof src !== 'string') { - throw new Error('marked(): input parameter is of type ' - + Object.prototype.toString.call(src) + ', string expected'); - } - - if (callback || typeof opt === 'function') { - if (!callback) { - callback = opt; - opt = null; - } - - opt = merge({}, marked.defaults, opt || {}); - - var highlight = opt.highlight, - tokens, - pending, - i = 0; - - try { - tokens = Lexer.lex(src, opt) - } catch (e) { - return callback(e); - } - - pending = tokens.length; - - var done = function(err) { - if (err) { - opt.highlight = highlight; - return callback(err); - } - - var out; - - try { - out = Parser.parse(tokens, opt); - } catch (e) { - err = e; - } - - opt.highlight = highlight; - - return err - ? callback(err) - : callback(null, out); - }; - - if (!highlight || highlight.length < 3) { - return done(); - } - - delete opt.highlight; - - if (!pending) return done(); - - for (; i < tokens.length; i++) { - (function(token) { - if (token.type !== 'code') { - return --pending || done(); - } - return highlight(token.text, token.lang, function(err, code) { - if (err) return done(err); - if (code == null || code === token.text) { - return --pending || done(); - } - token.text = code; - token.escaped = true; - --pending || done(); - }); - })(tokens[i]); - } - - return; - } - try { - if (opt) opt = merge({}, marked.defaults, opt); - return Parser.parse(Lexer.lex(src, opt), opt); - } catch (e) { - e.message += '\nPlease report this to https://github.com/markedjs/marked.'; - if ((opt || marked.defaults).silent) { - return '<p>An error occurred:</p><pre>' - + escape(e.message + '', true) - + '</pre>'; - } - throw e; - } -} - -/** - * Options - */ - -marked.options = -marked.setOptions = function(opt) { - merge(marked.defaults, opt); - return marked; -}; - -marked.getDefaults = function () { - return { - baseUrl: null, - breaks: false, - gfm: true, - headerIds: true, - headerPrefix: '', - highlight: null, - langPrefix: 'language-', - mangle: true, - pedantic: false, - renderer: new Renderer(), - sanitize: false, - sanitizer: null, - silent: false, - smartLists: false, - smartypants: false, - tables: true, - xhtml: false - }; -} - -marked.defaults = marked.getDefaults(); - -/** - * Expose - */ - -marked.Parser = Parser; -marked.parser = Parser.parse; - -marked.Renderer = Renderer; -marked.TextRenderer = TextRenderer; - -marked.Lexer = Lexer; -marked.lexer = Lexer.lex; - -marked.InlineLexer = InlineLexer; -marked.inlineLexer = InlineLexer.output; - -marked.parse = marked; - -if (typeof module !== 'undefined' && typeof exports === 'object') { - module.exports = marked; -} else if (typeof define === 'function' && define.amd) { - define(function() { return marked; }); -} else { - root.marked = marked; -} -})(this || (typeof window !== 'undefined' ? window : global)); diff --git a/examples/webenginewidgets/markdowneditor/resources/3rdparty/qt_attribution.json b/examples/webenginewidgets/markdowneditor/resources/3rdparty/qt_attribution.json deleted file mode 100644 index d51ac744b..000000000 --- a/examples/webenginewidgets/markdowneditor/resources/3rdparty/qt_attribution.json +++ /dev/null @@ -1,34 +0,0 @@ -[ - { - "Id": "markdowneditor-marked", - "Name": "Marked (WebEngine Markdown Editor example)", - "QDocModule": "qtwebengine", - "QtUsage": "Marked is used in the WebEngine MarkDown Editor example", - "QtParts": [ "examples" ], - "Files": "marked.js", - "Description": "A full-featured markdown parser and compiler, written in JavaScript. Built for speed.", - "Homepage": "https://github.com/chjj/marked", - "Version": "0.4.0", - "DownloadLocation": "https://github.com/markedjs/marked/blob/0.4.0/lib/marked.js", - "Copyright": "Copyright (c) 2011-2018, Christopher Jeffrey", - "License": "MIT License", - "LicenseId": "MIT", - "LicenseFile": "MARKED-LICENSE.txt" - }, - { - "Id": "markdowneditor-markdowncss", - "Name": "Markdown.css (WebEngine Markdown Editor example)", - "QDocModule": "qtwebengine", - "QtUsage": "markdown.css is used in the WebEngine MarkDown Editor example", - "QtParts": [ "examples" ], - "Files": "markdown.css", - "Description": "Markdown.css is better default styling for your Markdown files.", - "Version": "188530e4b5d020d7e237fc6b26be13ebf4a8def3", - "DownloadLocation": "https://bitbucket.org/kevinburke/markdowncss/src/188530e4b5d020d7e237fc6b26be13ebf4a8def3/markdown.css", - "Copyright": "Copyright 2011 Kevin Burke - Copyright Twitter Inc.", - "License": "Apache License 2.0", - "LicenseId": "Apache-2.0", - "LicenseFile": "MARKDOWN-LICENSE.txt" - } -] diff --git a/examples/webenginewidgets/markdowneditor/resources/default.md b/examples/webenginewidgets/markdowneditor/resources/default.md deleted file mode 100644 index af835fa4d..000000000 --- a/examples/webenginewidgets/markdowneditor/resources/default.md +++ /dev/null @@ -1,12 +0,0 @@ -## WebEngine Markdown Editor Example - -This example uses [QWebEngineView](http://doc.qt.io/qt-5/qwebengineview.html) -to preview text written using the [Markdown](https://en.wikipedia.org/wiki/Markdown) -syntax. - -### Acknowledgments - -The conversion from Markdown to HTML is done with the help of the -[marked JavaScript library](https://github.com/chjj/marked) by _Christopher Jeffrey_. -The [style sheet](https://kevinburke.bitbucket.io/markdowncss/) -was created by _Kevin Burke_. diff --git a/examples/webenginewidgets/markdowneditor/resources/index.html b/examples/webenginewidgets/markdowneditor/resources/index.html deleted file mode 100644 index 289a2110b..000000000 --- a/examples/webenginewidgets/markdowneditor/resources/index.html +++ /dev/null @@ -1,32 +0,0 @@ -<!doctype html> -<html lang="en"> -<meta charset="utf-8"> -<head> - <link rel="stylesheet" type="text/css" href="3rdparty/markdown.css"> - <script src="3rdparty/marked.js"></script> - <script src="qrc:/qtwebchannel/qwebchannel.js"></script> -</head> -<body> - <div id="placeholder"></div> - <script> - 'use strict'; - - var placeholder = document.getElementById('placeholder'); - - var updateText = function(text) { - placeholder.innerHTML = marked(text); - } - - new QWebChannel(qt.webChannelTransport, - function(channel) { - var content = channel.objects.content; - updateText(content.text); - content.textChanged.connect(updateText); - } - ); - </script> -</body> -</html> - - - diff --git a/examples/webenginewidgets/markdowneditor/resources/markdowneditor.qrc b/examples/webenginewidgets/markdowneditor/resources/markdowneditor.qrc deleted file mode 100644 index bc738f1cf..000000000 --- a/examples/webenginewidgets/markdowneditor/resources/markdowneditor.qrc +++ /dev/null @@ -1,8 +0,0 @@ -<RCC> - <qresource prefix="/"> - <file>default.md</file> - <file>index.html</file> - <file>3rdparty/markdown.css</file> - <file>3rdparty/marked.js</file> - </qresource> -</RCC> diff --git a/examples/webenginewidgets/minimal/CMakeLists.txt b/examples/webenginewidgets/minimal/CMakeLists.txt deleted file mode 100644 index 7c1cac1d8..000000000 --- a/examples/webenginewidgets/minimal/CMakeLists.txt +++ /dev/null @@ -1,33 +0,0 @@ -cmake_minimum_required(VERSION 3.16) -project(minimal LANGUAGES CXX) - -set(CMAKE_AUTOMOC ON) - -if(NOT DEFINED INSTALL_EXAMPLESDIR) - set(INSTALL_EXAMPLESDIR "examples") -endif() - -set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/webenginewidgets/minimal-widgets") - -find_package(Qt6 REQUIRED COMPONENTS Core Gui WebEngineWidgets) - -qt_add_executable(minimal-widgets - main.cpp -) - -set_target_properties(minimal-widgets PROPERTIES - WIN32_EXECUTABLE TRUE - MACOSX_BUNDLE TRUE -) - -target_link_libraries(minimal-widgets PUBLIC - Qt::Core - Qt::Gui - Qt::WebEngineWidgets -) - -install(TARGETS minimal-widgets - RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" - BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" - LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" -) diff --git a/examples/webenginewidgets/minimal/doc/images/minimal-example.png b/examples/webenginewidgets/minimal/doc/images/minimal-example.png Binary files differdeleted file mode 100644 index 18ac9b177..000000000 --- a/examples/webenginewidgets/minimal/doc/images/minimal-example.png +++ /dev/null diff --git a/examples/webenginewidgets/minimal/doc/src/minimal.qdoc b/examples/webenginewidgets/minimal/doc/src/minimal.qdoc deleted file mode 100644 index 5fb5f8be3..000000000 --- a/examples/webenginewidgets/minimal/doc/src/minimal.qdoc +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only - -/*! - \example webenginewidgets/minimal - \title WebEngine Widgets Minimal Example - \ingroup webengine-widgetexamples - \brief Displays a web page using \QWE Widgets. - - \image minimal-example.png - - \e {WebEngine Widgets Minimal Example} demonstrates how to use - \l{QWebEngineView} to render a web page. It shows the minimum amount of code - needed to load and display an HTML page, and can be used as a basis for - further experimentation. - - \include examples-run.qdocinc - - \section1 The Code - - We first define a \c commandLineUrlArgument function that returns the URL to open. - This is either the first positional argument given on the command line, or - \c https://www.qt.io as a fallback. - - \quotefromfile webenginewidgets/minimal/main.cpp - \skipto #include - - \printto int main - - In the \c main function we first set the - \l{QCoreApplication::organizationName} property. This affects the locations - where \QWE stores persistent and cached data (see also - \l{QWebEngineProfile::cachePath} and - \l{QWebEngineProfile::persistentStoragePath}). - - Next, we instantiate a QApplication and a QWebEngineView. The URL - to load is taken from \c commandLineUrlArgument and - loaded by calling \l QWebEngineView::setUrl. The view widget is - given a reasonable default size, and shown. - Finally, QApplication::exec() launches the main event loop. - - \printuntil } - - \section1 Requirements - - The example requires a working internet connection to render - the \l{Qt Homepage}. - An optional system proxy should be picked up automatically. - However, for proxies that require a username or password, - you need to connect to - QWebEnginePage::proxyAuthenticationRequired. - - \l{Qt WebEngine Widgets} uses the \l{Qt Quick Scene Graph} to compose the - page. Therefore, OpenGL support is required. -*/ diff --git a/examples/webenginewidgets/minimal/main.cpp b/examples/webenginewidgets/minimal/main.cpp deleted file mode 100644 index 86c04e721..000000000 --- a/examples/webenginewidgets/minimal/main.cpp +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -#include <QApplication> -#include <QWebEngineView> - -QUrl commandLineUrlArgument() -{ - const QStringList args = QCoreApplication::arguments(); - for (const QString &arg : args.mid(1)) { - if (!arg.startsWith(QLatin1Char('-'))) - return QUrl::fromUserInput(arg); - } - return QUrl(QStringLiteral("https://www.qt.io")); -} - -int main(int argc, char *argv[]) -{ - QCoreApplication::setOrganizationName("QtExamples"); - QApplication app(argc, argv); - - QWebEngineView view; - view.setUrl(commandLineUrlArgument()); - view.resize(1024, 750); - view.show(); - - return app.exec(); -} diff --git a/examples/webenginewidgets/minimal/minimal.pro b/examples/webenginewidgets/minimal/minimal.pro deleted file mode 100644 index 849f4b9b6..000000000 --- a/examples/webenginewidgets/minimal/minimal.pro +++ /dev/null @@ -1,8 +0,0 @@ -TEMPLATE = app - -QT += webenginewidgets - -SOURCES += main.cpp - -target.path = $$[QT_INSTALL_EXAMPLES]/webenginewidgets/minimal -INSTALLS += target diff --git a/examples/webenginewidgets/notifications/CMakeLists.txt b/examples/webenginewidgets/notifications/CMakeLists.txt index 17d4f343b..688d06eb2 100644 --- a/examples/webenginewidgets/notifications/CMakeLists.txt +++ b/examples/webenginewidgets/notifications/CMakeLists.txt @@ -1,3 +1,6 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + cmake_minimum_required(VERSION 3.16) project(notifications LANGUAGES CXX) diff --git a/examples/webenginewidgets/printme/CMakeLists.txt b/examples/webenginewidgets/printme/CMakeLists.txt index 2c38103c1..013ba48ca 100644 --- a/examples/webenginewidgets/printme/CMakeLists.txt +++ b/examples/webenginewidgets/printme/CMakeLists.txt @@ -1,3 +1,6 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + cmake_minimum_required(VERSION 3.16) project(printme LANGUAGES CXX) diff --git a/examples/webenginewidgets/webui/CMakeLists.txt b/examples/webenginewidgets/push-notifications/CMakeLists.txt index e736a2cde..8ea2e25d1 100644 --- a/examples/webenginewidgets/webui/CMakeLists.txt +++ b/examples/webenginewidgets/push-notifications/CMakeLists.txt @@ -1,5 +1,8 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + cmake_minimum_required(VERSION 3.16) -project(webui LANGUAGES CXX) +project(notifications LANGUAGES CXX) set(CMAKE_AUTOMOC ON) @@ -7,39 +10,36 @@ if(NOT DEFINED INSTALL_EXAMPLESDIR) set(INSTALL_EXAMPLESDIR "examples") endif() -set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/webenginewidgets/webui") +set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/webenginewidgets/push-notifications") find_package(Qt6 REQUIRED COMPONENTS Core Gui WebEngineWidgets) -qt_add_executable(webui +qt_add_executable(push-notifications main.cpp - webuihandler.cpp webuihandler.h + notificationpopup.h ) -set_target_properties(webui PROPERTIES +set_target_properties(push-notifications PROPERTIES WIN32_EXECUTABLE TRUE MACOSX_BUNDLE TRUE ) -target_link_libraries(webui PUBLIC +target_link_libraries(push-notifications PUBLIC Qt::Core Qt::Gui Qt::WebEngineWidgets ) -# Resources: -set(webui_resource_files - "about.html" -) - -qt_add_resources(webui "webui" +qt_add_resources(push-notifications "data" PREFIX "/" + BASE + "data" FILES - ${webui_resource_files} + "data/icon.png" ) -install(TARGETS webui +install(TARGETS push-notifications RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" diff --git a/examples/webenginewidgets/push-notifications/content/index.html b/examples/webenginewidgets/push-notifications/content/index.html new file mode 100644 index 000000000..cce3055cd --- /dev/null +++ b/examples/webenginewidgets/push-notifications/content/index.html @@ -0,0 +1,26 @@ +<!DOCTYPE html> +<html> +<head> + <title>Push Notification Using Node and FCM</title> + <link rel="stylesheet" href="style.css"> +</head> +<body> + <h1>Push Notification Using NodeJS and QtWebEngine</h1> + <div id="app"> + <div id="ping-setup"> + <form> + <div> + Ping Me Every [sec]: + </div> + <div class="ping-input"> + <input type="number" name="seconds" min="0" max="3600" required=""> + </div><button>Ping Me</button> + </form> + </div> + <div id="ping-clear"> + <div id="ping-text"></div><button id="ping-clear-button">Clear</button> + </div> + </div> + <script src="ping.js"></script> +</body> +</html> diff --git a/examples/webenginewidgets/push-notifications/content/ping.js b/examples/webenginewidgets/push-notifications/content/ping.js new file mode 100644 index 000000000..285a57019 --- /dev/null +++ b/examples/webenginewidgets/push-notifications/content/ping.js @@ -0,0 +1,84 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +const publicVapidKey = + "BNO4fIv439RpvbReeABNlDNiiBD2Maykn7EVnwsPseH7-P5hjnzZLEfnejXVP7Zt6MFoKqKeHm4nV9BHvbgoRPg"; + +async function setup(delay) +{ + console.log('>> register service worker...'); + const register = await navigator.serviceWorker.register('/worker.js', { scope : '/' }); + console.log('>> service worker registered...'); + + console.log('>> subscribe push subscription to FCM'); + var subscription = await register.pushManager.subscribe( + { userVisibleOnly : true, applicationServerKey : publicVapidKey }); + console.log('>> subscription created...'); + + console.log('>> subscribe to push service...'); + await fetch('/subscribe', { + method : 'POST', + body : JSON.stringify(subscription), + headers : { 'content-type' : 'application/json', 'ping-time' : delay } + }); + console.log('>> push subscription created...') +} + +async function clear() +{ + const register = await navigator.serviceWorker.getRegistration(); + var subscription = await register.pushManager.getSubscription(); + console.log('>> unsubscribe to push service...'); + await fetch('/unsubscribe', { + method : 'POST', + body : JSON.stringify(subscription), + headers : { 'content-type' : 'application/json' } + }); + console.log('>> push unsubscription removed...') + + console.log('>> unsubscribe push subscription to FCM'); + await subscription.unsubscribe(); + console.log('>> subscription removed...'); +} + +function displaySetup(delay = null) +{ + const pingSetup = document.getElementById('ping-setup'); + const pingClear = document.getElementById('ping-clear'); + const pingText = document.getElementById('ping-text'); + if (delay) { + pingClear.style.display = 'block'; + pingSetup.style.display = 'none'; + pingText.innerHTML = 'Ping Me Every ' + delay + ' seconds'; + } else { + pingClear.style.display = 'none'; + pingSetup.style.display = 'block'; + pingText.innerHTML = ""; + } +} + +function handleSetupPing(event) +{ + event.preventDefault(); + + const seconds = document.forms[0].seconds.value; + document.forms[0].reset(); + + // check for service worker support + if ('serviceWorker' in navigator) { + setup(seconds).catch(err => console.error(err)); + } + + displaySetup(seconds); +}; + +function handleClearPing(event) +{ + event.preventDefault(); + clear(); + displaySetup(); +}; + +document.forms[0].addEventListener('submit', handleSetupPing); +const clearButton = document.getElementById('ping-clear-button'); +clearButton.addEventListener('click', handleClearPing); diff --git a/examples/webenginewidgets/push-notifications/content/style.css b/examples/webenginewidgets/push-notifications/content/style.css new file mode 100644 index 000000000..8176462a2 --- /dev/null +++ b/examples/webenginewidgets/push-notifications/content/style.css @@ -0,0 +1,29 @@ +body { + font-family: monaco; + height: 100vh; + flex-direction: column; + align-items: center; + justify-content: center; + display: flex; +} +#ping-clear, +#ping-setup { + font-size: 32px; + text-align: center; +} +.form-inputs{ + margin-top: 20px; +} +input { + width: 100px; + height: 30px; +} +button { + font-size: 16px; +} +#ping-text { + margin-bottom: 30px; +} +#ping-clear { + display: none; +} diff --git a/examples/webenginewidgets/push-notifications/content/worker.js b/examples/webenginewidgets/push-notifications/content/worker.js new file mode 100644 index 000000000..ed660a6e9 --- /dev/null +++ b/examples/webenginewidgets/push-notifications/content/worker.js @@ -0,0 +1,7 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +self.addEventListener('push', event => { + const data = event.data.json(); + self.registration.showNotification(data.title, { body : data.text }); +}); diff --git a/examples/webenginequick/customtouchhandle/qml.qrc b/examples/webenginewidgets/push-notifications/data/data.qrc index 5f6483ac3..619648d1b 100644 --- a/examples/webenginequick/customtouchhandle/qml.qrc +++ b/examples/webenginewidgets/push-notifications/data/data.qrc @@ -1,5 +1,5 @@ <RCC> <qresource prefix="/"> - <file>main.qml</file> + <file>icon.png</file> </qresource> </RCC> diff --git a/examples/webenginewidgets/push-notifications/data/icon.png b/examples/webenginewidgets/push-notifications/data/icon.png Binary files differnew file mode 100644 index 000000000..4c3870c06 --- /dev/null +++ b/examples/webenginewidgets/push-notifications/data/icon.png diff --git a/examples/webenginewidgets/push-notifications/doc/images/notification.png b/examples/webenginewidgets/push-notifications/doc/images/notification.png Binary files differnew file mode 100644 index 000000000..ec5c67457 --- /dev/null +++ b/examples/webenginewidgets/push-notifications/doc/images/notification.png diff --git a/examples/webenginewidgets/push-notifications/doc/images/permissions.png b/examples/webenginewidgets/push-notifications/doc/images/permissions.png Binary files differnew file mode 100644 index 000000000..dedb6e472 --- /dev/null +++ b/examples/webenginewidgets/push-notifications/doc/images/permissions.png diff --git a/examples/webenginewidgets/push-notifications/doc/images/push-notifications.png b/examples/webenginewidgets/push-notifications/doc/images/push-notifications.png Binary files differnew file mode 100644 index 000000000..c5ba5f589 --- /dev/null +++ b/examples/webenginewidgets/push-notifications/doc/images/push-notifications.png diff --git a/examples/webenginewidgets/push-notifications/doc/images/website.png b/examples/webenginewidgets/push-notifications/doc/images/website.png Binary files differnew file mode 100644 index 000000000..10a4a6150 --- /dev/null +++ b/examples/webenginewidgets/push-notifications/doc/images/website.png diff --git a/examples/webenginewidgets/push-notifications/doc/src/push-notifications.qdoc b/examples/webenginewidgets/push-notifications/doc/src/push-notifications.qdoc new file mode 100644 index 000000000..2f234c1e0 --- /dev/null +++ b/examples/webenginewidgets/push-notifications/doc/src/push-notifications.qdoc @@ -0,0 +1,202 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + +\example webenginewidgets/push-notifications +\title WebEngine Push Notifications Example +\ingroup webengine-widgetexamples +\brief Demonstrates how to subscribe to and unsubscribe from push notifications. + +In this example we are going to send push notifications from a web push service to the user. +This is the typical scenario where messages are sent from the application server i.e. website +back-end through a 3rd-party push service, to finally arrive at the user's browser in form of +notifications. To demonstrate this flow, we will implement a simple push service server +application, to which the user can subscribe to receive \e ping messages. + +As already mentioned, in such a workflow there are three different parties involved: + +\list + \li the user's web browser where they receive push notifications + \li 3rd-party push service, which is defined by a subscription endpoint and is a part + of a browser's push service implementation + \li application server, which will store user's subscriptions and initiate push messages + using a subscription endpoint +\endlist + +The user visits a website, where a JavaScript web application uses the JavaScript Push API +to create a push notification subscription. The user is then asked to grant a permission +to receive and display push notifications. Once accepted, the Push API establishes a push channel +with a 3rd-party push service, which in case of QtWebEngine is \l {https://firebase.google.com} +{Firebase Cloud Messaging (FCM)}. The unique push subscription is created that +includes the subscription endpoint URL. The browser then sends a subscription message +to the application server forwarding the endpoint setup. The application server can now +use the subscription endpoint to send notifications to the browser. The browser push service +implementation will deliver a push message. However, to show it, a service worker must +be registered. As the service worker runs in the background, it allows displaying notifications +even if a website, which has installed it, is no longer opened. + +\image push-notifications.png + +Let's go more into implementation details. We start with implementing our custom +push service using NodeJS with two modules: + +\list + \li \l {https://github.com/web-push-libs/web-push} {web-push} - provides the web-push protocol + implementation + \li \l {https://github.com/expressjs/express} {express} - provides the web application framework +\endlist + +Let's initialize a new project and install the required modules in the root directory of this +example: + +\snippet /push-notifications/commands 0 + +These commands should create package.js, which defines the start command: + +\snippet /push-notifications/commands 1 + +Now let's move on to the push service back-end implementation in server.js. + +We start by including the required modules and doing basic \e express framework setup, which +we use to create our custom push server. For simplicity we are going to handle only one +subscription at a time. To do that we need to create \e VAPID keys which we are going to +generate with \e web-push libs. The public key is going to be used by the front-end and authenticate +to the service. + +\quotefromfile webenginewidgets/push-notifications/server.js +\skipto const express +\printto add subscribe route + +\note We are not going to cover the encryption of messages in this example. + +To generate keys, we can use the tool shipped with \e web-push lib, that is installed by +\c npm in our example's root directory. + +\snippet /push-notifications/commands 2 + +Now we add two \c routes to the push server. One to \c subscribe and one to \c unsubscribe, +so that our front-end can send an HTTP POST request to handle the push subscription. +In the subscribe request we will get \c subscription in the request body and we also retrieve +the custom header \c ping-time that defines how often the ping messages should be pushed to +the user. We keep around the \c subscription to be able to send push notifications later. +As a confirmation, we send the 201 status code and schedule the first push notification based on +the \c ping-time value. The \c unsubscribe request simply removes a subscription. + +\quotefromfile webenginewidgets/push-notifications/server.js +\skipto add subscribe route +\printto function sendNotification + +The \c sendNotication() function sends push messages using the web-push lib. We create the payload +with the message we want to present to a user and schedule the next push message. + +\quotefromfile webenginewidgets/push-notifications/server.js +\skipto function sendNotification +\printto server.listen + +In the end we start the server to listen on the given port. + +\quotefromfile webenginewidgets/push-notifications/server.js +\skipto server.listen +\printline started + +Let's move now to our front-end. We create a simple page index.html, where the user can enter how +often they want to receive ping notification messages. We will have two buttons: +\e {Ping Me} to subscribe for push notifications and \e Clear to unsubscribe. +In the end we load ping.js, which we cover next. + +\quotefromfile webenginewidgets/push-notifications/content/index.html +\skipto <body> +\printuntil </body> + +The last piece is creating the logic for the push subscription within the front-end. Here we have two +functions, \c setup and \c clear, to handle subscriptions. When the user clicks on the \e {Ping Me} +button, \c setup is called. To be able to receive notifications, the service worker is needed. +This way the user can leave the website and still get notified as the service worker works +in the background and handles incoming messages. To achieve that, we have to first register +one with: + +\quotefromfile webenginewidgets/push-notifications/content/ping.js +\skipto const register +\printline worker.js + +The call to \c cpushManager.subscribe() will trigger a permission prompt, which is displayed to the +user. If the permission is granted, the push subscription is returned. It includes a URL endpoint +that allows sending notifications to the browser, where the registered service worker waits for +push messages. + +\quotefromfile webenginewidgets/push-notifications/content/ping.js +\skipto var subscription +\printto console.log + +As mentioned the subscription is created for FCM and should be now sent to our custom server +with an HTTP POST request. In addition, we add to the post request the HTTP header with the +\c ping-time the user entered on our website. + +\quotefromfile webenginewidgets/push-notifications/content/ping.js +\skipto await fetch +\printto console.log + +The function \c clear call unsubscribes first from our push server by sending an HTTP POST request +and later from the 3rd-party push service (FCM). + +\quotefromfile webenginewidgets/push-notifications/content/ping.js +\skipuntil async function clear() +\skipuntil { +\printto console.log +\dots +\skipto await fetch +\printto console.log +\dots +\skipto await subscription +\printto console.log + +The rest of code in ping.js is just boilerplate code to read a user provided value +and call \c setup() or \c clear(). + +As the last part of the front-end let's look inside a service worker script, where we simply +register an event listener for \e push events. + +\quotefromfile webenginewidgets/push-notifications/content/worker.js +\skipto self +\printuntil }); +\printuntil }); + +When a push event comes we simply use the Notification JavaScript API to display a notification. + +\note QtWebEngine Notification Example shows how to provide your own handler and customize +the look and feel of a notification message. + +Having the implementation in place, we can start the server on localhost at the port 5000. +To do that, we can simply enter in the console in the project's root directory: + +\snippet /push-notifications/commands 3 + +Now we can look into the \e push-notification browser application, which is based on +\l {WebEngine Notifications Example}: + +\quotefromfile webenginewidgets/push-notifications/main.cpp +\skipto main +\printuntil app.exec +\printuntil } + +This application simply opens the page at \c http:\\localhost:5000. We are not going into detail +about how to open a notification as it is documented \l {WebEngine Notifications Example}{here}. +However, you need to modify the application in two ways. First, \c QWebEngineProfile cannot be +set to \e off-the-record because push messaging would be disabled. Therefore, as you can see +above, \c QWebEngineProfile is initialized with the name. Second, you need to enable push +messaging with the call QWebEngineProfile::setPushServiceEnabled for the created \c profile. + +When the application runs it displays: + +\image website.png + +After granting the permission we can send our ping request: + +\image permissions.png + +We should see the coming push notification: + +\image notification.png + +*/ diff --git a/examples/webenginewidgets/push-notifications/doc/src/push-notifications.qmodel b/examples/webenginewidgets/push-notifications/doc/src/push-notifications.qmodel new file mode 100644 index 000000000..048a55911 --- /dev/null +++ b/examples/webenginewidgets/push-notifications/doc/src/push-notifications.qmodel @@ -0,0 +1,837 @@ +<?xml version="1.0" encoding="UTF-8"?> +<qmt> + <project> + <uid>{9fd14f7e-7b67-4e13-9980-ef8fbe24b780}</uid> + <root-package> + <instance> + <MPackage> + <base-MObject> + <MObject> + <base-MElement> + <MElement> + <uid>{a26e0efd-1b21-4fba-96ce-a607bbcdb4f7}</uid> + </MElement> + </base-MElement> + <name>push_notifications</name> + <children> + <handles> + <handles> + <qlist> + <item> + <handle> + <uid>{6b1a06e9-960b-4a10-872e-5504d8c0b127}</uid> + <target> + <instance type="MCanvasDiagram"> + <MCanvasDiagram> + <base-MDiagram> + <MDiagram> + <base-MObject> + <MObject> + <base-MElement> + <MElement> + <uid>{6b1a06e9-960b-4a10-872e-5504d8c0b127}</uid> + </MElement> + </base-MElement> + <name>push_notifications</name> + </MObject> + </base-MObject> + <elements> + <qlist> + <item> + <instance type="DBoundary"> + <DBoundary> + <base-DElement> + <DElement> + <uid>{d50762f2-c7f4-4d52-9592-8bcc07274ba1}</uid> + </DElement> + </base-DElement> + <text>Browser / User Agent</text> + <pos>x:415;y:390</pos> + <rect>x:-105;y:-125;w:210;h:250</rect> + </DBoundary> + </instance> + </item> + <item> + <instance type="DItem"> + <DItem> + <base-DObject> + <DObject> + <base-DElement> + <DElement> + <uid>{cc80cad6-2e47-4913-aeb1-0c6f41de44bb}</uid> + </DElement> + </base-DElement> + <object>{121189af-36df-4efa-b451-3f1f85ef339f}</object> + <name>JavaScript Web Application</name> + <pos>x:410;y:395</pos> + <rect>x:-75;y:-15;w:150;h:30</rect> + <visual-role>0</visual-role> + </DObject> + </base-DObject> + <shape-editable>false</shape-editable> + </DItem> + </instance> + </item> + <item> + <instance type="DItem"> + <DItem> + <base-DObject> + <DObject> + <base-DElement> + <DElement> + <uid>{8bde1815-304a-41f6-8528-c754dc3d1eda}</uid> + </DElement> + </base-DElement> + <object>{13c69399-5587-4169-a012-8d86216139fd}</object> + <name>Push Manager API</name> + <pos>x:410;y:320</pos> + <rect>x:-75;y:-15;w:150;h:30</rect> + <auto-sized>false</auto-sized> + <visual-role>0</visual-role> + </DObject> + </base-DObject> + <shape-editable>false</shape-editable> + </DItem> + </instance> + </item> + <item> + <instance type="DItem"> + <DItem> + <base-DObject> + <DObject> + <base-DElement> + <DElement> + <uid>{75431119-9e8e-4e3e-a539-9d7bad6dbf81}</uid> + </DElement> + </base-DElement> + <object>{3f1676be-d3a4-4751-8bcb-400b11c88ada}</object> + <name>Service Worker</name> + <pos>x:410;y:470</pos> + <rect>x:-75;y:-15;w:150;h:30</rect> + <auto-sized>false</auto-sized> + <visual-role>0</visual-role> + </DObject> + </base-DObject> + <shape-editable>false</shape-editable> + </DItem> + </instance> + </item> + <item> + <instance type="DDependency"> + <DDependency> + <base-DRelation> + <DRelation> + <base-DElement> + <DElement> + <uid>{0a7b0146-d34b-4c39-a1af-df15a1b4108d}</uid> + </DElement> + </base-DElement> + <object>{648fd8d0-6f61-4cc9-b5ce-6c5b9057fe5d}</object> + <a>{cc80cad6-2e47-4913-aeb1-0c6f41de44bb}</a> + <b>{8bde1815-304a-41f6-8528-c754dc3d1eda}</b> + <name>subscribe</name> + </DRelation> + </base-DRelation> + </DDependency> + </instance> + </item> + <item> + <instance type="DDependency"> + <DDependency> + <base-DRelation> + <DRelation> + <base-DElement> + <DElement> + <uid>{e7d4a675-ddbd-41aa-a802-32221a56dae2}</uid> + </DElement> + </base-DElement> + <object>{afae2401-ea81-4514-aa80-da53d61a8516}</object> + <a>{cc80cad6-2e47-4913-aeb1-0c6f41de44bb}</a> + <b>{75431119-9e8e-4e3e-a539-9d7bad6dbf81}</b> + <name>register</name> + </DRelation> + </base-DRelation> + </DDependency> + </instance> + </item> + <item> + <instance type="DItem"> + <DItem> + <base-DObject> + <DObject> + <base-DElement> + <DElement> + <uid>{998e7d36-3416-4b2f-843a-a437449f09f1}</uid> + </DElement> + </base-DElement> + <object>{5a7b90ea-801e-43ea-a0b5-edaa59761b5b}</object> + <name>Firebase Cloud Messaging </name> + <pos>x:770;y:320</pos> + <rect>x:-75;y:-15;w:150;h:30</rect> + <visual-role>6</visual-role> + </DObject> + </base-DObject> + <shape-editable>false</shape-editable> + </DItem> + </instance> + </item> + <item> + <instance type="DDependency"> + <DDependency> + <base-DRelation> + <DRelation> + <base-DElement> + <DElement> + <uid>{28c1fccd-c50e-458c-865d-4ba41d55690b}</uid> + </DElement> + </base-DElement> + <object>{d9959769-74a4-40ad-bf00-904c12a67309}</object> + <a>{8bde1815-304a-41f6-8528-c754dc3d1eda}</a> + <b>{998e7d36-3416-4b2f-843a-a437449f09f1}</b> + <name>create subscription</name> + </DRelation> + </base-DRelation> + <direction>2</direction> + </DDependency> + </instance> + </item> + <item> + <instance type="DItem"> + <DItem> + <base-DObject> + <DObject> + <base-DElement> + <DElement> + <uid>{ba581e41-cc00-4129-adae-0a1208bb3222}</uid> + </DElement> + </base-DElement> + <object>{13d79379-1727-46a3-947c-e3f2f6eb3a87}</object> + <name>Push Application Server</name> + <pos>x:770;y:390</pos> + <rect>x:-75;y:-15;w:150;h:30</rect> + <auto-sized>false</auto-sized> + <visual-role>6</visual-role> + </DObject> + </base-DObject> + <shape-editable>false</shape-editable> + </DItem> + </instance> + </item> + <item> + <instance type="DDependency"> + <DDependency> + <base-DRelation> + <DRelation> + <base-DElement> + <DElement> + <uid>{e796d1cc-f574-45db-a440-7e47d7560d71}</uid> + </DElement> + </base-DElement> + <object>{373c492c-0b9a-4032-9ff4-b3a7cc442883}</object> + <a>{cc80cad6-2e47-4913-aeb1-0c6f41de44bb}</a> + <b>{ba581e41-cc00-4129-adae-0a1208bb3222}</b> + <name>subscribe with endpoint</name> + </DRelation> + </base-DRelation> + </DDependency> + </instance> + </item> + <item> + <instance type="DDependency"> + <DDependency> + <base-DRelation> + <DRelation> + <base-DElement> + <DElement> + <uid>{6332f8ae-ef9d-4c15-9fbf-d6ee06ba3f84}</uid> + </DElement> + </base-DElement> + <object>{94241d64-4e88-4855-8e78-365be237108b}</object> + <a>{ba581e41-cc00-4129-adae-0a1208bb3222}</a> + <b>{998e7d36-3416-4b2f-843a-a437449f09f1}</b> + <name>push notification</name> + <points> + <qlist> + <item> + <DRelation--IntermediatePoint> + <pos>x:770;y:335</pos> + </DRelation--IntermediatePoint> + </item> + </qlist> + </points> + </DRelation> + </base-DRelation> + </DDependency> + </instance> + </item> + <item> + <instance type="DItem"> + <DItem> + <base-DObject> + <DObject> + <base-DElement> + <DElement> + <uid>{582af542-5f2d-4660-a6d0-a3bd983d9682}</uid> + </DElement> + </base-DElement> + <object>{00b8f12d-e08b-426a-83b8-83fea8907fab}</object> + <name>User</name> + <pos>x:185;y:395</pos> + <rect>x:-10;y:-20;w:20;h:40</rect> + <visual-role>0</visual-role> + </DObject> + </base-DObject> + <variety>actor</variety> + <shape-editable>false</shape-editable> + </DItem> + </instance> + </item> + <item> + <instance type="DDependency"> + <DDependency> + <base-DRelation> + <DRelation> + <base-DElement> + <DElement> + <uid>{66ca12e1-84a1-4247-82e5-ae6891f3f376}</uid> + </DElement> + </base-DElement> + <object>{6d0ce78f-28e1-4a82-a443-59f4efcb8e66}</object> + <a>{582af542-5f2d-4660-a6d0-a3bd983d9682}</a> + <b>{cc80cad6-2e47-4913-aeb1-0c6f41de44bb}</b> + <name>subscribe</name> + </DRelation> + </base-DRelation> + </DDependency> + </instance> + </item> + <item> + <instance type="DDependency"> + <DDependency> + <base-DRelation> + <DRelation> + <base-DElement> + <DElement> + <uid>{7657479a-a855-4d97-8c61-098690da9023}</uid> + </DElement> + </base-DElement> + <object>{eef542f5-e2cf-40a8-92c9-9892f3d06e1f}</object> + <a>{75431119-9e8e-4e3e-a539-9d7bad6dbf81}</a> + <b>{582af542-5f2d-4660-a6d0-a3bd983d9682}</b> + <name>notify</name> + <points> + <qlist> + <item> + <DRelation--IntermediatePoint> + <pos>x:340;y:430</pos> + </DRelation--IntermediatePoint> + </item> + </qlist> + </points> + </DRelation> + </base-DRelation> + </DDependency> + </instance> + </item> + <item> + <instance type="DDependency"> + <DDependency> + <base-DRelation> + <DRelation> + <base-DElement> + <DElement> + <uid>{97f3e5b0-93ca-4351-98d6-ad88567979f7}</uid> + </DElement> + </base-DElement> + <object>{963ad9f9-ecad-430f-8233-b7897fa630bd}</object> + <a>{75431119-9e8e-4e3e-a539-9d7bad6dbf81}</a> + <b>{998e7d36-3416-4b2f-843a-a437449f09f1}</b> + <name>push notification</name> + <points> + <qlist> + <item> + <DRelation--IntermediatePoint> + <pos>x:535;y:470</pos> + </DRelation--IntermediatePoint> + </item> + <item> + <DRelation--IntermediatePoint> + <pos>x:590;y:470</pos> + </DRelation--IntermediatePoint> + </item> + <item> + <DRelation--IntermediatePoint> + <pos>x:615;y:470</pos> + </DRelation--IntermediatePoint> + </item> + <item> + <DRelation--IntermediatePoint> + <pos>x:670;y:470</pos> + </DRelation--IntermediatePoint> + </item> + <item> + <DRelation--IntermediatePoint> + <pos>x:670;y:365</pos> + </DRelation--IntermediatePoint> + </item> + </qlist> + </points> + </DRelation> + </base-DRelation> + <direction>1</direction> + </DDependency> + </instance> + </item> + </qlist> + </elements> + <last-modified>1665083804306</last-modified> + <toolbarid>UseCases</toolbarid> + </MDiagram> + </base-MDiagram> + </MCanvasDiagram> + </instance> + </target> + </handle> + </item> + <item> + <handle> + <uid>{121189af-36df-4efa-b451-3f1f85ef339f}</uid> + <target> + <instance type="MItem"> + <MItem> + <base-MObject> + <MObject> + <base-MElement> + <MElement> + <uid>{121189af-36df-4efa-b451-3f1f85ef339f}</uid> + </MElement> + </base-MElement> + <name>JavaScript Web Application</name> + <relations> + <handles> + <handles> + <qlist> + <item> + <handle> + <uid>{648fd8d0-6f61-4cc9-b5ce-6c5b9057fe5d}</uid> + <target> + <instance type="MDependency"> + <MDependency> + <base-MRelation> + <MRelation> + <base-MElement> + <MElement> + <uid>{648fd8d0-6f61-4cc9-b5ce-6c5b9057fe5d}</uid> + </MElement> + </base-MElement> + <name>subscribe</name> + <a>{121189af-36df-4efa-b451-3f1f85ef339f}</a> + <b>{13c69399-5587-4169-a012-8d86216139fd}</b> + </MRelation> + </base-MRelation> + </MDependency> + </instance> + </target> + </handle> + </item> + <item> + <handle> + <uid>{afae2401-ea81-4514-aa80-da53d61a8516}</uid> + <target> + <instance type="MDependency"> + <MDependency> + <base-MRelation> + <MRelation> + <base-MElement> + <MElement> + <uid>{afae2401-ea81-4514-aa80-da53d61a8516}</uid> + </MElement> + </base-MElement> + <name>register</name> + <a>{121189af-36df-4efa-b451-3f1f85ef339f}</a> + <b>{3f1676be-d3a4-4751-8bcb-400b11c88ada}</b> + </MRelation> + </base-MRelation> + </MDependency> + </instance> + </target> + </handle> + </item> + <item> + <handle> + <uid>{373c492c-0b9a-4032-9ff4-b3a7cc442883}</uid> + <target> + <instance type="MDependency"> + <MDependency> + <base-MRelation> + <MRelation> + <base-MElement> + <MElement> + <uid>{373c492c-0b9a-4032-9ff4-b3a7cc442883}</uid> + </MElement> + </base-MElement> + <name>subscribe with endpoint</name> + <a>{121189af-36df-4efa-b451-3f1f85ef339f}</a> + <b>{13d79379-1727-46a3-947c-e3f2f6eb3a87}</b> + </MRelation> + </base-MRelation> + </MDependency> + </instance> + </target> + </handle> + </item> + </qlist> + </handles> + </handles> + </relations> + </MObject> + </base-MObject> + </MItem> + </instance> + </target> + </handle> + </item> + <item> + <handle> + <uid>{13c69399-5587-4169-a012-8d86216139fd}</uid> + <target> + <instance type="MItem"> + <MItem> + <base-MObject> + <MObject> + <base-MElement> + <MElement> + <uid>{13c69399-5587-4169-a012-8d86216139fd}</uid> + </MElement> + </base-MElement> + <name>Push Manager API</name> + <relations> + <handles> + <handles> + <qlist> + <item> + <handle> + <uid>{d9959769-74a4-40ad-bf00-904c12a67309}</uid> + <target> + <instance type="MDependency"> + <MDependency> + <base-MRelation> + <MRelation> + <base-MElement> + <MElement> + <uid>{d9959769-74a4-40ad-bf00-904c12a67309}</uid> + </MElement> + </base-MElement> + <name>create subscription</name> + <a>{13c69399-5587-4169-a012-8d86216139fd}</a> + <b>{5a7b90ea-801e-43ea-a0b5-edaa59761b5b}</b> + </MRelation> + </base-MRelation> + <direction>2</direction> + </MDependency> + </instance> + </target> + </handle> + </item> + </qlist> + </handles> + </handles> + </relations> + </MObject> + </base-MObject> + </MItem> + </instance> + </target> + </handle> + </item> + <item> + <handle> + <uid>{3f1676be-d3a4-4751-8bcb-400b11c88ada}</uid> + <target> + <instance type="MItem"> + <MItem> + <base-MObject> + <MObject> + <base-MElement> + <MElement> + <uid>{3f1676be-d3a4-4751-8bcb-400b11c88ada}</uid> + </MElement> + </base-MElement> + <name>Service Worker</name> + <relations> + <handles> + <handles> + <qlist> + <item> + <handle> + <uid>{bfebbc32-3541-4c1a-b3c6-a3652f700767}</uid> + <target> + <instance type="MDependency"> + <MDependency> + <base-MRelation> + <MRelation> + <base-MElement> + <MElement> + <uid>{bfebbc32-3541-4c1a-b3c6-a3652f700767}</uid> + </MElement> + </base-MElement> + <name>push notification</name> + <a>{3f1676be-d3a4-4751-8bcb-400b11c88ada}</a> + <b>{5a7b90ea-801e-43ea-a0b5-edaa59761b5b}</b> + </MRelation> + </base-MRelation> + <direction>1</direction> + </MDependency> + </instance> + </target> + </handle> + </item> + <item> + <handle> + <uid>{89d5f886-1913-4bb7-be32-cdd5cb61eebc}</uid> + <target> + <instance type="MDependency"> + <MDependency> + <base-MRelation> + <MRelation> + <base-MElement> + <MElement> + <uid>{89d5f886-1913-4bb7-be32-cdd5cb61eebc}</uid> + </MElement> + </base-MElement> + <name>notify</name> + <a>{3f1676be-d3a4-4751-8bcb-400b11c88ada}</a> + <b>{00b8f12d-e08b-426a-83b8-83fea8907fab}</b> + </MRelation> + </base-MRelation> + </MDependency> + </instance> + </target> + </handle> + </item> + <item> + <handle> + <uid>{eef542f5-e2cf-40a8-92c9-9892f3d06e1f}</uid> + <target> + <instance type="MDependency"> + <MDependency> + <base-MRelation> + <MRelation> + <base-MElement> + <MElement> + <uid>{eef542f5-e2cf-40a8-92c9-9892f3d06e1f}</uid> + </MElement> + </base-MElement> + <name>notify</name> + <a>{3f1676be-d3a4-4751-8bcb-400b11c88ada}</a> + <b>{00b8f12d-e08b-426a-83b8-83fea8907fab}</b> + </MRelation> + </base-MRelation> + </MDependency> + </instance> + </target> + </handle> + </item> + <item> + <handle> + <uid>{963ad9f9-ecad-430f-8233-b7897fa630bd}</uid> + <target> + <instance type="MDependency"> + <MDependency> + <base-MRelation> + <MRelation> + <base-MElement> + <MElement> + <uid>{963ad9f9-ecad-430f-8233-b7897fa630bd}</uid> + </MElement> + </base-MElement> + <name>push notification</name> + <a>{3f1676be-d3a4-4751-8bcb-400b11c88ada}</a> + <b>{5a7b90ea-801e-43ea-a0b5-edaa59761b5b}</b> + </MRelation> + </base-MRelation> + <direction>1</direction> + </MDependency> + </instance> + </target> + </handle> + </item> + </qlist> + </handles> + </handles> + </relations> + </MObject> + </base-MObject> + </MItem> + </instance> + </target> + </handle> + </item> + <item> + <handle> + <uid>{5a7b90ea-801e-43ea-a0b5-edaa59761b5b}</uid> + <target> + <instance type="MItem"> + <MItem> + <base-MObject> + <MObject> + <base-MElement> + <MElement> + <uid>{5a7b90ea-801e-43ea-a0b5-edaa59761b5b}</uid> + </MElement> + </base-MElement> + <name>Firebase Cloud Messaging </name> + <relations> + <handles> + <handles> + <qlist> + <item> + <handle> + <uid>{560fb941-261e-4f45-8909-2106283fdb3e}</uid> + <target> + <instance type="MDependency"> + <MDependency> + <base-MRelation> + <MRelation> + <base-MElement> + <MElement> + <uid>{560fb941-261e-4f45-8909-2106283fdb3e}</uid> + </MElement> + </base-MElement> + <a>{5a7b90ea-801e-43ea-a0b5-edaa59761b5b}</a> + <b>{13c69399-5587-4169-a012-8d86216139fd}</b> + </MRelation> + </base-MRelation> + </MDependency> + </instance> + </target> + </handle> + </item> + </qlist> + </handles> + </handles> + </relations> + </MObject> + </base-MObject> + </MItem> + </instance> + </target> + </handle> + </item> + <item> + <handle> + <uid>{13d79379-1727-46a3-947c-e3f2f6eb3a87}</uid> + <target> + <instance type="MItem"> + <MItem> + <base-MObject> + <MObject> + <base-MElement> + <MElement> + <uid>{13d79379-1727-46a3-947c-e3f2f6eb3a87}</uid> + </MElement> + </base-MElement> + <name>Push Application Server</name> + <relations> + <handles> + <handles> + <qlist> + <item> + <handle> + <uid>{94241d64-4e88-4855-8e78-365be237108b}</uid> + <target> + <instance type="MDependency"> + <MDependency> + <base-MRelation> + <MRelation> + <base-MElement> + <MElement> + <uid>{94241d64-4e88-4855-8e78-365be237108b}</uid> + </MElement> + </base-MElement> + <name>push notification</name> + <a>{13d79379-1727-46a3-947c-e3f2f6eb3a87}</a> + <b>{5a7b90ea-801e-43ea-a0b5-edaa59761b5b}</b> + </MRelation> + </base-MRelation> + </MDependency> + </instance> + </target> + </handle> + </item> + </qlist> + </handles> + </handles> + </relations> + </MObject> + </base-MObject> + </MItem> + </instance> + </target> + </handle> + </item> + <item> + <handle> + <uid>{00b8f12d-e08b-426a-83b8-83fea8907fab}</uid> + <target> + <instance type="MItem"> + <MItem> + <base-MObject> + <MObject> + <base-MElement> + <MElement> + <uid>{00b8f12d-e08b-426a-83b8-83fea8907fab}</uid> + </MElement> + </base-MElement> + <name>User</name> + <relations> + <handles> + <handles> + <qlist> + <item> + <handle> + <uid>{6d0ce78f-28e1-4a82-a443-59f4efcb8e66}</uid> + <target> + <instance type="MDependency"> + <MDependency> + <base-MRelation> + <MRelation> + <base-MElement> + <MElement> + <uid>{6d0ce78f-28e1-4a82-a443-59f4efcb8e66}</uid> + </MElement> + </base-MElement> + <name>subscribe</name> + <a>{00b8f12d-e08b-426a-83b8-83fea8907fab}</a> + <b>{121189af-36df-4efa-b451-3f1f85ef339f}</b> + </MRelation> + </base-MRelation> + </MDependency> + </instance> + </target> + </handle> + </item> + </qlist> + </handles> + </handles> + </relations> + </MObject> + </base-MObject> + <variety-editable>false</variety-editable> + <variety>actor</variety> + </MItem> + </instance> + </target> + </handle> + </item> + </qlist> + </handles> + </handles> + </children> + </MObject> + </base-MObject> + </MPackage> + </instance> + </root-package> + </project> +</qmt> diff --git a/examples/webenginewidgets/push-notifications/main.cpp b/examples/webenginewidgets/push-notifications/main.cpp new file mode 100644 index 000000000..18a862182 --- /dev/null +++ b/examples/webenginewidgets/push-notifications/main.cpp @@ -0,0 +1,41 @@ +// Copyright (C) 2019 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "notificationpopup.h" + +#include <QApplication> +#include <QWebEngineProfile> +#include <QWebEnginePage> +#include <QWebEngineView> + +int main(int argc, char *argv[]) +{ + QCoreApplication::setOrganizationName("QtExamples"); + QApplication app(argc, argv); + + const QString name = + QString::fromLatin1("push-notifications.%1").arg(qWebEngineChromiumVersion()); + + QScopedPointer<QWebEngineProfile> profile(new QWebEngineProfile(name)); + QWebEngineView view(profile.data()); + auto popup = new NotificationPopup(&view); + + QObject::connect(view.page(), &QWebEnginePage::featurePermissionRequested, + [&](const QUrl &origin, QWebEnginePage::Feature feature) { + if (feature != QWebEnginePage::Notifications) + return; + + view.page()->setFeaturePermission(origin, feature, + QWebEnginePage::PermissionGrantedByUser); + }); + + profile->setPushServiceEnabled(true); + profile->setNotificationPresenter([&](std::unique_ptr<QWebEngineNotification> notification) { + popup->present(notification); + }); + + view.resize(640, 480); + view.setUrl(QUrl("http://localhost:5000")); + view.show(); + return app.exec(); +} diff --git a/examples/webenginewidgets/push-notifications/notificationpopup.h b/examples/webenginewidgets/push-notifications/notificationpopup.h new file mode 100644 index 000000000..cf53ded45 --- /dev/null +++ b/examples/webenginewidgets/push-notifications/notificationpopup.h @@ -0,0 +1,91 @@ +// Copyright (C) 2019 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#pragma once + +#include <QHBoxLayout> +#include <QLabel> +#include <QMouseEvent> +#include <QPushButton> +#include <QSpacerItem> +#include <QTimer> +#include <QVBoxLayout> +#include <QWebEngineNotification> + +#include <memory> + +class NotificationPopup : public QWidget +{ + Q_OBJECT + + QLabel m_icon, m_title, m_message; + std::unique_ptr<QWebEngineNotification> notification; + +public: + NotificationPopup(QWidget *parent) : QWidget(parent) + { + setWindowFlags(Qt::ToolTip); + auto rootLayout = new QHBoxLayout(this); + + rootLayout->addWidget(&m_icon); + + auto bodyLayout = new QVBoxLayout; + rootLayout->addLayout(bodyLayout); + + auto titleLayout = new QHBoxLayout; + bodyLayout->addLayout(titleLayout); + + titleLayout->addWidget(&m_title); + titleLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding)); + + auto close = new QPushButton(tr("Close")); + titleLayout->addWidget(close); + connect(close, &QPushButton::clicked, this, &NotificationPopup::onClosed); + + bodyLayout->addWidget(&m_message); + adjustSize(); + } + + void present(std::unique_ptr<QWebEngineNotification> &newNotification) + { + if (notification) { + notification->close(); + notification.reset(); + } + + notification.swap(newNotification); + + m_title.setText("<b>" + notification->title() + "</b>"); + m_message.setText(notification->message()); + m_icon.setPixmap(QPixmap(":/icon.png").scaledToHeight(m_icon.height())); + + show(); + notification->show(); + + connect(notification.get(), &QWebEngineNotification::closed, this, + &NotificationPopup::onClosed); + QTimer::singleShot(10000, notification.get(), [&]() { onClosed(); }); + + // position our popup in the right corner of its parent widget + move(parentWidget()->mapToGlobal(parentWidget()->rect().bottomRight() + - QPoint(width() + 10, height() + 10))); + } + +protected slots: + void onClosed() + { + hide(); + notification->close(); + notification.reset(); + } + +protected: + void mouseReleaseEvent(QMouseEvent *event) override + { + QWidget::mouseReleaseEvent(event); + if (notification && event->button() == Qt::LeftButton) { + notification->click(); + onClosed(); + } + } +}; diff --git a/examples/webenginewidgets/push-notifications/push-notifications.pro b/examples/webenginewidgets/push-notifications/push-notifications.pro new file mode 100644 index 000000000..5ffb85fd7 --- /dev/null +++ b/examples/webenginewidgets/push-notifications/push-notifications.pro @@ -0,0 +1,10 @@ +QT += webenginewidgets + +HEADERS = notificationpopup.h + +SOURCES = main.cpp + +RESOURCES = data/data.qrc + +target.path = $$[QT_INSTALL_EXAMPLES]/webenginewidgets/push-notifications +INSTALLS += target diff --git a/examples/webenginewidgets/push-notifications/server.js b/examples/webenginewidgets/push-notifications/server.js new file mode 100644 index 000000000..fc7deb08a --- /dev/null +++ b/examples/webenginewidgets/push-notifications/server.js @@ -0,0 +1,65 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +const express = require('express'); +const webpush = require('web-push'); + +// setup server +const port = 5000; +const server = express(); +server.use(express.json()); +server.use(express.static('content')); + +// we support only one subscription at the time +var subscription = null; + +// setup vapid keys +const vapidKeys = { + publicKey : + "BNO4fIv439RpvbReeABNlDNiiBD2Maykn7EVnwsPseH7-P5hjnzZLEfnejXVP7Zt6MFoKqKeHm4nV9BHvbgoRPg", + privateKey : "HqhrzsRfG5-SB3j45lyUmV7cYZuy-71r2Bb0tgaOefk" +}; + +// set vapid keys for webpush libs +webpush.setVapidDetails('mailto:push@qt.io', vapidKeys.publicKey, vapidKeys.privateKey); + +// add subscribe route +server.post('/subscribe', (req, res) => { + + // subscription request + subscription = req.body; + const delay = req.headers['ping-time']; + console.log('Got subscription with endpoint: ' + subscription.endpoint); + console.log('Ping delay is at: ' + delay); + + // confirm resource created + res.status(201).json({}); + + // schedule notification + setTimeout(() => { sendNotification(delay) }, delay * 1000); +}); + +// add unsubscribe route +server.post('/unsubscribe', (req, res) => { + console.log('Got unsubscribe with endpoint: ' + req.body.endpoint); + subscription = null; + res.status(201).json({}); +}); + +function sendNotification(delay) +{ + if (!subscription) + return; + + // create payload text + const payload = JSON.stringify({ title : 'Ping !', text : 'Visit qt.io', url : 'www.qt.io' }); + + // send notification + console.log('Sending notification !'); + webpush.sendNotification(subscription, payload).catch(err => console.error(err)); + + // schedule next notification + setTimeout(() => { sendNotification(delay) }, delay * 1000); +} + +server.listen(port, () => console.log(`Push server started at port ${port}`)); diff --git a/examples/webenginewidgets/recipebrowser/CMakeLists.txt b/examples/webenginewidgets/recipebrowser/CMakeLists.txt new file mode 100644 index 000000000..d10409c09 --- /dev/null +++ b/examples/webenginewidgets/recipebrowser/CMakeLists.txt @@ -0,0 +1,71 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +cmake_minimum_required(VERSION 3.16) +project(recipebrowser LANGUAGES CXX) + +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTOUIC ON) + +if(NOT DEFINED INSTALL_EXAMPLESDIR) + set(INSTALL_EXAMPLESDIR "examples") +endif() + +set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/webenginewidgets/recipebrowser") + +find_package(Qt6 REQUIRED COMPONENTS Core Gui WebEngineWidgets) + +qt_add_executable(recipebrowser + main.cpp + mainwindow.cpp mainwindow.h mainwindow.ui + stylesheetdialog.cpp stylesheetdialog.h stylesheetdialog.ui + document.cpp document.h +) + +set_target_properties(recipebrowser PROPERTIES + WIN32_EXECUTABLE TRUE + MACOSX_BUNDLE TRUE +) + +target_link_libraries(recipebrowser PUBLIC + Qt::Core + Qt::Gui + Qt::WebEngineWidgets +) + +# Resources: +set(recipebrowser_resource_files + "assets/3rdparty/markdown.css" + "assets/3rdparty/marked.js" + "assets/custom.css" + "assets/custom.js" + "assets/pages/burger.html" + "assets/pages/cupcakes.html" + "assets/pages/images/burger.jpg" + "assets/pages/images/cupcakes.jpg" + "assets/pages/images/pasta.jpg" + "assets/pages/images/pizza.jpg" + "assets/pages/images/skewers.jpg" + "assets/pages/images/soup.jpg" + "assets/pages/images/steak.jpg" + "assets/pages/pasta.html" + "assets/pages/pizza.html" + "assets/pages/skewers.html" + "assets/pages/soup.html" + "assets/pages/steak.html" +) + +qt_add_resources(recipebrowser "recipebrowser" + PREFIX + "/" + BASE + "assets" + FILES + ${recipebrowser_resource_files} +) + +install(TARGETS recipebrowser + RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" + LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" +) diff --git a/examples/webenginequick/recipebrowser/resources/pages/assets/3rdparty/MARKDOWN-LICENSE.txt b/examples/webenginewidgets/recipebrowser/assets/3rdparty/MARKDOWN-LICENSE.txt index 23c52cc43..23c52cc43 100644 --- a/examples/webenginequick/recipebrowser/resources/pages/assets/3rdparty/MARKDOWN-LICENSE.txt +++ b/examples/webenginewidgets/recipebrowser/assets/3rdparty/MARKDOWN-LICENSE.txt diff --git a/examples/webenginequick/recipebrowser/resources/pages/assets/3rdparty/MARKED-LICENSE.txt b/examples/webenginewidgets/recipebrowser/assets/3rdparty/MARKED-LICENSE.txt index 8e3ba0e0a..8e3ba0e0a 100644 --- a/examples/webenginequick/recipebrowser/resources/pages/assets/3rdparty/MARKED-LICENSE.txt +++ b/examples/webenginewidgets/recipebrowser/assets/3rdparty/MARKED-LICENSE.txt diff --git a/examples/webenginequick/recipebrowser/resources/pages/assets/3rdparty/markdown.css b/examples/webenginewidgets/recipebrowser/assets/3rdparty/markdown.css index 24fc2ffe2..24fc2ffe2 100644 --- a/examples/webenginequick/recipebrowser/resources/pages/assets/3rdparty/markdown.css +++ b/examples/webenginewidgets/recipebrowser/assets/3rdparty/markdown.css diff --git a/examples/webenginequick/recipebrowser/resources/pages/assets/3rdparty/marked.js b/examples/webenginewidgets/recipebrowser/assets/3rdparty/marked.js index 33c02d9cf..33c02d9cf 100644 --- a/examples/webenginequick/recipebrowser/resources/pages/assets/3rdparty/marked.js +++ b/examples/webenginewidgets/recipebrowser/assets/3rdparty/marked.js diff --git a/examples/webenginewidgets/recipebrowser/assets/3rdparty/qt_attribution.json b/examples/webenginewidgets/recipebrowser/assets/3rdparty/qt_attribution.json new file mode 100644 index 000000000..f8b0fd023 --- /dev/null +++ b/examples/webenginewidgets/recipebrowser/assets/3rdparty/qt_attribution.json @@ -0,0 +1,34 @@ +[ + { + "Id" : "recipebrowser-marked", + "Name" : "Marked (WebEngine Recipe Browser example)", + "QDocModule" : "qtwebengine", + "QtUsage" : "Marked is used in the WebEngine Recipe Browser example", + "QtParts" : ["examples"], + "Files" : "marked.js", + "Description" : + "A full-featured markdown parser and compiler, written in JavaScript. Built for speed.", + "Homepage" : "https://github.com/chjj/marked", + "Version" : "0.4.0", + "DownloadLocation" : "https://github.com/markedjs/marked/blob/0.4.0/lib/marked.js", + "Copyright" : "Copyright (c) 2011-2018, Christopher Jeffrey", + "License" : "MIT License", + "LicenseId" : "MIT", + "LicenseFile" : "MARKED-LICENSE.txt" + }, + { + "Id" : "recipebrowser-markdowncss", + "Name" : "Markdown.css (WebEngine Recipe Browser example)", + "QDocModule" : "qtwebengine", + "QtUsage" : "markdown.css is used in the WebEngine Recipe Browser example", + "QtParts" : ["examples"], + "Files" : "markdown.css", + "Description" : "Markdown.css is better default styling for your Markdown files.", + "Version" : "188530e4b5d020d7e237fc6b26be13ebf4a8def3", + "DownloadLocation" : "https://bitbucket.org/kevinburke/markdowncss/src/188530e4b5d020d7e237fc6b26be13ebf4a8def3/markdown.css", + "Copyright" : "Copyright 2011 Kevin Burke Copyright Twitter Inc.", + "License" : "Apache License 2.0", + "LicenseId" : "Apache-2.0", + "LicenseFile" : "MARKDOWN-LICENSE.txt" + } + ] diff --git a/examples/webenginequick/recipebrowser/resources/pages/assets/custom.css b/examples/webenginewidgets/recipebrowser/assets/custom.css index 8d2f6cb0b..cc1106af3 100644 --- a/examples/webenginequick/recipebrowser/resources/pages/assets/custom.css +++ b/examples/webenginewidgets/recipebrowser/assets/custom.css @@ -1,4 +1,4 @@ -// Copyright (C) 2016 The Qt Company Ltd. +// Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause body { diff --git a/examples/webenginewidgets/recipebrowser/assets/custom.js b/examples/webenginewidgets/recipebrowser/assets/custom.js new file mode 100644 index 000000000..34470164e --- /dev/null +++ b/examples/webenginewidgets/recipebrowser/assets/custom.js @@ -0,0 +1,13 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +marked.setOptions({ + renderer : new marked.Renderer(), + gfm : true, + tables : true, + breaks : false, + pedantic : false, + sanitize : false, + smartLists : true, + smartypants : false +}); diff --git a/examples/webenginequick/recipebrowser/resources/pages/burger.html b/examples/webenginewidgets/recipebrowser/assets/pages/burger.html index 6651cc0f0..99497959f 100644 --- a/examples/webenginequick/recipebrowser/resources/pages/burger.html +++ b/examples/webenginewidgets/recipebrowser/assets/pages/burger.html @@ -3,8 +3,11 @@ <head> <meta charset="utf-8"> <title>Insanity Burger</title> - <link rel="stylesheet" type="text/css" href="assets/3rdparty/markdown.css"> - <link rel="stylesheet" type="text/css" href="assets/custom.css"> + <link rel="stylesheet" type="text/css" href="../3rdparty/markdown.css"> + <link rel="stylesheet" type="text/css" href="../custom.css"> + <script src="../3rdparty/marked.js"></script> + <script src="../custom.js"></script> + <script src="qrc:/qtwebchannel/qwebchannel.js"></script> </head> <body> <div id="placeholder"></div> @@ -68,8 +71,24 @@ give it a minute to go gorgeous and sloppy. </div><!--End of content--> - <script src="assets/3rdparty/marked.js"></script> - <script src="assets/custom.js"></script> + <script> + 'use strict'; + + var jsContent = document.getElementById('content'); + var placeholder = document.getElementById('placeholder'); + + var updateText = function(text) { + placeholder.innerHTML = marked.parse(text); + } + + new QWebChannel(qt.webChannelTransport, + function(channel) { + var content = channel.objects.content; + content.setInitialText(jsContent.innerHTML); + content.textChanged.connect(updateText); + } + ); + </script> </body> </html> diff --git a/examples/webenginequick/recipebrowser/resources/pages/cupcakes.html b/examples/webenginewidgets/recipebrowser/assets/pages/cupcakes.html index 4791c7ffa..e8e14a9b6 100644 --- a/examples/webenginequick/recipebrowser/resources/pages/cupcakes.html +++ b/examples/webenginewidgets/recipebrowser/assets/pages/cupcakes.html @@ -3,8 +3,11 @@ <head> <meta charset="utf-8"> <title>Cupcakes</title> - <link rel="stylesheet" type="text/css" href="assets/3rdparty/markdown.css"> - <link rel="stylesheet" type="text/css" href="assets/custom.css"> + <link rel="stylesheet" type="text/css" href="../3rdparty/markdown.css"> + <link rel="stylesheet" type="text/css" href="../custom.css"> + <script src="../3rdparty/marked.js"></script> + <script src="../custom.js"></script> + <script src="qrc:/qtwebchannel/qwebchannel.js"></script> </head> <body> <div id="placeholder"></div> @@ -46,8 +49,25 @@ Cupcakes **Enjoy!** </div><!--End of content--> - <script src="assets/3rdparty/marked.js"></script> - <script src="assets/custom.js"></script> + + <script> + 'use strict'; + + var jsContent = document.getElementById('content'); + var placeholder = document.getElementById('placeholder'); + + var updateText = function(text) { + placeholder.innerHTML = marked.parse(text); + } + + new QWebChannel(qt.webChannelTransport, + function(channel) { + var content = channel.objects.content; + content.setInitialText(jsContent.innerHTML); + content.textChanged.connect(updateText); + } + ); + </script> </body> </html> diff --git a/examples/webenginequick/recipebrowser/resources/pages/images/burger.jpg b/examples/webenginewidgets/recipebrowser/assets/pages/images/burger.jpg Binary files differindex edc0c65de..edc0c65de 100644 --- a/examples/webenginequick/recipebrowser/resources/pages/images/burger.jpg +++ b/examples/webenginewidgets/recipebrowser/assets/pages/images/burger.jpg diff --git a/examples/webenginequick/recipebrowser/resources/pages/images/cupcakes.jpg b/examples/webenginewidgets/recipebrowser/assets/pages/images/cupcakes.jpg Binary files differindex cce52ba23..cce52ba23 100644 --- a/examples/webenginequick/recipebrowser/resources/pages/images/cupcakes.jpg +++ b/examples/webenginewidgets/recipebrowser/assets/pages/images/cupcakes.jpg diff --git a/examples/webenginequick/recipebrowser/resources/pages/images/pasta.jpg b/examples/webenginewidgets/recipebrowser/assets/pages/images/pasta.jpg Binary files differindex 7ac924b79..7ac924b79 100644 --- a/examples/webenginequick/recipebrowser/resources/pages/images/pasta.jpg +++ b/examples/webenginewidgets/recipebrowser/assets/pages/images/pasta.jpg diff --git a/examples/webenginequick/recipebrowser/resources/pages/images/pizza.jpg b/examples/webenginewidgets/recipebrowser/assets/pages/images/pizza.jpg Binary files differindex 8d8f756af..8d8f756af 100644 --- a/examples/webenginequick/recipebrowser/resources/pages/images/pizza.jpg +++ b/examples/webenginewidgets/recipebrowser/assets/pages/images/pizza.jpg diff --git a/examples/webenginequick/recipebrowser/resources/pages/images/skewers.jpg b/examples/webenginewidgets/recipebrowser/assets/pages/images/skewers.jpg Binary files differindex 6bb2f1172..6bb2f1172 100644 --- a/examples/webenginequick/recipebrowser/resources/pages/images/skewers.jpg +++ b/examples/webenginewidgets/recipebrowser/assets/pages/images/skewers.jpg diff --git a/examples/webenginequick/recipebrowser/resources/pages/images/soup.jpg b/examples/webenginewidgets/recipebrowser/assets/pages/images/soup.jpg Binary files differindex fc9dff906..fc9dff906 100644 --- a/examples/webenginequick/recipebrowser/resources/pages/images/soup.jpg +++ b/examples/webenginewidgets/recipebrowser/assets/pages/images/soup.jpg diff --git a/examples/webenginequick/recipebrowser/resources/pages/images/steak.jpg b/examples/webenginewidgets/recipebrowser/assets/pages/images/steak.jpg Binary files differindex 240b72eb4..240b72eb4 100644 --- a/examples/webenginequick/recipebrowser/resources/pages/images/steak.jpg +++ b/examples/webenginewidgets/recipebrowser/assets/pages/images/steak.jpg diff --git a/examples/webenginequick/recipebrowser/resources/pages/pasta.html b/examples/webenginewidgets/recipebrowser/assets/pages/pasta.html index 41ed1a756..c2b3d840a 100644 --- a/examples/webenginequick/recipebrowser/resources/pages/pasta.html +++ b/examples/webenginewidgets/recipebrowser/assets/pages/pasta.html @@ -3,8 +3,11 @@ <head> <meta charset="utf-8"> <title>Pasta</title> - <link rel="stylesheet" type="text/css" href="assets/3rdparty/markdown.css"> - <link rel="stylesheet" type="text/css" href="assets/custom.css"> + <link rel="stylesheet" type="text/css" href="../3rdparty/markdown.css"> + <link rel="stylesheet" type="text/css" href="../custom.css"> + <script src="../3rdparty/marked.js"></script> + <script src="../custom.js"></script> + <script src="qrc:/qtwebchannel/qwebchannel.js"></script> </head> <body> <div id="placeholder"></div> @@ -49,8 +52,25 @@ Pasta **Enjoy!** </div><!--End of content--> - <script src="assets/3rdparty/marked.js"></script> - <script src="assets/custom.js"></script> + + <script> + 'use strict'; + + var jsContent = document.getElementById('content'); + var placeholder = document.getElementById('placeholder'); + + var updateText = function(text) { + placeholder.innerHTML = marked.parse(text); + } + + new QWebChannel(qt.webChannelTransport, + function(channel) { + var content = channel.objects.content; + content.setInitialText(jsContent.innerHTML); + content.textChanged.connect(updateText); + } + ); + </script> </body> </html> diff --git a/examples/webenginequick/recipebrowser/resources/pages/pizza.html b/examples/webenginewidgets/recipebrowser/assets/pages/pizza.html index 348d809e8..7e390a373 100644 --- a/examples/webenginequick/recipebrowser/resources/pages/pizza.html +++ b/examples/webenginewidgets/recipebrowser/assets/pages/pizza.html @@ -3,8 +3,11 @@ <head> <meta charset="utf-8"> <title>Pizza Diavola</title> - <link rel="stylesheet" type="text/css" href="assets/3rdparty/markdown.css"> - <link rel="stylesheet" type="text/css" href="assets/custom.css"> + <link rel="stylesheet" type="text/css" href="../3rdparty/markdown.css"> + <link rel="stylesheet" type="text/css" href="../custom.css"> + <script src="../3rdparty/marked.js"></script> + <script src="../custom.js"></script> + <script src="qrc:/qtwebchannel/qwebchannel.js"></script> </head> <body> <div id="placeholder"></div> @@ -40,8 +43,25 @@ Pizza Diavola **Enjoy!** </div><!--End of content--> - <script src="assets/3rdparty/marked.js"></script> - <script src="assets/custom.js"></script> + + <script> + 'use strict'; + + var jsContent = document.getElementById('content'); + var placeholder = document.getElementById('placeholder'); + + var updateText = function(text) { + placeholder.innerHTML = marked.parse(text); + } + + new QWebChannel(qt.webChannelTransport, + function(channel) { + var content = channel.objects.content; + content.setInitialText(jsContent.innerHTML); + content.textChanged.connect(updateText); + } + ); + </script> </body> </html> diff --git a/examples/webenginequick/recipebrowser/resources/pages/skewers.html b/examples/webenginewidgets/recipebrowser/assets/pages/skewers.html index aca4c4859..db2df5472 100644 --- a/examples/webenginequick/recipebrowser/resources/pages/skewers.html +++ b/examples/webenginewidgets/recipebrowser/assets/pages/skewers.html @@ -3,8 +3,11 @@ <head> <meta charset="utf-8"> <title>Grilled skewers</title> - <link rel="stylesheet" type="text/css" href="assets/3rdparty/markdown.css"> - <link rel="stylesheet" type="text/css" href="assets/custom.css"> + <link rel="stylesheet" type="text/css" href="../3rdparty/markdown.css"> + <link rel="stylesheet" type="text/css" href="../custom.css"> + <script src="../3rdparty/marked.js"></script> + <script src="../custom.js"></script> + <script src="qrc:/qtwebchannel/qwebchannel.js"></script> </head> <body> <div id="placeholder"></div> @@ -46,8 +49,24 @@ Grilled skewers </div><!--End of content--> - <script src="assets/3rdparty/marked.js"></script> - <script src="assets/custom.js"></script> + <script> + 'use strict'; + + var jsContent = document.getElementById('content'); + var placeholder = document.getElementById('placeholder'); + + var updateText = function(text) { + placeholder.innerHTML = marked.parse(text); + } + + new QWebChannel(qt.webChannelTransport, + function(channel) { + var content = channel.objects.content; + content.setInitialText(jsContent.innerHTML); + content.textChanged.connect(updateText); + } + ); + </script> </body> </html> diff --git a/examples/webenginequick/recipebrowser/resources/pages/soup.html b/examples/webenginewidgets/recipebrowser/assets/pages/soup.html index 1b7027e5d..ea51fc8a5 100644 --- a/examples/webenginequick/recipebrowser/resources/pages/soup.html +++ b/examples/webenginewidgets/recipebrowser/assets/pages/soup.html @@ -3,8 +3,11 @@ <head> <meta charset="utf-8"> <title>Soup</title> - <link rel="stylesheet" type="text/css" href="assets/3rdparty/markdown.css"> - <link rel="stylesheet" type="text/css" href="assets/custom.css"> + <link rel="stylesheet" type="text/css" href="../3rdparty/markdown.css"> + <link rel="stylesheet" type="text/css" href="../custom.css"> + <script src="../3rdparty/marked.js"></script> + <script src="../custom.js"></script> + <script src="qrc:/qtwebchannel/qwebchannel.js"></script> </head> <body> <div id="placeholder"></div> @@ -34,8 +37,25 @@ Soup **Enjoy!** </div><!--End of content--> - <script src="assets/3rdparty/marked.js"></script> - <script src="assets/custom.js"></script> + + <script> + 'use strict'; + + var jsContent = document.getElementById('content'); + var placeholder = document.getElementById('placeholder'); + + var updateText = function(text) { + placeholder.innerHTML = marked.parse(text); + } + + new QWebChannel(qt.webChannelTransport, + function(channel) { + var content = channel.objects.content; + content.setInitialText(jsContent.innerHTML); + content.textChanged.connect(updateText); + } + ); + </script> </body> </html> diff --git a/examples/webenginequick/recipebrowser/resources/pages/steak.html b/examples/webenginewidgets/recipebrowser/assets/pages/steak.html index a56313e27..26391b409 100644 --- a/examples/webenginequick/recipebrowser/resources/pages/steak.html +++ b/examples/webenginewidgets/recipebrowser/assets/pages/steak.html @@ -3,8 +3,11 @@ <head> <meta charset="utf-8"> <title>Grilled steak and rice</title> - <link rel="stylesheet" type="text/css" href="assets/3rdparty/markdown.css"> - <link rel="stylesheet" type="text/css" href="assets/custom.css"> + <link rel="stylesheet" type="text/css" href="../3rdparty/markdown.css"> + <link rel="stylesheet" type="text/css" href="../custom.css"> + <script src="../3rdparty/marked.js"></script> + <script src="../custom.js"></script> + <script src="qrc:/qtwebchannel/qwebchannel.js"></script> </head> <body> <div id="placeholder"></div> @@ -60,8 +63,24 @@ Grilled steak and rice </div><!--End of content--> - <script src="assets/3rdparty/marked.js"></script> - <script src="assets/custom.js"></script> + <script> + 'use strict'; + + var jsContent = document.getElementById('content'); + var placeholder = document.getElementById('placeholder'); + + var updateText = function(text) { + placeholder.innerHTML = marked.parse(text); + } + + new QWebChannel(qt.webChannelTransport, + function(channel) { + var content = channel.objects.content; + content.setInitialText(jsContent.innerHTML); + content.textChanged.connect(updateText); + } + ); + </script> </body> </html> diff --git a/examples/webenginewidgets/recipebrowser/doc/images/recipebrowser.webp b/examples/webenginewidgets/recipebrowser/doc/images/recipebrowser.webp Binary files differnew file mode 100644 index 000000000..8446bcde3 --- /dev/null +++ b/examples/webenginewidgets/recipebrowser/doc/images/recipebrowser.webp diff --git a/examples/webenginewidgets/recipebrowser/doc/src/recipebrowser.qdoc b/examples/webenginewidgets/recipebrowser/doc/src/recipebrowser.qdoc new file mode 100644 index 000000000..dd701f1f2 --- /dev/null +++ b/examples/webenginewidgets/recipebrowser/doc/src/recipebrowser.qdoc @@ -0,0 +1,197 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \example webenginewidgets/recipebrowser + \title Recipe Browser + \examplecategory {Application Examples} + \meta tag {widgets, webengine, webchannel, webenginescript} + \ingroup webengine-widgetexamples + \brief Injecting custom stylsheets into web pages and providing a rich text preview + tool for a custom markup language. + + \image recipebrowser.webp + + \e {Recipe Browser} is a small hybrid web browser application. It demonstrates how to + use the \l{Qt WebEngine Widgets C++ Classes} {Qt WebEngine C++ classes} to combine + C++ and JavaScript logic in the following ways. + + \list + \li Running arbitrary JavaScript code via \c QWebEnginePage::runJavaScript() to + inject custom CSS stylesheets + \li Using QWebEngineScript and QWebEngineScriptCollection to persist the JavaScript + code and inject it to every page + \li Using QWebChannel to interact with and provide a rich text preview for a custom + markup language + \endlist + + \l{http://daringfireball.net/projects/markdown/}{Markdown} is a lightweight + markup language with a plain text formatting syntax. + Some services, such as \l{http://github.com}{github}, acknowledge the + format, and render the content as rich text when viewed in a browser. + + The Recipe Browser main window is split into a navigation on the left and + a preview area on the right. The preview area on the right switches to an + editor when the user clicks the Edit button on the top left of the main window. + The editor supports the Markdown syntax and is implemented by using + QPlainTextEdit. The document is rendered as rich text in the preview area, + once the user clicks the View button,to which the Edit button transforms to. + This rendering is implemented by using QWebEngineView. To render the text, + a JavaScript library inside the web engine converts the Markdown text to HTML. + The preview is updated from the editor through QWebChannel. + + \include examples-run.qdocinc + + \section1 Exposing Document Text + + To render the current Markdown text it needs to be exposed to the web engine through + QWebChannel. To achieve this it has to be part of Qt metatype system. This is done + by using a dedicated \c Document class that exposes the document text as a + \c {Q_PROPERTY}: + + \quotefromfile webenginewidgets/recipebrowser/document.h + \skipto class Document + \printto #endif + + The \c Document class wraps a QString \c m_currentText to be set on the C++ + side with the \c setText() method and exposes it at runtime as a \c text + property with a \c textChanged signal. We define the \c setText method as + follows: + + \quotefromfile webenginewidgets/recipebrowser/document.cpp + \skipto Document::setText(const QString &text) + \printuntil /^\}/ + + Additionally, the \c Document class keeps track of the current recipe via + \c m_currentPage. We call the recipes pages here, because each recipe has + its distinct HTML document that contains the initial text content. + Furthermore, \c m_textCollection is a QMap<QString, QString> that contains + the key/value pairs \{page, text\}, so that changes made to the text content + of a page is persisted between navigation. Nevertheless, we do not write the + modified text contents to the drive, but instead we persist them between + application start and shutdown via QSettings. + + \section1 Creating the Main Window + + The \c MainWindow class inherits the QMainWindow class: + + \quotefromfile webenginewidgets/recipebrowser/mainwindow.h + \skipto class MainWindow : + \printto endif + + The class declares private slots that match the two buttons on the + top left, over the navigation list view. Additionally, helper + methods for custom CSS stylesheets are declared. + + The actual layout of the main window is specified in a \c .ui file. + The widgets and actions are available at runtime in the \c ui member + variable. + + \c m_isEditMode is a boolean that toggles between the editor and the + preview area. + \c m_content is an instance of the \c Document class. + + The actual setup of the different objects is done in the \c MainWindow + constructor: + + \quotefromfile webenginewidgets/recipebrowser/mainwindow.cpp + \skipto MainWindow::MainWindow + \printto connect + + The constructor first calls \c setupUi to construct the widgets and menu + actions according to the UI file. The text editor font is set to one + with a fixed character width, and the QWebEngineView widget is told not + to show a context menu. Furthermore, the editor is hidden away. + + \printto ui->recipes + + Here the \c clicked signals of QPushButton are connected to respective functions + that show the stylesheets dialog or toggle between edit and view mode, that is, + hide and show the editor and preview area respectively. + + \printto m_content.setTextEdit + + Here the navigation QListWidget on the left is setup with the 7 recipes. + Also, the currentItemChanged signal of QListWidget is connected to a lambda + that loads the new, current recipe page and updates the page in \c m_content. + + \printto connect + + Next, the pointer to the ui editor, a QPlainTextEdit, is passed to \c m_content to ensure that + calls to \c Document::setInitialText() work properly. + + \printto QSettings + + Here the \c textChanged signal of the editor is connected to a lambda that + updates the text in \c m_content. This object is then exposed to the JS side + by \c QWebChannel under the name \c{content}. + + \printto ui->recipes + + By using QSettings we persist stylesheets between application runs. If there + should be no stylesheets configured, for example, because the user deleted all of them + in a previous run, we load default ones. + + \printto } + + Finally, we set the currently selected list item to the first contained in the + navigation list widget. This triggers the previously mentioned + QListWidget::currentItemChanged signal and navigates to the page of the list item. + + \section1 Working With Stylesheets + + We use JavaScript to create and append CSS elements to the documents. + After declaring the script source, QWebEnginePage::runJavaScript() can run it + immediately and apply newly created styles on the current content of the web view. + Encapsulating the script into a QWebEngineScript and adding it to the script collection + of QWebEnginePage makes its effect permanent. + + \quotefromfile webenginewidgets/recipebrowser/mainwindow.cpp + \skipto MainWindow::insertStyleSheet + \printuntil /^\}/ + + Removing stylesheets can be done similarly: + + \quotefromfile webenginewidgets/recipebrowser/mainwindow.cpp + \skipto MainWindow::removeStyleSheet + \printuntil /^\}/ + + \section1 Creating a recipe file + + \quotefile webenginewidgets/recipebrowser/assets/pages/burger.html + + All the different recipe pages are set up the same way. + + In the \c <head> part they + include two CSS files: \c markdown.css, that styles the markdown, and + custom.css, that does some further styling but most importantly hides the + \c <div> with id \e content, as this \c <div> only contains the unmodified, + initial content text. Also, three JS scripts are included. \c marked.js + is responsible for parsing the markdown and transforming it into HTML. + \c custom.js does some configuration of \c marked.js, and \c qwebchannel.js + exposes the QWebChannel JavaScript API. + + In the body there are two \c <div> elements. The \c <div> with id \e placeholder + gets the markdown text injected that is rendered and visible. The \c <div> with id + \e content is hidden by \c custom.css and only contains the original, unmodified + text content of the recipe. + + Finally, on the bottom of each recipe HTML file is a script that is responsible for + the communication between the C++ and JavaScript side via QWebChannel. The original, + unmodified text content inside the \c <div> with id \e content is passed to the C++ + side and a callback is setup that is invoked when the \c textChanged signal of + \c m_content is emitted. The callback then updates the contents of the \c <div> + \e placeholder with the parsed markdown. + + \section1 Files and Attributions + + The example bundles the following code with third-party licenses: + \table + \row + \li \l{recipebrowser-marked}{Marked} + \li MIT License + \row + \li \l{recipebrowser-markdowncss}{Markdown.css} + \li Apache License 2.0 + \endtable +*/ diff --git a/examples/webenginewidgets/recipebrowser/document.cpp b/examples/webenginewidgets/recipebrowser/document.cpp new file mode 100644 index 000000000..c991e14f8 --- /dev/null +++ b/examples/webenginewidgets/recipebrowser/document.cpp @@ -0,0 +1,48 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "document.h" + +#include <QSettings> + +Document::Document(QObject *parent) : QObject(parent) +{ + QSettings settings; + settings.beginGroup("textCollection"); + QStringList pageTexts = settings.allKeys(); + for (const QString &name : std::as_const(pageTexts)) { + QString pageText = settings.value(name).value<QString>(); + if (!pageText.isEmpty()) + m_textCollection.insert(name, pageText); + } + settings.endGroup(); +} + +void Document::setTextEdit(QPlainTextEdit *textEdit) +{ + m_textEdit = textEdit; +} + +void Document::setCurrentPage(const QString &page) +{ + m_currentPage = page; +} + +void Document::setInitialText(const QString &text) +{ + m_textEdit->setPlainText(m_textCollection.value(m_currentPage, text)); +} + +void Document::setText(const QString &text) +{ + if (text == m_currentText) + return; + m_currentText = text; + emit textChanged(m_currentText); + + QSettings settings; + settings.beginGroup("textCollection"); + settings.setValue(m_currentPage, text); + m_textCollection.insert(m_currentPage, text); + settings.endGroup(); +} diff --git a/examples/webenginewidgets/recipebrowser/document.h b/examples/webenginewidgets/recipebrowser/document.h new file mode 100644 index 000000000..f6b537eb8 --- /dev/null +++ b/examples/webenginewidgets/recipebrowser/document.h @@ -0,0 +1,36 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef DOCUMENT_H +#define DOCUMENT_H + +#include <QObject> +#include <QString> +#include <QPlainTextEdit> + +class Document : public QObject +{ + Q_OBJECT + Q_PROPERTY(QString text MEMBER m_currentText NOTIFY textChanged FINAL) +public: + explicit Document(QObject *parent = nullptr); + + void setTextEdit(QPlainTextEdit *textEdit); + void setCurrentPage(const QString &page); + +public slots: + void setInitialText(const QString &text); + void setText(const QString &text); + +signals: + void textChanged(const QString &text); + +private: + QPlainTextEdit *m_textEdit; + + QString m_currentText; + QString m_currentPage; + QMap<QString, QString> m_textCollection; +}; + +#endif // DOCUMENT_H diff --git a/examples/webenginewidgets/stylesheetbrowser/main.cpp b/examples/webenginewidgets/recipebrowser/main.cpp index cf1e5f718..b0b42d16b 100644 --- a/examples/webenginewidgets/stylesheetbrowser/main.cpp +++ b/examples/webenginewidgets/recipebrowser/main.cpp @@ -1,4 +1,4 @@ -// Copyright (C) 2017 The Qt Company Ltd. +// Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause #include "mainwindow.h" @@ -12,7 +12,7 @@ int main(int argc, char *argv[]) QCoreApplication::setOrganizationName("QtExamples"); QApplication a(argc, argv); - MainWindow w(QUrl("http://qt.io")); + MainWindow w; w.show(); return a.exec(); } diff --git a/examples/webenginewidgets/recipebrowser/mainwindow.cpp b/examples/webenginewidgets/recipebrowser/mainwindow.cpp new file mode 100644 index 000000000..7288fe4ce --- /dev/null +++ b/examples/webenginewidgets/recipebrowser/mainwindow.cpp @@ -0,0 +1,153 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "mainwindow.h" +#include "stylesheetdialog.h" +#include "ui_mainwindow.h" + +#include <QWebChannel> + +static QMap<QString, QString> defaultStyleSheets = { + { "Upside down", "body { -webkit-transform: rotate(180deg); }" }, + { "Darkmode", + "body { color: #FFF; background-color: #000 }" + "h1, h2, h3, h4, h5 { color: #FFF }" } +}; + +MainWindow::MainWindow(QWidget *parent) + : QMainWindow(parent), ui(new Ui::MainWindow), m_isEditMode(false) +{ + ui->setupUi(this); + ui->textEdit->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); + ui->textEdit->hide(); + ui->webEngineView->setContextMenuPolicy(Qt::NoContextMenu); + + connect(ui->stylesheetsButton, &QPushButton::clicked, this, &MainWindow::showStyleSheetsDialog); + connect(ui->editViewButton, &QPushButton::clicked, this, &MainWindow::toggleEditView); + + ui->recipes->insertItem(0, "Burger"); + ui->recipes->insertItem(1, "Cupcakes"); + ui->recipes->insertItem(2, "Pasta"); + ui->recipes->insertItem(3, "Pizza"); + ui->recipes->insertItem(4, "Skewers"); + ui->recipes->insertItem(5, "Soup"); + ui->recipes->insertItem(6, "Steak"); + connect(ui->recipes, &QListWidget::currentItemChanged, this, + [this](QListWidgetItem *current, QListWidgetItem * /* previous */) { + const QString page = current->text().toLower(); + const QString url = QStringLiteral("qrc:/pages/") + page + QStringLiteral(".html"); + ui->webEngineView->setUrl(QUrl(url)); + m_content.setCurrentPage(page); + }); + + m_content.setTextEdit(ui->textEdit); + + connect(ui->textEdit, &QPlainTextEdit::textChanged, this, + [this]() { m_content.setText(ui->textEdit->toPlainText()); }); + + QWebChannel *channel = new QWebChannel(this); + channel->registerObject(QStringLiteral("content"), &m_content); + ui->webEngineView->page()->setWebChannel(channel); + + QSettings settings; + settings.beginGroup("styleSheets"); + QStringList styleSheets = settings.allKeys(); + if (styleSheets.empty()) { + // Add back default style sheets if the user cleared them out + loadDefaultStyleSheets(); + } else { + for (const auto &name : std::as_const(styleSheets)) { + StyleSheet styleSheet = settings.value(name).value<StyleSheet>(); + if (styleSheet.second) + insertStyleSheet(name, styleSheet.first, false); + } + } + settings.endGroup(); + + ui->recipes->setCurrentItem(ui->recipes->item(0)); +} + +MainWindow::~MainWindow() +{ + delete ui; +} + +void MainWindow::insertStyleSheet(const QString &name, const QString &source, bool immediately) +{ + QWebEngineScript script; + QString s = QString::fromLatin1("(function() {" + " css = document.createElement('style');" + " css.type = 'text/css';" + " css.id = '%1';" + " document.head.appendChild(css);" + " css.innerText = '%2';" + "})()") + .arg(name, source.simplified()); + if (immediately) + ui->webEngineView->page()->runJavaScript(s, QWebEngineScript::ApplicationWorld); + + script.setName(name); + script.setSourceCode(s); + script.setInjectionPoint(QWebEngineScript::DocumentReady); + script.setRunsOnSubFrames(true); + script.setWorldId(QWebEngineScript::ApplicationWorld); + ui->webEngineView->page()->scripts().insert(script); +} + +void MainWindow::removeStyleSheet(const QString &name, bool immediately) +{ + QString s = QString::fromLatin1("(function() {" + " var element = document.getElementById('%1');" + " element.outerHTML = '';" + " delete element;" + "})()") + .arg(name); + if (immediately) + ui->webEngineView->page()->runJavaScript(s, QWebEngineScript::ApplicationWorld); + + const QList<QWebEngineScript> scripts = ui->webEngineView->page()->scripts().find(name); + if (!scripts.isEmpty()) + ui->webEngineView->page()->scripts().remove(scripts.first()); +} + +bool MainWindow::hasStyleSheet(const QString &name) +{ + const QList<QWebEngineScript> scripts = ui->webEngineView->page()->scripts().find(name); + return !scripts.isEmpty(); +} + +void MainWindow::loadDefaultStyleSheets() +{ + QSettings settings; + settings.beginGroup("styleSheets"); + + for (auto it = defaultStyleSheets.constBegin(); it != defaultStyleSheets.constEnd(); ++it) { + settings.setValue(it.key(), QVariant::fromValue(qMakePair(it.value(), true))); + insertStyleSheet(it.key(), it.value(), false); + } + + settings.endGroup(); +} + +void MainWindow::showStyleSheetsDialog() +{ + StylesheetDialog *dialog = new StylesheetDialog(this); + dialog->show(); +} + +void MainWindow::toggleEditView() +{ + m_isEditMode = !m_isEditMode; + + if (m_isEditMode) { + ui->webEngineView->hide(); + ui->textEdit->show(); + + ui->editViewButton->setText(QStringLiteral("View")); + } else { + ui->textEdit->hide(); + ui->webEngineView->show(); + + ui->editViewButton->setText(QStringLiteral("Edit")); + } +} diff --git a/examples/webenginewidgets/stylesheetbrowser/mainwindow.h b/examples/webenginewidgets/recipebrowser/mainwindow.h index 92d6d02fe..959c82af8 100644 --- a/examples/webenginewidgets/stylesheetbrowser/mainwindow.h +++ b/examples/webenginewidgets/recipebrowser/mainwindow.h @@ -1,12 +1,15 @@ -// Copyright (C) 2017 The Qt Company Ltd. +// Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause #ifndef MAINWINDOW_H #define MAINWINDOW_H +#include "document.h" #include <QMainWindow> #include <QSettings> #include <QWebEngineScriptCollection> +#include <QWebEngineView> +#include <QPlainTextEdit> QT_BEGIN_NAMESPACE namespace Ui { @@ -19,7 +22,7 @@ class MainWindow : public QMainWindow Q_OBJECT public: - explicit MainWindow(const QUrl &url); + explicit MainWindow(QWidget *parent = nullptr); ~MainWindow(); void insertStyleSheet(const QString &name, const QString &source, bool immediately); @@ -28,13 +31,14 @@ public: void loadDefaultStyleSheets(); private slots: - void urlEntered(); - void urlChanged(const QUrl &url); void showStyleSheetsDialog(); - void reloadRequested(); + void toggleEditView(); private: Ui::MainWindow *ui; + + bool m_isEditMode; + Document m_content; }; #endif // MAINWINDOW_H diff --git a/examples/webenginewidgets/recipebrowser/mainwindow.ui b/examples/webenginewidgets/recipebrowser/mainwindow.ui new file mode 100644 index 000000000..b7c29f1b8 --- /dev/null +++ b/examples/webenginewidgets/recipebrowser/mainwindow.ui @@ -0,0 +1,106 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>MainWindow</class> + <widget class="QMainWindow" name="MainWindow"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>713</width> + <height>455</height> + </rect> + </property> + <property name="windowTitle"> + <string>Recipe Browser</string> + </property> + <property name="unifiedTitleAndToolBarOnMac"> + <bool>false</bool> + </property> + <widget class="QWidget" name="centralWidget"> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0"> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <layout class="QVBoxLayout" name="navigation"> + <item> + <layout class="QHBoxLayout" name="navigationBar"> + <item> + <widget class="QLabel" name="label"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Recipes</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="editViewButton"> + <property name="text"> + <string>Edit</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="stylesheetsButton"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Stylesheets</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QListWidget" name="recipes"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QWebEngineView" name="webEngineView" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + <item> + <widget class="QPlainTextEdit" name="textEdit"/> + </item> + </layout> + </item> + </layout> + </widget> + </widget> + <layoutdefault spacing="6" margin="11"/> + <customwidgets> + <customwidget> + <class>QWebEngineView</class> + <extends>QWidget</extends> + <header>qwebengineview.h</header> + <container>1</container> + </customwidget> + </customwidgets> + <resources/> + <connections/> +</ui> diff --git a/examples/webenginewidgets/stylesheetbrowser/stylesheetbrowser.pro b/examples/webenginewidgets/recipebrowser/recipebrowser.pro index a9ff54400..8f8895b52 100644 --- a/examples/webenginewidgets/stylesheetbrowser/stylesheetbrowser.pro +++ b/examples/webenginewidgets/recipebrowser/recipebrowser.pro @@ -1,22 +1,24 @@ TEMPLATE = app -TARGET = stylesheetbrowser +TARGET = recipebrowser QT += webenginewidgets HEADERS += \ mainwindow.h \ - stylesheetdialog.h + stylesheetdialog.h \ + document.h SOURCES += \ main.cpp \ mainwindow.cpp \ - stylesheetdialog.cpp + stylesheetdialog.cpp \ + document.cpp FORMS += \ mainwindow.ui \ stylesheetdialog.ui -RESOURCES += stylesheetbrowser.qrc +RESOURCES += recipebrowser.qrc # install -target.path = $$[QT_INSTALL_EXAMPLES]/webenginewidgets/stylesheetbrowser +target.path = $$[QT_INSTALL_EXAMPLES]/webenginewidgets/recipebrowser INSTALLS += target diff --git a/examples/webenginewidgets/recipebrowser/recipebrowser.qrc b/examples/webenginewidgets/recipebrowser/recipebrowser.qrc new file mode 100644 index 000000000..2dc003f30 --- /dev/null +++ b/examples/webenginewidgets/recipebrowser/recipebrowser.qrc @@ -0,0 +1,15 @@ +<RCC> + <qresource prefix="/"> + <file>assets/3rdparty/markdown.css</file> + <file>assets/3rdparty/marked.js</file> + <file>assets/custom.js</file> + <file>assets/custom.css</file> + <file>assets/pages/burger.html</file> + <file>assets/pages/cupcakes.html</file> + <file>assets/pages/pasta.html</file> + <file>assets/pages/pizza.html</file> + <file>assets/pages/skewers.html</file> + <file>assets/pages/soup.html</file> + <file>assets/pages/steak.html</file> + </qresource> +</RCC> diff --git a/examples/webenginewidgets/stylesheetbrowser/stylesheetdialog.cpp b/examples/webenginewidgets/recipebrowser/stylesheetdialog.cpp index 30409f107..2137617c3 100644 --- a/examples/webenginewidgets/stylesheetbrowser/stylesheetdialog.cpp +++ b/examples/webenginewidgets/recipebrowser/stylesheetdialog.cpp @@ -1,18 +1,18 @@ -// Copyright (C) 2017 The Qt Company Ltd. +// Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause #include "mainwindow.h" #include "stylesheetdialog.h" #include "ui_stylesheetdialog.h" -StylesheetDialog::StylesheetDialog(QWidget *parent) : - QDialog(parent), - ui(new Ui::StylesheetDialog) +StylesheetDialog::StylesheetDialog(QWidget *parent) : QDialog(parent), ui(new Ui::StylesheetDialog) { ui->setupUi(this); - connect(ui->styleSheetList, &QListWidget::currentItemChanged, this, &StylesheetDialog::currentStyleSheetChanged); - connect(ui->styleSheetList, &QListWidget::itemClicked, this, &StylesheetDialog::listItemClicked); + connect(ui->styleSheetList, &QListWidget::currentItemChanged, this, + &StylesheetDialog::currentStyleSheetChanged); + connect(ui->styleSheetList, &QListWidget::itemClicked, this, + &StylesheetDialog::listItemClicked); connect(ui->fileNameEdit, &QLineEdit::textChanged, this, &StylesheetDialog::fileNameChanged); connect(ui->addButton, &QPushButton::clicked, this, &StylesheetDialog::addButtonClicked); @@ -20,8 +20,8 @@ StylesheetDialog::StylesheetDialog(QWidget *parent) : QSettings settings; settings.beginGroup("styleSheets"); - for (auto name : settings.allKeys()) { - QListWidgetItem *listItem = new QListWidgetItem(name, ui->styleSheetList); + for (const auto &name : settings.allKeys()) { + QListWidgetItem *listItem = new QListWidgetItem(name, ui->styleSheetList); listItem->setFlags(listItem->flags() | Qt::ItemIsUserCheckable); bool checked = settings.value(name).value<StyleSheet>().second; listItem->setCheckState(checked ? Qt::Checked : Qt::Unchecked); @@ -59,9 +59,8 @@ void StylesheetDialog::listItemClicked(QListWidgetItem *item) { MainWindow *window = static_cast<MainWindow *>(parent()); const QString name = item->text(); - bool checkedStateChanged = - (item->checkState() == Qt::Checked && !window->hasStyleSheet(name)) || - (item->checkState() == Qt::Unchecked && window->hasStyleSheet(name)); + bool checkedStateChanged = (item->checkState() == Qt::Checked && !window->hasStyleSheet(name)) + || (item->checkState() == Qt::Unchecked && window->hasStyleSheet(name)); if (!checkedStateChanged) return; @@ -96,7 +95,7 @@ void StylesheetDialog::addButtonClicked() if (name.isEmpty() || source.isEmpty()) return; - QListWidgetItem *listItem = new QListWidgetItem(ui->fileNameEdit->text(), ui->styleSheetList); + QListWidgetItem *listItem = new QListWidgetItem(ui->fileNameEdit->text(), ui->styleSheetList); listItem->setFlags(listItem->flags() | Qt::ItemIsUserCheckable); listItem->setCheckState(Qt::Checked); diff --git a/examples/webenginewidgets/stylesheetbrowser/stylesheetdialog.h b/examples/webenginewidgets/recipebrowser/stylesheetdialog.h index 7bebff74f..ca1b4ae99 100644 --- a/examples/webenginewidgets/stylesheetbrowser/stylesheetdialog.h +++ b/examples/webenginewidgets/recipebrowser/stylesheetdialog.h @@ -1,4 +1,4 @@ -// Copyright (C) 2017 The Qt Company Ltd. +// Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause #ifndef STYLESHEETDIALOG_H diff --git a/examples/webenginewidgets/stylesheetbrowser/stylesheetdialog.ui b/examples/webenginewidgets/recipebrowser/stylesheetdialog.ui index 19db267e8..19db267e8 100644 --- a/examples/webenginewidgets/stylesheetbrowser/stylesheetdialog.ui +++ b/examples/webenginewidgets/recipebrowser/stylesheetdialog.ui diff --git a/examples/webenginewidgets/simplebrowser/CMakeLists.txt b/examples/webenginewidgets/simplebrowser/CMakeLists.txt index f23bce709..2dd4fd57a 100644 --- a/examples/webenginewidgets/simplebrowser/CMakeLists.txt +++ b/examples/webenginewidgets/simplebrowser/CMakeLists.txt @@ -1,3 +1,6 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + cmake_minimum_required(VERSION 3.16) project(simplebrowser LANGUAGES CXX) diff --git a/examples/webenginewidgets/simplebrowser/browserwindow.cpp b/examples/webenginewidgets/simplebrowser/browserwindow.cpp index 0458cd1f4..d5b097b22 100644 --- a/examples/webenginewidgets/simplebrowser/browserwindow.cpp +++ b/examples/webenginewidgets/simplebrowser/browserwindow.cpp @@ -25,14 +25,6 @@ BrowserWindow::BrowserWindow(Browser *browser, QWebEngineProfile *profile, bool : m_browser(browser) , m_profile(profile) , m_tabWidget(new TabWidget(profile, this)) - , m_progressBar(nullptr) - , m_historyBackAction(nullptr) - , m_historyForwardAction(nullptr) - , m_stopAction(nullptr) - , m_reloadAction(nullptr) - , m_stopReloadAction(nullptr) - , m_urlLineEdit(nullptr) - , m_favAction(nullptr) { setAttribute(Qt::WA_DeleteOnClose, true); setFocusPolicy(Qt::ClickFocus); @@ -58,7 +50,8 @@ BrowserWindow::BrowserWindow(Browser *browser, QWebEngineProfile *profile, bool m_progressBar->setMaximumHeight(1); m_progressBar->setTextVisible(false); - m_progressBar->setStyleSheet(QStringLiteral("QProgressBar {border: 0px} QProgressBar::chunk {background-color: #da4453}")); + m_progressBar->setStyleSheet(QStringLiteral( + "QProgressBar {border: 0px} QProgressBar::chunk {background-color: #da4453}")); layout->addWidget(m_progressBar); } @@ -266,11 +259,19 @@ QMenu *BrowserWindow::createWindowMenu(TabWidget *tabWidget) previousTabAction->setShortcuts(shortcuts); connect(previousTabAction, &QAction::triggered, tabWidget, &TabWidget::previousTab); - connect(menu, &QMenu::aboutToShow, [this, menu, nextTabAction, previousTabAction]() { + QAction *inspectorAction = new QAction(tr("Open inspector in new window"), this); + shortcuts.clear(); + shortcuts.append(QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_I)); + inspectorAction->setShortcuts(shortcuts); + connect(inspectorAction, &QAction::triggered, [this]() { emit currentTab()->devToolsRequested(currentTab()->page()); }); + + connect(menu, &QMenu::aboutToShow, [this, menu, nextTabAction, previousTabAction, inspectorAction]() { menu->clear(); menu->addAction(nextTabAction); menu->addAction(previousTabAction); menu->addSeparator(); + menu->addAction(inspectorAction); + menu->addSeparator(); QList<BrowserWindow*> windows = m_browser->windows(); int index(-1); @@ -292,6 +293,20 @@ QMenu *BrowserWindow::createHelpMenu() return helpMenu; } +static bool isBackspace(const QKeySequence &k) +{ + return (k[0].key() & Qt::Key_unknown) == Qt::Key_Backspace; +} + +// Chromium already handles navigate on backspace when appropriate. +static QList<QKeySequence> removeBackspace(QList<QKeySequence> keys) +{ + const auto it = std::find_if(keys.begin(), keys.end(), isBackspace); + if (it != keys.end()) + keys.erase(it); + return keys; +} + QToolBar *BrowserWindow::createToolBar() { QToolBar *navigationBar = new QToolBar(tr("Navigation")); @@ -299,14 +314,7 @@ QToolBar *BrowserWindow::createToolBar() navigationBar->toggleViewAction()->setEnabled(false); m_historyBackAction = new QAction(this); - QList<QKeySequence> backShortcuts = QKeySequence::keyBindings(QKeySequence::Back); - for (auto it = backShortcuts.begin(); it != backShortcuts.end();) { - // Chromium already handles navigate on backspace when appropriate. - if ((*it)[0].key() == Qt::Key_Backspace) - it = backShortcuts.erase(it); - else - ++it; - } + auto backShortcuts = removeBackspace(QKeySequence::keyBindings(QKeySequence::Back)); // For some reason Qt doesn't bind the dedicated Back key to Back. backShortcuts.append(QKeySequence(Qt::Key_Back)); m_historyBackAction->setShortcuts(backShortcuts); @@ -319,13 +327,7 @@ QToolBar *BrowserWindow::createToolBar() navigationBar->addAction(m_historyBackAction); m_historyForwardAction = new QAction(this); - QList<QKeySequence> fwdShortcuts = QKeySequence::keyBindings(QKeySequence::Forward); - for (auto it = fwdShortcuts.begin(); it != fwdShortcuts.end();) { - if (((*it)[0].key() & Qt::Key_unknown) == Qt::Key_Backspace) - it = fwdShortcuts.erase(it); - else - ++it; - } + auto fwdShortcuts = removeBackspace(QKeySequence::keyBindings(QKeySequence::Forward)); fwdShortcuts.append(QKeySequence(Qt::Key_Forward)); m_historyForwardAction->setShortcuts(fwdShortcuts); m_historyForwardAction->setIconVisibleInMenu(false); @@ -352,9 +354,8 @@ QToolBar *BrowserWindow::createToolBar() downloadsAction->setIcon(QIcon(QStringLiteral(":go-bottom.png"))); downloadsAction->setToolTip(tr("Show downloads")); navigationBar->addAction(downloadsAction); - connect(downloadsAction, &QAction::triggered, [this]() { - m_browser->downloadManagerWidget().show(); - }); + connect(downloadsAction, &QAction::triggered, + &m_browser->downloadManagerWidget(), &QWidget::show); return navigationBar; } diff --git a/examples/webenginewidgets/simplebrowser/browserwindow.h b/examples/webenginewidgets/simplebrowser/browserwindow.h index 47fdf6314..55eeb46c2 100644 --- a/examples/webenginewidgets/simplebrowser/browserwindow.h +++ b/examples/webenginewidgets/simplebrowser/browserwindow.h @@ -22,7 +22,8 @@ class BrowserWindow : public QMainWindow Q_OBJECT public: - BrowserWindow(Browser *browser, QWebEngineProfile *profile, bool forDevTools = false); + explicit BrowserWindow(Browser *browser, QWebEngineProfile *profile, + bool forDevTools = false); QSize sizeHint() const override; TabWidget *tabWidget() const; WebView *currentTab() const; @@ -55,14 +56,14 @@ private: Browser *m_browser; QWebEngineProfile *m_profile; TabWidget *m_tabWidget; - QProgressBar *m_progressBar; - QAction *m_historyBackAction; - QAction *m_historyForwardAction; - QAction *m_stopAction; - QAction *m_reloadAction; - QAction *m_stopReloadAction; - QLineEdit *m_urlLineEdit; - QAction *m_favAction; + QProgressBar *m_progressBar = nullptr; + QAction *m_historyBackAction = nullptr; + QAction *m_historyForwardAction = nullptr; + QAction *m_stopAction = nullptr; + QAction *m_reloadAction = nullptr; + QAction *m_stopReloadAction = nullptr; + QLineEdit *m_urlLineEdit = nullptr; + QAction *m_favAction = nullptr; QString m_lastSearch; }; diff --git a/examples/webenginewidgets/simplebrowser/downloadmanagerwidget.cpp b/examples/webenginewidgets/simplebrowser/downloadmanagerwidget.cpp index bfb857cd8..fdddc4fb0 100644 --- a/examples/webenginewidgets/simplebrowser/downloadmanagerwidget.cpp +++ b/examples/webenginewidgets/simplebrowser/downloadmanagerwidget.cpp @@ -13,7 +13,6 @@ DownloadManagerWidget::DownloadManagerWidget(QWidget *parent) : QWidget(parent) - , m_numDownloads(0) { setupUi(this); } diff --git a/examples/webenginewidgets/simplebrowser/downloadmanagerwidget.h b/examples/webenginewidgets/simplebrowser/downloadmanagerwidget.h index b9d5e9bd7..67df492b9 100644 --- a/examples/webenginewidgets/simplebrowser/downloadmanagerwidget.h +++ b/examples/webenginewidgets/simplebrowser/downloadmanagerwidget.h @@ -30,7 +30,7 @@ private: void add(DownloadWidget *downloadWidget); void remove(DownloadWidget *downloadWidget); - int m_numDownloads; + int m_numDownloads = 0; }; #endif // DOWNLOADMANAGERWIDGET_H diff --git a/examples/webenginewidgets/simplebrowser/downloadwidget.cpp b/examples/webenginewidgets/simplebrowser/downloadwidget.cpp index d4998853e..f8b96c6e6 100644 --- a/examples/webenginewidgets/simplebrowser/downloadwidget.cpp +++ b/examples/webenginewidgets/simplebrowser/downloadwidget.cpp @@ -38,12 +38,11 @@ inline QString DownloadWidget::withUnit(qreal bytes) { if (bytes < (1 << 10)) return tr("%L1 B").arg(bytes); - else if (bytes < (1 << 20)) + if (bytes < (1 << 20)) return tr("%L1 KiB").arg(bytes / (1 << 10), 0, 'f', 2); - else if (bytes < (1 << 30)) + if (bytes < (1 << 30)) return tr("%L1 MiB").arg(bytes / (1 << 20), 0, 'f', 2); - else - return tr("%L1 GiB").arg(bytes / (1 << 30), 0, 'f', 2); + return tr("%L1 GiB").arg(bytes / (1 << 30), 0, 'f', 2); } void DownloadWidget::updateWidget() @@ -63,16 +62,14 @@ void DownloadWidget::updateWidget() m_progressBar->setDisabled(false); m_progressBar->setFormat( tr("%p% - %1 of %2 downloaded - %3/s") - .arg(withUnit(receivedBytes)) - .arg(withUnit(totalBytes)) - .arg(withUnit(bytesPerSecond))); + .arg(withUnit(receivedBytes), withUnit(totalBytes), + withUnit(bytesPerSecond))); } else { m_progressBar->setValue(0); m_progressBar->setDisabled(false); m_progressBar->setFormat( tr("unknown size - %1 downloaded - %2/s") - .arg(withUnit(receivedBytes)) - .arg(withUnit(bytesPerSecond))); + .arg(withUnit(receivedBytes), withUnit(bytesPerSecond))); } break; case QWebEngineDownloadRequest::DownloadCompleted: @@ -80,16 +77,14 @@ void DownloadWidget::updateWidget() m_progressBar->setDisabled(true); m_progressBar->setFormat( tr("completed - %1 downloaded - %2/s") - .arg(withUnit(receivedBytes)) - .arg(withUnit(bytesPerSecond))); + .arg(withUnit(receivedBytes), withUnit(bytesPerSecond))); break; case QWebEngineDownloadRequest::DownloadCancelled: m_progressBar->setValue(0); m_progressBar->setDisabled(true); m_progressBar->setFormat( tr("cancelled - %1 downloaded - %2/s") - .arg(withUnit(receivedBytes)) - .arg(withUnit(bytesPerSecond))); + .arg(withUnit(receivedBytes), withUnit(bytesPerSecond))); break; case QWebEngineDownloadRequest::DownloadInterrupted: m_progressBar->setValue(0); diff --git a/examples/webenginewidgets/simplebrowser/main.cpp b/examples/webenginewidgets/simplebrowser/main.cpp index 477a8171b..b945ef27e 100644 --- a/examples/webenginewidgets/simplebrowser/main.cpp +++ b/examples/webenginewidgets/simplebrowser/main.cpp @@ -5,6 +5,7 @@ #include "browserwindow.h" #include "tabwidget.h" #include <QApplication> +#include <QLoggingCategory> #include <QWebEngineProfile> #include <QWebEngineSettings> @@ -24,6 +25,7 @@ int main(int argc, char **argv) QApplication app(argc, argv); app.setWindowIcon(QIcon(QStringLiteral(":AppLogoColor.png"))); + QLoggingCategory::setFilterRules(QStringLiteral("qt.webenginecontext.debug=true")); QWebEngineProfile::defaultProfile()->settings()->setAttribute(QWebEngineSettings::PluginsEnabled, true); QWebEngineProfile::defaultProfile()->settings()->setAttribute(QWebEngineSettings::DnsPrefetchEnabled, true); diff --git a/examples/webenginewidgets/simplebrowser/tabwidget.cpp b/examples/webenginewidgets/simplebrowser/tabwidget.cpp index 9e19cf782..e4e204ce9 100644 --- a/examples/webenginewidgets/simplebrowser/tabwidget.cpp +++ b/examples/webenginewidgets/simplebrowser/tabwidget.cpp @@ -34,8 +34,8 @@ TabWidget::TabWidget(QWebEngineProfile *profile, QWidget *parent) QLabel *icon = new QLabel(this); QPixmap pixmap(QStringLiteral(":ninja.png")); icon->setPixmap(pixmap.scaledToHeight(tabBar->height())); - setStyleSheet(QStringLiteral("QTabWidget::tab-bar { left: %1px; }"). - arg(icon->pixmap().width())); + setStyleSheet( + QStringLiteral("QTabWidget::tab-bar { left: %1px; }").arg(icon->pixmap().width())); } } diff --git a/examples/webenginewidgets/simplebrowser/tabwidget.h b/examples/webenginewidgets/simplebrowser/tabwidget.h index 08caab52c..a1a893b62 100644 --- a/examples/webenginewidgets/simplebrowser/tabwidget.h +++ b/examples/webenginewidgets/simplebrowser/tabwidget.h @@ -19,7 +19,7 @@ class TabWidget : public QTabWidget Q_OBJECT public: - TabWidget(QWebEngineProfile *profile, QWidget *parent = nullptr); + explicit TabWidget(QWebEngineProfile *profile, QWidget *parent = nullptr); WebView *currentWebView() const; diff --git a/examples/webenginewidgets/simplebrowser/webpage.cpp b/examples/webenginewidgets/simplebrowser/webpage.cpp index 66de5d6d9..699e3d2ed 100644 --- a/examples/webenginewidgets/simplebrowser/webpage.cpp +++ b/examples/webenginewidgets/simplebrowser/webpage.cpp @@ -22,29 +22,6 @@ void WebPage::handleCertificateError(QWebEngineCertificateError error) [this, error]() mutable { emit createCertificateErrorDialog(error); }); } -inline QString questionForFeature(QWebEnginePage::Feature feature) -{ - switch (feature) { - case QWebEnginePage::Geolocation: - return WebPage::tr("Allow %1 to access your location information?"); - case QWebEnginePage::MediaAudioCapture: - return WebPage::tr("Allow %1 to access your microphone?"); - case QWebEnginePage::MediaVideoCapture: - return WebPage::tr("Allow %1 to access your webcam?"); - case QWebEnginePage::MediaAudioVideoCapture: - return WebPage::tr("Allow %1 to access your microphone and webcam?"); - case QWebEnginePage::MouseLock: - return WebPage::tr("Allow %1 to lock your mouse cursor?"); - case QWebEnginePage::DesktopVideoCapture: - return WebPage::tr("Allow %1 to capture video of your desktop?"); - case QWebEnginePage::DesktopAudioVideoCapture: - return WebPage::tr("Allow %1 to capture audio and video of your desktop?"); - case QWebEnginePage::Notifications: - return WebPage::tr("Allow %1 to show notification on your desktop?"); - } - return QString(); -} - void WebPage::handleSelectClientCertificate(QWebEngineClientCertificateSelection selection) { // Just select one. diff --git a/examples/webenginewidgets/simplebrowser/webpage.h b/examples/webenginewidgets/simplebrowser/webpage.h index 7fa2be335..83a4e833f 100644 --- a/examples/webenginewidgets/simplebrowser/webpage.h +++ b/examples/webenginewidgets/simplebrowser/webpage.h @@ -13,7 +13,7 @@ class WebPage : public QWebEnginePage Q_OBJECT public: - WebPage(QWebEngineProfile *profile, QObject *parent = nullptr); + explicit WebPage(QWebEngineProfile *profile, QObject *parent = nullptr); signals: void createCertificateErrorDialog(QWebEngineCertificateError error); diff --git a/examples/webenginewidgets/simplebrowser/webpopupwindow.h b/examples/webenginewidgets/simplebrowser/webpopupwindow.h index d13f5f183..0726bf0c2 100644 --- a/examples/webenginewidgets/simplebrowser/webpopupwindow.h +++ b/examples/webenginewidgets/simplebrowser/webpopupwindow.h @@ -19,7 +19,7 @@ class WebPopupWindow : public QWidget Q_OBJECT public: - WebPopupWindow(QWebEngineProfile *profile); + explicit WebPopupWindow(QWebEngineProfile *profile); WebView *view() const; private slots: diff --git a/examples/webenginewidgets/simplebrowser/webview.cpp b/examples/webenginewidgets/simplebrowser/webview.cpp index 16a58ce6c..e41d4334e 100644 --- a/examples/webenginewidgets/simplebrowser/webview.cpp +++ b/examples/webenginewidgets/simplebrowser/webview.cpp @@ -19,7 +19,6 @@ WebView::WebView(QWidget *parent) : QWebEngineView(parent) - , m_loadProgress(100) { connect(this, &QWebEngineView::loadStarted, [this]() { m_loadProgress = 0; @@ -57,7 +56,7 @@ WebView::WebView(QWidget *parent) tr("Render process exited with code: %1\n" "Do you want to reload the page ?").arg(statusCode)); if (btn == QMessageBox::Yes) - QTimer::singleShot(0, [this] { reload(); }); + QTimer::singleShot(0, this, &WebView::reload); }); } @@ -149,13 +148,15 @@ QIcon WebView::favIcon() const if (m_loadProgress < 0) { static QIcon errorIcon(QStringLiteral(":dialog-error.png")); return errorIcon; - } else if (m_loadProgress < 100) { + } + + if (m_loadProgress < 100) { static QIcon loadingIcon(QStringLiteral(":view-refresh.png")); return loadingIcon; - } else { - static QIcon defaultIcon(QStringLiteral(":text-html.png")); - return defaultIcon; } + + static QIcon defaultIcon(":text-html.png"); + return defaultIcon; } QWebEngineView *WebView::createWindow(QWebEnginePage::WebWindowType type) @@ -193,12 +194,8 @@ void WebView::contextMenuEvent(QContextMenuEvent *event) if (viewSource == actions.cend()) menu->addSeparator(); - QAction *action = new QAction(menu); - action->setText("Open inspector in new window"); + QAction *action = menu->addAction("Open inspector in new window"); connect(action, &QAction::triggered, [this]() { emit devToolsRequested(page()); }); - - QAction *before(inspectElement == actions.cend() ? nullptr : *inspectElement); - menu->insertAction(before, action); } else { (*inspectElement)->setText(tr("Inspect element")); } @@ -239,8 +236,8 @@ void WebView::handleAuthenticationRequired(const QUrl &requestUrl, QAuthenticato passwordDialog.m_iconLabel->setPixmap(icon.pixmap(32, 32)); QString introMessage(tr("Enter username and password for \"%1\" at %2") - .arg(auth->realm()) - .arg(requestUrl.toString().toHtmlEscaped())); + .arg(auth->realm(), + requestUrl.toString().toHtmlEscaped())); passwordDialog.m_infoLabel->setText(introMessage); passwordDialog.m_infoLabel->setWordWrap(true); diff --git a/examples/webenginewidgets/simplebrowser/webview.h b/examples/webenginewidgets/simplebrowser/webview.h index 63f82c3c9..41bc04ac0 100644 --- a/examples/webenginewidgets/simplebrowser/webview.h +++ b/examples/webenginewidgets/simplebrowser/webview.h @@ -20,7 +20,7 @@ class WebView : public QWebEngineView Q_OBJECT public: - WebView(QWidget *parent = nullptr); + explicit WebView(QWidget *parent = nullptr); void setPage(WebPage *page); int loadProgress() const; @@ -51,7 +51,7 @@ private: void createWebActionTrigger(QWebEnginePage *page, QWebEnginePage::WebAction); private: - int m_loadProgress; + int m_loadProgress = 100; }; #endif diff --git a/examples/webenginewidgets/spellchecker/CMakeLists.txt b/examples/webenginewidgets/spellchecker/CMakeLists.txt index d76881c65..74cb626e1 100644 --- a/examples/webenginewidgets/spellchecker/CMakeLists.txt +++ b/examples/webenginewidgets/spellchecker/CMakeLists.txt @@ -1,3 +1,6 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + cmake_minimum_required(VERSION 3.16) project(spellchecker LANGUAGES CXX) diff --git a/examples/webenginewidgets/stylesheetbrowser/3rdparty/COPYING b/examples/webenginewidgets/stylesheetbrowser/3rdparty/COPYING deleted file mode 100644 index 220881da6..000000000 --- a/examples/webenginewidgets/stylesheetbrowser/3rdparty/COPYING +++ /dev/null @@ -1 +0,0 @@ -The icons in this repository are herefore released into the Public Domain. diff --git a/examples/webenginewidgets/stylesheetbrowser/3rdparty/qt_attribution.json b/examples/webenginewidgets/stylesheetbrowser/3rdparty/qt_attribution.json deleted file mode 100644 index f779da7e2..000000000 --- a/examples/webenginewidgets/stylesheetbrowser/3rdparty/qt_attribution.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "Id": "stylesheetbrowser-tango", - "Name": "Tango Icon Library", - "QDocModule": "qtwebengine", - "QtUsage": "Used in WebEngine StyleSheet Browser example.", - - "QtParts": [ "examples" ], - "Description": "Selected icons from the Tango Icon Library", - "Homepage": "http://tango.freedesktop.org/Tango_Icon_Library", - "Version": "0.8.90", - "DownloadLocation": "http://tango.freedesktop.org/releases/tango-icon-theme-0.8.90.tar.gz", - "LicenseId": "urn:dje:license:public-domain", - "License": "Public Domain", - "LicenseFile": "COPYING", - "Copyright": "Ulisse Perusin <uli.peru@gmail.com> -Steven Garrity <sgarrity@silverorange.com> -Lapo Calamandrei <calamandrei@gmail.com> -Ryan Collier <rcollier@novell.com> -Rodney Dawes <dobey@novell.com> -Andreas Nilsson <nisses.mail@home.se> -Tuomas Kuosmanen <tigert@tigert.com> -Garrett LeSage <garrett@novell.com> -Jakub Steiner <jimmac@novell.com>" -} diff --git a/examples/webenginewidgets/stylesheetbrowser/3rdparty/view-refresh.png b/examples/webenginewidgets/stylesheetbrowser/3rdparty/view-refresh.png Binary files differdeleted file mode 100644 index cab4d02c7..000000000 --- a/examples/webenginewidgets/stylesheetbrowser/3rdparty/view-refresh.png +++ /dev/null diff --git a/examples/webenginewidgets/stylesheetbrowser/CMakeLists.txt b/examples/webenginewidgets/stylesheetbrowser/CMakeLists.txt deleted file mode 100644 index 55bd6715e..000000000 --- a/examples/webenginewidgets/stylesheetbrowser/CMakeLists.txt +++ /dev/null @@ -1,50 +0,0 @@ -cmake_minimum_required(VERSION 3.16) -project(stylesheetbrowser LANGUAGES CXX) - -set(CMAKE_AUTOMOC ON) -set(CMAKE_AUTOUIC ON) - -if(NOT DEFINED INSTALL_EXAMPLESDIR) - set(INSTALL_EXAMPLESDIR "examples") -endif() - -set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/webenginewidgets/stylesheetbrowser") - -find_package(Qt6 REQUIRED COMPONENTS Core Gui WebEngineWidgets) - -qt_add_executable(stylesheetbrowser - main.cpp - mainwindow.cpp mainwindow.h mainwindow.ui - stylesheetdialog.cpp stylesheetdialog.h stylesheetdialog.ui -) - -set_target_properties(stylesheetbrowser PROPERTIES - WIN32_EXECUTABLE TRUE - MACOSX_BUNDLE TRUE -) - -target_link_libraries(stylesheetbrowser PUBLIC - Qt::Core - Qt::Gui - Qt::WebEngineWidgets -) - -# Resources: -set(stylesheetbrowser_resource_files - "3rdparty/view-refresh.png" -) - -qt_add_resources(stylesheetbrowser "stylesheetbrowser" - PREFIX - "/" - BASE - "3rdparty" - FILES - ${stylesheetbrowser_resource_files} -) - -install(TARGETS stylesheetbrowser - RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" - BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" - LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" -) diff --git a/examples/webenginewidgets/stylesheetbrowser/doc/images/stylesheetbrowser.png b/examples/webenginewidgets/stylesheetbrowser/doc/images/stylesheetbrowser.png Binary files differdeleted file mode 100644 index 32c7c43ed..000000000 --- a/examples/webenginewidgets/stylesheetbrowser/doc/images/stylesheetbrowser.png +++ /dev/null diff --git a/examples/webenginewidgets/stylesheetbrowser/doc/src/stylesheetbrowser.qdoc b/examples/webenginewidgets/stylesheetbrowser/doc/src/stylesheetbrowser.qdoc deleted file mode 100644 index 80f333323..000000000 --- a/examples/webenginewidgets/stylesheetbrowser/doc/src/stylesheetbrowser.qdoc +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (C) 2017 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only - -/*! - \example webenginewidgets/stylesheetbrowser - \title WebEngine StyleSheet Browser Example - \ingroup webengine-widgetexamples - \brief Demonstrates how to inject CSS into web pages using user scripts. - - \image stylesheetbrowser.png - - \e {StyleSheet Browser} demonstrates how to use the \l{Qt WebEngine Widgets C++ Classes} - {Qt WebEngine C++ classes} to inject user stylesheets into web pages. - - \include examples-run.qdocinc - - \section1 Working With Stylesheets - - We use JavaScript to create and append CSS elements to the documents. - After declaring the script source, QWebEnginePage::runJavaScript() can run it - immediately and apply newly created styles on the current content of the web view. - Encapsulating the script into a QWebEngineScript and adding it to the script collection - of QWebEnginePage makes its effect permanent. - - \quotefromfile webenginewidgets/stylesheetbrowser/mainwindow.cpp - \skipto MainWindow::insertStyleSheet - \printuntil /^\}/ - - Removing stylesheets can be done similarly: - - \quotefromfile webenginewidgets/stylesheetbrowser/mainwindow.cpp - \skipto MainWindow::removeStyleSheet - \printuntil /^\}/ - - \section1 Files and Attributions - - The example uses icons from the Tango Icon Library: - - \table - \row - \li \l{stylesheetbrowser-tango}{Tango Icon Library} - \li Public Domain - \endtable -*/ diff --git a/examples/webenginewidgets/stylesheetbrowser/mainwindow.cpp b/examples/webenginewidgets/stylesheetbrowser/mainwindow.cpp deleted file mode 100644 index ab786d5c9..000000000 --- a/examples/webenginewidgets/stylesheetbrowser/mainwindow.cpp +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright (C) 2017 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -#include "mainwindow.h" -#include "stylesheetdialog.h" -#include "ui_mainwindow.h" - -static QMap<QString, QString> defaultStyleSheets = { - {"Upside down", "body { -webkit-transform: rotate(180deg); }"} -}; - -MainWindow::MainWindow(const QUrl &url) : - QMainWindow(), - ui(new Ui::MainWindow) -{ - ui->setupUi(this); - - connect(ui->urlBar, &QLineEdit::returnPressed, this, &MainWindow::urlEntered); - connect(ui->webEngineView, &QWebEngineView::urlChanged, this, &MainWindow::urlChanged); - connect(ui->settingsButton, &QPushButton::clicked, this, &MainWindow::showStyleSheetsDialog); - connect(ui->reloadButton, &QPushButton::clicked, this, &MainWindow::reloadRequested); - - QSettings settings; - settings.beginGroup("styleSheets"); - QStringList styleSheets = settings.allKeys(); - if (styleSheets.empty()) { - // Add back default style sheets if the user cleared them out - loadDefaultStyleSheets(); - } else { - for (auto name : std::as_const(styleSheets)) { - StyleSheet styleSheet = settings.value(name).value<StyleSheet>(); - if (styleSheet.second) - insertStyleSheet(name, styleSheet.first, false); - } - } - settings.endGroup(); - - ui->webEngineView->setUrl(url); -} - -MainWindow::~MainWindow() -{ - delete ui; -} - -void MainWindow::insertStyleSheet(const QString &name, const QString &source, bool immediately) -{ - QWebEngineScript script; - QString s = QString::fromLatin1("(function() {"\ - " css = document.createElement('style');"\ - " css.type = 'text/css';"\ - " css.id = '%1';"\ - " document.head.appendChild(css);"\ - " css.innerText = '%2';"\ - "})()").arg(name).arg(source.simplified()); - if (immediately) - ui->webEngineView->page()->runJavaScript(s, QWebEngineScript::ApplicationWorld); - - script.setName(name); - script.setSourceCode(s); - script.setInjectionPoint(QWebEngineScript::DocumentReady); - script.setRunsOnSubFrames(true); - script.setWorldId(QWebEngineScript::ApplicationWorld); - ui->webEngineView->page()->scripts().insert(script); -} - -void MainWindow::removeStyleSheet(const QString &name, bool immediately) -{ - QString s = QString::fromLatin1("(function() {"\ - " var element = document.getElementById('%1');"\ - " element.outerHTML = '';"\ - " delete element;"\ - "})()").arg(name); - if (immediately) - ui->webEngineView->page()->runJavaScript(s, QWebEngineScript::ApplicationWorld); - - const QList<QWebEngineScript> scripts = ui->webEngineView->page()->scripts().find(name); - if (!scripts.isEmpty()) - ui->webEngineView->page()->scripts().remove(scripts.first()); -} - -bool MainWindow::hasStyleSheet(const QString &name) -{ - const QList<QWebEngineScript> scripts = ui->webEngineView->page()->scripts().find(name); - return !scripts.isEmpty(); -} - -void MainWindow::loadDefaultStyleSheets() -{ - QSettings settings; - settings.beginGroup("styleSheets"); - - auto it = defaultStyleSheets.constBegin(); - while (it != defaultStyleSheets.constEnd()) { - settings.setValue(it.key(), QVariant::fromValue(qMakePair(it.value(), true))); - insertStyleSheet(it.key(), it.value(), false); - ++it; - } - - settings.endGroup(); -} - -void MainWindow::urlEntered() -{ - ui->webEngineView->setUrl(QUrl::fromUserInput(ui->urlBar->text())); -} - -void MainWindow::urlChanged(const QUrl &url) -{ - ui->urlBar->setText(url.toString()); -} - -void MainWindow::showStyleSheetsDialog() -{ - StylesheetDialog *dialog = new StylesheetDialog(this); - dialog->show(); -} - -void MainWindow::reloadRequested() -{ - ui->webEngineView->reload(); -} diff --git a/examples/webenginewidgets/stylesheetbrowser/mainwindow.ui b/examples/webenginewidgets/stylesheetbrowser/mainwindow.ui deleted file mode 100644 index bc68c16bb..000000000 --- a/examples/webenginewidgets/stylesheetbrowser/mainwindow.ui +++ /dev/null @@ -1,133 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<ui version="4.0"> - <class>MainWindow</class> - <widget class="QMainWindow" name="MainWindow"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>713</width> - <height>455</height> - </rect> - </property> - <property name="windowTitle"> - <string>StyleSheet Browser</string> - </property> - <property name="unifiedTitleAndToolBarOnMac"> - <bool>false</bool> - </property> - <widget class="QWidget" name="centralWidget"> - <layout class="QHBoxLayout" name="horizontalLayout"> - <property name="spacing"> - <number>0</number> - </property> - <property name="leftMargin"> - <number>0</number> - </property> - <property name="topMargin"> - <number>0</number> - </property> - <property name="rightMargin"> - <number>0</number> - </property> - <property name="bottomMargin"> - <number>0</number> - </property> - <item> - <widget class="QWidget" name="webContentsWidget" native="true"> - <layout class="QVBoxLayout" name="verticalLayout_3"> - <property name="spacing"> - <number>0</number> - </property> - <property name="leftMargin"> - <number>0</number> - </property> - <property name="topMargin"> - <number>0</number> - </property> - <property name="rightMargin"> - <number>0</number> - </property> - <property name="bottomMargin"> - <number>0</number> - </property> - <item> - <widget class="QWidget" name="urlBarWidget" native="true"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <layout class="QHBoxLayout" name="horizontalLayout_2"> - <item> - <widget class="QLineEdit" name="urlBar"/> - </item> - <item> - <widget class="QPushButton" name="reloadButton"> - <property name="text"> - <string/> - </property> - <property name="icon"> - <iconset resource="stylesheetbrowser.qrc"> - <normaloff>:/view-refresh.png</normaloff>:/view-refresh.png</iconset> - </property> - <property name="shortcut"> - <string>Ctrl+R</string> - </property> - </widget> - </item> - <item> - <widget class="QPushButton" name="settingsButton"> - <property name="text"> - <string>Settings</string> - </property> - </widget> - </item> - </layout> - </widget> - </item> - <item> - <widget class="QWebEngineView" name="webEngineView" native="true"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="url" stdset="0"> - <url> - <string>about:blank</string> - </url> - </property> - </widget> - </item> - </layout> - </widget> - </item> - </layout> - </widget> - <widget class="QMenuBar" name="menuBar"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>713</width> - <height>20</height> - </rect> - </property> - </widget> - </widget> - <layoutdefault spacing="6" margin="11"/> - <customwidgets> - <customwidget> - <class>QWebEngineView</class> - <extends>QWidget</extends> - <header location="global">QtWebEngineWidgets/QWebEngineView</header> - </customwidget> - </customwidgets> - <resources> - <include location="stylesheetbrowser.qrc"/> - </resources> - <connections/> -</ui> diff --git a/examples/webenginewidgets/stylesheetbrowser/stylesheetbrowser.qrc b/examples/webenginewidgets/stylesheetbrowser/stylesheetbrowser.qrc deleted file mode 100644 index a1cebd6a7..000000000 --- a/examples/webenginewidgets/stylesheetbrowser/stylesheetbrowser.qrc +++ /dev/null @@ -1,5 +0,0 @@ -<RCC> - <qresource prefix="/"> - <file alias="view-refresh.png">3rdparty/view-refresh.png</file> - </qresource> -</RCC> diff --git a/examples/webenginewidgets/videoplayer/CMakeLists.txt b/examples/webenginewidgets/videoplayer/CMakeLists.txt index 4e3e3cc2c..87ec9b086 100644 --- a/examples/webenginewidgets/videoplayer/CMakeLists.txt +++ b/examples/webenginewidgets/videoplayer/CMakeLists.txt @@ -1,3 +1,6 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + cmake_minimum_required(VERSION 3.16) project(videoplayer LANGUAGES CXX) diff --git a/examples/webenginewidgets/webenginewidgets.pro b/examples/webenginewidgets/webenginewidgets.pro index b99dd5f74..0d65d6b95 100644 --- a/examples/webenginewidgets/webenginewidgets.pro +++ b/examples/webenginewidgets/webenginewidgets.pro @@ -3,17 +3,15 @@ QT_FOR_CONFIG += webenginecore webenginecore-private network-private TEMPLATE=subdirs SUBDIRS += \ - minimal \ contentmanipulation \ cookiebrowser \ notifications \ simplebrowser \ - stylesheetbrowser \ - videoplayer \ - webui + push-notifications \ + videoplayer qtConfig(webengine-geolocation): SUBDIRS += maps -qtConfig(webengine-webchannel): SUBDIRS += markdowneditor +qtConfig(webengine-webchannel): SUBDIRS += recipebrowser qtConfig(webengine-printing-and-pdf) { SUBDIRS += printme html2pdf diff --git a/examples/webenginewidgets/webui/about.html b/examples/webenginewidgets/webui/about.html deleted file mode 100644 index 7b5a58969..000000000 --- a/examples/webenginewidgets/webui/about.html +++ /dev/null @@ -1,129 +0,0 @@ -<!DOCTYPE html> -<html> - <head> - <title>Qt WebEngine WebUI Example</title> - <style> - html { - background: #f0f0f0; - color: #303030; - font: 16px system-ui; - height: 100%; - } - - body { - margin: 0; - padding: 0; - height: 100%; - display: flex; - flex-direction: column; - align-items: stretch; - } - - body > * { - padding-left: 20px; - padding-right: 20px; - } - - header { - flex: none; - display: flex; - align-items: center; - background: #f0fff0; - border-bottom: 1px solid #e0e0e0; - padding-top: 20px; - padding-bottom: 20px; - } - - header > h1 { - font: bold 20px system-ui; - margin-left: 18px; - } - - main { - flex: auto; - } - - footer { - flex: none; - display: flex; - justify-content: center; - padding-bottom: 20px; - } - - button { - background: #41cd52; - color: #f0f0f0; - font: 16px system-ui; - border: 0; - box-shadow: 0px 1px 3px rgb(0,0,0,0.5); - cursor: pointer; - margin: 0 0 1px; - padding: 10px 24px; - } - - button:hover { - background: #50dc61; - } - - button:active { - background: #50dc61; - box-shadow: 0px 1px 2px rgb(0,0,0,0.5); - margin: 1px 0 0; - } - - button:focus { - outline: 0; - } - - </style> - </head> - <body> - <header> - <img width="48px" height="48px" - src="qrc:/qt-project.org/qmessagebox/images/qtlogo-64.png"> - <h1>WebEngine Widgets<br>WebUI Example</h1> - </header> - <main> - <p> - Aside from the built-in schemes, such as <code>http</code> and - <code>qrc</code>, Qt WebEngine may be extended with <em>custom - schemes</em> by creating <em>custom scheme handlers</em>. - </p> - - <p> - This is a simple HTML page loaded from a custom scheme and - displayed by a <code>QWebEngineView</code>. Even the Quit button - below is a standard HTML <code><button></code> element. - </p> - - <p> - Read the documentation to find out - </p> - <ul> - <li> - <p> - How to create a custom scheme handler which serves HTML - and handles HTML form submissions. - </p> - </li> - <li> - <p> - How to prevent ordinary web content from accessing the - custom scheme. - </p> - </li> - <li> - <p> - How to prevent any other scheme from submitting HTML - form data. - </p> - </li> - </ul> - </main> - <footer> - <form action="" method="post"> - <button name="quit">Quit</button> - </form> - </footer> - </body> -</html> diff --git a/examples/webenginewidgets/webui/doc/images/webui-example.png b/examples/webenginewidgets/webui/doc/images/webui-example.png Binary files differdeleted file mode 100644 index 84e2c7fc3..000000000 --- a/examples/webenginewidgets/webui/doc/images/webui-example.png +++ /dev/null diff --git a/examples/webenginewidgets/webui/doc/src/webui.qdoc b/examples/webenginewidgets/webui/doc/src/webui.qdoc deleted file mode 100644 index c8ab43ea8..000000000 --- a/examples/webenginewidgets/webui/doc/src/webui.qdoc +++ /dev/null @@ -1,141 +0,0 @@ -// Copyright (C) 2018 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only - -/*! - \example webenginewidgets/webui - \title WebEngine Widgets WebUI Example - \ingroup webengine-widgetexamples - \brief Displays HTML over a custom scheme. - - \image webui-example.png - - \e {WebUI} demonstrates how to implement a custom scheme in a secure way. - - Aside from the built-in URL schemes, such as \c {http} and \c {qrc}, - \QWE may be extended with \e {custom schemes} by creating \e {custom - scheme handlers}. This example shows: - - \list - \li How to create a custom scheme handler which serves HTML and handles - HTML form submissions. - \li How to prevent ordinary web content from accessing the custom scheme. - \li How to prevent any other scheme from submitting HTML form data. - \endlist - - \include examples-run.qdocinc - - \section1 Overview - - The example program consists of a single \l {QWebEngineView} showing a - simple HTML page loaded from the URL \c {webui:about}, over our custom - scheme. Pressing the button at the bottom of the page will trigger an HTML - form submission via POST to the same URL, at which point our custom scheme - handler will cause the application to exit. - - The program is divided into two parts, the \c {main} function for setting - everything up, and the \c {WebUiHandler} class for implementing our custom - scheme handler. The \c {main} function is quite short: - - \quotefromfile webenginewidgets/webui/main.cpp - \skipto int main - \printuntil /^\}/ - - Aside from the relatively standard setup of widgets, two points are - noteworthy. First, we call the static method \c - {WebUiHandler::registerUrlScheme()} to register our custom scheme with the - web engine. Second, we create and install our custom scheme handler \c - {WebUiHandler} using \l - {QWebEngineProfile::installUrlSchemeHandler()}{installUrlSchemeHandler()}. - The following sections describe these aspects in more detail. - - \section1 Registering the Scheme - - As custom schemes are integrated directly into the web engine, they do not - necessarily need to follow the standard security rules which apply to - ordinary web content. Depending on the chosen configuration, content served - over a custom scheme may be given access to local resources, be set to - ignore Content-Security-Policy rules, or conversely, be denied access to any - other content entirely. - - In order to take advantage of these possibilities, the custom scheme must - first be registered. This means creating and configuring a \l - {QWebEngineUrlScheme} object and then handing it over to \l - {QWebEngineUrlScheme::registerScheme()}. The example program does exactly this in - the static method \c {WebUiHandler::registerUrlScheme()}: - - \quotefromfile webenginewidgets/webui/webuihandler.cpp - \skipto void WebUiHandler::registerUrlScheme - \printuntil /^\}/ - - A custom scheme needs a name, which can be set by passing it to - the constructor of \c {QWebEngineUrlScheme} or by calling \l - {QWebEngineUrlScheme::setName}. In the above, the name \c {webui} is set - through the constructor. Additionally, we activate the flags \l - {QWebEngineUrlScheme::SecureScheme}{SecureScheme}, \l - {QWebEngineUrlScheme::LocalScheme}{LocalScheme} and \l - {QWebEngineUrlScheme::LocalAccessAllowed}{LocalAccessAllowed}. Since our - custom scheme handler will not deliver resources received from insecure - network connections, we can safely mark it as a \c {SecureScheme}. The \c {LocalScheme} - flag prevents content from non-local schemes (such as \c {http}) from - interacting with our custom scheme. Without this flag it would be possible, - for example, to embed the \c {webui:about} page in an \c <iframe> element on - a remotely loaded HTML page, perhaps to attempt a phishing attack. We also - need the \c {LocalAccessAllowed} flag without which we would not be able to - access the \c {webui} scheme from our \c {webui:about} page. - - Earlier we saw that the call to \c {WebUiHandler::registerUrlScheme()} is - made already at the top of the \c {main} function. This is so because custom - schemes need to be registered as early as possible so that that they can be - passed to all subprocesses. Specifically, custom schemes need to be registered - before any other \QWE classes are instantiated by the application. - - \section1 Handling Requests - - A custom scheme handler is, broadly speaking, similar to a web application - served over HTTP. However, because custom schemes are integrated directly - into the web engine, they have the advantage in terms of efficiency: there's - no need for generating and parsing HTTP messages or for transferring data - over sockets. - - Implementing a handler means creating a subclass of \l - {QWebEngineUrlSchemeHandler}, which is just what is done by the \c - {WebUiHandler} class of the example program: - - \quotefromfile webenginewidgets/webui/webuihandler.h - \skipto class WebUiHandler - \printuntil /^\}/ - - For each request to a \c {webui} URL, the \c - {WebUiHandler::requestStarted()} method will be called: - - \quotefromfile webenginewidgets/webui/webuihandler.cpp - \skipto void WebUiHandler::requestStarted - \printuntil /^\}/ - - The \l {QWebEngineUrlRequestJob} object \c {job} contains the request's - attributes and provides methods for replying to the request with a response. - Responses are generated asynchronously by reading them from the \l - {QIODevice} that the application passes to \l - {QWebEngineUrlRequestJob::reply()}{reply()}. - - \warning The \c requestStarted() method is not called from the main thread, - but from the web engine's IO thread. Care must be taken to synchronize - access to any resources on the main thread. - - Aside from the usual fare of \l - {QWebEngineUrlRequestJob::requestMethod()}{requestMethod} and \l - {QWebEngineUrlRequestJob::requestUrl()}{requestUrl}, there is also the \l - {QWebEngineUrlRequestJob::initiator()}{initiator}, holding the origin of the - content which initiated the request. An empty \c initiator means the request - was initiated directly by the application (via \l - {QWebEnginePage::setUrl()}, for example). The special value \c "null" - corresponds to an opaque origin (a sandboxed \c {<iframe>} element, for - example). Otherwise, the \c initiator will contain the URL scheme, hostname, - and port of the content which initiated the request. - - In this example, the \c initiator is used to ensure that \c {POST} requests - to \c {webui:about} will only trigger the application's exit if they - originate from the \c {webui} scheme. This prevents content loaded over - other schemes from triggering the application's exit. - -*/ diff --git a/examples/webenginewidgets/webui/main.cpp b/examples/webenginewidgets/webui/main.cpp deleted file mode 100644 index cbbb9ad25..000000000 --- a/examples/webenginewidgets/webui/main.cpp +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (C) 2018 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -#include "webuihandler.h" - -#include <QApplication> -#include <QWebEnginePage> -#include <QWebEngineProfile> -#include <QWebEngineView> - -int main(int argc, char *argv[]) -{ - QCoreApplication::setOrganizationName("QtExamples"); - - WebUiHandler::registerUrlScheme(); - - QApplication app(argc, argv); - - QWebEngineProfile profile; - - WebUiHandler handler; - profile.installUrlSchemeHandler(WebUiHandler::schemeName, &handler); - - QWebEnginePage page(&profile); - QWebEngineView view; - view.setPage(&page); - page.load(WebUiHandler::aboutUrl); - view.setContextMenuPolicy(Qt::NoContextMenu); - view.resize(500, 600); - view.show(); - - return app.exec(); -} diff --git a/examples/webenginewidgets/webui/webui.pro b/examples/webenginewidgets/webui/webui.pro deleted file mode 100644 index 714833587..000000000 --- a/examples/webenginewidgets/webui/webui.pro +++ /dev/null @@ -1,16 +0,0 @@ -TEMPLATE = app - -QT += webenginewidgets - -HEADERS += \ - webuihandler.h - -SOURCES += \ - main.cpp \ - webuihandler.cpp - -RESOURCES += \ - webui.qrc - -target.path = $$[QT_INSTALL_EXAMPLES]/webenginewidgets/webui -INSTALLS += target diff --git a/examples/webenginewidgets/webui/webui.qrc b/examples/webenginewidgets/webui/webui.qrc deleted file mode 100644 index 6ddf01fa2..000000000 --- a/examples/webenginewidgets/webui/webui.qrc +++ /dev/null @@ -1,5 +0,0 @@ -<RCC> - <qresource prefix="/"> - <file>about.html</file> - </qresource> -</RCC> diff --git a/examples/webenginewidgets/webui/webuihandler.cpp b/examples/webenginewidgets/webui/webuihandler.cpp deleted file mode 100644 index 932d622e5..000000000 --- a/examples/webenginewidgets/webui/webuihandler.cpp +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (C) 2018 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -#include "webuihandler.h" - -#include <QApplication> -#include <QFile> -#include <QWebEngineUrlRequestJob> -#include <QWebEngineUrlScheme> - -#define SCHEMENAME "webui" - -const QByteArray WebUiHandler::schemeName = QByteArrayLiteral(SCHEMENAME); -const QUrl WebUiHandler::aboutUrl = QUrl(QStringLiteral(SCHEMENAME ":about")); - -WebUiHandler::WebUiHandler(QObject *parent) - : QWebEngineUrlSchemeHandler(parent) -{ -} - -void WebUiHandler::requestStarted(QWebEngineUrlRequestJob *job) -{ - static const QUrl webUiOrigin(QStringLiteral(SCHEMENAME ":")); - static const QByteArray GET(QByteArrayLiteral("GET")); - static const QByteArray POST(QByteArrayLiteral("POST")); - - QByteArray method = job->requestMethod(); - QUrl url = job->requestUrl(); - QUrl initiator = job->initiator(); - - if (method == GET && url == aboutUrl) { - QFile *file = new QFile(QStringLiteral(":/about.html"), job); - file->open(QIODevice::ReadOnly); - job->reply(QByteArrayLiteral("text/html"), file); - } else if (method == POST && url == aboutUrl && initiator == webUiOrigin) { - job->fail(QWebEngineUrlRequestJob::RequestAborted); - QApplication::exit(); - } else { - job->fail(QWebEngineUrlRequestJob::UrlNotFound); - } -} - -// static -void WebUiHandler::registerUrlScheme() -{ - QWebEngineUrlScheme webUiScheme(schemeName); - webUiScheme.setFlags(QWebEngineUrlScheme::SecureScheme | - QWebEngineUrlScheme::LocalScheme | - QWebEngineUrlScheme::LocalAccessAllowed); - QWebEngineUrlScheme::registerScheme(webUiScheme); -} diff --git a/examples/webenginewidgets/webui/webuihandler.h b/examples/webenginewidgets/webui/webuihandler.h deleted file mode 100644 index 072c0809c..000000000 --- a/examples/webenginewidgets/webui/webuihandler.h +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (C) 2018 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -#ifndef WEBUIHANDLER_H -#define WEBUIHANDLER_H - -#include <QWebEngineUrlSchemeHandler> - -class WebUiHandler : public QWebEngineUrlSchemeHandler -{ - Q_OBJECT -public: - explicit WebUiHandler(QObject *parent = nullptr); - - void requestStarted(QWebEngineUrlRequestJob *job) override; - - static void registerUrlScheme(); - - const static QByteArray schemeName; - const static QUrl aboutUrl; -}; - -#endif // !WEBUIHANDLER_H |