From 9ad9af9f2596b91e1dd65e71543f75b0644e8283 Mon Sep 17 00:00:00 2001 From: rtk0c Date: Mon, 27 Jun 2022 00:10:58 +0000 Subject: (From git) Initial GUI setup for text document git-svn-id: file:///home/arch/svn/epistmool/trunk@3 71f44415-077c-4ad7-a976-72ddbf76608f --- ui.qt/CMakeLists.txt | 10 ++- ui.qt/CMakeLists.txt.user | 2 +- ui.qt/source/document.cpp | 144 ++++++++++++++++++++++++++--------- ui.qt/source/document.hpp | 68 ++++++++++------- ui.qt/source/fwd.hpp | 3 +- ui.qt/source/main.cpp | 6 +- ui.qt/source/qml/Document.qml | 164 +++++++++++++++++++++++++++++++++++----- ui.qt/source/qml/MainWindow.qml | 11 ++- ui.qt/source/qml/Navigator.qml | 36 +++++++++ 9 files changed, 349 insertions(+), 95 deletions(-) create mode 100644 ui.qt/source/qml/Navigator.qml (limited to 'ui.qt') diff --git a/ui.qt/CMakeLists.txt b/ui.qt/CMakeLists.txt index 7d7624a..c16e915 100644 --- a/ui.qt/CMakeLists.txt +++ b/ui.qt/CMakeLists.txt @@ -4,24 +4,25 @@ project(EpistmoolUI VERSION 0.1 LANGUAGES CXX) set(CMAKE_AUTOMOC ON) set(CMAKE_CXX_STANDARD_REQUIRED ON) -find_package(Qt6 6.2 COMPONENTS Quick REQUIRED) +find_package(Qt6 6.2 COMPONENTS Widgets Quick REQUIRED) qt_add_executable(appEpistmoolUI source/main.cpp source/fwd.hpp - source/document.hpp source/document.cpp source/knowledgefragment.hpp source/knowledgefragment.cpp source/keyword.hpp source/keyword.cpp ) - qt_add_qml_module(appEpistmoolUI URI EpistmoolUI VERSION 1.0 + SOURCES + source/document.hpp source/document.cpp QML_FILES source/qml/MainWindow.qml - source/qml/GoToKnowledge.qml + source/qml/Navigator.qml source/qml/Document.qml + source/qml/GoToKnowledge.qml ) set_target_properties(appEpistmoolUI PROPERTIES @@ -39,5 +40,6 @@ PRIVATE ) target_link_libraries(appEpistmoolUI PRIVATE + Qt6::Widgets Qt6::Quick ) diff --git a/ui.qt/CMakeLists.txt.user b/ui.qt/CMakeLists.txt.user index 69693ad..8611bff 100644 --- a/ui.qt/CMakeLists.txt.user +++ b/ui.qt/CMakeLists.txt.user @@ -1,6 +1,6 @@ - + EnvironmentId diff --git a/ui.qt/source/document.cpp b/ui.qt/source/document.cpp index b418b0d..377913b 100644 --- a/ui.qt/source/document.cpp +++ b/ui.qt/source/document.cpp @@ -1,35 +1,29 @@ #include "document.hpp" -#include +#include +#include +#include #include -DocumentBlock::DocumentBlock(QObject* parent) +DocumentHandler::DocumentHandler(QObject* parent) : QObject{ parent } { } -DocumentModel* DocumentBlock::getModel() const -{ - return mModel; -} - -void DocumentBlock::setModel(DocumentModel* newModel) -{ - mModel = newModel; -} - -QQuickTextDocument* DocumentBlock::getDoc() const +QQuickTextDocument* DocumentHandler::getDoc() const { return mDoc; } -void DocumentBlock::setDoc(QQuickTextDocument* newDoc) +void DocumentHandler::setDoc(QQuickTextDocument* newDoc) { if (mDoc != newDoc) { - if (mDoc) { - disconnect(mDoc->textDocument(), nullptr, this, nullptr); - } + auto oldDoc = mDoc; mDoc = newDoc; + + if (oldDoc) { + disconnect(oldDoc->textDocument(), nullptr, this, nullptr); + } if (newDoc) { connect(newDoc->textDocument(), &QTextDocument::modificationChanged, this, [&]() { // TODO add a timer to wait for 1 second before updating? @@ -37,42 +31,122 @@ void DocumentBlock::setDoc(QQuickTextDocument* newDoc) emit modificationChanged(); }); } - emit docChanged(); + + emit docChanged(oldDoc); } } -const QDateTime& DocumentBlock::getModifyTime() const +const QDateTime& DocumentHandler::getModifyTime() const { return mModifyTime; } -void DocumentModel::appendBlock(DocumentBlock* block) +int DocumentHandler::getCursorPos() const +{ + return mCursorPos; +} + +void DocumentHandler::setCursorPos(int newCursorPos) { - mBlocks.push_back(block); + if (mCursorPos == newCursorPos) { + return; + } + mCursorPos = newCursorPos; + emit cursorPosChanged(); } -int DocumentModel::rowCount(const QModelIndex& parent) const +int DocumentHandler::getSelectionBegin() const { - return mBlocks.size(); + return mSelectionBegin; } -QVariant DocumentModel::data(const QModelIndex& index, int role) const +void DocumentHandler::setSelectionBegin(int newSelectionBegin) { - if (index.row() < 0 || index.row() >= mBlocks.size()) { - return QVariant(); + if (mSelectionBegin == newSelectionBegin) { + return; } + mSelectionBegin = newSelectionBegin; + emit selectionBeginChanged(); +} - switch (role) { - case Qt::DisplayRole: return QVariant::fromValue(mBlocks[index.row()]); - case ModifyTimeRole: return mBlocks[index.row()]->getModifyTime(); - default: return QVariant(); +int DocumentHandler::getSelectionEnd() const +{ + return mSelectionEnd; +} + +void DocumentHandler::setSelectionEnd(int newSelectionEnd) +{ + if (mSelectionEnd == newSelectionEnd) { + return; } + mSelectionEnd = newSelectionEnd; + emit selectionEndChanged(); } -QHash DocumentModel::roleNames() const +QFont DocumentHandler::getActiveFont() const { - QHash roles; - roles[Qt::DisplayRole] = "display", - roles[ModifyTimeRole] = "modifyTime"; - return roles; + auto cursor = makeTextCursor(); + if (cursor.isNull()) { + return mDoc->textDocument()->defaultFont(); + } + auto format = cursor.charFormat(); + return format.font(); +} + +void DocumentHandler::setActiveFont(const QFont& font) +{ + auto cursor = makeTextCursor(); + if (!cursor.isNull() && cursor.charFormat().font() == font) { + return; + } + + QTextCharFormat format; + format.setFont(font); + mergeFormatOnWordOrSelection(format); + + emit activeFontChanged(); +} + +QColor DocumentHandler::getActiveTextColor() const +{ + auto cursor = makeTextCursor(); + if (cursor.isNull()) { + return QColor(Qt::black); + } + QTextCharFormat format = cursor.charFormat(); + return format.foreground().color(); +} + +void DocumentHandler::setActiveTextColor(const QColor& color) +{ + QTextCharFormat format; + format.setForeground(QBrush(color)); + mergeFormatOnWordOrSelection(format); + emit activeTextColorChanged(); +} + +QTextCursor DocumentHandler::makeTextCursor() const +{ + auto doc = mDoc->textDocument(); + if (!doc) { + return QTextCursor(); + } + + QTextCursor cursor(doc); + if (mSelectionBegin != mSelectionEnd) { + cursor.setPosition(mSelectionBegin); + cursor.setPosition(mSelectionEnd, QTextCursor::KeepAnchor); + } else { + cursor.setPosition(mCursorPos); + } + return cursor; +} + +void DocumentHandler::mergeFormatOnWordOrSelection(const QTextCharFormat& format) +{ + auto cursor = makeTextCursor(); + if (!cursor.hasSelection()) { + cursor.select(QTextCursor::WordUnderCursor); + } + cursor.mergeCharFormat(format); } diff --git a/ui.qt/source/document.hpp b/ui.qt/source/document.hpp index ba11e26..5ef1bba 100644 --- a/ui.qt/source/document.hpp +++ b/ui.qt/source/document.hpp @@ -6,57 +6,67 @@ #include #include #include +#include -class DocumentBlock : public QObject +// To be instanciated in QML as the logic backend to some TextArea +class DocumentHandler : public QObject { Q_OBJECT QML_ELEMENT - Q_PROPERTY(QQuickTextDocument* textDocument READ getDoc WRITE setDoc NOTIFY docChanged) + Q_PROPERTY(QQuickTextDocument* document READ getDoc WRITE setDoc NOTIFY docChanged) + Q_PROPERTY(QDateTime modifyTime READ getModifyTime NOTIFY modificationChanged) + + Q_PROPERTY(int cursorPos READ getCursorPos WRITE setCursorPos NOTIFY cursorPosChanged) + Q_PROPERTY(int selectionBegin READ getSelectionBegin WRITE setSelectionBegin NOTIFY selectionBeginChanged) + Q_PROPERTY(int selectionEnd READ getSelectionEnd WRITE setSelectionEnd NOTIFY selectionEndChanged) + + Q_PROPERTY(QFont activeFont READ getActiveFont WRITE setActiveFont NOTIFY activeFontChanged) + Q_PROPERTY(QColor activeTextColor READ getActiveTextColor WRITE setActiveTextColor NOTIFY activeTextColorChanged) private: - DocumentModel* mModel; QQuickTextDocument* mDoc = nullptr; QDateTime mModifyTime; -public: - explicit DocumentBlock(QObject* parent = nullptr); + int mCursorPos; + int mSelectionBegin; + int mSelectionEnd; - DocumentModel* getModel() const; - void setModel(DocumentModel* newModel); +public: + explicit DocumentHandler(QObject* parent = nullptr); QQuickTextDocument* getDoc() const; void setDoc(QQuickTextDocument* newDoc); const QDateTime& getModifyTime() const; -signals: - void docChanged(); - void modificationChanged(); -}; + int getCursorPos() const; + void setCursorPos(int newCursorPos); -class DocumentModel : public QAbstractItemModel -{ - Q_OBJECT - QML_ELEMENT + int getSelectionBegin() const; + void setSelectionBegin(int newSelectionBegin); -private: - std::vector mBlocks; + int getSelectionEnd() const; + void setSelectionEnd(int newSelectionEnd); -public: - enum DocumentRoles { - ModifyTimeRole = Qt::UserRole + 1, - }; + QFont getActiveFont() const; + void setActiveFont(const QFont& font); - DocumentModel(QObject* parent = nullptr); + QColor getActiveTextColor() const; + void setActiveTextColor(const QColor& color); - void appendBlock(DocumentBlock* block); - // TODO - // void moveBlock() +signals: + void docChanged(QQuickTextDocument* oldDoc); + void modificationChanged(); // Redirected from the currently bound document + + void cursorPosChanged(); + void selectionBeginChanged(); + void selectionEndChanged(); - int rowCount(const QModelIndex& parent = QModelIndex()) const override; - QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + void activeFontChanged(); + void activeTextColorChanged(); -protected: - QHash roleNames() const override; +private: + QTextCursor makeTextCursor() const; + void mergeFormatOnWordOrSelection(const QTextCharFormat& format); }; diff --git a/ui.qt/source/fwd.hpp b/ui.qt/source/fwd.hpp index 7f98627..7a24ded 100644 --- a/ui.qt/source/fwd.hpp +++ b/ui.qt/source/fwd.hpp @@ -1,8 +1,7 @@ #pragma once // document.hpp -class DocumentBlock; -class DocumentModel; +class DocumentHandler; // keyword.hpp class Keyword; diff --git a/ui.qt/source/main.cpp b/ui.qt/source/main.cpp index e92863f..fab148c 100644 --- a/ui.qt/source/main.cpp +++ b/ui.qt/source/main.cpp @@ -1,13 +1,13 @@ -#include +#include #include #include #include int main(int argc, char *argv[]) { - QGuiApplication app(argc, argv); + QApplication app(argc, argv); - QTranslator translator; + QTranslator translator; const QStringList uiLanguages = QLocale::system().uiLanguages(); for (const QString& locale : uiLanguages) { const QString baseName = "EpistmoolUI_" + QLocale(locale).name(); diff --git a/ui.qt/source/qml/Document.qml b/ui.qt/source/qml/Document.qml index e62b731..a3d4075 100644 --- a/ui.qt/source/qml/Document.qml +++ b/ui.qt/source/qml/Document.qml @@ -1,39 +1,163 @@ import QtCore import QtQuick import QtQuick.Controls +import QtQuick.Dialogs +import Qt.labs.platform as Platform import EpistmoolUI Item { - DocumentModel { - id: documentModel + Action { + id: boldAction + shortcut: StandardKey.Bold + onTriggered: docHandler.bold = !docHandler.bold } - ScrollView { - id: scrollView + Action { + id: italicAction + shortcut: StandardKey.Italic + onTriggered: docHandler.italic = !docHandler.italic + } + + Action { + id: underlineAction + shortcut: StandardKey.Underline + onTriggered: docHandler.underline = !docHandler.underline + } + + Action { + id: strikeoutAction + shortcut: "Ctrl+Shift+X" + onTriggered: docHandler.strikeout = !docHandler.strikeout + } - ListView { - id: listView - model: documentModel - anchors.fill: parent + Platform.ColorDialog { + id: colorDialog + currentColor: "black" + } + + Item { + id: toolbar + width: parent.width + height: childrenRect.height + + Row { + id: toolbarLeft + layoutDirection: Qt.LeftToRight + + ToolButton { + id: boldButton + text: "B" + font.bold: true + focusPolicy: Qt.TabFocus + checkable: true + checked: docHandler.bold + action: boldAction + } + ToolButton { + id: italicButton + text: "I" + font.italic: true + focusPolicy: Qt.TabFocus + checkable: true + checked: docHandler.italic + action: italicAction + } + ToolButton { + id: underlineButton + text: "U" + font.underline: true + focusPolicy: Qt.TabFocus + checkable: true + checked: docHandler.underline + action: underlineAction + } + ToolButton { + id: strikeoutButton + text: "S" + font.strikeout: true + focusPolicy: Qt.TabFocus + checkable: true + checked: docHandler.strikeout + action: strikeoutAction + } + ToolButton { + id: textColorButton + text: "\uF1FC" // icon-brush + font.family: "fontello" + focusPolicy: Qt.TabFocus + onClicked: colorDialog.open() - delegate: Item { - required property DocumentBlock documentBlock - required property date modifyTime + Rectangle { + width: aFontMetrics.width + 3 + height: 2 + color: docHandler.activeTextColor + parent: textColorButton.contentItem + anchors.horizontalCenter: parent.horizontalCenter + anchors.baseline: parent.baseline + anchors.baselineOffset: 6 - Component.onCompleted: { - documentBlock.textDocument = textArea.textDocument + TextMetrics { + id: aFontMetrics + font: textColorButton.font + text: textColorButton.text + } } + } + } + + Row { + id: toolbarRight + layoutDirection: Qt.RightToLeft + height: parent.height + anchors.left: toolbarLeft.right + anchors.right: parent.right + + Label { + text: docHandler.modifyTime.toLocaleTimeString() - TextArea { - id: textArea - textFormat: Qt.RichText - wrapMode: TextArea.Wrap - focus: true - selectByMouse: true - persistentSelection: true + ToolTip.visible: ma.containsMouse + ToolTip.text: docHandler.modifyTime.toLocaleString() + + MouseArea { + id: ma + anchors.fill: parent + hoverEnabled: true } } } } + + DocumentHandler { + id: docHandler + document: textArea.textDocument + + // Binding for current editing state of the TextArea + cursorPos: textArea.cursorPosition + selectionBegin: textArea.selectionStart + selectionEnd: textArea.selectionEnd + + property alias family: docHandler.activeFont.family + property alias bold: docHandler.activeFont.bold + property alias italic: docHandler.activeFont.italic + property alias underline: docHandler.activeFont.underline + property alias strikeout: docHandler.activeFont.strikeout + property alias size: docHandler.activeFont.pointSize + } + + ScrollView { + id: scrollView + width: parent.width + anchors.top: toolbar.bottom + anchors.bottom: parent.bottom + + TextArea { + id: textArea + textFormat: Qt.RichText + wrapMode: TextArea.Wrap + focus: true + selectByMouse: true + persistentSelection: true + } + } } diff --git a/ui.qt/source/qml/MainWindow.qml b/ui.qt/source/qml/MainWindow.qml index 905f0e8..d81fb4f 100644 --- a/ui.qt/source/qml/MainWindow.qml +++ b/ui.qt/source/qml/MainWindow.qml @@ -6,7 +6,16 @@ Window { visible: true title: qsTr("Hello World") + Navigator { + id: navigator + width: childrenRect.width + height: parent.height + } + Document { - id: doc + id: document + height: parent.height + anchors.left: navigator.right + anchors.right: parent.right } } diff --git a/ui.qt/source/qml/Navigator.qml b/ui.qt/source/qml/Navigator.qml new file mode 100644 index 0000000..40c66e4 --- /dev/null +++ b/ui.qt/source/qml/Navigator.qml @@ -0,0 +1,36 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +Item { + ColumnLayout { + spacing: 0 + + Button { + contentItem: Label { + text: qsTr("Settings") + anchors.verticalCenter: parent.verticalCenter + } + + Layout.fillWidth: true + } + + Button { + contentItem: Label { + text: qsTr("Keyword") + anchors.verticalCenter: parent.verticalCenter + } + + Layout.fillWidth: true + } + + Button { + contentItem: Label { + text: qsTr("Knowledge Fragments") + anchors.verticalCenter: parent.verticalCenter + } + + Layout.fillWidth: true + } + } +} -- cgit v1.2.3-70-g09d2