diff options
Diffstat (limited to 'examples')
31 files changed, 1331 insertions, 30 deletions
diff --git a/examples/webengine/lifecycle/WebBrowser.qml b/examples/webengine/lifecycle/WebBrowser.qml new file mode 100644 index 000000000..23c2500e2 --- /dev/null +++ b/examples/webengine/lifecycle/WebBrowser.qml @@ -0,0 +1,172 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Controls.Material 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Window 2.12 + +ApplicationWindow { + id: root + + readonly property Action newTabAction: Action { + text: qsTr("New tab") + shortcut: StandardKey.AddTab + onTriggered: root.createNewTab({url: "about:blank"}) + } + + visible: true + width: Screen.width * 0.5 + height: Screen.height * 0.5 + title: tabStack.currentTab ? tabStack.currentTab.title : "" + + header: WebTabBar { + id: tabBar + + z: 1 + + newTabAction: root.newTabAction + } + + WebTabStack { + id: tabStack + + z: 0 + anchors.fill: parent + + currentIndex: tabBar.currentIndex + freezeDelay: freezeSpin.enabled && freezeSpin.value + discardDelay: discardSpin.enabled && discardSpin.value + + onCloseRequested: function(index) { + root.closeTab(index) + } + + onDrawerRequested: drawer.toggle() + } + + Drawer { + id: drawer + + edge: Qt.RightEdge + height: root.height + + Control { + padding: 16 + contentItem: ColumnLayout { + Label { + Layout.alignment: Qt.AlignHCenter + text: qsTr("Settings") + font.capitalization: Font.AllUppercase + } + MenuSeparator {} + CheckBox { + id: lifecycleCheck + text: qsTr("Automatic lifecycle control") + checked: true + } + CheckBox { + id: freezeCheck + text: qsTr("Freeze after delay (seconds)") + enabled: lifecycleCheck.checked + checked: true + } + SpinBox { + id: freezeSpin + editable: true + enabled: freezeCheck.checked + value: 60 + from: 1 + to: 60*60 + } + CheckBox { + id: discardCheck + text: qsTr("Discard after delay (seconds)") + enabled: lifecycleCheck.checked + checked: true + } + SpinBox { + id: discardSpin + editable: true + enabled: discardCheck.checked + value: 60*60 + from: 1 + to: 60*60 + } + } + } + + function toggle() { + if (drawer.visible) + drawer.close() + else + drawer.open() + } + } + + Component.onCompleted: { + createNewTab({url: "https://www.qt.io"}) + } + + function createNewTab(properties) { + const tab = tabStack.createNewTab(properties) + tabBar.createNewTab({tab: tab}) + tabBar.currentIndex = tab.index + return tab + } + + function closeTab(index) { + if (tabStack.count == 1) + Qt.quit() + tabBar.closeTab(index) + tabStack.closeTab(index) + } +} diff --git a/examples/webengine/lifecycle/WebTab.qml b/examples/webengine/lifecycle/WebTab.qml new file mode 100644 index 000000000..645758104 --- /dev/null +++ b/examples/webengine/lifecycle/WebTab.qml @@ -0,0 +1,212 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQml 2.12 +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Controls.Material 2.12 +import QtQuick.Layouts 1.12 +import QtWebEngine 1.11 + +ColumnLayout { + id: root + + signal closeRequested + signal drawerRequested + + property int freezeDelay + property int discardDelay + + property alias icon : view.icon + property alias loading : view.loading + property alias url : view.url + property alias lifecycleState : view.lifecycleState + property alias recommendedState : view.recommendedState + + readonly property string title : { + if (view.url == "about:blank") + return qsTr("New tab") + if (view.title) + return view.title + return view.url + } + + readonly property Action backAction: Action { + property WebEngineAction webAction: view.action(WebEngineView.Back) + enabled: webAction.enabled + text: qsTr("Back") + shortcut: root.visible && StandardKey.Back + onTriggered: webAction.trigger() + } + + readonly property Action forwardAction: Action { + property WebEngineAction webAction: view.action(WebEngineView.Forward) + enabled: webAction.enabled + text: qsTr("Forward") + shortcut: root.visible && StandardKey.Forward + onTriggered: webAction.trigger() + } + + readonly property Action reloadAction: Action { + property WebEngineAction webAction: view.action(WebEngineView.Reload) + enabled: webAction.enabled + text: qsTr("Reload") + shortcut: root.visible && StandardKey.Refresh + onTriggered: webAction.trigger() + } + + readonly property Action stopAction: Action { + property WebEngineAction webAction: view.action(WebEngineView.Stop) + enabled: webAction.enabled + text: qsTr("Stop") + shortcut: root.visible && StandardKey.Cancel + onTriggered: webAction.trigger() + } + + readonly property Action closeAction: Action { + text: qsTr("Close") + shortcut: root.visible && StandardKey.Close + onTriggered: root.closeRequested() + } + + readonly property Action activateAction: Action { + text: qsTr("Active") + checkable: true + checked: view.lifecycleState == WebEngineView.LifecycleState.Active + enabled: checked || (view.lifecycleState != WebEngineView.LifecycleState.Active) + onTriggered: view.lifecycleState = WebEngineView.LifecycleState.Active + } + + readonly property Action freezeAction: Action { + text: qsTr("Frozen") + checkable: true + checked: view.lifecycleState == WebEngineView.LifecycleState.Frozen + enabled: checked || (!view.visible && view.lifecycleState == WebEngineView.LifecycleState.Active) + onTriggered: view.lifecycleState = WebEngineView.LifecycleState.Frozen + } + + readonly property Action discardAction: Action { + text: qsTr("Discarded") + checkable: true + checked: view.lifecycleState == WebEngineView.LifecycleState.Discarded + enabled: checked || (!view.visible && view.lifecycleState == WebEngineView.LifecycleState.Frozen) + onTriggered: view.lifecycleState = WebEngineView.LifecycleState.Discarded + } + + spacing: 0 + + ToolBar { + Layout.fillWidth: true + Material.elevation: 0 + Material.background: Material.color(Material.Grey, Material.Shade800) + + RowLayout { + anchors.fill: parent + WebToolButton { + action: root.backAction + text: "←" + ToolTip.text: root.backAction.text + } + WebToolButton { + action: root.forwardAction + text: "→" + ToolTip.text: root.forwardAction.text + } + WebToolButton { + action: root.reloadAction + visible: root.reloadAction.enabled + text: "↻" + ToolTip.text: root.reloadAction.text + } + WebToolButton { + action: root.stopAction + visible: root.stopAction.enabled + text: "✕" + ToolTip.text: root.stopAction.text + } + TextField { + Layout.fillWidth: true + Layout.topMargin: 6 + + placeholderText: qsTr("Type a URL") + text: view.url == "about:blank" ? "" : view.url + selectByMouse: true + + onAccepted: { view.url = text } + } + WebToolButton { + text: "⋮" + ToolTip.text: qsTr("Settings") + onClicked: root.drawerRequested() + } + } + } + + WebEngineView { + id: view + Layout.fillHeight: true + Layout.fillWidth: true + } + + Timer { + interval: { + switch (view.recommendedState) { + case WebEngineView.LifecycleState.Active: + return 1 + case WebEngineView.LifecycleState.Frozen: + return root.freezeDelay * 1000 + case WebEngineView.LifecycleState.Discarded: + return root.discardDelay * 1000 + } + } + running: interval && view.lifecycleState != view.recommendedState + onTriggered: view.lifecycleState = view.recommendedState + } +} diff --git a/examples/webengine/lifecycle/WebTabBar.qml b/examples/webengine/lifecycle/WebTabBar.qml new file mode 100644 index 000000000..326ad39d2 --- /dev/null +++ b/examples/webengine/lifecycle/WebTabBar.qml @@ -0,0 +1,100 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Controls.Material 2.12 +import QtQuick.Layouts 1.12 + +Pane { + id: root + + property Action newTabAction + + property alias currentIndex: tabBar.currentIndex + + signal closeRequested(int index) + + Material.background: Material.color(Material.Grey, Material.Shade900) + Material.elevation: 4 + padding: 0 + + RowLayout { + spacing: 0 + anchors.fill: parent + + TabBar { + id: tabBar + Layout.fillWidth: true + Layout.fillHeight: true + } + + WebToolButton { + Layout.bottomMargin: 2 + + action: root.newTabAction + text: "+" + ToolTip.text: root.newTabAction.text + } + } + + Component { + id: factory + WebTabButton {} + } + + function createNewTab(properties) { + return factory.createObject(tabBar, properties) + } + + function closeTab(index) { + tabBar.takeItem(index).destroy() + } +} diff --git a/examples/webengine/lifecycle/WebTabButton.qml b/examples/webengine/lifecycle/WebTabButton.qml new file mode 100644 index 000000000..815e2fb09 --- /dev/null +++ b/examples/webengine/lifecycle/WebTabButton.qml @@ -0,0 +1,158 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Controls.Material 2.12 +import QtQuick.Layouts 1.12 +import QtWebEngine 1.11 + +TabButton { + id: root + + property WebTab tab + + text: root.tab.title + + ToolTip.delay: 1000 + ToolTip.visible: root.hovered + ToolTip.text: root.text + + padding: 6 + + contentItem: RowLayout { + Item { + implicitWidth: 16 + implicitHeight: 16 + BusyIndicator { + visible: root.tab.loading + anchors.fill: parent + leftInset: 0 + topInset: 0 + rightInset: 0 + bottomInset: 0 + padding: 0 + } + Image { + visible: !root.tab.loading + source: root.tab.icon + anchors.fill: parent + } + } + Label { + Layout.fillWidth: true + Layout.leftMargin: 4 + Layout.rightMargin: 4 + text: root.text + elide: Text.ElideRight + color: { + switch (root.tab.lifecycleState) { + case WebEngineView.LifecycleState.Active: + return Material.color(Material.Grey, Material.Shade100) + case WebEngineView.LifecycleState.Frozen: + return Material.color(Material.Blue, Material.Shade400) + case WebEngineView.LifecycleState.Discarded: + return Material.color(Material.Red, Material.Shade400) + } + } + + } + WebToolButton { + action: root.tab.closeAction + text: "✕" + ToolTip.text: action.text + } + } + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.RightButton + propagateComposedEvents: true + onClicked: contextMenu.popup() + } + + Menu { + id: contextMenu + Control { + contentItem: Label { + text: qsTr("Manual lifecycle control") + } + verticalPadding: 9 + horizontalPadding: 14 + } + Repeater { + model: [root.tab.activateAction, root.tab.freezeAction, root.tab.discardAction] + RadioButton { + action: modelData + verticalPadding: 9 + horizontalPadding: 14 + } + } + Control { + contentItem: Label { + text: qsTr("Recommended: %1").arg(recommendedStateText) + property string recommendedStateText: { + switch (root.tab.recommendedState) { + case WebEngineView.LifecycleState.Active: + return root.tab.activateAction.text + case WebEngineView.LifecycleState.Frozen: + return root.tab.freezeAction.text + case WebEngineView.LifecycleState.Discarded: + return root.tab.discardAction.text + } + } + color: Material.hintTextColor + } + font.pointSize: 8 + verticalPadding: 9 + horizontalPadding: 14 + } + } +} diff --git a/examples/webengine/lifecycle/WebTabStack.qml b/examples/webengine/lifecycle/WebTabStack.qml new file mode 100644 index 000000000..75cbba861 --- /dev/null +++ b/examples/webengine/lifecycle/WebTabStack.qml @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQml.Models 2.12 +import QtQuick 2.12 +import QtQuick.Controls 2.12 + +Rectangle { + id: root + + signal closeRequested(int index) + signal drawerRequested + + property int freezeDelay + property int discardDelay + + property int currentIndex + property var currentTab: model.children[currentIndex] + property alias count: model.count + + color: "white" + + ObjectModel { + id: model + } + + Component { + id: factory + WebTab { + readonly property int index : ObjectModel.index + anchors.fill: parent + visible: index == root.currentIndex + freezeDelay: root.freezeDelay + discardDelay: root.discardDelay + onCloseRequested: root.closeRequested(index) + onDrawerRequested: root.drawerRequested() + } + } + + function createNewTab(properties) { + const tab = factory.createObject(root, properties) + model.append(tab) + return tab + } + + function closeTab(index) { + const tab = model.get(index) + model.remove(index) + tab.destroy() + } +} diff --git a/examples/webengine/lifecycle/WebToolButton.qml b/examples/webengine/lifecycle/WebToolButton.qml new file mode 100644 index 000000000..958c083cd --- /dev/null +++ b/examples/webengine/lifecycle/WebToolButton.qml @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Controls.Material 2.12 + +ToolButton { + id: root + font.bold: true + font.pointSize: 12 + ToolTip.delay: 1000 + ToolTip.visible: hovered + implicitWidth: 32 + implicitHeight: 32 +} diff --git a/examples/webengine/lifecycle/doc/images/lifecycle-automatic.png b/examples/webengine/lifecycle/doc/images/lifecycle-automatic.png Binary files differnew file mode 100644 index 000000000..2c62967af --- /dev/null +++ b/examples/webengine/lifecycle/doc/images/lifecycle-automatic.png diff --git a/examples/webengine/lifecycle/doc/images/lifecycle-manual.png b/examples/webengine/lifecycle/doc/images/lifecycle-manual.png Binary files differnew file mode 100644 index 000000000..7fb5c5a80 --- /dev/null +++ b/examples/webengine/lifecycle/doc/images/lifecycle-manual.png diff --git a/examples/webengine/lifecycle/doc/images/lifecycle.png b/examples/webengine/lifecycle/doc/images/lifecycle.png Binary files differnew file mode 100644 index 000000000..87b719022 --- /dev/null +++ b/examples/webengine/lifecycle/doc/images/lifecycle.png diff --git a/examples/webengine/lifecycle/doc/src/lifecycle.qdoc b/examples/webengine/lifecycle/doc/src/lifecycle.qdoc new file mode 100644 index 000000000..4151d0597 --- /dev/null +++ b/examples/webengine/lifecycle/doc/src/lifecycle.qdoc @@ -0,0 +1,167 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: https://www.gnu.org/licenses/fdl-1.3.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \example webengine/lifecycle + \title WebEngine Lifecycle Example + \ingroup webengine-examples + \brief Freezes and discards background tabs to reduce CPU and memory usage. + + \image lifecycle.png + + \e {WebEngine Lifecycle Example} demonstrates how the \l + {WebEngineView::}{lifecycleState} and \l {WebEngineView::}{recommendedState} + properties of the \l {WebEngineView} can be used to reduce the CPU and + memory usage of background tabs in a tabbed browser. + + \include examples-run.qdocinc + + \section1 UI Elements of the Example + + The example uses \l {Qt Quick Controls 2} to implement a traditional tabbed + browser in the \l {Material Style} (dark variant). The main application + window (\c {WebBrowser.qml}) is divided into a header bar at the top and a + main viewing area filling the rest of the window. The header contains the + tab bar (\c {WebTabBar.qml}) with one button per tab (\c + {WebTabButton.qml}). The main area consists of a stack of tabs (\c + {WebTabStack.qml} and \c {WebTab.qml}). Each tab in turn has a tool bar at + the top and a \l {WebEngineView} for displaying web pages. Finally, the main + window also has a \l {Drawer} for changing settings. The drawer can be + opened by clicking the "⋮" button on the tool bar. + + \section1 Overview of Lifecycle States + + Each \l {WebEngineView} item can be in one of three \e {lifecycle states}: + active, frozen, or discarded. These states, like the sleep states of a CPU, + control the resource usage of web views. + + The \e {active} state is the normal, unrestricted state of a web view. All + visible web views are always in the active state, as are all web views that + have not yet finished loading. Only invisible, idle web views can be + transitioned to other lifecycle states. + + The \e {frozen} state is a low CPU usage state. In this state, most HTML + task sources are suspended (frozen) and, as a result, most DOM event + processing and JavaScript execution will also be suspended. The web view + must be invisible in order to be frozen as rendering is not possible in this + state. + + The \e {discarded} state is an extreme resource-saving state. In this state, + the browsing context of the web view will be discarded and the corresponding + renderer subprocess shut down. CPU and memory usage in this state is reduced + virtually to zero. On exiting this state the web page will be automatically + reloaded. The process of entering and exiting the discarded state is similar + to serializing the browsing history of the web view and destroying the view, + then creating a new view and restoring its history. + + See also \l {WebEngineView::LifecycleState}. The equivalent in the Widgets + API is \l {QWebEnginePage::LifecycleState}. + + \section2 The \c {lifecycleState} and \c {recommendedState} Properties + + The \l {WebEngineView::}{lifecycleState} property of the \l {WebEngineView} + type is a read-write property that controls the current lifecycle state of + the web view. This property is designed to place as few restrictions as + possible on what states can be transitioned to. For example, it is allowed + to freeze a web view that is currently playing music in the background, + stopping the music. In order to implement a less aggressive resource-saving + strategy that avoids interrupting user-visible background activity, the \l + {WebEngineView::} {recommendedState} property must be used. + + The \l {WebEngineView::}{recommendedState} property of the \l + {WebEngineView} type is a read-only property that calculates a safe limit on + the \l {WebEngineView::}{lifecycleState} property, taking into account the + current activity of the web view. So, in the example of a web view playing + music in the background, the recommended state will be \c {Active} since a + more aggressive state would stop the music. If the application wants to + avoid interrupting background activity, then it should avoid putting the web + view into a more aggressively resource-saving lifecycle state than what's + given by \l {WebEngineView::}{recommendedState}. + + See also \l {WebEngineView::lifecycleState} and \l + {WebEngineView::recommendedState}. The equivalents in the Widgets API are \l + {QWebEnginePage::lifecycleState} and \l {QWebEnginePage::recommendedState}. + + \section2 The Page Lifecycle API + + The \l {WebEngineView::}{lifecycleState} property is connected to the \l + {https://wicg.github.io/page-lifecycle/spec.html}{Page Lifecycle API}, a + work-in-progress extension to the HTML standard that specifies two new DOM + events, \c {freeze} and \c {resume}, and adds a new \c + {Document.wasDiscarded} boolean property. The \c {freeze} and \c {resume} + events are fired when transitioning from the \c {Active} to the \c {Frozen + state}, and vice-versa. The \c {Document.wasDiscarded} property is set to \c + {true} when transition from the \c {Discarded} state to the \c {Active} + state. + + \section1 Lifecycle States in the Example + + The example implements two ways of changing the lifecycle state: manual and + automatic. The manual way uses the \l {WebEngineView::}{lifecycleState} + property directly to change the web view lifecycle state, while the + automatic way is timer-based and also takes into account the \l + {WebEngineView::}{recommendedState}. + + The tab titles in the tab bar are color coded with frozen tabs shown in blue + and discarded in red. + + \section2 Manual Lifecycle Control + + \image lifecycle-manual.png + + Manual control is provided by context menus on the tab bar buttons (\c + {WebTabButton.qml}). The menu has three radio buttons, one for each + lifecycle state, with the current state checked. Some buttons may be + disabled, either because they represent illegal state transitions (for + example, a \c {Discarded} view cannot directly transition to the \c {Frozen} + state), or because other preconditions are not fulfilled (for example, a + visible view can only be in the \c {Active} state). + + \section2 Automatic Lifecycle Control + + \image lifecycle-automatic.png + + Automatic control is implemented with a \l {Timer} in the \c {WebTab} + component (\c {WebTab.qml}). The timer is started whenever the \l + {WebEngineView::}{lifecycleState} of the web view does not match it's \l + {WebEngineView::}{recommendedState}. Once the timer fires, the view's + lifecycle state is set to the recommended state. + + The time delay is used to avoid changing the lifecycle state too quickly + when the user is switching between tabs. The freezing and discarding delays + can be changed in the settings drawer accessed through the "⋮" button on the + tool bar. + + This is a rather simple algorithm for automatic lifecycle control, however + more sophisticated algorithms could also be conceived and implemented on the + basis of the \l {WebEngineView::}{lifecycleState} property. For example, the + Chromium browser experimentally uses a pretrained deep neural network to + predict the next tab activation time by the user, essentially ranking tabs + based on how interesting they are to the user. Implementing such an + algorithm is left as an exercise to the reader for now. + +*/ diff --git a/examples/webengine/lifecycle/lifecycle.pro b/examples/webengine/lifecycle/lifecycle.pro new file mode 100644 index 000000000..74fbf23c1 --- /dev/null +++ b/examples/webengine/lifecycle/lifecycle.pro @@ -0,0 +1,10 @@ +TEMPLATE = app + +QT += quickcontrols2 webengine + +SOURCES += main.cpp + +RESOURCES += resources.qrc + +target.path = $$[QT_INSTALL_EXAMPLES]/webengine/lifecycle +INSTALLS += target diff --git a/examples/webengine/lifecycle/main.cpp b/examples/webengine/lifecycle/main.cpp new file mode 100644 index 000000000..83907cbaf --- /dev/null +++ b/examples/webengine/lifecycle/main.cpp @@ -0,0 +1,66 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QGuiApplication> +#include <QQmlApplicationEngine> +#include <QQmlContext> +#include <QQuickStyle> +#include <qtwebengineglobal.h> + +int main(int argc, char *argv[]) +{ + QCoreApplication::setOrganizationName("QtExamples"); + QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + QGuiApplication app(argc, argv); + QtWebEngine::initialize(); + QQmlApplicationEngine engine; + engine.load(QUrl(QStringLiteral("qrc:/WebBrowser.qml"))); + return app.exec(); +} diff --git a/examples/webengine/lifecycle/qtquickcontrols2.conf b/examples/webengine/lifecycle/qtquickcontrols2.conf new file mode 100644 index 000000000..68c77cec5 --- /dev/null +++ b/examples/webengine/lifecycle/qtquickcontrols2.conf @@ -0,0 +1,6 @@ +[Controls] +Style=Material + +[Material] +Theme=Dark +Variant=Dense diff --git a/examples/webengine/lifecycle/resources.qrc b/examples/webengine/lifecycle/resources.qrc new file mode 100644 index 000000000..41e5092ce --- /dev/null +++ b/examples/webengine/lifecycle/resources.qrc @@ -0,0 +1,11 @@ +<RCC> + <qresource prefix="/"> + <file>WebBrowser.qml</file> + <file>WebTab.qml</file> + <file>WebTabBar.qml</file> + <file>WebTabButton.qml</file> + <file>WebTabStack.qml</file> + <file>WebToolButton.qml</file> + <file>qtquickcontrols2.conf</file> + </qresource> +</RCC> diff --git a/examples/webengine/minimal/main.cpp b/examples/webengine/minimal/main.cpp index 9db6ea6aa..8bcd0e0e9 100644 --- a/examples/webengine/minimal/main.cpp +++ b/examples/webengine/minimal/main.cpp @@ -56,6 +56,7 @@ int main(int argc, char *argv[]) { QCoreApplication::setOrganizationName("QtExamples"); QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts); QGuiApplication app(argc, argv); QtWebEngine::initialize(); diff --git a/examples/webengine/quicknanobrowser/ApplicationRoot.qml b/examples/webengine/quicknanobrowser/ApplicationRoot.qml index 3bc571546..df4bbdb4a 100644 --- a/examples/webengine/quicknanobrowser/ApplicationRoot.qml +++ b/examples/webengine/quicknanobrowser/ApplicationRoot.qml @@ -49,7 +49,7 @@ ****************************************************************************/ import QtQuick 2.1 -import QtWebEngine 1.9 +import QtWebEngine 1.10 QtObject { id: root diff --git a/examples/webengine/quicknanobrowser/BrowserWindow.qml b/examples/webengine/quicknanobrowser/BrowserWindow.qml index d095703fb..39a13df59 100644 --- a/examples/webengine/quicknanobrowser/BrowserWindow.qml +++ b/examples/webengine/quicknanobrowser/BrowserWindow.qml @@ -57,7 +57,7 @@ import QtQuick.Controls.Styles 1.0 import QtQuick.Dialogs 1.2 import QtQuick.Layouts 1.0 import QtQuick.Window 2.1 -import QtWebEngine 1.9 +import QtWebEngine 1.11 ApplicationWindow { id: browserWindow @@ -73,6 +73,10 @@ ApplicationWindow { // Make sure the Qt.WindowFullscreenButtonHint is set on OS X. Component.onCompleted: flags = flags | Qt.WindowFullscreenButtonHint + onCurrentWebViewChanged: { + findBar.reset(); + } + // Create a styleItem to determine the platform. // When using style "mac", ToolButtons are not supposed to accept focus. QQCPrivate.StyleItem { id: styleItem } @@ -136,6 +140,9 @@ ApplicationWindow { fullScreenNotification.hide(); currentWebView.triggerWebAction(WebEngineView.ExitFullScreen); } + + if (findBar.visible) + findBar.visible = false; } } Action { @@ -187,6 +194,21 @@ ApplicationWindow { shortcut: StandardKey.Forward onTriggered: currentWebView.triggerWebAction(WebEngineView.Forward) } + Action { + shortcut: StandardKey.Find + onTriggered: { + if (!findBar.visible) + findBar.visible = true; + } + } + Action { + shortcut: StandardKey.FindNext + onTriggered: findBar.findNext() + } + Action { + shortcut: StandardKey.FindPrevious + onTriggered: findBar.findPrevious() + } toolBar: ToolBar { id: navigationBar @@ -553,6 +575,19 @@ ApplicationWindow { selection.certificates[0].select(); } + onFindTextFinished: function(result) { + if (!findBar.visible) + findBar.visible = true; + + findBar.numberOfMatches = result.numberOfMatches; + findBar.activeMatchOrdinal = result.activeMatchOrdinal; + } + + onLoadingChanged: function(loadRequest) { + if (loadRequest.status == WebEngineView.LoadStartedStatus) + findBar.reset(); + } + Timer { id: reloadTimer interval: 0 @@ -625,6 +660,28 @@ ApplicationWindow { download.accept(); } + FindBar { + id: findBar + visible: false + anchors.right: parent.right + anchors.rightMargin: 10 + anchors.top: parent.top + + onFindNext: { + if (text) + currentWebView && currentWebView.findText(text); + else if (!visible) + visible = true; + } + onFindPrevious: { + if (text) + currentWebView && currentWebView.findText(text, WebEngineView.FindBackward); + else if (!visible) + visible = true; + } + } + + Rectangle { id: statusBubble color: "oldlace" diff --git a/examples/webengine/quicknanobrowser/DownloadView.qml b/examples/webengine/quicknanobrowser/DownloadView.qml index f6ebeab4a..e26c770bc 100644 --- a/examples/webengine/quicknanobrowser/DownloadView.qml +++ b/examples/webengine/quicknanobrowser/DownloadView.qml @@ -101,7 +101,7 @@ Rectangle { } Label { id: label - text: path + text: downloadDirectory + "/" + downloadFileName anchors { verticalCenter: cancelButton.verticalCenter left: parent.left diff --git a/examples/webengine/quicknanobrowser/FindBar.qml b/examples/webengine/quicknanobrowser/FindBar.qml new file mode 100644 index 000000000..2d673592a --- /dev/null +++ b/examples/webengine/quicknanobrowser/FindBar.qml @@ -0,0 +1,146 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtWebEngine module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import QtQuick.Controls.Styles 1.4 +import QtQuick.Layouts 1.0 + +Rectangle { + id: root + + property int numberOfMatches: 0 + property int activeMatchOrdinal: 0 + property alias text: findTextField.text + + function reset() { + numberOfMatches = 0; + activeMatchOrdinal = 0; + visible = false; + } + + signal findNext() + signal findPrevious() + + width: 250 + height: 35 + radius: 2 + + border.width: 1 + border.color: "black" + color: "white" + + onVisibleChanged: { + if (visible) + findTextField.forceActiveFocus(); + } + + + RowLayout { + anchors.fill: parent + anchors.topMargin: 5 + anchors.bottomMargin: 5 + anchors.leftMargin: 10 + anchors.rightMargin: 10 + + spacing: 5 + + Rectangle { + Layout.fillWidth: true + Layout.fillHeight: true + + TextField { + id: findTextField + anchors.fill: parent + + style: TextFieldStyle { + background: Rectangle { + color: "transparent" + } + } + + onAccepted: root.findNext() + onTextChanged: root.findNext() + onActiveFocusChanged: activeFocus ? selectAll() : deselect() + } + } + + Label { + text: activeMatchOrdinal + "/" + numberOfMatches + visible: findTextField.text != "" + } + + Rectangle { + border.width: 1 + border.color: "#ddd" + width: 2 + height: parent.height + anchors.topMargin: 5 + anchors.bottomMargin: 5 + } + + ToolButton { + text: "<" + enabled: numberOfMatches > 0 + onClicked: root.findPrevious() + } + + ToolButton { + text: ">" + enabled: numberOfMatches > 0 + onClicked: root.findNext() + } + + ToolButton { + text: "x" + onClicked: root.visible = false + } + } +} diff --git a/examples/webengine/quicknanobrowser/quicknanobrowser.pro b/examples/webengine/quicknanobrowser/quicknanobrowser.pro index 5a27f5fd4..922cf79e2 100644 --- a/examples/webengine/quicknanobrowser/quicknanobrowser.pro +++ b/examples/webengine/quicknanobrowser/quicknanobrowser.pro @@ -10,6 +10,7 @@ OTHER_FILES += ApplicationRoot.qml \ BrowserDialog.qml \ BrowserWindow.qml \ DownloadView.qml \ + FindBar.qml \ FullScreenNotification.qml RESOURCES += resources.qrc diff --git a/examples/webengine/quicknanobrowser/resources.qrc b/examples/webengine/quicknanobrowser/resources.qrc index c6270897d..50ea05f5e 100644 --- a/examples/webengine/quicknanobrowser/resources.qrc +++ b/examples/webengine/quicknanobrowser/resources.qrc @@ -4,6 +4,7 @@ <file>BrowserDialog.qml</file> <file>BrowserWindow.qml</file> <file>DownloadView.qml</file> + <file>FindBar.qml</file> <file>FullScreenNotification.qml</file> </qresource> <qresource prefix="icons"> diff --git a/examples/webengine/webengine.pro b/examples/webengine/webengine.pro index 5ad620390..058868395 100644 --- a/examples/webengine/webengine.pro +++ b/examples/webengine/webengine.pro @@ -8,5 +8,6 @@ SUBDIRS += \ qtHaveModule(quickcontrols2) { SUBDIRS += \ + lifecycle \ recipebrowser } diff --git a/examples/webenginewidgets/simplebrowser/browserwindow.cpp b/examples/webenginewidgets/simplebrowser/browserwindow.cpp index 5d00cd19a..7b167ded7 100644 --- a/examples/webenginewidgets/simplebrowser/browserwindow.cpp +++ b/examples/webenginewidgets/simplebrowser/browserwindow.cpp @@ -66,6 +66,7 @@ #include <QStatusBar> #include <QToolBar> #include <QVBoxLayout> +#include <QWebEngineFindTextResult> #include <QWebEngineProfile> BrowserWindow::BrowserWindow(Browser *browser, QWebEngineProfile *profile, bool forDevTools) @@ -99,7 +100,7 @@ BrowserWindow::BrowserWindow(Browser *browser, QWebEngineProfile *profile, bool QWidget *centralWidget = new QWidget(this); QVBoxLayout *layout = new QVBoxLayout; layout->setSpacing(0); - layout->setMargin(0); + layout->setContentsMargins(0, 0, 0, 0); if (!forDevTools) { addToolBarBreak(); @@ -129,6 +130,7 @@ BrowserWindow::BrowserWindow(Browser *browser, QWebEngineProfile *profile, bool connect(m_urlLineEdit, &QLineEdit::returnPressed, [this]() { m_tabWidget->setUrl(QUrl::fromUserInput(m_urlLineEdit->text())); }); + connect(m_tabWidget, &TabWidget::findTextFinished, this, &BrowserWindow::handleFindTextFinished); QAction *focusUrlLineEditAction = new QAction(this); addAction(focusUrlLineEditAction); @@ -460,10 +462,7 @@ void BrowserWindow::handleFindActionTriggered() m_lastSearch, &ok); if (ok && !search.isEmpty()) { m_lastSearch = search; - currentTab()->findText(m_lastSearch, 0, [this](bool found) { - if (!found) - statusBar()->showMessage(tr("\"%1\" not found.").arg(m_lastSearch)); - }); + currentTab()->findText(m_lastSearch); } } @@ -526,3 +525,14 @@ void BrowserWindow::handleDevToolsRequested(QWebEnginePage *source) source->setDevToolsPage(m_browser->createDevToolsWindow()->currentTab()->page()); source->triggerAction(QWebEnginePage::InspectElement); } + +void BrowserWindow::handleFindTextFinished(const QWebEngineFindTextResult &result) +{ + if (result.numberOfMatches() == 0) { + statusBar()->showMessage(tr("\"%1\" not found.").arg(m_lastSearch)); + } else { + statusBar()->showMessage(tr("\"%1\" found: %2/%3").arg(m_lastSearch, + QString::number(result.activeMatchOrdinal()), + QString::number(result.numberOfMatches()))); + } +} diff --git a/examples/webenginewidgets/simplebrowser/browserwindow.h b/examples/webenginewidgets/simplebrowser/browserwindow.h index 8f328b751..11a655469 100644 --- a/examples/webenginewidgets/simplebrowser/browserwindow.h +++ b/examples/webenginewidgets/simplebrowser/browserwindow.h @@ -88,6 +88,7 @@ private slots: void handleWebViewTitleChanged(const QString &title); void handleWebActionEnabledChanged(QWebEnginePage::WebAction action, bool enabled); void handleDevToolsRequested(QWebEnginePage *source); + void handleFindTextFinished(const QWebEngineFindTextResult &result); private: QMenu *createFileMenu(TabWidget *tabWidget); diff --git a/examples/webenginewidgets/simplebrowser/downloadmanagerwidget.cpp b/examples/webenginewidgets/simplebrowser/downloadmanagerwidget.cpp index e0a511475..b6f9e9c13 100644 --- a/examples/webenginewidgets/simplebrowser/downloadmanagerwidget.cpp +++ b/examples/webenginewidgets/simplebrowser/downloadmanagerwidget.cpp @@ -55,6 +55,7 @@ #include "downloadwidget.h" #include <QFileDialog> +#include <QDir> #include <QWebEngineDownloadItem> DownloadManagerWidget::DownloadManagerWidget(QWidget *parent) @@ -68,11 +69,12 @@ void DownloadManagerWidget::downloadRequested(QWebEngineDownloadItem *download) { Q_ASSERT(download && download->state() == QWebEngineDownloadItem::DownloadRequested); - QString path = QFileDialog::getSaveFileName(this, tr("Save as"), download->path()); + QString path = QFileDialog::getSaveFileName(this, tr("Save as"), QDir(download->downloadDirectory()).filePath(download->downloadFileName())); if (path.isEmpty()) return; - download->setPath(path); + download->setDownloadDirectory(QFileInfo(path).path()); + download->setDownloadFileName(QFileInfo(path).fileName()); download->accept(); add(new DownloadWidget(download)); diff --git a/examples/webenginewidgets/simplebrowser/downloadwidget.cpp b/examples/webenginewidgets/simplebrowser/downloadwidget.cpp index 1d599a19d..ddddc5e5d 100644 --- a/examples/webenginewidgets/simplebrowser/downloadwidget.cpp +++ b/examples/webenginewidgets/simplebrowser/downloadwidget.cpp @@ -57,10 +57,11 @@ DownloadWidget::DownloadWidget(QWebEngineDownloadItem *download, QWidget *parent) : QFrame(parent) , m_download(download) - , m_timeAdded(QTime::currentTime()) + , m_timeAdded() { + m_timeAdded.start(); setupUi(this); - m_dstName->setText(QFileInfo(m_download->path()).fileName()); + m_dstName->setText(m_download->downloadFileName()); m_srcUrl->setText(m_download->url().toDisplayString()); connect(m_cancelButton, &QPushButton::clicked, diff --git a/examples/webenginewidgets/simplebrowser/downloadwidget.h b/examples/webenginewidgets/simplebrowser/downloadwidget.h index c08b298bc..c20676aa6 100644 --- a/examples/webenginewidgets/simplebrowser/downloadwidget.h +++ b/examples/webenginewidgets/simplebrowser/downloadwidget.h @@ -54,7 +54,7 @@ #include "ui_downloadwidget.h" #include <QFrame> -#include <QTime> +#include <QElapsedTimer> QT_BEGIN_NAMESPACE class QWebEngineDownloadItem; @@ -80,7 +80,7 @@ private: QString withUnit(qreal bytes); QWebEngineDownloadItem *m_download; - QTime m_timeAdded; + QElapsedTimer m_timeAdded; }; #endif // DOWNLOADWIDGET_H diff --git a/examples/webenginewidgets/simplebrowser/tabwidget.cpp b/examples/webenginewidgets/simplebrowser/tabwidget.cpp index 369bebfd9..3b6d84ebe 100644 --- a/examples/webenginewidgets/simplebrowser/tabwidget.cpp +++ b/examples/webenginewidgets/simplebrowser/tabwidget.cpp @@ -200,6 +200,10 @@ void TabWidget::setupView(WebView *webView) closeTab(index); }); connect(webView, &WebView::devToolsRequested, this, &TabWidget::devToolsRequested); + connect(webPage, &QWebEnginePage::findTextFinished, [this, webView](const QWebEngineFindTextResult &result) { + if (currentIndex() == indexOf(webView)) + emit findTextFinished(result); + }); } WebView *TabWidget::createTab() diff --git a/examples/webenginewidgets/simplebrowser/tabwidget.h b/examples/webenginewidgets/simplebrowser/tabwidget.h index bf83781df..fba61d44f 100644 --- a/examples/webenginewidgets/simplebrowser/tabwidget.h +++ b/examples/webenginewidgets/simplebrowser/tabwidget.h @@ -78,6 +78,7 @@ signals: void favIconChanged(const QIcon &icon); void webActionEnabledChanged(QWebEnginePage::WebAction action, bool enabled); void devToolsRequested(QWebEnginePage *source); + void findTextFinished(const QWebEngineFindTextResult &result); public slots: // current tab/page slots diff --git a/examples/webenginewidgets/simplebrowser/webpage.cpp b/examples/webenginewidgets/simplebrowser/webpage.cpp index 99849c77d..2e49f651f 100644 --- a/examples/webenginewidgets/simplebrowser/webpage.cpp +++ b/examples/webenginewidgets/simplebrowser/webpage.cpp @@ -57,6 +57,7 @@ #include <QAuthenticator> #include <QMessageBox> #include <QStyle> +#include <QTimer> #include <QWebEngineCertificateError> WebPage::WebPage(QWebEngineProfile *profile, QObject *parent) @@ -74,22 +75,33 @@ WebPage::WebPage(QWebEngineProfile *profile, QObject *parent) bool WebPage::certificateError(const QWebEngineCertificateError &error) { QWidget *mainWindow = view()->window(); - if (error.isOverridable()) { - QDialog dialog(mainWindow); - dialog.setModal(true); - dialog.setWindowFlags(dialog.windowFlags() & ~Qt::WindowContextHelpButtonHint); - Ui::CertificateErrorDialog certificateDialog; - certificateDialog.setupUi(&dialog); - certificateDialog.m_iconLabel->setText(QString()); - QIcon icon(mainWindow->style()->standardIcon(QStyle::SP_MessageBoxWarning, 0, mainWindow)); - certificateDialog.m_iconLabel->setPixmap(icon.pixmap(32, 32)); - certificateDialog.m_errorLabel->setText(error.errorDescription()); - dialog.setWindowTitle(tr("Certificate Error")); - return dialog.exec() == QDialog::Accepted; - } - QMessageBox::critical(mainWindow, tr("Certificate Error"), error.errorDescription()); - return false; + QWebEngineCertificateError deferredError = error; + deferredError.defer(); + + QTimer::singleShot(0, mainWindow, [mainWindow, error = std::move(deferredError)] () mutable { + if (!error.deferred()) { + QMessageBox::critical(mainWindow, tr("Certificate Error"), error.errorDescription()); + } else { + QDialog dialog(mainWindow); + dialog.setModal(true); + dialog.setWindowFlags(dialog.windowFlags() & ~Qt::WindowContextHelpButtonHint); + + Ui::CertificateErrorDialog certificateDialog; + certificateDialog.setupUi(&dialog); + certificateDialog.m_iconLabel->setText(QString()); + QIcon icon(mainWindow->style()->standardIcon(QStyle::SP_MessageBoxWarning, 0, mainWindow)); + certificateDialog.m_iconLabel->setPixmap(icon.pixmap(32, 32)); + certificateDialog.m_errorLabel->setText(error.errorDescription()); + dialog.setWindowTitle(tr("Certificate Error")); + + if (dialog.exec() == QDialog::Accepted) + error.ignoreCertificateError(); + else + error.rejectCertificate(); + } + }); + return true; } void WebPage::handleAuthenticationRequired(const QUrl &requestUrl, QAuthenticator *auth) diff --git a/examples/webenginewidgets/simplebrowser/webpopupwindow.cpp b/examples/webenginewidgets/simplebrowser/webpopupwindow.cpp index 19e3b0124..566723e1f 100644 --- a/examples/webenginewidgets/simplebrowser/webpopupwindow.cpp +++ b/examples/webenginewidgets/simplebrowser/webpopupwindow.cpp @@ -66,7 +66,7 @@ WebPopupWindow::WebPopupWindow(QWebEngineProfile *profile) setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); QVBoxLayout *layout = new QVBoxLayout; - layout->setMargin(0); + layout->setContentsMargins(0, 0, 0, 0); setLayout(layout); layout->addWidget(m_urlLineEdit); layout->addWidget(m_view); |