Skip to content

Commit

Permalink
Add the markdown entry
Browse files Browse the repository at this point in the history
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
  • Loading branch information
kqwyf authored and filipesaraiva committed Aug 23, 2018
1 parent a441853 commit ea6b09e
Show file tree
Hide file tree
Showing 12 changed files with 414 additions and 3 deletions.
6 changes: 6 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
41 changes: 41 additions & 0 deletions cmake/FindDiscount.cmake
Original file line number Diff line number Diff line change
@@ -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, <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()
8 changes: 6 additions & 2 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ set(cantor_PART_SRCS
worksheetimageitem.cpp
commandentry.cpp
textentry.cpp
markdownentry.cpp
pagebreakentry.cpp
imageentry.cpp
latexentry.cpp
Expand Down Expand Up @@ -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 )
4 changes: 4 additions & 0 deletions src/cantor_part.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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()));
Expand Down
3 changes: 2 additions & 1 deletion src/cantor_part.rc
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!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"/>
Expand Down Expand Up @@ -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"/>
Expand Down
2 changes: 2 additions & 0 deletions src/config-cantor.h.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,6 @@

#cmakedefine LIBSPECTRE_FOUND 1

#cmakedefine Discount_FOUND 1

#define PATH_TO_CANTOR_PLUGINS "${PATH_TO_CANTOR_BACKENDS}"
239 changes: 239 additions & 0 deletions src/markdownentry.cpp
Original file line number Diff line number Diff line change
@@ -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 <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;
}
Loading

0 comments on commit ea6b09e

Please sign in to comment.