diff --git a/src/icons/16/document-open-remote.svg b/src/icons/16/document-open-remote.svg new file mode 100644 index 00000000..86938c66 --- /dev/null +++ b/src/icons/16/document-open-remote.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/src/icons/icons.qrc b/src/icons/icons.qrc index be18a6c6..b0bb7cec 100644 --- a/src/icons/icons.qrc +++ b/src/icons/icons.qrc @@ -32,6 +32,7 @@ 16/document-save-as.svg 16/document-open.svg 16/document-open-recent.svg + 16/document-open-remote.svg 16/format-font-size-more.svg 16/edit-clear-history.svg 16/configure.svg diff --git a/src/kirigami_ui/+android/main.qml b/src/kirigami_ui/+android/main.qml index 67e9e4f7..2dbf7759 100644 --- a/src/kirigami_ui/+android/main.qml +++ b/src/kirigami_ui/+android/main.qml @@ -185,6 +185,15 @@ Kirigami.ApplicationWindow { root.pageStack.currentItem.document.open() } }, + Kirigami.Action { + text: i18nc("Main menu and global menu actions", "&Open remote file") + //iconName: "document-open-remote" + iconSource: "qrc:/icons/document-open-remote.svg" + onTriggered: { + root.onDiscard = Prompter.CloseActions.Network + root.pageStack.currentItem.document.openFromNetwork() + } + }, Kirigami.Action { text: i18nc("Main menu and global menu actions", "&Save") iconName: "document-save" @@ -338,6 +347,8 @@ Kirigami.ApplicationWindow { root.pageStack.currentItem.keyConfigurationOverlay.close() else if (root.pageStack.currentItem.namedMarkerConfiguration.sheetOpen) root.pageStack.currentItem.namedMarkerConfiguration.close() + else if (root.pageStack.currentItem.networkDialog.sheetOpen) + root.pageStack.currentItem.networkDialog.close() else if (wheelSettings.sheetOpen) wheelSettings.close() // Close find, compare against enabled instead of isOpen to prevent closing find while it is invisible. @@ -529,6 +540,9 @@ Kirigami.ApplicationWindow { case Prompter.CloseActions.Open: root.pageStack.currentItem.openDialog.open(); break; + case Prompter.CloseActions.Network: + root.pageStack.currentItem.networkDialog.open(); + break; case Prompter.CloseActions.Quit: Qt.quit(); break; case Prompter.CloseActions.Ignore: diff --git a/src/kirigami_ui/+windows/main.qml b/src/kirigami_ui/+windows/main.qml index 9ac88499..14c34934 100644 --- a/src/kirigami_ui/+windows/main.qml +++ b/src/kirigami_ui/+windows/main.qml @@ -195,6 +195,15 @@ Kirigami.ApplicationWindow { root.pageStack.currentItem.document.open() } }, + Kirigami.Action { + text: i18nc("Main menu and global menu actions", "&Open remote file") + //iconName: "document-open-remote" + iconSource: "qrc:/icons/document-open-remote.svg" + onTriggered: { + root.onDiscard = Prompter.CloseActions.Network + root.pageStack.currentItem.document.openFromNetwork() + } + }, Kirigami.Action { text: i18nc("Main menu and global menu actions", "&Save") iconName: "document-save" @@ -380,6 +389,8 @@ Kirigami.ApplicationWindow { root.pageStack.currentItem.keyConfigurationOverlay.close() else if (root.pageStack.currentItem.namedMarkerConfiguration.sheetOpen) root.pageStack.currentItem.namedMarkerConfiguration.close() + else if (root.pageStack.currentItem.networkDialog.sheetOpen) + root.pageStack.currentItem.networkDialog.close() else if (wheelSettings.sheetOpen) wheelSettings.close() // Close find, compare against enabled instead of isOpen to prevent closing find while it is invisible. @@ -605,6 +616,9 @@ Kirigami.ApplicationWindow { case Prompter.CloseActions.Open: root.pageStack.currentItem.openDialog.open(); break; + case Prompter.CloseActions.Network: + root.pageStack.currentItem.networkDialog.open(); + break; case Prompter.CloseActions.Quit: Qt.quit(); break; case Prompter.CloseActions.Ignore: diff --git a/src/kirigami_ui/EditorToolbar.qml b/src/kirigami_ui/EditorToolbar.qml index 60709f46..f310476d 100644 --- a/src/kirigami_ui/EditorToolbar.qml +++ b/src/kirigami_ui/EditorToolbar.qml @@ -1107,5 +1107,18 @@ ToolBar { } } } + RowLayout { + Button { + visible: networkDialog.autoReloadRunning + text: i18nc("Next reload starts at 10:11:12", "Next reload starts at
%1
", networkDialog.nextReloadTime) + onClicked: networkDialog.open() + flat: true + contentItem: Loader { sourceComponent: textComponent } + Layout.topMargin: -8 + Layout.bottomMargin: -32 + Layout.rightMargin: 3 + Layout.leftMargin: showSliderIcons ? 1 : 8 + } + } } } diff --git a/src/kirigami_ui/PrompterPage.qml b/src/kirigami_ui/PrompterPage.qml index aa8b34d0..8ef141c6 100644 --- a/src/kirigami_ui/PrompterPage.qml +++ b/src/kirigami_ui/PrompterPage.qml @@ -25,6 +25,7 @@ import QtQuick.Window 2.12 import QtQuick.Controls 2.12 import QtQuick.Layouts 1.12 import QtQuick.Dialogs 1.3 +import Qt.labs.settings 1.0 //import Qt.labs.platform 1.1 as Labs import com.cuperino.qprompt.markers 1.0 @@ -42,6 +43,7 @@ Kirigami.Page { property alias overlay: viewport.overlay property alias document: viewport.document property alias openDialog: viewport.openDialog + property alias networkDialog: networkDialog property alias prompterBackground: viewport.prompterBackground property alias find: viewport.find property alias keyConfigurationOverlay: keyConfigurationOverlay @@ -1003,6 +1005,174 @@ Kirigami.Page { } } + Settings { + category: "networkDialog" + property alias url: openUrl.text + property alias autoReload: networkDialog.autoReload + property alias autoReloadHours: autoReloadHours.value + property alias autoReloadMinutes: autoReloadMinutes.value + property alias autoReloadSeconds: autoReloadSeconds.value + } + Timer { + id: autoReloadTimer + running: networkDialog.autoReloadRunning + repeat: true + triggeredOnStart: false + interval: 1000 * (3600 * autoReloadHours.value + 60 * autoReloadMinutes.value + autoReloadSeconds.value) + onTriggered: networkDialog.openFromRemote(); + } + Kirigami.OverlaySheet { + id: networkDialog + header: Kirigami.Heading { + text: i18n("Open from network...") + level: 1 + } + property bool autoReload: false + property bool autoReloadRunning: false + property string nextReloadTime: "" + function updateNextReloadTime() { + const date = new Date(); + date.setTime(date.getTime() + autoReloadTimer.interval); + nextReloadTime = date.toLocaleTimeString(); + } + function openFromRemote() { + document.loadFromNetwork(openUrl.text); + if (!autoReloadRunning) + editor.lastDocument = "" + if (autoReload) { + autoReloadRunning = true; + updateNextReloadTime(); + } + } + function disableAutoReload() { + networkDialog.autoReloadRunning = false; + } + ColumnLayout { + width: parent.width + RowLayout { + Label { + text: i18n("URL:") + } + TextField { + id: openUrl + placeholderText: i18n("http, https, and ftp protocols supported") + Layout.fillWidth: true + Layout.leftMargin: Kirigami.Units.smallSpacing + Layout.rightMargin: Kirigami.Units.smallSpacing + onAccepted: networkDialog.openFromRemote() + } + } + RowLayout { + Button { + id: autoReloadToggle + flat: true + text: i18n("Auto reload") + checkable: true + checked: networkDialog.autoReload + onToggled: { + networkDialog.autoReload = checked; + if (!checked) + networkDialog.disableAutoReload(); + } + } + Label { + text: i18n("Hours:") + } + SpinBox { + id: autoReloadHours + Layout.fillWidth: true + Layout.leftMargin: Units.SmallSpacing + Layout.rightMargin: Units.SmallSpacing + enabled: networkDialog.autoReload + value: 0 + to: 168 + onValueModified: { + if (value === to) { + autoReloadMinutes.value = 0; + autoReloadSeconds.value = 0; + } + // Since this onValueModified will get called regardless of which SpinBox gets modified, this is where and when we update the nextReloadTime + networkDialog.updateNextReloadTime(); + } + } + Label { + text: i18n("Minutes:") + } + SpinBox { + id: autoReloadMinutes + Layout.fillWidth: true + Layout.leftMargin: Units.SmallSpacing + Layout.rightMargin: Units.SmallSpacing + enabled: networkDialog.autoReload + value: 5 + from: value>0 || autoReloadMinutes.value>0 || autoReloadHours.value>0 ? -1 : 0 + to: /*autoReloadHours.value===autoReloadHours.to ? 0 :*/ 60 + onValueModified: { + if (value < 0) { + value = 59; + --autoReloadHours.value; + } + else if (value > 59) { + value = 0; + ++autoReloadHours.value; + } + autoReloadHours.onValueModified(); + } + } + Label { + text: i18n("Seconds:") + } + SpinBox { + id: autoReloadSeconds + Layout.fillWidth: true + Layout.leftMargin: Units.SmallSpacing + Layout.rightMargin: Units.SmallSpacing + enabled: networkDialog.autoReload + value: 0 + from: value>0 || autoReloadMinutes.value>0 || autoReloadHours.value>0 ? -1 : 1 + to: /*autoReloadHours.value===autoReloadHours.to ? 0 :*/ 60 + onValueModified: { + if (value < 0) { + value = 59; + --autoReloadMinutes.value; + } + else if (value > 59) { + value = 0; + ++autoReloadMinutes.value; + } + autoReloadMinutes.onValueModified(); + } + } + } + RowLayout { + Label { + text: autoReloadTimer.running ? + i18nc("Next reload starts at 10:11:12", "Next reload starts at %1", networkDialog.nextReloadTime) + : i18n("Auto reload is not running") + horizontalAlignment: Text.AlignHCenter + Layout.fillWidth: true + } + Button { + enabled: openUrl.text !== "" + flat: true + text: i18n("Load from Network") + onClicked: { + networkDialog.openFromRemote(); + networkDialog.close(); + } + } + Button { + flat: true + text: i18n("Close") + onClicked: { + networkDialog.close(); + viewport.prompter.restoreFocus(); + } + } + } + } + } + //Kirigami.OverlaySheet { //id: stepsConfiguration //onSheetOpenChanged: { diff --git a/src/kirigami_ui/main.qml b/src/kirigami_ui/main.qml index 15f005a3..021e4fe3 100644 --- a/src/kirigami_ui/main.qml +++ b/src/kirigami_ui/main.qml @@ -197,6 +197,15 @@ Kirigami.ApplicationWindow { root.pageStack.currentItem.document.open() } }, + Kirigami.Action { + text: i18nc("Main menu and global menu actions", "&Open remote file") + //iconName: "document-open-remote" + iconSource: "qrc:/icons/document-open-remote.svg" + onTriggered: { + root.onDiscard = Prompter.CloseActions.Network + root.pageStack.currentItem.document.openFromNetwork() + } + }, Kirigami.Action { text: i18nc("Main menu and global menu actions", "&Save") //iconName: "document-save" @@ -397,6 +406,8 @@ Kirigami.ApplicationWindow { root.pageStack.currentItem.keyConfigurationOverlay.close() else if (root.pageStack.currentItem.namedMarkerConfiguration.sheetOpen) root.pageStack.currentItem.namedMarkerConfiguration.close() + else if (root.pageStack.currentItem.networkDialog.sheetOpen) + root.pageStack.currentItem.networkDialog.close() else if (wheelSettings.sheetOpen) wheelSettings.close() // Close find, compare against enabled instead of isOpen to prevent closing find while it is invisible. @@ -939,6 +950,9 @@ Kirigami.ApplicationWindow { case Prompter.CloseActions.Open: root.pageStack.currentItem.openDialog.open(); break; + case Prompter.CloseActions.Network: + root.pageStack.currentItem.networkDialog.open(); + break; case Prompter.CloseActions.Quit: Qt.quit(); break; diff --git a/src/prompter/Prompter.qml b/src/prompter/Prompter.qml index afeb85a8..8fca3d9c 100644 --- a/src/prompter/Prompter.qml +++ b/src/prompter/Prompter.qml @@ -95,6 +95,7 @@ Flickable { LoadNew, LoadGuide, Open, + Network, Ignore } enum AtEndActions { @@ -1101,14 +1102,21 @@ Flickable { editor.resetPosition = false; } } + function clearLastDocument() { + editor.lastDocument = "blank://"; + } + function close() { + networkDialog.autoReloadRunning = false; + document.load("qrc:/blank.html"); + } function newDocument() { root.onDiscard = Prompter.CloseActions.LoadNew if (document.modified) closeDialog.open() else { - document.load("qrc:/blank.html") + document.close() document.load("qrc:/untitled.html") - editor.lastDocument = "blank://" + clearLastDocument(); isNewFile = true resetDocumentPosition() if (root.passiveNotifications) @@ -1120,7 +1128,7 @@ Flickable { if (document.modified) closeDialog.open() else { - document.load("qrc:/blank.html") + document.close() document.load("qrc:/"+i18n("welcome_en.html")) editor.lastDocument = "" isNewFile = true @@ -1131,7 +1139,7 @@ Flickable { } function loadLastDocument() { root.onDiscard = Prompter.CloseActions.Open - document.load("qrc:/blank.html") + document.close() document.load(editor.lastDocument) isNewFile = false prompter.position = 0 @@ -1145,6 +1153,13 @@ Flickable { else openDialog.open() } + function openFromNetwork() { + root.onDiscard = Prompter.CloseActions.Network + if (document.modified) + closeDialog.open() + else + networkDialog.open() + } function saveAsDialog() { saveDialog.open(parseInt(root.onDiscard)===Prompter.CloseActions.Quit) } @@ -1169,6 +1184,7 @@ Flickable { case Prompter.CloseActions.LoadGuide: document.loadGuide(); break; case Prompter.CloseActions.LoadNew: document.newDocument(); break; case Prompter.CloseActions.Open: document.open(); break; + case Prompter.CloseActions.Network: document.openFromNetwork(); break; case Prompter.CloseActions.Quit: Qt.quit() } } @@ -1233,7 +1249,7 @@ Flickable { folder: shortcuts.documents //fileMode: Labs.FileDialog.OpenFile onAccepted: { - document.load("qrc:/blank.html") + document.close() document.load(openDialog.fileUrl) editor.lastDocument = document.fileUrl; editor.resetPosition = true; @@ -1279,6 +1295,7 @@ Flickable { case Prompter.CloseActions.LoadGuide: document.modified = false; document.loadGuide(); break; case Prompter.CloseActions.LoadNew: document.modified = false; document.newDocument(); break; case Prompter.CloseActions.Open: document.open(); /*openOpenDialog.start();*/ break; + case Prompter.CloseActions.Network: document.modified = false; document.openFromNetwork(); break; case Prompter.CloseActions.Quit: Qt.quit() //default: Qt.quit(); } diff --git a/src/prompter/documenthandler.cpp b/src/prompter/documenthandler.cpp index 7a199aa8..e7f72873 100644 --- a/src/prompter/documenthandler.cpp +++ b/src/prompter/documenthandler.cpp @@ -99,8 +99,10 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -120,7 +122,16 @@ DocumentHandler::DocumentHandler(QObject *parent) office_importer = QString::fromStdString("soffice"); m_fontDialog = new SystemFontChooserDialog(); - QObject::connect(m_fontDialog, &SystemFontChooserDialog::fontFamilyChanged, this, &DocumentHandler::setFontFamily); + m_network = new QNetworkAccessManager(this); + m_cache = new QTemporaryFile(this); + connect(m_fontDialog, &SystemFontChooserDialog::fontFamilyChanged, this, &DocumentHandler::setFontFamily); + connect(m_network, &QNetworkAccessManager::finished, this, &DocumentHandler::loadFromNetworkFinihed); + connect(m_reply, &QNetworkReply::downloadProgress, this, &DocumentHandler::networkReplyProgress); + connect(m_reply, &QNetworkReply::finished, this, &DocumentHandler::loadFromNetworkFinihed); + + // connect(reply, &QNetworkReply::finished, this, &HttpWindow::httpFinished); + // connect(reply, &QIODevice::readyRead, this, &HttpWindow::httpReadyRead); + // connect(reply, &QNetworkReply::finished, progressDialog, &ProgressDialog::hide); } DocumentHandler::~DocumentHandler() @@ -480,6 +491,47 @@ void DocumentHandler::reload(const QString &fileUrl) load(QUrl(QString::fromStdString("file://") + fileUrl)); } +void DocumentHandler::loadFromNetwork(const QUrl &url) +{ + QUrl resultingUrl; + QNetworkRequest req; + if (url.isRelative()) { + resultingUrl.setScheme("http"); + resultingUrl.setHost(url.path()); + resultingUrl.setPort(url.port()); + resultingUrl.setUserName(url.userName()); + resultingUrl.setPassword(url.password()); + resultingUrl.setFragment(url.fragment()); + resultingUrl.setQuery(url.query()); + } else + resultingUrl = url; + if (url.isValid()) { + req = QNetworkRequest(resultingUrl); + req.setAttribute(QNetworkRequest::RedirectPolicyAttribute, true); + m_reply = m_network->get(req); + } +} + +void DocumentHandler::networkReplyProgress() +{ + // ToDo: Add loading progress feedback +} + +void DocumentHandler::loadFromNetworkFinihed() +{ + auto document = m_reply->readAll(); + + if (document != "") { + static QRegularExpression regex_0( + QString::fromStdString("((font-size|letter-spacing|word-spacing|font-weight):\\s*-?[\\d]+(?:.[\\d]+)*(?:(?:px)|(?:pt)|(?:em)|(?:ex));?\\s*)")); + QString html = QString::fromUtf8(document).replace(regex_0, QString::fromStdString("")); + Q_EMIT loaded(html, Qt::RichText); + + m_fileUrl = m_cache->fileName(); + Q_EMIT fileUrlChanged(); + } +} + void DocumentHandler::load(const QUrl &fileUrl) { // if (fileUrl == m_fileUrl) diff --git a/src/prompter/documenthandler.h b/src/prompter/documenthandler.h index 4e7a16bf..c39fd3d2 100644 --- a/src/prompter/documenthandler.h +++ b/src/prompter/documenthandler.h @@ -74,6 +74,7 @@ #include #include +#include #include #include #include @@ -197,7 +198,12 @@ class DocumentHandler : public QObject Q_INVOKABLE bool showFontDialog(); + Q_INVOKABLE void networkReplyProgress(); + + Q_INVOKABLE void loadFromNetwork(const QUrl &url); + public Q_SLOTS: + void loadFromNetworkFinihed(); void load(const QUrl &fileUrl); void reload(const QString &fileUrl); void saveAs(const QUrl &fileUrl); @@ -257,6 +263,9 @@ public Q_SLOTS: QUrl m_fileUrl; QString pdf_importer; QString office_importer; + QNetworkAccessManager *m_network; + QNetworkReply *m_reply; + QTemporaryFile *m_cache; }; QT_END_NAMESPACE