- add markdown editor

This commit is contained in:
dmytro.bogovych 2019-05-12 20:17:36 +03:00
parent 90ddb7535a
commit dec5587c38
30 changed files with 3985 additions and 0 deletions

8
client/qmarkdowntextedit/.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
*.o
*.log
*.dSYM
*.plist
*.pro.user
.directory
build-*
.idea

View File

@ -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

View File

@ -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)

View File

@ -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.

View File

@ -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.

View File

@ -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

View File

@ -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 <QApplication>
#include <QFileInfo>
#include <QDebug>
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();
}

View File

@ -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 <QDebug>
#include <QFileDialog>
#include <QMessageBox>
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();
}

View File

@ -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 <QMainWindow>
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;
};

View File

@ -0,0 +1,82 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1070</width>
<height>839</height>
</rect>
</property>
<property name="windowTitle">
<string notr="true">QMarkdownTextEdit</string>
</property>
<widget class="QWidget" name="centralWidget">
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QMarkdownTextEdit" name="textEdit">
<property name="plainText">
<string>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 &quot;as is&quot; and &quot;with all faults.&quot; 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.
</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menuBar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1070</width>
<height>25</height>
</rect>
</property>
</widget>
<widget class="QToolBar" name="mainToolBar">
<attribute name="toolBarArea">
<enum>TopToolBarArea</enum>
</attribute>
<attribute name="toolBarBreak">
<bool>false</bool>
</attribute>
</widget>
<widget class="QStatusBar" name="statusBar"/>
</widget>
<layoutdefault spacing="6" margin="11"/>
<customwidgets>
<customwidget>
<class>QMarkdownTextEdit</class>
<extends>QPlainTextEdit</extends>
<header location="global">qmarkdowntextedit.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

View File

