diff --git a/src/Morph/Web/MorphWebContext.qml b/src/Morph/Web/MorphWebContext.qml
index da79298cd..ae5dccf48 100644
--- a/src/Morph/Web/MorphWebContext.qml
+++ b/src/Morph/Web/MorphWebContext.qml
@@ -26,6 +26,8 @@ WebEngineProfile {
property alias dataPath: oxideContext.persistentStoragePath
property alias maxCacheSizeHint: oxideContext.httpCacheMaximumSize
property alias incognito: oxideContext.offTheRecord
+ property int userAgentId: 0
+ property string customUserAgent: ""
readonly property string defaultUserAgent: __ua.defaultUA
offTheRecord: false
@@ -35,7 +37,7 @@ WebEngineProfile {
cachePath: cacheLocation
maxCacheSizeHint: cacheSizeHint
- userAgent: defaultUserAgent
+ userAgent: (customUserAgent !== "") ? customUserAgent : defaultUserAgent
persistentCookiesPolicy: WebEngineProfile.ForcePersistentCookies
diff --git a/src/Morph/Web/MorphWebView.qml b/src/Morph/Web/MorphWebView.qml
index e288f388a..c06a4e85c 100644
--- a/src/Morph/Web/MorphWebView.qml
+++ b/src/Morph/Web/MorphWebView.qml
@@ -18,7 +18,7 @@
import QtQuick 2.4
import QtQuick.Window 2.2
-import QtWebEngine 1.5
+import QtWebEngine 1.10
import Ubuntu.Components 1.3
import Ubuntu.Components.Popups 1.3
import "." // QTBUG-34418
diff --git a/src/Morph/Web/plugin.cpp b/src/Morph/Web/plugin.cpp
index d405e0f3c..b523ebbfe 100644
--- a/src/Morph/Web/plugin.cpp
+++ b/src/Morph/Web/plugin.cpp
@@ -232,6 +232,7 @@ void MorphBrowserPlugin::initializeEngine(QQmlEngine* engine, const char* uri)
void MorphBrowserPlugin::registerTypes(const char* uri)
{
Q_ASSERT(uri == QLatin1String("Morph.Web"));
+ qmlRegisterModule(uri, 0, 1);
}
#include "plugin.moc"
diff --git a/src/app/BrowserView.qml b/src/app/BrowserView.qml
index 2d08e632a..f83ebab81 100644
--- a/src/app/BrowserView.qml
+++ b/src/app/BrowserView.qml
@@ -52,6 +52,8 @@ FocusScope {
OrientationHelper {
id: contentsItem
+ automaticOrientation: false
+
KeyboardRectangle {
id: _osk
}
diff --git a/src/app/BrowserWindow.qml b/src/app/BrowserWindow.qml
index cf69c807e..0e8c6293c 100644
--- a/src/app/BrowserWindow.qml
+++ b/src/app/BrowserWindow.qml
@@ -29,7 +29,6 @@ Window {
property var currentWebview: null
property bool hasTouchScreen: false
- contentOrientation: Screen.orientation
minimumWidth: units.gu(40)
minimumHeight: units.gu(20)
diff --git a/src/app/ChromeBase.qml b/src/app/ChromeBase.qml
index 7dcb78d99..aa7c007b8 100644
--- a/src/app/ChromeBase.qml
+++ b/src/app/ChromeBase.qml
@@ -30,8 +30,8 @@ StyledItem {
property alias backgroundColor: backgroundRect.color
- property alias loading: progressBar.visible
- property alias loadProgress: progressBar.value
+ property bool loading: false
+ property real loadProgress: 0.0
states: [
State {
@@ -71,6 +71,9 @@ StyledItem {
ThinProgressBar {
id: progressBar
+ visible: chrome.loading
+ value: chrome.loadProgress
+
anchors {
left: parent.left
right: parent.right
diff --git a/src/app/CustomUserAgentsPage.qml b/src/app/CustomUserAgentsPage.qml
index a8837bca4..72faef0c1 100644
--- a/src/app/CustomUserAgentsPage.qml
+++ b/src/app/CustomUserAgentsPage.qml
@@ -28,22 +28,12 @@ BrowserPage {
property bool selectMode
signal done()
- signal reload(string selectedUserAgent)
+ signal reload()
title: i18n.tr("Custom User Agents")
showBackAction: !selectMode
- function setUserAgentAsCurrentItem(userAgentName) {
- for (var index = 0; index < customUserAgentsListView.count; index++) {
- var userAgent = customUserAgentsListView.model.get(index);
- if (userAgent.name === userAgentName) {
- customUserAgentsListView.currentIndex = index;
- return;
- }
- }
- }
-
leadingActions: [
Action {
objectName: "close"
@@ -100,20 +90,11 @@ BrowserPage {
iconName: "add"
visible: !selectMode
onTriggered: {
- var promptDialog = PopupUtils.open(Qt.resolvedUrl("PromptDialog.qml"), customUserAgentsPage);
- promptDialog.title = i18n.tr("New User Agent")
- promptDialog.message = i18n.tr("Add the name for the new user agent")
- promptDialog.accept.connect(function(text) {
- if (text !== "") {
- if (UserAgentsModel.contains(text)) {
- customUserAgentsPage.setUserAgentAsCurrentItem(text);
- }
- else {
- customUserAgentsListView.currentIndex = -1;
- UserAgentsModel.insertEntry(text, "");
- reload(text);
- }
- }
+ var addDialog = PopupUtils.open(Qt.resolvedUrl("EditCustomUserAgentDialog.qml"), customUserAgentsPage);
+ addDialog.title = i18n.tr("New User Agent");
+ addDialog.accept.connect(function(userAgentName, userAgentString) {
+ UserAgentsModel.insertEntry(userAgentName, userAgentString);
+ reload();
});
}
}
@@ -140,81 +121,15 @@ BrowserPage {
delegate: ListItem {
id: item
- readonly property bool isCurrentItem: item.ListView.isCurrentItem
- //height: isCurrentItem ? layout.height : units.gu(5)
- height: layout.height
- color: isCurrentItem ? theme.palette.selected.base : theme.palette.normal.background
-
- MouseArea {
- anchors.fill: parent
- onClicked: customUserAgentsListView.currentIndex = index
- }
-
- SlotsLayout {
- id: layout
- width: parent.width
-
- mainSlot:
-
- Column {
-
- spacing: units.gu(2)
-
- Row {
- spacing: units.gu(1.5)
- height: item.ListView.isCurrentItem ? units.gu(2) : units.gu(1)
- width: parent.width
-
- Icon {
- anchors.verticalCenter: userAgentName.verticalCenter
- visible: item.ListView.isCurrentItem
- name: "avatar-default-symbolic"
- height: units.gu(2)
- width: height
- }
-
- Label {
- id: userAgentLabel
- anchors.verticalCenter: parent.verticalCenter
- visible: ! item.ListView.isCurrentItem
- width: parent.width
- height: units.gu(1)
- text: model.name
- }
-
- TextField {
- id: userAgentName
- visible: item.ListView.isCurrentItem
- text: model.name
- onFocusChanged: {
- if (!focus) {
- if (text === "") {
- text = model.name;
- }
- else {
- UserAgentsModel.setUserAgentName(model.id, text);
- }
- }
- }
- }
-
- }
- TextArea {
- visible: item.ListView.isCurrentItem
- width: parent.width
- text: model.userAgentString
- placeholderText: i18n.tr("enter user agent string...")
- onFocusChanged: {
- if (! focus) {
- UserAgentsModel.setUserAgentString(model.id, text);
- }
- }
- }
- }
+ Label {
+ id: userAgentLabel
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.horizontalCenter: parent.horizontalCenter
+ height: units.gu(1)
+ text: model.name
}
-
leadingActions: deleteActionList
ListItemActions {
@@ -231,6 +146,30 @@ BrowserPage {
}
]
}
+
+ trailingActions: trailingActionList
+
+ ListItemActions {
+ id: trailingActionList
+ actions: [
+ Action {
+ objectName: "trailingActionList.edit"
+ iconName: "edit"
+ enabled: true
+ onTriggered: {
+ var editDialog = PopupUtils.open(Qt.resolvedUrl("EditCustomUserAgentDialog.qml"), customUserAgentsPage);
+ editDialog.title = i18n.tr("Edit User Agent");
+ editDialog.previousUserAgentName = model.name;
+ editDialog.userAgentName = model.name;
+ editDialog.userAgentString = model.userAgentString;
+ editDialog.accept.connect(function(userAgentName, userAgentString) {
+ UserAgentsModel.setUserAgentString(model.id, userAgentString);
+ UserAgentsModel.setUserAgentName(model.id, userAgentName);
+ });
+ }
+ }
+ ]
+ }
}
}
diff --git a/src/app/DomainSettingsPage.qml b/src/app/DomainSettingsPage.qml
index 21a67a747..a19c33c9e 100644
--- a/src/app/DomainSettingsPage.qml
+++ b/src/app/DomainSettingsPage.qml
@@ -17,6 +17,7 @@
*/
import QtQuick 2.6
+import QtQuick.Controls 2.2
import Qt.labs.settings 1.0
import Ubuntu.Components 1.3
import Ubuntu.Components.Popups 1.3
@@ -143,8 +144,7 @@ FocusScope {
ListItem {
id: useragentsMenu
z: 3
- // custom user agents deactivated for now
- height: 0 //units.gu(6)
+ height: units.gu(6)
color: theme.palette.normal.background
ListItemLayout {
title.text: i18n.tr("Custom User Agents")
@@ -170,6 +170,7 @@ FocusScope {
readonly property bool isCurrentItem: item.ListView.isCurrentItem
readonly property string domain: model.domain
readonly property int userAgentId: model.userAgentId
+ readonly property int locationPreference: model.allowLocation
height: isCurrentItem ? layout.height : units.gu(5)
color: isCurrentItem ? ((theme.palette.selected.background.hslLightness > 0.5) ? Qt.darker(theme.palette.selected.background, 1.05) : Qt.lighter(theme.palette.selected.background, 1.5)) : theme.palette.normal.background
@@ -204,11 +205,13 @@ FocusScope {
Label {
width: parent.width * 0.9
text: i18n.tr("allowed to launch other apps")
+ anchors.verticalCenter: parent.verticalCenter
}
CheckBox {
checked: model.allowCustomUrlSchemes
onTriggered: DomainSettingsModel.allowCustomUrlSchemes(model.domain, checked)
+ anchors.verticalCenter: parent.verticalCenter
}
}
@@ -219,25 +222,29 @@ FocusScope {
visible: item.ListView.isCurrentItem
Label {
- width: parent.width * 0.9
- text: i18n.tr("allowed to access your location")
+ width: parent.width * 0.5
+ text: i18n.tr("access your location")
+ anchors.verticalCenter: parent.verticalCenter
}
- CheckBox {
- checked: model.allowLocation
- onTriggered: DomainSettingsModel.allowLocation(model.domain, checked)
+ ComboBox {
+ model: [ i18n.tr("Ask each time"), i18n.tr("Allowed"), i18n.tr("Denied") ]
+ currentIndex: item.locationPreference
+ onCurrentIndexChanged: DomainSettingsModel.setLocationPreference(item.domain, currentIndex)
+ anchors.verticalCenter: parent.verticalCenter
}
}
Row {
spacing: units.gu(1.5)
height: units.gu(1)
- // deactivated for now
- visible: false //item.ListView.isCurrentItem
+ visible: item.ListView.isCurrentItem
Label {
+ width: parent.width * 0.9
text: i18n.tr("custom user agent")
opacity: UserAgentsModel.count > 0 ? 1.0 : 0.5
+ anchors.verticalCenter: parent.verticalCenter
}
CheckBox {
@@ -245,33 +252,30 @@ FocusScope {
enabled: UserAgentsModel.count > 0
checked: model.userAgentId > 0
onTriggered: {
- optSelect.selectedIndex = -1;
+ userAgentSelect.currentIndex = -1;
if (checked) {
- if( UserAgentsModel.count === 1)
+ if(UserAgentsModel.count === 1)
{
- optSelect.selectedIndex = 0;
- DomainSettingsModel.setUserAgentId(item.domain, optSelect.model.get(optSelect.selectedIndex).id);
+ userAgentSelect.currentIndex = 0;
+ DomainSettingsModel.setUserAgentId(item.domain, userAgentSelect.model.get(userAgentSelect.currentIndex).id);
}
else
{
- optSelect.currentlyExpanded = true;
+ userAgentSelect.onPressedChanged();
}
}
else {
DomainSettingsModel.setUserAgentId(model.domain, 0);
}
}
+ anchors.verticalCenter: parent.verticalCenter
}
}
- /* ToDo: Can we do sth. about the following log messages ?
- file:///usr/lib/arm-linux-gnueabihf/qt5/qml/Ubuntu/Components/1.3/OptionSelector.qml:330:13:
- QML ListView: Binding loop detected for property "itemHeight"
- */
- OptionSelector {
+ ComboBox {
- id: optSelect
+ id: userAgentSelect
visible: customUserAgentCheckbox.checked
enabled: (UserAgentsModel.count > 1)
@@ -281,15 +285,14 @@ FocusScope {
sort.property: "name"
sort.order: Qt.AscendingOrder
}
- delegate: OptionSelectorDelegate {
- text: model.name
- }
-
+
+ textRole: "name"
+
function updateIndex() {
for (var i = 0; i < model.count; ++i) {
if (item.userAgentId === model.get(i).id)
{
- selectedIndex = i;
+ currentIndex = i;
}
}
}
@@ -299,26 +302,30 @@ FocusScope {
onIsCurrentItemChanged: {
if (item.isCurrentItem && (item.userAgentId > 0)) {
- optSelect.updateIndex();
+ userAgentSelect.updateIndex();
}
}
}
-
- onDelegateClicked: {
- DomainSettingsModel.setUserAgentId(item.domain, model.get(index).id);
- }
+
+ onActivated: DomainSettingsModel.setUserAgentId(item.domain, model.get(index).id);
}
- // within one label the check if zoom factor is set could not be properly done
- Label {
- height: units.gu(1)
- text: i18n.tr("Zoom: ") + Math.round(model.zoomFactor * 100) + "%"
- visible: item.ListView.isCurrentItem && ! isNaN(model.zoomFactor)
- }
- Label {
+ Row {
+ spacing: units.gu(1.5)
height: units.gu(1)
- text: i18n.tr("Zoom: ") + i18n.tr("not set")
- visible: item.ListView.isCurrentItem && isNaN(model.zoomFactor)
+ visible: item.ListView.isCurrentItem
+
+ // within one label the check if zoom factor is set could not be properly done
+ Label {
+ text: i18n.tr("Zoom: ") + Math.round(model.zoomFactor * 100) + "%"
+ visible: ! isNaN(model.zoomFactor)
+ anchors.verticalCenter: parent.verticalCenter
+ }
+ Label {
+ text: i18n.tr("Zoom: ") + i18n.tr("not set")
+ visible: isNaN(model.zoomFactor)
+ anchors.verticalCenter: parent.verticalCenter
+ }
}
}
}
@@ -353,13 +360,6 @@ FocusScope {
horizontalAlignment: Text.AlignHCenter
text: i18n.tr("No domain specific settings available")
}
-
- Connections {
- target: UserAgentsModel
- enabled: ! customUserAgentsViewLoader.active
- // the OptionSelector does not properly update the model (duplicate entries instead of new user agents)
- onRowCountChanged: reload()
- }
}
Loader {
@@ -381,10 +381,6 @@ FocusScope {
onReload: {
customUserAgentsViewLoader.active = false;
customUserAgentsViewLoader.active = true;
-
- if (selectedUserAgent) {
- customUserAgentsViewLoader.item.setUserAgentAsCurrentItem(selectedUserAgent)
- }
}
}
}
diff --git a/src/app/DownloadDelegate.qml b/src/app/DownloadDelegate.qml
index f05b00b65..9a19b2cca 100644
--- a/src/app/DownloadDelegate.qml
+++ b/src/app/DownloadDelegate.qml
@@ -18,7 +18,9 @@
import QtQuick 2.4
import Ubuntu.Components 1.3
+import QtQuick.Layouts 1.3
import ".."
+import "FileUtils.js" as FileUtils
ListItem {
id: downloadDelegate
@@ -31,6 +33,7 @@ ListItem {
property string downloadId
property var download
readonly property int progress: download ? 100 * (download.receivedBytes / download.totalBytes) : -1
+ property real speed: 0
property bool paused: download.isPaused
property alias incognito: incognitoIcon.visible
@@ -40,31 +43,67 @@ ListItem {
signal cancelled()
height: visible ? layout.height : 0
+
+ Timer {
+ id: speedTimer
+
+ property real prevBytes: 0
+
+ interval: 1000
+ running: download && !paused? true : false
+ repeat: true
+ onTriggered: {
+ if (download) {
+ speed = download.receivedBytes - prevBytes
+ prevBytes = download.receivedBytes
+ }
+ }
+ }
+
+ MimeData {
+ id: linkMimeData
+
+ text: model ? model.url : ""
+ }
SlotsLayout {
id: layout
- Item {
+ ColumnLayout {
SlotsLayout.position: SlotsLayout.Leading
- width: units.gu(3)
- height: units.gu(3)
-
- Image {
- id: thumbimage
- asynchronous: true
- anchors.fill: parent
- fillMode: Image.PreserveAspectFit
- sourceSize.width: width
- sourceSize.height: height
+ spacing: units.gu(1)
+
+ Item {
+ Layout.alignment: Qt.AlignHCenter
+ implicitWidth: units.gu(3)
+ implicitHeight: units.gu(3)
+
+ Image {
+ id: thumbimage
+ asynchronous: true
+ anchors.fill: parent
+ fillMode: Image.PreserveAspectFit
+ sourceSize.width: width
+ sourceSize.height: height
+ }
+
+ Image {
+ asynchronous: true
+ anchors.fill: parent
+ anchors.margins: units.gu(0.2)
+ source: "image://theme/%1".arg(downloadDelegate.icon || "save")
+ visible: thumbimage.status !== Image.Ready
+ cache: true
+ }
}
- Image {
- asynchronous: true
- anchors.fill: parent
- anchors.margins: units.gu(0.2)
- source: "image://theme/%1".arg(downloadDelegate.icon || "save")
- visible: thumbimage.status !== Image.Ready
- cache: true
+ Label {
+ Layout.alignment: Qt.AlignHCenter
+ visible: !progressBar.indeterminateProgress && incomplete
+ horizontalAlignment: Text.AlignHCenter
+ // TRANSLATORS: %1 is the percentage of the download completed so far
+ text: i18n.tr("%1%").arg(progressBar.progress)
+ opacity: paused ? 0.5 : 1
}
}
@@ -133,70 +172,55 @@ ListItem {
}
}
- IndeterminateProgressBar {
- id: progressBar
+ ColumnLayout {
+ visible: incomplete && !error.visible
anchors {
left: parent.left
right: parent.right
}
- height: units.gu(0.5)
- visible: incomplete && !error.visible
- progress: downloadDelegate.progress
- // Work around UDM bug #1450144
- indeterminateProgress: progress < 0 || progress > 100
- opacity: paused ? 0.5 : 1
- }
- }
-
- Column {
- SlotsLayout.position: SlotsLayout.Trailing
- spacing: units.gu(1)
- width: (incomplete && !error.visible) ? cancelButton.width : 0
- Button {
- id: cancelButton
- visible: incomplete && !error.visible
- text: i18n.tr("Cancel")
- onClicked: {
- if (download) {
- download.cancel()
- cancelled()
- }
+ IndeterminateProgressBar {
+ id: progressBar
+ Layout.fillWidth: true
+ implicitHeight: units.gu(0.5)
+ progress: downloadDelegate.progress
+ // Work around UDM bug #1450144
+ indeterminateProgress: progress < 0 || progress > 100
+ opacity: paused ? 0.5 : 1
}
- }
- Label {
- visible: !progressBar.indeterminateProgress && incomplete && !error.visible
- width: cancelButton.width
- horizontalAlignment: Text.AlignHCenter
- textSize: Label.Small
- // TRANSLATORS: %1 is the percentage of the download completed so far
- text: i18n.tr("%1%").arg(progressBar.progress)
- opacity: paused ? 0.5 : 1
- }
+ RowLayout {
+ Layout.fillWidth: true
+ implicitHeight: units.gu(4)
- Button {
- visible: incomplete && ! paused && ! error.visible
- text: i18n.tr("Pause")
- width: cancelButton.width
- onClicked: {
- if (download) {
- download.pause()
+ Label {
+ horizontalAlignment: Text.AlignHCenter
+ textSize: Label.Small
+ text: download ? FileUtils.formatBytes(download.receivedBytes) + " / " + FileUtils.formatBytes(download.totalBytes) : i18n.tr("Unknown")
+ opacity: paused ? 0.5 : 1
}
- }
- }
- Button {
- visible: incomplete && paused && ! error.visible
- text: i18n.tr("Resume")
- width: cancelButton.width
- onClicked: {
- if (download) {
- download.resume()
+ Label {
+ horizontalAlignment: Text.AlignHCenter
+ textSize: Label.Small
+ // TRANSLATORS: %1 is the number of bytes i.e. 2bytes, 5KB, 1MB
+ text: "(" + i18n.tr("%1/s").arg(FileUtils.formatBytes(downloadDelegate.speed)) + ")"
+ opacity: paused ? 0.5 : 1
}
}
}
}
+ Icon {
+ id: iconPauseResume
+
+ implicitWidth: units.gu(4)
+ implicitHeight: implicitWidth
+ SlotsLayout.position: SlotsLayout.Trailing
+ asynchronous: true
+ name: paused ? "media-preview-start" : "media-preview-pause"
+ visible: incomplete && !error.visible
+ color: theme.palette.normal.overlayText
+ }
}
Icon {
@@ -213,7 +237,8 @@ ListItem {
name: "private-browsing"
}
- leadingActions: error.visible || !incomplete ? deleteActionList : null
+ leadingActions: deleteActionList
+ trailingActions: trailingActionList
ListItemActions {
id: deleteActionList
@@ -222,10 +247,49 @@ ListItem {
objectName: "leadingAction.delete"
iconName: "delete"
enabled: error.visible || !incomplete
+ visible: enabled
+ text: i18n.tr("Delete File")
+ onTriggered: {
+ removed()
+ }
+ },
+ Action {
+ objectName: "leadingAction.remove"
+ iconName: "list-remove"
+ enabled: !incomplete && !error.visible
+ visible: enabled
+ text: i18n.tr("Remove from History")
onTriggered: {
-
removed()
+ }
+ },
+ Action {
+ objectName: "leadingAction.cancel"
+ iconName: "cancel"
+ enabled: incomplete && !error.visible
+ visible: enabled
+ text: i18n.tr("Cancel")
+ onTriggered: {
+ if (download) {
+ download.cancel()
+ cancelled()
+ }
+ }
+ }
+ ]
+ }
+ ListItemActions {
+ id: trailingActionList
+ actions: [
+ Action {
+ objectName: "trailingAction.CopyLink"
+ iconName: "edit-copy"
+ enabled: model.url != "" ? true : false
+ visible: enabled
+ text: i18n.tr("Copy Download Link")
+ onTriggered: {
+ Clipboard.push(linkMimeData)
}
}
]
diff --git a/src/app/DownloadsDialog.qml b/src/app/DownloadsDialog.qml
new file mode 100644
index 000000000..9cbec6d44
--- /dev/null
+++ b/src/app/DownloadsDialog.qml
@@ -0,0 +1,196 @@
+/*
+ * Copyright 2015-2016 Canonical Ltd.
+ *
+ * This file is part of morph-browser.
+ *
+ * morph-browser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * morph-browser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+import QtQuick 2.9
+import QtWebEngine 1.10
+import Ubuntu.Components 1.3
+import Ubuntu.Components.Popups 1.3
+import QtQuick.Layouts 1.3
+import Ubuntu.Content 1.3
+import webbrowsercommon.private 0.1
+
+import "MimeTypeMapper.js" as MimeTypeMapper
+import "FileUtils.js" as FileUtils
+
+Popover {
+ id: downloadsDialog
+
+ property var downloadsList: []
+ property bool isEmpty: downloadsListView.count === 0
+ readonly property real controlsHeight: (downloadsDialogColumn.spacing * (downloadsDialogColumn.children.length - 1))
+ + (downloadsDialogColumn.anchors.margins * 2) + buttonsBar.height + titleLabel.height
+ property real maximumWidth: units.gu(60)
+ property real preferredWidth: browser.width - units.gu(6)
+
+ property real maximumHeight: browser.height - units.gu(6)
+ property real preferredHeight: downloadsDialogColumn.height + units.gu(2)
+
+ signal showDownloadsPage()
+ signal preview(string url)
+
+ contentHeight: preferredHeight > maximumHeight ? maximumHeight : preferredHeight
+ contentWidth: preferredWidth > maximumWidth ? maximumWidth : preferredWidth
+
+ grabDismissAreaEvents: true
+
+ ColumnLayout {
+ id: downloadsDialogColumn
+
+ anchors {
+ top: parent.top
+ left: parent.left
+ right: parent.right
+ margins: units.gu(1)
+ }
+
+ spacing: units.gu(2)
+
+ Label {
+ id: titleLabel
+
+ Layout.fillWidth: true
+ font.bold: true
+ text: i18n.tr("Recent Downloads")
+ }
+
+ Rectangle {
+ id: emptyLabel
+
+ visible: isEmpty
+ color: "transparent"
+
+ Layout.preferredHeight: units.gu(10)
+ Layout.minimumHeight: label.height
+ Layout.fillWidth: true
+
+ Label {
+ id: label
+ text: i18n.tr("No Recent Downloads")
+ anchors.centerIn: parent
+ }
+ }
+
+ ListView {
+ id: downloadsListView
+
+ visible: !isEmpty
+ Layout.fillWidth: true
+ Layout.preferredHeight: downloadsListView.count * units.gu(7)
+ Layout.maximumHeight: browser.height - controlsHeight - units.gu(7)
+ Layout.minimumHeight: units.gu(7)
+ clip: true
+
+ model: downloadsList
+
+ property int selectedIndex: -1
+
+ delegate: SimpleDownloadDelegate {
+ download: modelData
+ title.text: FileUtils.getFilename(modelData.path)
+ subtitle.text: if (cancelled) {
+ i18n.tr("Cancelled")
+ } else {
+ // TRANSLATORS: %1 is the percentage of the download completed so far
+ (error ? modelData.interruptReasonString : (incomplete ? i18n.tr("%1%").arg(progress) : i18n.tr("Completed")))
+ + " - " + FileUtils.formatBytes(download.receivedBytes)
+ }
+
+ image: !incomplete && thumbnailLoader.status == Loader.Ready
+ && (modelData.mimeType.indexOf("image") === 0
+ || modelData.mimeType.indexOf("video") === 0)
+ ? "image://thumbnailer/file://" + modelData.path : ""
+ icon: MimeDatabase.iconForMimetype(modelData.mimeType)
+
+ onClicked: {
+ /* TODO: Enable once content picker in a popover is merged */
+ /*if (!incomplete && !error) {
+ var properties = {"path": download.path, "contentType": MimeTypeMapper.mimeTypeToContentType(download.mimeType), "mimeType": download.mimeType, "downloadUrl": download.url}
+ var exportDialog = PopupUtils.open(Qt.resolvedUrl("ContentExportDialog.qml"), downloadsDialog.parent, properties)
+ exportDialog.preview.connect(downloadsDialog.preview)
+ } else {*/
+ if (download) {
+ if (paused) {
+ download.resume()
+ } else {
+ download.pause()
+ }
+ }
+ //}
+ }
+
+ onRemove: downloadsListView.removeItem(index)
+ onCancel: DownloadsModel.cancelDownload(download.id)
+ onDeleted: {
+ if (!incomplete) {
+ DownloadsModel.deleteDownload(download.path)
+ }
+ }
+ }
+
+ Keys.onDeletePressed: {
+ currentItem.removeItem(currentItem.download)
+ }
+
+ function removeItem(index) {
+ downloadsList.splice(index, 1);
+ model = downloadsList
+ forceLayout()
+ }
+
+ function clear() {
+ downloadsList.splice(0, downloadsList.length);
+ model = downloadsList
+ forceLayout()
+ }
+ }
+
+ Item {
+ id: buttonsBar
+
+ Layout.fillWidth: true
+ implicitHeight: clearButton.height
+
+ Button {
+ id: clearButton
+ visible: !isEmpty
+ objectName: "downloadsDialog.clearButton"
+ text: i18n.tr("Clear")
+ onClicked: {
+ downloadsListView.clear()
+ }
+ }
+
+ Button {
+ id: viewButton
+ objectName: "downloadsDialog.viewButton"
+ anchors.right: parent.right
+ text: i18n.tr("View All")
+ color: theme.palette.normal.activity
+ onClicked: {
+ showDownloadsPage()
+ downloadsDialog.destroy()
+ }
+ }
+ }
+ }
+
+ Loader {
+ id: thumbnailLoader
+ source: "Thumbnailer.qml"
+ }
+}
diff --git a/src/app/DownloadsPage.qml b/src/app/DownloadsPage.qml
index 900d8cfe7..20875e22c 100644
--- a/src/app/DownloadsPage.qml
+++ b/src/app/DownloadsPage.qml
@@ -16,14 +16,16 @@
* along with this program. If not, see .
*/
-import QtQuick 2.4
-import QtWebEngine 1.5
+import QtQuick 2.9
+import QtWebEngine 1.10
import Ubuntu.Components 1.3
+import Ubuntu.Components.Popups 1.3
import Ubuntu.Content 1.3
import webbrowsercommon.private 0.1
import "MimeTypeMapper.js" as MimeTypeMapper
import "UrlUtils.js" as UrlUtils
+import "FileUtils.js" as FileUtils
BrowserPage {
id: downloadsItem
@@ -117,7 +119,7 @@ BrowserPage {
},
Action {
iconName: "edit"
- visible: !selectMode && !pickingMode && !exportPeerPicker.visible
+ visible: !selectMode && !pickingMode
enabled: downloadsListView.count > 0
onTriggered: {
selectMode = true
@@ -207,9 +209,11 @@ BrowserPage {
}
delegate: DownloadDelegate {
+ id: downloadDelegate
+
download: ActiveDownloadsSingleton.currentDownloads[model.downloadId]
downloadId: model.downloadId
- title: getDisplayPath(model.path)
+ title: FileUtils.getFilename(model.path)
url: model.url
image: model.complete && thumbnailLoader.status == Loader.Ready
&& (model.mimetype.indexOf("image") === 0
@@ -238,12 +242,22 @@ BrowserPage {
}
onClicked: {
- if (model.complete && !selectMode) {
- exportPeerPicker.contentType = MimeTypeMapper.mimeTypeToContentType(model.mimetype);
- exportPeerPicker.visible = true;
- exportPeerPicker.path = model.path;
- exportPeerPicker.mimeType = model.mimetype;
- exportPeerPicker.downloadUrl = model.url;
+ if (!selectMode) {
+ if (model.complete) {
+ exportPeerPicker.contentType = MimeTypeMapper.mimeTypeToContentType(model.mimetype);
+ exportPeerPicker.visible = true;
+ exportPeerPicker.path = model.path;
+ exportPeerPicker.mimeType = model.mimetype;
+ exportPeerPicker.downloadUrl = model.url;
+ } else {
+ if (download) {
+ if (paused) {
+ download.resume()
+ } else {
+ download.pause()
+ }
+ }
+ }
}
}
diff --git a/src/app/EditCustomUserAgentDialog.qml b/src/app/EditCustomUserAgentDialog.qml
new file mode 100644
index 000000000..45fa31848
--- /dev/null
+++ b/src/app/EditCustomUserAgentDialog.qml
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2020 UBports Foundation
+ *
+ * This file is part of morph-browser.
+ *
+ * morph-browser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * morph-browser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+import QtQuick 2.9
+import Ubuntu.Components 1.3 as UITK
+import Ubuntu.Components.Popups 1.3 as Popups
+import Ubuntu.Content 1.3
+import webbrowsercommon.private 0.1
+
+Popups.Dialog {
+ id: editCustomUserAgent
+
+ property string previousUserAgentName: ""
+ property alias userAgentName: editUserAgentName.text
+ property alias userAgentString: editUserAgentString.text
+
+ readonly property bool userAgentNameAlreadyTaken: (userAgentName !== previousUserAgentName) && UserAgentsModel.contains(userAgentName)
+
+ signal accept(string userAgentName, string userAgentString)
+ signal cancel()
+
+ onAccept: hide()
+ onCancel: hide()
+
+ UITK.Label {
+ visible: userAgentNameAlreadyTaken
+ text: i18n.tr("this user agent name is already taken")
+ color: theme.palette.normal.negative
+ }
+
+ UITK.TextField {
+ id: editUserAgentName
+ placeholderText: i18n.tr("Add the name for the user agent")
+ inputMethodHints: Qt.ImhNoPredictiveText
+ }
+
+ UITK.TextArea {
+ id: editUserAgentString
+ placeholderText: i18n.tr("enter user agent string...")
+ inputMethodHints: Qt.ImhNoPredictiveText
+ }
+
+ Row {
+ spacing: units.gu(2)
+
+ UITK.Button {
+ text: i18n.tr("OK")
+ color: theme.palette.normal.positive
+ enabled: (userAgentName !== "") && ! userAgentNameAlreadyTaken
+ onClicked: accept(userAgentName, userAgentString)
+ }
+
+ UITK.Button {
+ text: i18n.tr("Cancel")
+ onClicked: cancel()
+ }
+ }
+}
diff --git a/src/app/FileUtils.js b/src/app/FileUtils.js
new file mode 100644
index 000000000..bea44d930
--- /dev/null
+++ b/src/app/FileUtils.js
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2014 Canonical Ltd.
+ *
+ * This file is part of morph-browser.
+ *
+ * morph-browser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * morph-browser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+*/
+/*jslint node: true */
+.pragma library
+'use strict';
+
+function formatBytes(bytes, decimals) {
+ decimals = decimals ? decimals : 2
+ if (bytes === 0) return '0 B';
+
+ var k = 1000;
+ var dm = decimals < 0 ? 0 : decimals;
+ var sizes = ["B", "KB", "MB", "GB", "TB"];
+
+ var i = Math.floor(Math.log(bytes) / Math.log(k));
+
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
+}
+
+function getFilename(path) {
+ return path.replace(/^.*[\\\/]/, '')
+}
+
diff --git a/src/app/GeolocationPermissionRequest.qml b/src/app/GeolocationPermissionRequest.qml
index 67c808500..c8af4a6fe 100644
--- a/src/app/GeolocationPermissionRequest.qml
+++ b/src/app/GeolocationPermissionRequest.qml
@@ -24,7 +24,7 @@ Dialog {
id: dialog
property string securityOrigin
- property bool showAllowPermanentlyCheckBox
+ property bool showRememberDecisionCheckBox
title: i18n.tr("Permission Request")
text: securityOrigin + "
" + i18n.tr("This page wants to know your device’s location.")
@@ -32,28 +32,29 @@ Dialog {
signal allow()
signal allowPermanently()
signal reject()
+ signal rejectPermanently()
onAllow: { PopupUtils.close(dialog); }
onAllowPermanently: { PopupUtils.close(dialog); }
onReject: { PopupUtils.close(dialog); }
+ onRejectPermanently: { PopupUtils.close(dialog); }
ListItemLayout {
- visible: showAllowPermanentlyCheckBox
+ visible: showRememberDecisionCheckBox
title.text: i18n.tr("Remember decision")
CheckBox {
- id: allowPermanentlyCheckBox
+ id: rememberDecisionCheckBox
}
}
Button {
objectName: "allow"
text: i18n.tr("Allow")
color: theme.palette.normal.positive
- onClicked: allowPermanentlyCheckBox.checked ? allowPermanently() : allow()
+ onClicked: rememberDecisionCheckBox.checked ? allowPermanently() : allow()
}
Button {
objectName: "deny"
text: i18n.tr("Deny")
- enabled: ! allowPermanentlyCheckBox.checked
- onClicked: reject()
+ onClicked: rememberDecisionCheckBox.checked ? rejectPermanently() : reject()
}
}
diff --git a/src/app/SimpleDownloadDelegate.qml b/src/app/SimpleDownloadDelegate.qml
new file mode 100644
index 000000000..4f62c9d1e
--- /dev/null
+++ b/src/app/SimpleDownloadDelegate.qml
@@ -0,0 +1,164 @@
+/*
+ * Copyright 2021 UBports Foundation
+ *
+ * This file is part of morph-browser.
+ *
+ * morph-browser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * morph-browser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+import QtQuick 2.9
+import QtWebEngine 1.10
+import Ubuntu.Components 1.3
+
+ListItem {
+ id: simpleDownloadDelegate
+
+ property var download
+ property alias title: listItemlayout.title
+ property alias subtitle: listItemlayout.subtitle
+ property string icon
+ property alias image: thumbimage.source
+
+ readonly property bool incomplete: !download.isFinished
+ readonly property bool error: download.interruptReason > WebEngineDownloadItem.NoReason
+ readonly property bool cancelled: download.interruptReason == WebEngineDownloadItem.UserCanceled
+ readonly property bool paused: download.isPaused
+ readonly property int progress: download ? 100 * (download.receivedBytes / download.totalBytes) : -1
+
+ signal cancel()
+ signal remove()
+ signal deleted()
+
+ divider.visible: false
+
+ MimeData {
+ id: linkMimeData
+
+ text: download.url
+ }
+
+ ListItemLayout {
+ id: listItemlayout
+
+ subtitle.opacity: paused && !cancelled && !error ? 0.5 : 1
+
+ Item {
+
+ SlotsLayout.position: SlotsLayout.Leading
+ implicitWidth: units.gu(3)
+ implicitHeight: implicitWidth
+
+ Image {
+ id: thumbimage
+
+ asynchronous: true
+ anchors.fill: parent
+ fillMode: Image.PreserveAspectFit
+ sourceSize.width: width
+ sourceSize.height: height
+ }
+
+ Image {
+ asynchronous: true
+ anchors.fill: parent
+ anchors.margins: units.gu(0.2)
+ source: "image://theme/%1".arg(simpleDownloadDelegate.icon || "save")
+ visible: thumbimage.status !== Image.Ready
+ cache: true
+ }
+ }
+
+ Icon {
+ id: iconError
+
+ implicitWidth: units.gu(4)
+ implicitHeight: implicitWidth
+ SlotsLayout.position: SlotsLayout.Last
+ asynchronous: true
+ name: "dialog-warning-symbolic"
+ visible: error && !cancelled
+ color: theme.palette.normal.negative
+ }
+
+ Icon {
+ id: iconPauseResume
+
+ implicitWidth: units.gu(4)
+ implicitHeight: implicitWidth
+ SlotsLayout.position: SlotsLayout.Trailing
+ asynchronous: true
+ name: paused ? "media-preview-start" : "media-preview-pause"
+ visible: incomplete && !error
+ color: theme.palette.normal.backgroundText
+ }
+ }
+
+ leadingActions: deleteActionList
+ trailingActions: trailingActionList
+
+ ListItemActions {
+ id: deleteActionList
+ actions: [
+ Action {
+ objectName: "leadingAction.delete"
+ iconName: "delete"
+ enabled: error || !incomplete
+ visible: enabled
+ text: i18n.tr("Delete File")
+ onTriggered: {
+ deleted()
+ remove()
+ }
+ },
+ Action {
+ objectName: "leadingAction.remove"
+ iconName: "list-remove"
+ enabled: !incomplete && !error
+ visible: enabled
+ text: i18n.tr("Remove from List")
+ onTriggered: {
+ remove()
+ }
+ },
+ Action {
+ objectName: "leadingAction.cancel"
+ iconName: "cancel"
+ text: i18n.tr("Cancel")
+ enabled: incomplete && !error
+ visible: enabled
+ onTriggered: {
+ if (download) {
+ download.cancel()
+ cancel()
+ }
+ }
+ }
+ ]
+ }
+
+ ListItemActions {
+ id: trailingActionList
+ actions: [
+ Action {
+ objectName: "trailingAction.CopyLink"
+ iconName: "edit-copy"
+ enabled: download.url != "" ? true : false
+ visible: enabled
+ text: i18n.tr("Copy Download Link")
+ onTriggered: {
+ Clipboard.push(linkMimeData)
+ }
+ }
+ ]
+ }
+}
diff --git a/src/app/WebViewImpl.qml b/src/app/WebViewImpl.qml
index 25fd571a6..c49a47009 100644
--- a/src/app/WebViewImpl.qml
+++ b/src/app/WebViewImpl.qml
@@ -196,21 +196,31 @@ WebView {
case WebEngineView.Geolocation:
var domain = UrlUtils.extractHost(securityOrigin);
+ var locationPreference = DomainSettingsModel.getLocationPreference(domain);
- if (DomainSettingsModel.isLocationAllowed(domain))
+ if (locationPreference === DomainSettingsModel.AllowLocationAccess)
{
grantFeaturePermission(securityOrigin, feature, true);
return;
}
+ if (locationPreference === DomainSettingsModel.DenyLocationAccess)
+ {
+ grantFeaturePermission(securityOrigin, feature, false);
+ return;
+ }
+
var geoPermissionDialog = PopupUtils.open(Qt.resolvedUrl("GeolocationPermissionRequest.qml"), this);
geoPermissionDialog.securityOrigin = securityOrigin;
- geoPermissionDialog.showAllowPermanentlyCheckBox = (domain !== "") && ! incognito
+ geoPermissionDialog.showRememberDecisionCheckBox = (domain !== "") && ! incognito
geoPermissionDialog.allow.connect(function() { grantFeaturePermission(securityOrigin, feature, true); });
geoPermissionDialog.allowPermanently.connect(function() { grantFeaturePermission(securityOrigin, feature, true);
- DomainSettingsModel.allowLocation(domain, true);
+ DomainSettingsModel.setLocationPreference(domain, DomainSettingsModel.AllowLocationAccess);
})
geoPermissionDialog.reject.connect(function() { grantFeaturePermission(securityOrigin, feature, false); });
+ geoPermissionDialog.rejectPermanently.connect(function() { grantFeaturePermission(securityOrigin, feature, false);
+ DomainSettingsModel.setLocationPreference(domain, DomainSettingsModel.DenyLocationAccess);
+ })
break;
case WebEngineView.MediaAudioCapture:
@@ -702,6 +712,13 @@ WebView {
}
onFullScreenRequested: function(request) {
+ if (request.toggleOn) {
+ // twice because of QTBUG-84313
+ webview.zoomFactor = 1.0;
+ webview.zoomFactor = 1.0;
+ } else {
+ webview.zoomController.refresh();
+ }
request.accept();
}
diff --git a/src/app/ZoomControls.qml b/src/app/ZoomControls.qml
index 8d81a8b5f..c666c450b 100644
--- a/src/app/ZoomControls.qml
+++ b/src/app/ZoomControls.qml
@@ -163,6 +163,10 @@ UbuntuShape {
}
}
+ function refresh() {
+ internal.setWebviewZoomFactor(controller.currentZoomFactor);
+ }
+
// If current domain has changed, we have to forget about previous zoom factors and update page zoom.
// This also means, that loading is in progress, fit to widt updates will be done there.
onCurrentDomainChanged: {
@@ -175,7 +179,7 @@ UbuntuShape {
// To keep webview.zoomFactor in sync with currentZoomFactor.
onCurrentZoomFactorChanged: {
//console.log("[ZC] controller.onCurrentZoomFactorChanged: %1".arg(controller.currentZoomFactor));
- webview.zoomFactor = controller.currentZoomFactor;
+ internal.setWebviewZoomFactor(controller.currentZoomFactor);
}
}
@@ -286,6 +290,16 @@ UbuntuShape {
//console.log("[ZC] currentZoomFactor: %1".arg(controller.currentZoomFactor));
}
+ function setWebviewZoomFactor(newZoomFactor) {
+ if (Math.abs(webview.zoomFactor - newZoomFactor) > 0.01) {
+ //https://bugreports.qt.io/browse/QTBUG-84313
+ // zoom is not set reliably on the first change
+ // set it twice so that changes are not ignored
+ webview.zoomFactor = newZoomFactor;
+ webview.zoomFactor = newZoomFactor;
+ }
+ }
+
function updateFitToWidth() {
//console.log("[ZC] internal.updateFitToWidth called");
@@ -315,7 +329,7 @@ UbuntuShape {
//console.log("[ZC] zooming to default and autofitting");
// Automatic fit to width is done from defaultZoomFactor
- webview.zoomFactor = controller.defaultZoomFactor;
+ internal.setWebviewZoomFactor(controller.defaultZoomFactor);
// Wait, to be sure that any page layout change (css, js, ...) after previous zoom or width change takes effect.
internal.updateFitToWidthTimer.restart();
}
@@ -336,7 +350,7 @@ UbuntuShape {
if (width === null || width <= 0) {
//console.log("[ZC] no scrollWidth");
// Sync zoom factors in case they are out of sync.
- webview.zoomFactor = currentZoomFactor;
+ internal.setWebviewZoomFactor(controller.currentZoomFactor);
return;
}
@@ -347,7 +361,7 @@ UbuntuShape {
if (Math.abs(controller.currentZoomFactor - controller.fitToWidthZoomFactor) < 0.1) {
//console.log("[ZC] not autofitting, close to currentZoomFactor");
// Sync zoom factors in case they are out of sync.
- webview.zoomFactor = controller.currentZoomFactor;
+ internal.setWebviewZoomFactor(controller.currentZoomFactor);
return;
}
@@ -403,7 +417,7 @@ UbuntuShape {
// This is a workaround, because sometimes a page is not zoomed after loading (happens after manual url change),
// although the webview.zoomFactor (and currentZoomFactor) is correctly set.
- webview.zoomFactor = controller.currentZoomFactor;
+ internal.setWebviewZoomFactor(controller.currentZoomFactor);
// End of workaround.
if (webview.visible === false) {
diff --git a/src/app/browserapplication.cpp b/src/app/browserapplication.cpp
index 253348a38..0c44835e4 100644
--- a/src/app/browserapplication.cpp
+++ b/src/app/browserapplication.cpp
@@ -185,6 +185,12 @@ bool BrowserApplication::initialize(const QString& qmlFileSubPath
qputenv("UBUNTU_WEBVIEW_DEVTOOLS_HOST", devtoolsHost.toUtf8());
qputenv("UBUNTU_WEBVIEW_DEVTOOLS_PORT", devtoolsPort.toUtf8());
}
+
+ // set suru style
+ if (qgetenv("QT_QUICK_CONTROLS_STYLE") == QString())
+ {
+ qputenv("QT_QUICK_CONTROLS_STYLE", "Suru");
+ }
const char* uri = "webbrowsercommon.private";
qmlRegisterSingletonType(uri, 0, 1, "BrowserUtils", BrowserUtils_singleton_factory);
@@ -229,12 +235,6 @@ bool BrowserApplication::initialize(const QString& qmlFileSubPath
}
QQmlProperty::write(m_object, QStringLiteral("hasTouchScreen"), hasTouchScreen);
- // set suru style
- if (qgetenv("QT_QUICK_CONTROLS_STYLE") == QString())
- {
- qputenv("QT_QUICK_CONTROLS_STYLE", "Suru");
- }
-
inputMethodHandler * handler = new inputMethodHandler();
this->installEventFilter(handler);
diff --git a/src/app/domain-permissions-model.h b/src/app/domain-permissions-model.h
index 5834cb9c2..62904db58 100644
--- a/src/app/domain-permissions-model.h
+++ b/src/app/domain-permissions-model.h
@@ -32,6 +32,7 @@ class DomainPermissionsModel : public QAbstractListModel
Q_PROPERTY(int count READ rowCount NOTIFY rowCountChanged)
Q_PROPERTY(bool whiteListMode READ whiteListMode WRITE setWhiteListMode NOTIFY whiteListModeChanged)
+ Q_ENUMS(DomainPermission)
Q_ENUMS(Roles)
public:
@@ -43,7 +44,6 @@ class DomainPermissionsModel : public QAbstractListModel
Blocked = 1,
Whitelisted = 2
};
- Q_ENUMS(DomainPermission)
enum Roles {
Domain = Qt::UserRole + 1,
diff --git a/src/app/domain-settings-model.cpp b/src/app/domain-settings-model.cpp
index dfc5796c3..95cd1cb33 100644
--- a/src/app/domain-settings-model.cpp
+++ b/src/app/domain-settings-model.cpp
@@ -22,6 +22,7 @@
#include
#include
#include
+#include
#define CONNECTION_NAME "morph-browser-domainsettings"
@@ -110,7 +111,7 @@ void DomainSettingsModel::createOrAlterDatabaseSchema()
{
QSqlQuery createQuery(m_database);
QString query = QLatin1String("CREATE TABLE IF NOT EXISTS domainsettings "
- "(domain VARCHAR NOT NULL UNIQUE, domainWithoutSubdomain VARCHAR, allowCustomUrlSchemes BOOL, allowLocation BOOL, "
+ "(domain VARCHAR NOT NULL UNIQUE, domainWithoutSubdomain VARCHAR, allowCustomUrlSchemes BOOL, allowLocation INTEGER, "
"userAgentId INTEGER, zoomFactor REAL, PRIMARY KEY(domain), FOREIGN KEY(userAgentId) REFERENCES useragents(id)); ");
createQuery.prepare(query);
createQuery.exec();
@@ -129,7 +130,7 @@ void DomainSettingsModel::populateFromDatabase()
entry.domain = populateQuery.value("domain").toString();
entry.domainWithoutSubdomain = populateQuery.value("domainWithoutSubdomain").toString();
entry.allowCustomUrlSchemes = populateQuery.value("allowCustomUrlSchemes").toBool();
- entry.allowLocation = populateQuery.value("allowLocation").toBool();
+ entry.allowLocation = static_cast(populateQuery.value("allowLocation").toInt());
entry.userAgentId = populateQuery.value("userAgentId").toInt();
entry.zoomFactor = populateQuery.value("zoomFactor").isNull() ? std::numeric_limits::quiet_NaN()
: populateQuery.value("zoomFactor").toDouble();
@@ -214,33 +215,33 @@ void DomainSettingsModel::allowCustomUrlSchemes(const QString& domain, bool allo
}
}
-bool DomainSettingsModel::isLocationAllowed(const QString& domain) const
+DomainSettingsModel::AllowLocationPreference DomainSettingsModel::getLocationPreference(const QString& domain) const
{
int index = getIndexForDomain(domain);
if (index == -1)
{
- return false;
+ return AllowLocationPreference::AskForLocationAccess;
}
return m_entries[index].allowLocation;
}
-void DomainSettingsModel::allowLocation(const QString& domain, bool allow)
+void DomainSettingsModel::setLocationPreference(const QString& domain, DomainSettingsModel::AllowLocationPreference preference)
{
insertEntry(domain);
int index = getIndexForDomain(domain);
if (index != -1) {
DomainSetting& entry = m_entries[index];
- if (entry.allowLocation == allow) {
+ if (entry.allowLocation == preference) {
return;
}
- entry.allowLocation = allow;
+ entry.allowLocation = preference;
Q_EMIT dataChanged(this->index(index, 0), this->index(index, 0), QVector() << AllowLocation);
QSqlQuery query(m_database);
static QString updateStatement = QLatin1String("UPDATE domainsettings SET allowLocation=? WHERE domain=?;");
query.prepare(updateStatement);
- query.addBindValue(allow);
+ query.addBindValue(entry.allowLocation);
query.addBindValue(domain);
query.exec();
}
@@ -345,7 +346,7 @@ void DomainSettingsModel::insertEntry(const QString &domain)
entry.domain = domain;
entry.domainWithoutSubdomain = DomainUtils::getDomainWithoutSubdomain(domain);
entry.allowCustomUrlSchemes = false;
- entry.allowLocation = false;
+ entry.allowLocation = AllowLocationPreference::AskForLocationAccess;
entry.userAgentId = 0;
entry.zoomFactor = std::numeric_limits::quiet_NaN();
m_entries.append(entry);
diff --git a/src/app/domain-settings-model.h b/src/app/domain-settings-model.h
index d1009a7f9..d3271584c 100644
--- a/src/app/domain-settings-model.h
+++ b/src/app/domain-settings-model.h
@@ -23,6 +23,7 @@
#include
#include
+
class DomainSettingsModel : public QAbstractListModel
{
Q_OBJECT
@@ -31,12 +32,19 @@ class DomainSettingsModel : public QAbstractListModel
Q_PROPERTY(int count READ rowCount NOTIFY rowCountChanged)
Q_PROPERTY(double defaultZoomFactor READ defaultZoomFactor WRITE setDefaultZoomFactor)
+ Q_ENUMS(AllowLocationPreference)
Q_ENUMS(Roles)
public:
DomainSettingsModel(QObject* parent=0);
~DomainSettingsModel();
+ enum AllowLocationPreference {
+ AskForLocationAccess = 0,
+ AllowLocationAccess = 1,
+ DenyLocationAccess = 2
+ };
+
enum Roles {
Domain = Qt::UserRole + 1,
DomainWithoutSubdomain,
@@ -61,8 +69,8 @@ class DomainSettingsModel : public QAbstractListModel
Q_INVOKABLE void deleteAndResetDataBase();
Q_INVOKABLE bool areCustomUrlSchemesAllowed(const QString& domain);
Q_INVOKABLE void allowCustomUrlSchemes(const QString& domain, bool allow);
- Q_INVOKABLE bool isLocationAllowed(const QString& domain) const;
- Q_INVOKABLE void allowLocation(const QString& domain, bool allow);
+ Q_INVOKABLE AllowLocationPreference getLocationPreference(const QString& domain) const;
+ Q_INVOKABLE void setLocationPreference(const QString& domain, AllowLocationPreference preference);
Q_INVOKABLE int getUserAgentId(const QString& domain) const;
Q_INVOKABLE void setUserAgentId(const QString& domain, int userAgentId);
Q_INVOKABLE void removeUserAgentIdFromAllDomains(int userAgentId);
@@ -84,7 +92,7 @@ class DomainSettingsModel : public QAbstractListModel
QString domain;
QString domainWithoutSubdomain;
bool allowCustomUrlSchemes;
- bool allowLocation;
+ AllowLocationPreference allowLocation;
int userAgentId;
double zoomFactor;
};
diff --git a/src/app/webbrowser/Browser.qml b/src/app/webbrowser/Browser.qml
index 8f2750e5d..565018faf 100644
--- a/src/app/webbrowser/Browser.qml
+++ b/src/app/webbrowser/Browser.qml
@@ -25,6 +25,8 @@ import Qt.labs.settings 1.0
import Morph.Web 0.1
import Ubuntu.Components 1.3
import Ubuntu.Components.Popups 1.3
+import Ubuntu.Content 1.3
+import QtQuick.Controls 2.2 as QQC2
import webbrowserapp.private 0.1
import webbrowsercommon.private 0.1
import "../actions" as Actions
@@ -266,11 +268,27 @@ Common.BrowserView {
// handle user agents
if (isMainFrame)
{
- currentWebview.context.__ua.setDesktopMode(browser.settings ? browser.settings.setDesktopMode : false);
- console.log("user agent: " + currentWebview.context.httpUserAgent);
- }
+ currentWebview.hideContextMenu();
+ var newUserAgentId = (UserAgentsModel.count > 0) ? DomainSettingsModel.getUserAgentId(requestDomain) : 0;
+
+ // change of the custom user agent
+ if (newUserAgentId !== currentWebview.context.userAgentId)
+ {
+ currentWebview.context.userAgentId = newUserAgentId;
+ currentWebview.context.customUserAgent = (newUserAgentId > 0) ? UserAgentsModel.getUserAgentString(newUserAgentId) : "";
- //currentWebview.showMessage(url)
+ // for some reason when letting through the request, another navigation request will take us back to the
+ // to the previous page. Therefore we block it first and navigate to the new url with the correct user agent.
+ request.action = WebEngineNavigationRequest.IgnoreRequest;
+ currentWebview.url = url;
+ return;
+ }
+ else
+ {
+ currentWebview.context.__ua.setDesktopMode(browser.settings ? browser.settings.setDesktopMode : false);
+ console.log("user agent: " + currentWebview.context.httpUserAgent);
+ }
+ }
}
}
@@ -639,24 +657,54 @@ Common.BrowserView {
onClicked: recentView.closeAndSwitchToTab(0)
}
+
+ Row {
+ id: rightActionsRow
+
+ spacing: units.gu(2)
+ height: parent.height - units.gu(2)
- ToolbarAction {
- objectName: "newTabButton"
anchors {
right: parent.right
rightMargin: units.gu(2)
verticalCenter: parent.verticalCenter
}
- height: parent.height - units.gu(2)
- text: i18n.tr("New Tab")
+ ToolbarAction {
+ objectName: "reopenTabButton"
+ visible: !incognito && internal.closedTabHistory.length > 0
+ anchors {
+ top: parent.top
+ bottom: parent.bottom
+ }
+
+ text: i18n.tr("Reopen Tab")
+
+ iconName: "undo"
+ color: theme.palette.normal.foregroundText
+
+ onClicked: {
+ recentView.reset()
+ internal.undoCloseTab()
+ }
+ }
+
+ ToolbarAction {
+ objectName: "newTabButton"
+ anchors {
+ top: parent.top
+ bottom: parent.bottom
+ }
+
+ text: i18n.tr("New Tab")
- iconName: browser.incognito ? "private-tab-new" : "add"
- color: theme.palette.normal.foregroundText
+ iconName: browser.incognito ? "private-tab-new" : "add"
+ color: theme.palette.normal.foregroundText
- onClicked: {
- recentView.reset()
- internal.openUrlInNewTab("", true)
+ onClicked: {
+ recentView.reset()
+ internal.openUrlInNewTab("", true)
+ }
}
}
}
@@ -716,6 +764,9 @@ Common.BrowserView {
// QtWebEngine icons are provided as e.g. image://favicon/https://duckduckgo.com/favicon.ico
else internal.addBookmark(tab.url, tab.title, (UrlUtils.schemeIs(tab.icon, "image") && UrlUtils.hostIs(tab.icon, "favicon")) ? tab.icon.toString().substring(("image://favicon/").length) : tab.icon)
}
+ onToggleDownloads: {
+ internal.showDownloadsDialog()
+ }
onWebviewChanged: bookmarked = isCurrentUrlBookmarked()
Connections {
target: chrome.tab
@@ -859,16 +910,16 @@ Common.BrowserView {
target: browser.currentWebview
onLoadingChanged: {
if (browser.currentWebview.loading && !recentView.visible) {
- chrome.state = "shown"
+ chrome.state = "shown";
} else if (browser.currentWebview.isFullScreen) {
chrome.state = "hidden"
}
}
onIsFullScreenChanged: {
if (browser.currentWebview.isFullScreen) {
- chrome.state = "hidden"
+ chrome.state = "hidden";
} else {
- chrome.state = "shown"
+ chrome.state = "shown";
}
}
}
@@ -1352,10 +1403,14 @@ Common.BrowserView {
if (tab) {
if (!incognito && tab.url.toString().length > 0) {
- closedTabHistory.push({
+
+ // For triggering property change on closedTabHistory
+ var temp = closedTabHistory.slice()
+ temp.push({
state: serializeTabState(tab),
index: index
})
+ closedTabHistory = temp.slice()
}
// When moving a tab between windows don't close the tab as it has been moved
@@ -1388,7 +1443,10 @@ Common.BrowserView {
function undoCloseTab() {
if (!incognito && closedTabHistory.length > 0) {
- var tabInfo = closedTabHistory.pop()
+ // For triggering property change on closedTabHistory
+ var temp = closedTabHistory.slice()
+ var tabInfo = temp.pop()
+ closedTabHistory = temp.slice()
var tab = restoreTabState(tabInfo.state)
addTab(tab, true, tabInfo.index)
tab.load()
@@ -1539,6 +1597,57 @@ Common.BrowserView {
internal.currentBookmarkOptionsDialog = PopupUtils.open(Qt.resolvedUrl("BookmarkOptions.qml"),
location, properties)
}
+
+ property var recentDownloads: []
+ property var currentDownloadsDialog: null
+ function showDownloadsDialog(caller) {
+ if (!internal.currentDownloadsDialog) {
+ chrome.downloadNotify = false
+ if (caller === undefined) caller = chrome.downloadsButtonPlaceHolder
+ var properties = {"downloadsList": recentDownloads}
+
+ internal.currentDownloadsDialog = PopupUtils.open(Qt.resolvedUrl("../DownloadsDialog.qml"),
+ caller, properties)
+ }
+ }
+
+ function addNewDownload(download) {
+ recentDownloads.unshift(download)
+ chrome.showDownloadButton = Qt.binding(
+ function(){
+ if (browser.wide) {
+ return true;
+ } else {
+ if (internal.currentDownloadsDialog) {
+ if (internal.currentDownloadsDialog.isEmpty) {
+ return false;
+ } else {
+ return true;
+ }
+ } else {
+ if (recentDownloads.length > 0) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+ }
+ })
+ if (internal.currentDownloadsDialog) {
+ internal.currentDownloadsDialog.downloadsList = recentDownloads
+ }
+ }
+ }
+
+ Connections {
+ target: internal.currentDownloadsDialog
+
+ onShowDownloadsPage: showDownloadsPage()
+
+ onPreview: {
+ PopupUtils.close(internal.currentDownloadsDialog);
+ currentWebview.url = url;
+ }
}
// Work around https://launchpad.net/bugs/1502675 by delaying the switch to
@@ -1807,7 +1916,9 @@ Common.BrowserView {
console.log("adding download with id " + downloadIdDataBase)
Common.ActiveDownloadsSingleton.currentDownloads[downloadIdDataBase] = download
DownloadsModel.add(downloadIdDataBase, (download.url.toString().indexOf("file://%1/pdf_tmp".arg(cacheLocation)) === 0) ? "" : download.url, download.path, download.mimeType, incognito)
- downloadsViewLoader.active = true
+
+ internal.addNewDownload(download)
+ internal.showDownloadsDialog()
}
function setDownloadComplete(download) {
@@ -1828,6 +1939,10 @@ Common.BrowserView {
{
DownloadsModel.setError(downloadIdDataBase, download.interruptReasonString)
}
+
+ if (!internal.currentDownloadsDialog) {
+ chrome.downloadNotify = true
+ }
}
Connections {
@@ -1850,14 +1965,12 @@ Common.BrowserView {
console.log("a download was requested with path %1".arg(download.path))
download.accept();
- browser.showDownloadsPage();
browser.startDownload(download);
}
onDownloadFinished: {
console.log(incognito? "download finished" : "a download was finished with path %1.".arg(download.path));
- browser.showDownloadsPage();
browser.setDownloadComplete(download);
// delete pdf in cache / close the tab
diff --git a/src/app/webbrowser/Chrome.qml b/src/app/webbrowser/Chrome.qml
index 9ad0be601..8db1f7006 100644
--- a/src/app/webbrowser/Chrome.qml
+++ b/src/app/webbrowser/Chrome.qml
@@ -32,6 +32,9 @@ ChromeBase {
property alias bookmarked: navigationBar.bookmarked
signal closeTabRequested()
signal toggleBookmark()
+ signal toggleDownloads()
+ property bool showDownloadButton: false
+ property bool downloadNotify: false
property alias drawerActions: navigationBar.drawerActions
property alias drawerOpen: navigationBar.drawerOpen
property alias requestedUrl: navigationBar.requestedUrl
@@ -45,6 +48,7 @@ ChromeBase {
property alias showFaviconInAddressBar: navigationBar.showFaviconInAddressBar
property alias availableHeight: navigationBar.availableHeight
readonly property alias bookmarkTogglePlaceHolder: navigationBar.bookmarkTogglePlaceHolder
+ readonly property alias downloadsButtonPlaceHolder: navigationBar.downloadsButtonPlaceHolder
property bool touchEnabled: true
readonly property real tabsBarHeight: tabsBar.height + tabsBar.anchors.topMargin + content.anchors.topMargin
property BrowserWindow thisWindow
@@ -112,6 +116,8 @@ ChromeBase {
loading: chrome.loading
fgColor: theme.palette.normal.backgroundText
iconColor: (incognito && !showTabsBar) ? theme.palette.normal.baseText : fgColor
+ showDownloadButton: chrome.showDownloadButton
+ downloadNotify: chrome.downloadNotify
focus: true
@@ -124,6 +130,7 @@ ChromeBase {
onCloseTabRequested: chrome.closeTabRequested()
onToggleBookmark: chrome.toggleBookmark()
+ onToggleDownloads: chrome.toggleDownloads()
}
}
diff --git a/src/app/webbrowser/NavigationBar.qml b/src/app/webbrowser/NavigationBar.qml
index c3dfd5a9c..b6df9a850 100644
--- a/src/app/webbrowser/NavigationBar.qml
+++ b/src/app/webbrowser/NavigationBar.qml
@@ -16,9 +16,9 @@
* along with this program. If not, see .
*/
-import QtQuick 2.4
+import QtQuick 2.7
import Ubuntu.Components 1.3
-import QtWebEngine 1.7
+import QtWebEngine 1.10
import ".."
FocusScope {
@@ -31,6 +31,9 @@ FocusScope {
property alias bookmarked: addressbar.bookmarked
signal closeTabRequested()
signal toggleBookmark()
+ signal toggleDownloads()
+ property bool showDownloadButton: false
+ property bool downloadNotify: false
property list drawerActions
readonly property bool drawerOpen: internal.openDrawer
property alias requestedUrl: addressbar.requestedUrl
@@ -42,6 +45,7 @@ FocusScope {
property alias incognito: addressbar.incognito
property alias showFaviconInAddressBar: addressbar.showFavicon
readonly property alias bookmarkTogglePlaceHolder: addressbar.bookmarkTogglePlaceHolder
+ readonly property alias downloadsButtonPlaceHolder: downloadsButton
property color fgColor: theme.palette.normal.baseText
property color iconColor: theme.palette.normal.baseText
property real availableHeight
@@ -220,6 +224,66 @@ FocusScope {
onTriggered: internal.webview.findController.next()
}
+ ChromeButton {
+ id: downloadsButton
+ objectName: "downloadsButton"
+
+ visible: showDownloadButton && !tabListMode
+ iconName: "save"
+ iconSize: 0.5 * height
+ iconColor: downloadNotify ? theme.palette.normal.focus : root.iconColor
+
+ height: root.height
+ width: height * 0.8
+
+ anchors.verticalCenter: parent.verticalCenter
+
+ Connections {
+ target: root
+
+ onDownloadNotifyChanged: {
+ if (downloadNotify) {
+ shakeAnimation.start()
+ }
+ }
+ }
+
+ Behavior on iconColor {
+ ColorAnimation { duration: UbuntuAnimation.BriskDuration }
+ }
+
+ SequentialAnimation {
+ id: shakeAnimation
+
+ loops: 4
+
+ RotationAnimation {
+ target: downloadsButton
+ direction: RotationAnimation.Counterclockwise
+ to: 350
+ duration: 50
+ }
+
+ RotationAnimation {
+ target: downloadsButton
+ direction: RotationAnimation.Clockwise
+ to: 10
+ duration: 50
+ }
+
+ RotationAnimation {
+ target: downloadsButton
+ direction: RotationAnimation.Counterclockwise
+ to: 0
+ duration: 50
+ }
+ }
+
+ onTriggered: {
+ toggleDownloads()
+ }
+ }
+
ChromeButton {
id: drawerButton
objectName: "drawerButton"
diff --git a/src/app/webbrowser/TabItem.qml b/src/app/webbrowser/TabItem.qml
index ed0a5e23d..43fd42a1f 100644
--- a/src/app/webbrowser/TabItem.qml
+++ b/src/app/webbrowser/TabItem.qml
@@ -115,15 +115,13 @@ Item {
anchors.bottom: parent.bottom
anchors.right: parent.right
acceptedButtons: Qt.AllButtons
- onPressed: {
+
+ onClicked: {
if (mouse.button === Qt.LeftButton) {
tabItem.selected()
} else if (mouse.button === Qt.RightButton) {
tabItem.contextMenu()
- }
- }
- onClicked: {
- if ((mouse.buttons === 0) && (mouse.button === Qt.MiddleButton)) {
+ } else if ((mouse.buttons === 0) && (mouse.button === Qt.MiddleButton)) {
tabItem.closed()
}
}
diff --git a/src/app/webbrowser/TabPreview.qml b/src/app/webbrowser/TabPreview.qml
index c5b2e6af6..099b2ac6b 100644
--- a/src/app/webbrowser/TabPreview.qml
+++ b/src/app/webbrowser/TabPreview.qml
@@ -24,7 +24,7 @@ QQC2.SwipeDelegate {
id: tabPreview
property alias title: chrome.title
- property alias icon: chrome.icon
+ property alias tabIcon: chrome.icon
property alias incognito: chrome.incognito
property var tab
readonly property url url: tab ? tab.url : ""
@@ -32,7 +32,10 @@ QQC2.SwipeDelegate {
background: Rectangle {
color: "transparent"
}
- padding: 0
+ leftPadding: 0
+ rightPadding: 0
+ topPadding: 0
+ bottomPadding: 0
swipe.enabled: true
swipe.behind: Rectangle {
width: tabPreview.width
diff --git a/src/app/webbrowser/TabsList.qml b/src/app/webbrowser/TabsList.qml
index 85dc6a2be..3786ba7a1 100644
--- a/src/app/webbrowser/TabsList.qml
+++ b/src/app/webbrowser/TabsList.qml
@@ -106,7 +106,7 @@ Item {
sourceComponent: TabPreview {
title: delegate.title
- icon: delegate.icon
+ tabIcon: delegate.icon
incognito: tabslist.incognito
tab: model.tab
diff --git a/src/app/webbrowser/UrlDelegate.qml b/src/app/webbrowser/UrlDelegate.qml
index b1d51d6c6..ee44e2262 100644
--- a/src/app/webbrowser/UrlDelegate.qml
+++ b/src/app/webbrowser/UrlDelegate.qml
@@ -48,18 +48,18 @@ ListItem {
Loader {
id: headerComponentLoader
- anchors.verticalCenter: parent.verticalCenter
+ Layout.alignment: Qt.AlignVCenter
visible: status == Loader.Ready
}
Favicon {
id: icon
- anchors.verticalCenter: parent.verticalCenter
+ Layout.alignment: Qt.AlignVCenter
}
Column {
Layout.fillWidth: true
- anchors.verticalCenter: parent.verticalCenter
+ Layout.alignment: Qt.AlignVCenter
Label {
id: title
diff --git a/src/app/webbrowser/morph-browser.qml b/src/app/webbrowser/morph-browser.qml
index f84a6b2d4..119aae939 100644
--- a/src/app/webbrowser/morph-browser.qml
+++ b/src/app/webbrowser/morph-browser.qml
@@ -96,6 +96,8 @@ QtObject {
BrowserWindow {
id: window
+ color: "#111111"
+
property alias incognito: browser.incognito
readonly property alias model: browser.tabsModel
readonly property var tabsModel: browser.tabsModel
diff --git a/src/app/webcontainer/Chrome.qml b/src/app/webcontainer/Chrome.qml
index 83100a697..07d2ddc7a 100644
--- a/src/app/webcontainer/Chrome.qml
+++ b/src/app/webcontainer/Chrome.qml
@@ -26,6 +26,10 @@ ChromeBase {
property var webview: null
property bool navigationButtonsVisible: false
property bool accountSwitcher: false
+ signal toggleDownloads()
+ property bool showDownloadButton: false
+ property bool downloadNotify: false
+ readonly property alias downloadsButtonPlaceHolder: downloadsButton
loading: webview && webview.loading && webview.loadProgress !== 100
loadProgress: loading ? webview.loadProgress : 0
@@ -39,6 +43,8 @@ ChromeBase {
reloadButton.iconColor = color;
settingsButton.iconColor = color;
accountsButton.iconColor = color;
+
+ downloadsButton.iconColor = Qt.binding(function(){ return downloadNotify ? theme.palette.normal.focus : color})
}
signal chooseAccount()
@@ -141,7 +147,7 @@ ChromeBase {
width: visible ? height : 0
anchors {
- right: settingsButton.left
+ right: downloadsButton.left
verticalCenter: parent.verticalCenter
}
@@ -149,6 +155,68 @@ ChromeBase {
onTriggered: chrome.webview.reload()
}
+ ChromeButton {
+ id: downloadsButton
+ objectName: "downloadsButton"
+
+ visible: chrome.navigationButtonsVisible && showDownloadButton
+ iconName: "save"
+ iconSize: 0.6 * height
+
+ height: parent.height
+ width: visible ? height : 0
+
+ anchors {
+ right: settingsButton.left
+ verticalCenter: parent.verticalCenter
+ }
+
+ Connections {
+ target: root
+
+ onDownloadNotifyChanged: {
+ if (downloadNotify) {
+ shakeAnimation.start()
+ }
+ }
+ }
+
+ Behavior on iconColor {
+ ColorAnimation { duration: UbuntuAnimation.BriskDuration }
+ }
+
+ SequentialAnimation {
+ id: shakeAnimation
+
+ loops: 4
+
+ RotationAnimation {
+ target: downloadsButton
+ direction: RotationAnimation.Counterclockwise
+ to: 350
+ duration: 50
+ }
+
+ RotationAnimation {
+ target: downloadsButton
+ direction: RotationAnimation.Clockwise
+ to: 10
+ duration: 50
+ }
+
+ RotationAnimation {
+ target: downloadsButton
+ direction: RotationAnimation.Counterclockwise
+ to: 0
+ duration: 50
+ }
+ }
+
+ onTriggered: {
+ toggleDownloads()
+ }
+ }
+
ChromeButton {
id: settingsButton
objectName: "settingsButton"
diff --git a/src/app/webcontainer/WebApp.qml b/src/app/webcontainer/WebApp.qml
index 81126ed43..3235af014 100644
--- a/src/app/webcontainer/WebApp.qml
+++ b/src/app/webcontainer/WebApp.qml
@@ -16,12 +16,13 @@
* along with this program. If not, see .
*/
-import QtQuick 2.5
+import QtQuick 2.7
import QtWebEngine 1.10
import Qt.labs.settings 1.0
import webbrowsercommon.private 0.1
import Morph.Web 0.1
import Ubuntu.Components 1.3
+import Ubuntu.Components.Popups 1.3
import Ubuntu.Unity.Action 1.1 as UnityActions
import "../actions" as Actions
import ".." as Common
@@ -60,6 +61,9 @@ Common.BrowserView {
property bool chromeVisible: false
readonly property bool chromeless: !chromeVisible && !backForwardButtonsVisible && !accountSwitcher
readonly property real themeColorTextContrastFactor: 3.0
+
+ property var recentDownloads: []
+ property var currentDownloadsDialog: null
signal chooseAccount()
@@ -175,7 +179,14 @@ Common.BrowserView {
console.log("adding download with id " + downloadIdDataBase)
Common.ActiveDownloadsSingleton.currentDownloads[downloadIdDataBase] = download
DownloadsModel.add(downloadIdDataBase, download.url, download.path, download.mimeType, false)
- downloadsViewLoader.active = true
+
+ addNewDownload(download)
+
+ if (webapp.chromeless) {
+ showDownloadsDialog(anchorItem)
+ } else {
+ showDownloadsDialog()
+ }
}
function setDownloadComplete(download) {
@@ -196,6 +207,62 @@ Common.BrowserView {
{
DownloadsModel.setError(downloadIdDataBase, download.interruptReasonString)
}
+
+ if (!currentDownloadsDialog && chromeLoader.item) {
+ if (!webapp.chromeless) {
+ chromeLoader.item.downloadNotify = true
+
+ if (!chromeLoader.item.navigationButtonsVisible) {
+ showDownloadsDialog()
+ }
+ } else {
+ showDownloadsDialog(anchorItem)
+ }
+ }
+ }
+
+ function showDownloadsDialog(caller) {
+ if (!currentDownloadsDialog && chromeLoader.item) {
+ if (!webapp.chromeless) {
+ chromeLoader.item.downloadNotify = false
+ if (caller === undefined) caller = chromeLoader.item.downloadsButtonPlaceHolder
+ } else {
+ if (caller === undefined) caller = webapp
+ }
+ var properties = {"downloadsList": recentDownloads}
+ currentDownloadsDialog = PopupUtils.open(Qt.resolvedUrl("../DownloadsDialog.qml"),
+ caller, properties)
+ }
+ }
+
+ function addNewDownload(download) {
+ recentDownloads.unshift(download)
+ if (chromeLoader.item && !webapp.chromeless) {
+ chromeLoader.item.showDownloadButton = true
+ }
+ if (currentDownloadsDialog) {
+ currentDownloadsDialog.downloadsList = recentDownloads
+ }
+ }
+
+ Connections {
+ target: currentDownloadsDialog
+ onShowDownloadsPage: showDownloadsPage()
+ onPreview: {
+ PopupUtils.close(currentDownloadsDialog);
+ webapp.currentWebview.url = url;
+ }
+ }
+
+ /* Only used for anchoring the downloads dialog to the top when chromeless */
+ Item {
+ id: anchorItem
+ anchors {
+ top: parent.top
+ right: parent.right
+ }
+ height: units.gu(2)
+ width: height
}
Item {
@@ -306,6 +373,9 @@ Common.BrowserView {
y: webapp.currentWebview ? containerWebView.currentWebview.locationBarController.offset : 0
onChooseAccount: webapp.chooseAccount()
+ onToggleDownloads: {
+ webapp.showDownloadsDialog()
+ }
}
}
@@ -418,9 +488,9 @@ Common.BrowserView {
onIsFullScreenChanged: {
if (webapp.currentWebview.isFullScreen) {
- chromeLoader.item.state = "hidden"
+ chromeLoader.item.state = "hidden";
} else {
- chromeLoader.item.state === "shown"
+ chromeLoader.item.state === "shown";
}
}
}
@@ -434,14 +504,12 @@ Common.BrowserView {
console.log("a download was requested with path %1".arg(download.path))
download.accept();
- webapp.showDownloadsPage();
webapp.startDownload(download);
}
onDownloadFinished: {
console.log("a download was finished with path %1.".arg(download.path))
- webapp.showDownloadsPage()
webapp.setDownloadComplete(download)
}
}
diff --git a/src/app/webcontainer/WebViewImplOxide.qml b/src/app/webcontainer/WebViewImplOxide.qml
index fd2a04fcf..ba96ef33e 100644
--- a/src/app/webcontainer/WebViewImplOxide.qml
+++ b/src/app/webcontainer/WebViewImplOxide.qml
@@ -335,6 +335,30 @@ WebappWebview {
return;
}
+ // handle user agents
+ if (isMainFrame)
+ {
+ var newUserAgentId = (UserAgentsModel.count > 0) ? DomainSettingsModel.getUserAgentId(requestDomain) : 0;
+
+ // change of the custom user agent
+ if (newUserAgentId !== webview.context.userAgentId)
+ {
+ webview.context.userAgentId = newUserAgentId;
+ webview.context.userAgent = (newUserAgentId > 0) ? UserAgentsModel.getUserAgentString(newUserAgentId)
+ : localUserAgentOverride ? localUserAgentOverride : webview.context.defaultUserAgent;
+
+ // for some reason when letting through the request, another navigation request will take us back to the
+ // to the previous page. Therefore we block it first and navigate to the new url with the correct user agent.
+ request.action = WebEngineNavigationRequest.IgnoreRequest;
+ webview.url = url;
+ return;
+ }
+ else
+ {
+ console.log("user agent: " + webview.context.httpUserAgent);
+ }
+ }
+
if (runningLocalApplication && url.indexOf("file://") !== 0) {
request.action = WebEngineNavigationRequest.IgnoreRequest;
openUrlExternally(url, true);