diff --git a/CMakeLists.txt b/CMakeLists.txt --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -70,6 +70,12 @@ set(WITH_EPS Off) endif(NOT WIN32) +find_package(Discount) +set_package_properties(Discount PROPERTIES DESCRIPTION "A C implementation of the Markdown markup language" + URL "https://www.pell.portland.or.us/~orc/Code/discount/" + TYPE OPTIONAL + PURPOSE "Used for Markdown entries in Cantor") + add_definitions(-DQT_USE_FAST_CONCATENATION -DQT_USE_FAST_OPERATOR_PLUS) if (CMAKE_BUILD_TYPE STREQUAL "RELEASE") add_definitions(-DQT_NO_DEBUG_OUTPUT) diff --git a/cmake/FindDiscount.cmake b/cmake/FindDiscount.cmake new file mode 100644 --- /dev/null +++ b/cmake/FindDiscount.cmake @@ -0,0 +1,41 @@ +# - Find Discount +# Find the Discount markdown library. +# +# This module defines +# Discount_FOUND - whether the Discount library was found +# Discount_LIBRARIES - the Discount library +# Discount_INCLUDE_DIR - the include path of the Discount library + +# Copyright (c) 2017, Julian Wolff, +# Copyright (c) 2018, Sune Vuorela, +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + + +if (Discount_INCLUDE_DIR AND Discount_LIBRARIES) + + # Already in cache + set (Discount_FOUND TRUE) + +else (Discount_INCLUDE_DIR AND Discount_LIBRARIES) + + find_library (Discount_LIBRARIES + NAMES markdown libmarkdown + ) + + find_path (Discount_INCLUDE_DIR + NAMES mkdio.h + ) + + include (FindPackageHandleStandardArgs) + find_package_handle_standard_args (Discount DEFAULT_MSG Discount_LIBRARIES Discount_INCLUDE_DIR) + +endif (Discount_INCLUDE_DIR AND Discount_LIBRARIES) + +mark_as_advanced(Discount_INCLUDE_DIR Discount_LIBRARIES) + +if (Discount_FOUND) + add_library(Discount::Lib UNKNOWN IMPORTED) + set_target_properties(Discount::Lib PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${Discount_INCLUDE_DIR} IMPORTED_LOCATION ${Discount_LIBRARIES}) +endif() diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -62,6 +62,7 @@ worksheetimageitem.cpp commandentry.cpp textentry.cpp + markdownentry.cpp pagebreakentry.cpp imageentry.cpp latexentry.cpp @@ -98,11 +99,14 @@ target_link_libraries(cantorpart KF5::Parts KF5::NewStuff KF5::TextEditor ${Qt5XmlPatterns_LIBRARIES} - KF5::KIOCore KF5::KIOFileWidgets KF5::KIOWidgets + KF5::KIOCore KF5::KIOFileWidgets KF5::KIOWidgets Qt5::PrintSupport cantorlibs cantor_config ) if(LIBSPECTRE_FOUND) - target_link_libraries(cantorpart ${LIBSPECTRE_LIBRARY}) + target_link_libraries(cantorpart ${LIBSPECTRE_LIBRARY}) endif(LIBSPECTRE_FOUND) +if(Discount_FOUND) + target_link_libraries(cantorpart Discount::Lib) +endif(Discount_FOUND) install( FILES cantor_part.rc DESTINATION ${KDE_INSTALL_KXMLGUI5DIR}/cantor ) install( FILES cantor_scripteditor.rc DESTINATION ${KDE_INSTALL_KXMLGUI5DIR}/cantor ) diff --git a/src/cantor_part.cpp b/src/cantor_part.cpp --- a/src/cantor_part.cpp +++ b/src/cantor_part.cpp @@ -286,6 +286,10 @@ actionCollection()->addAction(QLatin1String("insert_text_entry"), insertTextEntry); connect(insertTextEntry, SIGNAL(triggered()), m_worksheet, SLOT(insertTextEntry())); + QAction * insertMarkdownEntry=new QAction(i18n("Insert Markdown Entry"), actionCollection()); + actionCollection()->addAction(QLatin1String("insert_markdown_entry"), insertMarkdownEntry); + connect(insertMarkdownEntry, SIGNAL(triggered()), m_worksheet, SLOT(insertMarkdownEntry())); + QAction * insertLatexEntry=new QAction(i18n("Insert Latex Entry"), actionCollection()); actionCollection()->addAction(QLatin1String("insert_latex_entry"), insertLatexEntry); connect(insertLatexEntry, SIGNAL(triggered()), m_worksheet, SLOT(insertLatexEntry())); diff --git a/src/cantor_part.rc b/src/cantor_part.rc --- a/src/cantor_part.rc +++ b/src/cantor_part.rc @@ -1,5 +1,5 @@ - + @@ -34,6 +34,7 @@ + diff --git a/src/config-cantor.h.cmake b/src/config-cantor.h.cmake --- a/src/config-cantor.h.cmake +++ b/src/config-cantor.h.cmake @@ -4,4 +4,6 @@ #cmakedefine LIBSPECTRE_FOUND 1 +#cmakedefine Discount_FOUND 1 + #define PATH_TO_CANTOR_PLUGINS "${PATH_TO_CANTOR_BACKENDS}" diff --git a/src/markdownentry.h b/src/markdownentry.h new file mode 100644 --- /dev/null +++ b/src/markdownentry.h @@ -0,0 +1,73 @@ +/* + 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; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + + --- + Copyright (C) 2018 Yifei Wu + */ + +#ifndef MARKDOWNENTRY_H +#define MARKDOWNENTRY_H + +#include "worksheetentry.h" +#include "worksheettextitem.h" + +class MarkdownEntry : public WorksheetEntry +{ + Q_OBJECT + public: + MarkdownEntry(Worksheet* worksheet); + ~MarkdownEntry() override; + + enum {Type = UserType + 7}; + int type() const Q_DECL_OVERRIDE; + + bool isEmpty() Q_DECL_OVERRIDE; + + bool acceptRichText() Q_DECL_OVERRIDE; + + bool focusEntry(int pos = WorksheetTextItem::TopLeft, qreal xCoord=0) Q_DECL_OVERRIDE; + + void setContent(const QString& content) Q_DECL_OVERRIDE; + void setContent(const QDomElement& content, const KZip& file) Q_DECL_OVERRIDE; + + QDomElement toXml(QDomDocument& doc, KZip* archive) Q_DECL_OVERRIDE; + QString toPlain(const QString& commandSep, const QString& commentStartingSeq, const QString& commentEndingSeq) Q_DECL_OVERRIDE; + + void interruptEvaluation() Q_DECL_OVERRIDE; + + void layOutForWidth(qreal w, bool force = false) Q_DECL_OVERRIDE; + + WorksheetCursor search(const QString& pattern, unsigned flags, + QTextDocument::FindFlags qt_flags, + const WorksheetCursor& pos = WorksheetCursor()) Q_DECL_OVERRIDE; + + public Q_SLOTS: + bool evaluate(WorksheetEntry::EvaluationOption evalOp = FocusNext) Q_DECL_OVERRIDE; + void updateEntry() Q_DECL_OVERRIDE; + + protected: + bool renderMarkdown(QString& plain); + bool eventFilter(QObject* object, QEvent* event) Q_DECL_OVERRIDE; + bool wantToEvaluate() Q_DECL_OVERRIDE; + + protected: + WorksheetTextItem* m_textItem; + QString plain; + QString html; + bool rendered; +}; + +#endif //MARKDOWNENTRY_H diff --git a/src/markdownentry.cpp b/src/markdownentry.cpp new file mode 100644 --- /dev/null +++ b/src/markdownentry.cpp @@ -0,0 +1,239 @@ +/* + 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; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + + --- + Copyright (C) 2018 Yifei Wu + */ + +#include "markdownentry.h" + +#include "config-cantor.h" + +#ifdef Discount_FOUND +extern "C" { +#include +} +#endif + +#include + +MarkdownEntry::MarkdownEntry(Worksheet* worksheet) : WorksheetEntry(worksheet), m_textItem(new WorksheetTextItem(this, Qt::TextEditorInteraction)), rendered(false) +{ + m_textItem->enableRichText(false); + m_textItem->installEventFilter(this); + connect(m_textItem, &WorksheetTextItem::moveToPrevious, this, &MarkdownEntry::moveToPreviousEntry); + connect(m_textItem, &WorksheetTextItem::moveToNext, this, &MarkdownEntry::moveToNextEntry); + connect(m_textItem, SIGNAL(execute()), this, SLOT(evaluate())); +} + +MarkdownEntry::~MarkdownEntry() +{ +} + +bool MarkdownEntry::isEmpty() +{ + return m_textItem->document()->isEmpty(); +} + +int MarkdownEntry::type() const +{ + return Type; +} + +bool MarkdownEntry::acceptRichText() +{ + return false; +} + +bool MarkdownEntry::focusEntry(int pos, qreal xCoord) +{ + if (aboutToBeRemoved()) + return false; + m_textItem->setFocusAt(pos, xCoord); + return true; +} + +void MarkdownEntry::setContent(const QString& content) +{ + rendered = false; + plain = content; + QTextDocument* doc = m_textItem->document(); + doc->setPlainText(plain); + m_textItem->setDocument(doc); +} + +void MarkdownEntry::setContent(const QDomElement& content, const KZip& file) +{ + Q_UNUSED(file); + + rendered = content.attribute(QLatin1String("rendered"), QLatin1String("1")) == QLatin1String("1"); + QDomElement htmlEl = content.firstChildElement(QLatin1String("HTML")); + if(!htmlEl.isNull()) + html = htmlEl.text(); + else + { + html = QLatin1String(""); + rendered = false; // No html provided. Assume that it hasn't been rendered. + } + QDomElement plainEl = content.firstChildElement(QLatin1String("Plain")); + if(!plainEl.isNull()) + plain = plainEl.text(); + else + { + plain = QLatin1String(""); + html = QLatin1String(""); // No plain text provided. The entry shouldn't render anything, or the user can't re-edit it. + } + if(rendered) + m_textItem->setHtml(html); + else + m_textItem->setPlainText(plain); +} + +QDomElement MarkdownEntry::toXml(QDomDocument& doc, KZip* archive) +{ + Q_UNUSED(archive); + + if(!rendered) + plain = m_textItem->toPlainText(); + + QDomElement el = doc.createElement(QLatin1String("Markdown")); + el.setAttribute(QLatin1String("rendered"), (int)rendered); + + QDomElement plainEl = doc.createElement(QLatin1String("Plain")); + plainEl.appendChild(doc.createTextNode(plain)); + el.appendChild(plainEl); + if(rendered) + { + QDomElement htmlEl = doc.createElement(QLatin1String("HTML")); + htmlEl.appendChild(doc.createTextNode(html)); + el.appendChild(htmlEl); + } + return el; +} + +QString MarkdownEntry::toPlain(const QString& commandSep, const QString& commentStartingSeq, const QString& commentEndingSeq) +{ + Q_UNUSED(commandSep); + + if (commentStartingSeq.isEmpty()) + return QString(); + + if(!rendered) + plain = m_textItem->toPlainText(); + + QString text = QString(plain); + + if (!commentEndingSeq.isEmpty()) + return commentStartingSeq + text + commentEndingSeq + QLatin1String("\n"); + return commentStartingSeq + text.replace(QLatin1String("\n"), QLatin1String("\n") + commentStartingSeq) + QLatin1String("\n"); +} + +void MarkdownEntry::interruptEvaluation() +{ +} + +bool MarkdownEntry::evaluate(EvaluationOption evalOp) +{ + if(!rendered) + { + plain = m_textItem->toPlainText(); + rendered = renderMarkdown(plain); + } + + evaluateNext(evalOp); + return true; +} + +bool MarkdownEntry::renderMarkdown(QString& plain) +{ +#ifdef Discount_FOUND + QByteArray mdCharArray = plain.toUtf8(); + MMIOT* mdHandle = mkd_string(mdCharArray.data(), mdCharArray.size()+1, 0); + if(!mkd_compile(mdHandle, MKD_FENCEDCODE | MKD_GITHUBTAGS)) + { + qDebug()<<"Failed to compile the markdown document"; + mkd_cleanup(mdHandle); + return false; + } + char *htmlDocument; + int htmlSize = mkd_document(mdHandle, &htmlDocument); + html = QString::fromUtf8(htmlDocument, htmlSize); + mkd_cleanup(mdHandle); + + m_textItem->setHtml(html); + return true; +#else + Q_UNUSED(plain); + + return false; +#endif +} + +void MarkdownEntry::updateEntry() +{ +} + +WorksheetCursor MarkdownEntry::search(const QString& pattern, unsigned flags, + QTextDocument::FindFlags qt_flags, + const WorksheetCursor& pos) +{ + if (!(flags & WorksheetEntry::SearchText) || + (pos.isValid() && pos.entry() != this)) + return WorksheetCursor(); + + QTextCursor textCursor = m_textItem->search(pattern, qt_flags, pos); + if (textCursor.isNull()) + return WorksheetCursor(); + else + return WorksheetCursor(this, m_textItem, textCursor); +} + +void MarkdownEntry::layOutForWidth(qreal w, bool force) +{ + if (size().width() == w && !force) + return; + + m_textItem->setGeometry(0, 0, w); + setSize(QSizeF(m_textItem->width(), m_textItem->height() + VerticalMargin)); +} + +bool MarkdownEntry::eventFilter(QObject* object, QEvent* event) +{ + if(object == m_textItem) + { + if(event->type() == QEvent::GraphicsSceneMouseDoubleClick) + { + QGraphicsSceneMouseEvent* mouseEvent = dynamic_cast(event); + if(!mouseEvent) return false; + if(mouseEvent->button() == Qt::LeftButton && rendered) + { + QTextDocument* doc = m_textItem->document(); + doc->setPlainText(plain); + m_textItem->setDocument(doc); + m_textItem->setCursorPosition(mouseEvent->pos()); + m_textItem->textCursor().clearSelection(); + rendered = false; + return true; + } + } + } + return false; +} + +bool MarkdownEntry::wantToEvaluate() +{ + return !rendered; +} diff --git a/src/worksheet.h b/src/worksheet.h --- a/src/worksheet.h +++ b/src/worksheet.h @@ -116,17 +116,20 @@ WorksheetEntry* appendCommandEntry(); void appendCommandEntry(const QString& text); WorksheetEntry* appendTextEntry(); + WorksheetEntry* appendMarkdownEntry(); WorksheetEntry* appendImageEntry(); WorksheetEntry* appendPageBreakEntry(); WorksheetEntry* appendLatexEntry(); WorksheetEntry* insertCommandEntry(WorksheetEntry* current = nullptr); void insertCommandEntry(const QString& text); WorksheetEntry* insertTextEntry(WorksheetEntry* current = nullptr); + WorksheetEntry* insertMarkdownEntry(WorksheetEntry* current = nullptr); WorksheetEntry* insertImageEntry(WorksheetEntry* current = nullptr); WorksheetEntry* insertPageBreakEntry(WorksheetEntry* current = nullptr); WorksheetEntry* insertLatexEntry(WorksheetEntry* current = nullptr); WorksheetEntry* insertCommandEntryBefore(WorksheetEntry* current = nullptr); WorksheetEntry* insertTextEntryBefore(WorksheetEntry* current = nullptr); + WorksheetEntry* insertMarkdownEntryBefore(WorksheetEntry* current = nullptr); WorksheetEntry* insertImageEntryBefore(WorksheetEntry* current = nullptr); WorksheetEntry* insertPageBreakEntryBefore(WorksheetEntry* current = nullptr); WorksheetEntry* insertLatexEntryBefore(WorksheetEntry* current = nullptr); diff --git a/src/worksheet.cpp b/src/worksheet.cpp --- a/src/worksheet.cpp +++ b/src/worksheet.cpp @@ -37,6 +37,7 @@ #include "settings.h" #include "commandentry.h" #include "textentry.h" +#include "markdownentry.h" #include "latexentry.h" #include "imageentry.h" #include "pagebreakentry.h" @@ -544,6 +545,10 @@ return appendEntry(TextEntry::Type); } +WorksheetEntry* Worksheet::appendMarkdownEntry() +{ + return appendEntry(MarkdownEntry::Type); +} WorksheetEntry* Worksheet::appendPageBreakEntry() { @@ -612,6 +617,11 @@ return insertEntry(TextEntry::Type, current); } +WorksheetEntry* Worksheet::insertMarkdownEntry(WorksheetEntry* current) +{ + return insertEntry(MarkdownEntry::Type, current); +} + WorksheetEntry* Worksheet::insertCommandEntry(WorksheetEntry* current) { return insertEntry(CommandEntry::Type, current); @@ -675,6 +685,11 @@ return insertEntryBefore(TextEntry::Type, current); } +WorksheetEntry* Worksheet::insertMarkdownEntryBefore(WorksheetEntry* current) +{ + return insertEntryBefore(MarkdownEntry::Type, current); +} + WorksheetEntry* Worksheet::insertCommandEntryBefore(WorksheetEntry* current) { return insertEntryBefore(CommandEntry::Type, current); @@ -1073,6 +1088,10 @@ { entry = appendTextEntry(); entry->setContent(expressionChild, file); + } else if (tag == QLatin1String("Markdown")) + { + entry = appendMarkdownEntry(); + entry->setContent(expressionChild, file); } else if (tag == QLatin1String("Latex")) { entry = appendLatexEntry(); @@ -1170,12 +1189,14 @@ insert->addAction(i18n("Command Entry"), entry, SLOT(insertCommandEntry())); insert->addAction(i18n("Text Entry"), entry, SLOT(insertTextEntry())); + insert->addAction(i18n("Markdown Entry"), entry, SLOT(insertMarkdownEntry())); insert->addAction(i18n("LaTeX Entry"), entry, SLOT(insertLatexEntry())); insert->addAction(i18n("Image"), entry, SLOT(insertImageEntry())); insert->addAction(i18n("Page Break"), entry, SLOT(insertPageBreakEntry())); insertBefore->addAction(i18n("Command Entry"), entry, SLOT(insertCommandEntryBefore())); insertBefore->addAction(i18n("Text Entry"), entry, SLOT(insertTextEntryBefore())); + insertBefore->addAction(i18n("Markdown Entry"), entry, SLOT(insertMarkdownEntryBefore())); insertBefore->addAction(i18n("LaTeX Entry"), entry, SLOT(insertLatexEntryBefore())); insertBefore->addAction(i18n("Image"), entry, SLOT(insertImageEntryBefore())); insertBefore->addAction(i18n("Page Break"), entry, SLOT(insertPageBreakEntryBefore())); @@ -1187,6 +1208,7 @@ } else { menu->addAction(i18n("Insert Command Entry"), this, SLOT(appendCommandEntry())); menu->addAction(i18n("Insert Text Entry"), this, SLOT(appendTextEntry())); + menu->addAction(i18n("Insert Markdown Entry"), this, SLOT(appendMarkdownEntry())); menu->addAction(i18n("Insert LaTeX Entry"), this, SLOT(appendLatexEntry())); menu->addAction(i18n("Insert Image"), this, SLOT(appendImageEntry())); menu->addAction(i18n("Insert Page Break"), this, SLOT(appendPageBreakEntry())); diff --git a/src/worksheetentry.h b/src/worksheetentry.h --- a/src/worksheetentry.h +++ b/src/worksheetentry.h @@ -29,6 +29,7 @@ #include "worksheetcursor.h" class TextEntry; +class MarkdownEntry; class CommandEntry; class ImageEntry; class PageBreakEntry; @@ -112,11 +113,13 @@ void insertCommandEntry(); void insertTextEntry(); + void insertMarkdownEntry(); void insertLatexEntry(); void insertImageEntry(); void insertPageBreakEntry(); void insertCommandEntryBefore(); void insertTextEntryBefore(); + void insertMarkdownEntryBefore(); void insertLatexEntryBefore(); void insertImageEntryBefore(); void insertPageBreakEntryBefore(); diff --git a/src/worksheetentry.cpp b/src/worksheetentry.cpp --- a/src/worksheetentry.cpp +++ b/src/worksheetentry.cpp @@ -21,6 +21,7 @@ #include "worksheetentry.h" #include "commandentry.h" #include "textentry.h" +#include "markdownentry.h" #include "latexentry.h" #include "imageentry.h" #include "pagebreakentry.h" @@ -88,6 +89,8 @@ { case TextEntry::Type: return new TextEntry(worksheet); + case MarkdownEntry::Type: + return new MarkdownEntry(worksheet); case CommandEntry::Type: return new CommandEntry(worksheet); case ImageEntry::Type: @@ -111,6 +114,11 @@ worksheet()->insertTextEntry(this); } +void WorksheetEntry::insertMarkdownEntry() +{ + worksheet()->insertMarkdownEntry(this); +} + void WorksheetEntry::insertLatexEntry() { worksheet()->insertLatexEntry(this); @@ -136,6 +144,11 @@ worksheet()->insertTextEntryBefore(this); } +void WorksheetEntry::insertMarkdownEntryBefore() +{ + worksheet()->insertMarkdownEntryBefore(this); +} + void WorksheetEntry::insertLatexEntryBefore() { worksheet()->insertLatexEntryBefore(this);