@ -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 <QTimer>
#include <QDebug>
#include "markdownhighlighter.h"
#include <QRegularExpression>
#include <QRegularExpressionMatch>
#include <QRegularExpressionMatchIterator>
/**
* 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<HighlighterState, QTextCharFormat> 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 = "<!--";
QString endText = "-->";
// 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<HighlightingRule> &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;
}

View File

@ -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 <QTextCharFormat>
#include <QSyntaxHighlighter>
#include <QRegularExpression>
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<HighlighterState, QTextCharFormat> 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<HighlightingRule> &rules,
QString text);
void highlightCodeBlock(QString text);
void highlightCommentBlock(QString text);
void addDirtyBlock(QTextBlock block);
void reHighlightDirtyBlocks();
QVector<HighlightingRule> _highlightingRulesPre;
QVector<HighlightingRule> _highlightingRulesAfter;
QVector<QTextBlock> _dirtyTextBlocks;
QHash<HighlighterState, QTextCharFormat> _formats;
QTimer *_timer;
bool _highlightingFinished;
HighlightingOptions _highlightingOptions;
void setCurrentBlockMargin(HighlighterState state);
};

View File

@ -0,0 +1,9 @@
<RCC>
<qresource prefix="/">
<file>media/window-close.svg</file>
<file>media/go-top.svg</file>
<file>media/go-bottom.svg</file>
<file>media/edit-find-replace.svg</file>
<file>media/format-text-superscript.svg</file>
</qresource>
</RCC>

View File

@ -0,0 +1,15 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#4d4d4d;
}
</style>
</defs>
<path
transform="translate(1,1)"
style="fill:currentColor;fill-opacity:1;stroke:none"
d="M 9 3 C 5.6759952 3 3 5.6759952 3 9 C 3 12.324005 5.6759952 15 9 15 C 10.481205 15 11.830584 14.465318 12.875 13.582031 L 18.292969 19 L 19 18.292969 L 13.582031 12.875 C 14.465318 11.830584 15 10.481205 15 9 C 15 5.6759952 12.324005 3 9 3 z M 9 4 C 11.770005 4 14 6.2299952 14 9 C 14 11.770005 11.770005 14 9 14 C 6.2299952 14 4 11.770005 4 9 C 4 6.2299952 6.2299952 4 9 4 z M 4 17 C 3.4460048 17 3 17.446005 3 18 C 3 18.553995 3.4460048 19 4 19 C 4.5539952 19 5 18.553995 5 18 C 5 17.446005 4.5539952 17 4 17 z M 8 17 C 7.4460048 17 7 17.446005 7 18 C 7 18.553995 7.4460048 19 8 19 C 8.5539952 19 9 18.553995 9 18 C 9 17.446005 8.5539952 17 8 17 z M 12 17 C 11.446005 17 11 17.446005 11 18 C 11 18.553995 11.446005 19 12 19 C 12.553995 19 13 18.553995 13 18 C 13 17.446005 12.553995 17 12 17 z "
class="ColorScheme-Text"
/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,16 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#4d4d4d;
}
</style>
</defs>
<a transform="translate(1,1)">
<path
style="fill:currentColor;fill-opacity:1;stroke:none"
d="M 14 3 L 14 5 L 14 6 L 14 8 L 14 9 L 15 9 L 17 9 C 18.108 9 19 8.108 19 7 C 19 6.1891 18.518727 5.4980937 17.828125 5.1835938 C 17.934063 4.9776938 18 4.7483 18 4.5 C 18 3.669 17.330999 3 16.5 3 L 15 3 L 14 3 z M 15 4 L 16.5 4 C 16.777 4 17 4.223 17 4.5 C 17 4.777 16.777 5 16.5 5 L 15 5 L 15 4 z M 8.2167969 5 L 3 19 L 4.6875 19 L 6.3554688 14.570312 L 11.917969 14.570312 L 13.451172 19 L 15.140625 19 L 10.210938 5 L 8.2167969 5 z M 15 6 L 16.5 6 L 17 6 C 17.553999 6 18 6.446 18 7 C 18 7.554 17.553999 8 17 8 L 15 8 L 15 6 z M 9.2324219 6.6113281 L 11.361328 13.285156 L 6.8730469 13.285156 L 9.2324219 6.6113281 z "
class="ColorScheme-Text"
/>
</a>
</svg>

After

Width:  |  Height:  |  Size: 998 B

View File

@ -0,0 +1,156 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="24"
height="24"
id="svg3869"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="go-bottom.svg">
<defs
id="defs3871">
<linearGradient
id="linearGradient3257">
<stop
offset="0"
style="stop-color:#a50000;stop-opacity:1"
id="stop3259" />
<stop
offset="1"
style="stop-color:#e73800;stop-opacity:1"
id="stop3261" />
</linearGradient>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath4210">
<rect
y="1024.3622"
x="-7"
height="34"
width="34"
id="rect4212"
style="opacity:1;fill:#0000ff;fill-opacity:0.51376145;stroke:none;stroke-opacity:1" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath4160">
<rect
style="opacity:1;fill:#aade87;fill-opacity:0.47247709;stroke:none;stroke-opacity:1"
id="rect4162"
width="32"
height="32.000015"
x="-6"
y="1028.3619" />
</clipPath>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="22.627416"
inkscape:cx="5.4926209"
inkscape:cy="10.264796"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="true"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:window-width="1366"
inkscape:window-height="709"
inkscape:window-x="-4"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:showpageshadow="false"
inkscape:object-nodes="true"
inkscape:snap-bbox="true">
<inkscape:grid
type="xygrid"
id="grid4132" />
<sodipodi:guide
position="4,18"
orientation="18,0"
id="guide4138" />
<sodipodi:guide
position="5,3"
orientation="0,18"
id="guide4140" />
<sodipodi:guide
position="20,2.0000174"
orientation="-18,0"
id="guide4142" />
<sodipodi:guide
position="2,21"
orientation="0,-18"
id="guide4144" />
<sodipodi:guide
position="3,19.000017"
orientation="16,0"
id="guide4146" />
<sodipodi:guide
position="2,4"
orientation="0,16"
id="guide4148" />
<sodipodi:guide
position="21,20"
orientation="-16,0"
id="guide4150" />
<sodipodi:guide
position="2,20"
orientation="0,-16"
id="guide4152" />
</sodipodi:namedview>
<metadata
id="metadata3874">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Capa 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-326,-532.3622)">
<g
transform="translate(327,507.3622)"
inkscape:label="Layer 1"
id="layer1-68">
<g
inkscape:label="Capa 1"
id="layer1-6"
transform="translate(5e-7,-1006.3622)">
<path
inkscape:connector-curvature="0"
style="fill:#4d4d4d;fill-opacity:1;stroke:none"
d="M 3.7070312,7 3,7.7070312 l 6.125,6.1249998 1.875,1.875 1.875,-1.875 L 19,7.7070312 18.292969,7 12.167969,13.125 11,14.292969 9.8320312,13.125 3.7070312,7 Z"
transform="translate(-5e-7,1030.3622)"
id="rect4176" />
</g>
<rect
y="42"
x="3"
height="1"
width="16"
id="rect4186"
style="opacity:1;fill:#4d4d4d;fill-opacity:1;stroke:none" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

@ -0,0 +1,476 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="24"
height="24"
id="svg3869"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="go-top.svg">
<defs
id="defs3871">
<linearGradient
id="linearGradient3257">
<stop
offset="0"
style="stop-color:#a50000;stop-opacity:1"
id="stop3259" />
<stop
offset="1"
style="stop-color:#e73800;stop-opacity:1"
id="stop3261" />
</linearGradient>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath4210">
<rect
y="1024.3622"
x="-7"
height="34"
width="34"
id="rect4212"
style="opacity:1;fill:#0000ff;fill-opacity:0.51376145;stroke:none;stroke-opacity:1" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath4160">
<rect
style="opacity:1;fill:#aade87;fill-opacity:0.47247709;stroke:none;stroke-opacity:1"
id="rect4162"
width="32"
height="32.000015"
x="-6"
y="1028.3619" />
</clipPath>
<clipPath
id="clipPath4160-4"
clipPathUnits="userSpaceOnUse">
<rect
y="1023.3622"
x="7"
height="1"
width="1"
id="rect4162-5"
style="opacity:1;fill:#f2f2f2;fill-opacity:1;stroke:none;stroke-opacity:1" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath16">
<path
d="m 0,706.465 1490.926,0 L 1490.926,0 0,0 0,706.465 Z"
id="path18" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath24">
<path
d="m 22.1953,686.117 1447.7347,0 0,-667.1902 -1447.7347,0 L 22.1953,686.117 Z"
id="path26" />
</clipPath>
<inkscape:perspective
id="perspective4146-36"
inkscape:persp3d-origin="12 : 8 : 1"
inkscape:vp_z="24 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 12 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="24 : 12 : 1"
inkscape:persp3d-origin="12 : 8 : 1"
id="perspective4146-3-7" />
<inkscape:perspective
id="perspective4146-0"
inkscape:persp3d-origin="12 : 8 : 1"
inkscape:vp_z="24 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 12 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="24 : 12 : 1"
inkscape:persp3d-origin="12 : 8 : 1"
id="perspective4146-3-8" />
<inkscape:perspective
id="perspective4146-3"
inkscape:persp3d-origin="12 : 8 : 1"
inkscape:vp_z="24 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 12 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="24 : 12 : 1"
inkscape:persp3d-origin="12 : 8 : 1"
id="perspective4146" />
<inkscape:perspective
id="perspective4146-36-7"
inkscape:persp3d-origin="12 : 8 : 1"
inkscape:vp_z="24 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 12 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="24 : 12 : 1"
inkscape:persp3d-origin="12 : 8 : 1"
id="perspective4146-3-7-6" />
<inkscape:perspective
id="perspective4146-0-6"
inkscape:persp3d-origin="12 : 8 : 1"
inkscape:vp_z="24 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 12 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="24 : 12 : 1"
inkscape:persp3d-origin="12 : 8 : 1"
id="perspective4146-3-8-7" />
<inkscape:perspective
id="perspective4146-3-9"
inkscape:persp3d-origin="12 : 8 : 1"
inkscape:vp_z="24 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 12 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="24 : 12 : 1"
inkscape:persp3d-origin="12 : 8 : 1"
id="perspective4146-2" />
<inkscape:perspective
id="perspective4146-36-4"
inkscape:persp3d-origin="12 : 8 : 1"
inkscape:vp_z="24 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 12 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="24 : 12 : 1"
inkscape:persp3d-origin="12 : 8 : 1"
id="perspective4146-3-7-7" />
<inkscape:perspective
id="perspective4146-0-0"
inkscape:persp3d-origin="12 : 8 : 1"
inkscape:vp_z="24 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 12 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="24 : 12 : 1"
inkscape:persp3d-origin="12 : 8 : 1"
id="perspective4146-3-8-6" />
<inkscape:perspective
id="perspective4146-3-81"
inkscape:persp3d-origin="12 : 8 : 1"
inkscape:vp_z="24 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 12 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="24 : 12 : 1"
inkscape:persp3d-origin="12 : 8 : 1"
id="perspective4146-6" />
<inkscape:perspective
id="perspective4146-36-7-2"
inkscape:persp3d-origin="12 : 8 : 1"
inkscape:vp_z="24 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 12 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="24 : 12 : 1"
inkscape:persp3d-origin="12 : 8 : 1"
id="perspective4146-3-7-6-3" />
<inkscape:perspective
id="perspective4146-0-6-4"
inkscape:persp3d-origin="12 : 8 : 1"
inkscape:vp_z="24 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 12 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="24 : 12 : 1"
inkscape:persp3d-origin="12 : 8 : 1"
id="perspective4146-3-8-7-6" />
<inkscape:perspective
id="perspective4146-3-9-0"
inkscape:persp3d-origin="12 : 8 : 1"
inkscape:vp_z="24 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 12 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="24 : 12 : 1"
inkscape:persp3d-origin="12 : 8 : 1"
id="perspective4146-2-1" />
<inkscape:perspective
id="perspective4146-36-8"
inkscape:persp3d-origin="12 : 8 : 1"
inkscape:vp_z="24 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 12 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="24 : 12 : 1"
inkscape:persp3d-origin="12 : 8 : 1"
id="perspective4146-3-7-0" />
<inkscape:perspective
id="perspective4146-0-3"
inkscape:persp3d-origin="12 : 8 : 1"
inkscape:vp_z="24 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 12 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="24 : 12 : 1"
inkscape:persp3d-origin="12 : 8 : 1"
id="perspective4146-3-8-78" />
<inkscape:perspective
id="perspective4146-3-1"
inkscape:persp3d-origin="12 : 8 : 1"
inkscape:vp_z="24 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 12 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="24 : 12 : 1"
inkscape:persp3d-origin="12 : 8 : 1"
id="perspective4146-05" />
<inkscape:perspective
id="perspective4146-36-3"
inkscape:persp3d-origin="12 : 8 : 1"
inkscape:vp_z="24 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 12 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="24 : 12 : 1"
inkscape:persp3d-origin="12 : 8 : 1"
id="perspective4146-3-7-8" />
<inkscape:perspective
id="perspective4146-0-9"
inkscape:persp3d-origin="12 : 8 : 1"
inkscape:vp_z="24 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 12 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="24 : 12 : 1"
inkscape:persp3d-origin="12 : 8 : 1"
id="perspective4146-3-8-3" />
<inkscape:perspective
id="perspective4146-3-4"
inkscape:persp3d-origin="12 : 8 : 1"
inkscape:vp_z="24 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 12 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="24 : 12 : 1"
inkscape:persp3d-origin="12 : 8 : 1"
id="perspective4146-9" />
<inkscape:perspective
id="perspective4146-36-7-6"
inkscape:persp3d-origin="12 : 8 : 1"
inkscape:vp_z="24 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 12 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="24 : 12 : 1"
inkscape:persp3d-origin="12 : 8 : 1"
id="perspective4146-3-7-6-0" />
<inkscape:perspective
id="perspective4146-0-6-7"
inkscape:persp3d-origin="12 : 8 : 1"
inkscape:vp_z="24 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 12 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="24 : 12 : 1"
inkscape:persp3d-origin="12 : 8 : 1"
id="perspective4146-3-8-7-7" />
<inkscape:perspective
id="perspective4146-3-9-7"
inkscape:persp3d-origin="12 : 8 : 1"
inkscape:vp_z="24 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 12 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 12 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="24 : 12 : 1"
inkscape:persp3d-origin="12 : 8 : 1"
id="perspective4146-2-13" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="22.627416"
inkscape:cx="5.4926209"
inkscape:cy="10.264796"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="true"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:window-width="1366"
inkscape:window-height="709"
inkscape:window-x="-4"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:showpageshadow="false"
inkscape:object-nodes="true"
inkscape:snap-bbox="true">
<inkscape:grid
type="xygrid"
id="grid4132" />
<sodipodi:guide
position="4,18"
orientation="18,0"
id="guide4138" />
<sodipodi:guide
position="5,3"
orientation="0,18"
id="guide4140" />
<sodipodi:guide
position="20,2.0000174"
orientation="-18,0"
id="guide4142" />
<sodipodi:guide
position="2,21"
orientation="0,-18"
id="guide4144" />
<sodipodi:guide
position="3,19.000017"
orientation="16,0"
id="guide4146" />
<sodipodi:guide
position="2,4"
orientation="0,16"
id="guide4148" />
<sodipodi:guide
position="21,20"
orientation="-16,0"
id="guide4150" />
<sodipodi:guide
position="2,20"
orientation="0,-16"
id="guide4152" />
</sodipodi:namedview>
<metadata
id="metadata3874">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Capa 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-326,-532.3622)">
<g
transform="translate(327,507.3622)"
inkscape:label="Layer 1"
id="layer1-2">
<g
inkscape:label="Capa 1"
id="layer1-6"
transform="matrix(1,0,0,-1,5e-7,1080.3622)">
<path
inkscape:connector-curvature="0"
style="fill:#4d4d4d;fill-opacity:1;stroke:none"
d="M 3.7070312,7 3,7.7070312 l 6.125,6.1249998 1.875,1.875 1.875,-1.875 L 19,7.7070312 18.292969,7 12.167969,13.125 11,14.292969 9.8320312,13.125 3.7070312,7 Z"
transform="translate(-5e-7,1030.3622)"
id="rect4176" />
</g>
<rect
transform="scale(1,-1)"
y="-32"
x="3"
height="1"
width="16"
id="rect4186"
style="opacity:1;fill:#4d4d4d;fill-opacity:1;stroke:none" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -0,0 +1,148 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="24"
height="24"
id="svg3869"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="window-close.svg">
<defs
id="defs3871">
<linearGradient
id="linearGradient3257">
<stop
offset="0"
style="stop-color:#a50000;stop-opacity:1"
id="stop3259" />
<stop
offset="1"
style="stop-color:#e73800;stop-opacity:1"
id="stop3261" />
</linearGradient>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath4210">
<rect
y="1024.3622"
x="-7"
height="34"
width="34"
id="rect4212"
style="opacity:1;fill:#0000ff;fill-opacity:0.51376145;stroke:none;stroke-opacity:1" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath4160">
<rect
style="opacity:1;fill:#aade87;fill-opacity:0.47247709;stroke:none;stroke-opacity:1"
id="rect4162"
width="32"
height="32.000015"
x="-6"
y="1028.3619" />
</clipPath>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="22.627416"
inkscape:cx="5.4926209"
inkscape:cy="10.264796"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="true"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:window-width="1366"
inkscape:window-height="709"
inkscape:window-x="-4"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:showpageshadow="false"
inkscape:object-nodes="true"
inkscape:snap-bbox="true">
<inkscape:grid
type="xygrid"
id="grid4132" />
<sodipodi:guide
position="4,18"
orientation="18,0"
id="guide4138" />
<sodipodi:guide
position="5,3"
orientation="0,18"
id="guide4140" />
<sodipodi:guide
position="20,2.0000174"
orientation="-18,0"
id="guide4142" />
<sodipodi:guide
position="2,21"
orientation="0,-18"
id="guide4144" />
<sodipodi:guide
position="3,19.000017"
orientation="16,0"
id="guide4146" />
<sodipodi:guide
position="2,4"
orientation="0,16"
id="guide4148" />
<sodipodi:guide
position="21,20"
orientation="-16,0"
id="guide4150" />
<sodipodi:guide
position="2,20"
orientation="0,-16"
id="guide4152" />
</sodipodi:namedview>
<metadata
id="metadata3874">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Capa 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-326,-532.3622)">
<g
transform="translate(-94.71429,7.57146)"
id="layer1-7"
inkscape:label="Capa 1">
<circle
id="circle16"
cx="421.71429"
cy="-504.57132"
r="0" />
<path
id="path4144"
d="m 432.71429,528.79074 a 7.9999995,8.0000081 0 0 0 -8,8 7.9999995,8.0000081 0 0 0 8,8 7.9999995,8.0000081 0 0 0 8,-8 7.9999995,8.0000081 0 0 0 -8,-8 z m -3.29297,4 3.29297,3.29297 3.29297,-3.29297 0.70703,0.70703 -3.29297,3.29297 3.29297,3.29297 -0.70703,0.70703 -3.29297,-3.29297 -3.29297,3.29297 -0.70703,-0.70703 3.29297,-3.29297 -3.29297,-3.29297 0.70703,-0.70703 z"
style="opacity:1;fill:#da4453;fill-opacity:1"
inkscape:connector-curvature="0" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -0,0 +1,6 @@
INCLUDEPATH += $$PWD/
HEADERS += \
$$PWD/markdownhighlighter.h \
$$PWD/qmarkdowntextedit.h \
$$PWD/qplaintexteditsearchwidget.h

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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 <QPlainTextEdit>
#include <QEvent>
#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<QString, QString> 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);
};

View File

@ -0,0 +1,4 @@
INCLUDEPATH += $$PWD/
include($$PWD/qmarkdowntextedit-headers.pri)
include($$PWD/qmarkdowntextedit-sources.pri)

View File

@ -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)

View File

@ -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 <QEvent>
#include <QKeyEvent>
#include <QDebug>
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<QKeyEvent *>(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<QTextDocument::FindFlag> 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;
}

View File

@ -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 <QWidget>
#include <QPlainTextEdit>
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);
};

View File

@ -0,0 +1,253 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>QPlainTextEditSearchWidget</class>
<widget class="QWidget" name="QPlainTextEditSearchWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>836</width>
<height>142</height>
</rect>
</property>
<property name="autoFillBackground">
<bool>true</bool>
</property>
<layout class="QGridLayout" name="gridLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item row="0" column="6">
<widget class="QPushButton" name="replaceToggleButton">
<property name="toolTip">
<string>replace text</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset theme="edit-find-replace" resource="media.qrc">
<normaloff>:/media/edit-find-replace.svg</normaloff>:/media/edit-find-replace.svg</iconset>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="searchLabel">
<property name="text">
<string>Find:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QPushButton" name="closeButton">
<property name="toolTip">
<string>close search</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset theme="window-close" resource="media.qrc">
<normaloff>:/media/window-close.svg</normaloff>:/media/window-close.svg</iconset>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="QLineEdit" name="searchLineEdit">
<property name="placeholderText">
<string>find in text</string>
</property>
</widget>
</item>
<item row="0" column="4">
<widget class="QPushButton" name="searchDownButton">
<property name="toolTip">
<string>search forward</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset theme="go-bottom" resource="media.qrc">
<normaloff>:/media/go-bottom.svg</normaloff>:/media/go-bottom.svg</iconset>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="5">
<widget class="QPushButton" name="searchUpButton">
<property name="toolTip">
<string>search backward</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset theme="go-top" resource="media.qrc">
<normaloff>:/media/go-top.svg</normaloff>:/media/go-top.svg</iconset>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="3">
<widget class="QLineEdit" name="replaceLineEdit">
<property name="placeholderText">
<string>replace with</string>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QLabel" name="replaceLabel">
<property name="text">
<string>Replace:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="2" column="3">
<widget class="QFrame" name="buttonFrame">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>9</number>
</property>
<item>
<widget class="QComboBox" name="modeComboBox">
<item>
<property name="text">
<string>Plain text</string>
</property>
</item>
<item>
<property name="text">
<string>Whole words</string>
</property>
</item>
<item>
<property name="text">
<string>Regular expression</string>
</property>
</item>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="replaceButton">
<property name="text">
<string>Replace</string>
</property>
<property name="flat">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="replaceAllButton">
<property name="text">
<string>Replace All</string>
</property>
<property name="flat">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="QLabel" name="modeLabel">
<property name="text">
<string>Mode:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="1" column="4">
<widget class="QPushButton" name="matchCaseSensitiveButton">
<property name="toolTip">
<string>Match case sensitive</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset theme="format-text-superscript" resource="media.qrc">
<normaloff>:/media/format-text-superscript.svg</normaloff>:/media/format-text-superscript.svg</iconset>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
<tabstops>
<tabstop>searchLineEdit</tabstop>
<tabstop>replaceLineEdit</tabstop>
<tabstop>replaceButton</tabstop>
<tabstop>replaceAllButton</tabstop>
<tabstop>searchDownButton</tabstop>
<tabstop>searchUpButton</tabstop>
<tabstop>replaceToggleButton</tabstop>
<tabstop>closeButton</tabstop>
</tabstops>
<resources>
<include location="media.qrc"/>
</resources>
<connections/>
</ui>

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

View File

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.0" language="de_DE">
<context>
<name>QPlainTextEditSearchWidget</name>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="62"/>
<source>close search</source>
<translation>Suche schließen</translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="52"/>
<source>Find:</source>
<translation>Finden:</translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="32"/>
<source>replace text</source>
<translation>Text ersetzen</translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="79"/>
<source>find in text</source>
<translation>im Text finden</translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="86"/>
<source>search forward</source>
<translation>vorwärts suchen</translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="103"/>
<source>search backward</source>
<translation>rückwärts suchen</translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="120"/>
<source>replace with</source>
<translation>ersetzen mit</translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="127"/>
<source>Replace:</source>
<translation>Ersetzen:</translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="168"/>
<source>Replace</source>
<translation>Ersetzen</translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="178"/>
<source>Replace All</source>
<translation>Alle ersetzen</translation>
</message>
</context>
</TS>