Commit ea6b09e9 authored by Yifei Wu's avatar Yifei Wu Committed by Filipe Saraiva
Browse files

Add the markdown entry

Summary:
Add the markdown entry to Cantor. Markdown support enables users to
write a document with runnable code in Markdown style. This
implementation supports LaTeX just like the text entry.

A third-party library Discount is linked in order to convert
markdown into HTML.

Update CMakeLists.txt

A screenshot for preview:
{F6186425}

Reviewers: pino, #cantor, filipesaraiva, sirgienko

Reviewed By: #cantor, filipesaraiva, sirgienko

Subscribers: sirgienko, filipesaraiva, pino, asemke, kde-edu

Tags: #kde_edu

Maniphest Tasks: T9108

Closes T9108

Differential Revision: https://phabricator.kde.org/D14738
parent a4418538
......@@ -70,6 +70,12 @@ else(NOT WIN32)
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)
......
# - 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, <wolff@julianwolff.de>
# Copyright (c) 2018, Sune Vuorela, <sune@kde.org>
#
# 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()
......@@ -62,6 +62,7 @@ set(cantor_PART_SRCS
worksheetimageitem.cpp
commandentry.cpp
textentry.cpp
markdownentry.cpp
pagebreakentry.cpp
imageentry.cpp
latexentry.cpp
......@@ -98,11 +99,14 @@ set_target_properties(cantorpart PROPERTIES PREFIX "${CMAKE_SHARED_LIBRARY_PREFI
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 )
......@@ -286,6 +286,10 @@ CantorPart::CantorPart( QWidget *parentWidget, QObject *parent, const QVariantLi
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()));
......
<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
<kpartgui name="cantor_part" version="2">
<kpartgui name="cantor_part" version="3">
<MenuBar>
<Menu name="file">
<Action name="file_save"/>
......@@ -34,6 +34,7 @@
<Action name="evaluate_current"/>
<Action name="insert_command_entry"/>
<Action name="insert_text_entry"/>
<Action name="insert_markdown_entry"/>
<Action name="insert_latex_entry"/>
<Action name="insert_image"/>
<Action name="insert_page_break_entry"/>
......
......@@ -4,4 +4,6 @@
#cmakedefine LIBSPECTRE_FOUND 1
#cmakedefine Discount_FOUND 1
#define PATH_TO_CANTOR_PLUGINS "${PATH_TO_CANTOR_BACKENDS}"
/*
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 <kqwyfg@gmail.com>
*/
#include "markdownentry.h"
#include "config-cantor.h"
#ifdef Discount_FOUND
extern "C" {
#include <mkdio.h>
}
#endif
#include <QDebug>
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<QGraphicsSceneMouseEvent*>(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;
}
/*
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 <kqwyfg@gmail.com>
*/
#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
......@@ -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 @@ WorksheetEntry* Worksheet::appendTextEntry()
return appendEntry(TextEntry::Type);
}
WorksheetEntry* Worksheet::appendMarkdownEntry()
{
return appendEntry(MarkdownEntry::Type);
}
WorksheetEntry* Worksheet::appendPageBreakEntry()
{
......@@ -612,6 +617,11 @@ WorksheetEntry* Worksheet::insertTextEntry(WorksheetEntry* current)
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 @@ WorksheetEntry* Worksheet::insertTextEntryBefore(WorksheetEntry* current)
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 @@ bool Worksheet::load(QIODevice* device)
{
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 @@ void Worksheet::populateMenu(QMenu *menu, QPointF pos)
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 @@ void Worksheet::populateMenu(QMenu *menu, QPointF pos)
} 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()));
......
......@@ -116,17 +116,20 @@ class Worksheet : public QGraphicsScene
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);
......
......@@ -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 @@ WorksheetEntry* WorksheetEntry::create(int t, Worksheet* worksheet)
{
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 @@ void WorksheetEntry::insertTextEntry()
worksheet()->insertTextEntry(this);
}
void WorksheetEntry::insertMarkdownEntry()
{
worksheet()->insertMarkdownEntry(this);
}
void WorksheetEntry::insertLatexEntry()
{
worksheet()->insertLatexEntry(this);
......@@ -136,6 +144,11 @@ void WorksheetEntry::insertTextEntryBefore()
worksheet()->insertTextEntryBefore(this);
}
void WorksheetEntry::insertMarkdownEntryBefore()
{
worksheet()->insertMarkdownEntryBefore(this);
}
void WorksheetEntry::insertLatexEntryBefore()
{
worksheet()->insertLatexEntryBefore(this);
......
......@@ -29,6 +29,7 @@
#include "worksheetcursor.h"
class TextEntry;
class MarkdownEntry;
class CommandEntry;
class ImageEntry;
class PageBreakEntry;
......@@ -112,11 +113,13 @@ class WorksheetEntry : public QGraphicsObject
void insertCommandEntry();
void insertTextEntry();
void insertMarkdownEntry();
void insertLatexEntry();
void insertImageEntry();
void insertPageBreakEntry();
void insertCommandEntryBefore();
void insertTextEntryBefore();
void insertMarkdownEntryBefore();
void insertLatexEntryBefore();
void insertImageEntryBefore();
void insertPageBreakEntryBefore();
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment