From dec5587c383b6dc0f0eb965da27e155f6f2b71ea Mon Sep 17 00:00:00 2001 From: "dmytro.bogovych" Date: Sun, 12 May 2019 20:17:36 +0300 Subject: [PATCH] - add markdown editor --- client/qmarkdowntextedit/.gitignore | 8 + client/qmarkdowntextedit/.travis.yml | 66 + client/qmarkdowntextedit/CMakeLists.txt | 49 + client/qmarkdowntextedit/LICENSE | 8 + client/qmarkdowntextedit/README.md | 33 + client/qmarkdowntextedit/appveyor.yml | 12 + client/qmarkdowntextedit/main.cpp | 41 + client/qmarkdowntextedit/mainwindow.cpp | 118 ++ client/qmarkdowntextedit/mainwindow.h | 44 + client/qmarkdowntextedit/mainwindow.ui | 82 ++ .../qmarkdowntextedit/markdownhighlighter.cpp | 698 +++++++++++ .../qmarkdowntextedit/markdownhighlighter.h | 137 ++ client/qmarkdowntextedit/media.qrc | 9 + .../media/edit-find-replace.svg | 15 + .../media/format-text-superscript.svg | 16 + client/qmarkdowntextedit/media/go-bottom.svg | 156 +++ client/qmarkdowntextedit/media/go-top.svg | 476 +++++++ .../qmarkdowntextedit/media/window-close.svg | 148 +++ .../qmarkdowntextedit-headers.pri | 6 + .../qmarkdowntextedit-sources.pri | 14 + .../qmarkdowntextedit/qmarkdowntextedit.cpp | 1099 +++++++++++++++++ client/qmarkdowntextedit/qmarkdowntextedit.h | 87 ++ .../qmarkdowntextedit/qmarkdowntextedit.pri | 4 + .../qmarkdowntextedit/qmarkdowntextedit.pro | 23 + .../qplaintexteditsearchwidget.cpp | 266 ++++ .../qplaintexteditsearchwidget.h | 60 + .../qplaintexteditsearchwidget.ui | 253 ++++ client/qmarkdowntextedit/screenshot.png | Bin 0 -> 132460 bytes .../trans/qmarkdowntextedit_de.qm | Bin 0 -> 951 bytes .../trans/qmarkdowntextedit_de.ts | 57 + 30 files changed, 3985 insertions(+) create mode 100644 client/qmarkdowntextedit/.gitignore create mode 100644 client/qmarkdowntextedit/.travis.yml create mode 100644 client/qmarkdowntextedit/CMakeLists.txt create mode 100644 client/qmarkdowntextedit/LICENSE create mode 100644 client/qmarkdowntextedit/README.md create mode 100644 client/qmarkdowntextedit/appveyor.yml create mode 100644 client/qmarkdowntextedit/main.cpp create mode 100644 client/qmarkdowntextedit/mainwindow.cpp create mode 100644 client/qmarkdowntextedit/mainwindow.h create mode 100644 client/qmarkdowntextedit/mainwindow.ui create mode 100644 client/qmarkdowntextedit/markdownhighlighter.cpp create mode 100644 client/qmarkdowntextedit/markdownhighlighter.h create mode 100644 client/qmarkdowntextedit/media.qrc create mode 100644 client/qmarkdowntextedit/media/edit-find-replace.svg create mode 100644 client/qmarkdowntextedit/media/format-text-superscript.svg create mode 100644 client/qmarkdowntextedit/media/go-bottom.svg create mode 100644 client/qmarkdowntextedit/media/go-top.svg create mode 100644 client/qmarkdowntextedit/media/window-close.svg create mode 100644 client/qmarkdowntextedit/qmarkdowntextedit-headers.pri create mode 100644 client/qmarkdowntextedit/qmarkdowntextedit-sources.pri create mode 100644 client/qmarkdowntextedit/qmarkdowntextedit.cpp create mode 100644 client/qmarkdowntextedit/qmarkdowntextedit.h create mode 100644 client/qmarkdowntextedit/qmarkdowntextedit.pri create mode 100644 client/qmarkdowntextedit/qmarkdowntextedit.pro create mode 100644 client/qmarkdowntextedit/qplaintexteditsearchwidget.cpp create mode 100644 client/qmarkdowntextedit/qplaintexteditsearchwidget.h create mode 100644 client/qmarkdowntextedit/qplaintexteditsearchwidget.ui create mode 100644 client/qmarkdowntextedit/screenshot.png create mode 100644 client/qmarkdowntextedit/trans/qmarkdowntextedit_de.qm create mode 100644 client/qmarkdowntextedit/trans/qmarkdowntextedit_de.ts diff --git a/client/qmarkdowntextedit/.gitignore b/client/qmarkdowntextedit/.gitignore new file mode 100644 index 0000000..9fcd300 --- /dev/null +++ b/client/qmarkdowntextedit/.gitignore @@ -0,0 +1,8 @@ +*.o +*.log +*.dSYM +*.plist +*.pro.user +.directory +build-* +.idea diff --git a/client/qmarkdowntextedit/.travis.yml b/client/qmarkdowntextedit/.travis.yml new file mode 100644 index 0000000..a703d15 --- /dev/null +++ b/client/qmarkdowntextedit/.travis.yml @@ -0,0 +1,66 @@ +language: cpp + +os: + - linux + - osx + +branches: + only: + - develop + - master + - testing + +env: + matrix: + - CONFIG=release + #- CONFIG=debug + +install: + - if [ "${TRAVIS_OS_NAME}" = "linux" ]; then + lsb_release -a + && sudo apt-add-repository -y ppa:ubuntu-toolchain-r/test + && sudo apt-add-repository -y ppa:beineri/opt-qt591-trusty + && sudo apt-get -qq update + && sudo apt-get -qq install g++-4.8 libc6-i386 qt59tools qt59svg qt59quickcontrols qt59quickcontrols2 qt59webengine qt59script + && export CXX="g++-4.8" + && export CC="gcc-4.8" + ; + else + brew update > /dev/null + && brew install qt5 + && chmod -R 755 /usr/local/opt/qt5/* + ; + fi + +script: + - if [ "${TRAVIS_OS_NAME}" = "linux" ]; then + QTDIR="/opt/qt59" + && PATH="$QTDIR/bin:$PATH" + && qt59-env.sh + ; + else + QTDIR="/usr/local/opt/qt5" + && PATH="$QTDIR/bin:$PATH" + && LDFLAGS=-L$QTDIR/lib + && CPPFLAGS=-I$QTDIR/include + ; + fi + - qmake qmarkdowntextedit.pro CONFIG+=$CONFIG + - make + +notifications: + recipients: + - developer@bekerle.com + email: + on_success: change + on_failure: change + irc: + # https://docs.travis-ci.com/user/notifications/#IRC-notification + channels: + - "chat.freenode.net#qownnotes" + template: + - "[%{commit}] %{repository} (%{branch}): %{message} | Commit message: %{commit_message} | Changes: %{compare_url} | Build details: %{build_url}" + on_success: always + on_failure: always + use_notice: true + skip_join: true diff --git a/client/qmarkdowntextedit/CMakeLists.txt b/client/qmarkdowntextedit/CMakeLists.txt new file mode 100644 index 0000000..cdf5e5e --- /dev/null +++ b/client/qmarkdowntextedit/CMakeLists.txt @@ -0,0 +1,49 @@ +cmake_minimum_required(VERSION 3.3) +project(qmarkdowntextedit) + +#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTOUIC ON) +set(CMAKE_AUTORCC ON) +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +find_package( Qt5Core REQUIRED ) +find_package( Qt5Widgets REQUIRED ) +find_package( Qt5Gui REQUIRED ) + +qt5_wrap_ui(ui_qplaintexteditsearchwidget.h qplaintexteditsearchwidget.ui) + +set(RESOURCE_FILES + media.qrc + ) + +qt5_add_resources(RESOURCE_ADDED ${RESOURCE_FILES}) + +set(SOURCE_FILES + markdownhighlighter.cpp + markdownhighlighter.h + main.cpp + mainwindow.cpp + mainwindow.h + mainwindow.ui + qmarkdowntextedit.cpp + qmarkdowntextedit.h + qplaintexteditsearchwidget.ui + qplaintexteditsearchwidget.cpp + qplaintexteditsearchwidget.h + ) + +add_executable(qmarkdowntextedit ${SOURCE_FILES} ${RESOURCE_ADDED}) + +include_directories(${Qt5Widgets_INCLUDES}) + +# We need add -DQT_WIDGETS_LIB when using QtWidgets in Qt 5. +add_definitions(${Qt5Widgets_DEFINITIONS}) + +# Executables fail to build with Qt 5 in the default configuration +# without -fPIE. We add that here. +set(CMAKE_CXX_FLAGS "${Qt5Widgets_EXECUTABLE_COMPILE_FLAGS}") + +# The Qt5Widgets_LIBRARIES variable also includes QtGui and QtCore +target_link_libraries(qmarkdowntextedit Qt5::Widgets) diff --git a/client/qmarkdowntextedit/LICENSE b/client/qmarkdowntextedit/LICENSE new file mode 100644 index 0000000..41f4fbb --- /dev/null +++ b/client/qmarkdowntextedit/LICENSE @@ -0,0 +1,8 @@ +The MIT License (MIT) +Copyright (c) 2014-2019 Patrizio Bekerle + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/client/qmarkdowntextedit/README.md b/client/qmarkdowntextedit/README.md new file mode 100644 index 0000000..b94bd77 --- /dev/null +++ b/client/qmarkdowntextedit/README.md @@ -0,0 +1,33 @@ +# [QMarkdownTextEdit](https://github.com/pbek/qmarkdowntextedit) +[![Build Status Linux/OS X](https://travis-ci.org/pbek/qmarkdowntextedit.svg?branch=develop)](https://travis-ci.org/pbek/qmarkdowntextedit) +[![Build Status Windows](https://ci.appveyor.com/api/projects/status/github/pbek/qmarkdowntextedit)](https://ci.appveyor.com/project/pbek/qmarkdowntextedit) + +QMarkdownTextEdit is a C++ Qt [QPlainTextEdit](http://doc.qt.io/qt-5/qtextplainedit.html) widget with [markdown](https://en.wikipedia.org/wiki/Markdown) highlighting and some other goodies. + +## Features +- markdown highlighting +- clickable links with `Ctrl + Click` +- block indent with `Tab` and `Shift + Tab` +- duplicate text with `Ctrl + Alt + Down` +- searching of text with `Ctrl + F` + - jump between search results with `Up` and `Down` + - close search field with `Escape` +- replacing of text with `Ctrl + R` + - you can also replace text with regular expressions or whole words +- and much more... + +## Screenshot +![Screenhot](screenshot.png) + +## How to use this widget +- include [qmarkdowntextedit.pri](https://github.com/pbek/qmarkdowntextedit/blob/develop/qmarkdowntextedit.pri) + to your project like this `include (qmarkdowntextedit/qmarkdowntextedit.pri)` +- add a normal `QPlainTextEdit` to your UI and promote it to `QMarkdownTextEdit` (base class `QPlainTextEdit`) + +## References +- [QOwnNotes - cross-platform open source plain-text file notepad](http://www.qownnotes.org) + +## Disclaimer +This SOFTWARE PRODUCT is provided by THE PROVIDER "as is" and "with all faults." THE PROVIDER makes no representations or warranties of any kind concerning the safety, suitability, lack of viruses, inaccuracies, typographical errors, or other harmful components of this SOFTWARE PRODUCT. + +There are inherent dangers in the use of any software, and you are solely responsible for determining whether this SOFTWARE PRODUCT is compatible with your equipment and other software installed on your equipment. You are also solely responsible for the protection of your equipment and backup of your data, and THE PROVIDER will not be liable for any damages you may suffer in connection with using, modifying, or distributing this SOFTWARE PRODUCT. diff --git a/client/qmarkdowntextedit/appveyor.yml b/client/qmarkdowntextedit/appveyor.yml new file mode 100644 index 0000000..3fd31cc --- /dev/null +++ b/client/qmarkdowntextedit/appveyor.yml @@ -0,0 +1,12 @@ +# AppVeyor build configuration +# http://www.appveyor.com/docs/build-configuration +os: unstable +skip_tags: true + +install: + - set QTDIR=C:\Qt\5.10.1\mingw53_32 + - set PATH=%PATH%;%QTDIR%\bin;C:\MinGW\bin + +build_script: + - qmake qmarkdowntextedit.pro -r -spec win32-g++ + - mingw32-make diff --git a/client/qmarkdowntextedit/main.cpp b/client/qmarkdowntextedit/main.cpp new file mode 100644 index 0000000..e960337 --- /dev/null +++ b/client/qmarkdowntextedit/main.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2014-2019 Patrizio Bekerle -- http://www.bekerle.com + * + * 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; version 2 of the License. + * + * 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. + * + */ + +#include "mainwindow.h" +#include +#include +#include + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + QString filename; + if (argc > 1) { + filename = argv[1]; + } + if (!filename.isEmpty() && !QFileInfo(filename).isReadable()) { + qWarning() << filename << "is not a readable file"; + return 1; + } + + MainWindow w; + w.show(); + + if (!filename.isEmpty()) { + w.loadFile(filename); + } + + + return a.exec(); +} diff --git a/client/qmarkdowntextedit/mainwindow.cpp b/client/qmarkdowntextedit/mainwindow.cpp new file mode 100644 index 0000000..be3adde --- /dev/null +++ b/client/qmarkdowntextedit/mainwindow.cpp @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2014-2019 Patrizio Bekerle -- http://www.bekerle.com + * + * 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; version 2 of the License. + * + * 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. + * + * mainwindow.cpp + * + * Example to show the QMarkdownTextEdit widget + */ + +#include "mainwindow.h" +#include "ui_mainwindow.h" +#include "qmarkdowntextedit.h" +#include +#include +#include + +MainWindow::MainWindow(QWidget *parent) : + QMainWindow(parent), + ui(new Ui::MainWindow) +{ + ui->setupUi(this); + QToolBar *toolBar = new QToolBar; + addToolBar(toolBar); + QAction *openAction = new QAction(QIcon::fromTheme("document-open"), tr("Open...")); + openAction->setShortcut(QKeySequence::Open); + connect(openAction, &QAction::triggered, this, &MainWindow::open); + + QAction *saveAction = new QAction(QIcon::fromTheme("document-save"), tr("Save")); + saveAction->setShortcut(QKeySequence::Save); + QAction *saveAsAction = new QAction(QIcon::fromTheme("document-save-as"), tr("Save as...")); + saveAsAction->setShortcut(QKeySequence::SaveAs); + QAction *quitAction = new QAction(QIcon::fromTheme("view-close"), tr("Quit")); + quitAction->setShortcut(QKeySequence::Quit); + connect(quitAction, &QAction::triggered, this, &MainWindow::onQuit); + + m_loadedContent = ui->textEdit->toPlainText(); + + toolBar->addActions({openAction, saveAction, saveAsAction, quitAction}); +} + +MainWindow::~MainWindow() +{ + delete ui; +} + +void MainWindow::loadFile(const QString &filename) +{ + QFile file(filename); + if (!file.open(QIODevice::ReadOnly)) { + qWarning() << "Failed to open" << filename; + return; + } + + m_filename = filename; + m_loadedContent = QString::fromLocal8Bit(file.readAll()); + ui->textEdit->setPlainText(m_loadedContent); +} + +void MainWindow::saveToFile(const QString &filename) +{ + QFile file(filename); + if (!file.open(QIODevice::WriteOnly)) { + qWarning() << "Failed to open" << filename; + return; + } + + m_filename = filename; + + m_loadedContent = ui->textEdit->toPlainText(); + file.write(m_loadedContent.toLocal8Bit()); +} + +void MainWindow::open() +{ + QString filename = QFileDialog::getOpenFileName(); + if (filename.isEmpty()) { + return; + } + loadFile(filename); +} + +void MainWindow::save() +{ + if (!m_filename.isEmpty()) { + saveAs(); + return; + } + + saveToFile(m_filename); +} + +void MainWindow::saveAs() +{ + QString filename = QFileDialog::getSaveFileName(); + if (filename.isEmpty()) { + return; + } + + saveToFile(filename); +} + +void MainWindow::onQuit() +{ + if (ui->textEdit->toPlainText() != m_loadedContent) { + if (QMessageBox::question(this, tr("Not saved"), tr("Document not saved, sure you want to quit?")) != QMessageBox::Yes) { + return; + } + } + close(); +} diff --git a/client/qmarkdowntextedit/mainwindow.h b/client/qmarkdowntextedit/mainwindow.h new file mode 100644 index 0000000..f3487ac --- /dev/null +++ b/client/qmarkdowntextedit/mainwindow.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2014-2019 Patrizio Bekerle -- http://www.bekerle.com + * + * 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; version 2 of the License. + * + * 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. + * + */ + +#pragma once + +#include + +namespace Ui { +class MainWindow; +} + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + explicit MainWindow(QWidget *parent = nullptr); + ~MainWindow(); + + void loadFile(const QString &filename); + void saveToFile(const QString &filename); + +private slots: + void open(); + void save(); + void saveAs(); + void onQuit(); + +private: + Ui::MainWindow *ui; + QString m_loadedContent; + QString m_filename; +}; diff --git a/client/qmarkdowntextedit/mainwindow.ui b/client/qmarkdowntextedit/mainwindow.ui new file mode 100644 index 0000000..a5b9a5b --- /dev/null +++ b/client/qmarkdowntextedit/mainwindow.ui @@ -0,0 +1,82 @@ + + + MainWindow + + + + 0 + 0 + 1070 + 839 + + + + QMarkdownTextEdit + + + + + + + QMarkdownTextEdit +============== + +*QMarkdownTextEdit* is a C++ Qt [QPlainTextEdit](http://doc.qt.io/qt-5/qplaintextedit.html) widget with **markdown highlighting** and some other goodies. + +## Features + +- markdown highlighting +- clickable links with `Ctrl + Click` +- block indent with `Tab` and `Shift + Tab` +- duplicate text with `Ctrl + Alt + Down` +- searching of text with `Ctrl + F` + - jump between search results with `Up` and `Down` + - close search field with `Escape` +- and much more... + +## References + +- [QOwnNotes - cross-platform open source plain-text notepad](http://www.qownnotes.org) + +## Disclaimer + +This SOFTWARE PRODUCT is provided by THE PROVIDER "as is" and "with all faults." THE PROVIDER makes no representations or warranties of any kind concerning the safety, suitability, lack of viruses, inaccuracies, typographical errors, or other harmful components of this SOFTWARE PRODUCT. + +There are inherent dangers in the use of any software, and you are solely responsible for determining whether this SOFTWARE PRODUCT is compatible with your equipment and other software installed on your equipment. You are also solely responsible for the protection of your equipment and backup of your data, and THE PROVIDER will not be liable for any damages you may suffer in connection with using, modifying, or distributing this SOFTWARE PRODUCT. + + + + + + + + + + 0 + 0 + 1070 + 25 + + + + + + TopToolBarArea + + + false + + + + + + + + QMarkdownTextEdit + QPlainTextEdit +
qmarkdowntextedit.h
+
+
+ + +
diff --git a/client/qmarkdowntextedit/markdownhighlighter.cpp b/client/qmarkdowntextedit/markdownhighlighter.cpp new file mode 100644 index 0000000..1038631 --- /dev/null +++ b/client/qmarkdowntextedit/markdownhighlighter.cpp @@ -0,0 +1,698 @@ +/* + * Copyright (c) 2014-2019 Patrizio Bekerle -- http://www.bekerle.com + * + * 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; version 2 of the License. + * + * 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. + * + * QPlainTextEdit markdown highlighter + */ + +#include +#include +#include "markdownhighlighter.h" +#include +#include +#include + + +/** + * Markdown syntax highlighting + * + * markdown syntax: + * http://daringfireball.net/projects/markdown/syntax + * + * @param parent + * @return + */ +MarkdownHighlighter::MarkdownHighlighter( + QTextDocument *parent, HighlightingOptions highlightingOptions) + : QSyntaxHighlighter(parent) { + _highlightingOptions = highlightingOptions; + _timer = new QTimer(this); + QObject::connect(_timer, SIGNAL(timeout()), + this, SLOT(timerTick())); + _timer->start(1000); + + // initialize the highlighting rules + initHighlightingRules(); + + // initialize the text formats + initTextFormats(); +} + +/** + * Does jobs every second + */ +void MarkdownHighlighter::timerTick() { + // qDebug() << "timerTick: " << this << ", " << this->parent()->parent()->parent()->objectName(); + + // re-highlight all dirty blocks + reHighlightDirtyBlocks(); + + // emit a signal every second if there was some highlighting done + if (_highlightingFinished) { + _highlightingFinished = false; + emit(highlightingFinished()); + } +} + +/** + * Re-highlights all dirty blocks + */ +void MarkdownHighlighter::reHighlightDirtyBlocks() { + while (_dirtyTextBlocks.count() > 0) { + QTextBlock block = _dirtyTextBlocks.at(0); + rehighlightBlock(block); + _dirtyTextBlocks.removeFirst(); + } +} + +/** + * Clears the dirty blocks vector + */ +void MarkdownHighlighter::clearDirtyBlocks() { + _dirtyTextBlocks.clear(); +} + +/** + * Adds a dirty block to the list if it doesn't already exist + * + * @param block + */ +void MarkdownHighlighter::addDirtyBlock(QTextBlock block) { + if (!_dirtyTextBlocks.contains(block)) { + _dirtyTextBlocks.append(block); + } +} + +/** + * Initializes the highlighting rules + * + * regexp tester: + * https://regex101.com + * + * other examples: + * /usr/share/kde4/apps/katepart/syntax/markdown.xml + */ +void MarkdownHighlighter::initHighlightingRules() { + // highlight the reference of reference links + { + HighlightingRule rule(HighlighterState::MaskedSyntax); + rule.pattern = QRegularExpression("^\\[.+?\\]: \\w+://.+$"); + _highlightingRulesPre.append(rule); + } + + // highlight unordered lists + { + HighlightingRule rule(HighlighterState::List); + rule.pattern = QRegularExpression("^\\s*[-*+]\\s"); + rule.useStateAsCurrentBlockState = true; + _highlightingRulesPre.append(rule); + + // highlight ordered lists + rule.pattern = QRegularExpression("^\\s*\\d+\\.\\s"); + _highlightingRulesPre.append(rule); + } + + // highlight block quotes + { + HighlightingRule rule(HighlighterState::BlockQuote); + rule.pattern = QRegularExpression( + _highlightingOptions.testFlag( + HighlightingOption::FullyHighlightedBlockQuote) ? + "^\\s*(>\\s*.+)" : "^\\s*(>\\s*)+"); + _highlightingRulesPre.append(rule); + } + + // highlight horizontal rulers + { + HighlightingRule rule(HighlighterState::HorizontalRuler); + rule.pattern = QRegularExpression("^([*\\-_]\\s?){3,}$"); + _highlightingRulesPre.append(rule); + } + + // highlight tables without starting | + // we drop that for now, it's far too messy to deal with +// rule = HighlightingRule(); +// rule.pattern = QRegularExpression("^.+? \\| .+? \\| .+$"); +// rule.state = HighlighterState::Table; +// _highlightingRulesPre.append(rule); + + /* + * highlight italic + * this goes before bold so that bold can overwrite italic + * + * text to test: + * **bold** normal **bold** + * *start of line* normal + * normal *end of line* + * * list item *italic* + */ + { + HighlightingRule rule(HighlighterState::Italic); + // we don't allow a space after the starting * to prevent problems with + // unordered lists starting with a * + rule.pattern = QRegularExpression( + "(?:^|[^\\*\\b])(?:\\*([^\\* ][^\\*]*?)\\*)(?:[^\\*\\b]|$)"); + rule.capturingGroup = 1; + _highlightingRulesAfter.append(rule); + + rule.pattern = QRegularExpression("\\b_([^_]+)_\\b"); + _highlightingRulesAfter.append(rule); + } + + { + HighlightingRule rule(HighlighterState::Bold); + // highlight bold + rule.pattern = QRegularExpression("\\B\\*{2}(.+?)\\*{2}\\B"); + rule.capturingGroup = 1; + _highlightingRulesAfter.append(rule); + rule.pattern = QRegularExpression("\\b__(.+?)__\\b"); + _highlightingRulesAfter.append(rule); + } + + // highlight urls + { + HighlightingRule rule(HighlighterState::Link); + + // highlight urls without any other markup + rule.pattern = QRegularExpression("\\b\\w+?:\\/\\/[^\\s]+"); + rule.capturingGroup = 1; + _highlightingRulesAfter.append(rule); + + // rule.pattern = QRegularExpression("<(.+?:\\/\\/.+?)>"); + rule.pattern = QRegularExpression("<([^\\s`][^`]*?[^\\s`])>"); + rule.capturingGroup = 1; + _highlightingRulesAfter.append(rule); + + // highlight urls with title + // rule.pattern = QRegularExpression("\\[(.+?)\\]\\(.+?://.+?\\)"); + // rule.pattern = QRegularExpression("\\[(.+?)\\]\\(.+\\)\\B"); + rule.pattern = QRegularExpression("\\[([^\\[\\]]+)\\]\\((\\S+|.+?)\\)\\B"); + _highlightingRulesAfter.append(rule); + + // highlight urls with empty title + // rule.pattern = QRegularExpression("\\[\\]\\((.+?://.+?)\\)"); + rule.pattern = QRegularExpression("\\[\\]\\((.+?)\\)"); + _highlightingRulesAfter.append(rule); + + // highlight email links + rule.pattern = QRegularExpression("<(.+?@.+?)>"); + _highlightingRulesAfter.append(rule); + + // highlight reference links + rule.pattern = QRegularExpression("\\[(.+?)\\]\\s?\\[.+?\\]"); + _highlightingRulesAfter.append(rule); + } + + // Images + { + // highlight images with text + HighlightingRule rule(HighlighterState::Image); + rule.pattern = QRegularExpression("!\\[(.+?)\\]\\(.+?\\)"); + rule.capturingGroup = 1; + _highlightingRulesAfter.append(rule); + + // highlight images without text + rule.pattern = QRegularExpression("!\\[\\]\\((.+?)\\)"); + _highlightingRulesAfter.append(rule); + } + + // highlight images links + { +// HighlightingRule rule; + HighlightingRule rule(HighlighterState::Link); + rule.pattern = QRegularExpression("\\[!\\[(.+?)\\]\\(.+?\\)\\]\\(.+?\\)"); + rule.capturingGroup = 1; + _highlightingRulesAfter.append(rule); + + // highlight images links without text + rule.pattern = QRegularExpression("\\[!\\[\\]\\(.+?\\)\\]\\((.+?)\\)"); + _highlightingRulesAfter.append(rule); + } + + // highlight inline code + { + HighlightingRule rule(HighlighterState::InlineCodeBlock); +// HighlightingRule rule; + rule.pattern = QRegularExpression("`(.+?)`"); + rule.capturingGroup = 1; + _highlightingRulesAfter.append(rule); + } + + // highlight code blocks with four spaces or tabs in front of them + // and no list character after that + { + HighlightingRule rule(HighlighterState::CodeBlock); +// HighlightingRule rule; + rule.pattern = QRegularExpression("^((\\t)|( {4,})).+$"); + rule.disableIfCurrentStateIsSet = true; + _highlightingRulesAfter.append(rule); + } + + // highlight inline comments + { + HighlightingRule rule(HighlighterState::Comment); + rule.pattern = QRegularExpression(""); + rule.capturingGroup = 1; + _highlightingRulesAfter.append(rule); + + // highlight comments for Rmarkdown for academic papers + rule.pattern = QRegularExpression("^\\[.+?\\]: # \\(.+?\\)$"); + _highlightingRulesAfter.append(rule); + } + + // highlight tables with starting | + { + HighlightingRule rule(HighlighterState::Table); + rule.pattern = QRegularExpression("^\\|.+?\\|$"); + _highlightingRulesAfter.append(rule); + } +} + +/** + * Initializes the text formats + * + * @param defaultFontSize + */ +void MarkdownHighlighter::initTextFormats(int defaultFontSize) { + QTextCharFormat format; + + // set character formats for headlines + format = QTextCharFormat(); + format.setForeground(QBrush(QColor(0, 49, 110))); + format.setFontWeight(QFont::Bold); + format.setFontPointSize(defaultFontSize * 1.6); + _formats[H1] = format; + format.setFontPointSize(defaultFontSize * 1.5); + _formats[H2] = format; + format.setFontPointSize(defaultFontSize * 1.4); + _formats[H3] = format; + format.setFontPointSize(defaultFontSize * 1.3); + _formats[H4] = format; + format.setFontPointSize(defaultFontSize * 1.2); + _formats[H5] = format; + format.setFontPointSize(defaultFontSize * 1.1); + _formats[H6] = format; + format.setFontPointSize(defaultFontSize); + + // set character format for horizontal rulers + format = QTextCharFormat(); + format.setForeground(QBrush(Qt::darkGray)); + format.setBackground(QBrush(Qt::lightGray)); + _formats[HorizontalRuler] = format; + + // set character format for lists + format = QTextCharFormat(); + format.setForeground(QBrush(QColor(163, 0, 123))); + _formats[List] = format; + + // set character format for links + format = QTextCharFormat(); + format.setForeground(QBrush(QColor(0, 128, 255))); + format.setFontUnderline(true); + _formats[Link] = format; + + // set character format for images + format = QTextCharFormat(); + format.setForeground(QBrush(QColor(0, 191, 0))); + format.setBackground(QBrush(QColor(228, 255, 228))); + _formats[Image] = format; + + // set character format for code blocks + format = QTextCharFormat(); + format.setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); + format.setBackground(QColor(220, 220, 220)); + _formats[CodeBlock] = format; + _formats[InlineCodeBlock] = format; + + // set character format for italic + format = QTextCharFormat(); + format.setFontWeight(QFont::StyleItalic); + format.setFontItalic(true); + _formats[Italic] = format; + + // set character format for bold + format = QTextCharFormat(); + format.setFontWeight(QFont::Bold); + _formats[Bold] = format; + + // set character format for comments + format = QTextCharFormat(); + format.setForeground(QBrush(Qt::gray)); + _formats[Comment] = format; + + // set character format for masked syntax + format = QTextCharFormat(); + format.setForeground(QBrush("#cccccc")); + _formats[MaskedSyntax] = format; + + // set character format for tables + format = QTextCharFormat(); + format.setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); + format.setForeground(QBrush(QColor("#649449"))); + _formats[Table] = format; + + // set character format for block quotes + format = QTextCharFormat(); + format.setForeground(QBrush(QColor(Qt::darkRed))); + _formats[BlockQuote] = format; + + format = QTextCharFormat(); + _formats[HeadlineEnd] = format; + + format = QTextCharFormat(); + _formats[NoState] = format; +} + +/** + * Sets the text formats + * + * @param formats + */ +void MarkdownHighlighter::setTextFormats( + QHash formats) { + _formats = formats; +} + +/** + * Sets a text format + * + * @param formats + */ +void MarkdownHighlighter::setTextFormat(HighlighterState state, + QTextCharFormat format) { + _formats[state] = format; +} + +/** + * Does the markdown highlighting + * + * @param text + */ +void MarkdownHighlighter::highlightBlock(const QString &text) { + setCurrentBlockState(HighlighterState::NoState); + currentBlock().setUserState(HighlighterState::NoState); + highlightMarkdown(text); + _highlightingFinished = true; +} + +void MarkdownHighlighter::highlightMarkdown(QString text) { + if (!text.isEmpty()) { + highlightAdditionalRules(_highlightingRulesPre, text); + + // needs to be called after the horizontal ruler highlighting + highlightHeadline(text); + + highlightAdditionalRules(_highlightingRulesAfter, text); + } + + highlightCommentBlock(text); + highlightCodeBlock(text); +} + +/** + * Highlight headlines + * + * @param text + */ +void MarkdownHighlighter::highlightHeadline(QString text) { + QRegularExpression regex("^(#+)\\s+(.+?)$"); + QRegularExpressionMatch match = regex.match(text); + QTextCharFormat &maskedFormat = _formats[HighlighterState::MaskedSyntax]; + + // check for headline blocks with # in front of them + if (match.hasMatch()) { + int count = match.captured(1).count(); + + // we just have H1 to H6 + count = qMin(count, 6); + + HighlighterState state = HighlighterState( + HighlighterState::H1 + count - 1); + + QTextCharFormat &format = _formats[state]; + QTextCharFormat currentMaskedFormat = maskedFormat; + + // set the font size from the current rule's font format + currentMaskedFormat.setFontPointSize(format.fontPointSize()); + + // first highlight everything as MaskedSyntax + setFormat(match.capturedStart(), match.capturedLength(), + currentMaskedFormat); + + // then highlight with the real format + setFormat(match.capturedStart(2), match.capturedLength(2), + _formats[state]); + + // set a margin for the current block + setCurrentBlockMargin(state); + + setCurrentBlockState(state); + currentBlock().setUserState(state); + return; + } + + // take care of ==== and ---- headlines + QRegularExpression patternH1 = QRegularExpression("^=+$"); + QRegularExpression patternH2 = QRegularExpression("^-+$"); + QTextBlock previousBlock = currentBlock().previous(); + QString previousText = previousBlock.text(); + previousText.trimmed().remove(QRegularExpression("[=-]")); + + // check for ===== after a headline text and highlight as H1 + if (patternH1.match(text).hasMatch()) { + if (((previousBlockState() == HighlighterState::H1) || + (previousBlockState() == HighlighterState::NoState)) && + (previousText.length() > 0)) { + // set the font size from the current rule's font format + QTextCharFormat currentMaskedFormat = maskedFormat; + currentMaskedFormat.setFontPointSize( + _formats[HighlighterState::H1].fontPointSize()); + + setFormat(0, text.length(), currentMaskedFormat); + setCurrentBlockState(HighlighterState::HeadlineEnd); + previousBlock.setUserState(HighlighterState::H1); + + // set a margin for the current block + setCurrentBlockMargin(HighlighterState::H1); + + // we want to re-highlight the previous block + // this must not done directly, but with a queue, otherwise it + // will crash + // setting the character format of the previous text, because this + // causes text to be formatted the same way when writing after + // the text + addDirtyBlock(previousBlock); + } + + return; + } + + // check for ----- after a headline text and highlight as H2 + if (patternH2.match(text).hasMatch()) { + if (((previousBlockState() == HighlighterState::H2) || + (previousBlockState() == HighlighterState::NoState)) && + (previousText.length() > 0)) { + // set the font size from the current rule's font format + QTextCharFormat currentMaskedFormat = maskedFormat; + currentMaskedFormat.setFontPointSize( + _formats[HighlighterState::H2].fontPointSize()); + + setFormat(0, text.length(), currentMaskedFormat); + setCurrentBlockState(HighlighterState::HeadlineEnd); + previousBlock.setUserState(HighlighterState::H2); + + // set a margin for the current block + setCurrentBlockMargin(HighlighterState::H2); + + // we want to re-highlight the previous block + addDirtyBlock(previousBlock); + } + + return; + } + + QTextBlock nextBlock = currentBlock().next(); + QString nextBlockText = nextBlock.text(); + + // highlight as H1 if next block is ===== + if (patternH1.match(nextBlockText).hasMatch() || + patternH2.match(nextBlockText).hasMatch()) { + setFormat(0, text.length(), _formats[HighlighterState::H1]); + setCurrentBlockState(HighlighterState::H1); + currentBlock().setUserState(HighlighterState::H1); + } + + // highlight as H2 if next block is ----- + if (patternH2.match(nextBlockText).hasMatch()) { + setFormat(0, text.length(), _formats[HighlighterState::H2]); + setCurrentBlockState(HighlighterState::H2); + currentBlock().setUserState(HighlighterState::H2); + } +} + +/** + * Sets a margin for the current block + * + * @param state + */ +void MarkdownHighlighter::setCurrentBlockMargin( + MarkdownHighlighter::HighlighterState state) { + // this is currently disabled because it causes multiple problems: + // - it prevents "undo" in headlines + // https://github.com/pbek/QOwnNotes/issues/520 + // - invisible lines at the end of a note + // https://github.com/pbek/QOwnNotes/issues/667 + // - a crash when reaching the invisible lines when the current line is + // highlighted + // https://github.com/pbek/QOwnNotes/issues/701 + return; + + qreal margin; + + switch (state) { + case HighlighterState::H1: + margin = 5; + break; + case HighlighterState::H2: + case HighlighterState::H3: + case HighlighterState::H4: + case HighlighterState::H5: + case HighlighterState::H6: + margin = 3; + break; + default: + return; + } + + QTextBlockFormat blockFormat = currentBlock().blockFormat(); + blockFormat.setTopMargin(2); + blockFormat.setBottomMargin(margin); + + // this prevents "undo" in headlines! + QTextCursor* myCursor = new QTextCursor(currentBlock()); + myCursor->setBlockFormat(blockFormat); +} + +/** + * Highlight multi-line code blocks + * + * @param text + */ +void MarkdownHighlighter::highlightCodeBlock(QString text) { + QRegularExpression regex("^```\\w*?$"); + QRegularExpressionMatch match = regex.match(text); + + if (match.hasMatch()) { + setCurrentBlockState( + previousBlockState() == HighlighterState::CodeBlock ? + HighlighterState::CodeBlockEnd : HighlighterState::CodeBlock); + // set the font size from the current rule's font format + QTextCharFormat &maskedFormat = + _formats[HighlighterState::MaskedSyntax]; + maskedFormat.setFontPointSize( + _formats[HighlighterState::CodeBlock].fontPointSize()); + + setFormat(0, text.length(), maskedFormat); + } else if (previousBlockState() == HighlighterState::CodeBlock) { + setCurrentBlockState(HighlighterState::CodeBlock); + setFormat(0, text.length(), _formats[HighlighterState::CodeBlock]); + } +} + +/** + * Highlight multi-line comments + * + * @param text + */ +void MarkdownHighlighter::highlightCommentBlock(QString text) { + bool highlight = false; + text = text.trimmed(); + QString startText = ""; + + // we will skip this case because that is an inline comment and causes + // troubles here + if (text.startsWith(startText) && text.contains(endText)) { + return; + } + + if (text.startsWith(startText) || + (!text.endsWith(endText) && + (previousBlockState() == HighlighterState::Comment))) { + setCurrentBlockState(HighlighterState::Comment); + highlight = true; + } else if (text.endsWith(endText)) { + highlight = true; + } + + if (highlight) { + setFormat(0, text.length(), _formats[HighlighterState::Comment]); + } +} + +/** + * Highlights the rules from the _highlightingRules list + * + * @param text + */ +void MarkdownHighlighter::highlightAdditionalRules( + QVector &rules, QString text) { + QTextCharFormat &maskedFormat = _formats[HighlighterState::MaskedSyntax]; + + for(const HighlightingRule &rule : rules) { + // continue if an other current block state was already set if + // disableIfCurrentStateIsSet is set + if (rule.disableIfCurrentStateIsSet && + (currentBlockState() != HighlighterState::NoState)) { + continue; + } + + QRegularExpression expression(rule.pattern); + QRegularExpressionMatchIterator iterator = expression.globalMatch(text); + int capturingGroup = rule.capturingGroup; + int maskedGroup = rule.maskedGroup; + QTextCharFormat &format = _formats[rule.state]; + + // store the current block state if useStateAsCurrentBlockState + // is set + if (iterator.hasNext() && rule.useStateAsCurrentBlockState) { + setCurrentBlockState(rule.state); + } + + // find and format all occurrences + while (iterator.hasNext()) { + QRegularExpressionMatch match = iterator.next(); + + // if there is a capturingGroup set then first highlight + // everything as MaskedSyntax and highlight capturingGroup + // with the real format + if (capturingGroup > 0) { + QTextCharFormat currentMaskedFormat = maskedFormat; + // set the font size from the current rule's font format + if (format.fontPointSize() > 0) { + currentMaskedFormat.setFontPointSize(format.fontPointSize()); + } + + setFormat(match.capturedStart(maskedGroup), + match.capturedLength(maskedGroup), + currentMaskedFormat); + } + + setFormat(match.capturedStart(capturingGroup), + match.capturedLength(capturingGroup), + format); + } + } +} + +void MarkdownHighlighter::setHighlightingOptions(HighlightingOptions options) { + _highlightingOptions = options; +} diff --git a/client/qmarkdowntextedit/markdownhighlighter.h b/client/qmarkdowntextedit/markdownhighlighter.h new file mode 100644 index 0000000..e66f86f --- /dev/null +++ b/client/qmarkdowntextedit/markdownhighlighter.h @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2014-2019 Patrizio Bekerle -- http://www.bekerle.com + * + * 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; version 2 of the License. + * + * 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. + * + * QPlainTextEdit markdown highlighter + */ + + +#pragma once + +#include +#include +#include + +QT_BEGIN_NAMESPACE +class QTextDocument; + +QT_END_NAMESPACE + +class MarkdownHighlighter : public QSyntaxHighlighter +{ +Q_OBJECT + +public: + enum HighlightingOption{ + None = 0, + FullyHighlightedBlockQuote = 0x01 + }; + Q_DECLARE_FLAGS(HighlightingOptions, HighlightingOption) + + MarkdownHighlighter(QTextDocument *parent = nullptr, + HighlightingOptions highlightingOptions = + HighlightingOption::None); + + // we use some predefined numbers here to be compatible with + // the peg-markdown parser + enum HighlighterState { + NoState = -1, + Link = 0, + Image = 3, + CodeBlock, + Italic = 7, + Bold, + List, + Comment = 11, + H1, + H2, + H3, + H4, + H5, + H6, + BlockQuote, + HorizontalRuler = 21, + Table, + InlineCodeBlock, + MaskedSyntax, + CurrentLineBackgroundColor, + BrokenLink, + + // internal + CodeBlockEnd = 100, + HeadlineEnd + }; + Q_ENUM(HighlighterState) + +// enum BlockState { +// NoBlockState = 0, +// H1, +// H2, +// H3, +// Table, +// CodeBlock, +// CodeBlockEnd +// }; + + void setTextFormats(QHash formats); + void setTextFormat(HighlighterState state, QTextCharFormat format); + void clearDirtyBlocks(); + void setHighlightingOptions(HighlightingOptions options); + void initHighlightingRules(); + +signals: + void highlightingFinished(); + +protected slots: + void timerTick(); + +protected: + struct HighlightingRule { + HighlightingRule(const HighlighterState state_) : state(state_) {} + HighlightingRule() = default; + + QRegularExpression pattern; + HighlighterState state = NoState; + int capturingGroup = 0; + int maskedGroup = 0; + bool useStateAsCurrentBlockState = false; + bool disableIfCurrentStateIsSet = false; + }; + + void highlightBlock(const QString &text) Q_DECL_OVERRIDE; + + void initTextFormats(int defaultFontSize = 12); + + void highlightMarkdown(QString text); + + void highlightHeadline(QString text); + + void highlightAdditionalRules(QVector &rules, + QString text); + + void highlightCodeBlock(QString text); + + void highlightCommentBlock(QString text); + + void addDirtyBlock(QTextBlock block); + + void reHighlightDirtyBlocks(); + + QVector _highlightingRulesPre; + QVector _highlightingRulesAfter; + QVector _dirtyTextBlocks; + QHash _formats; + QTimer *_timer; + bool _highlightingFinished; + HighlightingOptions _highlightingOptions; + + void setCurrentBlockMargin(HighlighterState state); +}; diff --git a/client/qmarkdowntextedit/media.qrc b/client/qmarkdowntextedit/media.qrc new file mode 100644 index 0000000..0346de9 --- /dev/null +++ b/client/qmarkdowntextedit/media.qrc @@ -0,0 +1,9 @@ + + + media/window-close.svg + media/go-top.svg + media/go-bottom.svg + media/edit-find-replace.svg + media/format-text-superscript.svg + + diff --git a/client/qmarkdowntextedit/media/edit-find-replace.svg b/client/qmarkdowntextedit/media/edit-find-replace.svg new file mode 100644 index 0000000..c1c4671 --- /dev/null +++ b/client/qmarkdowntextedit/media/edit-find-replace.svg @@ -0,0 +1,15 @@ + + + + + + diff --git a/client/qmarkdowntextedit/media/format-text-superscript.svg b/client/qmarkdowntextedit/media/format-text-superscript.svg new file mode 100644 index 0000000..871845e --- /dev/null +++ b/client/qmarkdowntextedit/media/format-text-superscript.svg @@ -0,0 +1,16 @@ + + + + + + + + diff --git a/client/qmarkdowntextedit/media/go-bottom.svg b/client/qmarkdowntextedit/media/go-bottom.svg new file mode 100644 index 0000000..9b85210 --- /dev/null +++ b/client/qmarkdowntextedit/media/go-bottom.svg @@ -0,0 +1,156 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + diff --git a/client/qmarkdowntextedit/media/go-top.svg b/client/qmarkdowntextedit/media/go-top.svg new file mode 100644 index 0000000..50b4ca6 --- /dev/null +++ b/client/qmarkdowntextedit/media/go-top.svg @@ -0,0 +1,476 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + diff --git a/client/qmarkdowntextedit/media/window-close.svg b/client/qmarkdowntextedit/media/window-close.svg new file mode 100644 index 0000000..5d1539d --- /dev/null +++ b/client/qmarkdowntextedit/media/window-close.svg @@ -0,0 +1,148 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/client/qmarkdowntextedit/qmarkdowntextedit-headers.pri b/client/qmarkdowntextedit/qmarkdowntextedit-headers.pri new file mode 100644 index 0000000..646e593 --- /dev/null +++ b/client/qmarkdowntextedit/qmarkdowntextedit-headers.pri @@ -0,0 +1,6 @@ +INCLUDEPATH += $$PWD/ + +HEADERS += \ + $$PWD/markdownhighlighter.h \ + $$PWD/qmarkdowntextedit.h \ + $$PWD/qplaintexteditsearchwidget.h diff --git a/client/qmarkdowntextedit/qmarkdowntextedit-sources.pri b/client/qmarkdowntextedit/qmarkdowntextedit-sources.pri new file mode 100644 index 0000000..352e0bf --- /dev/null +++ b/client/qmarkdowntextedit/qmarkdowntextedit-sources.pri @@ -0,0 +1,14 @@ +INCLUDEPATH += $$PWD/ + +QT += gui +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets + +SOURCES += \ + $$PWD/markdownhighlighter.cpp \ + $$PWD/qmarkdowntextedit.cpp \ + $$PWD/qplaintexteditsearchwidget.cpp + +RESOURCES += \ + $$PWD/media.qrc + +FORMS += $$PWD/qplaintexteditsearchwidget.ui diff --git a/client/qmarkdowntextedit/qmarkdowntextedit.cpp b/client/qmarkdowntextedit/qmarkdowntextedit.cpp new file mode 100644 index 0000000..a6e786d --- /dev/null +++ b/client/qmarkdowntextedit/qmarkdowntextedit.cpp @@ -0,0 +1,1099 @@ +/* + * Copyright (c) 2014-2019 Patrizio Bekerle -- http://www.bekerle.com + * + * 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; version 2 of the License. + * + * 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. + * + */ + + +#include "qmarkdowntextedit.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +QMarkdownTextEdit::QMarkdownTextEdit(QWidget *parent, bool initHighlighter) + : QPlainTextEdit(parent) { + installEventFilter(this); + viewport()->installEventFilter(this); + _autoTextOptions = AutoTextOption::None; + _openingCharacters = QStringList() << "(" << "[" << "{" << "<" << "*" + << "\"" << "'" << "_" << "~"; + _closingCharacters = QStringList() << ")" << "]" << "}" << ">" << "*" + << "\"" << "'" << "_" << "~"; + + // markdown highlighting is enabled by default + _highlightingEnabled = true; + if (initHighlighter) { + _highlighter = new MarkdownHighlighter(document()); + } +// setHighlightingEnabled(true); + + QFont font = this->font(); + + // set the tab stop to the width of 4 spaces in the editor + const int tabStop = 4; + QFontMetrics metrics(font); + setTabStopWidth(tabStop * metrics.width(' ')); + + // add shortcuts for duplicating text +// new QShortcut( QKeySequence( "Ctrl+D" ), this, SLOT( duplicateText() ) ); +// new QShortcut( QKeySequence( "Ctrl+Alt+Down" ), this, SLOT( duplicateText() ) ); + + // add a layout to the widget + QVBoxLayout *layout = new QVBoxLayout; + layout->setContentsMargins(0, 0, 0, 0); + layout->setMargin(0); + layout->addStretch(); + this->setLayout(layout); + + // add the hidden search widget + _searchWidget = new QPlainTextEditSearchWidget(this); + this->layout()->addWidget(_searchWidget); + + QObject::connect(this, SIGNAL(textChanged()), + this, SLOT(adjustRightMargin())); + + // workaround for disabled signals up initialization + QTimer::singleShot(300, this, SLOT(adjustRightMargin())); +} + +/** + * Enables or disables the markdown highlighting + * + * @param enabled + */ +void QMarkdownTextEdit::setHighlightingEnabled(bool enabled) { + if (_highlightingEnabled == enabled) { + return; + } + + _highlighter->setDocument(enabled ? document() : Q_NULLPTR); + _highlightingEnabled = enabled; + + if (enabled) { + _highlighter->rehighlight(); + } +} + +/** + * Leave a little space on the right side if the document is too long, so + * that the search buttons don't get visually blocked by the scroll bar + */ +void QMarkdownTextEdit::adjustRightMargin() { + QMargins margins = layout()->contentsMargins(); + int rightMargin = document()->size().height() > + viewport()->size().height() ? 24 : 0; + margins.setRight(rightMargin); + layout()->setContentsMargins(margins); +} + +bool QMarkdownTextEdit::eventFilter(QObject *obj, QEvent *event) { + //qDebug() << event->type(); + if (event->type() == QEvent::HoverMove) { + QMouseEvent *mouseEvent = static_cast(event); + + QWidget *viewPort = this->viewport(); + // toggle cursor when control key has been pressed or released + viewPort->setCursor(mouseEvent->modifiers().testFlag( + Qt::ControlModifier) ? + Qt::PointingHandCursor : + Qt::IBeamCursor); + } else if (event->type() == QEvent::KeyPress) { + QKeyEvent *keyEvent = static_cast(event); + + // set cursor to pointing hand if control key was pressed + if (keyEvent->modifiers().testFlag(Qt::ControlModifier)) { + QWidget *viewPort = this->viewport(); + viewPort->setCursor(Qt::PointingHandCursor); + } + + // disallow keys if text edit hasn't focus + if (!this->hasFocus()) { + return true; + } + + if ((keyEvent->key() == Qt::Key_Escape) && _searchWidget->isVisible()) { + _searchWidget->deactivate(); + return true; + } else if ((keyEvent->key() == Qt::Key_Tab) || + (keyEvent->key() == Qt::Key_Backtab)) { + // handle entered tab and reverse tab keys + return handleTabEntered( + keyEvent->key() == Qt::Key_Backtab); + } else if ((keyEvent->key() == Qt::Key_F) && + keyEvent->modifiers().testFlag(Qt::ControlModifier)) { + _searchWidget->activate(); + return true; + } else if ((keyEvent->key() == Qt::Key_R) && + keyEvent->modifiers().testFlag(Qt::ControlModifier)) { + _searchWidget->activateReplace(); + return true; +// } else if (keyEvent->key() == Qt::Key_Delete) { + } else if (keyEvent->key() == Qt::Key_Backspace) { + return handleBracketRemoval(); + } else if (keyEvent->key() == Qt::Key_Asterisk) { + return handleBracketClosing("*"); + } else if (keyEvent->key() == Qt::Key_QuoteDbl) { + return quotationMarkCheck("\""); + // apostrophe bracket closing is temporary disabled because + // apostrophes are used in different contexts +// } else if (keyEvent->key() == Qt::Key_Apostrophe) { +// return handleBracketClosing("'"); + // underline bracket closing is temporary disabled because + // underlines are used in different contexts +// } else if (keyEvent->key() == Qt::Key_Underscore) { +// return handleBracketClosing("_"); + } + else if (keyEvent->key() == Qt::Key_QuoteLeft) { + return quotationMarkCheck("`"); + } else if (keyEvent->key() == Qt::Key_AsciiTilde) { + return handleBracketClosing("~"); +#ifdef Q_OS_MAC + } else if (keyEvent->modifiers().testFlag(Qt::AltModifier) && + keyEvent->key() == Qt::Key_ParenLeft) { + // bracket closing for US keyboard on macOS + return handleBracketClosing("{", "}"); +#endif + } else if (keyEvent->key() == Qt::Key_ParenLeft) { + return handleBracketClosing("(", ")"); + } else if (keyEvent->key() == Qt::Key_BraceLeft) { + return handleBracketClosing("{", "}"); + } else if (keyEvent->key() == Qt::Key_BracketLeft) { + return handleBracketClosing("[", "]"); + } else if (keyEvent->key() == Qt::Key_Less) { + return handleBracketClosing("<", ">"); +#ifdef Q_OS_MAC + } else if (keyEvent->modifiers().testFlag(Qt::AltModifier) && + keyEvent->key() == Qt::Key_ParenRight) { + // bracket closing for US keyboard on macOS + return bracketClosingCheck("{", "}"); +#endif + } else if (keyEvent->key() == Qt::Key_ParenRight) { + return bracketClosingCheck("(", ")"); + } else if (keyEvent->key() == Qt::Key_BraceRight) { + return bracketClosingCheck("{", "}"); + } else if (keyEvent->key() == Qt::Key_BracketRight) { + return bracketClosingCheck("[", "]"); + } else if (keyEvent->key() == Qt::Key_Return && + keyEvent->modifiers().testFlag(Qt::ShiftModifier)) { + QTextCursor cursor = this->textCursor(); + cursor.insertText(" \n"); + return true; + } else if (keyEvent->key() == Qt::Key_Return && + keyEvent->modifiers().testFlag(Qt::ControlModifier)) { + QTextCursor cursor = this->textCursor(); + cursor.movePosition(QTextCursor::EndOfLine); + cursor.insertText("\n"); + setTextCursor(cursor); + return true; + } else if (keyEvent == QKeySequence::Copy || keyEvent == QKeySequence::Cut) { + QTextCursor cursor = this->textCursor(); + if (!cursor.hasSelection()) { + QString text; + if (cursor.block().length() <= 1) // no content + text = "\n"; + else { + //cursor.select(QTextCursor::BlockUnderCursor); // negative, it will include the previous paragraph separator + cursor.movePosition(QTextCursor::StartOfBlock); + cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor); + text = cursor.selectedText(); + if (!cursor.atEnd()) { + text += "\n"; + // this is the paragraph separator + cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, 1); + } + } + if (keyEvent == QKeySequence::Cut) { + if (!cursor.atEnd() && text == "\n") + cursor.deletePreviousChar(); + else + cursor.removeSelectedText(); + cursor.movePosition(QTextCursor::StartOfLine); + setTextCursor(cursor); + } + qApp->clipboard()->setText(text); + return true; + } + } + else if (keyEvent == QKeySequence::Paste) { + if (qApp->clipboard()->ownsClipboard() && + QRegExp("[^\n]*\n$").exactMatch(qApp->clipboard()->text())) { + QTextCursor cursor = this->textCursor(); + if (!cursor.hasSelection()) { + cursor.movePosition(QTextCursor::StartOfLine); + setTextCursor(cursor); + } + } + } else if ((keyEvent->key() == Qt::Key_Down) && + keyEvent->modifiers().testFlag(Qt::ControlModifier) && + keyEvent->modifiers().testFlag(Qt::AltModifier)) { + // duplicate text with `Ctrl + Alt + Down` + duplicateText(); + return true; +#ifndef Q_OS_MAC + } else if ((keyEvent->key() == Qt::Key_Down) && + keyEvent->modifiers().testFlag(Qt::ControlModifier)) { + // scroll the page down + auto *scrollBar = verticalScrollBar(); + scrollBar->setSliderPosition(scrollBar->sliderPosition() + 1); + return true; + } else if ((keyEvent->key() == Qt::Key_Up) && + keyEvent->modifiers().testFlag(Qt::ControlModifier)) { + // scroll the page up + auto *scrollBar = verticalScrollBar(); + scrollBar->setSliderPosition(scrollBar->sliderPosition() - 1); + return true; +#endif + } else if ((keyEvent->key() == Qt::Key_Down) && + keyEvent->modifiers().testFlag(Qt::NoModifier)) { + // if you are in the last line and press cursor down the cursor will + // jump to the end of the line + QTextCursor cursor = textCursor(); + if (cursor.position() >= document()->lastBlock().position()) { + cursor.movePosition(QTextCursor::EndOfLine); + setTextCursor(cursor); + } + return false; + } else if ((keyEvent->key() == Qt::Key_Up) && + keyEvent->modifiers().testFlag(Qt::NoModifier)) { + // if you are in the first line and press cursor up the cursor will + // jump to the start of the line + QTextCursor cursor = textCursor(); + QTextBlock block = document()->firstBlock(); + int endOfFirstLinePos = block.position() + block.length(); + + if (cursor.position() <= endOfFirstLinePos) { + cursor.movePosition(QTextCursor::StartOfLine); + setTextCursor(cursor); + } + return false; + } else if (keyEvent->key() == Qt::Key_Return) { + return handleReturnEntered(); + } else if ((keyEvent->key() == Qt::Key_F3)) { + _searchWidget->doSearch( + !keyEvent->modifiers().testFlag(Qt::ShiftModifier)); + return true; + } + + return false; + } else if (event->type() == QEvent::KeyRelease) { + QKeyEvent *keyEvent = static_cast(event); + + // reset cursor if control key was released + if (keyEvent->key() == Qt::Key_Control) { + resetMouseCursor(); + } + + return false; + } else if (event->type() == QEvent::MouseButtonRelease) { + QMouseEvent *mouseEvent = static_cast(event); + + // track `Ctrl + Click` in the text edit + if ((obj == this->viewport()) && + (mouseEvent->button() == Qt::LeftButton) && + (QGuiApplication::keyboardModifiers() == Qt::ExtraButton24)) { + // open the link (if any) at the current position + // in the noteTextEdit + openLinkAtCursorPosition(); + return true; + } + } + + return QPlainTextEdit::eventFilter(obj, event); +} + +/** + * Resets the cursor to Qt::IBeamCursor + */ +void QMarkdownTextEdit::resetMouseCursor() const { + QWidget *viewPort = viewport(); + viewPort->setCursor(Qt::IBeamCursor); +} + +/** + * Resets the cursor to Qt::IBeamCursor if the widget looses the focus + */ +void QMarkdownTextEdit::focusOutEvent(QFocusEvent *event) { + resetMouseCursor(); + QPlainTextEdit::focusOutEvent(event); +} + +/** + * Enters a closing character after an opening character if needed + * + * @param openingCharacter + * @param closingCharacter + * @return + */ +bool QMarkdownTextEdit::handleBracketClosing(QString openingCharacter, + QString closingCharacter) { + // check if bracket closing is enabled + if (!(_autoTextOptions & AutoTextOption::BracketClosing)) { + return false; + } + + QTextCursor cursor = textCursor(); + + // get the current text from the block (inserted character not included) + QString text = cursor.block().text(); + + if (closingCharacter.isEmpty()) { + closingCharacter = openingCharacter; + } + + QString selectedText = cursor.selectedText(); + + // When user currently has text selected, we prepend the openingCharacter + // and append the closingCharacter. E.g. 'text' -> '(text)'. We keep the + // current selectedText selected. + // + // TODO(sanderboom): how to make ctrl-z keep the selectedText selected? + if (selectedText != "") { + // Insert. The selectedText is overwritten. + cursor.insertText(openingCharacter); + cursor.insertText(selectedText); + cursor.insertText(closingCharacter); + + // Re-select the selectedText. + int selectionEnd = cursor.position() - 1; + int selectionStart = selectionEnd - selectedText.length(); + cursor.setPosition(selectionStart); + cursor.setPosition(selectionEnd, QTextCursor::KeepAnchor); + this->setTextCursor(cursor); + + return true; + } else { + // if not text was selected check if we are inside the text + int positionInBlock = cursor.position() - cursor.block().position(); + + // only allow the closing if the cursor was at the end of a block + // we are making a special allowance for openingCharacter == * + if ((positionInBlock != text.count()) && + !((openingCharacter == "*") && + (positionInBlock == (text.count() - 1)))) { + return false; + } + } + + + // Remove whitespace at start of string (e.g. in multilevel-lists). + text = text.remove(QRegExp("^\\s+")); + + // Default positions to move the cursor back. + int cursorSubtract = 1; + + // Special handling for `*` opening character, as this could be: + // - start of a list (or sublist); + // - start of a bold text; + if (openingCharacter == "*") { + // User wants: '*'. + // This could be the start of a list, don't autocomplete. + if (text == "") { + return false; + } + // User wants: '**'. + // Not the start of a list, probably bold text. We autocomplete with + // extra closingCharacter and cursorSubtract to 'catchup'. + else if (text == "*") { + closingCharacter = "**"; + cursorSubtract = 2; + } + // User wants: '* *'. + // We are in a list already, proceed as normal (autocomplete). + else if (text == "* ") { + // no-op. + } + } + + // Auto completion for ``` pair + if (openingCharacter == "`") { + if (QRegExp("[^`]*``").exactMatch(text)) { + cursor.insertText(openingCharacter); + cursor.insertText(openingCharacter); + cursorSubtract = 3; + } + } + + cursor.insertText(openingCharacter); + cursor.insertText(closingCharacter); + cursor.setPosition(cursor.position() - cursorSubtract); + setTextCursor(cursor); + return true; +} + +/** + * Checks if the closing character should be output or not + * + * @param openingCharacter + * @param closingCharacter + * @return + */ +bool QMarkdownTextEdit::bracketClosingCheck(QString openingCharacter, + QString closingCharacter) { + // check if bracket closing is enabled + if (!(_autoTextOptions & AutoTextOption::BracketClosing)) { + return false; + } + + if (closingCharacter.isEmpty()) { + closingCharacter = openingCharacter; + } + + QTextCursor cursor = textCursor(); + int positionInBlock = cursor.position() - cursor.block().position(); + + // get the current text from the block + QString text = cursor.block().text(); + int textLength = text.length(); + + // if we are at the end of the line we just want to enter the character + if (positionInBlock >= textLength) { + return false; + } + + QString currentChar = text.at(positionInBlock); + + if (closingCharacter == openingCharacter) { + + } + + qDebug() << __func__ << " - 'currentChar': " << currentChar; + + + // if the current character is not the closing character we just want to + // enter the character + if (currentChar != closingCharacter) { + return false; + } + + QString leftText = text.left(positionInBlock); + int openingCharacterCount = leftText.count(openingCharacter); + int closingCharacterCount = leftText.count(closingCharacter); + + // if there were enough opening characters just enter the character + if (openingCharacterCount < (closingCharacterCount + 1)) { + return false; + } + + // move the cursor to the right and don't enter the character + cursor.movePosition(QTextCursor::Right); + setTextCursor(cursor); + return true; +} + +/** + * Checks if the closing character should be output or not or if a closing + * character after an opening character if needed + * + * @param quotationCharacter + * @return + */ +bool QMarkdownTextEdit::quotationMarkCheck(QString quotationCharacter) { + // check if bracket closing is enabled + if (!(_autoTextOptions & AutoTextOption::BracketClosing)) { + return false; + } + + QTextCursor cursor = textCursor(); + int positionInBlock = cursor.position() - cursor.block().position(); + + // get the current text from the block + QString text = cursor.block().text(); + int textLength = text.length(); + + // if we are at the end of the line we just want to enter the character + if (positionInBlock >= textLength) { + return handleBracketClosing(quotationCharacter); + } + + QString currentChar = text.at(positionInBlock); + + // if the current character is not the quotation character we just want to + // enter the character + if (currentChar != quotationCharacter) { + return handleBracketClosing(quotationCharacter); + } + + // move the cursor to the right and don't enter the character + cursor.movePosition(QTextCursor::Right); + setTextCursor(cursor); + return true; +} + +/** + * Handles removing of matching brackets and other markdown characters + * Only works with backspace to remove text + * + * @return + */ +bool QMarkdownTextEdit::handleBracketRemoval() { + // check if bracket removal is enabled + if (!(_autoTextOptions & AutoTextOption::BracketRemoval)) { + return false; + } + + QTextCursor cursor = textCursor(); + + // return if some text was selected + if (!cursor.selectedText().isEmpty()) { + return false; + } + + int position = cursor.position(); + int positionInBlock = position - cursor.block().position(); + + // return if backspace was pressed at the beginning of a block + if (positionInBlock == 0) { + return false; + } + + // get the current text from the block + QString text = cursor.block().text(); + QString charInFront = text.at(positionInBlock - 1); + int openingCharacterIndex = _openingCharacters.indexOf(charInFront); + + // return if the character in front of the cursor is no opening character + if (openingCharacterIndex == -1) { + return false; + } + + QString closingCharacter = _closingCharacters.at(openingCharacterIndex); + + // remove everything in front of the cursor + text.remove(0, positionInBlock); + int closingCharacterIndex = text.indexOf(closingCharacter); + + // return if no closing character was found in the text after the cursor + if (closingCharacterIndex == -1) { + return false; + } + + // removing the closing character + cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, + closingCharacterIndex); + cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor); + cursor.removeSelectedText(); + + // moving the cursor back to the old position so the previous character + // can be removed + cursor.setPosition(position); + setTextCursor(cursor); + return false; +} + +/** + * Increases (or decreases) the indention of the selected text + * (if there is a text selected) in the noteTextEdit + * @return + */ +bool QMarkdownTextEdit::increaseSelectedTextIndention(bool reverse) { + QTextCursor cursor = this->textCursor(); + QString selectedText = cursor.selectedText(); + + if (selectedText != "") { + // we need this strange newline character we are getting in the + // selected text for newlines + QString newLine = QString::fromUtf8(QByteArray::fromHex("e280a9")); + QString newText; + + if (reverse) { + // un-indent text + + // remove strange newline characters + newText = selectedText.replace( + QRegularExpression(newLine + "[\\t ]"), "\n"); + + // remove leading \t or space + newText.remove(QRegularExpression("^[\\t ]")); + } else { + // indent text + newText = selectedText.replace(newLine, "\n\t").prepend("\t"); + + // remove trailing \t + newText.replace(QRegularExpression("\\t$"), ""); + } + + // insert the new text + cursor.insertText(newText); + + // update the selection to the new text + cursor.setPosition(cursor.position() - newText.size(), QTextCursor::KeepAnchor); + this->setTextCursor(cursor); + + return true; + } else if (reverse) { + // if nothing was selected but we want to reverse the indention check + // if there is a \t in front or after the cursor and remove it if so + int position = cursor.position(); + + if (!cursor.atStart()) { + // get character in front of cursor + cursor.setPosition(position - 1, QTextCursor::KeepAnchor); + } + + // check for \t or space in front of cursor + QRegularExpression re("[\\t ]"); + QRegularExpressionMatch match = re.match(cursor.selectedText()); + + if (!match.hasMatch()) { + // (select to) check for \t or space after the cursor + cursor.setPosition(position); + + if (!cursor.atEnd()) { + cursor.setPosition(position + 1, QTextCursor::KeepAnchor); + } + } + + match = re.match(cursor.selectedText()); + + if (match.hasMatch()) { + cursor.removeSelectedText(); + } + + return true; + } + + return false; +} + +/** + * @brief Opens the link (if any) at the current cursor position + */ +bool QMarkdownTextEdit::openLinkAtCursorPosition() { + QTextCursor cursor = this->textCursor(); + int clickedPosition = cursor.position(); + + // select the text in the clicked block and find out on + // which position we clicked + cursor.movePosition(QTextCursor::StartOfBlock); + int positionFromStart = clickedPosition - cursor.position(); + cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor); + + QString selectedText = cursor.selectedText(); + + // find out which url in the selected text was clicked + QString urlString = getMarkdownUrlAtPosition(selectedText, + positionFromStart); + QUrl url = QUrl(urlString); + bool isRelativeFileUrl = urlString.startsWith("file://.."); + + qDebug() << __func__ << " - 'emit urlClicked( urlString )': " + << urlString; + + emit urlClicked(urlString); + + if ((url.isValid() && isValidUrl(urlString)) || isRelativeFileUrl) { + // ignore some schemata + if (!(_ignoredClickUrlSchemata.contains(url.scheme()) || + isRelativeFileUrl)) { + // open the url + openUrl(urlString); + } + + return true; + } + + return false; +} + +/** + * Checks if urlString is a valid url + * + * @param urlString + * @return + */ +bool QMarkdownTextEdit::isValidUrl(QString urlString) { + QRegularExpressionMatch match = + QRegularExpression("^\\w+:\\/\\/.+").match(urlString); + return match.hasMatch(); +} + +/** + * Handles clicked urls + * + * examples: + * - opens the webpage + * - opens the file + * "/path/to/my/file/QOwnNotes.pdf" if the operating system supports that + * handler + */ +void QMarkdownTextEdit::openUrl(QString urlString) { + qDebug() << "QMarkdownTextEdit " << __func__ << " - 'urlString': " + << urlString; + + QDesktopServices::openUrl(QUrl(urlString)); +} + +/** + * @brief Returns the highlighter instance + * @return + */ +MarkdownHighlighter *QMarkdownTextEdit::highlighter() { + return _highlighter; +} + +/** + * @brief Returns the searchWidget instance + * @return + */ +QPlainTextEditSearchWidget *QMarkdownTextEdit::searchWidget() { + return _searchWidget; +} + +/** + * @brief Sets url schemata that will be ignored when clicked on + * @param urlSchemes + */ +void QMarkdownTextEdit::setIgnoredClickUrlSchemata( + QStringList ignoredUrlSchemata) { + _ignoredClickUrlSchemata = ignoredUrlSchemata; +} + +/** + * @brief Returns a map of parsed markdown urls with their link texts as key + * + * @param text + * @return parsed urls + */ +QMap QMarkdownTextEdit::parseMarkdownUrlsFromText( + QString text) { + QMap urlMap; + QRegularExpression regex; + QRegularExpressionMatchIterator iterator; + + // match urls like this: +// re = QRegularExpression("(<(.+?:\\/\\/.+?)>)"); + regex = QRegularExpression("(<(.+?)>)"); + iterator = regex.globalMatch(text); + while (iterator.hasNext()) { + QRegularExpressionMatch match = iterator.next(); + QString linkText = match.captured(1); + QString url = match.captured(2); + urlMap[linkText] = url; + } + + // match urls like this: [this url](http://mylink) +// QRegularExpression re("(\\[.*?\\]\\((.+?:\\/\\/.+?)\\))"); + regex = QRegularExpression("(\\[.*?\\]\\((.+?)\\))"); + iterator = regex.globalMatch(text); + while (iterator.hasNext()) { + QRegularExpressionMatch match = iterator.next(); + QString linkText = match.captured(1); + QString url = match.captured(2); + urlMap[linkText] = url; + } + + // match urls like this: http://mylink + regex = QRegularExpression("\\b\\w+?:\\/\\/[^\\s]+[^\\s>\\)]"); + iterator = regex.globalMatch(text); + while (iterator.hasNext()) { + QRegularExpressionMatch match = iterator.next(); + QString url = match.captured(0); + urlMap[url] = url; + } + + // match reference urls like this: [this url][1] with this later: + // [1]: http://domain + regex = QRegularExpression("\\[(.*?)\\]\\s?\\[(.+?)\\]"); + iterator = regex.globalMatch(text); + while (iterator.hasNext()) { + QRegularExpressionMatch match = iterator.next(); + QString linkText = match.captured(1); + QString referenceId = match.captured(2); + + // search for the referenced url in the whole text edit +// QRegularExpression refRegExp( +// "\\[" + QRegularExpression::escape(referenceId) + +// "\\]: (.+?:\\/\\/.+)"); + QRegularExpression refRegExp( + "\\[" + QRegularExpression::escape(referenceId) + "\\]: (.+?)"); + QRegularExpressionMatch urlMatch = refRegExp.match(toPlainText()); + + if (urlMatch.hasMatch()) { + QString url = urlMatch.captured(1); + urlMap[linkText] = url; + } + } + + return urlMap; +} + +/** + * @brief Returns the markdown url at position + * @param text + * @param position + * @return url string + */ +QString QMarkdownTextEdit::getMarkdownUrlAtPosition( + QString text, int position) { + QString url; + + // get a map of parsed markdown urls with their link texts as key + QMap urlMap = parseMarkdownUrlsFromText(text); + + QMapIterator iterator(urlMap); + while (iterator.hasNext()) { + iterator.next(); + QString linkText = iterator.key(); + QString urlString = iterator.value(); + + int foundPositionStart = text.indexOf(linkText); + + if (foundPositionStart >= 0) { + // calculate end position of found linkText + int foundPositionEnd = foundPositionStart + linkText.size(); + + // check if position is in found string range + if ((position >= foundPositionStart) && + (position <= foundPositionEnd)) { + url = urlString; + break; + } + } + } + + return url; +} + +/** + * @brief Duplicates the text in the text edit + */ +void QMarkdownTextEdit::duplicateText() { + QTextCursor cursor = this->textCursor(); + QString selectedText = cursor.selectedText(); + + // duplicate line if no text was selected + if (selectedText == "") { + int position = cursor.position(); + + // select the whole line + cursor.movePosition(QTextCursor::StartOfLine); + cursor.movePosition(QTextCursor::EndOfLine, QTextCursor::KeepAnchor); + + int positionDiff = cursor.position() - position; + selectedText = "\n" + cursor.selectedText(); + + // insert text with new line at end of the selected line + cursor.setPosition(cursor.selectionEnd()); + cursor.insertText(selectedText); + + // set the position to same position it was in the duplicated line + cursor.setPosition(cursor.position() - positionDiff); + } else { + // duplicate selected text + cursor.setPosition(cursor.selectionEnd()); + int selectionStart = cursor.position(); + + // insert selected text + cursor.insertText(selectedText); + int selectionEnd = cursor.position(); + + // select the inserted text + cursor.setPosition(selectionStart); + cursor.setPosition(selectionEnd, QTextCursor::KeepAnchor); + } + + this->setTextCursor(cursor); +} + +void QMarkdownTextEdit::setText(const QString & text) { + setPlainText(text); +} + +void QMarkdownTextEdit::setPlainText(const QString & text) { + // clear the dirty blocks vector to increase performance and prevent + // a possible crash in QSyntaxHighlighter::rehighlightBlock + _highlighter->clearDirtyBlocks(); + + QPlainTextEdit::setPlainText(text); + adjustRightMargin(); +} + +/** + * Uses an other widget as parent for the search widget + */ +void QMarkdownTextEdit::initSearchFrame(QWidget *searchFrame, bool darkMode) { + _searchFrame = searchFrame; + + // remove the search widget from our layout + layout()->removeWidget(_searchWidget); + + QLayout *layout = _searchFrame->layout(); + + // create a grid layout for the frame and add the search widget to it + if (layout == NULL) { + layout = new QVBoxLayout(); + layout->setSpacing(0); + layout->setContentsMargins(0, 0, 0, 0); + } + + _searchWidget->setDarkMode(darkMode); + layout->addWidget(_searchWidget); + _searchFrame->setLayout(layout); +} + +/** + * Hides the text edit and the search widget + */ +void QMarkdownTextEdit::hide() { + _searchWidget->hide(); + QWidget::hide(); +} + +/** + * Handles an entered return key + */ +bool QMarkdownTextEdit::handleReturnEntered() { + QTextCursor cursor = this->textCursor(); + int position = cursor.position(); + + cursor.movePosition(QTextCursor::StartOfBlock, QTextCursor::KeepAnchor); + QString currentLineText = cursor.selectedText(); + + // if return is pressed and there is just a list symbol then we want to + // remove the list symbol + // Valid listCharacters: '+ ', '-' , '* ', '+ [ ] ', '+ [x] ', '- [ ] ', '- [x] ', '* [ ] ', '* [x] '. + QRegularExpression regex("^(\\s*)([+|\\-|\\*] \\[(x| )\\]|[+\\-\\*])(\\s+)$"); + QRegularExpressionMatchIterator iterator = regex.globalMatch(currentLineText); + if (iterator.hasNext()) { + cursor.removeSelectedText(); + return true; + } + + // Check if we are in a list. + // We are in a list when we have '* ', '- ' or '+ ', possibly with preceding + // whitespace. If e.g. user has entered '**text**' and pressed enter - we + // don't want do anymore list-stuff. + QChar char0 = currentLineText.trimmed()[0]; + QChar char1 = currentLineText.trimmed()[1]; + bool inList = ((char0 == '*' || char0 == '-' || char0 == '+') && char1 == ' '); + + if (inList) { + // if the current line starts with a list character (possibly after + // whitespaces) add the whitespaces at the next line too + // Valid listCharacters: '+ ', '-' , '* ', '+ [ ] ', '+ [x] ', '- [ ] ', '- [x] ', '* [ ] ', '* [x] '. + regex = QRegularExpression("^(\\s*)([+|\\-|\\*] \\[(x| )\\]|[+\\-\\*])(\\s+)"); + iterator = regex.globalMatch(currentLineText); + if (iterator.hasNext()) { + QRegularExpressionMatch match = iterator.next(); + QString whitespaces = match.captured(1); + QString listCharacter = match.captured(2); + QString whitespaceCharacter = match.captured(4); + + cursor.setPosition(position); + cursor.insertText("\n" + whitespaces + listCharacter + whitespaceCharacter); + + // scroll to the cursor if we are at the bottom of the document + ensureCursorVisible(); + return true; + } + } + + return false; +} + +/** + * Handles entered tab or reverse tab keys + */ +bool QMarkdownTextEdit::handleTabEntered(bool reverse) { + QTextCursor cursor = this->textCursor(); + + // only check for lists if we haven't a text selected + if (cursor.selectedText().isEmpty()) { + cursor.movePosition(QTextCursor::StartOfLine, QTextCursor::KeepAnchor); + QString currentLineText = cursor.selectedText(); + + // check if we want to indent or un-indent a list + // Valid listCharacters: '+ ', '-' , '* ', '+ [ ] ', '+ [x] ', '- [ ] ', '- [x] ', '* [ ] ', '* [x] '. + QRegularExpression re("^(\\s*)([+|\\-|\\*] \\[(x| )\\]|[+\\-\\*])(\\s+)$"); + QRegularExpressionMatchIterator i = re.globalMatch(currentLineText); + + if (i.hasNext()) { + QRegularExpressionMatch match = i.next(); + QString whitespaces = match.captured(1); + QString listCharacter = match.captured(2); + QString whitespaceCharacter = match.captured(4); + + // add or remove one tabulator key + if (reverse) { + whitespaces.chop(1); + } else { + whitespaces += "\t"; + } + + cursor.insertText(whitespaces + listCharacter + whitespaceCharacter); + return true; + } + } + + // check if we want to intent the whole text + return increaseSelectedTextIndention(reverse); +} + +/** + * Sets the auto text options + */ +void QMarkdownTextEdit::setAutoTextOptions(AutoTextOptions options) { + _autoTextOptions = options; +} + +/** + * Overrides QPlainTextEdit::paintEvent to fix the RTL bug of QPlainTextEdit + * + * @param e + */ +void QMarkdownTextEdit::paintEvent(QPaintEvent *e) { + QTextBlock block = firstVisibleBlock(); + + while (block.isValid()) { + QTextLayout *layout = block.layout(); + + // this fixes the RTL bug of QPlainTextEdit + // https://bugreports.qt.io/browse/QTBUG-7516 + if (block.text().isRightToLeft()) + { + QTextOption opt = document()->defaultTextOption(); + opt = QTextOption(Qt::AlignRight); + opt.setTextDirection(Qt::RightToLeft); + layout->setTextOption(opt); + } + + block = block.next(); + } + + QPlainTextEdit::paintEvent(e); +} + +/** + * Overrides QPlainTextEdit::setReadOnly to fix a problem with Chinese and + * Japanese input methods + * + * @param ro + */ +void QMarkdownTextEdit::setReadOnly(bool ro) { + QPlainTextEdit::setReadOnly(ro); + + // attempted to fix a problem with Chinese and Japanese input methods + // @see https://github.com/pbek/QOwnNotes/issues/976 + setAttribute(Qt::WA_InputMethodEnabled, !isReadOnly()); +} diff --git a/client/qmarkdowntextedit/qmarkdowntextedit.h b/client/qmarkdowntextedit/qmarkdowntextedit.h new file mode 100644 index 0000000..5463f70 --- /dev/null +++ b/client/qmarkdowntextedit/qmarkdowntextedit.h @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2014-2019 Patrizio Bekerle -- http://www.bekerle.com + * + * 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; version 2 of the License. + * + * 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. + * + */ + +#pragma once + +#include +#include +#include "markdownhighlighter.h" +#include "qplaintexteditsearchwidget.h" + + +class QMarkdownTextEdit : public QPlainTextEdit +{ + Q_OBJECT + +public: + enum AutoTextOption { + None = 0x0000, + + // inserts closing characters for brackets and markdown characters + BracketClosing = 0x0001, + + // removes matching brackets and markdown characters + BracketRemoval = 0x0002 + }; + + Q_DECLARE_FLAGS(AutoTextOptions, AutoTextOption) + + explicit QMarkdownTextEdit(QWidget *parent = 0, bool initHighlighter = true); + MarkdownHighlighter *highlighter(); + QPlainTextEditSearchWidget *searchWidget(); + void setIgnoredClickUrlSchemata(QStringList ignoredUrlSchemata); + virtual void openUrl(QString urlString); + QString getMarkdownUrlAtPosition(QString text, int position); + void initSearchFrame(QWidget *searchFrame, bool darkMode = false); + void setAutoTextOptions(AutoTextOptions options); + void setHighlightingEnabled(bool enabled); + static bool isValidUrl(QString urlString); + void resetMouseCursor() const; + void setReadOnly(bool ro); + +public slots: + void duplicateText(); + void setText(const QString & text); + void setPlainText(const QString & text); + void adjustRightMargin(); + void hide(); + bool openLinkAtCursorPosition(); + bool handleBracketRemoval(); + +protected: + MarkdownHighlighter *_highlighter; + bool _highlightingEnabled; + QStringList _ignoredClickUrlSchemata; + QPlainTextEditSearchWidget *_searchWidget; + QWidget *_searchFrame; + AutoTextOptions _autoTextOptions; + QStringList _openingCharacters; + QStringList _closingCharacters; + + bool eventFilter(QObject *obj, QEvent *event); + bool increaseSelectedTextIndention(bool reverse); + bool handleTabEntered(bool reverse); + QMap parseMarkdownUrlsFromText(QString text); + bool handleReturnEntered(); + bool handleBracketClosing(QString openingCharacter, + QString closingCharacter = ""); + bool bracketClosingCheck(QString openingCharacter, + QString closingCharacter); + bool quotationMarkCheck(QString quotationCharacter); + void focusOutEvent(QFocusEvent *event); + void paintEvent(QPaintEvent *e); + +signals: + void urlClicked(QString url); +}; diff --git a/client/qmarkdowntextedit/qmarkdowntextedit.pri b/client/qmarkdowntextedit/qmarkdowntextedit.pri new file mode 100644 index 0000000..77112d3 --- /dev/null +++ b/client/qmarkdowntextedit/qmarkdowntextedit.pri @@ -0,0 +1,4 @@ +INCLUDEPATH += $$PWD/ + +include($$PWD/qmarkdowntextedit-headers.pri) +include($$PWD/qmarkdowntextedit-sources.pri) diff --git a/client/qmarkdowntextedit/qmarkdowntextedit.pro b/client/qmarkdowntextedit/qmarkdowntextedit.pro new file mode 100644 index 0000000..518ae85 --- /dev/null +++ b/client/qmarkdowntextedit/qmarkdowntextedit.pro @@ -0,0 +1,23 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2016-01-11T16:56:21 +# +#------------------------------------------------- + +QT += core gui + +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets + +TARGET = QMarkdownTextedit +TEMPLATE = app +TRANSLATIONS = trans/qmarkdowntextedit_de.ts +CONFIG += c++11 + +SOURCES += main.cpp \ + mainwindow.cpp \ + +HEADERS += mainwindow.h + +FORMS += mainwindow.ui + +include(qmarkdowntextedit.pri) diff --git a/client/qmarkdowntextedit/qplaintexteditsearchwidget.cpp b/client/qmarkdowntextedit/qplaintexteditsearchwidget.cpp new file mode 100644 index 0000000..54fb975 --- /dev/null +++ b/client/qmarkdowntextedit/qplaintexteditsearchwidget.cpp @@ -0,0 +1,266 @@ +/* + * Copyright (c) 2014-2019 Patrizio Bekerle -- http://www.bekerle.com + * + * 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; version 2 of the License. + * + * 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. + * + */ + +#include "qplaintexteditsearchwidget.h" +#include "ui_qplaintexteditsearchwidget.h" +#include +#include +#include + +QPlainTextEditSearchWidget::QPlainTextEditSearchWidget(QPlainTextEdit *parent) : + QWidget(parent), + ui(new Ui::QPlainTextEditSearchWidget) +{ + ui->setupUi(this); + _textEdit = parent; + _darkMode = false; + hide(); + + QObject::connect(ui->closeButton, SIGNAL(clicked()), + this, SLOT(deactivate())); + QObject::connect(ui->searchLineEdit, SIGNAL(textChanged(const QString &)), + this, SLOT(searchLineEditTextChanged(const QString &))); + QObject::connect(ui->searchDownButton, SIGNAL(clicked()), + this, SLOT(doSearchDown())); + QObject::connect(ui->searchUpButton, SIGNAL(clicked()), + this, SLOT(doSearchUp())); + QObject::connect(ui->replaceToggleButton, SIGNAL(toggled(bool)), + this, SLOT(setReplaceMode(bool))); + QObject::connect(ui->replaceButton, SIGNAL(clicked()), + this, SLOT(doReplace())); + QObject::connect(ui->replaceAllButton, SIGNAL(clicked()), + this, SLOT(doReplaceAll())); + + installEventFilter(this); + ui->searchLineEdit->installEventFilter(this); + ui->replaceLineEdit->installEventFilter(this); + +#ifdef Q_OS_MAC + // set the spacing to 8 for OS X + layout()->setSpacing(8); + ui->buttonFrame->layout()->setSpacing(9); + + // set the margin to 0 for the top buttons for OS X + QString buttonStyle = "QPushButton {margin: 0}"; + ui->closeButton->setStyleSheet(buttonStyle); + ui->searchDownButton->setStyleSheet(buttonStyle); + ui->searchUpButton->setStyleSheet(buttonStyle); + ui->replaceToggleButton->setStyleSheet(buttonStyle); + ui->matchCaseSensitiveButton->setStyleSheet(buttonStyle); +#endif +} + +QPlainTextEditSearchWidget::~QPlainTextEditSearchWidget() { + delete ui; +} + +void QPlainTextEditSearchWidget::activate() { + setReplaceMode(false); + show(); + + // preset the selected text as search text if there is any and there is no + // other search text + QString selectedText = _textEdit->textCursor().selectedText(); + if (!selectedText.isEmpty() && ui->searchLineEdit->text().isEmpty()) { + ui->searchLineEdit->setText(selectedText); + } + + ui->searchLineEdit->setFocus(); + ui->searchLineEdit->selectAll(); + doSearchDown(); +} + +void QPlainTextEditSearchWidget::activateReplace() { + // replacing is prohibited if the text edit is readonly + if (_textEdit->isReadOnly()) { + return; + } + + ui->searchLineEdit->setText(_textEdit->textCursor().selectedText()); + ui->searchLineEdit->selectAll(); + activate(); + setReplaceMode(true); +} + +void QPlainTextEditSearchWidget::deactivate() { + hide(); + _textEdit->setFocus(); +} + +void QPlainTextEditSearchWidget::setReplaceMode(bool enabled) { + ui->replaceToggleButton->setChecked(enabled); + ui->replaceLabel->setVisible(enabled); + ui->replaceLineEdit->setVisible(enabled); + ui->modeLabel->setVisible(enabled); + ui->buttonFrame->setVisible(enabled); + ui->matchCaseSensitiveButton->setVisible(enabled); +} + +bool QPlainTextEditSearchWidget::eventFilter(QObject *obj, QEvent *event) { + if (event->type() == QEvent::KeyPress) { + QKeyEvent *keyEvent = static_cast(event); + + if (keyEvent->key() == Qt::Key_Escape) { + deactivate(); + return true; + } else if ((keyEvent->modifiers().testFlag(Qt::ShiftModifier) && + (keyEvent->key() == Qt::Key_Return)) || + (keyEvent->key() == Qt::Key_Up)) { + doSearchUp(); + return true; + } else if ((keyEvent->key() == Qt::Key_Return) || + (keyEvent->key() == Qt::Key_Down)) { + doSearchDown(); + return true; + } else if (keyEvent->key() == Qt::Key_F3) { + doSearch(!keyEvent->modifiers().testFlag(Qt::ShiftModifier)); + return true; + } + +// if ((obj == ui->replaceLineEdit) && (keyEvent->key() == Qt::Key_Tab) +// && ui->replaceToggleButton->isChecked()) { +// ui->replaceLineEdit->setFocus(); +// } + + return false; + } + + return QWidget::eventFilter(obj, event); +} + +void QPlainTextEditSearchWidget::searchLineEditTextChanged(const QString &arg1) { + Q_UNUSED(arg1); + doSearchDown(); +} + +void QPlainTextEditSearchWidget::doSearchUp() { + doSearch(false); +} + +void QPlainTextEditSearchWidget::doSearchDown() { + doSearch(true); +} + +bool QPlainTextEditSearchWidget::doReplace(bool forAll) { + if (_textEdit->isReadOnly()) { + return false; + } + + QTextCursor cursor = _textEdit->textCursor(); + + if (!forAll && cursor.selectedText().isEmpty()) { + return false; + } + + int searchMode = ui->modeComboBox->currentIndex(); + if (searchMode == RegularExpressionMode) { + QString text = cursor.selectedText(); + text.replace(QRegExp(ui->searchLineEdit->text()), + ui->replaceLineEdit->text()); + cursor.insertText(text); + } else { + cursor.insertText(ui->replaceLineEdit->text()); + } + + if (!forAll) { + int position = cursor.position(); + + if (!doSearch(true)) { + // restore the last cursor position if text wasn't found any more + cursor.setPosition(position); + _textEdit->setTextCursor(cursor); + } + } + + return true; +} + +void QPlainTextEditSearchWidget::doReplaceAll() { + if (_textEdit->isReadOnly()) { + return; + } + + // start at the top + _textEdit->moveCursor(QTextCursor::Start); + + // replace until everything to the bottom is replaced + while (doSearch(true, false) && doReplace(true)) {} +} + +/** + * @brief Searches for text in the text edit + * @returns true if found + */ +bool QPlainTextEditSearchWidget::doSearch(bool searchDown, bool allowRestartAtTop) { + QString text = ui->searchLineEdit->text(); + + if (text == "") { + ui->searchLineEdit->setStyleSheet(""); + return false; + } + + int searchMode = ui->modeComboBox->currentIndex(); + + QFlags options = searchDown ? + QTextDocument::FindFlag(0) + : QTextDocument::FindBackward; + if (searchMode == WholeWordsMode) { + options |= QTextDocument::FindWholeWords; + } + + if (ui->matchCaseSensitiveButton->isChecked()) { + options |= QTextDocument::FindCaseSensitively; + } + + bool found; + if (searchMode == RegularExpressionMode) { + found = _textEdit->find(QRegExp(text), options); + } else { + found = _textEdit->find(text, options); + } + + // start at the top (or bottom) if not found + if (!found && allowRestartAtTop) { + _textEdit->moveCursor( + searchDown ? QTextCursor::Start : QTextCursor::End); + found = _textEdit->find(text, options); + } + + QRect rect = _textEdit->cursorRect(); + QMargins margins = _textEdit->layout()->contentsMargins(); + int searchWidgetHotArea = _textEdit->height() - this->height(); + int marginBottom = (rect.y() > searchWidgetHotArea) ? (this->height() + 10) + : 0; + + // move the search box a bit up if we would block the search result + if (margins.bottom() != marginBottom) { + margins.setBottom(marginBottom); + _textEdit->layout()->setContentsMargins(margins); + } + + // add a background color according if we found the text or not + QString colorCode = found ? "#D5FAE2" : "#FAE9EB"; + + if (_darkMode) { + colorCode = found ? "#135a13" : "#8d2b36"; + } + + ui->searchLineEdit->setStyleSheet("* { background: " + colorCode + "; }"); + + return found; +} + +void QPlainTextEditSearchWidget::setDarkMode(bool enabled) { + _darkMode = enabled; +} diff --git a/client/qmarkdowntextedit/qplaintexteditsearchwidget.h b/client/qmarkdowntextedit/qplaintexteditsearchwidget.h new file mode 100644 index 0000000..aaeb8b0 --- /dev/null +++ b/client/qmarkdowntextedit/qplaintexteditsearchwidget.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2014-2019 Patrizio Bekerle -- http://www.bekerle.com + * + * 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; version 2 of the License. + * + * 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. + * + */ + +#pragma once + +#include +#include + +namespace Ui { +class QPlainTextEditSearchWidget; +} + +class QPlainTextEditSearchWidget : public QWidget +{ + Q_OBJECT + +public: + enum SearchMode { + PlainTextMode, + WholeWordsMode, + RegularExpressionMode + }; + + explicit QPlainTextEditSearchWidget(QPlainTextEdit *parent = 0); + bool doSearch(bool searchDown = true, bool allowRestartAtTop = true); + void setDarkMode(bool enabled); + ~QPlainTextEditSearchWidget(); + +private: + Ui::QPlainTextEditSearchWidget *ui; + +protected: + QPlainTextEdit *_textEdit; + bool _darkMode; + bool eventFilter(QObject *obj, QEvent *event); + +public slots: + void activate(); + void deactivate(); + void doSearchDown(); + void doSearchUp(); + void setReplaceMode(bool enabled); + void activateReplace(); + bool doReplace(bool forAll = false); + void doReplaceAll(); + +protected slots: + void searchLineEditTextChanged(const QString &arg1); +}; diff --git a/client/qmarkdowntextedit/qplaintexteditsearchwidget.ui b/client/qmarkdowntextedit/qplaintexteditsearchwidget.ui new file mode 100644 index 0000000..efbba09 --- /dev/null +++ b/client/qmarkdowntextedit/qplaintexteditsearchwidget.ui @@ -0,0 +1,253 @@ + + + QPlainTextEditSearchWidget + + + + 0 + 0 + 836 + 142 + + + + true + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + replace text + + + + + + + :/media/edit-find-replace.svg:/media/edit-find-replace.svg + + + true + + + true + + + + + + + Find: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + close search + + + + + + + :/media/window-close.svg:/media/window-close.svg + + + true + + + + + + + find in text + + + + + + + search forward + + + + + + + :/media/go-bottom.svg:/media/go-bottom.svg + + + true + + + + + + + search backward + + + + + + + :/media/go-top.svg:/media/go-top.svg + + + true + + + + + + + replace with + + + + + + + Replace: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + QFrame::NoFrame + + + + 0 + + + 0 + + + 0 + + + 9 + + + + + + Plain text + + + + + Whole words + + + + + Regular expression + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Replace + + + false + + + + + + + Replace All + + + false + + + + + + + + + + Mode: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Match case sensitive + + + + + + + :/media/format-text-superscript.svg:/media/format-text-superscript.svg + + + true + + + true + + + + + + + searchLineEdit + replaceLineEdit + replaceButton + replaceAllButton + searchDownButton + searchUpButton + replaceToggleButton + closeButton + + + + + + diff --git a/client/qmarkdowntextedit/screenshot.png b/client/qmarkdowntextedit/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..ca3bf87196c45da08c96c32db6f6539810e8bba6 GIT binary patch literal 132460 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
wFP7j>CN@AC+6Q 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

y`CSZGX~ zZv}#85fSx}@t*zjbkD=+f5kcDoI8jeQL9b`hCv!o?Jl~h``kU%OVL(l^cISkNXC_Z#_bby`X^Jgp{A0POTcGDjKlOdy^Xfh=> zH#fImeod)mNK8xwB3g@!Y3U{^D|T88INjadQ}V;x@syEgVHN;?B=mnPx9tc0DT!)> zxN);l=7E$6{YB~UL80%5B#&fcz4{o!{NJzclPZBi&+Y9ktTQZh6&6cd9a4Z`XA=BqLk&3%qW_@&D#{EgB`5A=h> z(tt1nK38vV1dA$D;`a776AtLyv;lHO-Xj*RV83$q@bEaF1p5rfQ)bb$4Ofo~kc4(| zp&m_?&mR~Z1mWlJs}OL5hBO>X#$t%H6RA>A*T14iJ;-8M5Zl;m7We)KbtcshM0na zcZFF?`s7vhgxu-k`G|vmGR|?gco(oq3TMqol7-$c)AmC!bO>tHMAZ{Rc_j3_>+UnN z`udbULk!I5`j{o?9EQn&cV^?A|NPbG6hU7fNt1BQjpQO7PI;Mfhf2fpm4ImBwwN28 zCpyfi$)$FAf5iF&a*-G8GM)ICt7_!h_H+C42@x>#dm(f~%5q5Q`3klbeMCIY2hDto z)w`C_TR7bas(XCQj|r^c24B8f)N_YdDXlNb$c_=1=}!ZYf-Qr?gu9Lc{|t($Kp=B& zLF22?320X=!=7@J+z^oAZ~*cs0J53prap3;GBPv;>04Mmyt&nPfh2!yD+` zD`bv&%;p!rJtQ|PFq;X}y#rTY$0c@dU1!q~4_TQIM^b@30Mj+h&@ng!gN8iVMS-}? z5VMMqtsGW|_NEz8*aDJ<@uIcuU7l_A&9c+A59SRBM&cDAMWU4cN%h+)&u=xc`1nz< zFaizm!CNoSiF*(KX*96G*X3QR`MF?<2e$H1Xgc%S42s0I#7>ZE273MMzK8XdOey0z z6CV%0s}1<66>7?7y!2F<&!6f36lMiC5wFdm1s6!x_Ge>t#*9*Wt8-WHUOHLxm}f+c zgk&GQ#oX%5_sC{hSz@DZh0YO$y!7?)>w$t`3V??`enu1`ModLtHiWk(1j8m5FnzL1 z22bgd^Rj$>5@$=IOU3vEE@TA(vU>@CqYQ!UHW#&+C$#!A4nHCDJRfu@x`kb@bseZa zK!hU1`}Ca-;la>u+I1RrY`31;AT8aKUBG&xN5s{yO~7!CcJ%ASY_BG)7L}rmSwlic z5I)hSXCmYZxyUHxP22P2ZZ7BVa}Sgmf}1V&4>-ZBZ_OG>JBSz~*tMWMOIXJ%%=iluO+L=4vVZXX_wiQEO? zEFuD7{#jK=NABG#D;oZ<{Q&un?j)SyrYK+Gr!)Mv-kQ^jEnSgd$YVh$uB8U-~E6d=6Xh_}OK^EGA78IFF z^tyhH44!seH4O%u91{51s?r@!PnX0f6Oq!zWbFPDUo5^=;(@JC#F1|`yg$71ozoHa zhgCQjtu%+B;b)i_62k>#1NiB}r5ln7mG(}*F&|34n_y}5@iehrqX7I?lU}!M&9+|~ z6uY`vST(4hltLO;JEkNd5S^XCv5vvTcFsx@_DhgTV)z(dcSLa!((ki>q6ZVGHNUG? zC-kBrqO)?8$YE@NdHpH2%_DI3Q&1u|L~PEPj|buN6LjmxH7sZ*B%ua)Lu!-X5ktGyDD^U&kOIcw;i=m;oK zH?6R|tq=v1yx2}N#J7zSn~oH_3k=H-5eogy<6{bvNgmjS3;L@v->^E3uqoY%Pwk8s z-h=l9IZc7=e3kg^?@}W2s}e2j+PH%p&12rNEM#gU&KuQx2m)bH2;3bW1BZp%qIK@( zXKJA(1UQd2Gb>4c+BY0W1at0C5LJ!N_N29S|J}=iy&9hTAJUGHHzF5?u#eg^gXWX-Tx$y?&xT8Kl?nc{h&hGOn zO57QSh9!~zJlSzjt}Ma5nV%2@5+V{9uoBv5)P53n_Z0cWoWlW~%NyKl>~tE!YzxLO z`6SRD3NltKszD=pCAG^{DfMT2_!-&$YF0{p+IrLJ(hP)vgc_)wRixiuylWlu9~O~d zR_*(&pHN1tgS=*mW1ea5GqA3vB5WWaa?;Rpc%ogT8)RWMqUAo>JnCt6k~a_(}#f8h3J z3bNZiP`qBz2xW^8RuV01l}kcOnLZ?Y9lE=WU3O4RAMw-?L1Atpd{+L&)aJ{5XDCP} z>4nie2WmJ;k7~SWE?X;pdprJT^qJ8x4%G~(>6Ec|s-5p>Ar}F}$s~P3W({XF?|)3gM+#`%2n;a3C*TcdPXWMfGP5>40d!Dai~+O^hW9nWPwXNSxQ@dlZN# zDpV$^hGn%lak>u(@lzg*9P@ErHU8XNh#Ul%T=*JpXsfjj2~d#plb1~N=Kg+wUGGZ? z*1HssY}#&s@0g!4=v4~JurlQ(-1)kn|H9-NQ(%L;k&pF%1WMiVfaLy1-i|e589tyq42;& zZjIDC)~V9XPOu*fTs!iq#h^YIL6;#%J|D$fI2#Xnv>CTg#{zU+Bp=eEKFY zBJI&_{L5ifc4^p}PPRHPNt7+aT%Dx&2K)D0cA*3V^4eV_)krZzxT_W8$&Xh)Vi+kq zZ3FcpQbT1-xnLuc(5JHp&m9H{9JU}{%At1wi)dpH z&wL0};EFyGh1^6Q8vOY2Bc~ATt{@-bESq-LJ3BR-NBj4L@1;Iv!=qyiL_vdI{A7-O z$Z9u^gIdg!iJh371UVxqsy8T-!384d@FC)Kp9!NU_xc>``gbykEeqo43+Mj?W+v7g zx{WiwJhLV0Vf?|$pR*cct0|}CvG+OPCaoF4QLT_eRs^{0QZtj2O%=I0dx#WX+C)p6b*@xW zYDO~1Cany*9j{=746Yci^Hs)O-#$N3xKuS(`84|cIs6jV>(OorzbOS(l#&x=;1eUy zjg-ErOWGsb?%j&C&P)Mh&c2{SzJOr4Jupn#K7&0#79`(}D#Q?m1c7`+pm;Moxw?SG zfi2$1@!=WfDFua8Cd9|cYI1O_5;GswVVn%fW%y;L>I5z=R2qsy^JO_Q#I9K^o8P-a zV>a`%XupiF!YN|7%+6X?{TeBfu2?Ifq!8{0PtvGwB`-z3o{g_kx^F6DARRL|f9mLf0R^Pmn=E?k68M5x|j zF5T~~*k=41l~Wt^m~|l{aQR3$hnH8~)lEJyk^K;VTwokVkXs|RU|lqR2r2&Kg%H+l zkxBWLP4(;>bH;j9@Q=h)=px2IgAi9g`2(tVvru;}aJ(~;UIKjAV~C8PDVX)w_+prd z6nKaDGrKpk**cxCd^=#;HUu%NagjH8781f#4BOPy5r4`%k>-wj4&nw%;`cSbD+<*x zWW$Gm+@@~X3}M@D`u3nxYeB)pymk!J+q@`kEv=t#S$f3mCw`eaIE=jIw>Bv%FL!r! z{kFeL(_B;Y^%qCnfPh(;#$>1}!#6w{O|SDc{d~2((xtSf>{n`}v-7bZ2Dc`dm%?2> zKga!e%AO~9XJMhGr>6kl=EO(B)RG&_d{kk0k2>z*P2j@DQ@nYn8Wa~)tEpD_6(snx zMu8|C<%S-pGU%{;1#XfcC);B9MH(;hMbqArpBicgIpLhlxdE{V;x50<_?VPdQbYYZ zCGTiYX@5@C7+E0PZ&OJ1F(h1QhBkIL)E&Z7#0p(4@6m~}C+GO|4eEyyJJ>KA$K1F! zrKX?T8sWt6;2;Jt24W>g!QF5W)dMfAZDE}Bhqb2{rvYKN@wr$1E7n}K5kFtX&F&wu zN!;0c1Rx1Xy86oB)zrhb)g56r<9>b)|JDk5yEcvi--J2H^VM4t-wO@# z=%U8zg@nZhOP?(iWQgZ_Y6x#GJzT_&+CQ*}HO{+1Z?& zFTV~9#5m`kXGhw=)Vns{jRP&qZGDjPTKSP0K8LiDPcc5d zRPlXV&LD>_&7*9jj%qDeR92>0GJR2luArd6rK`(;^I0tTj3PbK3zW8s284w^ddU{@ zjA_>MdWqYE^evc@cG$)=M7yf5X(wV?HD#-H4FE+r+-q!unR+oSwW64ow-O7oyVBgq zscx6>g3tVW`2PNWUjk387TlV!X3f{Zd5##~uRNp?8Txk)p7{@OneR z7;!GMCI|y*tH!4x3yoCli-LBMk*tCt%o}y)`+<1;guJ|Cb({nT+12*W>gZY;5{;NO`uEx$D8Vb@;8u~o` zduvHYAbhG7_`N&_tFtQ5za)EAzNjxWT`%sGe(4Ts==In;Q|hcMBbY}wVM{njB9{3y z(KrhKfK-SaHPhU1vrfo2PZO-l&q6Kur@$QIqx!HN_<7{%DJ>Pbow*ep9gkN)+~Dz zHAgtR+eu)$g90yVF9>!zB+BlMB`h=q5W2oGsl=><`Y07AMsb;q_OX(oYMK}Yg*qaK zcROREyfj>@>+jElTNi{`NE2>K6qsWkaW+J`Nhvs=yR_kZc56%N67^S$rS8(=izD4e z(n_&od#k{0L3#8&P%x7NL~BsGXd6^4t_X<-Ofk9Kk<2*MP2WlWjj*7V&olv1(Jv)c zI#FKDz(E95fKbSM`9@wYY+o~)@8F#Y66CV^G|vrfleGz<-lJ{l#lnO|pW6`}4_w?i z24qS!jb@l-4?p{YvZ}uu_SmnzjQ3k+@J|_{J@gEKi^xBxzhRG|x^7=cf%9#CBtD&U(UxY^CzQcJM zda%>6#GkPESIIK`Y~ZFZt92U!+u$P;#EWkw)dCh=p|d+MhS^-qbN8kBZy}J)m8n10 z>TI0hk&1CzR^M>CpwiSuj}Ro4L;|%6#gD4W^Q~4@SPa^hpa#e!q@<|_d+Z{D`4D|^ z;VaMq8DC-3Rnsgj4RmNm%XMMKZY+*vOGYxrloK^ zo-_H~RNUUO&@oSy=c(su^`UBOz=OObD3=%*lvNmXY{FH?f5`v! z>kDUJkm4IDO9uz#1z3576mgdRQWwNH_YV?J>RMWKNrU}ychV^Hib_oNE$UGhH_#B6 zKPbe1Q#8pW8Am}lYJdigB@G4Kym64jI4#+Q=8f{z`jg|7^3@1;kEF}Be2WKB4!Thc zd@TYE5c-;+d1q`j=;V(jZ}cEGyJyai`n-@eLG0H@lm|rm&+>VbQg2NCwx3RHrWTii zA&}o5A*veB4p2G5bKafJ%>wWZ$pxdxswG4#!xD2I@b}7bE_p4}A1Fwr1Y$~_6I}O- zr<-2Pc?bCz6%B+5wnD}n3gj8TWd;1KLLw3^fm}{AmcNi(L_1A^hdBMwi?LjkPdS!Z z{(?>&*covnN@SCQEcbX(k%^@3yQg+7IT@4Y0niEqq!c%h|J7R-z;VDuK~aPR|T3!_~tM?9kK>_;;^nO>hwH zC@x=`8%)bqum<;fyi`NYI-Mz}U<*5^S4q`y!~9{m@sfhx(*6YvnVoFZYA31=TJ9+M zJFfGA1VUf_!*Q}hu5L?l@`mz{h`M79WzkJ!Q*gLICP=!0)A*OB@%l8q z*9r2<%B(;;aM^aSyE|;y0@Ug!R9HQ^65X}59?^_g@OHMhxqe@P4YX(13&toytr$p- z>oh)QE{Vvmo$LsW(#pw#B@ssf}!ZyhORhoto==nU9)9wYM8)L#Sio75rsk5n5y!WM~aWIuC5M#Ywhp( z?qAJ$K;sb$#F(`deVW)!x<@6@39o@!TVmSffG%C45Jf)EZSN*;P%3%;r9^R|gYMxU zE|~y+!P~}=Qe@=c*)(v{jyB1Hb;U%Fx*=c1N6#ED;C z7Lr%hX3M~kA4dvB_K4d2I*h6^ILD}cJ`p_2DH-xBG783!CI%~fEzv};yrg82Pa<*N z$OB}-RiYYOV!C{YQe!Hk_sFCIl4)YHCQit|CRL0+2{Y+DaeL&l$hq@kFsP8kWptb{Z* zI++RA!A-BK>H9!Mp@B#IDbrR98GJS7)$}pU?!rCC(ZyGRpap7#C1TPsScZeo6)F*0 zo?Ofj=cXCf!H?*b2{3d@K_X0A&q~VKuv$xzoY7QeY%dS#DFciDZ{%$WYhtK8fY36= zdEo2&a1rxgR{i(^+9tm3eC}`FvA44W900(o0CX4GB(fY;S-KxQr!UrbcY&uvW_mgc z4)U}O&mL-L9}EPP!q}1~1z!+m>M06ljG=22Jo91(eMwA&3QS2Md=vvrb0UAvC?u5c zpt4ra;nxNe(Rp@pp{Sz5&dOR=RK&r}tM->lP58nz46Rwv) zcj*Mm;N+ybPXYV)xC^rm(;q{_!&&oUNd;e*hK8aQa{;}@&cZ@^BnF*S01^n=zW~hz zxT|{oz3Rm%)|~%xw9SLL_F2?QrfL6OfSiz?4men$0)RxL5zFpp6j592;qC4HmlCG- z8GLRXQ3zMkAV}5RdpJ7h+PSZxAn#yyQKM z`vS~e1yt4^YbCXxR-hTL>83`5{JfMfCfW{9>AYWQU;I9=kM3WG#JPUm)X{Q~#_{nouja+IxA@X4XnlOoi z9U1o3*n3o1)@cIT6Koki;FF&o|MlG zEY3R>;^wXk&f18|OD7i?nnQ$YWFZ9cR*#EJnzrD0(RT1ag3!ntVpC)itx^JobwH8$WuGiwD$Vn@hD5RuoQlkO z&sIb@BIOu$o@uXj+;dp7SI!w9LFE{U524r9VrU>cA#n)12K6}LL%ex+Rxk=k!{;*M ze2B?bp;~a?Rzp3*T}o~5lAY zx%e@w(^UvkBjsN9Sq=(dK-Jc=16~n3yQNSxFB#~S4O7=L^i~z!~BG8bsZ^M>U;`jwfDPkkSa-~GSkd4EG2Q_eZ=p0ir zY1$Q#j(1g@(EfW&%_yPj{a#QYO%;E7>&pws?lQd*801{eApgS7UIBu~#Dv<%*SV?; zz%rw(tPHpxk8l!zQujg*%GmD`OT31c#hXV9)q|s>Kz#12EKRh|IY^;5y6&l|sY!U# ze4b$i2o;>5ZMxzCITKXoboA*G0G|?6{LIWY*4B&_)53vx&(25&@X`e+SQcLfFo1wp z`QtHZ=lx=f-~B(SdB9rgS$8TeD>Km7mxbgB&t$pMO!}XodB3leR!o2IbY0WKy^j@e zvJ$vfFr;_)8juNebyHOt4wvdp7@!L^0#4S6S38dHZ`Sdw4^nZ%I5>cbx=lJZg9&h1Y{;r?~A^Xk#c)I zgFl83GQz$$sw{Z5wzK8MCz`L>ON0Mj(17d;_zO&L=o0e3eM?SD<7P9NDvA|(>}V@I znDJM9=Kj51Mz-vxRPin}Z|nV-knz~m(v9u5v*OlWKS z#q8{co%%}Pvn46C`tgdCz(=8VwuXd0Qk0mE1pU=<5!SvidYSPU_7_NbJ#R#Hf+Fnd zs*&LG@^Z%07or?|BNLOHj0{p9cd23xK+*v6GGwtbCm6-x5Q@PIqy8!nzx@xA!O+l9 z>e38LWw@b2rtAVjLDjP4jjJ_jpj5T)s2H@A_Hk}72`VKfICW{-t& zYW^=R2E;c+DKn&h+n7Nwhav3$A%N@tkI{PB{|HxT{g0d%^Zy7};Qo(r1@iw9>#~9S zKWyr1=>Mq06#lJdqQBkB zk*}$kggH5ZcsTeI7`cLSZU8<&{Gz~gJz7YVXJKRWf4Jdh9bJ*{`^ZSt9YS*S9V!ssh^kt6-!$0k7^fs{(z{{}WLKGS+v4djWR%m8_p7EIlI zH6#d_#7YJ^x@Dl%B^nnRBDd+pSJ2H0|Ef@RhVhG=X>n6sE*($dO;0oG`5RuxB;G;{ z649}wZqw>Dy#KU)B)Ygs40~HwDhq#}asOQ> zx5$FJ#wW+{{{-cVwuB?J@H0%{(}ux0tDp??WgbqL1Te~4pATny{aXJHyv9$yRc*`t zU(e#}14nl?w(YK;&j&i5cl>1jy*<{g`7kyBk6cmBUgOX_U?PXnxmHsR^F{wvr%>Dt zvLGl9VE*EUJN^0G<|D|&k0hwSJsZFec)DMv$_bQFIoUt*_4@PkE31m_*>ltX8j*aV z#xW^N6gdNf3=JkYbv;>EcT`(tjF+9emnV@n{bgN42J|PdyYjhz(&pp)HXIGrI(~AT zFQfkVhA*BEbt2eU@DwajJ|dZf{}mTAUl>#y=Civ}C+Rn{r6rlJI4JTrcy1uLJ|6AG z9T2!HL>TR5$#^w3nQwx}LrstPbpV&1`ZK$fuy35^7j2-{FaRaSu*Scr<5>?{H4%=W>vB8gWqXgT~k4>bcaRNYpw00<-$*EAfyzDI$d3x;1Sp1kGwZ$_+RT4OIT%m+~b+Re_j=2KwL=fAJii(lBRWV=7 zhi@JoZU5r#+Y@?CO!^xgxUJsumBx(ZONQESxw=%^Gr$pWrTpsA$5VxLU*-9jpoz|( zO`D-uw9%_kf9?VvvZOS!f=}jl?e8uwSFKS#cXPa%KZ!uV2J<&6VREeim; zfRhWUoIeEH%3oayz=ip%OR>4xwPo`Kq;baaVIhDxUPi;t9xICuv`$0_ZztPJx^ah!p^@cFz?Z|);LYX9foNH}knq&%jas)zUq+pG;Ycjg zcUmy*hMInI*B%En-#_FcJS0Ga^OIl5?Q0NyrKM;D+$ z_$~`jeWF3zRxm_WQ&Z|Ul>m@p9c9P*n5ni44RMjaTEZv8&ALm@^yJ((Nzgi{D6tx$ z4;UlgAVdC7(`4u2Z~m-9KYe{`6J+j zGqO#xyYqMPnj6#wGbhbOq_sl%8_|EC=OW-BMfuYM`PD?9Vf2^uj>w@0u8b77yR$Xi zqq?wRQ_k@FgKt_TNdW;<$#wZsr(XIflv!oWBK$4~Pe%B^bQvi1*4HtQPdwUxjrhlRn3p5=YXAh6W4g4UL6B-H&JRa88){c*r z^8r#SYG}~?7tgEVJPP0V`3YBJbDzssH@fY)2y2r>J{=QnN*aCp!! z{($klyYqHGcXIZqp{;%pNMvfve(1XVMviU;YtTv>lg!D|7FuStQ8{l0?`nIpjS0qj zpGD@2=ClWoYfTjqi1D2!S<$istlzVD_`SDQWKhB<)q!)nNTDjVO6(^G@)oXAZEXy; zhy?JmHp&X~)SZh|sDDplWW;6&=N1M@$eSbYKWDO0#aDZo=(zbKRS{zTLl``h+(ZRN z>gq5MaQ?k;Fs6B-NR$V@mlW81tNW^n5_DgP-Y31m66hUPpFWVDF4owZCQ1?Ya(k_} zbLaGF(8Q;40er=huH|W!oX}Y=&RboW-mQ-gM&o)nE`UDFSX~%M^xo>xa#FZq)4gh& z<3bYI*!<(jiKFc`6nJ0|ga$29KM&xFk~Ycbiw?1|v6Rg4ny-4=;n`l(S4oBl#EWV_ zI9;4dS@-7Pjt}w}LH@S~sq155yQ|ChY55xqje>EaV4PW5Zo~2a+X0o%StRfi<)y;^ z%0YNyHGm_ENNazhkd30KUj{&eKvT+bmk<-#ljK#}czuyB3%B!o^fddP9l0&E(G!oX zXnlSk4$YE+%D1H}B1Ei0gkJd=RZ)A4;3;||xcA3LCugG`5Z{4{7ZTlA$g_&j31J7YnBh~c}6VA5mO zX#S|m+L6u4W@yFoKXpDExltyDSIgnrPG+JPVk|#c}mtdDY)_884svzS;E@g59;e zr+^cy>OAG~ELYtbHG=J=<>fE99nF6d@Dc*TQFnOLoY7=&WNEM_BupiC*uWTBXwJ+> zcW#_1TJ>1dWpHb8Hn1p1xuI5 zOjEeAPIB<0a(~L7e)0=~>^IX&{iPVxcRxAI{0lRLGYHs=*3bB&g)S`cj$qvQ1#qKP z^+(d}Qo%frfOX}t5dIG-^6QHHeSKx6Q$@Yn8X6Et+Hk?B7E7Xh@x$iu=TJay7i&AD z)l^l@&aJivZ8a0o)Cv8&?2b7r-g19cnb6S%h?sR}j=B_&UOQ_0Y|L`_XFJMUtdB;N z7hk9-X{zd8{>ib880jUuwGbDqy-U=d=GcH`j-vZ@MWCh0Y*2SBmH;?Vs>Qm-?yTOP z@W1?RagMB`5>wgX-xZbf)I5`gtS@`G{IcFZ{qGoI9{jb!`D3?f2D>r9vpr4Y-Cze2 zbY)~?D=(RR!(3JlE(;O)Bv1rIOG!%;4HrT@=Cqu zwTTI#ODjXzyJ^Sgi_tT*iqE2DtSLeAQ7^qQW!Sp$t!wz_7T=a;3c6ZVCe2Kj)EQ)La%8J<3mZaFSozz;{f)c0mzKEKVdqlOJ^zI^$%@j*2xIA~Qw*_ zglaC4a^WJn6<T%Uv|3PJ=K`DGd@W4C)Oa^d(LV% zw^W^H;VI-Fg2)UiG^D4N+<}9J!mSniQvf~53*+pv&CD&XS$46M@g;@)Naw8cP1p=( zR7xjw+JfHU>cK~NfsfXs1k(0Xi_9U+RKuUv zx~&_4DdFoL1`HtyNqZ>X66yqiR* zxpB|JlF=LVv)ySJCn{wa5WwvLX4PN{D7`bYFFThqyul>=|+9-lo>i>G{;WxUI3BY?Wz5AI>!M7%g-mv%r_3v!wmDRk)3_gRA{j^CK-6 zT-q>j0oEriYxESrIGTQM-dcX=o%iEyY1qd72ORhp^#0b1r`T9)I~e9*o3u6cbhf)L^jDM34@Gm@3-@Yr!8Xh7N--45)ntEH zb{t}bbug=Sx!Et~rbGKS55Ej`{A7Ax_L^UnB-19ME^USyZjAg@3A2R1A|puRVClQL$xg|3`~}kWyr!pU=Uy@xn-7`rt^F} zCSSdDc{25-{cjRV6zLqkrot)_(!41b{udWQRsV1EQ0XK(FfCDkTjD`iKR$NNKamD7 z)&BpH`1-#~)2;D`vQ@OS*s1O!c6zPo{4r5=k~J_`u42TJV13tjIu8Er&?~9qHHP|R zaImERVoj%5W^V59?6tUx*2k8VMFE#9eO0HvdKCQ2cSR*7D9Gqu_Zioz^^~ECE^A}! z;Q#eCj*m7{D2=Hl6>>)Ni>RD1&9vwcx}3y(`lxraFhSWVg8&!d`#j06Qo(_kQy8(R z#*C#vLzzR3wnHeuXa1<2RXc@;HaNUA!!8ILXinO^9iTv zvx%OMo}na_gSKXn3IhgOnL$uh7K#T`KVXCx`_(OH{BL8fwCGew9NBP=1)chwOsh+_dY_6U%f(KFr|iOjs`Uu!XOh*^;fD4Q_=(BYw=L%m8m_U& z{(?1j6DzA{>K3X2pn{0tb8pxTjo&0R+qoQ*>GH6-2MmN3!T=cH^LzNvVvx-a>i6I@ z`QYojJ(4n?*GMjBPC^BwZ1)!|PP3tag9;}vuVv|QXy=GURhUFhMqiD3?Lt{uhJ2kt)d?+hQm6*XNHBpaeQ>6-k5}mK;Lm?)BfLFfal8h zlLNSe7Q@NXYKo27Gv)Vpn|n^@0e`iUOjdII8iDaZ458-p+d?^f9Eg6s_v30pVZ!lh z#rN-^0pWaa!R?`L{DQcz*LF56OZa*Dj$INSea!G-=BRIrc@>S6>n4qT_H!DN-}PSp zmJ=VKNsK6eT5fPB#1r`Cp{fFmNsSUY5klXK{`gUIwKwB-HDoQ`?7c5&+J%r+8q-AV zwm$T6sPxdfYP+S;xf@VX(AdxINN#s-c!~WfYQtWb(cgKTJPr9DrbaT6H4hCN*-NOM z2TLj7v8K0rt}_oCEl#Rx8MfFABS>&p6(`W^yiLA$JvzUrnHHrPnw^!oho7L4&OG{q z$I_B@-qz-AXIILe&Y$CFpsu@LGxPHj*6OeZFx7ihQr_t5x^J)M)zrLNh;m$LavN`FeEqEvfb^6D1<7hciZ&~VHx4vh{ z;w)8QB+d=*3U*J>Mj53FCv>hSYb8e%TUW3e7^9!6P>2;5!&}jgB&vI0Xn>8Gy%zf;!Cugl z@b2OOJ41BjCzBs<&#tjRU#hUsQnkm@c)N*HpBMz_|9_ObR>>4_x!k{OZmel+tmkr` z7*qr;EVdPKCg4RtfQHHhS-w#-+55+y;ggvF{>lK3#2jELB(mH9Ee52{HC%_vh3_4f zKP^>1^0L*gE86ax6wWZl%K|4dpz-Z@cAjW2=;2p?g>97O5c5^1uv6yg_NmYaq`rVM z9RfPy_L(OzeX$(Le7@e?!&vpH1viiJFPzcR5}1AX6T6BuJfz4x?Xt6e-11nf#Kxu! zLXb*S{A#IMvE6>EQe!qe2GQ>BZnjnlF!6F&ttZCMI%N*gXBM0Rc30Sgzd6BO< zJ2hkR?j3M9*uMFw-V6=X^6;Q8DENZ93QX{ZEZ}x8wbbOcF>~bDqA%#`Bx@FNM2Lh! zI#I2VgnqOrd=lK0t-E|y@4*gqGOmuc6o9$ipQgcy&+u0m1qfOA>_(vCXeJ$#&9o|z z2W=+Q>mAHl%kwHt*nO%mqwzXCtChoL0Y{osgK2uNYUf61<-@uOyu9s${qTq~&QdLg z+1asw%Y|r6MM%!J)sL~T(G9=5@%yQ)+grG#a{bFwr$Tz7Si@G{c9mAU^5SB@gW-G^ z3#CFVkih7Q(VNQRN;y38m;qr{IgM+tjCXbI&n=Dct^dIHX>t&EsuJ=#Pb*dLY!D() z$8LqJ-_EJ-KikgwW!&>Q9;fRxhrA>shljse1;PJgOrEjmY^!_||;j`Ci1$NY>HuoO;!)%8U@`hk$PX@a_+LTJ40R++)bST0{|en_FQOg+iX5Tl<%l zW0W-Gp`FS`^pL5kspF+)kJT&$?Na^6-twdJa{JT<{2g}vyQ}-G!`Hd&BKOHpJiwvl zXyIN)PHwLY&Mf9u+3lR0C)N7}VwlB~@I$5tV`i?2iDb;J#h*VYsSOsBY(*)Pg;%H- z^v^1?in8RZ&nTnxv2O4S1TtmyN(4n{f!bg;sYR`O6y8YT zGw}6ej+YgEGLGrT2w)s7$%II+&h&gDtaiCxNhln#ieU3Nmd3?T3PmF=J>jP|0rO~Z zvT7X0)Rz>Mr`LtJfL)ux|IvOi`J?)MMCfp4?b`k&;$30%4>qn8yXzG{?(HFR904jC zyQxh=mzEnFKQF1UmPC45S`^q|9qxh&?BSuIUHqQ)yk4?=%Zrm)kIV11MaVB9t_KTS zi;gn@9h37uF=;dCS~x=WRlmR{R>`NKp&P`F1VJ3MvrbN$ZTv2Nz^T^tJ9n+?n{nU! z-2Dk1y|8l>6z)6qfVvB?4o3*QXT;F6Vc)e-${N@>lZ|E1AOwU3=}(9-f%h0h3qR8W z=76}47KEYwz z{>2C%)h#bE!1CpH7#T!CyN;lvZtpTZ1A|M#>23<_tF~M3?#cw?7re~5=6jS{V=`Wr zjRsO`mmAWP11mO{GvYzX4rw$3F5i{l?%pS1Io4{wo#X5^OeXrRq+vY(xsIiK12f4M z=~XgPI4%93<0{V*3LO{g6?PDRokvEV1l+yrm`Z7D5JW;EiUqiLo3f7!g>J4ltz{4*$azZ(XQ@dKk6Kqxt3#R}$IYG# z3WT8{cu_p=7qVdzFZ_>%%!YT^4M7wI{7cKKw9_a6ceW$Yj69B~&in^LCf9eOjZ_N( zra1R(iXLL|yH1ccXeK**>djJ07N`o4of}&>``Y^GVD}*G9bWMC$tre>&tv^}aJOS< z4AE@BX(cCiVaxrB3~;YgVz3zqi8yGze}9*rBq#=+qs91ztkj;~LgK8^SP+EOOied{=$D76(KDHU>fRZ!p%tTc{TLG!ke3Zz$Ie|CP%9}a4l^AL4#g1xPj$bAR}@o{Lb>Vhybbo%;>D;+g;2YRYXVtxp2+-@-;UXw@fUF2*A3L0|V@> z^QVuoq6t#cp9p|q-wvBqxs2b{gOm&cLeH)Ae85^owUIwHUX32rWe>0WNjQAV7|vyIfN zs7oqU$w~I2$#n>a--5Y<0*dr2W(9=_myrEYVT%h{0DT|jfIl88B%0K|s5V(yh|KY6 zi67$h^gF+RZBWdC4rp-6tz$1c>Uo&I+X4W7G?QN#cy#%ElvDWH>_*QOIWRW0`XXF1 zBG&h&eb3;1T+w1snt;J7ml5AN)_GL~HSPp)yzoiS&j|vqt`P?r)eE+`B-lALa zNKugc)4BaOFAyJzz(7GeJ@KdKvguEA)SjJEC5)~YiZQphUk3O;nQxo@4M1+v(sXiQ zZfKWG2Q?hyhQPxn?Cgd}!uvn%MQoB^YQAcqUWgWYEsb@0n-y?JNho_YQA+sDb7n@% zxXXG{gUrFNObmP-}rena}?_T;q0r!nhd|cF%S_D1OWj70RaK&9;F~5DM)vZ8r>-Z zq9QHL=x*s6AksCuL%O?Ru=n=+i}(He-Q^!#Zj5Ko?)yIH6XzV@4XZLfOcy(kuv*Gn z{G(o^?Q?MO#0pFca8c-wDV)H<+dQ;YepTf9vdl3(t4B8tZU+_u~hy*<3(9U?+CcU!;=q!PVmPSb<5Qv+W`-db<;n$`8BsoYTgL7x0~3kPb(C9r0`mDxkcMeq?7$ z9$!;}Vn|B^1~45P!p)EVEGr}by*aMSyh6xrD~~(i3{x`p?kCZ=2xm6Jr=!O;Fd@Pe z)zY~+-5W3GaBzVAOnK`Ph8!4pFC%|+x@oS>KiBfc4?||ivuJYk-oFItQBVc|4l9)t z(7l}m6Y0?V7+{h)-a^s#Xu8qCB2vu8-qA7ZR=0Yks;>0)l}m56D8kGCeKQ>{7*yv_ zRpl}hyI^V5WOmczgCg1s65iV-z+->5%x*9j{;U(xYG&VMdsIux$L0m)S z;o=4yU=H6v(I!hQ;%!Ny0iWGe!+y(@6KSp@$h{0wrt_ADdT}xQ?}$966ivJq2tVl! z5Br9PdjLLAcv$H1+1cGs8}R~B3SRKtMuhdXEZ{B1eo0&_(|zF<2gsQW4TJoG5>el!Q8%$$Tnu1O5NTW49(2OGEaU0*ffMy|CK%}B#sTB(I}QIr`)jld3`VrtF$Da zrdjjn&dzf#)*MaR_p-7B-99_JtAcI=^1H2p)*{Dzq>m^N^pP@_4>I6Z`sw(l;a4i0w_#PwpYPq>V zwlp<6i-O0n;M$c~#|?~5KO^FZdX%SKTvANjyXuJXaEsU-f_h%3lz1SN5&CD9V^42% zbv7qdE{TtA1swV0hRd&e9pGir7E7gR~p?ME5hVNR;5I;ALO%?R6SJ6t{k z?4KAtrs3UZZXR3jh{N^GF&^R`h&f+aILj9`Bw1@|d4LJZfMEhX9zkA`qnPp#gY%bz zqGFhBi2{|qjk~ln*AW44>f%TaCco+bY@ym>-ij0qg~s9poFD*VQ-vvgw-r}D{q=nr zS=s#qCi_z|_qaOeco1)AA)KoInE{qW{~>U$@+I}hKbV;6X1ab)>iPf&C!`4xz^30+ z4tPlk@dszPxEr(inTs-s$Yn&c=a_XVTP=w9oJ^Pc;!6Nj)a<=Wc`R15nY%B)7ctmp zN$vIEzD@8Pn6gLI8Ll9!2nIOZxt+W7sS*J)P<$#L4!)GiE^Fa&Fk%H*66qD#+CTv1 zvzznXL$xzS^Vr@1Xeb?iBX_eoRlOU=n&hmA?RdVXM&#;Wlnk0~PEX>JPlAcoT0fNM zz)3y`wZx@>Tokg4T32RIzLOGT^U=S1*K_jQyM>#JAbEgg`E2Th{nO(nWX?3bvZe0-) z5}BBoQ^9c}_8wXcXdprF73y8ypFd+kXSl2!QP6Vp;Oxw}s55@By#l7wAu+z^D+)%O zk|iXVcC=zIca)&_fr787rXo+-m zFg>98W-%8We~8NtYa2eDY!$euQy6j`pPFQPqa314DgNw-;ER)_m=6|b+a3D~Dcl4Y zRO_p-`SRtp-5Fk9a=avZ`fT;sSO!!W2SCgykfV=4n}xk62lf5^=We|FHdU>wLl;0|2P_wR}>8-c?v-*Sev=CxVmP5`K=Lq#eg#0_qo^voHJm?Q~AuI;9arp#>B*Nd8*9t^RU(o zV~nzznu*&kQ{Se!3t_5F`R7Cwo#quF8e?HR4qjg0mhfdx!F9$?W53wX=f>bj96u2T zsnbIBu7C$5`APkUI4}kGa*+^k#>`^{ zh_Cq-<`)|k-T#rYF8$JYVBF3Qid7i~p$iU%%~oCl>RdYP8H2{Ffs}^9CV0)^ahaF_ zund~Ycl|cJ__ZMnKi3=%@cmt2bs5_^oszXX8$-o?PL;VK~fe0TnBD#N@xF>wqINi$8POpbi= zV4V{q3yU0Ih!3hQA0X9NDe~7w)B6usSL>W!vlicqULiqnaNV6^BzPzl`s{2x$?n-m zSkJ*gBL2RVWcYI`lF`Ck8PncXdmsQT)Ze%Ti7O(_Ez=LPBL+AM>aTQ?Ipd(wlhf1g zULKJKc^i`gk;O0}yM3U@{5t}`c-n-7Z1uuZyzkt7|GkhIOcawyM*FzSsw~V!?j-u^ z;NWg70?NR%Ewo_E%9^F{>duvk0`;E75p36T;y1zJtobu*LMNwV;@;R~$nE*!J-^1| zGe;b`kW{}p@h`FD-7~$>JX%rn^&sN_X;Pc9!|=^h_$9dhdv5^$9s9~xL^O4Oq}bZX zNLgRsS6){)_-r?COZ<~x_-LgxFK?C7gL)p6u<$@{NJujlMl`f9J%@!%%mX+e73F0I zy84FeScQa!tV2lTpNpN1!@HIl6|4U0KMUwLuYqkJ_QzQx5W+P!3-5``G4Z|LTUx5S zhJlJ`s1AOVI5WZYO(!!8%bk{^?)1IIbvf7unZ29S&C#(ETZ%}X^|c+&I+6vr!_N`n z<>fAJuNi*I(l2|MT51VmXlnAbF;CaLJ+e$NH)!bTDX6c9c#c`Ed^G~Smr+4-8200a zcAOY5Bwmsc{pH|$s}58pFRatiTWLs;wzj4fGUKDVxdjQHtcTz+4!sX{9vmE?4=Er5 z%nW>D&40JXw!s|S<)p)qQ{!Q`w|6i$AGxoW>?yaq?Ms0hCl-ohHMJTqC)xdYPBq?aumAwc zu{$|(E%8za6xD&)YHz==$ITe#dpOlO=i?K0Tqfy zPeU{S3|>M!H=GGzb$!HqaPsQ)>mHwEQosoMevu*l6hB!DRvLz3BW#DsGGAi!m5Cw+ zNfI8$kNg>2ezMIGRAQgsdz|@k(ENN-nQrw_d)+bVnAK@MHx%>k@3O&k2r0kRv7I%t z=J8qT1Vo(lbGxyJM>XJ9dUB|n?_pfYY5(}WYJca=>k>a_1&BGS$-2~aATVpCL7DUM z*%?Ehf{`Y&1q0*jkMB)!5i)4JhZ{o@*(#E=wF!x5c|JmRp7;sTxvgVXE+3FBAS?}{ zBl$Tv%*PBhkERQOb#lga$LhCMN@(Y* z?F{?UVG%_Y&*^Y@(l&YFj65_$j5V zIo4%LaY9$*qIAxKV;iNd;+T1S!d(??4;w#*)ouuyF2TfcyRLB%QuM z0BHI(NkJd=UYQr-Ej7ZBz!UXvbwo;h#X{am*>w$CN~Cabk#AAJ_hO5i>E0hBThLLl^~Iq+Tt3FC@;2Z~BO?5# zE27ErG5>3UP>(+nj|1%M^L`_amF{|fqgo2TslNCXo!2D$)5loC-`4IJZmoO+5abEJ z9qhu0?xsk#DYg6x5f1Xw8J1W+7XoNz7g5o@;M-W4tUHx=SV6#a7oIOcT!WtCi0+`q zfuKK&Mc1h4@Oc9T^!X}j>d_@?1d5CJ+phRBk;h2a;eEGQCroBb|#&qlo zR10DX7oe30RW%JX$X9N#@Ohm|nLwxq2f@itP@B%-zC|eHwwwJ}5irOgw*d4y5Vde$ z6Oupzk;{NpjX%k}hw<%QJ*?9fFU{D1=MFA+>nn7^2^oh~5`q^PEQ z!Fh;RrFjDO8+M*l$Z8lkn!J`f}caNxp|2U4mpV!A8GLSjliV`p0jT%?q#6Vq3gl84{Y}&xB7!CEzUJ&zFlr zeH0PTJ);y@S=<}XmV?USgn+RZdGV7&Pn|OV#|r>jpW4}#0!}ni z7#vJuunY6co-w~0pOe=1UWDg^#3Y=G^73UOLU4M1H(Jw zk1zEaoEcajoHXq#4{>4!1>L*GvOY&wmsjGF@%#lrDhpZP#5w@yKJFSa@jAN^v|Mr* zWqp~Z&mvW`2Cq?|axXxBzI2-KKIjfxI4?qPIdN>3eXNL-$sR_1Ze6)%;!5c2A3FuW zBU>q)XfX4-h-?I~{=(LKEI_i*)lKg0>D_-P)oof1l=bYK&zPSUjnaXDwtznArRdf0 z^zw4qoAmEpCMJ2>!T1t1X8xxiXP*J7TBjK{GsWGq@aNB5&oGw=_wiQMLsIB^x&&_j zjJ3Hr1Hr+1pALunV2k!I0o0C1o$l@JmKxf6pKUp#=StvHYC?L04gsc)3WOL_XJT(} z&sEQDVvo&6&!xDEfGF~TCGk^v@y$IWcYR&A6r%^`51zF-_$}bG$8+-!BzxJS_C?lp zZ%zJK=M}Q+3)syJh?p?QZ>|nW$jS~Q#a@Wq^yV!}G>qGRnwTkE|I?|Ym<*A~9TB+{ zRhbwajOVuQn7UYN})=>g9C@7hVwPO zhAaTMJeP6{GRh6-pOFXkaP?d%G+6!P$B)|DSBE3(Su1X@NqTdV zPE#o+j+<&4o?tF*?U)W%_j98^t+?$g-1^8fzbw@kK`6oJet8+l+a&up`JxIn0OAdM z?79OP|9bo|?lL6K(!#=dZ&X=Bx}m5@_yvamKfm4K=Q_L|Mw+(Ktl}V#3F+|Ewe?lE z-RaBii67ERs98O|$@Hd(z*!LrP>)+x6}i}{l$syLrkYWe-F})>^N>cJ1SL)?qNgV! zG8Y`4A|v-+dHK0wfahTV(xfi}HgemW znm#GCYD|xMf!KOHsTw8+9lZARSR;LUQ0mW4n|Vel%;mF{b@gE8=C*CSj4#b^fmC_u zkln!2+Iq^lg3xiSl^S#exoXpS0hyi#iqB#N>Z;^-8dKW~=b?!aIEcU@c^m8gZ^4+D zEi0?Dv+C%MaF~YQb;3O_S5bW2@9s~Ci74Wdk{IdeOiU+x=ElrczNRO$KOY?#LAZ}! zg9-QHsTY{RU({7q3kvg354U*DM<0sf*gdke+*G6#YU^mP;p1AV=eIF4q9R2{No6{u zP~|$$$?BbpwP^QEKZj!)@a?J2TYXjHTB>HNg-6Y<#BiR?@F z2x-8*n0ZEVeJKVt2;-@o%pa1m;7J6OcYR_(MP@)@$VVjAUgL5byOsd5$U2S!J?jg< zV4&raRaXApGwj_~MzeE=XzC08P5VI33`id6^xs`!-B;%>tg7U>c0VP*>P^exv0F8^ zgC^asTFcwa_G*+C#>>N4i-+ifL#eqF{o*XhLwDwzR$CS`YxXank`gDySHgLH3R)J` zv1$H%!37OkAby9#BGe03B)zp&Ln~^_5Ab)8ya@sD;_`Bpw{O!C#i1=tM<&wYA5>I? z(Gvho+U6DJ-*CCNfcx;^F=e-4Xl(2naFns@*DtM8+n-*E5fTz=Cz2zC>(5s$YO79> zp@2LFeuHyxoPrW6xJ)ih2^b-sk2`ly!^;z?~4nu;EnIBd#9!nB{b>`+)==DZTk8;;`T>S|< zZGV5gN@)V&170fffZq{u(IeyIRD-4AP$m2%TXmPSH?Vj9gBF$f(F{fU=$UWP&`Avr zag~oBMO@v{AXo={meAtmHP?f9QHxw$e4}m%<6RPNH5(`*OHW@M;{2uLbo}%w@iaLF zIIkx9+w0z5z6=C!N2$X{Dd5c`@bdNEY9U`oF@K3cW?I_d*KJ25SLB!S^2&>gP44x3 zBxh*#SaWaltRV{>^la^UixKq#mTHC07Q#35MJw%+l3`W4jb?+aq}C-+g=+R@xp^%c ztgqLScb?y-_kQ-MU}0wYL#Ho%jGvdEzd2p!6nvJWP~^Xm>P|WCS=(ScQ5Q!^rz+ZXSya2!8*5|L*6{*X|yCOCk#xAlgb1 z)MI?Al|>(&4n{5mn-$gETpVkD<}w3#5+x6wM7?PY=R1n=Mk|o4>0vh2k%1?C$v3va zwSb2)Mmo-ae)xLl7q(t29WCtn92?4|E}Q$VSh5+*#I!&?7*0~-EcNDmxAwW<9&C9dHsT_u37bu?&T4ssgzCvu8}Xdq!C=SG?xM+1 zbAfK7^ZAYtY`ig6`x@W(bA#{k_*YJ%g0}Ha|PX-4ROGZpfhxp?mR@+SCi2 zbKqRvojii}nr9AqdOGhZs*)K0-{0u4T%0T4#j#(|yl}qr3pzWUmtBE@cMxDx3Zf?^KSy=+6gDt@H0`?c!?*<6pq+4}?4Qwb z+mn>)H1+N`92IACZ_djkSU*Pwk@+8qSK^B1FxtgqYF&ShR+JfPU{TwF9rb2mRS|($XA;g64FE6(dnW$HY%puxX$J2ExuO0yHaa-Q0|dDd}sE@Z5V0IZ*fR z`cRAd!Xx70F~CAJm+h3>9o?amn4~;%kl--YQ+0dCe-zzn^ThSCRIAQr-~SKD!GQX) zX08|eI(g#Da(-n?#$RHBr{1?qtNeEVMh470tD)j@|NGbfW@@1vC)XtSai>y>qP%xz zY3cby-A7#fqzQ^kJGMd_+o|cvgVPVzMZs(TKjP})RNqpAxGg(=kO>Ro= zsb?Ms9AfMXU0wETCrmpiq)z3Dr0EI5uOz>)(1Fbx_^DGrs`%{BW;xP-kkP#OKDO=R zQi;De2d1NVZ1#-JT|ZWlWbPwLS5;So3wU&Np1<|=^^Z(n%)&Ma)Z(RS6MJ#6Z_Q6g zIIpUzZe?jTIqjUp=aG?<^QC2&L44mKFaqS9P^7_+AK#f?y;fCL-kZ%|4s_R`bx2a<~C+VoIYDG&-!<+!V15AvkPKM*h}%5skZz>1=sy+o(+;DYKSAs zWr+gJe__PB2BsMK1kt455w)(D-thK~mGO%5g6{;a^GrGN}9$<5xE5c`cTngFa4{a-&tL}I$?tOok^7Nm^05+>~CJ*l3ZEdI%D zM0pdNnF-C<#NgNspYN`8!Q#-N43^ zrRHm=Ls6fz$!{M;X6J5aYxPxB$_Fejo^xJIK&taL;uD-m!^lN{)>j+h0RLF6b9-es z5mYhth>yu*r!7CZ;aQb|*ioI&Nn5_OQVMX!+RdDJUAyMAj*|Ju-OoVW-WN?=&6jhw znT<4=2mSl*U7bFDge|n@Bda1?o>#W9w%Y)bdvm=8VUMjEtGO@D18}REwHc4?Grd%h z^Tw{34}^i?aM?8JPyB4=&v@xfI!3!)N|tVd@sCN^3XRn%MQUqIg3nZ!KzQXkR)2SG?|F)oguk(ni@O;_6Y?>(Th0>pSWy+*^)gY>|N zz&3u6zeGnzZ%_Tvlh6}k7ZU>!fk@2nY=DfAq{(Jg8!Sr=C?gpDfln=_hw$Zo$+Pq0 zTp9ExJ1a0*)~6C&162xwX$Gm2wHHF?dr;{5&O${10RUi{YFaL+6-zh%I4X2U$!L?$ zN<2S&psc)d@WaR6f$qa&{CE+&p(eeVU7Lo()X0zsotMpNhCM$7!KAhngz)h2h=lkb zu*bZ2k1!=MF^#@|o~WWg+sxESOG?(+*(otGF^QqqldAXJ&-X+o{COGpZvRFu>p|SM zHir-ctD1>PBNmlidCy!o2#F~r9)1h^W~M|$Oey#9S2>F0*D*W9zi_RXS$r?to6niW z=VpPw=*DM+@175WDZ1n1NX5CE;@v_pRP%B)!KuQ@adr;)djHK%xVbny7OUAi!q(GA zD8_U9Ihd)b0sH|F7AP{wMe08c|0?Xe^66(*RvZ-Rx+`C8yHK1_z-4{)RQ#*|+qniS z8(ow^Tc<#uuK@-@ftsvAs-d*U|I=8aw>KWUPUO?2;ROJ^Hcy6sX8=qb+ z2cIV;xzGpVQVoi|MztfZ@qx(KpF+=cskf9=6Y$`Kh|AUk)wVyez@JbFtMEI2dS-f*C>it~Qb?IaZhncyDN@O-B=I3yex5vpv zenTOS%Wi9PS-t)QHNlwm-*YrbUFgsr+MsoU$qmpNJ zNf|oE7+fj&=pi@5rjm8YqOoE*X90p zDG}jBu|5qwJrfvQDzCVOO2}bg=;$b>7WQtm_^mte-Ql_fAFjB+P|BYQ`qT{GB!eI&1|oz4EUrNhlFZx781j zURx#Q+1sBw#xREgKW=QbBG}J&-u_mV1^R1}v>oHQJ^~9B6&tHa|KR=uooUF;S*Q8g z4}&J(;-Vr`zao&1h}cEKqrDUr+18_w$qg;0T+2%a_2*m5V}AB3M?z<>0i6ckQ>S`= zNq1mCw<=7+S)xuo&JBMSz&Nb_p@sNwqihk!PVKh zkxVzgGaenco$^YTX}^t!LaXC&FL0s2B3xki3wT(;&){FP-E>caj=z{coIZ{!g(=@s zRrdY+Zjka9?dr`pDl8>;R0V5*)tbD0D(W-dGjQ3oFX1X{sWsJ>&%LYXX$oEENa&+% zIMbF!yB&daz&5WAL$!#bL}M0iZovNT^_~g>C$z`msPra5Bs*toGf(i zy`}Tm>|)~(d@bTPsR;>YmfJkAab58HSy`sc4wHEfJJ|8!PxX8XOG*nK)`=Z049YW% z00YrhU!87qn6(#nQLHMZklP~rK+wSyi9ACz^JU9l0@t1|3;f^{d89)AC_3zq1y_9C z`k9oZqoUH9;COQJUQUM*oOe4H} zYW+&o+ckJ6r86Z6e*5x5G>@IpXptSf@Jl#VYEoRMB4|Bj?)Ox4x^>3^WH}`wjnC(V z2?>d&dh?5lP>GRmZ^WRt#^?Gz0@fO`vIROwQ4>bRRc)LJ2eq#Ht>HgT!0oYvT@D_xdkpE5+ZCcmhCD7=k5I|3$m z4ij60OPO4KVft^|ud|`r)uy;A%59#{nfL2xLT|U*(0`MoqO2^pPde9N$qOpql4^EM zbM0Y!5GNhb3N#_1J%NzqwiVojiIkh2r&bi9slte&q93xC2aAA33When1!M12A5D~8 zL1>1C$2S4ngV*yz=6jf%-d&bYEpvY0`onCgBuBre4$i5EO!xrN%NdF+(7eF|n~>6e3ae>2eQ&TTo3!Mb`H`MFfy!4~Y4}0S?;# zuC9~THq?RSRkE|gkyV(M%j&FAQdU-WUK-zlFR|HmnIu$-g4@a+$ke{RcTOitmF&c2 zKG?sa2~^Aks7pq6)YBWpPbPZ$cJ%rW3hwW7%JK>dW9?I*s*s(IE}8(4IZg}bkx@~@ zE7;3ZBj?-8W>&*|Ji@g6{NYGraE7fR;q4wZZ~iJ7UYiZRIpekli4jvY0~l!ySYW|i zGSMB<5?}ubTa<~_@OVxVuh(KWeYQ`dFtA6_W@ZZZWR0^*cwE}`69W=3G1Y*>s;#W2 zhxv@Kuih0H9~XO4G6N_Zv@ipHZ!FaC(*z4kwNeHn+I5Qs#Fa2H6*NTxyo|rV@S`*q0(2qqoc2*8rKGIW$;1^|O5op6A{O{jGfaBfP%D6QpNHG`a14QB~Bf}D{ts-8Yc?Ef8+us0* zni(*+9oU9K-93BV*r{eDn^`!f=?~#>gEQ6Ua|h+A&0G1GNDMjsL}F|{5~5J zxsMaw_VwmJ5llbX9svi`(ZJmG&d=+na4_r=-LdFGc>8zO0`Qlr2LC4Ae)td{9X&JM z^+8F4@dxxAIDlxcF~$mF-lCfjU@IBClXPPLg$-as@_luT+XgWmGqtgvs^)owo8Lg+ zCeC{QT7}dTs#GtpGuprl(JOQe^0*~!xSWd_9$r^rZeLCGMBk^XL;35W5Qs8fRk;C=UgRMdh))#M^*wB2RPM6;q~$h|$CaGY7EMJg_%qQV6=j#I4i zyfs^20hMs(rlx0GLS?)(2Of0H7BzGmop2fJ}-mGcy>E z>?tuN;Ac$CEC*8byD|#x#&p$!9wNl}Y$YbN(+I|fhlh0))Lhn6Zk|Lchn=~Os{@pA za``%%*}eppo?c=*+4J`%I9yFl?Uc8mi_46lzhDAvz!FT5zP^gdD9%0= zq>s6A82ofg%6{W>U7|F>2FIpB-V1p2=n-QW(Jb;GZ31#3uvPhsbm;ryZBR8bD#JpU zm>wp}N2T@QugM<(e4o{Yr(Rt`O-)s5 zxb|m{l~q(6-JDjrN#njyB`49+a&4__RvaFd=G`(rjM z_vD?5bK`%l>FoN%D)*@#(#Ro}I94(~Qvx*a_k#-?tKn)G#WM63sljd_PO0NfEt)g3 znS$^HqKgY9A!J1D8&G+z96>qoE4$H3o)9ANBpC%|!1jbC?mr$%y; zstO9e-W~O?W}&bEB)aGTQ%g>u;YCo zCx8-&!=?bxNztmH0F>7}7po4N?s%Vm_AP?u_lv*}6P$pz2_)cE<1Nw3+ll!Z8*!Ci z=zyf}Q@S|&brzWjiE=CsD^#*KGNPero9;30X~S;p<>#`Ie8|ba?P;-45)B=gYjjx$ zLJ<#nKM-Ha>2ZGG;$hQ(Qh~q-gNc$1#k}N~FRk=c9Zp%YnK9O5s7q0(&rIBF;O65| z3g-bb7dQ!=$!|cusf0>Vb4cdCPv{%+s%v7tNR^?m*(}OWd9-1EdPY@C>)UU`pFcMIHNuI9^TePdSNy{pGM7bZjmpI5|-#U(n)#{{&c3_UHXxv9f+tCpjCHIpyc& z->x@Z6sDwvF}=_4Dd+*;g}T}}v!!(#h+1fv`_@ttibgT@|i=bsuX@?{bJFrsV@W$#kR5Q za-A%76<;;uK0mnlwm_MBJ;I;_&XUEsYkaydc$Oa;Ck6wYG7(?Dr_|WzSI2_WJsKAe z&(zG+l4p7VaqZ+}-R=U;Z{4ACh)X>a6EVy;QUCug<@&4D!uyyW9=A^(O9pmA#bxYD z^@GC#_6^?sj~C!MKJMeq#XWUR&1d8!Mlj3Ye!~2~TVaJku`1bC${WGw6mSG+ z0H{q^XZsDl{j#pcQsxZ%KS9R;MHzRe-40eV%LG7&+56J7Gp5F-%;%e6m1K%B3JTVN zFNZx|XnSP+XW7A5Af@eyIjc%an!RZj4eb>q&RSh_SV+qt-|gx>^a|`3lu9@d^N)&4 zHL!N^a48D2z8tLqi0~B8Q5&NhZNm+a15qgfMc6Sc83nREhiQZw@05OsCQ0fVGZpQ>wUIAQKKg4I{> z{X1AddGYa$C=`gIkEyxh35;Qu4!a#3FSJogsREe@n;T$i2)f zq%Jus%Fpjc_Qs77pK0)aJz#vO=sn;WIzK`YNAz8Rp9P5A4d>puii!5?^FHD-bcw=; zf&wEOJmNAgF1+g}cvdcbXA`T<)>c+wl%g$yS(;jwWD}+!pU$g|=l1MQSL8p!YZ6^k zdMVlYKK@aQ-+8Xg`}cb@RX)F?%mCJexLTy98?lJ85RW|x7@nzEU(93#C+6Pb-dP*6 zxu5`akePpc^>m&tFv8`_!kDt0uHS9k4MBKelesB@IK-{s^dUPJENXRQZu#3tM7Xr7Rg=M33upnh00da4#Z15;KGQ^_hVSer_cV#@e3}Y)^g4{%$ z8gAHuEH=$}>>4;aYWA-P#Pc}Q@}TU#$P~tAug=R#z4#1kf9$0;-`WX?i zFY5|Zy6g5_SA4jUe#sPWpU->Mmor9rY z5?Bn;m$c=3U#>HlVOD8fg9l$B&DjMt=;M|LA^|YwA+3_?I5Cnyjp>@hRFcYsL)GBQZLD9`1KplG0Cd?j9`MOug=F*qf^>uFWzX zM@g!(_g{V8`ucWgXuO(ZoBZi>Z55TJ>w{lEvrNPHZ1J@3S{lL^om@`frDEt_->-v` zN*Zwlty8(IfJy{w%SG`u4b891a6C4}Fep391z>R3E5N4{h=-*JbXU#1eA#q)a0n=0 zcCpm$FiYRJpkMScF&>^|=FpY63n&Cwz4n|;TxMZjU%cdJbX7Jz&B~I2hN_xso|^%X#H;zZP}@gpC1Gr> zPxS(LEc{lKw6vJ$q8H9+#Z`L&3rsw|7aftBsWn4hGlBz~ zb82DTGIzn${vPySFg;)afEZI#XFxP<%@h^wM1OV#jg1O(3!{^g&KLhkZ<)BboR%Q4 z1PZBVx<}S0X{=>sxAdqD6!1cA)j#VfVW*NkLTAkQZESpQ?+xmz{on;kSqA);8{uweH_YVsLv;i9z}qqdQlA#v;mRVC>O zXuB&K@*fF_l~o-3R~v^Ts76$Yo{-;7;^D@()P_PiA1ro2$rUI088lh#c60wH%*AnP z#k;||ovs#57^T8fclNf5a&vVwHJ_5Ak6T#7dY19^t_# z(f}m67i9*CQJ0`)#g{-y_f%1e^CL5$Uwl+ixk05C0$~&Zk$ra8uN9rk!%ZRPp>2mu z;Tq;rO1;d@$_b?wsvn3mw@(xRHhNTVTEt*FU1xp000>d$X6x@7zxH)?o%k)W&@*lN zZ0+<;l(Mq+ot~aNbM|z$(bYxCX^=7&Jo(w!h+13Vf2~vHy7`?K>@gtas&f|3Gay2~ zhmnaG$A?zMM2LM<($G*+ItN>NSVd*jUH2nH*!kAj2!MrvZESDrAlumPZ1x0Rr~M7X zPLE>s^&gG967R|-FGw-j@mzo}dL1@X2ki@FLI`_;hy+OPtXw}bUT=$uX-bcvcP?(- zy~y*8y#d9_>heb4W0L&2PGxX3M|=U=!lb6o-07j-FLQ+DOttd^L}?u3exWv|dM^-S z=sK0Ob+jw3%+m7G4sMsF(d+&|M)2M%Db}m<2F2(&u|*r3LweG3EL;$V+tosF_rZrM ze|t&I=X|hPLE52^xVE(f)JJe^ew4#;vU2_{^$ZZx0B-?X2ynr(nl2E32fm^q2`^}$ zR+;zMw4P8kPyN1~o0|qHRJs|8WK>8pvx7#%;mhD$3_!4=3yfiAoR+P%-0N((6oLc& zV_LvRV=X0RFEg1+;pb9TLr=>Llf(=H*^pk{*#r>m>*^pB8{6w(^D!ejy=6jRg{$< zW8;CgPykwHja>3-SNy6zI`pZv+AV$TS{3dp3`3;XGvOCYB8boT(m8?Y?cn%E>KsBJ%dK}Sai3QApDpSo$pMGdPgV1(_?jd82;WK=!;Hw6qrxSmFVrb3RjZspy z6DkYzAtBfOV^J4S_jGEx)8KP|ob0wr@#RD+jnSfGE5h^vh(1A5nY)wa|5!&L12};F z%xQ~oxFN^H#1zH)!1TVGQs-8;-1wAp|05yc58y9)A`aZiK-upEtThf^_$xLx03A4O z%*p^R!Exdi|JszY_w(B_pjX83aY4^?b&Zb|H8pS^Uz>&w(Xs4T%&(Fmu9MfJ34n*p z%rJ@h`4liL$%3#)P^9wWAsX9eBv5Yj$M5y#q=Ans>B=7g$e z>z(!iRsct$F;@aP`4+Hzrt*8`{HWxiG2MuG`{>b8sXk+1;1@X=j+4`qShhs5l{hs1 zGh^t?ZfgP&6p1FjrR4%Hy|-`mHKd`fwL|x=c+=WhL#1Y});kbbkFPI8(`+l1$i>tZ z6iz1>Z>}FdfBfjN+1O~j%jS;H$=_v0-(*rXmw_%X>{n5&v}Lc{@hG@7b>y~zWA)tI zyHM*jfM3xXp%lVp%?B*2S-_8CYH_i73?1Nqpskf?(Ci_nCK+_hCHZfwO6`Q7YH`@P zN}8Fuw}n7&@9^^S0{hJxp5A?RIWM7^6Ltd=<7NFOPrY_iD{IS8DnT1m{1DixE}%8Z zkMp7Bd8oMfpS&7gr4#|%;Xntx-C+pmr=&$T1@rFy)x)kc#C^B5B1`1OXY@E?OA0bArpO{>p6YkA){=-_=_|$MwH~)5Hg#;e`uEC&(KA#Pj{l=|(QrI{cQGVtNqWXZV zv9)E>HQ6B)S5X-Ov*lASbpQVLn2dNUZ$7r#T)J?2I5sq%$mj7nDdZTC*qh?BLQ zb%jZ9>U=J8zTQbmUw@$cx9{z&)($D6V#vbu{EQXrF*U*mnOmDT*_O3iwE5A4eLHAk zBSTI>Ze(=SLdPr1OEj&z<5si(F z2TB!n4~w|(TI&&&ihWcWc}R2S8d}PSX5uORO~;-pIA5Cp92Vqy%4rT^&(B3&)=*%B z<>xG7ykL`+lk>vAXwQPpeC))pan?`MGBr0pr4TL(k7K-6+xU=cGN0S11Q#iDW3>A^ zYsa>jvwJ2@fefZY?g( zJDeR#cm+%i?Z4J(&GGBrC+D^7z#p2x#Q2-*wmc38szbr90O~%-W~Oe^{-f@XmgSbP zDYC{faeh!&2mU%r9}y8Diam8BpuU0T2Ve(V`P+QECn`xwMqHeq&*?t+iKP1(kFu$= zyc@s9VW>`11$D;nXBaI?JF`J@Q-Xs0w{^v(x$9%Snb{ThG7bglU4XCv{=}81r(}D3 zy3rGm{g?f!-ddc`s=0sLwzOm*^-c;CGk9%fr9=nuxhB_`<=)Lf8k9A%iW=UM0rs9> z<_x1m$hFBm$Ob=#I4Q=bHJ6PIhX(;epgYJIODvD`iPD9Ur9~d7` zd%Hg#;<0qW>Hgl;hn6-oj7ls&rvPF$e8JEZz0&+Z607A)6Wf1WjDs@&V{H6@1K$4M zgWw+RW4(C>dSF+i6x?H-ZvS2dw$}gp8gC^4>H?-m5_X+-Kdwl-L{L2so(z(H_bBD* zyLVkMR&nVtFYwFsAi=k_eyZYm6$cF1UZ>Q+oXBV1s!FrodPi1XQmS1ZE?Cn5HK35L z(euK8D3-}V{4AIYNoitvcyS1BX02K`+o{!H`S!;A@7o*tm%h{A|8vKTWnY(m z0HY zw?<$zFwlphy8&cJ*(QKwvKz_%BJgJ{kk*)dYEJ)iYaU9xmLUBe7#LVzPp**>l|g%o z1YW_c?5toE*G9v7;hiS$o5UD6Fy@KabQuZ?YB5K)8z&^Ws71t~Qyg$;-f3yoB~=lJ zO1&el0gd;MN!e$nHQjaw^hxj{7!V=Z|40AZ(im* zOoj>Nd++z2d(Nkv8?`D(gnbH$EGoh62Qu)soL-8xY zwH(ggMML-1*@sqD}^ot z(AX1+LOMI+tVo*Hp!f_M_757V4`;p9SJnmolLl%=z^7A^lE!eSH#^ei^fq$~AC-sq zh57mUjh3(fq^t|+v4#iZI}Ajm=H`ZjM7p2nJbS1lKYyaE!x8vO5c8U0Gm}WO8*iTE z-EiaSwSfC0+bBx?sJiG8;&3$e=cr|c9To4cU%%cu^BN@Jz(*1YgyhOV02QpvJ4FwdQdUEVvpFteXg2j}+7j6EmU|auQtdOMM}DMoZ)vK2+rpjG zx3f=)ajPQ9e@%sd$VIpr#jW)aGNL^>@6L~CAI5TSpLgg_7CRbr7Kc7X>!~V5BxMbt z)Ur$vMG@&vYlm_^_PS?3>I?45)D|}V@r}B*HE=LMk1HAy(BX|^;J@r@(+lQ30U0Ma zY*s@;0a#L2VK~!6E_!+C^u4-{c^)4h52_EKt{Er$0Xc`o<7jKEOa$VQ4vKuAUmrfo zWBS}@W!naC;x_;|M570?&|zn6?Xjm%uh$5FRMt(!(~9TkA%r}=vd*X_k&B9y8qb-& zd@)=<&6~N4K7M$*vFRnKo-O0!A^8lxn3J86d_Y?6`}YT_3};B!fq$YzpTd#kIc4w{ zJ083N^M}#8BG{2YJm>360WRGfej5W{1|WqrCXYSnnkgq`Ze`_a9i;x!RRZ?IfdL#S z%D($D@S}lFb?C!~gEzViAwA6G!xvuIViyzjAvAEv)AW|9x3>da%WA^(m}kWIc{Mjn zvkZcY8)76~6ZOrsjY5$zf)dsC#S<{-b-(q&DF7Razkc2Spp=<*4Q+UNhxJ&T7AXB1v4)Wlycry85m`w)hlZgd~E;%nK7H*=Y!0#VL%l^=0A?ZfhdwF4eD!$ z9fDo$=3q!i(}QId_M`#nN=;>Cf%C)}Xy28UkopM1V?{XMuNi!arsgelEzKMo2&4}_ zird*A1O0FqKD3~qvyBO~5FD`XcjHp4gRATF3tTD4t1b9EX$0ScF}(a9Iwbk^R^YQ( z05ep;wV5rxZ=XzszfU{&52Jr-qYmk~nwt-lJN^Xl_#ghOc55=(Wh30N<@(%&BX04oZs2sXq@h2z8;$ikE1#&FMfoP-lmy`5q5~cK_^|4Ifv-T1x7dAg+UjnzfLa+Vnk^fnaLrZo`!4fb!l2~Us&mZmj~@|4wb($1=d;U zu5{fj%k<+{T42pMlsGqd^1Rtb)BBGF`1-z1u%C;Dz!bpKE9q&7G{mnx2SLTPZ36wM zQCOT{E`xn>%2HoTYcIhw-CN4dE24UD101+_Z(+P~x8u%m1$c9EOQWZWjO>^ex~B=w zcsjj@ei+t>d&#NeTk*>as1d~*O6TGpWt)Y*kbSnKCe zi#yWx_7B7Y39hHQxjmuzRS);0QOOeA+nW>CShv8wH8su&CLNy3c&@)bL@Otq7ScbaEdO&GSl}DnGE8E-@o?d!pyDJs3Hu z7RW(QCCa&@4$SoheDEy)`Jt?+5k|LC%}*P(-M*HRh4}iUq=WtB8<5E{*KN`Yx&bR= z`$B&ZP%E{!R|GBl=u;g9>-iLlF``YQb$W|h4UHyz3U>oo4i1|(uc>KSMC8{Sd^|8R zUZAOVbo9yI_y*V=P>XwcMR)06hJ|D-Dd>6|_x*{8l%gVwtX(cMAp(*;>hgx8e)Frz zM+l|5uz-s~?eNA6nAY5_E2eVwk>X4qHoKHcrJX1ACtqDE{EBn=!fgk^mvlF}Qxw%u zC|(z5g2~JGVP$_q9r~nUpmRH10J1A(vslG^wdBf5Jtd73k(*FWbkt}^eq5@UtvjLh z@Q24hZd_8)l9K{mHk8ng5)u$=BfZNg6oizN3bDG{fX|-|;SVAq|I4|n3AE0pqbQf{ zJ~Q%B;T|NgZ>w^9nvvh9oTqBgj;IZW4^}1g}Sv&bQF*S z`L-{26Q%bhghIFHnZKM$k=g%>s`Q%++rIrHzOddt`s&FmF8gOtN0024krn?(tikaC z+aXUl$rT{Th8S;(k3-wrVQ5!-4{rr7`DG4$rLSy{%*13}7~d+^2@(e9e#k0n<`r}e z4i(djiy=7^MAQ=R2KC;DMHemnbmMpbJn%k#Ec;xkybfD<-M-3#aeh4##vgQ69ou)X z_r^c<6*X878gtKIJK4A_tEQ_Lqy`LmQj(36olfY|i{wJqCF}H+MFpk&>gq#=YFnoe zZZjdZbr}hF-ZoVQCmTg?<`)!zus=O5t&B_t@uq50)ouw^eqctN?Ae+cLYj~PvuT6F zp?@2{@oe+@Z?;nIfWZ~4Yb&d(PO6ZK-Onc;8XwUMP#CBZp@0@VJ)l4dHH7Bo zX1n8t{wPVz&t_(3#^*GZUi7zJ!^EVbUd4`&$ca9*Jo=%qi4nHohV04?Lv#pBqxu!Li0>_`|qgTy$ z-iedC6Svo>9{G1QZals)6mu74DRC!muuhoM>PUnG#Y8Xpw%(Qq_H{xgTT-S<6{pGS zZNbs!Fn`zgi7)rp>v#-e-noB4J$w%yjp(oSlvVQLE|HnvR!RR|^>>Frx|JdPkSP5M zAQx}k_J5-!*QO+KNB#|-{0CaV>@vOe{zvjtvdE7r_AdKPvJKn%7W-ArRqtyIb!Jck zd4e>vRKivUM(Hd%K#CNDJ-O(x4>b&y`=$ z3^80MZ^COwy+t5{w6_*2rejLSjw$Psk&<7=C;JD`@4wD9vAy4&-))A$JoHaf$2xzW z3bCu2;LQ|`jMxhilq7mdZ$r3!e)In$EOzYb-{bec5B+zIQ}@#ycLMnT`aIoYh<7JE Xno-hBzQqsy4 mlb)>sC$NS*fi18I=pM`ye-i5L2CKqSc~7Hjn?FLE&-VfA=H9>n literal 0 HcmV?d00001 diff --git a/client/qmarkdowntextedit/trans/qmarkdowntextedit_de.ts b/client/qmarkdowntextedit/trans/qmarkdowntextedit_de.ts new file mode 100644 index 0000000..134f5aa --- /dev/null +++ b/client/qmarkdowntextedit/trans/qmarkdowntextedit_de.ts @@ -0,0 +1,57 @@ + + + + + QPlainTextEditSearchWidget + + + close search + Suche schließen + + + + Find: + Finden: + + + + replace text + Text ersetzen + + + + find in text + im Text finden + + + + search forward + vorwärts suchen + + + + search backward + rückwärts suchen + + + + replace with + ersetzen mit + + + + Replace: + Ersetzen: + + + + Replace + Ersetzen + + + + Replace All + Alle ersetzen + + +