diff --git a/src/controls/Editor.qml b/src/controls/Editor.qml index 5ec6108..be01812 100644 --- a/src/controls/Editor.qml +++ b/src/controls/Editor.qml @@ -1,289 +1,314 @@ import QtQuick 2.10 import QtQuick.Controls 2.10 import QtQuick.Layouts 1.3 import org.kde.mauikit 1.0 as Maui import org.kde.kirigami 2.7 as Kirigami import "private" Maui.Page { id: control Kirigami.Theme.inherit: false Kirigami.Theme.colorSet: Kirigami.Theme.View property bool showLineCount : true property bool showSyntaxHighlighting: true property alias body : body property alias document : document property alias scrollView: _scrollView property alias text: body.text property alias uppercase: document.uppercase property alias underline: document.underline property alias italic: document.italic property alias bold: document.bold property alias canRedo: body.canRedo Maui.DocumentHandler { id: document document: body.textDocument cursorPosition: body.cursorPosition selectionStart: body.selectionStart selectionEnd: body.selectionEnd textColor: control.Kirigami.Theme.textColor backgroundColor: control.Kirigami.Theme.backgroundColor onError: { body.text = message body.visible = true } } Row { z: _scrollView.z +1 visible: showLineCount anchors { right: parent.right bottom: parent.bottom margins: Maui.Style.space.big } width: implicitWidth height: implicitHeight Label { text: body.length + " / " + body.lineCount color: control.Kirigami.Theme.textColor opacity: 0.5 font.pointSize: Maui.Style.fontSizes.medium } } Menu { id: documentMenu z: 999 MenuItem { text: qsTr("Copy") onTriggered: body.copy() enabled: body.selectedText.length } MenuItem { text: qsTr("Cut") onTriggered: body.cut() enabled: !body.readOnly && body.selectedText.length } MenuItem { text: qsTr("Paste") onTriggered: body.paste() enabled: !body.readOnly } MenuItem { text: qsTr("Select all") onTriggered: body.selectAll() } MenuItem { text: qsTr("Web search") onTriggered: Qt.openUrlExternally("https://www.google.com/search?q="+body.selectedText) enabled: body.selectedText.length } } headBar.visible: !body.readOnly headBar.leftContent: [ ToolButton { icon.name: "edit-undo" enabled: body.canUndo onClicked: body.undo() opacity: enabled ? 1 : 0.5 }, ToolButton { icon.name: "edit-redo" enabled: body.canRedo onClicked: body.redo() opacity: enabled ? 1 : 0.5 }, Row { id: _editingActions visible: document.isRich && !body.readOnly ToolButton { icon.name: "format-text-bold" focusPolicy: Qt.TabFocus icon.color: checked ? control.Kirigami.Theme.highlightColor : control.Kirigami.Theme.textColor checkable: false checked: document.bold onClicked: document.bold = !document.bold } ToolButton { icon.name: "format-text-italic" icon.color: checked ? control.Kirigami.Theme.highlightColor : control.Kirigami.Theme.textColor focusPolicy: Qt.TabFocus checkable: false checked: document.italic onClicked: document.italic = !document.italic } ToolButton { icon.name: "format-text-underline" icon.color: checked ? control.Kirigami.Theme.highlightColor : control.Kirigami.Theme.textColor focusPolicy: Qt.TabFocus checkable: true checked: document.underline onClicked: document.underline = !document.underline } ToolButton { icon.name: "format-text-uppercase" icon.color: checked ? control.Kirigami.Theme.highlightColor : control.Kirigami.Theme.textColor focusPolicy: Qt.TabFocus checkable: true checked: document.uppercase onClicked: document.uppercase = !document.uppercase } } ] // footBar.visible: !body.readOnly footBar.rightContent: [ ToolButton { icon.name: "zoom-in" onClicked: zoomIn() }, ToolButton { icon.name: "zoom-out" onClicked: zoomOut() }, ComboBox { visible: control.showSyntaxHighlighting id: languagesListComboBox model: document.getLanguageNameList() onCurrentIndexChanged: document.formatName = languagesListComboBox.model[currentIndex] } ] ColumnLayout { anchors.fill: parent + spacing: 0 Repeater { model: document.alerts Maui.ToolBar { + property var alert : model.alert + Layout.fillWidth: true - Kirigami.Theme.backgroundColor: Kirigami.Theme.neutralTextColor + Kirigami.Theme.backgroundColor: + { + switch(alert.level) + { + case 0: return Kirigami.Theme.positiveTextColor + case 1: return Kirigami.Theme.neutralTextColor + case 2: return Kirigami.Theme.negativeTextColor + } + } leftContent: Maui.ListItemTemplate { Layout.fillWidth: true Layout.fillHeight: true - label1.text: model.warning.title - label2.text: model.warning.body + label1.text: alert.title + label2.text: alert.body + } + + rightContent: Repeater + { + model: alert.actionLabels() + + Button + { + text: modelData + onClicked: alert.triggerAction(index) + + Kirigami.Theme.backgroundColor: Qt.rgba(Kirigami.Theme.backgroundColor.r, Kirigami.Theme.backgroundColor.g, Kirigami.Theme.backgroundColor.b, 0.2) + Kirigami.Theme.textColor: Kirigami.Theme.textColor + } } } } Kirigami.ScrollablePage { id: _scrollView Layout.fillWidth: true Layout.fillHeight: true contentWidth: width contentHeight: body.implicitHeight leftPadding: 0 rightPadding: 0 topPadding: 0 bottomPadding: 0 TextArea { id: body text: document.text font.family: languagesListComboBox.currentIndex > 0 ? "Monospace" : undefined placeholderText: qsTr("Body") selectByKeyboard :!Kirigami.Settings.isMobile selectByMouse : !Kirigami.Settings.isMobile textFormat: TextEdit.AutoText palette.text: control.Kirigami.Theme.textColor color: control.Kirigami.Theme.textColor font.pointSize: Maui.Style.fontSizes.large wrapMode: TextEdit.WrapAnywhere activeFocusOnPress: true activeFocusOnTab: true persistentSelection: true background: Rectangle { color: document.backgroundColor implicitWidth: body.implicitWidth implicitHeight: Math.max(body.implicitHeight, control.height) } // onPressAndHold: isMobile ? documentMenu.popup() : undefined onPressed: { if(!Kirigami.Settings.isMobile && event.button === Qt.RightButton) documentMenu.popup() } } // ScrollBar.vertical.height: _scrollView.height - body.topPadding // ScrollBar.vertical.y: body.topPadding } } function zoomIn() { body.font.pointSize = body.font.pointSize + 2 } function zoomOut() { body.font.pointSize = body.font.pointSize - 2 } } diff --git a/src/mauikit.cpp b/src/mauikit.cpp index 13f4448..f62f643 100644 --- a/src/mauikit.cpp +++ b/src/mauikit.cpp @@ -1,233 +1,234 @@ /* * Copyright 2018 Camilo Higuita * * 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, 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 Library 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 "mauikit.h" #include #include "handy.h" #include "mauimodel.h" #include "mauilist.h" #include "pathlist.h" #include "mauiapp.h" #include "fmstatic.h" #ifdef COMPONENT_ACCOUNTS #include "mauiaccounts.h" #endif #ifdef COMPONENT_FM #include "fm.h" #include "placeslist.h" #include "fmlist.h" #endif #ifdef COMPONENT_TAGGING #include "tagsmodel.h" #include "tagslist.h" #endif #ifdef COMPONENT_STORE #include "storemodel.h" #include "storelist.h" #endif #ifdef COMPONENT_EDITOR #include "documenthandler.h" #endif #ifdef Q_OS_ANDROID #include "mauiandroid.h" #elif defined Q_OS_LINUX #include "mauikde.h" #endif #if defined MAUIKIT_STYLE #include #include #endif QUrl MauiKit::componentUrl(const QString &fileName) const { #ifdef MAUI_APP return QUrl(QStringLiteral("qrc:/maui/kit/") + fileName); #else return QUrl(resolveFileUrl(fileName)); #endif } void MauiKit::registerTypes(const char *uri) { Q_ASSERT(uri == QLatin1String("org.kde.mauikit")); qmlRegisterSingletonType(componentUrl(QStringLiteral("Style.qml")), uri, 1, 0, "Style"); qmlRegisterType(componentUrl(QStringLiteral("ToolBar.qml")), uri, 1, 0, "ToolBar"); qmlRegisterType(componentUrl(QStringLiteral("ApplicationWindow.qml")), uri, 1, 0, "ApplicationWindow"); qmlRegisterType(componentUrl(QStringLiteral("Page.qml")), uri, 1, 0, "Page"); qmlRegisterType(componentUrl(QStringLiteral("ShareDialog.qml")), uri, 1, 0, "ShareDialog"); qmlRegisterType(componentUrl(QStringLiteral("OpenWithDialog.qml")), uri, 1, 0, "OpenWithDialog"); qmlRegisterType(componentUrl(QStringLiteral("PieButton.qml")), uri, 1, 0, "PieButton"); qmlRegisterType(componentUrl(QStringLiteral("SideBar.qml")), uri, 1, 0, "SideBar"); qmlRegisterType(componentUrl(QStringLiteral("AbstractSideBar.qml")), uri, 1, 0, "AbstractSideBar"); qmlRegisterType(componentUrl(QStringLiteral("Holder.qml")), uri, 1, 0, "Holder"); qmlRegisterType(componentUrl(QStringLiteral("GlobalDrawer.qml")), uri, 1, 0, "GlobalDrawer"); qmlRegisterType(componentUrl(QStringLiteral("ListDelegate.qml")), uri, 1, 0, "ListDelegate"); qmlRegisterType(componentUrl(QStringLiteral("ListBrowserDelegate.qml")), uri, 1, 0, "ListBrowserDelegate"); qmlRegisterType(componentUrl(QStringLiteral("SwipeItemDelegate.qml")), uri, 1, 0, "SwipeItemDelegate"); qmlRegisterType(componentUrl(QStringLiteral("SwipeBrowserDelegate.qml")), uri, 1, 0, "SwipeBrowserDelegate"); qmlRegisterType(componentUrl(QStringLiteral("ItemDelegate.qml")), uri, 1, 0, "ItemDelegate"); qmlRegisterType(componentUrl(QStringLiteral("GridBrowserDelegate.qml")), uri, 1, 0, "GridBrowserDelegate"); qmlRegisterType(componentUrl(QStringLiteral("SelectionBar.qml")), uri, 1, 0, "SelectionBar"); qmlRegisterType(componentUrl(QStringLiteral("LabelDelegate.qml")), uri, 1, 0, "LabelDelegate"); qmlRegisterType(componentUrl(QStringLiteral("NewDialog.qml")), uri, 1, 0, "NewDialog"); qmlRegisterType(componentUrl(QStringLiteral("Dialog.qml")), uri, 1, 0, "Dialog"); qmlRegisterType(componentUrl(QStringLiteral("AboutDialog.qml")), uri, 1, 0, "AboutDialog"); qmlRegisterType(componentUrl(QStringLiteral("Popup.qml")), uri, 1, 0, "Popup"); qmlRegisterType(componentUrl(QStringLiteral("TextField.qml")), uri, 1, 0, "TextField"); qmlRegisterType(componentUrl(QStringLiteral("Badge.qml")), uri, 1, 0, "Badge"); qmlRegisterType(componentUrl(QStringLiteral("GridView.qml")), uri, 1, 0, "GridView"); qmlRegisterType(componentUrl(QStringLiteral("ColorsBar.qml")), uri, 1, 0, "ColorsBar"); qmlRegisterType(componentUrl(QStringLiteral("ImageViewer.qml")), uri, 1, 0, "ImageViewer"); qmlRegisterType(componentUrl(QStringLiteral("TabBar.qml")), uri, 1, 0, "TabBar"); qmlRegisterType(componentUrl(QStringLiteral("TabButton.qml")), uri, 1, 0, "TabButton"); qmlRegisterType(componentUrl(QStringLiteral("ActionGroup.qml")), uri, 1, 0, "ActionGroup"); qmlRegisterType(componentUrl(QStringLiteral("ActionSideBar.qml")), uri, 1, 0, "ActionSideBar"); qmlRegisterType(componentUrl(QStringLiteral("ToolActions.qml")), uri, 1, 0, "ToolActions"); qmlRegisterType(componentUrl(QStringLiteral("ToolButtonMenu.qml")), uri, 1, 0, "ToolButtonMenu"); qmlRegisterType(componentUrl(QStringLiteral("ListItemTemplate.qml")), uri, 1, 0, "ListItemTemplate"); qmlRegisterType(componentUrl(QStringLiteral("GridItemTemplate.qml")), uri, 1, 0, "GridItemTemplate"); qmlRegisterType(componentUrl(QStringLiteral("FloatingButton.qml")), uri, 1, 0, "FloatingButton"); qmlRegisterType(componentUrl(QStringLiteral("PathBar.qml")), uri, 1, 0, "PathBar"); qmlRegisterType(uri, 1, 0, "PathList"); /** 1.1 **/ qmlRegisterType(componentUrl(QStringLiteral("labs/SelectionBar.qml")), uri, 1, 1, "SelectionBar"); /** STORE CONTROLS, MODELS AND INTERFACES **/ #ifdef COMPONENT_STORE qmlRegisterType("StoreList", 1, 0, "StoreList"); qmlRegisterType("StoreModel", 1, 0, "StoreModel"); qmlRegisterType(componentUrl(QStringLiteral("private/StoreDelegate.qml")), uri, 1, 0, "StoreDelegate"); qmlRegisterType(componentUrl(QStringLiteral("Store.qml")), uri, 1, 0, "Store"); #endif /** BROWSING CONTROLS **/ qmlRegisterType(componentUrl(QStringLiteral("ListBrowser.qml")), uri, 1, 0, "ListBrowser"); qmlRegisterType(componentUrl(QStringLiteral("GridBrowser.qml")), uri, 1, 0, "GridBrowser"); /** FM CONTROLS, MODELS AND INTERFACES **/ #ifdef COMPONENT_FM qmlRegisterType(uri, 1, 0, "PlacesList"); qmlRegisterType(uri, 1, 0, "FMList"); qmlRegisterSingletonType(uri, 1, 0, "FM", [](QQmlEngine *engine, QJSEngine *scriptEngine) -> QObject* { Q_UNUSED(engine) Q_UNUSED(scriptEngine) return new FMStatic; }); qmlRegisterType(componentUrl(QStringLiteral("FileBrowser.qml")), uri, 1, 0, "FileBrowser"); qmlRegisterType(componentUrl(QStringLiteral("PlacesSidebar.qml")), uri, 1, 0, "PlacesSidebar"); qmlRegisterType(componentUrl(QStringLiteral("PlacesListBrowser.qml")), uri, 1, 0, "PlacesListBrowser"); qmlRegisterType(componentUrl(QStringLiteral("FilePreviewer.qml")), uri, 1, 0, "FilePreviewer"); qmlRegisterType(componentUrl(QStringLiteral("FileDialog.qml")), uri, 1, 0, "FileDialog"); #endif #ifdef COMPONENT_EDITOR /** EDITOR CONTROLS **/ qmlRegisterType(uri, 1, 0, "DocumentHandler"); qmlRegisterType(); + qmlRegisterAnonymousType(uri, 1); qmlRegisterType(componentUrl(QStringLiteral("Editor.qml")), uri, 1, 0, "Editor"); qmlRegisterType(componentUrl(QStringLiteral("private/DocumentPreview.qml")), uri, 1, 0, "DocumentPreview"); #endif /** PLATFORMS SPECIFIC CONTROLS **/ #ifdef Q_OS_ANDROID qmlRegisterSingletonType(uri, 1, 0, "Android", [](QQmlEngine *engine, QJSEngine *scriptEngine) -> QObject* { Q_UNUSED(engine) Q_UNUSED(scriptEngine) return new MAUIAndroid; }); #elif defined Q_OS_LINUX qmlRegisterType(componentUrl(QStringLiteral("Terminal.qml")), uri, 1, 0, "Terminal"); qmlRegisterSingletonType(uri, 1, 0, "KDE", [](QQmlEngine *engine, QJSEngine *scriptEngine) -> QObject* { Q_UNUSED(engine) Q_UNUSED(scriptEngine) return new MAUIKDE; }); #elif defined Q_OS_WIN32 //here window platform integration interfaces #endif /** DATA MODELING TEMPLATED INTERFACES **/ qmlRegisterType(); //ABSTRACT BASE LIST qmlRegisterType(uri, 1, 0, "BaseModel"); //BASE MODEL /** TAGGING INTERFACES AND MODELS **/ qmlRegisterType("TagsList", 1, 0, "TagsList"); qmlRegisterType("TagsModel", 1, 0, "TagsModel"); qmlRegisterType(componentUrl(QStringLiteral("private/TagList.qml")), uri, 1, 0, "TagList"); qmlRegisterType(componentUrl(QStringLiteral("TagsBar.qml")), uri, 1, 0, "TagsBar"); qmlRegisterType(componentUrl(QStringLiteral("TagsDialog.qml")), uri, 1, 0, "TagsDialog"); /** MAUI APPLICATION SPECIFIC PROPS **/ #ifdef COMPONENT_ACCOUNTS qmlRegisterType(); qmlRegisterType(componentUrl(QStringLiteral("SyncDialog.qml")), uri, 1, 0, "SyncDialog"); //to be rename to accountsDialog #endif qmlRegisterUncreatableType(uri, 1, 0, "App", "Cannot be created App"); /** HELPERS **/ qmlRegisterSingletonType(uri, 1, 0, "Handy", [](QQmlEngine *engine, QJSEngine *scriptEngine) -> QObject* { Q_UNUSED(engine) Q_UNUSED(scriptEngine) return new Handy; }); #if defined MAUIKIT_STYLE this->initResources(); #endif qmlProtectModule(uri, 1); } void MauiKit::initResources() { #if defined QICON_H && defined QQUICKSTYLE_H Q_INIT_RESOURCE(mauikit); #ifdef ICONS_PNG Q_INIT_RESOURCE(icons_png); #else Q_INIT_RESOURCE(icons); #endif Q_INIT_RESOURCE(style); QIcon::setThemeSearchPaths({":/icons/luv-icon-theme"}); QIcon::setThemeName("Luv"); QQuickStyle::setStyle(":/style"); #endif } //#include "moc_mauikit.cpp" diff --git a/src/utils/editor/documenthandler.cpp b/src/utils/editor/documenthandler.cpp index 48ccfdc..91ffbb2 100644 --- a/src/utils/editor/documenthandler.cpp +++ b/src/utils/editor/documenthandler.cpp @@ -1,632 +1,701 @@ /**************************************************************************** * * ** Copyright (C) 2017 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the examples of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:BSD$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** BSD License Usage ** Alternatively, you may use this file under the terms of the BSD license ** as follows: ** ** "Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions are ** met: ** * Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** * Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in ** the documentation and/or other materials provided with the ** distribution. ** * Neither the name of The Qt Company Ltd nor the names of its ** contributors may be used to endorse or promote products derived ** from this software without specific prior written permission. ** ** ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "documenthandler.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "fmh.h" #include "utils.h" #ifdef STATIC_MAUIKIT #include #include #include #include #else #include #include #include #include #endif /** * Global Variables */ KSyntaxHighlighting::Repository *DocumentHandler::m_repository = nullptr; int DocumentHandler::m_instanceCount = 0; -Alerts::Alerts(QObject* parent) -{ -} +Alerts::Alerts(QObject* parent) : QAbstractListModel(parent) +{} QVariant Alerts::data(const QModelIndex& index, int role) const { + if(role == ROLES::ALERT) + return QVariant::fromValue(this->m_alerts.at(index.row())); + return QVariant(); } int Alerts::rowCount(const QModelIndex& parent) const { + if (parent.isValid()) + return 0; + return this->m_alerts.count(); } +QHash Alerts::roleNames() const +{ + return {{ROLES::ALERT, "alert"}}; +} + +void Alerts::append(DocumentAlert *alert) +{ + const auto index = this->rowCount(); + beginInsertRows(QModelIndex(), index, index); + + // watch out for when the alert is done: such as when an action is triggered + connect(alert, &DocumentAlert::done, [&](int index) + { + this->beginRemoveRows(QModelIndex(), index, index); + auto item = this->m_alerts.takeAt(index); + item->deleteLater(); + this->endRemoveRows(); + }); + + alert->setIndex(index); + this->m_alerts << alert; + endInsertRows(); +} + void FileLoader::loadFile(const QUrl& url) { if (FMH::fileExists(url)) { QFile file(url.toLocalFile()); if (file.open(QFile::ReadOnly)) { - qDebug()<< "LOAD FILE OPENDED << "; const auto array = file.readAll(); const bool isHtml = QFileInfo(url.toLocalFile()).suffix().contains(QLatin1String("htm")); QTextCodec *codec = isHtml ? QTextCodec::codecForHtml(array) : QTextCodec::codecForUtfText(array, QTextCodec::codecForLocale()); emit this->fileReady(codec->toUnicode(array), url); } } } +DocumentAlert * DocumentHandler::externallyModifiedAlert() +{ + auto alert = new DocumentAlert(tr("File changed externally"), tr("You can reload the file or save your changes now"), DocumentAlert::WARNING_LEVEL); + + const auto reloadAction = [&]() + { + emit this->loadFile(this->fileUrl()); + }; + + const auto autoReloadAction = [&]() + { + this->setAutoReload(true); + emit this->loadFile(this->fileUrl()); + }; + + const auto ignoreAction = [&]() + {}; + + alert->setActions({{tr("Reload"), reloadAction}, {tr("Auto Reload"), autoReloadAction}, {tr("Ignore"), ignoreAction}}); + return alert; +} + +DocumentAlert * DocumentHandler::missingAlert() +{ + auto alert = new DocumentAlert(tr("Your file was removed"), tr("This file does not longer exists in your local storage, however you can save it again"), DocumentAlert::DANGER_LEVEL); + + const auto saveAction = [&]() + { + this->saveAs(this->fileUrl()); + }; + + alert->setActions({{tr("Save"), saveAction}}); + return alert; +} + DocumentHandler::DocumentHandler(QObject *parent) : QObject(parent) , m_document(nullptr) , m_watcher(new QFileSystemWatcher(this)) , m_cursorPosition(-1) , m_selectionStart(0) , m_selectionEnd(0) , m_highlighter(new KSyntaxHighlighting::SyntaxHighlighter(this)) , m_alerts(new Alerts(this)) { ++m_instanceCount; //start file loader thread implementation - FileLoader *m_loader = new FileLoader; - m_loader->moveToThread(&m_worker); - connect(&m_worker, &QThread::finished, m_loader, &QObject::deleteLater); - connect(this, &DocumentHandler::loadFile, m_loader, &FileLoader::loadFile); - connect(m_loader, &FileLoader::fileReady, [&](QString array, QUrl url) - { - if (QTextDocument *doc = textDocument()) - doc->setModified(false); - - this->isRich = QFileInfo(url.toLocalFile()).suffix().contains(QLatin1String("rtf"));// - emit this->isRichChanged(); - - this->setText(array); - - this->setFormatName(DocumentHandler::getLanguageNameFromFileName(url)); - - emit this->loaded(url); - - reset(); - }); - - m_worker.start(); - + { + FileLoader *m_loader = new FileLoader; + m_loader->moveToThread(&m_worker); + connect(&m_worker, &QThread::finished, m_loader, &QObject::deleteLater); + connect(this, &DocumentHandler::loadFile, m_loader, &FileLoader::loadFile); + connect(m_loader, &FileLoader::fileReady, [&](QString array, QUrl url) + { + if (QTextDocument *doc = textDocument()) + doc->setModified(false); + + this->isRich = QFileInfo(url.toLocalFile()).suffix().contains(QLatin1String("rtf"));// + emit this->isRichChanged(); + + this->setText(array); + + this->setFormatName(DocumentHandler::getLanguageNameFromFileName(url)); + + emit this->loaded(url); + + reset(); + }); + m_worker.start(); + } //end file loader thread implementation connect(this->m_watcher, &QFileSystemWatcher::fileChanged, [&](QString url) { if(this->fileUrl() == QUrl::fromLocalFile(url)) { + //THE FILE WAS REMOVED + if(!FMH::fileExists(this->fileUrl())) + { + this->m_alerts->append(DocumentHandler::missingAlert()); + return; + } + + //THE FILE CHANGED BUT STILL EXISTS LOCALLY if(m_internallyModified) { m_internallyModified = false; return; } - this->setExternallyModified(true); - + this->setExternallyModified(true); if(!this->m_autoReload) { - /*this->m_warning.setWarning("File changed externally", "You can reload the file or save your changes now"); - emit this->warningChanged();*/ + this->m_alerts->append(DocumentHandler::externallyModifiedAlert()); return; } emit this->loadFile(this->fileUrl()); } }); } DocumentHandler::~DocumentHandler() { m_worker.quit(); m_worker.wait(); --m_instanceCount; if (!m_instanceCount) { delete DocumentHandler::m_repository; DocumentHandler::m_repository = nullptr; } } void DocumentHandler::setText(const QString &text) { if (text != m_text) { m_text = text; emit textChanged(); } } bool DocumentHandler::getAutoReload() const { return this->m_autoReload; } void DocumentHandler::setAutoReload(const bool& value) { if(value == this->m_autoReload) return; this->m_autoReload = value; emit this->autoReloadChanged(); } bool DocumentHandler::getExternallyModified() const { return this->m_externallyModified; } void DocumentHandler::setExternallyModified(const bool& value) { if(value == this->m_externallyModified) return; this->m_externallyModified = value; emit this->externallyModifiedChanged(); } void DocumentHandler::setStyle() { if (!DocumentHandler::m_repository) DocumentHandler::m_repository = new KSyntaxHighlighting::Repository(); const auto isDark = UTIL::isDark(this->m_backgroundColor); const auto style = DocumentHandler::m_repository->defaultTheme(isDark ?KSyntaxHighlighting::Repository::DarkTheme : KSyntaxHighlighting::Repository::LightTheme); m_highlighter->setTheme(style); m_highlighter->setDefinition(DocumentHandler::m_repository->definitionForName(this->m_formatName)); } QString DocumentHandler::formatName() const { return this->m_formatName; } void DocumentHandler::setFormatName(const QString& formatName) { if (m_formatName != formatName) { m_formatName = formatName; emit this->formatNameChanged(); this->setStyle(); } } QColor DocumentHandler::getBackgroundColor() const { return this->m_backgroundColor; } void DocumentHandler::setBackgroundColor(const QColor& color) { if(this->m_backgroundColor == color) return; this->m_backgroundColor = color; emit this->backgroundColorChanged(); this->setStyle(); } Alerts *DocumentHandler::getAlerts() const { return this->m_alerts; } QQuickTextDocument *DocumentHandler::document() const { return m_document; } void DocumentHandler::setDocument(QQuickTextDocument *document) { if (document == m_document) return; m_document = document; emit documentChanged(); m_highlighter->setDocument(m_document->textDocument()); } int DocumentHandler::cursorPosition() const { return m_cursorPosition; } void DocumentHandler::setCursorPosition(int position) { if (position == m_cursorPosition) return; m_cursorPosition = position; reset(); emit cursorPositionChanged(); } int DocumentHandler::selectionStart() const { return m_selectionStart; } void DocumentHandler::setSelectionStart(int position) { if (position == m_selectionStart) return; m_selectionStart = position; emit selectionStartChanged(); } int DocumentHandler::selectionEnd() const { return m_selectionEnd; } void DocumentHandler::setSelectionEnd(int position) { if (position == m_selectionEnd) return; m_selectionEnd = position; emit selectionEndChanged(); } QString DocumentHandler::fontFamily() const { QTextCursor cursor = textCursor(); if (cursor.isNull()) return QString(); QTextCharFormat format = cursor.charFormat(); return format.font().family(); } void DocumentHandler::setFontFamily(const QString &family) { QTextCharFormat format; format.setFontFamily(family); mergeFormatOnWordOrSelection(format); emit fontFamilyChanged(); } QColor DocumentHandler::textColor() const { QTextCursor cursor = textCursor(); if (cursor.isNull()) return QColor(Qt::black); QTextCharFormat format = cursor.charFormat(); return format.foreground().color(); } void DocumentHandler::setTextColor(const QColor &color) { QTextCharFormat format; format.setForeground(QBrush(color)); mergeFormatOnWordOrSelection(format); emit textColorChanged(); } Qt::Alignment DocumentHandler::alignment() const { QTextCursor cursor = textCursor(); if (cursor.isNull()) return Qt::AlignLeft; return textCursor().blockFormat().alignment(); } void DocumentHandler::setAlignment(Qt::Alignment alignment) { QTextBlockFormat format; format.setAlignment(alignment); QTextCursor cursor = textCursor(); cursor.mergeBlockFormat(format); emit alignmentChanged(); } bool DocumentHandler::bold() const { QTextCursor cursor = textCursor(); if (cursor.isNull()) return false; return textCursor().charFormat().fontWeight() == QFont::Bold; } void DocumentHandler::setBold(bool bold) { QTextCharFormat format; format.setFontWeight(bold ? QFont::Bold : QFont::Normal); mergeFormatOnWordOrSelection(format); emit boldChanged(); } bool DocumentHandler::uppercase() const { QTextCursor cursor = textCursor(); if (cursor.isNull()) return false; return textCursor().charFormat().fontCapitalization() == QFont::AllUppercase; } void DocumentHandler::setUppercase(bool uppercase) { QTextCharFormat format; format.setFontCapitalization(uppercase ? QFont::AllUppercase : QFont::AllLowercase); mergeFormatOnWordOrSelection(format); emit uppercaseChanged(); } bool DocumentHandler::italic() const { QTextCursor cursor = textCursor(); if (cursor.isNull()) return false; return textCursor().charFormat().fontItalic(); } void DocumentHandler::setItalic(bool italic) { QTextCharFormat format; format.setFontItalic(italic); mergeFormatOnWordOrSelection(format); emit italicChanged(); } bool DocumentHandler::underline() const { QTextCursor cursor = textCursor(); if (cursor.isNull()) return false; return textCursor().charFormat().fontUnderline(); } void DocumentHandler::setUnderline(bool underline) { QTextCharFormat format; format.setFontUnderline(underline); mergeFormatOnWordOrSelection(format); emit underlineChanged(); } bool DocumentHandler::getIsRich() const { return this->isRich; } int DocumentHandler::fontSize() const { QTextCursor cursor = textCursor(); if (cursor.isNull()) return 0; QTextCharFormat format = cursor.charFormat(); return format.font().pointSize(); } void DocumentHandler::setFontSize(int size) { if (size <= 0) return; QTextCursor cursor = textCursor(); if (cursor.isNull()) return; if (!cursor.hasSelection()) cursor.select(QTextCursor::WordUnderCursor); if (cursor.charFormat().property(QTextFormat::FontPointSize).toInt() == size) return; QTextCharFormat format; format.setFontPointSize(size); mergeFormatOnWordOrSelection(format); emit fontSizeChanged(); } QString DocumentHandler::fileName() const { const QString filePath = QQmlFile::urlToLocalFileOrQrc(m_fileUrl); const QString fileName = QFileInfo(filePath).fileName(); if (fileName.isEmpty()) return QStringLiteral("untitled.txt"); return fileName; } QString DocumentHandler::fileType() const { return QFileInfo(fileName()).suffix(); } QUrl DocumentHandler::fileUrl() const { return m_fileUrl; } void DocumentHandler::load(const QUrl &fileUrl) { qDebug()<< "TRYING TO LOAD FILE << " << fileUrl; if (fileUrl == m_fileUrl) return; QQmlEngine *engine = qmlEngine(this); if (!engine) { qWarning() << "load() called before DocumentHandler has QQmlEngine"; return; } const QUrl path = QQmlFileSelector::get(engine)->selector()->select(fileUrl); const QString fileName = QQmlFile::urlToLocalFileOrQrc(path); m_fileUrl = fileUrl; emit fileUrlChanged(); this->m_watcher->addPath(m_fileUrl.toLocalFile()); emit this->loadFile(m_fileUrl); } void DocumentHandler::saveAs(const QUrl &fileUrl) { QTextDocument *doc = textDocument(); if (!doc) return; const QString filePath = fileUrl.toLocalFile(); const bool isHtml = QFileInfo(filePath).suffix().contains(QLatin1String("htm")); this->m_internallyModified = true; QFile file(filePath); if (!file.open(QFile::WriteOnly | QFile::Truncate | (isHtml ? QFile::NotOpen : QFile::Text))) { emit error(tr("Cannot save: ") + file.errorString()); } else { QTextStream out(&file); out.setCodec("UTF-8"); out << (isHtml ? doc->toHtml() : doc->toPlainText()).toUtf8(); file.close(); if (fileUrl == m_fileUrl) return; m_fileUrl = fileUrl; emit fileUrlChanged(); doc->setModified(false); } } const QString DocumentHandler::getLanguageNameFromFileName(const QUrl& fileName) { if (!DocumentHandler::m_repository) DocumentHandler::m_repository = new KSyntaxHighlighting::Repository(); return DocumentHandler::m_repository->definitionForFileName(fileName.toString()).name(); } const QStringList DocumentHandler::getLanguageNameList() { if (!DocumentHandler::m_repository) m_repository = new KSyntaxHighlighting::Repository(); const auto definitions = DocumentHandler::m_repository->definitions(); return std::accumulate(definitions.constBegin(), definitions.constEnd(), QStringList(), [](QStringList &languages, const auto &definition ) -> QStringList { languages.append(definition.name()); return languages; }); } void DocumentHandler::reset() { emit fontFamilyChanged(); emit alignmentChanged(); emit boldChanged(); emit italicChanged(); emit underlineChanged(); emit fontSizeChanged(); emit textColorChanged(); } QTextCursor DocumentHandler::textCursor() const { QTextDocument *doc = textDocument(); if (!doc) return QTextCursor(); QTextCursor cursor = QTextCursor(doc); if (m_selectionStart != m_selectionEnd) { cursor.setPosition(m_selectionStart); cursor.setPosition(m_selectionEnd, QTextCursor::KeepAnchor); } else { cursor.setPosition(m_cursorPosition); } return cursor; } QTextDocument *DocumentHandler::textDocument() const { if (!m_document) return nullptr; return m_document->textDocument(); } void DocumentHandler::mergeFormatOnWordOrSelection(const QTextCharFormat &format) { QTextCursor cursor = textCursor(); if (!cursor.hasSelection()) cursor.select(QTextCursor::WordUnderCursor); cursor.mergeCharFormat(format); } diff --git a/src/utils/editor/documenthandler.h b/src/utils/editor/documenthandler.h index 3f2a068..b7a8ea7 100644 --- a/src/utils/editor/documenthandler.h +++ b/src/utils/editor/documenthandler.h @@ -1,302 +1,350 @@ /**************************************************************************** * * ** Copyright (C) 2017 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the examples of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:BSD$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** BSD License Usage ** Alternatively, you may use this file under the terms of the BSD license ** as follows: ** ** "Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions are ** met: ** * Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** * Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in ** the documentation and/or other materials provided with the ** distribution. ** * Neither the name of The Qt Company Ltd nor the names of its ** contributors may be used to endorse or promote products derived ** from this software without specific prior written permission. ** ** ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #ifndef DOCUMENTHANDLER_H #define DOCUMENTHANDLER_H #include #include #include #include #include #include QT_BEGIN_NAMESPACE class QFileSystemWatcher; class QTextDocument; class QQuickTextDocument; QT_END_NAMESPACE namespace KSyntaxHighlighting { class Repository; class SyntaxHighlighter; } -class DocumentAlert + +struct AlertAction { - Q_GADGET - Q_PROPERTY(bool hasWarning MEMBER m_hasWarning) - Q_PROPERTY(QString title MEMBER m_title) - Q_PROPERTY(QString body MEMBER m_body) - bool m_hasWarning = false; + QString label; + std::function action; +} ; + +class DocumentAlert : public QObject +{ + Q_OBJECT + Q_PROPERTY(QString title MEMBER m_title CONSTANT FINAL) + Q_PROPERTY(QString body MEMBER m_body CONSTANT FINAL) + Q_PROPERTY(uint level MEMBER m_level CONSTANT FINAL) + +public: + enum LEVEL : uint + { + INFO_LEVEL = 0, + WARNING_LEVEL = 1, + DANGER_LEVEL = 2 + }; + + DocumentAlert(const QString &title, const QString &body, const uint &level, QObject *parent = nullptr) : QObject(parent) + { + this->m_title = title; + this->m_body = body; + this->m_level = level; + } + + void setIndex(const int &index) + { + this->m_index = index; + } + + void setActions(QVector actions) + { + this->m_actions = actions; + } + +private: QString m_title; QString m_body; + uint m_level; + int m_index = -1; -public: -// DocumentWarning(const QString &title, const QString &body) -// { -// setWarning(title, body); -// } + QVector m_actions; - void setWarning(const QString &title, const QString &body) +public slots: + QStringList actionLabels() const { - m_title = title; - m_body = body; - m_hasWarning = true; + return std::accumulate(this->m_actions.constBegin(), this->m_actions.constEnd(), QStringList(), [](QStringList &labels, const AlertAction &action) -> QStringList + { + labels << action.label; + return labels; + }); } - void accept() - {} - void reject() - {} + + void triggerAction(const int &index) + { + this->m_actions.takeAt(index).action(); + emit this->done(m_index); + } + +signals: + void done(int index); }; -Q_DECLARE_METATYPE(DocumentAlert) class Alerts : public QAbstractListModel { - Q_OBJECT + Q_OBJECT public: + enum ROLES : uint + { + ALERT = Qt::DisplayRole + 1 + }; + explicit Alerts(QObject *parent = nullptr); QVariant data(const QModelIndex & index, int role) const override final; int rowCount(const QModelIndex &parent = QModelIndex()) const override final; + QHash roleNames() const override; + + void append(DocumentAlert *alert); private: QVector m_alerts; }; class FileLoader : public QObject { Q_OBJECT public slots: void loadFile(const QUrl &url); signals: void fileReady(QString array, QUrl url); }; class DocumentHandler : public QObject { Q_OBJECT Q_PROPERTY(QQuickTextDocument *document READ document WRITE setDocument NOTIFY documentChanged) Q_PROPERTY(int cursorPosition READ cursorPosition WRITE setCursorPosition NOTIFY cursorPositionChanged) Q_PROPERTY(int selectionStart READ selectionStart WRITE setSelectionStart NOTIFY selectionStartChanged) Q_PROPERTY(int selectionEnd READ selectionEnd WRITE setSelectionEnd NOTIFY selectionEndChanged) Q_PROPERTY(QColor textColor READ textColor WRITE setTextColor NOTIFY textColorChanged) Q_PROPERTY(QString fontFamily READ fontFamily WRITE setFontFamily NOTIFY fontFamilyChanged) Q_PROPERTY(Qt::Alignment alignment READ alignment WRITE setAlignment NOTIFY alignmentChanged) Q_PROPERTY(bool bold READ bold WRITE setBold NOTIFY boldChanged) Q_PROPERTY(bool uppercase READ uppercase WRITE setUppercase NOTIFY uppercaseChanged) Q_PROPERTY(bool italic READ italic WRITE setItalic NOTIFY italicChanged) Q_PROPERTY(bool underline READ underline WRITE setUnderline NOTIFY underlineChanged) Q_PROPERTY(bool isRich READ getIsRich NOTIFY isRichChanged) Q_PROPERTY(int fontSize READ fontSize WRITE setFontSize NOTIFY fontSizeChanged) Q_PROPERTY(QString fileName READ fileName NOTIFY fileUrlChanged) Q_PROPERTY(QString fileType READ fileType NOTIFY fileUrlChanged) Q_PROPERTY(QUrl fileUrl READ fileUrl NOTIFY fileUrlChanged) Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged) Q_PROPERTY(bool externallyModified READ getExternallyModified WRITE setExternallyModified NOTIFY externallyModifiedChanged) Q_PROPERTY(bool autoReload READ getAutoReload WRITE setAutoReload NOTIFY autoReloadChanged) Q_PROPERTY(QString formatName READ formatName WRITE setFormatName NOTIFY formatNameChanged) Q_PROPERTY(Alerts *alerts READ getAlerts CONSTANT FINAL) Q_PROPERTY(QColor backgroundColor READ getBackgroundColor WRITE setBackgroundColor NOTIFY backgroundColorChanged) public: explicit DocumentHandler(QObject *parent = nullptr); ~DocumentHandler(); QQuickTextDocument *document() const; void setDocument(QQuickTextDocument *document); int cursorPosition() const; void setCursorPosition(int position); int selectionStart() const; void setSelectionStart(int position); int selectionEnd() const; void setSelectionEnd(int position); QString fontFamily() const; void setFontFamily(const QString &family); QColor textColor() const; void setTextColor(const QColor &color); Qt::Alignment alignment() const; void setAlignment(Qt::Alignment alignment); bool bold() const; void setBold(bool bold); bool uppercase() const; void setUppercase(bool uppercase); bool italic() const; void setItalic(bool italic); bool underline() const; void setUnderline(bool underline); bool getIsRich() const; int fontSize() const; void setFontSize(int size); QString fileName() const; QString fileType() const; QUrl fileUrl() const; inline QString text() const { return m_text; } void setText(const QString &text); bool getAutoReload() const; void setAutoReload(const bool &value); bool getExternallyModified() const; void setExternallyModified(const bool &value); QString formatName() const; void setFormatName(const QString &formatName); QColor getBackgroundColor() const; void setBackgroundColor(const QColor &color); Alerts *getAlerts() const; public slots: void load(const QUrl &fileUrl); void saveAs(const QUrl &fileUrl); static const QStringList getLanguageNameList(); static const QString getLanguageNameFromFileName(const QUrl &fileName); signals: void documentChanged(); void cursorPositionChanged(); void selectionStartChanged(); void selectionEndChanged(); void fontFamilyChanged(); void textColorChanged(); void alignmentChanged(); void boldChanged(); void uppercaseChanged(); void italicChanged(); void underlineChanged(); void isRichChanged(); void fontSizeChanged(); void textChanged(); void fileUrlChanged(); void loaded(const QUrl &url); void error(const QString &message); void loadFile(QUrl url); void autoReloadChanged(); void externallyModifiedChanged(); void backgroundColorChanged(); void formatNameChanged() const; private: void reset(); void setStyle(); QTextCursor textCursor() const; QTextDocument *textDocument() const; void mergeFormatOnWordOrSelection(const QTextCharFormat &format); QQuickTextDocument *m_document; QFileSystemWatcher *m_watcher; int m_cursorPosition; int m_selectionStart; int m_selectionEnd; bool isRich = false; QFont m_font; int m_fontSize; QUrl m_fileUrl; QThread m_worker; QString m_text; bool m_autoReload = false; bool m_externallyModified = false; bool m_internallyModified = false; QColor m_backgroundColor; static int m_instanceCount; QString m_formatName; static KSyntaxHighlighting::Repository *m_repository; KSyntaxHighlighting::SyntaxHighlighter *m_highlighter; Alerts *m_alerts; + DocumentAlert * missingAlert(); + DocumentAlert * externallyModifiedAlert(); }; #endif // DOCUMENTHANDLER_H