diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt index a5aec3b..ac82a63 100644 --- a/autotests/CMakeLists.txt +++ b/autotests/CMakeLists.txt @@ -1,20 +1,20 @@ include(ECMAddTests) find_package(Qt5 ${REQUIRED_QT_VERSION} CONFIG REQUIRED Test) include_directories(../src) ecm_add_test(urlmodeltest.cpp ../src/urlmodel.cpp TEST_NAME urlmodeltest LINK_LIBRARIES Qt5::Test ) ecm_add_test(useragenttest.cpp ../src/useragent.cpp TEST_NAME useragenttest LINK_LIBRARIES Qt5::Test ) -ecm_add_test(browsermanagertest.cpp ../src/browsermanager.cpp ../src/urlmodel.cpp +ecm_add_test(browsermanagertest.cpp ../src/browsermanager.cpp ../src/urlmodel.cpp ../src/urlutils.cpp TEST_NAME browsermanagertest LINK_LIBRARIES Qt5::Test Qt5::Qml ) diff --git a/autotests/browsermanagertest.cpp b/autotests/browsermanagertest.cpp index 51b9fc1..37a88e4 100644 --- a/autotests/browsermanagertest.cpp +++ b/autotests/browsermanagertest.cpp @@ -1,74 +1,75 @@ /* * Copyright 2019 Jonah Brüchert * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License version 2 as published by the Free Software Foundation; * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #include #include #include "browsermanager.h" +#include "urlutils.h" using namespace AngelFish; class UserAgentTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase() { m_browserManager = new BrowserManager(); } void cleanupTestCase() { delete m_browserManager; } void homepage() { const QString newHomepage = QStringLiteral("https://kde.org"); m_browserManager->setHomepage(newHomepage); QCOMPARE(m_browserManager->homepage(), newHomepage); } void searchEngine() { const QString newSearchEngineUrl = QStringLiteral("https://search.angelfish.kde?q="); m_browserManager->setSearchBaseUrl(newSearchEngineUrl); QCOMPARE(m_browserManager->searchBaseUrl(), newSearchEngineUrl); } void urlFromUserInput() { const QString incompleteUrl = QStringLiteral("kde.org"); const QString completeUrl = QStringLiteral("http://kde.org"); - QCOMPARE(m_browserManager->urlFromUserInput(incompleteUrl), completeUrl); + QCOMPARE(UrlUtils::urlFromUserInput(incompleteUrl), completeUrl); } private: BrowserManager *m_browserManager; }; QTEST_MAIN(UserAgentTest); #include "browsermanagertest.moc" diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 105f4d3..4c2c048 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,14 +1,15 @@ set(angelfish_SRCS main.cpp browsermanager.cpp urlmodel.cpp urlfilterproxymodel.cpp + urlutils.cpp useragent.cpp ) qt5_add_resources(RESOURCES resources.qrc) add_executable(angelfish ${angelfish_SRCS} ${RESOURCES}) target_link_libraries(angelfish Qt5::Core Qt5::Qml Qt5::Quick Qt5::Svg Qt5::WebEngine KF5::I18n) install(TARGETS angelfish ${KF5_INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/src/browsermanager.cpp b/src/browsermanager.cpp index 2786749..fc4b8db 100644 --- a/src/browsermanager.cpp +++ b/src/browsermanager.cpp @@ -1,195 +1,190 @@ /*************************************************************************** * Copyright 2014 Sebastian Kügler * * * * This program 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; either version 2 of the License, or * * (at your option) any later version. * * * * This program 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, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * ***************************************************************************/ #include "browsermanager.h" #include #include #include #include #include #include using namespace AngelFish; BrowserManager::BrowserManager(QObject *parent) : QObject(parent), m_settings(new QSettings(this)) { loadTabs(); } BrowserManager::~BrowserManager() { history()->save(); bookmarks()->save(); } void BrowserManager::reload() { qDebug() << "BookmarksManager::reload()"; } UrlModel *BrowserManager::bookmarks() { // qDebug() << "BookmarksManager::bookmarks()"; if (!m_bookmarks) { m_bookmarks = new UrlModel(QStringLiteral("bookmarks.json"), this); m_bookmarks->load(); } return m_bookmarks; } UrlModel *BrowserManager::history() { // qDebug() << "BrowserManager::history()"; if (!m_history) { m_history = new UrlModel(QStringLiteral("history.json"), this); m_history->load(); } return m_history; } void BrowserManager::addBookmark(const QVariantMap &bookmarkdata) { qDebug() << "Add bookmark"; qDebug() << " data: " << bookmarkdata; bookmarks()->add(QJsonObject::fromVariantMap(bookmarkdata)); } void BrowserManager::removeBookmark(const QString &url) { bookmarks()->remove(url); } void BrowserManager::addToHistory(const QVariantMap &pagedata) { // qDebug() << "Add History"; // qDebug() << " data: " << pagedata; history()->add(QJsonObject::fromVariantMap(pagedata)); emit historyChanged(); } void BrowserManager::removeFromHistory(const QString &url) { history()->remove(url); emit historyChanged(); } -QString BrowserManager::urlFromUserInput(const QString &input) -{ - return QUrl::fromUserInput(input).toString(); -} - void BrowserManager::setHomepage(const QString &homepage) { m_settings->setValue("browser/homepage", homepage); emit homepageChanged(); } QString BrowserManager::homepage() { return m_settings->value("browser/homepage", "https://start.duckduckgo.com").toString(); } void BrowserManager::setSearchBaseUrl(const QString &searchBaseUrl) { m_settings->setValue("browser/searchBaseUrl", searchBaseUrl); emit searchBaseUrlChanged(); } QString BrowserManager::searchBaseUrl() { return m_settings->value("browser/searchBaseUrl", "https://start.duckduckgo.com/?q=") .toString(); } int BrowserManager::currentTab() const { return m_current_tab; } QString BrowserManager::tabs() const { QJsonArray arr; for (auto i=m_tabs.constBegin(); i != m_tabs.constEnd(); ++i) { QJsonObject o; o["url"] = i->url; o["isMobile"] = i->isMobile; arr.append(o); } return QJsonDocument(arr).toJson(); } void BrowserManager::loadTabs() { QJsonArray arr = QJsonDocument::fromJson(m_settings->value("browser/tabs").toByteArray()).array(); m_tabs.clear(); for (auto i = arr.constBegin(); i != arr.constEnd(); ++i) { QJsonObject o = i->toObject(); TabState ts(o.value("url").toString(), o.value("isMobile").toBool()); m_tabs.push_back(ts); } m_current_tab = m_settings->value("browser/current_tab", 0).toInt(); } void BrowserManager::saveTabs() { m_settings->setValue("browser/tabs", tabs()); } void BrowserManager::setCurrentTab(int index) { if (m_tabs_readonly) return; m_current_tab = index; m_settings->setValue("browser/current_tab", m_current_tab); } void BrowserManager::setTab(int index, QString url, bool isMobile) { if (m_tabs_readonly) return; while (m_tabs.length() <= index) { m_tabs.append(TabState()); } m_tabs[index] = TabState(url, isMobile); saveTabs(); } void BrowserManager::setTabIsMobile(int index, bool isMobile) { TabState ts = m_tabs.value(index); setTab(index, ts.url, isMobile); } void BrowserManager::setTabUrl(int index, QString url) { TabState ts = m_tabs.value(index); setTab(index, url, ts.isMobile); } void BrowserManager::setTabsWritable() { m_tabs_readonly = false; } void BrowserManager::rmTab(int index) { if (index >= 0 && index < m_tabs.size()) { m_tabs.removeAt(index); saveTabs(); } } diff --git a/src/browsermanager.h b/src/browsermanager.h index 8596e46..177764b 100644 --- a/src/browsermanager.h +++ b/src/browsermanager.h @@ -1,115 +1,113 @@ /*************************************************************************** * * * Copyright 2014 Sebastian Kügler * * * * This program 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; either version 2 of the License, or * * (at your option) any later version. * * * * This program 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, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * * * ***************************************************************************/ #ifndef BOOKMARKSMANAGER_H #define BOOKMARKSMANAGER_H #include #include #include "urlmodel.h" class QSettings; namespace AngelFish { /** * @class BookmarksManager * @short Access to Bookmarks and History. This is a singleton for * administration and access to the various models and browser-internal * data. */ class BrowserManager : public QObject { Q_OBJECT Q_PROPERTY(QAbstractListModel *bookmarks READ bookmarks NOTIFY bookmarksChanged) Q_PROPERTY(QAbstractListModel *history READ history NOTIFY historyChanged) Q_PROPERTY(QString homepage READ homepage WRITE setHomepage NOTIFY homepageChanged) Q_PROPERTY(QString searchBaseUrl READ searchBaseUrl WRITE setSearchBaseUrl NOTIFY searchBaseUrlChanged) public: BrowserManager(QObject *parent = nullptr); ~BrowserManager() override; UrlModel *bookmarks(); UrlModel *history(); QString homepage(); QString searchBaseUrl(); Q_INVOKABLE int currentTab() const; Q_INVOKABLE QString tabs() const; Q_INVOKABLE void setCurrentTab(int index); Q_INVOKABLE void setTab(int index, QString url, bool isMobile); Q_INVOKABLE void setTabIsMobile(int index, bool isMobile); Q_INVOKABLE void setTabUrl(int index, QString url); Q_INVOKABLE void setTabsWritable(); Q_INVOKABLE void rmTab(int index); - Q_INVOKABLE static QString urlFromUserInput(const QString &input); - signals: void updated(); void bookmarksChanged(); void historyChanged(); void homepageChanged(); void searchBaseUrlChanged(); void loadUrlRequested(const QString &url); public slots: void reload(); void addBookmark(const QVariantMap &bookmarkdata); void removeBookmark(const QString &url); void addToHistory(const QVariantMap &pagedata); void removeFromHistory(const QString &url); void setHomepage(const QString &homepage); void setSearchBaseUrl(const QString &searchBaseUrl); protected: void loadTabs(); void saveTabs(); private: UrlModel *m_bookmarks = nullptr; UrlModel *m_history = nullptr; QSettings *m_settings; struct TabState { TabState(QString u=QString(), bool im=false): url(u), isMobile(im) {} QString url; bool isMobile = false; }; int m_current_tab = 0; QList m_tabs; bool m_tabs_readonly = true; }; } // namespace #endif // BOOKMARKSMANAGER_H diff --git a/src/contents/ui/Navigation.qml b/src/contents/ui/Navigation.qml index 1885191..4f3ee5e 100644 --- a/src/contents/ui/Navigation.qml +++ b/src/contents/ui/Navigation.qml @@ -1,181 +1,203 @@ /*************************************************************************** * * * Copyright 2014-2015 Sebastian Kügler * * * * This program 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; either version 2 of the License, or * * (at your option) any later version. * * * * This program 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, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * * * ***************************************************************************/ import QtQuick 2.3 import QtQuick.Layouts 1.0 import QtWebEngine 1.4 import QtQuick.Controls 2.0 as Controls import org.kde.kirigami 2.5 as Kirigami - -import "regex-weburl.js" as RegexWebUrl - +import org.kde.mobile.angelfish 1.0 Item { id: navigation property bool navigationShown: true - property alias textFocus: urlInput.activeFocus - property alias text: urlInput.text - property int expandedHeight: Kirigami.Units.gridUnit * 3 property int buttonSize: Kirigami.Units.gridUnit * 2 + signal activateUrlEntry; + Behavior on height { NumberAnimation { duration: Kirigami.Units.longDuration; easing.type: Easing.InOutQuad} } Rectangle { anchors.fill: parent; color: Kirigami.Theme.backgroundColor; } RowLayout { id: layout anchors.fill: parent anchors.leftMargin: Kirigami.Units.gridUnit / 2 anchors.rightMargin: Kirigami.Units.gridUnit / 2 visible: navigationShown spacing: Kirigami.Units.smallSpacing Kirigami.Theme.inherit: true Controls.ToolButton { icon.name: "open-menu-symbolic" Layout.preferredWidth: buttonSize Layout.preferredHeight: buttonSize Kirigami.Theme.inherit: true onClicked: globalDrawer.open() } Controls.ToolButton { icon.name: "tab-duplicate" Layout.preferredWidth: buttonSize Layout.preferredHeight: buttonSize Kirigami.Theme.inherit: true onClicked: { pageStack.push(Qt.resolvedUrl("Tabs.qml")) } } Controls.ToolButton { id: backButton Layout.preferredWidth: buttonSize Layout.preferredHeight: buttonSize visible: currentWebView.canGoBack && !Kirigami.Settings.isMobile icon.name: "go-previous" Kirigami.Theme.inherit: true onClicked: currentWebView.goBack() } Controls.ToolButton { id: forwardButton Layout.preferredWidth: buttonSize Layout.preferredHeight: buttonSize visible: currentWebView.canGoForward && !Kirigami.Settings.isMobile icon.name: "go-next" Kirigami.Theme.inherit: true onClicked: currentWebView.goForward() } - Controls.TextField { - id: urlInput - + Item { + id: labelItem Layout.fillWidth: true - - text: currentWebView.url - - selectByMouse: true - focus: false - - Kirigami.Theme.inherit: true - - onActiveFocusChanged: { - if (activeFocus) { - selectAll() + Layout.preferredHeight: layout.height + + property string scheme: UrlUtils.urlScheme(currentWebView.url) + + Controls.ToolButton { + id: schemeIcon + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + icon.name: { + if (labelItem.scheme === "https") return "lock"; + if (labelItem.scheme === "http") return "unlock"; + return ""; + } + visible: icon.name + height: buttonSize * 0.75 + width: visible ? buttonSize * 0.75 : 0 + Kirigami.Theme.inherit: true + background: Rectangle { + implicitWidth: schemeIcon.width + implicitHeight: schemeIcon.height + color: "transparent" } } - onAccepted: { - if (text.match(RegexWebUrl.re_weburl)) { - load(browserManager.urlFromUserInput(text)) - } else { - load(browserManager.urlFromUserInput(browserManager.searchBaseUrl + text)) + Controls.Label { + anchors.left: schemeIcon.right + anchors.right: parent.right + anchors.top: parent.top + height: parent.height + + text: { + if (labelItem.scheme === "http" || labelItem.scheme === "https") { + var h = UrlUtils.urlHostPort(currentWebView.url); + var p = UrlUtils.urlPath(currentWebView.url); + if (p === "/") p = "" + return '%1%2'.arg(h).arg(p); + } + return currentWebView.url; } + textFormat: Text.StyledText + elide: Text.ElideRight + verticalAlignment: Text.AlignVCenter + } + + MouseArea { + anchors.fill: parent + onClicked: activateUrlEntry() } } Controls.ToolButton { id: reloadButton Layout.preferredWidth: buttonSize Layout.preferredHeight: buttonSize visible: !Kirigami.Settings.isMobile icon.name: currentWebView.loading ? "process-stop" : "view-refresh" Kirigami.Theme.inherit: true onClicked: currentWebView.loading ? currentWebView.stop() : currentWebView.reload() } Controls.ToolButton { id: optionsButton property string targetState: "overview" Layout.fillWidth: false Layout.preferredWidth: buttonSize Layout.preferredHeight: buttonSize icon.name: "overflow-menu" Kirigami.Theme.inherit: true onClicked: contextDrawer.open() } } states: [ State { name: "shown" when: navigationShown PropertyChanges { target: navigation; height: expandedHeight} }, State { name: "hidden" when: !navigationShown PropertyChanges { target: navigation; height: 0} } ] - } diff --git a/src/contents/ui/NavigationEntrySheet.qml b/src/contents/ui/NavigationEntrySheet.qml new file mode 100644 index 0000000..c2f1492 --- /dev/null +++ b/src/contents/ui/NavigationEntrySheet.qml @@ -0,0 +1,154 @@ +/*************************************************************************** + * * + * Copyright 2019 Simon Schmeisser * + * Copyright 2019 Jonah Brüchert * + * * + * This program 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; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program 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, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + * * + ***************************************************************************/ + +import QtQuick 2.7 +import QtQuick.Controls 2.2 as Controls +import QtQuick.Layouts 1.2 + +import org.kde.kirigami 2.5 as Kirigami +import org.kde.mobile.angelfish 1.0 + +import "regex-weburl.js" as RegexWebUrl + +Controls.Drawer { + id: overlay + dragMargin: 0 + edge: Qt.BottomEdge + width: parent.width + + bottomPadding: 0 + topPadding: 0 + rightPadding: 0 + leftPadding: 0 + + property int buttonSize: Kirigami.Units.gridUnit * 2 + property int fullHeight: 0.9 * rootPage.height + property bool openedState: false + + property Item urlInput + property Item listView + + contentHeight: fullHeight - topPadding - bottomPadding + contentWidth: parent.width - rightPadding - leftPadding + contentItem: Item { + width: parent.width + height: parent.height + + RowLayout { + id: editRow + anchors.top: parent.top + anchors.horizontalCenter: parent.horizontalCenter + height: Kirigami.Units.gridUnit * 3 + width: parent.width - Kirigami.Units.gridUnit + + Controls.ToolButton { + Layout.preferredWidth: buttonSize + Layout.preferredHeight: buttonSize + + icon.name: "window-minimize" + + Kirigami.Theme.inherit: true + + onClicked: overlay.close() + } + + Controls.TextField { + id: urlInput + + Layout.fillWidth: true + clip: true + focus: false + text: currentWebView.url + selectByMouse: true + Kirigami.Theme.inherit: true + + onActiveFocusChanged: if (activeFocus) selectAll() + onAccepted: applyUrl() + onTextChanged: urlFilter.setFilterFixedString(text) + Keys.onEscapePressed: if (overlay.sheetOpen) overlay.close() + Component.onCompleted: overlay.urlInput = urlInput + + function applyUrl() { + if (text.match(RegexWebUrl.re_weburl)) { + load(UrlUtils.urlFromUserInput(text)) + } else { + load(UrlUtils.urlFromUserInput(browserManager.searchBaseUrl + text)) + } + overlay.close(); + } + } + + Controls.ToolButton { + Layout.preferredWidth: buttonSize + Layout.preferredHeight: buttonSize + + icon.name: "go-next" + + Kirigami.Theme.inherit: true + + onClicked: urlInput.applyUrl(); + } + } + + ListView { + id: listView + + anchors { + bottom: parent.bottom + left: parent.left + right: parent.right + top: editRow.bottom + } + + boundsBehavior: Flickable.StopAtBounds + clip: true + + delegate: UrlDelegate { + showRemove: false + onClicked: overlay.close() + highlightText: urlInput.text + } + + model: UrlFilterProxyModel { + id: urlFilter + sourceModel: browserManager.history + } + + Component.onCompleted: overlay.listView = listView + } + } + + onOpened: { + // check if the drawer was just slightly slided + if (openedState) return; + openedState = true; + urlInput.text = currentWebView.url; + urlInput.forceActiveFocus(); + urlInput.selectAll(); + listView.positionViewAtBeginning(); + } + + onClosed: { + openedState = false; + currentWebView.forceActiveFocus(); + } +} diff --git a/src/contents/ui/Settings.qml b/src/contents/ui/Settings.qml index 9d91463..1c0e930 100644 --- a/src/contents/ui/Settings.qml +++ b/src/contents/ui/Settings.qml @@ -1,123 +1,124 @@ /*************************************************************************** * * * Copyright 2014-2015 Sebastian Kügler * * * * This program 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; either version 2 of the License, or * * (at your option) any later version. * * * * This program 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, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * * * ***************************************************************************/ import QtQuick 2.3 import QtQuick.Controls 2.4 as Controls import QtQuick.Layouts 1.11 import org.kde.kirigami 2.7 as Kirigami +import org.kde.mobile.angelfish 1.0 Kirigami.ScrollablePage { title: i18n("Settings") topPadding: 0 bottomPadding: 0 leftPadding: 0 rightPadding: 0 Kirigami.ColumnView.fillWidth: false background: Rectangle { Kirigami.Theme.colorSet: Kirigami.Theme.View color: Kirigami.Theme.backgroundColor } ColumnLayout { id: settingsPage spacing: 0 Kirigami.AbstractListItem { width: parent.width Controls.CheckBox { text: i18n("Enable JavaScript") Layout.fillWidth: true onCheckedChanged: { var settings = currentWebView.settings; settings.javascriptEnabled = checked; // FIXME: save to config } Component.onCompleted: { checked = currentWebView.settings.javascriptEnabled; } } } Kirigami.AbstractListItem { width: parent.width Controls.CheckBox { text: i18n("Load images") Layout.fillWidth: true onCheckedChanged: { var settings = currentWebView.settings; settings.autoLoadImages = checked; // FIXME: save to config } Component.onCompleted: { checked = currentWebView.settings.autoLoadImages; } } } InputSheet { id: homePagePopup title: i18n("Homepage") description: i18n("Website that should be loaded on startup") placeholderText: browserManager.homepage onAccepted: { if (homePagePopup.text !== "") - browserManager.homepage = browserManager.urlFromUserInput(homePagePopup.text) + browserManager.homepage = UrlUtils.urlFromUserInput(homePagePopup.text) } } InputSheet { id: searchEnginePopup title: i18n("Search Engine") description: i18n("Base URL of your preferred search engine") placeholderText: browserManager.searchBaseUrl onAccepted: { if (searchEnginePopup.text !== "") - browserManager.searchBaseUrl = browserManager.urlFromUserInput(searchEnginePopup.text); + browserManager.searchBaseUrl = UrlUtils.urlFromUserInput(searchEnginePopup.text); } } Kirigami.BasicListItem { text: i18n("Homepage") Layout.fillWidth: true onClicked: { homePagePopup.open() } } Kirigami.BasicListItem { text: i18n("Search Engine") Layout.fillWidth: true onClicked: { searchEnginePopup.open() } } Item { Layout.fillHeight: true } } } diff --git a/src/contents/ui/webbrowser.qml b/src/contents/ui/webbrowser.qml index eba0693..e6dd48d 100644 --- a/src/contents/ui/webbrowser.qml +++ b/src/contents/ui/webbrowser.qml @@ -1,427 +1,404 @@ /*************************************************************************** * * * Copyright 2014-2015 Sebastian Kügler * * * * This program 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; either version 2 of the License, or * * (at your option) any later version. * * * * This program 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, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * * * ***************************************************************************/ import QtQuick 2.1 import QtWebEngine 1.6 import QtQuick.Window 2.3 import QtGraphicalEffects 1.0 import org.kde.kirigami 2.7 as Kirigami import org.kde.mobile.angelfish 1.0 +import QtQuick.Layouts 1.2 Kirigami.ApplicationWindow { id: webBrowser title: i18n("Angelfish Web Browser") /** Pointer to the currently active view. * * Browser-level functionality should use this to refer to the current * view, rather than looking up views in the mode, as far as possible. */ property Item currentWebView: tabs.currentItem // Pointer to the currently active list of tabs. // // As there are private and normal tabs, switch between // them according to the current mode. property Item tabs: rootPage.privateMode ? privateTabs : regularTabs onCurrentWebViewChanged: { print("Current WebView is now : " + tabs.currentIndex); } property int borderWidth: Math.round(Kirigami.Units.gridUnit / 18); property color borderColor: Kirigami.Theme.highlightColor; /** * Load a url in the current tab */ function load(url) { print("Loading url: " + url); currentWebView.url = url; currentWebView.forceActiveFocus() } BrowserManager { id: browserManager } /** * Make loading available to c++ */ Connections { target: browserManager onLoadUrlRequested: { load(url) } } width: Kirigami.Units.gridUnit * 20 height: Kirigami.Units.gridUnit * 30 /** * Add page of currently active webview to history */ function addHistoryEntry() { //print("Adding history"); var request = new Object;// FIXME request.url = currentWebView.url; request.title = currentWebView.title; request.icon = currentWebView.icon; request.lastVisited = new Date(); browserManager.addToHistory(request); } pageStack.globalToolBar.showNavigationButtons: { if (pageStack.depth <= 1) return Kirigami.ApplicationHeaderStyle.None; if (pageStack.currentIndex === pageStack.depth - 1) return Kirigami.ApplicationHeaderStyle.ShowBackButton; // not used so far, but maybe in future return (Kirigami.ApplicationHeaderStyle.ShowBackButton | Kirigami.ApplicationHeaderStyle.ShowForwardButton); } globalDrawer: Kirigami.GlobalDrawer { id: globalDrawer handleVisible: false actions: [ Kirigami.Action { icon.name: "tab-duplicate" onTriggered: { pageStack.push(Qt.resolvedUrl("Tabs.qml")) } text: i18n("Tabs") }, Kirigami.Action { icon.name: "view-private" onTriggered: { rootPage.privateMode ? rootPage.privateMode = false : rootPage.privateMode = true } text: rootPage.privateMode ? i18n("Leave private mode") : i18n("Private mode") }, Kirigami.Action { icon.name: "bookmarks" onTriggered: { pageStack.push(Qt.resolvedUrl("Bookmarks.qml")) } text: i18n("Bookmarks") }, Kirigami.Action { icon.name: "view-history" onTriggered: { pageStack.push(Qt.resolvedUrl("History.qml")) } text: i18n("History") }, Kirigami.Action { icon.name: "configure" text: i18n("Settings") onTriggered: { pageStack.push(Qt.resolvedUrl("Settings.qml")) } } ] } contextDrawer: Kirigami.ContextDrawer { id: contextDrawer handleVisible: false } // Main Page pageStack.initialPage: Kirigami.Page { id: rootPage leftPadding: 0 rightPadding: 0 topPadding: 0 bottomPadding: 0 globalToolBarStyle: Kirigami.ApplicationHeaderStyle.None Kirigami.ColumnView.fillWidth: true Kirigami.ColumnView.pinned: true Kirigami.ColumnView.preventStealing: true property bool privateMode: false ListWebView { id: regularTabs anchors { top: parent.top left: parent.left right: parent.right bottom: navigation.top } activeTabs: !rootPage.privateMode } ListWebView { id: privateTabs anchors { top: parent.top left: parent.left right: parent.right bottom: navigation.top } activeTabs: rootPage.privateMode privateTabsMode: true } ErrorHandler { id: errorHandler errorString: currentWebView.errorString errorCode: currentWebView.errorCode anchors { top: parent.top left: parent.left right: parent.right bottom: navigation.top } visible: currentWebView.errorCode !== "" } Loader { id: questionLoader anchors.bottom: navigation.top anchors.left: parent.left anchors.right: parent.right } // Container for the progress bar Item { id: progressItem height: Math.round(Kirigami.Units.gridUnit / 6) z: navigation.z + 1 anchors { top: tabs.bottom topMargin: -Math.round(height / 2) left: tabs.left right: tabs.right } opacity: currentWebView.loading ? 1 : 0 Behavior on opacity { NumberAnimation { duration: Kirigami.Units.longDuration; easing.type: Easing.InOutQuad; } } Rectangle { color: Kirigami.Theme.highlightColor width: Math.round((currentWebView.loadProgress / 100) * parent.width) anchors { top: parent.top left: parent.left bottom: parent.bottom } } } Loader { id: sheetLoader } // The menu at the bottom right contextualActions: [ Kirigami.Action { icon.name: "edit-find" shortcut: "Ctrl+F" onTriggered: { if (!sheetLoader.item || !sheetLoader.item.sheetOpen) { sheetLoader.setSource("FindInPageSheet.qml") sheetLoader.item.open() } } text: i18n("Find in page") }, Kirigami.Action { icon.name: "document-share" text: i18n("Share page") onTriggered: { sheetLoader.setSource("ShareSheet.qml") sheetLoader.item.url = currentWebView.url sheetLoader.item.title = currentWebView.title sheetLoader.item.open() } }, Kirigami.Action { enabled: currentWebView.canGoBack icon.name: "go-previous" text: i18n("Go previous") onTriggered: { currentWebView.goBack() } }, Kirigami.Action { enabled: currentWebView.canGoForward icon.name: "go-next" text: i18n("Go forward") onTriggered: { currentWebView.goForward() } }, Kirigami.Action { icon.name: currentWebView.loading ? "process-stop" : "view-refresh" text: currentWebView.loading ? i18n("Stop loading") : i18n("Refresh") onTriggered: { currentWebView.loading ? currentWebView.stop() : currentWebView.reload() } }, Kirigami.Action { icon.name: "bookmarks" text: i18n("Add bookmark") onTriggered: { print("Adding bookmark"); var request = new Object;// FIXME request.url = currentWebView.url; request.title = currentWebView.title; request.icon = currentWebView.icon; request.bookmarked = true; browserManager.addBookmark(request); } }, Kirigami.Action { icon.name: "computer" text: i18n("Show desktop site") checkable: true checked: !currentWebView.userAgent.isMobile onTriggered: { if (currentWebView.userAgent.isMobile) { currentWebView.userAgent.isMobile = false } else { currentWebView.userAgent.isMobile = true } if (!rootPage.privateMode) { browserManager.setTabIsMobile(tabs.currentIndex, currentWebView.userAgent.isMobile); } currentWebView.reload() } } ] // Bottom navigation bar Navigation { id: navigation navigationShown: !webappcontainer && webBrowser.visibility !== Window.FullScreen - z: 2 - Kirigami.Theme.colorSet: rootPage.privateMode ? Kirigami.Theme.Complementary : Kirigami.Theme.Window layer.enabled: navigation.visible layer.effect: DropShadow { verticalOffset: - 1 color: Kirigami.Theme.disabledTextColor samples: 10 spread: 0.1 cached: true // element is static } anchors { - bottom: completion.top + bottom: parent.bottom left: parent.left right: parent.right } - onTextChanged: urlFilter.setFilterFixedString(text) + onActivateUrlEntry: urlEntry.open() } - Completion { - id: completion - - z: 1 - - model: UrlFilterProxyModel { - id: urlFilter - sourceModel: browserManager.history - } - width: parent.width - height: 0.5 * parent.height - visible: navigation.textFocus - searchText: navigation.text - - Behavior on height { - NumberAnimation { - duration: Kirigami.Units.shortDuration * 2 - } - } - - anchors { - bottom: parent.bottom - horizontalCenter: navigation.horizontalCenter - } + NavigationEntrySheet { + id: urlEntry } // Thin line above navigation Rectangle { height: webBrowser.borderWidth color: webBrowser.borderColor anchors { left: parent.left bottom: navigation.top right: parent.right } } } Connections { target: webBrowser.pageStack onCurrentIndexChanged: { // drop all sub pages as soon as the browser window is the // focussed one if (webBrowser.pageStack.currentIndex === 0) webBrowser.pageStack.pop(); } } Component.onCompleted: { if (!webappcontainer) { // initialize tabs var t = JSON.parse(browserManager.tabs()); var ct = browserManager.currentTab(); for (var i = 0; i < t.length; i++) { if (i < regularTabs.count) regularTabs.itemAt(i).url = t[i].url; else regularTabs.newTab(t[i].url); regularTabs.itemAt(i).userAgent.isMobile = t[i].isMobile; regularTabs.itemAt(i).reloadOnVisible = true; } if (ct >= 0 && ct < regularTabs.count) regularTabs.currentIndex = ct; browserManager.setTabsWritable(); if (initialUrl) { regularTabs.newTab(initialUrl); } } else { if (initialUrl) { load(initialUrl); } } } } diff --git a/src/main.cpp b/src/main.cpp index 66e764e..5acfb8e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,77 +1,87 @@ /* Copyright (C) 2019 Jonah Brüchert This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program 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 Library 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 . */ #include #include #include #include #include #include #include #include #include #include "browsermanager.h" #include "urlfilterproxymodel.h" #include "urlmodel.h" +#include "urlutils.h" #include "useragent.h" Q_DECL_EXPORT int main(int argc, char *argv[]) { QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QApplication app(argc, argv); QCoreApplication::setOrganizationName("KDE"); QCoreApplication::setOrganizationDomain("mobile.kde.org"); QCoreApplication::setApplicationName("angelfish"); // Command line parser QCommandLineParser parser; parser.addPositionalArgument("url", i18n("URL to open"), "[url]"); parser.addOption({ "webapp-container", i18n("Start without UI") }); parser.addHelpOption(); parser.process(app); // QML loading QQmlApplicationEngine engine; engine.rootContext()->setContextObject(new KLocalizedContext(&engine)); // Setup QtWebEngine qputenv("QTWEBENGINE_DIALOG_SET", "QtQuickControls2"); QtWebEngine::initialize(); // initial url command line parameter QUrl initialUrl; if (!parser.positionalArguments().isEmpty()) initialUrl = QUrl::fromUserInput(parser.positionalArguments().first()); engine.rootContext()->setContextProperty("initialUrl", initialUrl.url()); engine.rootContext()->setContextProperty("webappcontainer", parser.isSet("webapp-container")); // Browser manager qmlRegisterType("org.kde.mobile.angelfish", 1, 0, "BrowserManager"); qmlRegisterType("org.kde.mobile.angelfish", 1, 0, "UrlFilterProxyModel"); qmlRegisterType("org.kde.mobile.angelfish", 1, 0, "UserAgentGenerator"); + + // URL utils + qmlRegisterSingletonType("org.kde.mobile.angelfish", 1, 0, "UrlUtils", + [](QQmlEngine * /*engine*/, QJSEngine * /*scriptEngine*/) -> QObject * { + AngelFish::UrlUtils *urlUtils = new AngelFish::UrlUtils(); + return urlUtils; + }); + + // Load QML engine.load(QUrl(QStringLiteral("qrc:///webbrowser.qml"))); // Error handling if (engine.rootObjects().isEmpty()) { return -1; } return app.exec(); } diff --git a/src/resources.qrc b/src/resources.qrc index 78ee850..d6ac696 100644 --- a/src/resources.qrc +++ b/src/resources.qrc @@ -1,23 +1,23 @@ contents/ui/Bookmarks.qml contents/ui/ErrorHandler.qml contents/ui/History.qml contents/ui/ListWebView.qml contents/ui/Navigation.qml contents/ui/Settings.qml contents/ui/Tabs.qml contents/ui/UrlDelegate.qml contents/ui/webbrowser.qml contents/ui/WebView.qml regex-weburl/regex-weburl.js contents/ui/InputSheet.qml contents/ui/ShareSheet.qml - contents/ui/Completion.qml + contents/ui/NavigationEntrySheet.qml contents/ui/AuthSheet.qml contents/ui/NewTabQuestion.qml contents/ui/DownloadQuestion.qml contents/ui/PermissionQuestion.qml contents/ui/FindInPageSheet.qml diff --git a/src/contents/ui/Completion.qml b/src/urlutils.cpp similarity index 62% copy from src/contents/ui/Completion.qml copy to src/urlutils.cpp index 92832a9..d33e63d 100644 --- a/src/contents/ui/Completion.qml +++ b/src/urlutils.cpp @@ -1,60 +1,65 @@ /*************************************************************************** * * - * Copyright 2019 Simon Schmeisser * - * Copyright 2019 Jonah Brüchert * + * Copyright 2020 Jonah Brüchert * + * 2020 Rinigus * * * * This program 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; either version 2 of the License, or * * (at your option) any later version. * * * * This program 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, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * * * ***************************************************************************/ -import QtQuick 2.7 -import QtQuick.Controls 2.2 as Controls - -import org.kde.kirigami 2.5 as Kirigami - -Controls.ScrollView { - id: completion - - background: Rectangle { - color: Kirigami.Theme.backgroundColor - } - - property string searchText - property alias model: listView.model - property alias count: listView.count - - ListView { - id: listView - clip: true - - delegate: UrlDelegate { - showRemove: false - onClicked: tabs.forceActiveFocus() - highlightText: completion.searchText - } - } - - states: [ - State { - name: "hidden" - when: visible === false - PropertyChanges { - target: completion - height: 0 - } - } - ] +#include "urlutils.h" + +#include +#include +#include +#include +#include +#include + +using namespace AngelFish; + +UrlUtils::UrlUtils(QObject *parent) : QObject(parent) +{ +} + +UrlUtils::~UrlUtils() +{ +} + +QString UrlUtils::urlFromUserInput(const QString &input) +{ + return QUrl::fromUserInput(input).toString(); } + +QString UrlUtils::urlScheme(const QString &url) +{ + return QUrl::fromUserInput(url).scheme(); +} + +QString UrlUtils::urlHostPort(const QString &url) +{ + QUrl u(url); + QString r = u.host(); + int p = u.port(-1); + if (p > 0) r = QString("%1:%2").arg(r).arg(p); + return r; +} + +QString UrlUtils::urlPath(const QString &url) +{ + return QUrl::fromUserInput(url).path(); +} + diff --git a/src/contents/ui/Completion.qml b/src/urlutils.h similarity index 62% rename from src/contents/ui/Completion.qml rename to src/urlutils.h index 92832a9..ae0a237 100644 --- a/src/contents/ui/Completion.qml +++ b/src/urlutils.h @@ -1,60 +1,52 @@ /*************************************************************************** * * - * Copyright 2019 Simon Schmeisser * - * Copyright 2019 Jonah Brüchert * + * Copyright 2020 Jonah Brüchert * + * 2020 Rinigus * * * * This program 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; either version 2 of the License, or * * (at your option) any later version. * * * * This program 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, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * * * ***************************************************************************/ -import QtQuick 2.7 -import QtQuick.Controls 2.2 as Controls - -import org.kde.kirigami 2.5 as Kirigami - -Controls.ScrollView { - id: completion - - background: Rectangle { - color: Kirigami.Theme.backgroundColor - } - - property string searchText - property alias model: listView.model - property alias count: listView.count - - ListView { - id: listView - clip: true - - delegate: UrlDelegate { - showRemove: false - onClicked: tabs.forceActiveFocus() - highlightText: completion.searchText - } - } - - states: [ - State { - name: "hidden" - when: visible === false - PropertyChanges { - target: completion - height: 0 - } - } - ] -} +#ifndef URLUTILS_H +#define URLUTILS_H + +#include + +#include "urlmodel.h" + +namespace AngelFish { +/** + * @class UrlUtils + * @short Utilities for URL manipulation and parsing. + */ +class UrlUtils : public QObject +{ + Q_OBJECT + +public: + UrlUtils(QObject *parent = nullptr); + ~UrlUtils() override; + + Q_INVOKABLE static QString urlFromUserInput(const QString &input); + Q_INVOKABLE static QString urlScheme(const QString &url); + Q_INVOKABLE static QString urlHostPort(const QString &url); + Q_INVOKABLE static QString urlPath(const QString &url); + +}; + +} // namespace + +#endif // URLUTILS_H