Compare commits

...

2 Commits

55 changed files with 13125 additions and 2138 deletions

View File

@ -39,7 +39,7 @@
#define APP_VERSION_MAJOR 0
#define APP_VERSION_MINOR 9
#define APP_VERSION_SUFFIX 5
#define APP_VERSION_SUFFIX 8
//#ifdef TARGET_OSX
#define ICONS ":/assets/images"

View File

@ -187,5 +187,5 @@ void IV::Generate(QByteArray &buffer)
{
uint8_t* data = (uint8_t*)buffer.data();
for (int i=0; i<buffer.length(); i++)
data[i] = qrand() % 256;
data[i] = rand() % 256;
}

View File

@ -143,7 +143,7 @@ void TaskSearch::run()
if (mRequest.startsWith("\"") && mRequest.endsWith("\""))
sl.append(mRequest);
else
sl = mRequest.split(" ", QString::SkipEmptyParts);
sl = mRequest.split(" ", Qt::SkipEmptyParts);
if (!sl.empty())
{

View File

@ -9,6 +9,8 @@
#include <QKeyEvent>
#include <QTextStream>
#include <QApplication>
#include <QStandardPaths>
#include <QFile>
#include <sstream>
@ -208,22 +210,14 @@ time_t chrono::strToTime(const std::string& s)
QString path::pathToSettings()
{
#if QT_VERSION >= 0x050000
QString folder = QStandardPaths::writableLocation(QStandardPaths::DataLocation);
#else
QString folder = QDesktopServices::storageLocation(QDesktopServices::DataLocation);
#endif
QString folder = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
QString path = folder + "/" + SETTINGS_FILENAME;
return path;
}
QString path::pathToDatabase()
{
#if QT_VERSION >= 0x050000
QString folder = QStandardPaths::writableLocation(QStandardPaths::DataLocation);
#else
QString folder = QDesktopServices::storageLocation(QDesktopServices::DataLocation);
#endif
QString folder = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
QString path = folder + "/" + DATABASENAME;
return path;
}
@ -255,11 +249,7 @@ QString path::pathToDatabaseTemplate()
QString path::pathToLog()
{
#if QT_VERSION >= 0x050000
QString folder = QStandardPaths::writableLocation(QStandardPaths::DataLocation);
#else
QString folder = QDesktopServices::storageLocation(QDesktopServices::DataLocation);
#endif
QString folder = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
return folder + "/" + LOGNAME;
}

View File

@ -10,9 +10,8 @@
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
helper::theme::applyCurrent(Settings::instance());
app.setApplicationName(APPNAME);
helper::theme::applyCurrent(Settings::instance());
auto& settings = Settings::instance();
QFont f;
if (settings.data().count(KEY_APP_FONT) > 0)

View File

@ -37,7 +37,7 @@
# include "platforms/linux/autostart.h"
#endif
#include <QDesktopWidget>
// #include <QDesktopWidget>
#include <QDebug>
#include <iostream>
@ -121,7 +121,7 @@ void MainWindow::connectUiToDatabase()
// Load recent tasks
QString recent = SETTINGS.data()[KEY_RECENT_TASKS].toString();
QStringList recentList = recent.split(";", QString::SkipEmptyParts);
QStringList recentList = recent.split(";", Qt::SkipEmptyParts);
for (QString& s: recentList)
{
Task::Id id = s.toULongLong();
@ -1016,7 +1016,7 @@ void MainWindow::updateData()
if (saveToDb)
mLogger->log("Flushing timeline to DB start");
mCurrentTask->timeline()->flush(saveToDb, QDateTime::currentDateTimeUtc().toTime_t());
mCurrentTask->timeline()->flush(saveToDb, QDateTime::currentDateTimeUtc().toSecsSinceEpoch());
if (saveToDb)
{
mLastTimelineFlush = QDateTime::currentDateTimeUtc();
@ -1191,9 +1191,9 @@ int MainWindow::showTrayWindow(QDialog* dlg)
int w = dlg->geometry().width();
int h = dlg->geometry().height();
QRect rec = QApplication::desktop()->screenGeometry();
int desktopHeight = rec.height();
int desktopWidth = rec.width();
auto screen_size = QGuiApplication::primaryScreen()->size();
int desktopHeight = screen_size.height();
int desktopWidth = screen_size.width();
QRect iconRect;
if (mTrayIcon)
@ -1412,7 +1412,7 @@ void MainWindow::checkForUpdates()
void MainWindow::systemSleep()
{
//qDebug() << "System goes to sleep";
stopTracking(TSR_Automatic, QDateTime::currentDateTimeUtc().toTime_t());
stopTracking(TSR_Automatic, QDateTime::currentDateTimeUtc().toSecsSinceEpoch());
}
void MainWindow::systemResume()
@ -1484,11 +1484,11 @@ void MainWindow::findRequested()
}
//ui->mFindEdit->setVisible(false);
QTextCursor c = ui->mNoteEdit->document()->find(pattern, mFindStartIndex, nullptr);
QTextCursor c = ui->mNoteEdit->document()->find(pattern, mFindStartIndex);
if (c.isNull())
{
mFindStartIndex = 0;
c = ui->mNoteEdit->document()->find(pattern, mFindStartIndex, nullptr);
c = ui->mNoteEdit->document()->find(pattern, mFindStartIndex);
}
if (!c.isNull())
{
@ -1556,7 +1556,13 @@ void MainWindow::showMainWindow()
void MainWindow::setupAppMenu()
{
#if defined(TARGET_LINUX)
appmenu::install(QCoreApplication::applicationFilePath().toStdString());
auto exe_path = QFileInfo(QCoreApplication::arguments().front()).absoluteFilePath();
const char* appimage = std::getenv("APPIMAGE");
if (appimage != nullptr)
exe_path = appimage;
qDebug() << "Found exe path: " << exe_path;
appmenu::install(exe_path.toStdString());
#endif
}
@ -1569,7 +1575,7 @@ void MainWindow::continueOnIdle()
void MainWindow::breakOnIdle(const QDateTime& stopTime)
{
// Stop tracking
stopTracking(TSR_Manual, stopTime.toUTC().toTime_t());
stopTracking(TSR_Manual, stopTime.toUTC().toSecsSinceEpoch());
showTimeForSelectedTask();
}

View File

@ -98,8 +98,21 @@ static fs::path appmenu_install_dir()
void appmenu::install(const std::string& path_to_me)
{
auto path_to_desktop = appmenu_install_dir() / NOO_DESKTOP_NAME;
// Check if app is installed already.
// The code below checks for path to app; as this app is distributed as .AppImage with version numbers - every new version will trigger desktop file rewriting
std::ifstream ifs(path_to_desktop);
std::string content((std::istreambuf_iterator<char>(ifs)), std::istreambuf_iterator<char>());
if (content.find(path_to_me) != std::string::npos)
return;
// Remove old one
std::string uninstall_cmd = "/usr/bin/xdg-desktop-menu uninstall --novendor " + (appmenu_install_dir() / NOO_DESKTOP_NAME).string();
system(uninstall_cmd.c_str());
// Put .desktop file to ~/.config/autostart
std::ofstream ofs(appmenu_install_dir() / NOO_DESKTOP_NAME);
std::ofstream ofs(path_to_desktop);
if (ofs.is_open())
{
ofs << fixup_desktop_file(read_desktop_file(), path_to_me);
@ -126,6 +139,9 @@ void appmenu::install(const std::string& path_to_me)
}
}
}
std::string install_cmd = "/usr/bin/xdg-desktop-menu install --novendor " + (appmenu_install_dir() / NOO_DESKTOP_NAME).string();
system(install_cmd.c_str());
}
void appmenu::uninstall()

View File

@ -1,49 +1,106 @@
cmake_minimum_required(VERSION 3.3)
project(qmarkdowntextedit)
cmake_minimum_required(VERSION 3.16) # Qt requires CMake 3.16
project(qmarkdowntextedit LANGUAGES CXX VERSION 1.0.0)
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
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 )
# add option to disable test executable
option(QMARKDOWNTEXTEDIT_EXE "Build test executable" ON)
qt5_wrap_ui(ui_qplaintexteditsearchwidget.h qplaintexteditsearchwidget.ui)
# find qt
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets)
find_package(Qt${QT_VERSION_MAJOR} OPTIONAL_COMPONENTS Quick)
set(RESOURCE_FILES
media.qrc
)
# needed for windows
if(WIN32)
set(INTL_LDFLAGS -lintl)
endif(WIN32)
qt5_add_resources(RESOURCE_ADDED ${RESOURCE_FILES})
# QMarkdownTextEdit library
set(RC_FILES
media.qrc
)
set(SOURCE_FILES
# Translations arent loaded so don't include them
set(TS_FILES
trans/qmarkdowntextedit_de.ts
trans/qmarkdowntextedit_ur.ts
trans/qmarkdowntextedit_zh_CN.ts
)
set(QMARKDOWNTEXTEDIT_SOURCES
${RC_FILES}
linenumberarea.h # We need to keep this here, otherwise the build fails
markdownhighlighter.cpp
markdownhighlighter.h
main.cpp
mainwindow.cpp
mainwindow.h
mainwindow.ui
qmarkdowntextedit.cpp
qmarkdowntextedit.h
qplaintexteditsearchwidget.ui
qownlanguagedata.cpp
qownlanguagedata.h
qplaintexteditsearchwidget.cpp
qplaintexteditsearchwidget.ui
)
set(QMARKDOWNTEXTEDIT_HEADERS
markdownhighlighter.h
qmarkdowntextedit.h
qplaintexteditsearchwidget.h
)
add_library(qmarkdowntextedit ${QMARKDOWNTEXTEDIT_SOURCES})
set_target_properties(qmarkdowntextedit PROPERTIES
PUBLIC_HEADER "${QMARKDOWNTEXTEDIT_HEADERS}"
)
target_link_libraries(qmarkdowntextedit PUBLIC
Qt${QT_VERSION_MAJOR}::Widgets
${INTL_LDFLAGS}
)
if (Qt${QT_VERSION_MAJOR}Quick_FOUND)
target_link_libraries(qmarkdowntextedit PUBLIC Qt${QT_VERSION_MAJOR}::Quick)
add_executable(QtQuickExample examples/qml/example.cpp examples/qml/ressources.qrc)
target_link_libraries(QtQuickExample PRIVATE Qt${QT_VERSION_MAJOR}::Quick qmarkdowntextedit)
endif()
# QMarkdownTextEdit executable
if(QMARKDOWNTEXTEDIT_EXE)
set(SOURCE_FILES
main.cpp
mainwindow.cpp
mainwindow.h
mainwindow.ui
)
add_executable(qmarkdowntextedit ${SOURCE_FILES} ${RESOURCE_ADDED})
add_executable(qmarkdowntextedit-exe ${SOURCE_FILES})
set_target_properties(qmarkdowntextedit-exe PROPERTIES OUTPUT_NAME "qmarkdowntextedit")
target_link_libraries(qmarkdowntextedit-exe PRIVATE
Qt${QT_VERSION_MAJOR}::Widgets
${INTL_LDFLAGS}
qmarkdowntextedit
)
endif()
include_directories(${Qt5Widgets_INCLUDES})
include(GNUInstallDirs) # Doesn't fail on windows
# We need add -DQT_WIDGETS_LIB when using QtWidgets in Qt 5.
add_definitions(${Qt5Widgets_DEFINITIONS})
# Install the lib
install(TARGETS qmarkdowntextedit
ARCHIVE DESTINATION lib
LIBRARY DESTINATION lib
RUNTIME DESTINATION bin
PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)
# 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}")
# Add PkgConfig config file
configure_file(qmarkdowntextedit.pc.in ${CMAKE_BINARY_DIR}/qmarkdowntextedit.pc @ONLY)
install(FILES ${CMAKE_BINARY_DIR}/qmarkdowntextedit.pc DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/pkgconfig)
# The Qt5Widgets_LIBRARIES variable also includes QtGui and QtCore
target_link_libraries(qmarkdowntextedit Qt5::Widgets)
# Install exe
if(QMARKDOWNTEXTEDIT_EXE)
install(TARGETS qmarkdowntextedit-exe DESTINATION bin)
endif()

View File

@ -1,5 +1,5 @@
The MIT License (MIT)
Copyright (c) 2014-2019 Patrizio Bekerle
Copyright (c) 2014-2025 Patrizio Bekerle -- <patrizio@bekerle.com>
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:

View File

@ -1,33 +1,83 @@
# [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 GitHub Actions](https://github.com/pbek/qmarkdowntextedit/workflows/Build/badge.svg?branch=main)](https://github.com/pbek/qmarkdowntextedit/actions)
[![Build Status Linux/OS X](https://travis-ci.org/pbek/qmarkdowntextedit.svg?branch=main)](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.
QMarkdownTextEdit is a C++ Qt [QPlainTextEdit](http://doc.qt.io/qt-5/qplaintextedit.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...
## Widget Features
- Markdown highlighting
- Code syntax 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
- Line numbers (Qt >= 5.5)
- Very fast
- And much more...
## Supported Markdown Features
Commonmark compliance is enforced where possible however we are not fully Commonmark compliant yet. Following is a list of features/extensions supported by the highlighter. Please note that this is just a plaintext editor and as such, it only does the highlighting and not rendering of the markdown to HTML.
| Feature | Availablity |
| --------------------------------------------------------------------------------------- | ----------------------------------------------------- |
| Bolds and Italics | Yes |
| Lists (Unordered/Orderered) | Yes |
| Links and Images<br/>(Inline/Reference/Autolinks/E-mail) | Yes (Cannot handle nested links or complex cases yet) |
| Heading (ATX and Setext) | Yes |
| Codeblocks (indented and fenced)<br/> Both backtick and tilde code fences are supported | Yes (Only fenced code block has syntax highlighting) |
| Inline code | Yes |
| Strikethrough | Yes |
| Underline | Yes (Optional) |
| Blockquotes | Yes |
| Table | Yes |
## Screenshot
![Screenhot](screenshot.png)
## How to use this widget
- include [qmarkdowntextedit.pri](https://github.com/pbek/qmarkdowntextedit/blob/develop/qmarkdowntextedit.pri)
## Usage
There are multiple ways to use this. You can use the editor directly, or you can subclass it or you can just use the highlighter.
### Using the editor
#### QMake
- Include [qmarkdowntextedit.pri](https://github.com/pbek/qmarkdowntextedit/blob/main/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)
#### CMake
- Include [CMakeLists.txt](https://github.com/pbek/qmarkdowntextedit/blob/main/CMakeLists.txt)
to your project like this `add_subdirectory(qmarkdowntextedit)`
- add a normal `QPlainTextEdit` to your UI and promote it to `QMarkdownTextEdit` (base class `QPlainTextEdit`)
### Using the highlighter only
Highlighter can work with both `QPlainTextEdit` and `QTextEdit`. Example:
```cpp
auto doc = ui->plainTextEdit->document();
auto *highlighter = new MarkdownHighlighter(doc);
```
## Projects using QMarkdownTextEdit
- [QOwnNotes](https://github.com/pbek/QOwnNotes)
- [Notes](https://github.com/nuttyartist/notes)
- [CuteMarkEd-NG](https://github.com/Waqar144/CuteMarkEd-NG)
## 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.
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,24 @@
#include <QApplication>
#include <QQmlApplicationEngine>
#include "markdownhighlighter.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
qmlRegisterType<MarkdownHighlighter>("MarkdownHighlighter", 1, 0,
"MarkdownHighlighter");
QQmlApplicationEngine engine;
const QUrl url(QStringLiteral("qrc:/example.qml"));
QObject::connect(
&engine, &QQmlApplicationEngine::objectCreated, &app,
[url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl) QCoreApplication::exit(-1);
},
Qt::QueuedConnection);
engine.load(url);
return app.exec();
}

View File

@ -0,0 +1,22 @@
import QtQuick 2.0
import QtQuick.Window 2.0
import MarkdownHighlighter 1.0
Window {
id: mainwindow
width: 640
height: 400
visible: true
title: qsTr("QtQuick Project")
TextEdit {
id: editor
text: "# Hello world!"
focus: true
}
MarkdownHighlighter {
id: syntaxHighlighter
textDocument: editor.textDocument
}
}

View File

@ -0,0 +1,5 @@
<RCC>
<qresource prefix="/">
<file>example.qml</file>
</qresource>
</RCC>

View File

@ -0,0 +1,22 @@
# Use `just <recipe>` to run a recipe
# https://just.systems/man/en/
# By default, run the `--list` command
default:
@just --list
# Aliases
alias fmt := format
# Format all files
[group('linter')]
format:
nix-shell -p treefmt libclang nodePackages.prettier shfmt nixfmt-rfc-style taplo --run treefmt
# Add git commit hashes to the .git-blame-ignore-revs file
[group('linter')]
add-git-blame-ignore-revs:
git log --pretty=format:"%H" --grep="^lint" >> .git-blame-ignore-revs
sort .git-blame-ignore-revs | uniq > .git-blame-ignore-revs.tmp
mv .git-blame-ignore-revs.tmp .git-blame-ignore-revs

View File

@ -0,0 +1,112 @@
#ifndef LINENUMBERAREA_H
#define LINENUMBERAREA_H
#include <QDebug>
#include <QPainter>
#include <QScrollBar>
#include <QWidget>
#include "qmarkdowntextedit.h"
class LineNumArea final : public QWidget {
Q_OBJECT
public:
explicit LineNumArea(QMarkdownTextEdit *parent)
: QWidget(parent), textEdit(parent) {
Q_ASSERT(parent);
_currentLineColor = QColor(QStringLiteral("#eef067"));
_otherLinesColor = QColor(QStringLiteral("#a6a6a6"));
setHidden(true);
// We always use fixed font to avoid "width" issues
setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont));
}
void setCurrentLineColor(QColor color) { _currentLineColor = color; }
void setOtherLineColor(QColor color) {
_otherLinesColor = std::move(color);
}
int lineNumAreaWidth() const {
if (!enabled) {
return 0;
}
int digits = 2;
int max = std::max(1, textEdit->blockCount());
while (max >= 10) {
max /= 10;
++digits;
}
#if QT_VERSION >= 0x050B00
int space =
13 + textEdit->fontMetrics().horizontalAdvance(u'9') * digits;
#else
int space =
13 + textEdit->fontMetrics().width(QLatin1Char('9')) * digits;
#endif
return space;
}
bool isLineNumAreaEnabled() const { return enabled; }
void setLineNumAreaEnabled(bool e) {
enabled = e;
setHidden(!e);
}
QSize sizeHint() const override { return {lineNumAreaWidth(), 0}; }
protected:
void paintEvent(QPaintEvent *event) override {
QPainter painter(this);
painter.fillRect(event->rect(),
palette().color(QPalette::Active, QPalette::Window));
auto block = textEdit->firstVisibleBlock();
int blockNumber = block.blockNumber();
qreal top = textEdit->blockBoundingGeometry(block)
.translated(textEdit->contentOffset())
.top();
// Maybe the top is not 0?
top += textEdit->viewportMargins().top();
qreal bottom = top;
const QPen currentLine = _currentLineColor;
const QPen otherLines = _otherLinesColor;
painter.setFont(font());
while (block.isValid() && top <= event->rect().bottom()) {
top = bottom;
bottom = top + textEdit->blockBoundingRect(block).height();
if (block.isVisible() && bottom >= event->rect().top()) {
QString number = QString::number(blockNumber + 1);
auto isCurrentLine =
textEdit->textCursor().blockNumber() == blockNumber;
painter.setPen(isCurrentLine ? currentLine : otherLines);
painter.drawText(-5, top, sizeHint().width(),
textEdit->fontMetrics().height(),
Qt::AlignRight, number);
}
block = block.next();
++blockNumber;
}
}
private:
bool enabled = false;
QMarkdownTextEdit *textEdit;
QColor _currentLineColor;
QColor _otherLinesColor;
};
#endif // LINENUMBERAREA_H

View File

@ -1,41 +1,35 @@
/*
* Copyright (c) 2014-2019 Patrizio Bekerle -- http://www.bekerle.com
* MIT License
*
* 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.
* Copyright (c) 2014-2025 Patrizio Bekerle -- <patrizio@bekerle.com>
*
* 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.
* 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.
*/
#include "mainwindow.h"
#include <QApplication>
#include <QFileInfo>
#include <QDebug>
int main(int argc, char *argv[])
{
#include "mainwindow.h"
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

@ -1,14 +1,25 @@
/*
* Copyright (c) 2014-2019 Patrizio Bekerle -- http://www.bekerle.com
* MIT License
*
* 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.
* Copyright (c) 2014-2025 Patrizio Bekerle -- <patrizio@bekerle.com>
*
* 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.
* 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.
*
* mainwindow.cpp
*
@ -16,103 +27,12 @@
*/
#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)
{
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();
}
MainWindow::~MainWindow() { delete ui; }

View File

@ -1,15 +1,25 @@
/*
* Copyright (c) 2014-2019 Patrizio Bekerle -- http://www.bekerle.com
* MIT License
*
* 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.
* Copyright (c) 2014-2025 Patrizio Bekerle -- <patrizio@bekerle.com>
*
* 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.
* 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.
*/
#pragma once
@ -20,25 +30,13 @@ namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
class MainWindow : public QMainWindow {
Q_OBJECT
public:
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:
private:
Ui::MainWindow *ui;
QString m_loadedContent;
QString m_filename;
};

View File

@ -26,7 +26,9 @@
## Features
- markdown highlighting
- syntax highlighting
- clickable links with `Ctrl + Click`
- ~strikedout~ text and `inline code;`
- block indent with `Tab` and `Shift + Tab`
- duplicate text with `Ctrl + Alt + Down`
- searching of text with `Ctrl + F`
@ -36,7 +38,7 @@
## References
- [QOwnNotes - cross-platform open source plain-text notepad](http://www.qownnotes.org)
- [QOwnNotes - cross-platform open source plain-text file markdown note taking](https://www.qownnotes.org)
## Disclaimer
@ -55,7 +57,7 @@ There are inherent dangers in the use of any software, and you are solely respon
<x>0</x>
<y>0</y>
<width>1070</width>
<height>25</height>
<height>23</height>
</rect>
</property>
</widget>

File diff suppressed because it is too large Load Diff

View File

@ -1,52 +1,127 @@
/*
* Copyright (c) 2014-2019 Patrizio Bekerle -- http://www.bekerle.com
* MIT License
*
* 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.
* Copyright (c) 2014-2025 Patrizio Bekerle -- <patrizio@bekerle.com>
*
* 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.
* 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:
*
* QPlainTextEdit markdown highlighter
* 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.
*
* QPlainTextEdit Markdown highlighter
*/
#pragma once
#include <QTextCharFormat>
#include <QSyntaxHighlighter>
#include <QRegularExpression>
#include <QSyntaxHighlighter>
#include <QTextCharFormat>
#ifdef QT_QUICK_LIB
#include <QQuickTextDocument>
#endif
QT_BEGIN_NAMESPACE
class QTextDocument;
QT_END_NAMESPACE
class MarkdownHighlighter : public QSyntaxHighlighter
{
Q_OBJECT
class MarkdownHighlighter : public QSyntaxHighlighter {
Q_OBJECT
public:
enum HighlightingOption{
#ifdef QT_QUICK_LIB
Q_PROPERTY(QQuickTextDocument *textDocument READ textDocument WRITE
setTextDocument NOTIFY textDocumentChanged)
QQuickTextDocument *m_quickDocument = nullptr;
signals:
void textDocumentChanged();
public:
inline QQuickTextDocument *textDocument() const { return m_quickDocument; };
void setTextDocument(QQuickTextDocument *textDocument) {
if (!textDocument) return;
m_quickDocument = textDocument;
setDocument(m_quickDocument->textDocument());
Q_EMIT textDocumentChanged();
};
#endif
public:
enum HighlightingOption {
None = 0,
FullyHighlightedBlockQuote = 0x01
FullyHighlightedBlockQuote = 0x01,
Underline = 0x02
};
Q_DECLARE_FLAGS(HighlightingOptions, HighlightingOption)
MarkdownHighlighter(QTextDocument *parent = nullptr,
HighlightingOptions highlightingOptions =
HighlightingOption::None);
MarkdownHighlighter(
QTextDocument *parent = nullptr,
HighlightingOptions highlightingOptions = HighlightingOption::None);
// we use some predefined numbers here to be compatible with
// the peg-markdown parser
static inline QColor codeBlockBackgroundColor() {
const QBrush brush = _formats[CodeBlock].background();
if (!brush.isOpaque()) {
return QColor(Qt::transparent);
}
return brush.color();
}
static constexpr inline bool isOctal(const char c) {
return (c >= '0' && c <= '7');
}
static constexpr inline bool isHex(const char c) {
return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') ||
(c >= 'A' && c <= 'F');
}
static constexpr inline bool isCodeBlock(const int state) {
return state == MarkdownHighlighter::CodeBlock ||
state == MarkdownHighlighter::CodeBlockTilde ||
state == MarkdownHighlighter::CodeBlockComment ||
state == MarkdownHighlighter::CodeBlockTildeComment ||
state >= MarkdownHighlighter::CodeCpp;
}
static constexpr inline bool isCodeBlockEnd(const int state) {
return state == MarkdownHighlighter::CodeBlockEnd ||
state == MarkdownHighlighter::CodeBlockTildeEnd;
}
static constexpr inline bool isHeading(const int state) {
return state >= H1 && state <= H6;
}
enum class RangeType { CodeSpan, Emphasis, Link };
QPair<int, int> findPositionInRanges(MarkdownHighlighter::RangeType type,
int blockNum, int pos) const;
bool isPosInACodeSpan(int blockNumber, int position) const;
bool isPosInALink(int blockNumber, int position) const;
QPair<int, int> getSpanRange(RangeType rangeType, int blockNumber,
int position) const;
// we used some predefined numbers here to be compatible with
// the peg-Markdown parser
enum HighlighterState {
NoState = -1,
Link = 0,
Image = 3,
CodeBlock,
CodeBlockComment,
Italic = 7,
Bold,
List,
@ -64,74 +139,222 @@ public:
MaskedSyntax,
CurrentLineBackgroundColor,
BrokenLink,
FrontmatterBlock,
TrailingSpace,
CheckBoxUnChecked,
CheckBoxChecked,
StUnderline,
// code highlighting
CodeKeyWord = 1000,
CodeString = 1001,
CodeComment = 1002,
CodeType = 1003,
CodeOther = 1004,
CodeNumLiteral = 1005,
CodeBuiltIn = 1006,
// internal
CodeBlockIndented = 96,
CodeBlockTildeEnd = 97,
CodeBlockTilde = 98,
CodeBlockTildeComment,
CodeBlockEnd = 100,
HeadlineEnd
HeadlineEnd,
FrontmatterBlockEnd,
// languages
/*********
* When adding a language make sure that its value is a multiple of 2
* This is because we use the next number as comment for that language
* In case the language doesn't support multiline comments in the
* traditional C++ sense, leave the next value empty. Otherwise mark the
* next value as comment for that language. e.g CodeCpp = 200
* CodeCppComment = 201
*/
CodeCpp = 200,
CodeCppComment = 201,
CodeJs = 202,
CodeJsComment = 203,
CodeC = 204,
CodeCComment = 205,
CodeBash = 206,
CodePHP = 208,
CodePHPComment = 209,
CodeQML = 210,
CodeQMLComment = 211,
CodePython = 212,
CodeRust = 214,
CodeRustComment = 215,
CodeJava = 216,
CodeJavaComment = 217,
CodeCSharp = 218,
CodeCSharpComment = 219,
CodeGo = 220,
CodeGoComment = 221,
CodeV = 222,
CodeVComment = 223,
CodeSQL = 224,
CodeSQLComment = 225,
CodeJSON = 226,
CodeXML = 228,
CodeCSS = 230,
CodeCSSComment = 231,
CodeTypeScript = 232,
CodeTypeScriptComment = 233,
CodeYAML = 234,
CodeINI = 236,
CodeTaggerScript = 238,
CodeVex = 240,
CodeVexComment = 241,
CodeCMake = 242,
CodeMake = 244,
CodeNix = 246,
CodeForth = 248,
CodeForthComment = 249,
CodeSystemVerilog = 250,
CodeSystemVerilogComment = 251,
CodeGDScript = 252,
CodeTOML = 254,
CodeTOMLString = 255
};
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);
static void setTextFormats(
QHash<HighlighterState, QTextCharFormat> formats);
static void setTextFormat(HighlighterState state, QTextCharFormat format);
void clearDirtyBlocks();
void setHighlightingOptions(HighlightingOptions options);
void setHighlightingOptions(const HighlightingOptions options);
void initHighlightingRules();
signals:
Q_SIGNALS:
void highlightingFinished();
protected slots:
protected Q_SLOTS:
void timerTick();
protected:
protected:
struct HighlightingRule {
HighlightingRule(const HighlighterState state_) : state(state_) {}
explicit HighlightingRule(const HighlighterState state_)
: state(state_) {}
HighlightingRule() = default;
QRegularExpression pattern;
QString shouldContain;
HighlighterState state = NoState;
int capturingGroup = 0;
int maskedGroup = 0;
bool useStateAsCurrentBlockState = false;
bool disableIfCurrentStateIsSet = false;
uint8_t capturingGroup = 0;
uint8_t maskedGroup = 0;
};
struct InlineRange {
int begin;
int end;
RangeType type;
InlineRange() = default;
InlineRange(int begin_, int end_, RangeType type_)
: begin{begin_}, end{end_}, type{type_} {}
};
void highlightBlock(const QString &text) Q_DECL_OVERRIDE;
void highlightBlock(const QString &text) override;
void initTextFormats(int defaultFontSize = 12);
static void initTextFormats(int defaultFontSize = 12);
void highlightMarkdown(QString text);
static void initCodeLangs();
void highlightHeadline(QString text);
void highlightMarkdown(const QString &text);
void highlightAdditionalRules(QVector<HighlightingRule> &rules,
QString text);
void formatAndMaskRemaining(int formatBegin, int formatLength,
int beginningText, int endText,
const QTextCharFormat &format);
void highlightCodeBlock(QString text);
/******************************
* BLOCK LEVEL FUNCTIONS
******************************/
void highlightCommentBlock(QString text);
void highlightHeadline(const QString &text);
void addDirtyBlock(QTextBlock block);
void highlightSubHeadline(const QString &text, HighlighterState state);
void highlightAdditionalRules(const QVector<HighlightingRule> &rules,
const QString &text);
void highlightFrontmatterBlock(const QString &text);
void highlightCommentBlock(const QString &text);
void highlightThematicBreak(const QString &text);
void highlightLists(const QString &text);
void highlightCheckbox(const QString &text, int curPos);
/******************************
* INLINE FUNCTIONS
******************************/
void highlightInlineRules(const QString &text);
int highlightInlineSpans(const QString &text, int currentPos,
const QChar c);
void highlightEmAndStrong(const QString &text, const int pos);
Q_REQUIRED_RESULT int highlightInlineComment(const QString &text, int pos);
int highlightLinkOrImage(const QString &text, int startIndex);
void setHeadingStyles(MarkdownHighlighter::HighlighterState rule,
const QRegularExpressionMatch &match,
const int capturedGroup);
/******************************
* CODE HIGHLIGHTING FUNCTIONS
******************************/
void highlightIndentedCodeBlock(const QString &text);
void highlightCodeFence(const QString &text);
void highlightCodeBlock(const QString &text,
const QString &opener = QStringLiteral("```"));
void highlightSyntax(const QString &text);
Q_REQUIRED_RESULT int highlightNumericLiterals(const QString &text, int i);
Q_REQUIRED_RESULT int highlightStringLiterals(QChar strType,
const QString &text, int i);
void ymlHighlighter(const QString &text);
void iniHighlighter(const QString &text);
void cssHighlighter(const QString &text);
void xmlHighlighter(const QString &text);
void makeHighlighter(const QString &text);
void forthHighlighter(const QString &text);
void taggerScriptHighlighter(const QString &text);
void gdscriptHighlighter(const QString &text);
void sqlHighlighter(const QString &text);
void tomlHighlighter(const QString &text);
void addDirtyBlock(const QTextBlock &block);
void reHighlightDirtyBlocks();
QVector<HighlightingRule> _highlightingRulesPre;
QVector<HighlightingRule> _highlightingRulesAfter;
QVector<QTextBlock> _dirtyTextBlocks;
QHash<HighlighterState, QTextCharFormat> _formats;
QTimer *_timer;
bool _highlightingFinished;
HighlightingOptions _highlightingOptions;
QTimer *_timer;
QVector<QTextBlock> _dirtyTextBlocks;
QVector<QPair<int, int>> _linkRanges;
void setCurrentBlockMargin(HighlighterState state);
QHash<int, QVector<InlineRange>> _ranges;
static QVector<HighlightingRule> _highlightingRules;
static QHash<HighlighterState, QTextCharFormat> _formats;
static QHash<QString, HighlighterState> _langStringToEnum;
static constexpr int tildeOffset = 300;
};

View File

@ -1,15 +1 @@
<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>
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M10 4c-3.324 0-6 2.676-6 6s2.676 6 6 6a5.976 5.976 0 0 0 3.875-1.418L19.293 20l.707-.707-5.418-5.418A5.976 5.976 0 0 0 16 10c0-3.324-2.676-6-6-6zm0 1a4.99 4.99 0 0 1 5 5 4.99 4.99 0 0 1-5 5 4.99 4.99 0 0 1-5-5 4.99 4.99 0 0 1 5-5zM5 18a1 1 0 1 0 0 2 1 1 0 1 0 0-2zm4 0a1 1 0 1 0 0 2 1 1 0 1 0 0-2zm4 0a1 1 0 1 0 0 2 1 1 0 1 0 0-2z" fill="#4d4d4d"/></svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 423 B

View File

@ -1,16 +1 @@
<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>
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><a transform="translate(1 1)"><path d="M14 3v6h3a1.996 1.996 0 0 0 .828-3.816c.106-.206.172-.435.172-.684 0-.831-.669-1.5-1.5-1.5H15zm1 1h1.5a.5.5 0 1 1 0 1H15zM8.217 5 3 19h1.688l1.668-4.43h5.563L13.451 19h1.689L10.21 5zM15 6h2a1 1 0 1 1 0 2h-2zm-5.768.611 2.129 6.674H6.873z" fill="#4d4d4d"/></a></svg>

Before

Width:  |  Height:  |  Size: 998 B

After

Width:  |  Height:  |  Size: 364 B

View File

@ -1,156 +1 @@
<?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>
<svg fill="#4d4d4d" height="24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="M4.707 6 4 6.707l6.125 6.125L12 14.707l1.875-1.875L20 6.707 19.293 6l-6.125 6.125L12 13.293l-1.168-1.168zM4 17h16v1H4z"/></svg>

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 214 B

View File

@ -1,476 +1 @@
<?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>
<svg fill="#4d4d4d" height="24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="M4.707 18 4 17.293l6.125-6.125L12 9.293l1.875 1.875L20 17.293l-.707.707-6.125-6.125L12 10.707l-1.168 1.168zM4 7h16V6H4z"/></svg>

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 215 B

View File

@ -1,148 +1 @@
<?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>
<svg height="24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="M12 4a8 8 0 1 0 0 16 8 8 0 1 0 0-16zM8.707 8 12 11.293 15.293 8l.707.707L12.707 12 16 15.293l-.707.707L12 12.707 8.707 16 8 15.293 11.293 12 8 8.707z" fill="#da4453"/></svg>

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 245 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

View File

@ -0,0 +1,17 @@
TARGET = QMarkdownTextedit
TEMPLATE = app
QT += core gui widgets
CONFIG += c++11
SOURCES = main.cpp mainwindow.cpp
HEADERS = mainwindow.h
FORMS = mainwindow.ui
LIBS += -lQMarkdownTextedit -L$$OUT_PWD
win32: LIBS += -L$$OUT_PWD/release -L$$OUT_PWD/debug
target.path = $$[QT_INSTALL_BINS]
INSTALLS += target

View File

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

View File

@ -0,0 +1,34 @@
TARGET = QMarkdownTextedit
TEMPLATE = lib
QT += core gui widgets
CONFIG += c++11 create_prl no_install_prl create_pc
include(qmarkdowntextedit.pri)
TRANSLATIONS += trans/qmarkdowntextedit_de.ts \
trans/qmarkdowntextedit_zh_CN.ts \
trans/qmarkdowntextedit_es.ts
isEmpty(PREFIX):PREFIX=$$[QT_INSTALL_PREFIX]
isEmpty(LIBDIR):LIBDIR=$$[QT_INSTALL_LIBS]
isEmpty(HEADERDIR):HEADERDIR=$${PREFIX}/include/$$TARGET/
isEmpty(DSRDIR):DSRDIR=$${PREFIX}/share/$$TARGET
target.path = $${LIBDIR}
headers.files = $$HEADERS
headers.path = $${HEADERDIR}
license.files = LICENSE
license.path = $${DSRDIR}/licenses/
trans.files = trans/*.qm
trans.path = $${DSRDIR}/translations/
QMAKE_PKGCONFIG_NAME = QMarkdownTextedit
QMAKE_PKGCONFIG_DESCRIPTION = C++ Qt QPlainTextEdit widget with markdown highlighting and some other goodies
QMAKE_PKGCONFIG_INCDIR = $${headers.path}
QMAKE_PKGCONFIG_LIBDIR = $${LIBDIR}
QMAKE_PKGCONFIG_DESTDIR = pkgconfig
INSTALLS += target license headers trans

View File

@ -6,6 +6,7 @@ greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
SOURCES += \
$$PWD/markdownhighlighter.cpp \
$$PWD/qmarkdowntextedit.cpp \
$$PWD/qownlanguagedata.cpp \
$$PWD/qplaintexteditsearchwidget.cpp
RESOURCES += \

File diff suppressed because it is too large Load Diff

View File

@ -1,87 +1,144 @@
/*
* Copyright (c) 2014-2019 Patrizio Bekerle -- http://www.bekerle.com
* MIT License
*
* 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.
* Copyright (c) 2014-2025 Patrizio Bekerle -- <patrizio@bekerle.com>
*
* 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.
* 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.
*/
#pragma once
#include <QPlainTextEdit>
#include <QEvent>
#include <QPlainTextEdit>
#include "markdownhighlighter.h"
#include "qplaintexteditsearchwidget.h"
class LineNumArea;
class QMarkdownTextEdit : public QPlainTextEdit
{
class QMarkdownTextEdit : public QPlainTextEdit {
Q_OBJECT
Q_PROPERTY(
bool highlighting READ highlightingEnabled WRITE setHighlightingEnabled)
public:
friend class LineNumArea;
public:
enum AutoTextOption {
None = 0x0000,
// inserts closing characters for brackets and markdown characters
// inserts closing characters for brackets and Markdown characters
BracketClosing = 0x0001,
// removes matching brackets and markdown characters
// removes matching brackets and Markdown characters
BracketRemoval = 0x0002
};
Q_DECLARE_FLAGS(AutoTextOptions, AutoTextOption)
explicit QMarkdownTextEdit(QWidget *parent = 0, bool initHighlighter = true);
explicit QMarkdownTextEdit(QWidget *parent = nullptr,
bool initHighlighter = true);
MarkdownHighlighter *highlighter();
QPlainTextEditSearchWidget *searchWidget();
void setIgnoredClickUrlSchemata(QStringList ignoredUrlSchemata);
virtual void openUrl(QString urlString);
QString getMarkdownUrlAtPosition(QString text, int position);
virtual void openUrl(const QString &urlString);
QString getMarkdownUrlAtPosition(const QString &text, int position);
void initSearchFrame(QWidget *searchFrame, bool darkMode = false);
void setAutoTextOptions(AutoTextOptions options);
void setHighlightingEnabled(bool enabled);
static bool isValidUrl(QString urlString);
static bool isValidUrl(const QString &urlString);
void resetMouseCursor() const;
void setReadOnly(bool ro);
void doSearch(QString &searchText,
QPlainTextEditSearchWidget::SearchMode searchMode =
QPlainTextEditSearchWidget::SearchMode::PlainTextMode);
void hideSearchWidget(bool reset);
void updateSettings();
void setLineNumbersCurrentLineColor(QColor color);
void setLineNumbersOtherLineColor(QColor color);
void setSearchWidgetDebounceDelay(uint debounceDelay);
public slots:
void setHighlightingEnabled(bool enabled);
[[nodiscard]] bool highlightingEnabled() const;
void setHighlightCurrentLine(bool set);
bool highlightCurrentLine();
void setCurrentLineHighlightColor(const QColor &c);
QColor currentLineHighlightColor();
public Q_SLOTS:
void duplicateText();
void setText(const QString & text);
void setPlainText(const QString & text);
void setText(const QString &text);
void setPlainText(const QString &text);
void adjustRightMargin();
void hide();
bool openLinkAtCursorPosition();
bool handleBracketRemoval();
bool handleBackspaceEntered();
void centerTheCursor();
void undo();
void moveTextUpDown(bool up);
void setLineNumberEnabled(bool enabled);
protected:
MarkdownHighlighter *_highlighter;
protected:
QTextCursor _textCursor;
MarkdownHighlighter *_highlighter = nullptr;
bool _highlightingEnabled;
QStringList _ignoredClickUrlSchemata;
QPlainTextEditSearchWidget *_searchWidget;
QWidget *_searchFrame;
AutoTextOptions _autoTextOptions;
QStringList _openingCharacters;
QStringList _closingCharacters;
bool _mouseButtonDown = false;
bool _centerCursor = false;
bool _highlightCurrentLine = false;
QColor _currentLineHighlightColor = QColor();
uint _debounceDelay = 0;
bool eventFilter(QObject *obj, QEvent *event);
bool increaseSelectedTextIndention(bool reverse);
bool handleTabEntered(bool reverse);
QMap<QString, QString> parseMarkdownUrlsFromText(QString text);
bool eventFilter(QObject *obj, QEvent *event) override;
QMargins viewportMargins();
bool increaseSelectedTextIndention(
bool reverse,
const QString &indentCharacters = QChar::fromLatin1('\t'));
bool handleTabEntered(bool reverse, const QString &indentCharacters =
QChar::fromLatin1('\t'));
QMap<QString, QString> parseMarkdownUrlsFromText(const 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);
bool handleBracketClosing(const QChar openingCharacter,
QChar closingCharacter = QChar());
bool bracketClosingCheck(const QChar openingCharacter,
QChar closingCharacter);
bool quotationMarkCheck(const QChar quotationCharacter);
void focusOutEvent(QFocusEvent *event) override;
void paintEvent(QPaintEvent *e) override;
bool handleCharRemoval(MarkdownHighlighter::RangeType type, int block,
int position);
void resizeEvent(QResizeEvent *event) override;
void setLineNumberLeftMarginOffset(int offset);
int _lineNumberLeftMarginOffset = 0;
LineNumArea *lineNumberArea() { return _lineNumArea; }
void updateLineNumAreaGeometry();
void updateLineNumberArea(const QRect rect, int dy);
Q_SLOT void updateLineNumberAreaWidth(int);
bool _handleBracketClosingUsed;
LineNumArea *_lineNumArea;
signals:
Q_SIGNALS:
void urlClicked(QString url);
void zoomIn();
void zoomOut();
};

View File

@ -0,0 +1,12 @@
prefix=@CMAKE_INSTALL_PREFIX@
exec_prefix=@CMAKE_INSTALL_PREFIX@
libdir=${exec_prefix}/@CMAKE_INSTALL_LIBDIR@
includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@/
Name: @PROJECT_NAME@
Description: @PROJECT_DESCRIPTION@
Version: @PROJECT_VERSION@
Requires:
Libs: -L${libdir} -lqmarkdowntextedit
Cflags: -I${includedir}

View File

@ -1,23 +1,5 @@
#-------------------------------------------------
#
# 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)
TEMPLATE = subdirs
SUBDIRS = app lib
app.file = qmarkdowntextedit-app.pro
lib.file = qmarkdowntextedit-lib.pro
app.depends = lib

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,270 @@
/*
* MIT License
*
* Copyright (c) 2019-2021 Waqar Ahmed -- <waqar.17a@gmail.com>
*
* 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.
*/
#ifndef QOWNLANGUAGEDATA_H
#define QOWNLANGUAGEDATA_H
#include <QMultiHash>
/* ------------------------
* TEMPLATE FOR LANG DATA
* -------------------------
*
* loadXXXData, where XXX is the language
* keywords are the language keywords e.g, const
* types are built-in types i.e, int, char, var
* literals are words like, true false
* builtin are the library functions
* other can contain any other thing, for e.g, in cpp it contains the
preprocessor
static const QMultiHash<char, QLatin1String> xxx_keywords = {
};
static const QMultiHash<char, QLatin1String> xxx_types = {
};
static const QMultiHash<char, QLatin1String> xxx_literals = {
};
static const QMultiHash<char, QLatin1String> xxx_builtin = {
};
static const QMultiHash<char, QLatin1String> xxx_other = {
};
*/
/**********************************************************/
/* C/C++ Data *********************************************/
/**********************************************************/
void loadCppData(QMultiHash<char, QLatin1String> &typess,
QMultiHash<char, QLatin1String> &keywordss,
QMultiHash<char, QLatin1String> &builtins,
QMultiHash<char, QLatin1String> &literalss,
QMultiHash<char, QLatin1String> &others);
/**********************************************************/
/* Shell Data *********************************************/
/**********************************************************/
void loadShellData(QMultiHash<char, QLatin1String> &types,
QMultiHash<char, QLatin1String> &keywords,
QMultiHash<char, QLatin1String> &builtin,
QMultiHash<char, QLatin1String> &literals,
QMultiHash<char, QLatin1String> &other);
/**********************************************************/
/* JS Data *********************************************/
/**********************************************************/
void loadJSData(QMultiHash<char, QLatin1String> &types,
QMultiHash<char, QLatin1String> &keywords,
QMultiHash<char, QLatin1String> &builtin,
QMultiHash<char, QLatin1String> &literals,
QMultiHash<char, QLatin1String> &other);
/**********************************************************/
/* JS Data *********************************************/
/**********************************************************/
void loadNixData(QMultiHash<char, QLatin1String> &types,
QMultiHash<char, QLatin1String> &keywords,
QMultiHash<char, QLatin1String> &builtin,
QMultiHash<char, QLatin1String> &literals,
QMultiHash<char, QLatin1String> &other);
/**********************************************************/
/* PHP Data *********************************************/
/**********************************************************/
void loadPHPData(QMultiHash<char, QLatin1String> &types,
QMultiHash<char, QLatin1String> &keywords,
QMultiHash<char, QLatin1String> &builtin,
QMultiHash<char, QLatin1String> &literals,
QMultiHash<char, QLatin1String> &other);
/**********************************************************/
/* QML Data *********************************************/
/**********************************************************/
void loadQMLData(QMultiHash<char, QLatin1String> &types,
QMultiHash<char, QLatin1String> &keywords,
QMultiHash<char, QLatin1String> &builtin,
QMultiHash<char, QLatin1String> &literals,
QMultiHash<char, QLatin1String> &other);
/**********************************************************/
/* Python Data *********************************************/
/**********************************************************/
void loadPythonData(QMultiHash<char, QLatin1String> &types,
QMultiHash<char, QLatin1String> &keywords,
QMultiHash<char, QLatin1String> &builtin,
QMultiHash<char, QLatin1String> &literals,
QMultiHash<char, QLatin1String> &other);
/********************************************************/
/*** Rust DATA ***********************************/
/********************************************************/
void loadRustData(QMultiHash<char, QLatin1String> &types,
QMultiHash<char, QLatin1String> &keywords,
QMultiHash<char, QLatin1String> &builtin,
QMultiHash<char, QLatin1String> &literals,
QMultiHash<char, QLatin1String> &other);
/********************************************************/
/*** Java DATA ***********************************/
/********************************************************/
void loadJavaData(QMultiHash<char, QLatin1String> &types,
QMultiHash<char, QLatin1String> &keywords,
QMultiHash<char, QLatin1String> &builtin,
QMultiHash<char, QLatin1String> &literals,
QMultiHash<char, QLatin1String> &other);
/********************************************************/
/*** C# DATA *************************************/
/********************************************************/
void loadCSharpData(QMultiHash<char, QLatin1String> &types,
QMultiHash<char, QLatin1String> &keywords,
QMultiHash<char, QLatin1String> &builtin,
QMultiHash<char, QLatin1String> &literals,
QMultiHash<char, QLatin1String> &other);
/********************************************************/
/*** Go DATA *************************************/
/********************************************************/
void loadGoData(QMultiHash<char, QLatin1String> &types,
QMultiHash<char, QLatin1String> &keywords,
QMultiHash<char, QLatin1String> &builtin,
QMultiHash<char, QLatin1String> &literals,
QMultiHash<char, QLatin1String> &other);
/********************************************************/
/*** V DATA **************************************/
/********************************************************/
void loadVData(QMultiHash<char, QLatin1String> &types,
QMultiHash<char, QLatin1String> &keywords,
QMultiHash<char, QLatin1String> &builtin,
QMultiHash<char, QLatin1String> &literals,
QMultiHash<char, QLatin1String> &other);
/********************************************************/
/*** SQL DATA ************************************/
/********************************************************/
void loadSQLData(QMultiHash<char, QLatin1String> &types,
QMultiHash<char, QLatin1String> &keywords,
QMultiHash<char, QLatin1String> &builtin,
QMultiHash<char, QLatin1String> &literals,
QMultiHash<char, QLatin1String> &other);
/********************************************************/
/*** System Verilog DATA *************************/
/********************************************************/
void loadSystemVerilogData(QMultiHash<char, QLatin1String> &types,
QMultiHash<char, QLatin1String> &keywords,
QMultiHash<char, QLatin1String> &builtin,
QMultiHash<char, QLatin1String> &literals,
QMultiHash<char, QLatin1String> &other);
/********************************************************/
/*** JSON DATA ***********************************/
/********************************************************/
void loadJSONData(QMultiHash<char, QLatin1String> &types,
QMultiHash<char, QLatin1String> &keywords,
QMultiHash<char, QLatin1String> &builtin,
QMultiHash<char, QLatin1String> &literals,
QMultiHash<char, QLatin1String> &other);
/********************************************************/
/*** CSS DATA ***********************************/
/********************************************************/
void loadCSSData(QMultiHash<char, QLatin1String> &types,
QMultiHash<char, QLatin1String> &keywords,
QMultiHash<char, QLatin1String> &builtin,
QMultiHash<char, QLatin1String> &literals,
QMultiHash<char, QLatin1String> &other);
/********************************************************/
/*** Typescript DATA *********************************/
/********************************************************/
void loadTypescriptData(QMultiHash<char, QLatin1String> &types,
QMultiHash<char, QLatin1String> &keywords,
QMultiHash<char, QLatin1String> &builtin,
QMultiHash<char, QLatin1String> &literals,
QMultiHash<char, QLatin1String> &other);
/********************************************************/
/*** YAML DATA ***************************************/
/********************************************************/
void loadYAMLData(QMultiHash<char, QLatin1String> &types,
QMultiHash<char, QLatin1String> &keywords,
QMultiHash<char, QLatin1String> &builtin,
QMultiHash<char, QLatin1String> &literals,
QMultiHash<char, QLatin1String> &other);
/********************************************************/
/*** VEX DATA ****************************************/
/********************************************************/
void loadVEXData(QMultiHash<char, QLatin1String> &types,
QMultiHash<char, QLatin1String> &keywords,
QMultiHash<char, QLatin1String> &builtin,
QMultiHash<char, QLatin1String> &literals,
QMultiHash<char, QLatin1String> &other);
/********************************************************/
/*** CMake DATA **************************************/
/********************************************************/
void loadCMakeData(QMultiHash<char, QLatin1String> &types,
QMultiHash<char, QLatin1String> &keywords,
QMultiHash<char, QLatin1String> &builtin,
QMultiHash<char, QLatin1String> &literals,
QMultiHash<char, QLatin1String> &other);
/********************************************************/
/*** Make DATA ***************************************/
/********************************************************/
void loadMakeData(QMultiHash<char, QLatin1String> &types,
QMultiHash<char, QLatin1String> &keywords,
QMultiHash<char, QLatin1String> &builtin,
QMultiHash<char, QLatin1String> &literals,
QMultiHash<char, QLatin1String> &other);
/********************************************************/
/*** Forth DATA **************************************/
/********************************************************/
void loadForthData(QMultiHash<char, QLatin1String> &types,
QMultiHash<char, QLatin1String> &keywords,
QMultiHash<char, QLatin1String> &builtin,
QMultiHash<char, QLatin1String> &literals,
QMultiHash<char, QLatin1String> &other);
/********************************************************/
/*** GDScript DATA **************************************/
/********************************************************/
void loadGDScriptData(QMultiHash<char, QLatin1String> &types,
QMultiHash<char, QLatin1String> &keywords,
QMultiHash<char, QLatin1String> &builtin,
QMultiHash<char, QLatin1String> &literals,
QMultiHash<char, QLatin1String> &other);
/********************************************************/
/*** TOML DATA **************************************/
/********************************************************/
void loadTOMLData(QMultiHash<char, QLatin1String> &types,
QMultiHash<char, QLatin1String> &keywords,
QMultiHash<char, QLatin1String> &builtin,
QMultiHash<char, QLatin1String> &literals,
QMultiHash<char, QLatin1String> &other);
#endif

View File

@ -1,46 +1,66 @@
/*
* Copyright (c) 2014-2019 Patrizio Bekerle -- http://www.bekerle.com
* MIT License
*
* 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.
* Copyright (c) 2014-2025 Patrizio Bekerle -- <patrizio@bekerle.com>
*
* 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.
* 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.
*/
#include "qplaintexteditsearchwidget.h"
#include "ui_qplaintexteditsearchwidget.h"
#include <QDebug>
#include <QEvent>
#include <QKeyEvent>
#include <QDebug>
QPlainTextEditSearchWidget::QPlainTextEditSearchWidget(QPlainTextEdit *parent) :
QWidget(parent),
ui(new Ui::QPlainTextEditSearchWidget)
{
#include "ui_qplaintexteditsearchwidget.h"
QPlainTextEditSearchWidget::QPlainTextEditSearchWidget(QPlainTextEdit *parent)
: QWidget(parent),
ui(new Ui::QPlainTextEditSearchWidget),
selectionColor(0, 180, 0, 100) {
ui->setupUi(this);
_textEdit = parent;
_darkMode = false;
hide();
ui->searchCountLabel->setStyleSheet(QStringLiteral("* {color: grey}"));
// hiding will leave a open space in the horizontal layout
ui->searchCountLabel->setEnabled(false);
_currentSearchResult = 0;
_searchResultCount = 0;
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()));
connect(ui->closeButton, &QPushButton::clicked, this,
&QPlainTextEditSearchWidget::deactivate);
connect(ui->searchLineEdit, &QLineEdit::textChanged, this,
&QPlainTextEditSearchWidget::searchLineEditTextChanged);
connect(ui->searchDownButton, &QPushButton::clicked, this,
&QPlainTextEditSearchWidget::doSearchDown);
connect(ui->searchUpButton, &QPushButton::clicked, this,
&QPlainTextEditSearchWidget::doSearchUp);
connect(ui->replaceToggleButton, &QPushButton::toggled, this,
&QPlainTextEditSearchWidget::setReplaceMode);
connect(ui->replaceButton, &QPushButton::clicked, this,
&QPlainTextEditSearchWidget::doReplace);
connect(ui->replaceAllButton, &QPushButton::clicked, this,
&QPlainTextEditSearchWidget::doReplaceAll);
connect(&_debounceTimer, &QTimer::timeout, this,
&QPlainTextEditSearchWidget::performSearch);
installEventFilter(this);
ui->searchLineEdit->installEventFilter(this);
@ -52,7 +72,7 @@ QPlainTextEditSearchWidget::QPlainTextEditSearchWidget(QPlainTextEdit *parent) :
ui->buttonFrame->layout()->setSpacing(9);
// set the margin to 0 for the top buttons for OS X
QString buttonStyle = "QPushButton {margin: 0}";
QString buttonStyle = QStringLiteral("QPushButton {margin: 0}");
ui->closeButton->setStyleSheet(buttonStyle);
ui->searchDownButton->setStyleSheet(buttonStyle);
ui->searchUpButton->setStyleSheet(buttonStyle);
@ -61,25 +81,9 @@ QPlainTextEditSearchWidget::QPlainTextEditSearchWidget(QPlainTextEdit *parent) :
#endif
}
QPlainTextEditSearchWidget::~QPlainTextEditSearchWidget() {
delete ui;
}
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::activate() { activate(true); }
void QPlainTextEditSearchWidget::activateReplace() {
// replacing is prohibited if the text edit is readonly
@ -94,7 +98,13 @@ void QPlainTextEditSearchWidget::activateReplace() {
}
void QPlainTextEditSearchWidget::deactivate() {
stopDebounce();
hide();
// Clear the search extra selections when closing the search bar
clearSearchExtraSelections();
_textEdit->setFocus();
}
@ -109,29 +119,33 @@ void QPlainTextEditSearchWidget::setReplaceMode(bool enabled) {
bool QPlainTextEditSearchWidget::eventFilter(QObject *obj, QEvent *event) {
if (event->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
auto *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)) {
} else if ((!_debounceTimer.isActive() &&
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)) {
} else if (!_debounceTimer.isActive() &&
((keyEvent->key() == Qt::Key_Return) ||
(keyEvent->key() == Qt::Key_Down))) {
doSearchDown();
return true;
} else if (keyEvent->key() == Qt::Key_F3) {
} else if (!_debounceTimer.isActive() &&
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();
// }
// if ((obj == ui->replaceLineEdit) && (keyEvent->key() ==
// Qt::Key_Tab)
// && ui->replaceToggleButton->isChecked()) {
// ui->replaceLineEdit->setFocus();
// }
return false;
}
@ -139,19 +153,74 @@ bool QPlainTextEditSearchWidget::eventFilter(QObject *obj, QEvent *event) {
return QWidget::eventFilter(obj, event);
}
void QPlainTextEditSearchWidget::searchLineEditTextChanged(const QString &arg1) {
Q_UNUSED(arg1);
void QPlainTextEditSearchWidget::searchLineEditTextChanged(
const QString &arg1) {
_searchTerm = arg1;
if (_debounceTimer.interval() != 0 && !_searchTerm.isEmpty()) {
_debounceTimer.start();
ui->searchDownButton->setEnabled(false);
ui->searchUpButton->setEnabled(false);
} else {
performSearch();
}
}
void QPlainTextEditSearchWidget::performSearch() {
doSearchCount();
updateSearchExtraSelections();
doSearchDown();
}
void QPlainTextEditSearchWidget::doSearchUp() {
doSearch(false);
void QPlainTextEditSearchWidget::clearSearchExtraSelections() {
_searchExtraSelections.clear();
setSearchExtraSelections();
}
void QPlainTextEditSearchWidget::doSearchDown() {
doSearch(true);
void QPlainTextEditSearchWidget::updateSearchExtraSelections() {
_searchExtraSelections.clear();
const auto textCursor = _textEdit->textCursor();
_textEdit->moveCursor(QTextCursor::Start);
const QColor color = selectionColor;
QTextCharFormat extraFmt;
extraFmt.setBackground(color);
int findCounter = 0;
const int searchMode = ui->modeComboBox->currentIndex();
while (doSearch(true, false, false)) {
findCounter++;
// prevent infinite loops from regular expression searches like "$", "^"
// or "\b"
if (searchMode == RegularExpressionMode && findCounter >= 10000) {
break;
}
QTextEdit::ExtraSelection extra = QTextEdit::ExtraSelection();
extra.format = extraFmt;
extra.cursor = _textEdit->textCursor();
_searchExtraSelections.append(extra);
}
_textEdit->setTextCursor(textCursor);
this->setSearchExtraSelections();
}
void QPlainTextEditSearchWidget::setSearchExtraSelections() const {
this->_textEdit->setExtraSelections(this->_searchExtraSelections);
}
void QPlainTextEditSearchWidget::stopDebounce() {
_debounceTimer.stop();
ui->searchDownButton->setEnabled(true);
ui->searchUpButton->setEnabled(true);
}
void QPlainTextEditSearchWidget::doSearchUp() { doSearch(false); }
void QPlainTextEditSearchWidget::doSearchDown() { doSearch(true); }
bool QPlainTextEditSearchWidget::doReplace(bool forAll) {
if (_textEdit->isReadOnly()) {
return false;
@ -163,18 +232,18 @@ bool QPlainTextEditSearchWidget::doReplace(bool forAll) {
return false;
}
int searchMode = ui->modeComboBox->currentIndex();
const int searchMode = ui->modeComboBox->currentIndex();
if (searchMode == RegularExpressionMode) {
QString text = cursor.selectedText();
text.replace(QRegExp(ui->searchLineEdit->text()),
ui->replaceLineEdit->text());
text.replace(QRegularExpression(ui->searchLineEdit->text()),
ui->replaceLineEdit->text());
cursor.insertText(text);
} else {
cursor.insertText(ui->replaceLineEdit->text());
}
if (!forAll) {
int position = cursor.position();
const int position = cursor.position();
if (!doSearch(true)) {
// restore the last cursor position if text wasn't found any more
@ -195,72 +264,238 @@ void QPlainTextEditSearchWidget::doReplaceAll() {
_textEdit->moveCursor(QTextCursor::Start);
// replace until everything to the bottom is replaced
while (doSearch(true, false) && doReplace(true)) {}
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();
bool QPlainTextEditSearchWidget::doSearch(bool searchDown,
bool allowRestartAtTop,
bool updateUI) {
if (_debounceTimer.isActive()) {
stopDebounce();
}
const QString text = ui->searchLineEdit->text();
if (text.isEmpty()) {
if (updateUI) {
ui->searchLineEdit->setStyleSheet(QLatin1String(""));
}
if (text == "") {
ui->searchLineEdit->setStyleSheet("");
return false;
}
int searchMode = ui->modeComboBox->currentIndex();
const int searchMode = ui->modeComboBox->currentIndex();
const bool caseSensitive = ui->matchCaseSensitiveButton->isChecked();
QFlags<QTextDocument::FindFlag> options = searchDown ?
QTextDocument::FindFlag(0)
: QTextDocument::FindBackward;
QFlags<QTextDocument::FindFlag> options =
searchDown ? QTextDocument::FindFlag(0) : QTextDocument::FindBackward;
if (searchMode == WholeWordsMode) {
options |= QTextDocument::FindWholeWords;
}
if (ui->matchCaseSensitiveButton->isChecked()) {
if (caseSensitive) {
options |= QTextDocument::FindCaseSensitively;
}
bool found;
if (searchMode == RegularExpressionMode) {
found = _textEdit->find(QRegExp(text), options);
} else {
found = _textEdit->find(text, options);
// block signal to reduce too many signals being fired and too many updates
_textEdit->blockSignals(true);
bool found =
searchMode == RegularExpressionMode
?
#if (QT_VERSION >= QT_VERSION_CHECK(5, 13, 0))
_textEdit->find(
QRegularExpression(
text, caseSensitive
? QRegularExpression::NoPatternOption
: QRegularExpression::CaseInsensitiveOption),
options)
:
#else
_textEdit->find(QRegExp(text, caseSensitive ? Qt::CaseSensitive
: Qt::CaseInsensitive),
options)
:
#endif
_textEdit->find(text, options);
_textEdit->blockSignals(false);
if (found) {
const int result =
searchDown ? ++_currentSearchResult : --_currentSearchResult;
_currentSearchResult = std::min(result, _searchResultCount);
updateSearchCountLabelText();
}
// start at the top (or bottom) if not found
if (!found && allowRestartAtTop) {
_textEdit->moveCursor(
searchDown ? QTextCursor::Start : QTextCursor::End);
found = _textEdit->find(text, options);
_textEdit->moveCursor(searchDown ? QTextCursor::Start
: QTextCursor::End);
found =
searchMode == RegularExpressionMode
?
#if (QT_VERSION >= QT_VERSION_CHECK(5, 13, 0))
_textEdit->find(
QRegularExpression(
text, caseSensitive
? QRegularExpression::NoPatternOption
: QRegularExpression::CaseInsensitiveOption),
options)
:
#else
_textEdit->find(
QRegExp(text, caseSensitive ? Qt::CaseSensitive
: Qt::CaseInsensitive),
options)
:
#endif
_textEdit->find(text, options);
if (found && updateUI) {
_currentSearchResult = searchDown ? 1 : _searchResultCount;
updateSearchCountLabelText();
}
}
QRect rect = _textEdit->cursorRect();
QMargins margins = _textEdit->layout()->contentsMargins();
int searchWidgetHotArea = _textEdit->height() - this->height();
int marginBottom = (rect.y() > searchWidgetHotArea) ? (this->height() + 10)
: 0;
if (updateUI) {
const QRect rect = _textEdit->cursorRect();
QMargins margins = _textEdit->layout()->contentsMargins();
const int searchWidgetHotArea = _textEdit->height() - this->height();
const 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);
// 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
const QString bgColorCode = _darkMode
? (found ? QStringLiteral("#135a13")
: QStringLiteral("#8d2b36"))
: found ? QStringLiteral("#D5FAE2")
: QStringLiteral("#FAE9EB");
const QString fgColorCode =
_darkMode ? QStringLiteral("#cccccc") : QStringLiteral("#404040");
ui->searchLineEdit->setStyleSheet(
QStringLiteral("* { background: ") + bgColorCode +
QStringLiteral("; color: ") + fgColorCode + QStringLiteral("; }"));
// restore the search extra selections after the find command
this->setSearchExtraSelections();
}
// 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;
}
/**
* @brief Counts the search results
*/
void QPlainTextEditSearchWidget::doSearchCount() {
// Note that we are moving the anchor, so the search will start from the top
// again! Alternative: Restore cursor position afterward, but then we will
// not know
// at what _currentSearchResult we currently are
_textEdit->moveCursor(QTextCursor::Start, QTextCursor::MoveAnchor);
bool found;
_searchResultCount = 0;
_currentSearchResult = 0;
const int searchMode = ui->modeComboBox->currentIndex();
do {
found = doSearch(true, false, false);
if (found) {
_searchResultCount++;
}
// prevent infinite loops from regular expression searches like "$", "^"
// or "\b"
if (searchMode == RegularExpressionMode &&
_searchResultCount >= 10000) {
break;
}
} while (found);
updateSearchCountLabelText();
}
void QPlainTextEditSearchWidget::setDarkMode(bool enabled) {
_darkMode = enabled;
}
void QPlainTextEditSearchWidget::setSearchText(const QString &searchText) {
ui->searchLineEdit->setText(searchText);
}
void QPlainTextEditSearchWidget::setSearchMode(SearchMode searchMode) {
ui->modeComboBox->setCurrentIndex(searchMode);
}
void QPlainTextEditSearchWidget::setDebounceDelay(uint debounceDelay) {
_debounceTimer.setInterval(static_cast<int>(debounceDelay));
}
void QPlainTextEditSearchWidget::activate(bool focus) {
setReplaceMode(ui->modeComboBox->currentIndex() !=
SearchMode::PlainTextMode);
show();
// preset the selected text as search text if there is any and there is no
// other search text
const QString selectedText = _textEdit->textCursor().selectedText();
if (!selectedText.isEmpty() && ui->searchLineEdit->text().isEmpty()) {
ui->searchLineEdit->setText(selectedText);
}
if (focus) {
ui->searchLineEdit->setFocus();
}
ui->searchLineEdit->selectAll();
updateSearchExtraSelections();
doSearchDown();
}
void QPlainTextEditSearchWidget::reset() {
ui->searchLineEdit->clear();
setSearchMode(SearchMode::PlainTextMode);
setReplaceMode(false);
ui->searchCountLabel->setEnabled(false);
}
void QPlainTextEditSearchWidget::updateSearchCountLabelText() {
ui->searchCountLabel->setEnabled(true);
ui->searchCountLabel->setText(QStringLiteral("%1/%2").arg(
_currentSearchResult == 0 ? QChar('-')
: QString::number(_currentSearchResult),
_searchResultCount == 0 ? QChar('-')
: QString::number(_searchResultCount)));
}
void QPlainTextEditSearchWidget::setSearchSelectionColor(const QColor &color) {
selectionColor = color;
}
void QPlainTextEditSearchWidget::on_modeComboBox_currentIndexChanged(
int index) {
Q_UNUSED(index)
doSearchCount();
doSearchDown();
}
void QPlainTextEditSearchWidget::on_matchCaseSensitiveButton_toggled(
bool checked) {
Q_UNUSED(checked)
doSearchCount();
doSearchDown();
}

View File

@ -1,51 +1,73 @@
/*
* Copyright (c) 2014-2019 Patrizio Bekerle -- http://www.bekerle.com
* MIT License
*
* 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.
* Copyright (c) 2014-2025 Patrizio Bekerle -- <patrizio@bekerle.com>
*
* 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.
* 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.
*/
#pragma once
#include <QWidget>
#include <QPlainTextEdit>
#include <QTimer>
#include <QWidget>
namespace Ui {
class QPlainTextEditSearchWidget;
}
class QPlainTextEditSearchWidget : public QWidget
{
class QPlainTextEditSearchWidget : public QWidget {
Q_OBJECT
public:
enum SearchMode {
PlainTextMode,
WholeWordsMode,
RegularExpressionMode
};
public:
enum SearchMode { PlainTextMode, WholeWordsMode, RegularExpressionMode };
explicit QPlainTextEditSearchWidget(QPlainTextEdit *parent = 0);
bool doSearch(bool searchDown = true, bool allowRestartAtTop = true);
explicit QPlainTextEditSearchWidget(QPlainTextEdit *parent = nullptr);
bool doSearch(bool searchDown = true, bool allowRestartAtTop = true,
bool updateUI = true);
void setDarkMode(bool enabled);
~QPlainTextEditSearchWidget();
private:
Ui::QPlainTextEditSearchWidget *ui;
void setSearchText(const QString &searchText);
void setSearchMode(SearchMode searchMode);
void setDebounceDelay(uint debounceDelay);
void activate(bool focus);
void clearSearchExtraSelections();
void updateSearchExtraSelections();
protected:
private:
Ui::QPlainTextEditSearchWidget *ui;
int _searchResultCount;
int _currentSearchResult;
QList<QTextEdit::ExtraSelection> _searchExtraSelections;
QColor selectionColor;
QTimer _debounceTimer;
QString _searchTerm;
void setSearchExtraSelections() const;
void stopDebounce();
protected:
QPlainTextEdit *_textEdit;
bool _darkMode;
bool eventFilter(QObject *obj, QEvent *event);
bool eventFilter(QObject *obj, QEvent *event) override;
public slots:
public Q_SLOTS:
void activate();
void deactivate();
void doSearchDown();
@ -54,7 +76,15 @@ public slots:
void activateReplace();
bool doReplace(bool forAll = false);
void doReplaceAll();
void reset();
void doSearchCount();
protected slots:
protected Q_SLOTS:
void searchLineEditTextChanged(const QString &arg1);
void performSearch();
void updateSearchCountLabelText();
void setSearchSelectionColor(const QColor &color);
private Q_SLOTS:
void on_modeComboBox_currentIndexChanged(int index);
void on_matchCaseSensitiveButton_toggled(bool checked);
};

View File

@ -26,10 +26,85 @@
<property name="bottomMargin">
<number>0</number>
</property>
<item row="0" column="3">
<widget class="QLineEdit" name="searchLineEdit">
<property name="placeholderText">
<string>Find in text</string>
</property>
</widget>
</item>
<item row="1" column="3" colspan="2">
<widget class="QLineEdit" name="replaceLineEdit">
<property name="placeholderText">
<string>Replace with</string>
</property>
</widget>
</item>
<item row="0" column="4">
<widget class="QLabel" name="searchCountLabel">
<property name="text">
<string notr="true">-/-</string>
</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="6">
<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="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="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="7">
<widget class="QPushButton" name="replaceToggleButton">
<property name="toolTip">
<string>replace text</string>
<string>Advanced search / replace text</string>
</property>
<property name="text">
<string/>
@ -46,44 +121,10 @@
</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">
<item row="0" column="5">
<widget class="QPushButton" name="searchDownButton">
<property name="toolTip">
<string>search forward</string>
<string>Search forward</string>
</property>
<property name="text">
<string/>
@ -97,41 +138,37 @@
</property>
</widget>
</item>
<item row="0" column="5">
<widget class="QPushButton" name="searchUpButton">
<item row="1" column="5">
<widget class="QPushButton" name="matchCaseSensitiveButton">
<property name="toolTip">
<string>search backward</string>
<string>Match case sensitive</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>
<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>
<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">
<item row="2" column="0" colspan="2">
<widget class="QLabel" name="modeLabel">
<property name="text">
<string>Replace:</string>
<string>Mode:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="2" column="3">
<item row="2" column="3" colspan="2">
<widget class="QFrame" name="buttonFrame">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
@ -147,7 +184,7 @@
<number>0</number>
</property>
<property name="bottomMargin">
<number>9</number>
<number>0</number>
</property>
<item>
<widget class="QComboBox" name="modeComboBox">
@ -183,6 +220,9 @@
</item>
<item>
<widget class="QPushButton" name="replaceButton">
<property name="toolTip">
<string>Replace one text occurrence</string>
</property>
<property name="text">
<string>Replace</string>
</property>
@ -193,8 +233,11 @@
</item>
<item>
<widget class="QPushButton" name="replaceAllButton">
<property name="toolTip">
<string>Replace all text occurrences</string>
</property>
<property name="text">
<string>Replace All</string>
<string>Replace all</string>
</property>
<property name="flat">
<bool>false</bool>
@ -204,36 +247,6 @@
</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>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

@ -0,0 +1,28 @@
#!/bin/bash
#
# A tool to run clang-format on the entire project
#
# Some inspirations were taken from https://github.com/eklitzke/clang-format-all
# Variable that will hold the name of the clang-format command
FMT=""
# Some distros just call it clang-format. Others (e.g. Ubuntu) are insistent
# that the version number be part of the command. We prefer clang-format if
# that's present, otherwise we work backwards from highest version to lowest
# version.
for clangfmt in clang-format{,-{4,3}.{9,8,7,6,5,4,3,2,1,0}}; do
if which "$clangfmt" &>/dev/null; then
FMT="$clangfmt"
break
fi
done
# Check if we found a working clang-format
if [ -z "$FMT" ]; then
echo "failed to find clang-format"
exit 1
fi
$FMT -i *.cpp
$FMT -i *.h

Binary file not shown.

View File

@ -0,0 +1,92 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="es_ES">
<context>
<name>QPlainTextEditSearchWidget</name>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="32"/>
<source>Find in text</source>
<translation>Buscar en el texto</translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="39"/>
<source>Replace with</source>
<translation>Reemplazar por</translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="53"/>
<source>Find:</source>
<translation>Buscar:</translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="63"/>
<source>Search backward</source>
<translation>Buscar hacia atrás</translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="80"/>
<source>Replace:</source>
<translation>Reemplazar:</translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="90"/>
<source>Close search</source>
<translation>Cerrar búsqueda</translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="107"/>
<source>Advanced search / replace text</source>
<translation>Búsqueda avanzada / reemplazar texto</translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="127"/>
<source>Search forward</source>
<translation>Buscar hacia adelante</translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="144"/>
<source>Match case sensitive</source>
<translation>Distingue mayúsculas y minúsculas</translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="164"/>
<source>Mode:</source>
<translation>Modo:</translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="193"/>
<source>Plain text</source>
<translation>Texto plano</translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="198"/>
<source>Whole words</source>
<translation>Palabras enteras</translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="203"/>
<source>Regular expression</source>
<translation>Expresión regular</translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="224"/>
<source>Replace one text occurrence</source>
<translation>Reemplazar una ocurrencia del texto</translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="227"/>
<source>Replace</source>
<translation>Reemplazar</translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="237"/>
<source>Replace all text occurrences</source>
<translation>Reemplazar todas las ocurrencias del texto</translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="240"/>
<source>Replace all</source>
<translation>Reemplazar todo</translation>
</message>
</context>
</TS>

Binary file not shown.

View File

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="ur_PK">
<context>
<name>QPlainTextEditSearchWidget</name>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="62"/>
<source>close search</source>
<translation>تلاش بند کریں</translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="52"/>
<source>Find:</source>
<translation>تلاش:</translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="32"/>
<source>replace text</source>
<translation>ٹیکصٹ بدلیں</translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="79"/>
<source>find in text</source>
<translation>متن میں تلاش کریں</translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="86"/>
<source>search forward</source>
<translation>آگے تلاش کریں</translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="103"/>
<source>search backward</source>
<translation>پیچھے تلاش کریں</translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="120"/>
<source>replace with</source>
<translation>بدلیں اس سے</translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="127"/>
<source>Replace:</source>
<translation>بدلیں:</translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="168"/>
<source>Replace</source>
<translation>بدلیں</translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="178"/>
<source>Replace All</source>
<translation>تمام کو بدل دیں</translation>
</message>
</context>
</TS>

View File

@ -0,0 +1,92 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="zh_CN">
<context>
<name>QPlainTextEditSearchWidget</name>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="32"/>
<source>Find in text</source>
<translation></translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="39"/>
<source>Replace with</source>
<translation></translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="53"/>
<source>Find:</source>
<translation></translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="63"/>
<source>Search backward</source>
<translation></translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="80"/>
<source>Replace:</source>
<translation></translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="90"/>
<source>Close search</source>
<translation></translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="107"/>
<source>Advanced search / replace text</source>
<translation>/</translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="127"/>
<source>Search forward</source>
<translation></translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="144"/>
<source>Match case sensitive</source>
<translation></translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="164"/>
<source>Mode:</source>
<translation></translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="193"/>
<source>Plain text</source>
<translation></translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="198"/>
<source>Whole words</source>
<translation></translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="203"/>
<source>Regular expression</source>
<translation></translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="224"/>
<source>Replace one text occurrence</source>
<translation></translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="227"/>
<source>Replace</source>
<translation></translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="237"/>
<source>Replace all text occurrences</source>
<translation></translation>
</message>
<message>
<location filename="../qplaintexteditsearchwidget.ui" line="240"/>
<source>Replace all</source>
<translation></translation>
</message>
</context>
</TS>

View File

@ -0,0 +1,39 @@
# https://github.com/numtide/treefmt
# https://github.com/numtide/treefmt-nix
on-unmatched = "info"
[formatter.clang-format]
command = "clang-format"
# Only target the exact directories we want
includes = [
"*.cpp",
"*.h",
]
[formatter.prettier]
command = "prettier"
options = ["--write"]
includes = ["*.md", "*.yaml", "*.yml"]
[formatter.shfmt]
command = "shfmt"
excludes = []
includes = ["*.sh", "*.bash", "*.envrc", "*.envrc.*"]
options = ["-s", "-w", "-i", "2"]
[formatter.just]
command = "just"
includes = ["*.just"]
[formatter.taplo]
command = "taplo"
includes = ["*.toml"]
options = ["format"]
[formatter.nixfmt-rfc-style]
command = "nixfmt"
excludes = []
includes = ["*.nix"]
options = []

View File

@ -6,6 +6,7 @@
#include <QFileInfo>
#include <QDir>
#include <QRect>
#include <QDebug>
Settings::Settings()
{
@ -27,7 +28,8 @@ void Settings::save()
auto path = helper::path::pathToSettings();
QSettings settings(path, QSettings::IniFormat);
settings.clear();
for (const QString& e: data().keys())
for (const QString& e: mData.keys())
{
settings.setValue(e, data().value(e));
}
@ -42,6 +44,8 @@ void Settings::load()
// Path to settings file
QString path = helper::path::pathToSettings();
qDebug() << "Settings file path: " << path;
// Check if directory exists at all
QString dir = QFileInfo(path).absoluteDir().path();

View File

@ -608,7 +608,7 @@ int TimeLine::getTime(int year, int month, int day, std::vector<TimeRecord>* int
// Find range of related records [lowest, higher)
TimeArray::iterator lowest = std::lower_bound(mData.begin(), mData.end(), d, [] (const TimeRecord& tr, const QDate& d)
{
return tr.endTime() < QDateTime(d).toTime_t();
return tr.endTime() < QDateTime(d, QTime()).toSecsSinceEpoch();
});
//TimeArray::iterator higher = std::upper_bound(mData.begin(), mData.end(), d, [] (const QDate& d, const TimeRecord& tr) { return tr.startTime().toLocalTime().date() < d;});

View File

@ -47,7 +47,9 @@ QDateTime TimeIntervalDlg::finishDate()
void TimeIntervalDlg::accept()
{
// Insert interval
mModel->insertInterval(TimeRecord(startDate().toUTC().toTime_t(), finishDate().toUTC().toTime_t(), mTimeline->taskId()));
mModel->insertInterval(TimeRecord(startDate().toUTC().toSecsSinceEpoch(),
finishDate().toUTC().toSecsSinceEpoch(),
mTimeline->taskId()));
emit accepted();
close();
}
@ -68,7 +70,9 @@ void TimeIntervalDlg::closeEvent(QCloseEvent *event)
bool TimeIntervalDlg::validate()
{
assert(mTimeline);
return !mTimeline->hasIntersection(TimeRecord(startDate().toUTC().toTime_t(), finishDate().toUTC().toTime_t(), mTimeline->taskId()));
return !mTimeline->hasIntersection(TimeRecord(startDate().toUTC().toSecsSinceEpoch(),
finishDate().toUTC().toSecsSinceEpoch(),
mTimeline->taskId()));
}
void TimeIntervalDlg::dataChanged(const QDateTime& changed)

View File

@ -275,7 +275,7 @@ int TimeTreeModel::columnCount(const QModelIndex &parent) const
static QString monthToString(int month)
{
return QDate::longMonthName(month);
return QLocale::system().monthName(month);
}
QVariant TimeTreeModel::data(const QModelIndex &index, int role) const

View File

@ -2,9 +2,9 @@
# I use this script on two different hosts so there are logic to find proper Qt installation
export QT_HOME=/home/$USER/qt/5.12.10/gcc_64
export QT_HOME=/home/$USER/tools/qt/6.8.0/gcc_64
if [ ! -d "$QT_HOME" ] ; then
export QT_HOME=/home/$USER/qt5/5.12.12/gcc_64
export QT_HOME=/home/$USER/tools/qt/6.8.0/gcc_64
fi
# Build .appimage

Binary file not shown.