No OneTemporary

File Metadata

Created
Thu, May 2, 8:11 PM
This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 3c26c4c6..974eb00c 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -1,113 +1,114 @@
#########################################################################
# Subdirectories
#########################################################################
add_subdirectory(lib)
add_subdirectory(scripteditor)
include_directories( lib ${CMAKE_CURRENT_BINARY_DIR}/lib)
if(BUILD_TESTING)
include(ECMMarkAsTest)
include_directories( lib/test )
endif(BUILD_TESTING)
add_subdirectory(backends)
add_subdirectory(assistants)
add_subdirectory(xslt)
add_subdirectory(panelplugins)
#build the config object in a separate library, shared between shell and part
kconfig_add_kcfg_files(config_SRCS settings.kcfgc)
add_library( cantor_config SHARED ${config_SRCS} )
target_link_libraries( cantor_config KF5::Parts KF5::NewStuff )
install( TARGETS cantor_config ${KDE_INSTALL_TARGETS_DEFAULT_ARGS} )
set(cantor_SRCS
main.cpp
cantor.cpp
backendchoosedialog.cpp
)
install(FILES cantor.kcfg DESTINATION ${KDE_INSTALL_KCFGDIR})
ki18n_wrap_ui(cantor_SRCS settings.ui)
ki18n_wrap_ui(cantor_SRCS backendchooser.ui)
file(GLOB ICONS_SRCS "${CMAKE_CURRENT_SOURCE_DIR}/../icons/*-apps-cantor.png")
ecm_add_app_icon(cantor_SRCS ICONS ${ICONS_SRCS})
add_executable(cantor ${cantor_SRCS})
target_link_libraries(cantor KF5::Parts KF5::NewStuff KF5::ConfigCore KF5::CoreAddons KF5::ConfigGui
KF5::Crash KF5::XmlGui cantorlibs cantor_config)
########### install files ###############
install(TARGETS cantor ${KDE_INSTALL_TARGETS_DEFAULT_ARGS} )
install( PROGRAMS org.kde.cantor.desktop DESTINATION ${KDE_INSTALL_APPDIR} )
install( FILES cantor_shell.rc DESTINATION ${KDE_INSTALL_KXMLGUI5DIR}/cantor )
install( FILES cantor.knsrc DESTINATION ${KDE_INSTALL_CONFDIR} )
#########################################################################
# KPART SECTION
#########################################################################
set(cantor_PART_SRCS
cantor_part.cpp
worksheet.cpp
worksheetview.cpp
worksheetentry.cpp
worksheettextitem.cpp
worksheetimageitem.cpp
commandentry.cpp
textentry.cpp
markdownentry.cpp
pagebreakentry.cpp
imageentry.cpp
latexentry.cpp
placeholderentry.cpp
worksheetcursor.cpp
searchbar.cpp
actionbar.cpp
worksheettoolbutton.cpp
imagesettingsdialog.cpp
scripteditor/scripteditorwidget.cpp
resultitem.cpp
textresultitem.cpp
imageresultitem.cpp
animationresultitem.cpp
loadedexpression.cpp
animation.cpp
mathrender.cpp
mathrendertask.cpp
extended_document.cpp
+ worksheetcontrolitem.cpp
)
ki18n_wrap_ui(cantor_PART_SRCS imagesettings.ui)
ki18n_wrap_ui(cantor_PART_SRCS standardsearchbar.ui)
ki18n_wrap_ui(cantor_PART_SRCS extendedsearchbar.ui)
string(CONCAT PATH_TO_CANTOR_BACKENDS ${CMAKE_INSTALL_PREFIX} "/${PLUGIN_INSTALL_DIR}")
configure_file (config-cantor.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-cantor.h )
kcoreaddons_add_plugin(cantorpart
SOURCES ${cantor_PART_SRCS}
JSON "cantor_part.json"
INSTALL_NAMESPACE ".")
set_target_properties(cantorpart PROPERTIES PREFIX "${CMAKE_SHARED_LIBRARY_PREFIX}")
target_link_libraries(cantorpart KF5::Parts KF5::NewStuff
KF5::TextEditor ${Qt5XmlPatterns_LIBRARIES}
KF5::KIOCore KF5::KIOFileWidgets KF5::KIOWidgets
Qt5::PrintSupport cantorlibs cantor_config )
# if(Discount_FOUND)
target_link_libraries(cantorpart Discount::Lib)
# endif(Discount_FOUND)
if(BUILD_TESTING)
add_subdirectory(test)
endif(BUILD_TESTING)
install( FILES cantor_part.rc DESTINATION ${KDE_INSTALL_KXMLGUI5DIR}/cantor )
install( FILES cantor_scripteditor.rc DESTINATION ${KDE_INSTALL_KXMLGUI5DIR}/cantor )
diff --git a/src/actionbar.cpp b/src/actionbar.cpp
index 4ba4dde4..31b98e4d 100644
--- a/src/actionbar.cpp
+++ b/src/actionbar.cpp
@@ -1,93 +1,93 @@
/*
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) 2012 Martin Kuettler <martin.kuettler@gmail.com>
*/
#include "actionbar.h"
#include "worksheet.h"
#include "worksheetentry.h"
#include "worksheettoolbutton.h"
#include <QGraphicsProxyWidget>
ActionBar::ActionBar(WorksheetEntry* parent)
: QGraphicsObject(parent)
{
m_pos = 0;
m_height = 0;
QPointF p = worksheet()->worksheetView()->viewRect().topRight();
- qreal w = qMin(parent->size().width(),
+ qreal w = qMin(parent->size().width() - WorksheetEntry::RightMargin,
parent->mapFromScene(p).x());
setPos(w, 0);
connect(worksheet()->worksheetView(), SIGNAL(viewRectChanged(QRectF)),
this, SLOT(updatePosition()));
}
WorksheetToolButton* ActionBar::addButton(const QIcon& icon, const QString& toolTip,
QObject* receiver, const char* method )
{
WorksheetToolButton* button = new WorksheetToolButton(this);
button->setIcon(icon);
button->setIconScale(worksheet()->renderer()->scale());
button->setToolTip(toolTip);
if (receiver && method)
connect(button, SIGNAL(clicked()), receiver, method);
m_pos -= button->width() + 2;
m_height = (m_height > button->height()) ? m_height : button->height();
button->setPos(m_pos, 4);
m_buttons.append(button);
return button;
}
void ActionBar::addSpace()
{
m_pos -= 8;
}
void ActionBar::updatePosition()
{
if (!parentEntry())
return;
QPointF p = worksheet()->worksheetView()->viewRect().topRight();
- qreal w = qMin(parentEntry()->size().width(),
+ qreal w = qMin(parentEntry()->size().width() - WorksheetEntry::RightMargin,
parentEntry()->mapFromScene(p).x());
setPos(w, 0);
const qreal scale = worksheet()->renderer()->scale();
foreach(WorksheetToolButton* button, m_buttons) {
button->setIconScale(scale);
}
}
WorksheetEntry* ActionBar::parentEntry()
{
return qobject_cast<WorksheetEntry*>(parentObject());
}
QRectF ActionBar::boundingRect() const
{
return QRectF(m_pos, 0, -m_pos, m_height);
}
void ActionBar::paint(QPainter*, const QStyleOptionGraphicsItem*, QWidget*)
{
}
Worksheet* ActionBar::worksheet()
{
return qobject_cast<Worksheet*>(scene());
}
diff --git a/src/commandentry.cpp b/src/commandentry.cpp
index fbad8ab5..89d4aa84 100644
--- a/src/commandentry.cpp
+++ b/src/commandentry.cpp
@@ -1,1346 +1,1355 @@
/*
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) 2009 Alexander Rieder <alexanderrieder@gmail.com>
Copyright (C) 2012 Martin Kuettler <martin.kuettler@gmail.com>
Copyright (C) 2018-2019 Alexander Semke <alexander.semke@web.de>
*/
#include "commandentry.h"
#include "resultitem.h"
#include "loadedexpression.h"
#include "lib/jupyterutils.h"
#include "lib/result.h"
#include "lib/helpresult.h"
#include "lib/epsresult.h"
#include "lib/latexresult.h"
#include "lib/completionobject.h"
#include "lib/syntaxhelpobject.h"
#include "lib/session.h"
#include <QGuiApplication>
#include <QDebug>
#include <QDesktopWidget>
#include <QFontDialog>
#include <QScreen>
#include <QTimer>
#include <QToolTip>
#include <QPropertyAnimation>
#include <QJsonArray>
#include <QJsonObject>
#include <KLocalizedString>
#include <KColorScheme>
const QString CommandEntry::Prompt = QLatin1String(">>> ");
const QString CommandEntry::MidPrompt = QLatin1String(">> ");
const QString CommandEntry::HidePrompt = QLatin1String("> ");
const double CommandEntry::HorizontalSpacing = 4;
const double CommandEntry::VerticalSpacing = 4;
static const int colorsCount = 26;
static QColor colors[colorsCount] = {QColor(255,255,255), QColor(0,0,0),
QColor(192,0,0), QColor(255,0,0), QColor(255,192,192), //red
QColor(0,192,0), QColor(0,255,0), QColor(192,255,192), //green
QColor(0,0,192), QColor(0,0,255), QColor(192,192,255), //blue
QColor(192,192,0), QColor(255,255,0), QColor(255,255,192), //yellow
QColor(0,192,192), QColor(0,255,255), QColor(192,255,255), //cyan
QColor(192,0,192), QColor(255,0,255), QColor(255,192,255), //magenta
QColor(192,88,0), QColor(255,128,0), QColor(255,168,88), //orange
QColor(128,128,128), QColor(160,160,160), QColor(195,195,195) //grey
};
CommandEntry::CommandEntry(Worksheet* worksheet) : WorksheetEntry(worksheet),
m_promptItem(new WorksheetTextItem(this, Qt::NoTextInteraction)),
m_commandItem(new WorksheetTextItem(this, Qt::TextEditorInteraction)),
m_resultsCollapsed(false),
m_errorItem(nullptr),
m_expression(nullptr),
m_completionObject(nullptr),
m_syntaxHelpObject(nullptr),
m_evaluationOption(DoNothing),
m_menusInitialized(false),
m_backgroundColorActionGroup(nullptr),
m_backgroundColorMenu(nullptr),
m_textColorActionGroup(nullptr),
m_textColorMenu(nullptr),
m_fontMenu(nullptr)
{
m_promptItem->setPlainText(Prompt);
m_promptItem->setItemDragable(true);
m_commandItem->enableCompletion(true);
KColorScheme scheme = KColorScheme(QPalette::Normal, KColorScheme::View);
m_commandItem->setBackgroundColor(scheme.background(KColorScheme::AlternateBackground).color());
m_promptItemAnimation = new QPropertyAnimation(m_promptItem, "opacity", this);
m_promptItemAnimation->setDuration(600);
m_promptItemAnimation->setStartValue(1);
m_promptItemAnimation->setKeyValueAt(0.5, 0);
m_promptItemAnimation->setEndValue(1);
connect(m_promptItemAnimation, &QPropertyAnimation::finished, this, &CommandEntry::animatePromptItem);
m_promptItem->setDoubleClickBehaviour(WorksheetTextItem::DoubleClickEventBehaviour::Simple);
connect(m_promptItem, &WorksheetTextItem::doubleClick, this, &CommandEntry::changeResultCollapsingAction);
+ connect(&m_controlElement, &WorksheetControlItem::doubleClick, this, &CommandEntry::changeResultCollapsingAction);
+
connect(m_commandItem, &WorksheetTextItem::tabPressed, this, &CommandEntry::showCompletion);
connect(m_commandItem, &WorksheetTextItem::backtabPressed, this, &CommandEntry::selectPreviousCompletion);
connect(m_commandItem, &WorksheetTextItem::applyCompletion, this, &CommandEntry::applySelectedCompletion);
connect(m_commandItem, &WorksheetTextItem::execute, this, [=]() { evaluate();} );
connect(m_commandItem, &WorksheetTextItem::moveToPrevious, this, &CommandEntry::moveToPreviousItem);
connect(m_commandItem, &WorksheetTextItem::moveToNext, this, &CommandEntry::moveToNextItem);
connect(m_commandItem, &WorksheetTextItem::receivedFocus, worksheet, &Worksheet::highlightItem);
connect(m_promptItem, &WorksheetTextItem::drag, this, &CommandEntry::startDrag);
connect(worksheet, &Worksheet::updatePrompt, this, [=]() { updatePrompt();} );
}
CommandEntry::~CommandEntry()
{
if (m_completionBox)
m_completionBox->deleteLater();
if (m_menusInitialized)
{
m_backgroundColorMenu->deleteLater();
m_textColorMenu->deleteLater();
m_fontMenu->deleteLater();
}
}
int CommandEntry::type() const
{
return Type;
}
void CommandEntry::initMenus() {
//background color
const QString colorNames[colorsCount] = {i18n("White"), i18n("Black"),
i18n("Dark Red"), i18n("Red"), i18n("Light Red"),
i18n("Dark Green"), i18n("Green"), i18n("Light Green"),
i18n("Dark Blue"), i18n("Blue"), i18n("Light Blue"),
i18n("Dark Yellow"), i18n("Yellow"), i18n("Light Yellow"),
i18n("Dark Cyan"), i18n("Cyan"), i18n("Light Cyan"),
i18n("Dark Magenta"), i18n("Magenta"), i18n("Light Magenta"),
i18n("Dark Orange"), i18n("Orange"), i18n("Light Orange"),
i18n("Dark Grey"), i18n("Grey"), i18n("Light Grey")
};
//background color
m_backgroundColorActionGroup = new QActionGroup(this);
m_backgroundColorActionGroup->setExclusive(true);
connect(m_backgroundColorActionGroup, &QActionGroup::triggered, this, &CommandEntry::backgroundColorChanged);
m_backgroundColorMenu = new QMenu(i18n("Background Color"));
m_backgroundColorMenu->setIcon(QIcon::fromTheme(QLatin1String("format-fill-color")));
QPixmap pix(16,16);
QPainter p(&pix);
for (int i=0; i<colorsCount; ++i) {
p.fillRect(pix.rect(), colors[i]);
QAction* action = new QAction(QIcon(pix), colorNames[i], m_backgroundColorActionGroup);
action->setCheckable(true);
m_backgroundColorMenu->addAction(action);
}
//text color
m_textColorActionGroup = new QActionGroup(this);
m_textColorActionGroup->setExclusive(true);
connect(m_textColorActionGroup, &QActionGroup::triggered, this, &CommandEntry::textColorChanged);
m_textColorMenu = new QMenu(i18n("Text Color"));
m_textColorMenu->setIcon(QIcon::fromTheme(QLatin1String("format-text-color")));
for (int i=0; i<colorsCount; ++i) {
p.fillRect(pix.rect(), colors[i]);
QAction* action = new QAction(QIcon(pix), colorNames[i], m_textColorActionGroup);
action->setCheckable(true);
m_textColorMenu->addAction(action);
}
//font
m_fontMenu = new QMenu(i18n("Font"));
m_fontMenu->setIcon(QIcon::fromTheme(QLatin1String("preferences-desktop-font")));
QAction* action = new QAction(QIcon::fromTheme(QLatin1String("format-text-bold")), i18n("Bold"));
action->setCheckable(true);
connect(action, &QAction::triggered, this, &CommandEntry::fontBoldTriggered);
m_fontMenu->addAction(action);
action = new QAction(QIcon::fromTheme(QLatin1String("format-text-italic")), i18n("Italic"));
action->setCheckable(true);
connect(action, &QAction::triggered, this, &CommandEntry::fontItalicTriggered);
m_fontMenu->addAction(action);
m_fontMenu->addSeparator();
action = new QAction(QIcon::fromTheme(QLatin1String("format-font-size-less")), i18n("Increase Size"));
connect(action, &QAction::triggered, this, &CommandEntry::fontIncreaseTriggered);
m_fontMenu->addAction(action);
action = new QAction(QIcon::fromTheme(QLatin1String("format-font-size-more")), i18n("Decrease Size"));
connect(action, &QAction::triggered, this, &CommandEntry::fontDecreaseTriggered);
m_fontMenu->addAction(action);
m_fontMenu->addSeparator();
action = new QAction(QIcon::fromTheme(QLatin1String("preferences-desktop-font")), i18n("Select"));
connect(action, &QAction::triggered, this, &CommandEntry::fontSelectTriggered);
m_fontMenu->addAction(action);
m_menusInitialized = true;
}
void CommandEntry::backgroundColorChanged(QAction* action) {
int index = m_backgroundColorActionGroup->actions().indexOf(action);
if (index == -1 || index>=colorsCount)
index = 0;
m_commandItem->setBackgroundColor(colors[index]);
}
void CommandEntry::textColorChanged(QAction* action) {
int index = m_textColorActionGroup->actions().indexOf(action);
if (index == -1 || index>=colorsCount)
index = 0;
m_commandItem->setDefaultTextColor(colors[index]);
}
void CommandEntry::fontBoldTriggered()
{
QAction* action = static_cast<QAction*>(QObject::sender());
QFont font = m_commandItem->font();
font.setBold(action->isChecked());
m_commandItem->setFont(font);
}
void CommandEntry::fontItalicTriggered()
{
QAction* action = static_cast<QAction*>(QObject::sender());
QFont font = m_commandItem->font();
font.setItalic(action->isChecked());
m_commandItem->setFont(font);
}
void CommandEntry::fontIncreaseTriggered()
{
QFont font = m_commandItem->font();
const int currentSize = font.pointSize();
QFontDatabase fdb;
QList<int> sizes = fdb.pointSizes(font.family(), font.styleName());
for (int i = 0; i < sizes.size(); ++i)
{
const int size = sizes.at(i);
if (currentSize == size)
{
if (i + 1 < sizes.size())
{
font.setPointSize(sizes.at(i+1));
m_commandItem->setFont(font);
}
break;
}
}
}
void CommandEntry::fontDecreaseTriggered()
{
QFont font = m_commandItem->font();
const int currentSize = font.pointSize();
QFontDatabase fdb;
QList<int> sizes = fdb.pointSizes(font.family(), font.styleName());
for (int i = 0; i < sizes.size(); ++i)
{
const int size = sizes.at(i);
if (currentSize == size)
{
if (i - 1 >= 0)
{
font.setPointSize(sizes.at(i-1));
m_commandItem->setFont(font);
}
break;
}
}
}
void CommandEntry::fontSelectTriggered()
{
bool ok;
QFont font = QFontDialog::getFont(&ok, m_commandItem->font(), nullptr);
if (ok)
m_commandItem->setFont(font);
}
void CommandEntry::populateMenu(QMenu* menu, QPointF pos)
{
if (!m_menusInitialized)
initMenus();
if (!m_resultItems.isEmpty()) {
if (m_resultsCollapsed)
menu->addAction(i18n("Show Results"), this, &CommandEntry::expandResults);
else
menu->addAction(i18n("Hide Results"), this, &CommandEntry::collapseResults);
}
menu->addMenu(m_backgroundColorMenu);
menu->addMenu(m_textColorMenu);
menu->addMenu(m_fontMenu);
menu->addSeparator();
WorksheetEntry::populateMenu(menu, pos);
}
void CommandEntry::moveToNextItem(int pos, qreal x)
{
WorksheetTextItem* item = qobject_cast<WorksheetTextItem*>(sender());
if (!item)
return;
if (item == m_commandItem) {
if (m_informationItems.isEmpty() ||
!currentInformationItem()->isEditable())
moveToNextEntry(pos, x);
else
currentInformationItem()->setFocusAt(pos, x);
} else if (item == currentInformationItem()) {
moveToNextEntry(pos, x);
}
}
void CommandEntry::moveToPreviousItem(int pos, qreal x)
{
WorksheetTextItem* item = qobject_cast<WorksheetTextItem*>(sender());
if (!item)
return;
if (item == m_commandItem || item == nullptr) {
moveToPreviousEntry(pos, x);
} else if (item == currentInformationItem()) {
m_commandItem->setFocusAt(pos, x);
}
}
QString CommandEntry::command()
{
QString cmd = m_commandItem->toPlainText();
cmd.replace(QChar::ParagraphSeparator, QLatin1Char('\n')); //Replace the U+2029 paragraph break by a Normal Newline
cmd.replace(QChar::LineSeparator, QLatin1Char('\n')); //Replace the line break by a Normal Newline
return cmd;
}
void CommandEntry::setExpression(Cantor::Expression* expr)
{
/*
if ( m_expression ) {
if (m_expression->status() == Cantor::Expression::Computing) {
qDebug() << "OLD EXPRESSION STILL ACTIVE";
m_expression->interrupt();
}
m_expression->deleteLater();
}*/
// Delete any previous error
if(m_errorItem)
{
m_errorItem->deleteLater();
m_errorItem = nullptr;
}
for (auto* item : m_informationItems)
{
item->deleteLater();
}
m_informationItems.clear();
// Delete any previous result
clearResultItems();
m_expression = expr;
m_resultsCollapsed = false;
connect(expr, &Cantor::Expression::gotResult, this, &CommandEntry::updateEntry);
connect(expr, &Cantor::Expression::resultsCleared, this, &CommandEntry::clearResultItems);
connect(expr, &Cantor::Expression::resultRemoved, this, &CommandEntry::removeResultItem);
connect(expr, &Cantor::Expression::resultReplaced, this, &CommandEntry::replaceResultItem);
connect(expr, &Cantor::Expression::idChanged, this, [=]() { updatePrompt();} );
connect(expr, &Cantor::Expression::statusChanged, this, &CommandEntry::expressionChangedStatus);
connect(expr, &Cantor::Expression::needsAdditionalInformation, this, &CommandEntry::showAdditionalInformationPrompt);
connect(expr, &Cantor::Expression::statusChanged, this, [=]() { updatePrompt();} );
updatePrompt();
if(expr->result())
{
worksheet()->gotResult(expr);
updateEntry();
}
expressionChangedStatus(expr->status());
}
Cantor::Expression* CommandEntry::expression()
{
return m_expression;
}
bool CommandEntry::acceptRichText()
{
return false;
}
void CommandEntry::setContent(const QString& content)
{
m_commandItem->setPlainText(content);
}
void CommandEntry::setContent(const QDomElement& content, const KZip& file)
{
m_commandItem->setPlainText(content.firstChildElement(QLatin1String("Command")).text());
LoadedExpression* expr = new LoadedExpression( worksheet()->session() );
expr->loadFromXml(content, file);
//background color
QDomElement backgroundElem = content.firstChildElement(QLatin1String("Background"));
if (!backgroundElem.isNull())
{
QColor color;
color.setRed(backgroundElem.attribute(QLatin1String("red")).toInt());
color.setGreen(backgroundElem.attribute(QLatin1String("green")).toInt());
color.setBlue(backgroundElem.attribute(QLatin1String("blue")).toInt());
m_commandItem->setBackgroundColor(color);
}
//text properties
QDomElement textElem = content.firstChildElement(QLatin1String("Text"));
if (!textElem.isNull())
{
//text color
QDomElement colorElem = textElem.firstChildElement(QLatin1String("Color"));
QColor color;
color.setRed(colorElem.attribute(QLatin1String("red")).toInt());
color.setGreen(colorElem.attribute(QLatin1String("green")).toInt());
color.setBlue(colorElem.attribute(QLatin1String("blue")).toInt());
m_commandItem->setDefaultTextColor(color);
//font properties
QDomElement fontElem = textElem.firstChildElement(QLatin1String("Font"));
QFont font;
font.setFamily(fontElem.attribute(QLatin1String("family")));
font.setPointSize(fontElem.attribute(QLatin1String("pointSize")).toInt());
font.setWeight(fontElem.attribute(QLatin1String("weight")).toInt());
font.setItalic(fontElem.attribute(QLatin1String("italic")).toInt());
m_commandItem->setFont(font);
}
setExpression(expr);
}
void CommandEntry::setContentFromJupyter(const QJsonObject& cell)
{
m_commandItem->setPlainText(Cantor::JupyterUtils::getSource(cell));
LoadedExpression* expr=new LoadedExpression( worksheet()->session() );
expr->loadFromJupyter(cell);
setExpression(expr);
// https://nbformat.readthedocs.io/en/latest/format_description.html#cell-metadata
// 'collapsed': +
// 'scrolled', 'deletable', 'name', 'tags' don't supported Cantor, so ignore them
// 'source_hidden' don't supported
// 'format' for raw entry, so ignore
// I haven't found 'outputs_hidden' inside Jupyter notebooks, and difference from 'collapsed'
// not clear, so also ignore
const QJsonObject& metadata = Cantor::JupyterUtils::getMetadata(cell);
const QJsonValue& collapsed = metadata.value(QLatin1String("collapsed"));
if (collapsed.isBool() && collapsed.toBool() == true && !m_resultItems.isEmpty())
{
// Disable animation for hiding results, we don't need animation on document load stage
bool animationValue = worksheet()->animationsEnabled();
worksheet()->enableAnimations(false);
collapseResults();
worksheet()->enableAnimations(animationValue);
}
setJupyterMetadata(metadata);
}
QJsonValue CommandEntry::toJupyterJson()
{
QJsonObject entry;
entry.insert(QLatin1String("cell_type"), QLatin1String("code"));
QJsonValue executionCountValue;
if (expression() && expression()->id() != -1)
executionCountValue = QJsonValue(expression()->id());
entry.insert(QLatin1String("execution_count"), executionCountValue);
QJsonObject metadata(jupyterMetadata());
if (m_resultsCollapsed)
metadata.insert(QLatin1String("collapsed"), true);
entry.insert(QLatin1String("metadata"), metadata);
Cantor::JupyterUtils::setSource(entry, command());
QJsonArray outputs;
if (expression())
{
Cantor::Expression::Status status = expression()->status();
if (status == Cantor::Expression::Error || status == Cantor::Expression::Interrupted)
{
QJsonObject errorOutput;
errorOutput.insert(Cantor::JupyterUtils::outputTypeKey, QLatin1String("error"));
errorOutput.insert(QLatin1String("ename"), QLatin1String("Unknown"));
errorOutput.insert(QLatin1String("evalue"), QLatin1String("Unknown"));
QJsonArray traceback;
if (status == Cantor::Expression::Error)
{
const QStringList& error = expression()->errorMessage().split(QLatin1Char('\n'));
for (const QString& line: error)
traceback.append(line);
}
else
{
traceback.append(i18n("Interrupted"));
}
errorOutput.insert(QLatin1String("traceback"), traceback);
outputs.append(errorOutput);
}
for (auto* result : expression()->results())
{
const QJsonValue& resultJson = result->toJupyterJson();
if (!resultJson.isNull())
outputs.append(resultJson);
}
}
entry.insert(QLatin1String("outputs"), outputs);
return entry;
}
void CommandEntry::showCompletion()
{
const QString line = currentLine();
if(!worksheet()->completionEnabled() || line.trimmed().isEmpty())
{
if (m_commandItem->hasFocus())
m_commandItem->insertTab();
return;
} else if (isShowingCompletionPopup()) {
QString comp = m_completionObject->completion();
qDebug() << "command" << m_completionObject->command();
qDebug() << "completion" << comp;
if (comp != m_completionObject->command()
|| !m_completionObject->hasMultipleMatches()) {
if (m_completionObject->hasMultipleMatches()) {
completeCommandTo(comp, PreliminaryCompletion);
} else {
completeCommandTo(comp, FinalCompletion);
m_completionBox->hide();
}
} else {
m_completionBox->down();
}
} else {
int p = m_commandItem->textCursor().positionInBlock();
Cantor::CompletionObject* tco=worksheet()->session()->completionFor(line, p);
if(tco)
setCompletion(tco);
}
}
void CommandEntry::selectPreviousCompletion()
{
if (isShowingCompletionPopup())
m_completionBox->up();
}
QString CommandEntry::toPlain(const QString& commandSep, const QString& commentStartingSeq, const QString& commentEndingSeq)
{
Q_UNUSED(commentStartingSeq);
Q_UNUSED(commentEndingSeq);
if (command().isEmpty())
return QString();
return command() + commandSep;
}
QDomElement CommandEntry::toXml(QDomDocument& doc, KZip* archive)
{
QDomElement exprElem = doc.createElement( QLatin1String("Expression") );
QDomElement cmdElem = doc.createElement( QLatin1String("Command") );
cmdElem.appendChild(doc.createTextNode( command() ));
exprElem.appendChild(cmdElem);
// save results of the expression if they exist
if (expression())
{
const QString& errorMessage = expression()->errorMessage();
if (!errorMessage.isEmpty())
{
QDomElement errorElem = doc.createElement( QLatin1String("Error") );
errorElem.appendChild(doc.createTextNode(errorMessage));
exprElem.appendChild(errorElem);
}
for (auto* result : expression()->results())
{
const QDomElement& resultElem = result->toXml(doc);
exprElem.appendChild(resultElem);
if (archive)
result->saveAdditionalData(archive);
}
}
//save the background color if it differs from the default one
const QColor& backgroundColor = m_commandItem->backgroundColor();
KColorScheme scheme = KColorScheme(QPalette::Normal, KColorScheme::View);
if (backgroundColor != scheme.background(KColorScheme::AlternateBackground).color())
{
QDomElement colorElem = doc.createElement( QLatin1String("Background") );
colorElem.setAttribute(QLatin1String("red"), QString::number(backgroundColor.red()));
colorElem.setAttribute(QLatin1String("green"), QString::number(backgroundColor.green()));
colorElem.setAttribute(QLatin1String("blue"), QString::number(backgroundColor.blue()));
exprElem.appendChild(colorElem);
}
//save the text properties if they differ from default values
const QFont& font = m_commandItem->font();
if (font != QFontDatabase::systemFont(QFontDatabase::FixedFont))
{
QDomElement textElem = doc.createElement(QLatin1String("Text"));
//font properties
QDomElement fontElem = doc.createElement(QLatin1String("Font"));
fontElem.setAttribute(QLatin1String("family"), font.family());
fontElem.setAttribute(QLatin1String("pointSize"), QString::number(font.pointSize()));
fontElem.setAttribute(QLatin1String("weight"), QString::number(font.weight()));
fontElem.setAttribute(QLatin1String("italic"), QString::number(font.italic()));
textElem.appendChild(fontElem);
//text color
const QColor& textColor = m_commandItem->defaultTextColor();
QDomElement colorElem = doc.createElement( QLatin1String("Color") );
colorElem.setAttribute(QLatin1String("red"), QString::number(textColor.red()));
colorElem.setAttribute(QLatin1String("green"), QString::number(textColor.green()));
colorElem.setAttribute(QLatin1String("blue"), QString::number(textColor.blue()));
textElem.appendChild(colorElem);
exprElem.appendChild(textElem);
}
return exprElem;
}
QString CommandEntry::currentLine()
{
if (!m_commandItem->hasFocus())
return QString();
QTextBlock block = m_commandItem->textCursor().block();
return block.text();
}
bool CommandEntry::evaluateCurrentItem()
{
// we can't use m_commandItem->hasFocus() here, because
// that doesn't work when the scene doesn't have the focus,
// e.g. when an assistant is used.
if (m_commandItem == worksheet()->focusItem()) {
return evaluate();
} else if (informationItemHasFocus()) {
addInformation();
return true;
}
return false;
}
bool CommandEntry::evaluate(EvaluationOption evalOp)
{
if (worksheet()->session()->status() == Cantor::Session::Disable)
worksheet()->loginToSession();
removeContextHelp();
QToolTip::hideText();
QString cmd = command();
m_evaluationOption = evalOp;
if(cmd.isEmpty()) {
removeResults();
for (auto* item : m_informationItems) {
item->deleteLater();
}
m_informationItems.clear();
recalculateSize();
evaluateNext(m_evaluationOption);
return false;
}
Cantor::Expression* expr = worksheet()->session()->evaluateExpression(cmd);
connect(expr, &Cantor::Expression::gotResult, this, [=]() { worksheet()->gotResult(expr); });
setExpression(expr);
return true;
}
void CommandEntry::interruptEvaluation()
{
Cantor::Expression *expr = expression();
if(expr)
expr->interrupt();
}
void CommandEntry::updateEntry()
{
qDebug() << "update Entry";
Cantor::Expression* expr = expression();
if (expr == nullptr || expr->results().isEmpty())
return;
if (expr->results().last()->type() == Cantor::HelpResult::Type)
return; // Help is handled elsewhere
//CommandEntry::updateEntry() is only called if the worksheet view is resized
//or when we got a new result(s).
//In the second case the number of results and the number of result graphic objects is different
//and we add a new graphic objects for the new result(s) (new result(s) are located in the end).
// NOTE: LatexResult could request update (change from rendered to code, for example)
// So, just update results, if we haven't new results or something similar
if (m_resultItems.size() < expr->results().size())
{
if (m_resultsCollapsed)
expandResults();
for (int i = m_resultItems.size(); i < expr->results().size(); i++)
m_resultItems << ResultItem::create(this, expr->results()[i]);
}
else
{
for (ResultItem* item: m_resultItems)
item->update();
}
+
+ m_controlElement.isCollapsable = m_resultItems.size() > 0;
+
animateSizeChange();
}
void CommandEntry::expressionChangedStatus(Cantor::Expression::Status status)
{
switch (status)
{
case Cantor::Expression::Computing:
{
//change the background of the promt item and start animating it (fade in/out).
//don't start the animation immediately in order to avoid unwanted flickering for "short" commands,
//start the animation after 1 second passed.
if (worksheet()->animationsEnabled())
{
const int id = m_expression->id();
QTimer::singleShot(1000, this, [this, id] () {
if(m_expression->status() == Cantor::Expression::Computing && m_expression->id() == id)
m_promptItemAnimation->start();
});
}
break;
}
case Cantor::Expression::Error:
case Cantor::Expression::Interrupted:
m_promptItemAnimation->stop();
m_promptItem->setOpacity(1.);
m_commandItem->setFocusAt(WorksheetTextItem::BottomRight, 0);
if(!m_errorItem)
{
m_errorItem = new WorksheetTextItem(this, Qt::TextSelectableByMouse);
}
if (status == Cantor::Expression::Error)
{
QString error = m_expression->errorMessage().toHtmlEscaped();
while (error.endsWith(QLatin1Char('\n')))
error.chop(1);
error.replace(QLatin1String("\n"), QLatin1String("<br>"));
error.replace(QLatin1String(" "), QLatin1String("&nbsp;"));
m_errorItem->setHtml(error);
}
else
m_errorItem->setHtml(i18n("Interrupted"));
recalculateSize();
break;
case Cantor::Expression::Done:
m_promptItemAnimation->stop();
m_promptItem->setOpacity(1.);
evaluateNext(m_evaluationOption);
m_evaluationOption = DoNothing;
break;
default:
break;
}
}
void CommandEntry::animatePromptItem() {
if(m_expression->status() == Cantor::Expression::Computing)
m_promptItemAnimation->start();
}
bool CommandEntry::isEmpty()
{
if (m_commandItem->toPlainText().trimmed().isEmpty()) {
if (!m_resultItems.isEmpty())
return false;
return true;
}
return false;
}
bool CommandEntry::focusEntry(int pos, qreal xCoord)
{
if (aboutToBeRemoved())
return false;
WorksheetTextItem* item;
if (pos == WorksheetTextItem::TopLeft || pos == WorksheetTextItem::TopCoord)
item = m_commandItem;
else if (m_informationItems.size() && currentInformationItem()->isEditable())
item = currentInformationItem();
else
item = m_commandItem;
item->setFocusAt(pos, xCoord);
return true;
}
void CommandEntry::setCompletion(Cantor::CompletionObject* tc)
{
if (m_completionObject)
delete m_completionObject;
m_completionObject = tc;
connect(m_completionObject, &Cantor::CompletionObject::done, this, &CommandEntry::showCompletions);
connect(m_completionObject, &Cantor::CompletionObject::lineDone, this, &CommandEntry::completeLineTo);
}
void CommandEntry::showCompletions()
{
disconnect(m_completionObject, &Cantor::CompletionObject::done, this, &CommandEntry::showCompletions);
QString completion = m_completionObject->completion();
qDebug()<<"completion: "<<completion;
qDebug()<<"showing "<<m_completionObject->allMatches();
if(m_completionObject->hasMultipleMatches())
{
completeCommandTo(completion);
QToolTip::showText(QPoint(), QString(), worksheetView());
if (!m_completionBox)
m_completionBox = new KCompletionBox(worksheetView());
m_completionBox->clear();
m_completionBox->setItems(m_completionObject->allMatches());
QList<QListWidgetItem*> items = m_completionBox->findItems(m_completionObject->command(), Qt::MatchFixedString|Qt::MatchCaseSensitive);
if (!items.empty())
m_completionBox->setCurrentItem(items.first());
m_completionBox->setTabHandling(false);
m_completionBox->setActivateOnSelect(true);
connect(m_completionBox.data(), &KCompletionBox::activated, this, &CommandEntry::applySelectedCompletion);
connect(m_commandItem->document(), &QTextDocument::contentsChanged, this, &CommandEntry::completedLineChanged);
connect(m_completionObject, &Cantor::CompletionObject::done, this, &CommandEntry::updateCompletions);
m_commandItem->activateCompletion(true);
m_completionBox->popup();
m_completionBox->move(getPopupPosition());
} else
{
completeCommandTo(completion, FinalCompletion);
}
}
bool CommandEntry::isShowingCompletionPopup()
{
return m_completionBox && m_completionBox->isVisible();
}
void CommandEntry::applySelectedCompletion()
{
QListWidgetItem* item = m_completionBox->currentItem();
if(item)
completeCommandTo(item->text(), FinalCompletion);
m_completionBox->hide();
}
void CommandEntry::completedLineChanged()
{
if (!isShowingCompletionPopup()) {
// the completion popup is not visible anymore, so let's clean up
removeContextHelp();
return;
}
const QString line = currentLine();
//FIXME: For some reason, this slot constantly triggeres, so I have added checking, is this update really needed
if (line != m_completionObject->command())
m_completionObject->updateLine(line, m_commandItem->textCursor().positionInBlock());
}
void CommandEntry::updateCompletions()
{
if (!m_completionObject)
return;
QString completion = m_completionObject->completion();
qDebug()<<"completion: "<<completion;
qDebug()<<"showing "<<m_completionObject->allMatches();
if(m_completionObject->hasMultipleMatches() || !completion.isEmpty())
{
QToolTip::showText(QPoint(), QString(), worksheetView());
m_completionBox->setItems(m_completionObject->allMatches());
QList<QListWidgetItem*> items = m_completionBox->findItems(m_completionObject->command(), Qt::MatchFixedString|Qt::MatchCaseSensitive);
if (!items.empty())
m_completionBox->setCurrentItem(items.first());
else if (m_completionBox->items().count() == 1)
m_completionBox->setCurrentRow(0);
else
m_completionBox->clearSelection();
m_completionBox->move(getPopupPosition());
} else
{
removeContextHelp();
}
}
void CommandEntry::completeCommandTo(const QString& completion, CompletionMode mode)
{
qDebug() << "completion: " << completion;
Cantor::CompletionObject::LineCompletionMode cmode;
if (mode == FinalCompletion) {
cmode = Cantor::CompletionObject::FinalCompletion;
Cantor::SyntaxHelpObject* obj = worksheet()->session()->syntaxHelpFor(completion);
if(obj)
setSyntaxHelp(obj);
} else {
cmode = Cantor::CompletionObject::PreliminaryCompletion;
if(m_syntaxHelpObject)
m_syntaxHelpObject->deleteLater();
m_syntaxHelpObject=nullptr;
}
m_completionObject->completeLine(completion, cmode);
}
void CommandEntry::completeLineTo(const QString& line, int index)
{
qDebug() << "line completion: " << line;
QTextCursor cursor = m_commandItem->textCursor();
cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::MoveAnchor);
cursor.movePosition(QTextCursor::StartOfBlock, QTextCursor::KeepAnchor);
int startPosition = cursor.position();
cursor.insertText(line);
cursor.setPosition(startPosition + index);
m_commandItem->setTextCursor(cursor);
if (m_syntaxHelpObject) {
m_syntaxHelpObject->fetchSyntaxHelp();
// If we are about to show syntax help, then this was the final
// completion, and we should clean up.
removeContextHelp();
}
}
void CommandEntry::setSyntaxHelp(Cantor::SyntaxHelpObject* sh)
{
if(m_syntaxHelpObject)
m_syntaxHelpObject->deleteLater();
m_syntaxHelpObject=sh;
connect(sh, SIGNAL(done()), this, SLOT(showSyntaxHelp()));
}
void CommandEntry::showSyntaxHelp()
{
QString msg = m_syntaxHelpObject->toHtml();
const QPointF cursorPos = m_commandItem->cursorPosition();
// QToolTip doesn't support &nbsp;, but support multiple spaces
msg.replace(QLatin1String("&nbsp;"), QLatin1String(" "));
// Doesn't support &quot, neither;
msg.replace(QLatin1String("&quot;"), QLatin1String("\""));
QToolTip::showText(toGlobalPosition(cursorPos), msg, worksheetView());
}
void CommandEntry::resultDeleted()
{
qDebug()<<"result got removed...";
}
void CommandEntry::addInformation()
{
WorksheetTextItem *answerItem = currentInformationItem();
answerItem->setTextInteractionFlags(Qt::TextSelectableByMouse);
QString inf = answerItem->toPlainText();
inf.replace(QChar::ParagraphSeparator, QLatin1Char('\n'));
inf.replace(QChar::LineSeparator, QLatin1Char('\n'));
qDebug()<<"adding information: "<<inf;
if(m_expression)
m_expression->addInformation(inf);
}
void CommandEntry::showAdditionalInformationPrompt(const QString& question)
{
WorksheetTextItem* questionItem = new WorksheetTextItem(this, Qt::TextSelectableByMouse);
WorksheetTextItem* answerItem = new WorksheetTextItem(this, Qt::TextEditorInteraction);
//change the color and the font for when asking for additional information in order to
//better discriminate from the usual input in the command entry
KColorScheme scheme = KColorScheme(QPalette::Normal, KColorScheme::View);
QColor color = scheme.foreground(KColorScheme::PositiveText).color();
QFont font;
font.setItalic(true);
questionItem->setFont(font);
questionItem->setDefaultTextColor(color);
answerItem->setFont(font);
answerItem->setDefaultTextColor(color);
questionItem->setPlainText(question);
m_informationItems.append(questionItem);
m_informationItems.append(answerItem);
connect(answerItem, &WorksheetTextItem::moveToPrevious, this, &CommandEntry::moveToPreviousItem);
connect(answerItem, &WorksheetTextItem::moveToNext, this, &CommandEntry::moveToNextItem);
connect(answerItem, &WorksheetTextItem::execute, this, &CommandEntry::addInformation);
answerItem->setFocus();
recalculateSize();
}
void CommandEntry::removeResults()
{
//clear the Result objects
if(m_expression)
m_expression->clearResults();
}
void CommandEntry::removeResult(Cantor::Result* result)
{
if (m_expression)
m_expression->removeResult(result);
}
void CommandEntry::removeResultItem(int index)
{
fadeOutItem(m_resultItems[index]->graphicsObject());
m_resultItems.remove(index);
recalculateSize();
}
void CommandEntry::clearResultItems()
{
//fade out all result graphic objects
for(auto* item : m_resultItems)
fadeOutItem(item->graphicsObject());
m_resultItems.clear();
recalculateSize();
}
void CommandEntry::replaceResultItem(int index)
{
ResultItem* previousItem = m_resultItems[index];
m_resultItems[index] = ResultItem::create(this, m_expression->results()[index]);
previousItem->deleteLater();
recalculateSize();
}
void CommandEntry::removeContextHelp()
{
disconnect(m_commandItem->document(), SIGNAL(contentsChanged()), this, SLOT(completedLineChanged()));
m_commandItem->activateCompletion(false);
if (m_completionBox)
m_completionBox->hide();
}
void CommandEntry::updatePrompt(const QString& postfix)
{
KColorScheme color = KColorScheme(QPalette::Normal, KColorScheme::View);
m_promptItem->setPlainText(QString());
QTextCursor c = m_promptItem->textCursor();
QTextCharFormat cformat = c.charFormat();
cformat.clearForeground();
c.setCharFormat(cformat);
cformat.setFontWeight(QFont::Bold);
//insert the session id if available
if(m_expression && worksheet()->showExpressionIds()&&m_expression->id()!=-1)
c.insertText(QString::number(m_expression->id()),cformat);
//detect the correct color for the prompt, depending on the
//Expression state
if(m_expression)
{
if(m_expression ->status() == Cantor::Expression::Computing&&worksheet()->isRunning())
cformat.setForeground(color.foreground(KColorScheme::PositiveText));
else if(m_expression ->status() == Cantor::Expression::Queued)
cformat.setForeground(color.foreground(KColorScheme::InactiveText));
else if(m_expression ->status() == Cantor::Expression::Error)
cformat.setForeground(color.foreground(KColorScheme::NegativeText));
else if(m_expression ->status() == Cantor::Expression::Interrupted)
cformat.setForeground(color.foreground(KColorScheme::NeutralText));
else
cformat.setFontWeight(QFont::Normal);
}
c.insertText(postfix, cformat);
recalculateSize();
}
WorksheetTextItem* CommandEntry::currentInformationItem()
{
if (m_informationItems.isEmpty())
return nullptr;
return m_informationItems.last();
}
bool CommandEntry::informationItemHasFocus()
{
if (m_informationItems.isEmpty())
return false;
return m_informationItems.last()->hasFocus();
}
bool CommandEntry::focusWithinThisItem()
{
return focusItem() != nullptr;
}
QPoint CommandEntry::getPopupPosition()
{
const QPointF cursorPos = m_commandItem->cursorPosition();
const QPoint globalPos = toGlobalPosition(cursorPos);
const QScreen* desktop = QGuiApplication::primaryScreen();
const QRect screenRect = desktop->geometry();
if (globalPos.y() + m_completionBox->height() < screenRect.bottom()) {
return (globalPos);
} else {
QTextBlock block = m_commandItem->textCursor().block();
QTextLayout* layout = block.layout();
int pos = m_commandItem->textCursor().position() - block.position();
QTextLine line = layout->lineForTextPosition(pos);
int dy = - m_completionBox->height() - line.height() - line.leading();
return QPoint(globalPos.x(), globalPos.y() + dy);
}
}
void CommandEntry::invalidate()
{
qDebug() << "ToDo: Invalidate here";
}
bool CommandEntry::wantToEvaluate()
{
return !isEmpty();
}
QPoint CommandEntry::toGlobalPosition(QPointF localPos)
{
const QPointF scenePos = mapToScene(localPos);
const QPoint viewportPos = worksheetView()->mapFromScene(scenePos);
return worksheetView()->viewport()->mapToGlobal(viewportPos);
}
WorksheetCursor CommandEntry::search(const QString& pattern, unsigned flags,
QTextDocument::FindFlags qt_flags,
const WorksheetCursor& pos)
{
if (pos.isValid() && pos.entry() != this)
return WorksheetCursor();
WorksheetCursor p = pos;
QTextCursor cursor;
if (flags & WorksheetEntry::SearchCommand) {
cursor = m_commandItem->search(pattern, qt_flags, p);
if (!cursor.isNull())
return WorksheetCursor(this, m_commandItem, cursor);
}
if (p.textItem() == m_commandItem)
p = WorksheetCursor();
if (m_errorItem && flags & WorksheetEntry::SearchError) {
cursor = m_errorItem->search(pattern, qt_flags, p);
if (!cursor.isNull())
return WorksheetCursor(this, m_errorItem, cursor);
}
if (p.textItem() == m_errorItem)
p = WorksheetCursor();
for (auto* resultItem : m_resultItems)
{
WorksheetTextItem* textResult = dynamic_cast<WorksheetTextItem*>
(resultItem);
if (textResult && flags & WorksheetEntry::SearchResult) {
cursor = textResult->search(pattern, qt_flags, p);
if (!cursor.isNull())
return WorksheetCursor(this, textResult, cursor);
}
}
return WorksheetCursor();
}
void CommandEntry::layOutForWidth(qreal w, bool force)
{
if (w == size().width() && !force)
return;
- m_promptItem->setPos(0,0);
+ m_promptItem->setPos(0, 0);
double x = 0 + m_promptItem->width() + HorizontalSpacing;
double y = 0;
double width = 0;
- m_commandItem->setGeometry(x,y, w-x);
- width = qMax(width, m_commandItem->width());
+ const qreal margin = worksheet()->isPrinting() ? 0 : RightMargin;
+
+ m_commandItem->setGeometry(x, y, w - x - margin);
+ width = qMax(width, m_commandItem->width()+margin);
y += qMax(m_commandItem->height(), m_promptItem->height());
foreach(WorksheetTextItem* information, m_informationItems) {
y += VerticalSpacing;
- y += information->setGeometry(x,y,w-x);
- width = qMax(width, information->width());
+ y += information->setGeometry(x, y, w - x - margin);
+ width = qMax(width, information->width() + margin);
}
if (m_errorItem) {
y += VerticalSpacing;
- y += m_errorItem->setGeometry(x,y,w-x);
- width = qMax(width, m_errorItem->width());
+ y += m_errorItem->setGeometry(x,y,w - x - margin);
+ width = qMax(width, m_errorItem->width() + margin);
}
for (auto* resultItem : m_resultItems)
{
if (!resultItem || !resultItem->graphicsObject()->isVisible())
continue;
y += VerticalSpacing;
- y += resultItem->setGeometry(x, y, w-x);
- width = qMax(width, resultItem->width());
+ y += resultItem->setGeometry(x, y, w - x - margin);
+ width = qMax(width, resultItem->width() + margin);
}
y += VerticalMargin;
QSizeF s(x+ width, y);
if (animationActive()) {
updateSizeAnimation(s);
} else {
setSize(s);
}
}
void CommandEntry::startRemoving()
{
m_promptItem->setItemDragable(false);
WorksheetEntry::startRemoving();
}
WorksheetTextItem* CommandEntry::highlightItem()
{
return m_commandItem;
}
void CommandEntry::collapseResults()
{
for(auto* item : m_resultItems) {
fadeOutItem(item->graphicsObject(), nullptr);
item->graphicsObject()->hide();
}
m_resultsCollapsed = true;
if (worksheet()->animationsEnabled())
{
QTimer::singleShot(100, this, &CommandEntry::setMidPrompt);
QTimer::singleShot(200, this, &CommandEntry::setHidePrompt);
}
else
setHidePrompt();
+ m_controlElement.isCollapsed = true;
animateSizeChange();
}
void CommandEntry::expandResults()
{
for(auto* item : m_resultItems) {
fadeInItem(item->graphicsObject(), nullptr);
item->graphicsObject()->show();
}
m_resultsCollapsed = false;
if (worksheet()->animationsEnabled())
{
QTimer::singleShot(100, this, &CommandEntry::setMidPrompt);
QTimer::singleShot(200, this, SLOT(updatePrompt()));
}
else
this->updatePrompt();
+ m_controlElement.isCollapsed = false;
animateSizeChange();
}
void CommandEntry::setHidePrompt()
{
updatePrompt(HidePrompt);
}
void CommandEntry::setMidPrompt()
{
updatePrompt(MidPrompt);
}
void CommandEntry::changeResultCollapsingAction()
{
if (m_resultItems.size() == 0)
return;
if (m_resultsCollapsed)
expandResults();
else
collapseResults();
}
diff --git a/src/imageentry.cpp b/src/imageentry.cpp
index fa04cb85..a65ab311 100644
--- a/src/imageentry.cpp
+++ b/src/imageentry.cpp
@@ -1,381 +1,384 @@
/*
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) 2012 martin Kuettler <martin.kuettler@gmail.com>
*/
#include "imageentry.h"
#include "worksheetimageitem.h"
#include "actionbar.h"
#include "lib/jupyterutils.h"
#include <KLocalizedString>
#include <QDebug>
#include <QMenu>
#include <QFileSystemWatcher>
#include <QJsonValue>
#include <QJsonObject>
ImageEntry::ImageEntry(Worksheet* worksheet) : WorksheetEntry(worksheet)
{
m_imageItem = nullptr;
m_textItem = new WorksheetTextItem(this);
m_imageWatcher = new QFileSystemWatcher(this);
m_displaySize.width = -1;
m_displaySize.height = -1;
m_printSize.width = -1;
m_printSize.height = -1;
m_displaySize.widthUnit = ImageSize::Auto;
m_displaySize.heightUnit = ImageSize::Auto;
m_printSize.widthUnit = ImageSize::Auto;
m_printSize.heightUnit = ImageSize::Auto;
m_useDisplaySizeForPrinting = true;
connect(m_imageWatcher, &QFileSystemWatcher::fileChanged, this, &ImageEntry::updateEntry);
setFlag(QGraphicsItem::ItemIsFocusable);
updateEntry();
startConfigDialog();
}
void ImageEntry::populateMenu(QMenu* menu, QPointF pos)
{
menu->addAction(QIcon::fromTheme(QLatin1String("configure")), i18n("Configure Image"),
this, SLOT(startConfigDialog()));
menu->addSeparator();
WorksheetEntry::populateMenu(menu, pos);
}
bool ImageEntry::isEmpty()
{
return false;
}
int ImageEntry::type() const
{
return Type;
}
bool ImageEntry::acceptRichText()
{
return false;
}
void ImageEntry::setContent(const QString& content)
{
Q_UNUSED(content);
return;
}
void ImageEntry::setContent(const QDomElement& content, const KZip& file)
{
Q_UNUSED(file);
static QStringList unitNames;
if (unitNames.isEmpty())
unitNames << QLatin1String("(auto)") << QLatin1String("px") << QLatin1String("%");
QDomElement pathElement = content.firstChildElement(QLatin1String("Path"));
QDomElement displayElement = content.firstChildElement(QLatin1String("Display"));
QDomElement printElement = content.firstChildElement(QLatin1String("Print"));
m_imagePath = pathElement.text();
m_displaySize.width = displayElement.attribute(QLatin1String("width")).toDouble();
m_displaySize.height = displayElement.attribute(QLatin1String("height")).toDouble();
m_displaySize.widthUnit = unitNames.indexOf(displayElement.attribute(QLatin1String("widthUnit")));
m_displaySize.heightUnit = unitNames.indexOf(displayElement.attribute(QLatin1String("heightUnit")));
m_useDisplaySizeForPrinting = printElement.attribute(QLatin1String("useDisplaySize")).toInt();
m_printSize.width = printElement.attribute(QLatin1String("width")).toDouble();
m_printSize.height = printElement.attribute(QLatin1String("height")).toDouble();
m_printSize.widthUnit = unitNames.indexOf(printElement.attribute(QLatin1String("widthUnit")));
m_printSize.heightUnit = unitNames.indexOf(printElement.attribute(QLatin1String("heightUnit")));
updateEntry();
}
void ImageEntry::setContentFromJupyter(const QJsonObject& cell)
{
// No need use ImageEntry because without file this entry type are useless
Q_UNUSED(cell);
return;
}
QJsonValue ImageEntry::toJupyterJson()
{
QJsonValue value;
if (!m_imagePath.isEmpty() && m_imageItem)
{
const QImage& image = m_imageItem->pixmap().toImage();
if (!image.isNull())
{
QJsonObject entry;
entry.insert(QLatin1String("cell_type"), QLatin1String("markdown"));
QJsonObject metadata;
QJsonObject size;
size.insert(QLatin1String("width"), image.size().width());
size.insert(QLatin1String("height"), image.size().height());
metadata.insert(Cantor::JupyterUtils::pngMime, size);
entry.insert(Cantor::JupyterUtils::metadataKey, metadata);
QString text(QLatin1String("<img src='attachment:image.png'>"));
QJsonObject attachments;
attachments.insert(QLatin1String("image.png"), Cantor::JupyterUtils::packMimeBundle(image, Cantor::JupyterUtils::pngMime));
entry.insert(QLatin1String("attachments"), attachments);
Cantor::JupyterUtils::setSource(entry, text);
value = entry;
}
}
return value;
}
QDomElement ImageEntry::toXml(QDomDocument& doc, KZip* archive)
{
Q_UNUSED(archive);
static QStringList unitNames;
if (unitNames.isEmpty())
unitNames << QLatin1String("(auto)") << QLatin1String("px") << QLatin1String("%");
QDomElement image = doc.createElement(QLatin1String("Image"));
QDomElement path = doc.createElement(QLatin1String("Path"));
QDomText pathText = doc.createTextNode(m_imagePath);
path.appendChild(pathText);
image.appendChild(path);
QDomElement display = doc.createElement(QLatin1String("Display"));
display.setAttribute(QLatin1String("width"), m_displaySize.width);
display.setAttribute(QLatin1String("widthUnit"), unitNames[m_displaySize.widthUnit]);
display.setAttribute(QLatin1String("height"), m_displaySize.height);
display.setAttribute(QLatin1String("heightUnit"), unitNames[m_displaySize.heightUnit]);
image.appendChild(display);
QDomElement print = doc.createElement(QLatin1String("Print"));
print.setAttribute(QLatin1String("useDisplaySize"), m_useDisplaySizeForPrinting);
print.setAttribute(QLatin1String("width"), m_printSize.width);
print.setAttribute(QLatin1String("widthUnit"), unitNames[m_printSize.widthUnit]);
print.setAttribute(QLatin1String("height"), m_printSize.height);
print.setAttribute(QLatin1String("heightUnit"), unitNames[m_printSize.heightUnit]);
image.appendChild(print);
// For the conversion to latex
QDomElement latexSize = doc.createElement(QLatin1String("LatexSizeString"));
QString sizeString;
if (m_useDisplaySizeForPrinting)
sizeString = latexSizeString(m_displaySize);
else
sizeString = latexSizeString(m_printSize);
QDomText latexSizeString = doc.createTextNode(sizeString);
latexSize.appendChild(latexSizeString);
image.appendChild(latexSize);
return image;
}
QString ImageEntry::toPlain(const QString& commandSep, const QString& commentStartingSeq, const QString& commentEndingSeq)
{
Q_UNUSED(commandSep);
return commentStartingSeq + QLatin1String("image: ") + m_imagePath + commentEndingSeq;
}
QString ImageEntry::latexSizeString(const ImageSize& imgSize)
{
// We use the transformation 1 px = 1/72 in ( = 1 pt in Latex)
QString sizeString=QLatin1String("");
if (imgSize.widthUnit == ImageSize::Auto &&
imgSize.heightUnit == ImageSize::Auto)
return QLatin1String("");
if (imgSize.widthUnit == ImageSize::Percent) {
if (imgSize.heightUnit == ImageSize::Auto ||
(imgSize.heightUnit == ImageSize::Percent &&
imgSize.width == imgSize.height))
return QLatin1String("[scale=") + QString::number(imgSize.width / 100) + QLatin1String("]");
// else? We could set the size based on the actual image size
} else if (imgSize.widthUnit == ImageSize::Auto &&
imgSize.heightUnit == ImageSize::Percent) {
return QLatin1String("[scale=") + QString::number(imgSize.height / 100) + QLatin1String("]");
}
if (imgSize.heightUnit == ImageSize::Pixel)
sizeString = QLatin1String("height=") + QString::number(imgSize.height) + QLatin1String("pt");
if (imgSize.widthUnit == ImageSize::Pixel) {
if (!sizeString.isEmpty())
sizeString += QLatin1String(",");
sizeString += QLatin1String("width=") + QString::number(imgSize.width) + QLatin1String("pt");
}
return QLatin1String("[") + sizeString + QLatin1String("]");
}
void ImageEntry::interruptEvaluation()
{
}
bool ImageEntry::evaluate(EvaluationOption evalOp)
{
evaluateNext(evalOp);
return true;
}
qreal ImageEntry::height()
{
if (m_imageItem && m_imageItem->isVisible())
return m_imageItem->height();
else
return m_textItem->height();
}
void ImageEntry::updateEntry()
{
qreal oldHeight = height();
if (m_imagePath.isEmpty()) {
m_textItem->setPlainText(i18n("Right click here to insert image"));
m_textItem->setVisible(true);
if (m_imageItem)
m_imageItem->setVisible(false);
}
else {
if (!m_imageItem)
m_imageItem = new WorksheetImageItem(this);
if (m_imagePath.endsWith(QLatin1String(".eps"), Qt::CaseInsensitive)) {
m_imageItem->setEps(QUrl::fromLocalFile(m_imagePath));
} else {
QImage img(m_imagePath);
m_imageItem->setImage(img);
}
if (!m_imageItem->imageIsValid()) {
const QString msg = i18n("Cannot load image %1", m_imagePath);
m_textItem->setPlainText(msg);
m_textItem->setVisible(true);
m_imageItem->setVisible(false);
} else {
QSizeF size;
if (worksheet()->isPrinting() && ! m_useDisplaySizeForPrinting)
size = imageSize(m_printSize);
else
size = imageSize(m_displaySize);
// Hack: Eps images need to be scaled
if (m_imagePath.endsWith(QLatin1String(".eps"), Qt::CaseInsensitive))
size /= worksheet()->renderer()->scale();
m_imageItem->setSize(size);
qDebug() << size;
m_textItem->setVisible(false);
m_imageItem->setVisible(true);
}
}
qDebug() << oldHeight << height();
if (oldHeight != height())
recalculateSize();
}
QSizeF ImageEntry::imageSize(const ImageSize& imgSize)
{
const QSize& srcSize = m_imageItem->imageSize();
qreal w = 0.0;
qreal h = 0.0;
if (imgSize.heightUnit == ImageSize::Percent)
h = srcSize.height() * imgSize.height / 100;
else if (imgSize.heightUnit == ImageSize::Pixel)
h = imgSize.height;
if (imgSize.widthUnit == ImageSize::Percent)
w = srcSize.width() * imgSize.width / 100;
else if (imgSize.widthUnit == ImageSize::Pixel)
w = imgSize.width;
if (imgSize.widthUnit == ImageSize::Auto) {
if (imgSize.heightUnit == ImageSize::Auto)
return QSizeF(srcSize.width(), srcSize.height());
else if (h == 0)
w = 0;
else
w = h / srcSize.height() * srcSize.width();
} else if (imgSize.heightUnit == ImageSize::Auto) {
if (w == 0)
h = 0;
else
h = w / srcSize.width() * srcSize.height();
}
return QSizeF(w,h);
}
void ImageEntry::startConfigDialog()
{
ImageSettingsDialog* dialog = new ImageSettingsDialog(worksheet()->worksheetView());
dialog->setData(m_imagePath, m_displaySize, m_printSize,
m_useDisplaySizeForPrinting);
connect(dialog, &ImageSettingsDialog::dataChanged, this, &ImageEntry::setImageData);
dialog->show();
}
void ImageEntry::setImageData(const QString& path,
const ImageSize& displaySize,
const ImageSize& printSize,
bool useDisplaySizeForPrinting)
{
if (path != m_imagePath) {
m_imageWatcher->removePath(m_imagePath);
m_imageWatcher->addPath(path);
m_imagePath = path;
}
m_displaySize = displaySize;
m_printSize = printSize;
m_useDisplaySizeForPrinting = useDisplaySizeForPrinting;
updateEntry();
}
void ImageEntry::addActionsToBar(ActionBar* actionBar)
{
actionBar->addButton(QIcon::fromTheme(QLatin1String("configure")), i18n("Configure Image"),
this, SLOT(startConfigDialog()));
}
void ImageEntry::layOutForWidth(qreal w, bool force)
{
if (size().width() == w && !force)
return;
+ //TODO somethinkg wrong with geometry and control element: control element appears in wrong place
+ const qreal margin = worksheet()->isPrinting() ? 0 : RightMargin;
+
double width;
if (m_imageItem && m_imageItem->isVisible()) {
- m_imageItem->setGeometry(0, 0, w, true);
+ m_imageItem->setGeometry(0, 0, w - margin, true);
width = m_imageItem->width();
} else {
- m_textItem->setGeometry(0, 0, w, true);
+ m_textItem->setGeometry(0, 0, w - margin, true);
width = m_textItem->width();
}
- setSize(QSizeF(width, height() + VerticalMargin));
+ setSize(QSizeF(width + margin, height() + VerticalMargin));
}
bool ImageEntry::wantToEvaluate()
{
return false;
}
bool ImageEntry::wantFocus()
{
return false;
}
diff --git a/src/latexentry.cpp b/src/latexentry.cpp
index 0e46c286..467249af 100644
--- a/src/latexentry.cpp
+++ b/src/latexentry.cpp
@@ -1,569 +1,571 @@
/*
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) 2009 Alexander Rieder <alexanderrieder@gmail.com>
Copyright (C) 2012 Martin Kuettler <martin.kuettler@gmail.com>
*/
#include "latexentry.h"
#include "worksheetentry.h"
#include "worksheet.h"
#include "lib/renderer.h"
#include "lib/jupyterutils.h"
#include "lib/defaulthighlighter.h"
#include "lib/latexrenderer.h"
#include "config-cantor.h"
#include <QTextCursor>
#include <QStandardPaths>
#include <QDir>
#include <QDebug>
#include <QBuffer>
#include <QUuid>
#include <QJsonValue>
#include <QJsonObject>
#include <QJsonArray>
#include <KLocalizedString>
LatexEntry::LatexEntry(Worksheet* worksheet) : WorksheetEntry(worksheet), m_textItem(new WorksheetTextItem(this, Qt::TextEditorInteraction))
{
m_textItem->installEventFilter(this);
connect(m_textItem, &WorksheetTextItem::moveToPrevious, this, &LatexEntry::moveToPreviousEntry);
connect(m_textItem, &WorksheetTextItem::moveToNext, this, &LatexEntry::moveToNextEntry);
connect(m_textItem, SIGNAL(execute()), this, SLOT(evaluate()));
}
void LatexEntry::populateMenu(QMenu* menu, QPointF pos)
{
bool imageSelected = false;
QTextCursor cursor = m_textItem->textCursor();
const QChar repl = QChar::ObjectReplacementCharacter;
if (cursor.hasSelection()) {
QString selection = m_textItem->textCursor().selectedText();
imageSelected = selection.contains(repl);
} else {
// we need to try both the current cursor and the one after the that
cursor = m_textItem->cursorForPosition(pos);
for (int i = 2; i; --i) {
int p = cursor.position();
if (m_textItem->document()->characterAt(p-1) == repl &&
cursor.charFormat().hasProperty(Cantor::Renderer::CantorFormula)) {
m_textItem->setTextCursor(cursor);
imageSelected = true;
break;
}
cursor.movePosition(QTextCursor::NextCharacter);
}
}
if (imageSelected) {
menu->addAction(i18n("Show LaTeX code"), this, SLOT(resolveImagesAtCursor()));
menu->addSeparator();
}
WorksheetEntry::populateMenu(menu, pos);
}
int LatexEntry::type() const
{
return Type;
}
bool LatexEntry::isEmpty()
{
return m_textItem->document()->isEmpty();
}
bool LatexEntry::acceptRichText()
{
return false;
}
bool LatexEntry::focusEntry(int pos, qreal xCoord)
{
if (aboutToBeRemoved())
return false;
m_textItem->setFocusAt(pos, xCoord);
return true;
}
void LatexEntry::setContent(const QString& content)
{
m_latex = content;
m_textItem->setPlainText(m_latex);
}
void LatexEntry::setContent(const QDomElement& content, const KZip& file)
{
m_latex = content.text();
qDebug() << m_latex;
m_textItem->document()->clear();
QTextCursor cursor = m_textItem->textCursor();
cursor.movePosition(QTextCursor::Start);
QString imagePath;
bool useLatexCode = true;
if(content.hasAttribute(QLatin1String("filename")))
{
const KArchiveEntry* imageEntry=file.directory()->entry(content.attribute(QLatin1String("filename")));
if (imageEntry&&imageEntry->isFile())
{
const KArchiveFile* imageFile=static_cast<const KArchiveFile*>(imageEntry);
const QString& dir=QStandardPaths::writableLocation(QStandardPaths::TempLocation);
imageFile->copyTo(dir);
imagePath = dir + QDir::separator() + imageFile->name();
#ifdef LIBSPECTRE_FOUND
QString uuid = Cantor::LatexRenderer::genUuid();
m_renderedFormat = worksheet()->renderer()->render(m_textItem->document(), Cantor::Renderer::EPS, QUrl::fromLocalFile(imagePath), uuid);
qDebug()<<"rendering successful? " << !m_renderedFormat.name().isEmpty();
m_renderedFormat.setProperty(Cantor::Renderer::CantorFormula, Cantor::Renderer::LatexFormula);
m_renderedFormat.setProperty(Cantor::Renderer::ImagePath, imagePath);
m_renderedFormat.setProperty(Cantor::Renderer::Code, m_latex);
cursor.insertText(QString(QChar::ObjectReplacementCharacter), m_renderedFormat);
useLatexCode = false;
m_textItem->denyEditing();
#endif
}
}
if (useLatexCode && content.hasAttribute(QLatin1String("image")))
{
const QByteArray& ba = QByteArray::fromBase64(content.attribute(QLatin1String("image")).toLatin1());
QImage image;
if (image.loadFromData(ba))
{
// Create unique internal url for this loaded image
QUrl internal;
internal.setScheme(QLatin1String("internal"));
internal.setPath(QUuid::createUuid().toString());
m_textItem->document()->addResource(QTextDocument::ImageResource, internal, QVariant(image));
m_renderedFormat.setName(internal.url());
m_renderedFormat.setWidth(image.width());
m_renderedFormat.setHeight(image.height());
m_renderedFormat.setProperty(Cantor::Renderer::CantorFormula, Cantor::Renderer::LatexFormula);
if (!imagePath.isEmpty())
m_renderedFormat.setProperty(Cantor::Renderer::ImagePath, imagePath);
m_renderedFormat.setProperty(Cantor::Renderer::Code, m_latex);
cursor.insertText(QString(QChar::ObjectReplacementCharacter), m_renderedFormat);
useLatexCode = false;
m_textItem->denyEditing();
}
}
if (useLatexCode)
cursor.insertText(m_latex);
}
void LatexEntry::setContentFromJupyter(const QJsonObject& cell)
{
if (!Cantor::JupyterUtils::isCodeCell(cell))
return;
m_textItem->document()->clear();
QTextCursor cursor = m_textItem->textCursor();
cursor.movePosition(QTextCursor::Start);
bool useLatexCode = true;
QString source = Cantor::JupyterUtils::getSource(cell);
m_latex = source.remove(QLatin1String("%%latex\n"));
QJsonArray outputs = cell.value(Cantor::JupyterUtils::outputsKey).toArray();
if (outputs.size() == 1 && Cantor::JupyterUtils::isJupyterDisplayOutput(outputs[0]))
{
const QJsonObject data = outputs[0].toObject().value(Cantor::JupyterUtils::dataKey).toObject();
const QImage& image = Cantor::JupyterUtils::loadImage(data, Cantor::JupyterUtils::pngMime);
if (!image.isNull())
{
QUrl internal;
internal.setScheme(QLatin1String("internal"));
internal.setPath(QUuid::createUuid().toString());
m_textItem->document()->addResource(QTextDocument::ImageResource, internal, QVariant(image));
m_renderedFormat.setName(internal.url());
m_renderedFormat.setWidth(image.width());
m_renderedFormat.setHeight(image.height());
m_renderedFormat.setProperty(Cantor::Renderer::CantorFormula, Cantor::Renderer::LatexFormula);
m_renderedFormat.setProperty(Cantor::Renderer::Code, m_latex);
cursor.insertText(QString(QChar::ObjectReplacementCharacter), m_renderedFormat);
useLatexCode = false;
m_textItem->denyEditing();
}
}
if (useLatexCode)
{
cursor.insertText(m_latex);
m_latex.clear(); // We don't render image, so clear latex code cache
}
}
QJsonValue LatexEntry::toJupyterJson()
{
QJsonObject entry;
entry.insert(Cantor::JupyterUtils::cellTypeKey, QLatin1String("code"));
entry.insert(Cantor::JupyterUtils::executionCountKey, QJsonValue());
QJsonObject metadata, cantorMetadata;
cantorMetadata.insert(QLatin1String("latex_entry"), true);
metadata.insert(Cantor::JupyterUtils::cantorMetadataKey, cantorMetadata);
entry.insert(Cantor::JupyterUtils::metadataKey, metadata);
QJsonArray outputs;
QTextCursor cursor = m_textItem->document()->find(QString(QChar::ObjectReplacementCharacter));
if (!cursor.isNull())
{
QTextImageFormat format=cursor.charFormat().toImageFormat();
QUrl internal;
internal.setUrl(format.name());
const QImage& image = m_textItem->document()->resource(QTextDocument::ImageResource, internal).value<QImage>();
if (!image.isNull())
{
QByteArray ba;
QBuffer buffer(&ba);
buffer.open(QIODevice::WriteOnly);
image.save(&buffer, "PNG");
// Add image result with latex rendered image to this Jupyter code cell
QJsonObject imageResult;
imageResult.insert(Cantor::JupyterUtils::outputTypeKey, QLatin1String("display_data"));
QJsonObject data;
data.insert(Cantor::JupyterUtils::pngMime, Cantor::JupyterUtils::toJupyterMultiline(QString::fromLatin1(ba.toBase64())));
imageResult.insert(QLatin1String("data"), data);
imageResult.insert(Cantor::JupyterUtils::metadataKey, QJsonObject());
outputs.append(imageResult);
}
}
entry.insert(Cantor::JupyterUtils::outputsKey, outputs);
const QString& latex = latexCode();
Cantor::JupyterUtils::setSource(entry, QLatin1String("%%latex\n") + latex);
return entry;
}
QDomElement LatexEntry::toXml(QDomDocument& doc, KZip* archive)
{
QDomElement el = doc.createElement(QLatin1String("Latex"));
el.appendChild( doc.createTextNode( latexCode() ));
QTextCursor cursor = m_textItem->document()->find(QString(QChar::ObjectReplacementCharacter));
if (!cursor.isNull())
{
QTextImageFormat format=cursor.charFormat().toImageFormat();
QString fileName = format.property(Cantor::Renderer::ImagePath).toString();
// Check, if eps file exists, and if not true, rerender latex code
bool isEpsFileExists = QFile::exists(fileName);
#ifdef LIBSPECTRE_FOUND
if (!isEpsFileExists && renderLatexCode())
{
cursor = m_textItem->document()->find(QString(QChar::ObjectReplacementCharacter));
format=cursor.charFormat().toImageFormat();
fileName = format.property(Cantor::Renderer::ImagePath).toString();
isEpsFileExists = QFile::exists(fileName);
}
#endif
if (isEpsFileExists && archive)
{
const QUrl& url=QUrl::fromLocalFile(fileName);
archive->addLocalFile(url.toLocalFile(), url.fileName());
el.setAttribute(QLatin1String("filename"), url.fileName());
}
// Save also rendered QImage, if exist.
QUrl internal;
internal.setUrl(format.name());
const QImage& image = m_textItem->document()->resource(QTextDocument::ImageResource, internal).value<QImage>();
if (!image.isNull())
{
QByteArray ba;
QBuffer buffer(&ba);
buffer.open(QIODevice::WriteOnly);
image.save(&buffer, "PNG");
el.setAttribute(QLatin1String("image"), QString::fromLatin1(ba.toBase64()));
}
}
return el;
}
QString LatexEntry::toPlain(const QString& commandSep, const QString& commentStartingSeq, const QString& commentEndingSeq)
{
Q_UNUSED(commandSep);
if (commentStartingSeq.isEmpty())
return QString();
QString text = latexCode();
if (!commentEndingSeq.isEmpty())
return commentStartingSeq + text + commentEndingSeq + QLatin1String("\n");
return commentStartingSeq + text.replace(QLatin1String("\n"), QLatin1String("\n") + commentStartingSeq) + QLatin1String("\n");
}
void LatexEntry::interruptEvaluation()
{
}
bool LatexEntry::evaluate(EvaluationOption evalOp)
{
bool success = false;
if (isOneImageOnly())
{
success = true;
}
else
{
if (m_latex == latexCode())
{
bool renderWasSuccessful = !m_renderedFormat.name().isEmpty();
if (renderWasSuccessful)
{
QTextCursor cursor = m_textItem->textCursor();
cursor.movePosition(QTextCursor::Start);
cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);
cursor.insertText(QString(QChar::ObjectReplacementCharacter), m_renderedFormat);
m_textItem->denyEditing();
}
else
{
success = renderLatexCode();
}
}
else
{
m_latex = latexCode();
success = renderLatexCode();
}
}
qDebug()<<"rendering successful? "<<success;
evaluateNext(evalOp);
return success;
}
void LatexEntry::updateEntry()
{
QTextCursor cursor = m_textItem->document()->find(QString(QChar::ObjectReplacementCharacter));
while (!cursor.isNull())
{
qDebug()<<"found a formula... rendering the eps...";
QTextImageFormat format=cursor.charFormat().toImageFormat();
const QUrl& url=QUrl::fromLocalFile(format.property(Cantor::Renderer::ImagePath).toString());
QSizeF s = worksheet()->renderer()->renderToResource(m_textItem->document(), Cantor::Renderer::EPS, url, QUrl(format.name()));
qDebug()<<"rendering successful? "<< s.isValid();
cursor.movePosition(QTextCursor::NextCharacter);
cursor = m_textItem->document()->find(QString(QChar::ObjectReplacementCharacter), cursor);
}
}
bool LatexEntry::eventFilter(QObject* object, QEvent* event)
{
if(object == m_textItem && event->type() == QEvent::GraphicsSceneMouseDoubleClick)
{
// One image if we have rendered entry
if (isOneImageOnly())
{
QTextCursor cursor = m_textItem->textCursor();
if (!cursor.hasSelection())
cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor);
cursor.insertText(m_textItem->resolveImages(cursor));
m_textItem->allowEditing();
return true;
}
}
return false;
}
QString LatexEntry::latexCode()
{
QTextCursor cursor = m_textItem->textCursor();
cursor.movePosition(QTextCursor::Start);
cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);
QString code = m_textItem->resolveImages(cursor);
code.replace(QChar::ParagraphSeparator, QLatin1Char('\n')); //Replace the U+2029 paragraph break by a Normal Newline
code.replace(QChar::LineSeparator, QLatin1Char('\n')); //Replace the line break by a Normal Newline
return code;
}
bool LatexEntry::isOneImageOnly()
{
QTextCursor cursor = m_textItem->textCursor();
cursor.movePosition(QTextCursor::Start);
cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);
return (cursor.selectionEnd() == 1 && cursor.selectedText() == QString(QChar::ObjectReplacementCharacter));
}
int LatexEntry::searchText(const QString& text, const QString& pattern,
QTextDocument::FindFlags qt_flags)
{
Qt::CaseSensitivity caseSensitivity;
if (qt_flags & QTextDocument::FindCaseSensitively)
caseSensitivity = Qt::CaseSensitive;
else
caseSensitivity = Qt::CaseInsensitive;
int position;
if (qt_flags & QTextDocument::FindBackward)
position = text.lastIndexOf(pattern, -1, caseSensitivity);
else
position = text.indexOf(pattern, 0, caseSensitivity);
return position;
}
WorksheetCursor LatexEntry::search(const QString& pattern, unsigned flags,
QTextDocument::FindFlags qt_flags,
const WorksheetCursor& pos)
{
if (!(flags & WorksheetEntry::SearchLaTeX))
return WorksheetCursor();
if (pos.isValid() && (pos.entry() != this || pos.textItem() != m_textItem))
return WorksheetCursor();
QTextCursor textCursor = m_textItem->search(pattern, qt_flags, pos);
int position = 0;
QString latex;
const QString repl = QString(QChar::ObjectReplacementCharacter);
QTextCursor latexCursor = m_textItem->search(repl, qt_flags, pos);
while (!latexCursor.isNull()) {
latex = m_textItem->resolveImages(latexCursor);
position = searchText(latex, pattern, qt_flags);
if (position >= 0) {
break;
}
WorksheetCursor c(this, m_textItem, latexCursor);
latexCursor = m_textItem->search(repl, qt_flags, c);
}
if (latexCursor.isNull()) {
if (textCursor.isNull())
return WorksheetCursor();
else
return WorksheetCursor(this, m_textItem, textCursor);
} else {
if (textCursor.isNull() || latexCursor < textCursor) {
int start = latexCursor.selectionStart();
latexCursor.insertText(latex);
QTextCursor c = m_textItem->textCursor();
c.setPosition(start + position);
c.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor,
pattern.length());
return WorksheetCursor(this, m_textItem, c);
} else {
return WorksheetCursor(this, m_textItem, textCursor);
}
}
}
void LatexEntry::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));
+ const qreal margin = worksheet()->isPrinting() ? 0 : RightMargin;
+
+ m_textItem->setGeometry(0, 0, w - margin);
+ setSize(QSizeF(m_textItem->width() + margin, m_textItem->height() + VerticalMargin));
}
bool LatexEntry::wantToEvaluate()
{
return !isOneImageOnly();
}
bool LatexEntry::renderLatexCode()
{
bool success = false;
QString latex = latexCode();
m_renderedFormat = QTextImageFormat(); // clear rendered image
Cantor::LatexRenderer* renderer = new Cantor::LatexRenderer(this);
renderer->setLatexCode(latex);
renderer->setEquationOnly(false);
renderer->setMethod(Cantor::LatexRenderer::LatexMethod);
renderer->renderBlocking();
if (renderer->renderingSuccessful())
{
Cantor::Renderer* epsRend = worksheet()->renderer();
m_renderedFormat = epsRend->render(m_textItem->document(), renderer);
success = !m_renderedFormat.name().isEmpty();
}
else
qWarning() << "Fail to render LatexEntry with error " << renderer->errorMessage();
if(success)
{
QTextCursor cursor = m_textItem->textCursor();
cursor.movePosition(QTextCursor::Start);
cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);
cursor.insertText(QString(QChar::ObjectReplacementCharacter), m_renderedFormat);
m_textItem->denyEditing();
}
delete renderer;
return success;
}
bool LatexEntry::isConvertableToLatexEntry(const QJsonObject& cell)
{
if (!Cantor::JupyterUtils::isCodeCell(cell))
return false;
const QString& source = Cantor::JupyterUtils::getSource(cell);
return source.startsWith(QLatin1String("%%latex\n"));
}
void LatexEntry::resolveImagesAtCursor()
{
QTextCursor cursor = m_textItem->textCursor();
if (!cursor.hasSelection())
cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor);
cursor.insertText(m_textItem->resolveImages(cursor));
}
QString LatexEntry::plain() const
{
return m_textItem->toPlainText();
}
diff --git a/src/markdownentry.cpp b/src/markdownentry.cpp
index c75fda6d..bc04af74 100644
--- a/src/markdownentry.cpp
+++ b/src/markdownentry.cpp
@@ -1,740 +1,742 @@
/*
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 <QJsonArray>
#include <QJsonObject>
#include <QJsonValue>
#include <QImage>
#include <QImageReader>
#include <QBuffer>
#include <KLocalizedString>
#include <QDebug>
#include <QKeyEvent>
#include <QStandardPaths>
#include <QDir>
#include <QFileDialog>
#include <KMessageBox>
#include "jupyterutils.h"
#include "mathrender.h"
#include <config-cantor.h>
#include "settings.h"
#ifdef Discount_FOUND
extern "C" {
#include <mkdio.h>
}
#endif
MarkdownEntry::MarkdownEntry(Worksheet* worksheet) : WorksheetEntry(worksheet), m_textItem(new WorksheetTextItem(this, Qt::TextEditorInteraction)), rendered(false)
{
m_textItem->enableRichText(false);
m_textItem->setOpenExternalLinks(true);
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()));
}
void MarkdownEntry::populateMenu(QMenu* menu, QPointF pos)
{
if (!rendered)
menu->addAction(i18n("Insert Image Attachment"), this, &MarkdownEntry::insertImage);
if (attachedImages.size() != 0)
menu->addAction(i18n("Clear Attachments"), this, &MarkdownEntry::clearAttachments);
WorksheetEntry::populateMenu(menu, pos);
}
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;
setPlainText(plain);
}
void MarkdownEntry::setContent(const QDomElement& content, const KZip& 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.
}
const QDomNodeList& attachments = content.elementsByTagName(QLatin1String("Attachment"));
for (int x = 0; x < attachments.count(); x++)
{
const QDomElement& attachment = attachments.at(x).toElement();
QUrl url(attachment.attribute(QLatin1String("url")));
const QString& base64 = attachment.text();
QImage image;
image.loadFromData(QByteArray::fromBase64(base64.toLatin1()), "PNG");
attachedImages.push_back(std::make_pair(url, QLatin1String("image/png")));
m_textItem->document()->addResource(QTextDocument::ImageResource, url, QVariant(image));
}
if(rendered)
setRenderedHtml(html);
else
setPlainText(plain);
// Handle math after setting html
const QDomNodeList& maths = content.elementsByTagName(QLatin1String("EmbeddedMath"));
foundMath.clear();
for (int i = 0; i < maths.count(); i++)
{
const QDomElement& math = maths.at(i).toElement();
const QString mathCode = math.text();
foundMath.push_back(std::make_pair(mathCode, false));
}
if (rendered)
{
markUpMath();
for (int i = 0; i < maths.count(); i++)
{
const QDomElement& math = maths.at(i).toElement();
bool mathRendered = math.attribute(QLatin1String("rendered")).toInt();
const QString mathCode = math.text();
if (mathRendered)
{
const KArchiveEntry* imageEntry=file.directory()->entry(math.attribute(QLatin1String("path")));
if (imageEntry && imageEntry->isFile())
{
const KArchiveFile* imageFile=static_cast<const KArchiveFile*>(imageEntry);
const QString& dir=QStandardPaths::writableLocation(QStandardPaths::TempLocation);
imageFile->copyTo(dir);
const QString& pdfPath = dir + QDir::separator() + imageFile->name();
QString latex;
Cantor::LatexRenderer::EquationType type;
std::tie(latex, type) = parseMathCode(mathCode);
// Get uuid by removing 'cantor_' and '.pdf' extension
// len('cantor_') == 7, len('.pdf') == 4
QString uuid = pdfPath;
uuid.remove(0, 7);
uuid.chop(4);
bool success;
const auto& data = worksheet()->mathRenderer()->renderExpressionFromPdf(pdfPath, uuid, latex, type, &success);
if (success)
{
QUrl internal;
internal.setScheme(QLatin1String("internal"));
internal.setPath(uuid);
setRenderedMath(i+1, data.first, internal, data.second);
}
}
else if (worksheet()->embeddedMathEnabled())
renderMathExpression(i+1, mathCode);
}
}
}
// Because, all previous actions was on load stage,
// them shoudl unconverted by user
m_textItem->document()->clearUndoRedoStacks();
}
void MarkdownEntry::setContentFromJupyter(const QJsonObject& cell)
{
if (!Cantor::JupyterUtils::isMarkdownCell(cell))
return;
// https://nbformat.readthedocs.io/en/latest/format_description.html#cell-metadata
// There isn't Jupyter metadata for markdown cells, which could be handled by Cantor
// So we just store it
setJupyterMetadata(Cantor::JupyterUtils::getMetadata(cell));
const QJsonObject attachments = cell.value(QLatin1String("attachments")).toObject();
for (const QString& key : attachments.keys())
{
const QJsonValue& attachment = attachments.value(key);
const QString& mimeKey = Cantor::JupyterUtils::firstImageKey(attachment);
if (!mimeKey.isEmpty())
{
const QImage& image = Cantor::JupyterUtils::loadImage(attachment, mimeKey);
QUrl resourceUrl;
resourceUrl.setUrl(QLatin1String("attachment:")+key);
attachedImages.push_back(std::make_pair(resourceUrl, mimeKey));
m_textItem->document()->addResource(QTextDocument::ImageResource, resourceUrl, QVariant(image));
}
}
setPlainText(Cantor::JupyterUtils::getSource(cell));
m_textItem->document()->clearUndoRedoStacks();
}
QDomElement MarkdownEntry::toXml(QDomDocument& doc, KZip* 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);
QDomElement htmlEl = doc.createElement(QLatin1String("HTML"));
htmlEl.appendChild(doc.createTextNode(html));
el.appendChild(htmlEl);
QUrl url;
QString key;
for (const auto& data : attachedImages)
{
std::tie(url, key) = std::move(data);
QDomElement attachmentEl = doc.createElement(QLatin1String("Attachment"));
attachmentEl.setAttribute(QStringLiteral("url"), url.toString());
const QImage& image = m_textItem->document()->resource(QTextDocument::ImageResource, url).value<QImage>();
QByteArray ba;
QBuffer buffer(&ba);
buffer.open(QIODevice::WriteOnly);
image.save(&buffer, "PNG");
attachmentEl.appendChild(doc.createTextNode(QString::fromLatin1(ba.toBase64())));
el.appendChild(attachmentEl);
}
// If math rendered, then append result .pdf to archive
QTextCursor cursor = m_textItem->document()->find(QString(QChar::ObjectReplacementCharacter));
for (const auto& data : foundMath)
{
QDomElement mathEl = doc.createElement(QLatin1String("EmbeddedMath"));
mathEl.setAttribute(QStringLiteral("rendered"), data.second);
mathEl.appendChild(doc.createTextNode(data.first));
if (data.second)
{
bool foundNeededImage = false;
while(!cursor.isNull() && !foundNeededImage)
{
QTextImageFormat format=cursor.charFormat().toImageFormat();
if (format.hasProperty(Cantor::Renderer::CantorFormula))
{
const QString& latex = format.property(Cantor::Renderer::Code).toString();
const QString& delimiter = format.property(Cantor::Renderer::Delimiter).toString();
const QString& code = delimiter + latex + delimiter;
if (code == data.first)
{
const QUrl& url = QUrl::fromLocalFile(format.property(Cantor::Renderer::ImagePath).toString());
archive->addLocalFile(url.toLocalFile(), url.fileName());
mathEl.setAttribute(QStringLiteral("path"), url.fileName());
foundNeededImage = true;
}
}
cursor = m_textItem->document()->find(QString(QChar::ObjectReplacementCharacter), cursor);
}
}
el.appendChild(mathEl);
}
return el;
}
QJsonValue MarkdownEntry::toJupyterJson()
{
QJsonObject entry;
entry.insert(QLatin1String("cell_type"), QLatin1String("markdown"));
entry.insert(QLatin1String("metadata"), jupyterMetadata());
QJsonObject attachments;
QUrl url;
QString key;
for (const auto& data : attachedImages)
{
std::tie(url, key) = std::move(data);
const QImage& image = m_textItem->document()->resource(QTextDocument::ImageResource, url).value<QImage>();
QString attachmentKey = url.toString().remove(QLatin1String("attachment:"));
attachments.insert(attachmentKey, Cantor::JupyterUtils::packMimeBundle(image, key));
}
if (!attachments.isEmpty())
entry.insert(QLatin1String("attachments"), attachments);
Cantor::JupyterUtils::setSource(entry, plain);
return entry;
}
QString MarkdownEntry::toPlain(const QString& commandSep, const QString& commentStartingSeq, const QString& commentEndingSeq)
{
Q_UNUSED(commandSep);
if (commentStartingSeq.isEmpty())
return QString();
QString text(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)
{
if (m_textItem->toPlainText() == plain && !html.isEmpty())
{
setRenderedHtml(html);
rendered = true;
for (auto iter = foundMath.begin(); iter != foundMath.end(); iter++)
iter->second = false;
markUpMath();
}
else
{
plain = m_textItem->toPlainText();
rendered = renderMarkdown(plain);
}
m_textItem->document()->clearUndoRedoStacks();
}
if (rendered && worksheet()->embeddedMathEnabled())
renderMath();
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_LATEX | 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);
char *latexData;
int latexDataSize = mkd_latextext(mdHandle, &latexData);
QStringList latexUnits = QString::fromUtf8(latexData, latexDataSize).split(QLatin1Char(31), QString::SkipEmptyParts);
foundMath.clear();
mkd_cleanup(mdHandle);
setRenderedHtml(html);
QTextCursor cursor(m_textItem->document());
for (const QString& latex : latexUnits)
foundMath.push_back(std::make_pair(latex, false));
markUpMath();
return true;
#else
Q_UNUSED(plain);
return false;
#endif
}
void MarkdownEntry::updateEntry()
{
QTextCursor cursor = m_textItem->document()->find(QString(QChar::ObjectReplacementCharacter));
while(!cursor.isNull())
{
QTextImageFormat format=cursor.charFormat().toImageFormat();
if (format.hasProperty(Cantor::Renderer::CantorFormula))
worksheet()->mathRenderer()->rerender(m_textItem->document(), format);
cursor = m_textItem->document()->find(QString(QChar::ObjectReplacementCharacter), cursor);
}
}
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));
+ const qreal margin = worksheet()->isPrinting() ? 0 : RightMargin;
+
+ m_textItem->setGeometry(0, 0, w - margin);
+ setSize(QSizeF(m_textItem->width() + margin, 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)
{
if (rendered)
{
setPlainText(plain);
m_textItem->setCursorPosition(mouseEvent->pos());
m_textItem->textCursor().clearSelection();
rendered = false;
return true;
}
}
}
else if (event->type() == QEvent::KeyPress)
{
auto* key_event = static_cast<QKeyEvent*>(event);
if (key_event->matches(QKeySequence::Cancel))
{
setRenderedHtml(html);
for (auto iter = foundMath.begin(); iter != foundMath.end(); iter++)
iter->second = false;
rendered = true;
markUpMath();
if (worksheet()->embeddedMathEnabled())
renderMath();
return true;
}
}
}
return false;
}
bool MarkdownEntry::wantToEvaluate()
{
return !rendered;
}
void MarkdownEntry::setRenderedHtml(const QString& html)
{
m_textItem->setHtml(html);
m_textItem->denyEditing();
}
void MarkdownEntry::setPlainText(const QString& plain)
{
QTextDocument* doc = m_textItem->document();
doc->setPlainText(plain);
m_textItem->setDocument(doc);
m_textItem->allowEditing();
}
void MarkdownEntry::renderMath()
{
QTextCursor cursor(m_textItem->document());
for (int i = 0; i < (int)foundMath.size(); i++)
if (foundMath[i].second == false)
renderMathExpression(i+1, foundMath[i].first);
}
void MarkdownEntry::handleMathRender(QSharedPointer<MathRenderResult> result)
{
if (!result->successful)
{
if (Settings::self()->showMathRenderError())
KMessageBox::error(worksheetView(), result->errorMessage, i18n("Cantor Math Error"));
else
qDebug() << "MarkdownEntry: math render failed with message" << result->errorMessage;
return;
}
setRenderedMath(result->jobId, result->renderedMath, result->uniqueUrl, result->image);
}
void MarkdownEntry::renderMathExpression(int jobId, QString mathCode)
{
QString latex;
Cantor::LatexRenderer::EquationType type;
std::tie(latex, type) = parseMathCode(mathCode);
if (!latex.isNull())
worksheet()->mathRenderer()->renderExpression(jobId, latex, type, this, SLOT(handleMathRender(QSharedPointer<MathRenderResult>)));
}
std::pair<QString, Cantor::LatexRenderer::EquationType> MarkdownEntry::parseMathCode(QString mathCode)
{
static const QLatin1String inlineDelimiter("$");
static const QLatin1String displayedDelimiter("$$");
if (mathCode.startsWith(displayedDelimiter) && mathCode.endsWith(displayedDelimiter))
{
mathCode.remove(0, 2);
mathCode.chop(2);
if (mathCode[0] == QChar(6))
mathCode.remove(0, 1);
return std::make_pair(mathCode, Cantor::LatexRenderer::FullEquation);
}
else if (mathCode.startsWith(inlineDelimiter) && mathCode.endsWith(inlineDelimiter))
{
mathCode.remove(0, 1);
mathCode.chop(1);
if (mathCode[0] == QChar(6))
mathCode.remove(0, 1);
return std::make_pair(mathCode, Cantor::LatexRenderer::InlineEquation);
}
else if (mathCode.startsWith(QString::fromUtf8("\\begin{")) && mathCode.endsWith(QLatin1Char('}')))
{
if (mathCode[1] == QChar(6))
mathCode.remove(1, 1);
return std::make_pair(mathCode, Cantor::LatexRenderer::CustomEquation);
}
else
return std::make_pair(QString(), Cantor::LatexRenderer::InlineEquation);
}
void MarkdownEntry::setRenderedMath(int jobId, const QTextImageFormat& format, const QUrl& internal, const QImage& image)
{
if ((int)foundMath.size() < jobId)
return;
const auto& iter = foundMath.begin() + jobId-1;
QTextCursor cursor = findMath(jobId);
const QString delimiter = format.property(Cantor::Renderer::Delimiter).toString();
QString searchText = delimiter + format.property(Cantor::Renderer::Code).toString() + delimiter;
Cantor::LatexRenderer::EquationType type
= (Cantor::LatexRenderer::EquationType)format.intProperty(Cantor::Renderer::CantorFormula);
// From findMath we will be first symbol of math expression
// So in order to select all symbols of the expression, we need to go to previous symbol first
// But it working strange sometimes: some times we need to go to previous character, sometimes not
// So the code tests that we on '$' symbol and if it isn't true, then we revert back
cursor.movePosition(QTextCursor::PreviousCharacter);
bool withDollarDelimiter = type == Cantor::LatexRenderer::InlineEquation || type == Cantor::LatexRenderer::FullEquation;
if (withDollarDelimiter && m_textItem->document()->characterAt(cursor.position()) != QLatin1Char('$'))
cursor.movePosition(QTextCursor::NextCharacter);
else if (type == Cantor::LatexRenderer::CustomEquation && m_textItem->document()->characterAt(cursor.position()) != QLatin1Char('\\') )
cursor.movePosition(QTextCursor::NextCharacter);
cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, searchText.size());
if (!cursor.isNull())
{
m_textItem->document()->addResource(QTextDocument::ImageResource, internal, QVariant(image));
// Don't add new line for $$...$$ on document's begin and end
// And if we in block, which haven't non-space characters except out math expression
// In another sitation, Cantor will move rendered image into another QTextBlock
QTextCursor prevSymCursor = m_textItem->document()->find(QRegExp(QLatin1String("[^\\s]")), cursor, QTextDocument::FindBackward);
if (type == Cantor::LatexRenderer::FullEquation
&& cursor.selectionStart() != 0
&& prevSymCursor.block() == cursor.block()
)
{
cursor.insertBlock();
cursor.setPosition(prevSymCursor.position()+2, QTextCursor::KeepAnchor);
cursor.removeSelectedText();
}
cursor.insertText(QString(QChar::ObjectReplacementCharacter), format);
bool atDocEnd = cursor.position() == m_textItem->document()->characterCount()-1;
QTextCursor nextSymCursor = m_textItem->document()->find(QRegExp(QLatin1String("[^\\s]")), cursor);
if (type == Cantor::LatexRenderer::FullEquation && !atDocEnd && nextSymCursor.block() == cursor.block())
{
cursor.setPosition(nextSymCursor.position()-1, QTextCursor::KeepAnchor);
cursor.removeSelectedText();
cursor.insertBlock();
}
// Set that the formulas is rendered
iter->second = true;
m_textItem->document()->clearUndoRedoStacks();
}
}
QTextCursor MarkdownEntry::findMath(int id)
{
QTextCursor cursor(m_textItem->document());
do
{
QTextCharFormat format = cursor.charFormat();
if (format.intProperty(JobProperty) == id)
break;
}
while (cursor.movePosition(QTextCursor::NextCharacter));
return cursor;
}
void MarkdownEntry::markUpMath()
{
QTextCursor cursor(m_textItem->document());
for (int i = 0; i < (int)foundMath.size(); i++)
{
if (foundMath[i].second)
continue;
QString searchText = foundMath[i].first;
searchText.replace(QRegExp(QLatin1String("\\s+")), QLatin1String(" "));
cursor = m_textItem->document()->find(searchText, cursor);
// Mark up founded math code
QTextCharFormat format = cursor.charFormat();
// Use index+1 in math array as property tag
format.setProperty(JobProperty, i+1);
// We found the math expression, so remove 'marker' (ACII symbol 'Acknowledgement')
// The marker have been placed after "$" or "$$"
// We remove the marker, only if it presents
QString codeWithoutMarker = foundMath[i].first;
if (searchText.startsWith(QLatin1String("$$")))
{
if (codeWithoutMarker[2] == QChar(6))
codeWithoutMarker.remove(2, 1);
}
else if (searchText.startsWith(QLatin1String("$")))
{
if (codeWithoutMarker[1] == QChar(6))
codeWithoutMarker.remove(1, 1);
}
else if (searchText.startsWith(QLatin1String("\\")))
{
if (codeWithoutMarker[1] == QChar(6))
codeWithoutMarker.remove(1, 1);
}
cursor.insertText(codeWithoutMarker, format);
}
}
void MarkdownEntry::insertImage()
{
const QString& filename = QFileDialog::getOpenFileName(worksheet()->worksheetView(), i18n("Choose Image"), QString(), i18n("Images (*.png *.bmp *.jpg *.svg)"));
if (!filename.isEmpty())
{
QImageReader reader(filename);
const QImage img = reader.read();
if (!img.isNull())
{
const QString& name = QFileInfo(filename).fileName();
QUrl url;
url.setScheme(QLatin1String("attachment"));
url.setPath(name);
attachedImages.push_back(std::make_pair(url, QLatin1String("image/png")));
m_textItem->document()->addResource(QTextDocument::ImageResource, url, QVariant(img));
QTextCursor cursor = m_textItem->textCursor();
cursor.insertText(QString::fromLatin1("![%1](attachment:%1)").arg(name));
animateSizeChange();
}
else
KMessageBox::error(worksheetView(), i18n("Cantor failed to read image with error \"%1\"", reader.errorString()), i18n("Cantor"));
}
}
void MarkdownEntry::clearAttachments()
{
for (auto& attachment: attachedImages)
{
const QUrl& url = attachment.first;
m_textItem->document()->addResource(QTextDocument::ImageResource, url, QVariant());
}
attachedImages.clear();
animateSizeChange();
}
QString MarkdownEntry::plainText() const
{
return m_textItem->toPlainText();
}
diff --git a/src/pagebreakentry.cpp b/src/pagebreakentry.cpp
index 6a864dc3..c7b554ee 100644
--- a/src/pagebreakentry.cpp
+++ b/src/pagebreakentry.cpp
@@ -1,196 +1,198 @@
/*
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) 2012 Martin Kuettler <martin.kuettler@gmail.com>
*/
#include "pagebreakentry.h"
#include <QTextCursor>
#include <QTextCharFormat>
#include <QPalette>
#include <QJsonValue>
#include <QJsonObject>
#include <KColorScheme>
#include <KLocalizedString>
#include "lib/jupyterutils.h"
PageBreakEntry::PageBreakEntry(Worksheet* worksheet)
: WorksheetEntry(worksheet)
{
m_msgItem = new WorksheetTextItem(this);
QTextCursor cursor = m_msgItem->textCursor();
KColorScheme color = KColorScheme(QPalette::Normal, KColorScheme::View);
QTextCharFormat cformat(cursor.charFormat());
cformat.setForeground(color.foreground(KColorScheme::InactiveText));
cursor.insertText(i18n("--- Page Break ---"), cformat);
m_msgItem->setAlignment(Qt::AlignCenter);
setFlag(QGraphicsItem::ItemIsFocusable);
}
bool PageBreakEntry::isEmpty()
{
return false;
}
int PageBreakEntry::type() const
{
return Type;
}
void PageBreakEntry::populateMenu(QMenu* menu, QPointF pos)
{
WorksheetEntry::populateMenu(menu, pos);
}
bool PageBreakEntry::acceptRichText()
{
return false;
}
void PageBreakEntry::setContent(const QString& content)
{
Q_UNUSED(content);
return;
}
void PageBreakEntry::setContent(const QDomElement& content, const KZip& file)
{
Q_UNUSED(content);
Q_UNUSED(file);
return;
}
void PageBreakEntry::setContentFromJupyter(const QJsonObject& cell)
{
Q_UNUSED(cell);
return;
}
QJsonValue PageBreakEntry::toJupyterJson()
{
QJsonObject root;
root.insert(QLatin1String("cell_type"), QLatin1String("raw"));
QJsonObject metadata;
// "raw_mimetype" vs "format"?
// See https://github.com/jupyter/notebook/issues/4730
// For safety set both keys
metadata.insert(QLatin1String("format"), QLatin1String("text/latex"));
metadata.insert(QLatin1String("raw_mimetype"), QLatin1String("text/latex"));
QJsonObject cantor;
cantor.insert(QLatin1String("from_page_break"), true);
metadata.insert(Cantor::JupyterUtils::cantorMetadataKey, cantor);
root.insert(Cantor::JupyterUtils::metadataKey, metadata);
Cantor::JupyterUtils::setSource(root, QLatin1String("\\pagebreak"));
return root;
}
QDomElement PageBreakEntry::toXml(QDomDocument& doc, KZip* archive)
{
Q_UNUSED(archive);
QDomElement pgbrk = doc.createElement(QLatin1String("PageBreak"));
return pgbrk;
}
QString PageBreakEntry::toPlain(const QString& commandSep, const QString& commentStartingSeq, const QString& commentEndingSeq)
{
Q_UNUSED(commandSep);
return commentStartingSeq + QLatin1String("page break") + commentEndingSeq;
}
void PageBreakEntry::interruptEvaluation()
{
return;
}
void PageBreakEntry::layOutForWidth(qreal w, bool force)
{
if (size().width() == w && !force)
return;
+ const qreal margin = worksheet()->isPrinting() ? 0 : RightMargin;
+
if (m_msgItem->isVisible()) {
- m_msgItem->setGeometry(0, 0, w, true);
+ m_msgItem->setGeometry(0, 0, w - margin, true);
- setSize(QSizeF(m_msgItem->width(), m_msgItem->height() + VerticalMargin));
+ setSize(QSizeF(m_msgItem->width() + margin, m_msgItem->height() + VerticalMargin));
} else {
setSize(QSizeF(w, 0));
}
}
bool PageBreakEntry::evaluate(EvaluationOption evalOp)
{
evaluateNext(evalOp);
return true;
}
void PageBreakEntry::updateEntry()
{
if (worksheet()->isPrinting()) {
m_msgItem->setVisible(false);
recalculateSize();
} else if (!m_msgItem->isVisible()) {
m_msgItem->setVisible(true);
recalculateSize();
}
return;
}
/*
void PageBreakEntry::paint(QPainter* painter, const QStyleOptionGraphicsItem*,
QWidget*)
{
if (worksheet()->isPrinting()) {
QPaintDevice* device = painter->paintEngine()->paintDevice();
QPrinter* printer = qobject_cast<QPrinter*>(device);
if (printer)
printer->newPage();
}
}
*/
bool PageBreakEntry::wantToEvaluate()
{
return false;
}
bool PageBreakEntry::wantFocus()
{
return false;
}
bool PageBreakEntry::isConvertableToPageBreakEntry(const QJsonObject& cell)
{
if (!Cantor::JupyterUtils::isRawCell(cell))
return false;
QJsonObject metadata = Cantor::JupyterUtils::getCantorMetadata(cell);
QJsonValue value = metadata.value(QLatin1String("from_page_break"));
return value.isBool() && value.toBool() == true;
}
diff --git a/src/placeholderentry.cpp b/src/placeholderentry.cpp
index 781f71cc..156f5d72 100644
--- a/src/placeholderentry.cpp
+++ b/src/placeholderentry.cpp
@@ -1,126 +1,127 @@
/*
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) 2012 Martin Kuettler <martin.kuettler@gmail.com>
*/
#include "placeholderentry.h"
#include <QPropertyAnimation>
#include <QJsonObject>
PlaceHolderEntry::PlaceHolderEntry(Worksheet* worksheet, QSizeF s)
: WorksheetEntry(worksheet)
{
+ m_controlElement.hide();
setSize(s);
}
int PlaceHolderEntry::type() const
{
return Type;
}
bool PlaceHolderEntry::isEmpty()
{
/*
// This is counter-intuitive. isEmpty() is used to find out whether a new
// CommandEntry needs to be appended, and a PlaceHolderEntry should never
// prevent that.
return false;
*/
return true;
}
bool PlaceHolderEntry::acceptRichText()
{
return false;
}
void PlaceHolderEntry::setContent(const QString&)
{
}
void PlaceHolderEntry::setContent(const QDomElement&, const KZip&)
{
}
void PlaceHolderEntry::setContentFromJupyter(const QJsonObject& cell)
{
Q_UNUSED(cell);
return;
}
QJsonValue PlaceHolderEntry::toJupyterJson()
{
return QJsonValue();
}
QDomElement PlaceHolderEntry::toXml(QDomDocument&, KZip*)
{
return QDomElement();
}
QString PlaceHolderEntry::toPlain(const QString&, const QString&, const QString&){
return QString();
}
void PlaceHolderEntry::interruptEvaluation()
{
return;
}
void PlaceHolderEntry::layOutForWidth(qreal w, bool force)
{
Q_UNUSED(w);
Q_UNUSED(force);
}
bool PlaceHolderEntry::evaluate(EvaluationOption evalOp)
{
evaluateNext(evalOp);
return true;
}
void PlaceHolderEntry::updateEntry()
{
}
bool PlaceHolderEntry::wantToEvaluate()
{
return false;
}
void PlaceHolderEntry::changeSize(QSizeF s)
{
if (!worksheet()->animationsEnabled()) {
setSize(s);
worksheet()->updateEntrySize(this);
return;
}
if (aboutToBeRemoved())
return;
if (animationActive())
endAnimation();
QPropertyAnimation* sizeAn = sizeChangeAnimation(s);
sizeAn->setEasingCurve(QEasingCurve::InOutQuad);
sizeAn->start(QAbstractAnimation::DeleteWhenStopped);
}
diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt
index 12f70566..5aa088a6 100644
--- a/src/test/CMakeLists.txt
+++ b/src/test/CMakeLists.txt
@@ -1,72 +1,73 @@
set(worksheettest_SRCS
../worksheet.cpp
../worksheetview.cpp
../worksheetentry.cpp
../worksheettextitem.cpp
../worksheetimageitem.cpp
../commandentry.cpp
../textentry.cpp
../markdownentry.cpp
../pagebreakentry.cpp
../imageentry.cpp
../latexentry.cpp
../placeholderentry.cpp
../worksheetcursor.cpp
../searchbar.cpp
../actionbar.cpp
../worksheettoolbutton.cpp
../imagesettingsdialog.cpp
../scripteditor/scripteditorwidget.cpp
../resultitem.cpp
../textresultitem.cpp
../imageresultitem.cpp
../animationresultitem.cpp
../loadedexpression.cpp
../animation.cpp
../mathrender.cpp
../mathrendertask.cpp
../extended_document.cpp
+ ../worksheetcontrolitem.cpp
worksheet_test.cpp)
ki18n_wrap_ui(worksheettest_SRCS ../imagesettings.ui)
ki18n_wrap_ui(worksheettest_SRCS ../standardsearchbar.ui)
ki18n_wrap_ui(worksheettest_SRCS ../extendedsearchbar.ui)
kconfig_add_kcfg_files(worksheettest_SRCS ../settings.kcfgc)
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/data)
configure_file("data/Lecture-2B-Single-Atom-Lasing.ipynb" data COPYONLY)
configure_file("data/AEC.04 - Evolutionary Strategies and Covariance Matrix Adaptation.ipynb" data COPYONLY)
configure_file("data/Population_Genetics.ipynb" data COPYONLY)
configure_file("data/A Reaction-Diffusion Equation Solver in Python with Numpy.ipynb" data COPYONLY)
configure_file("data/Automata and Computability using Jupyter.ipynb" data COPYONLY)
configure_file("data/Cue Combination with Neural Populations .ipynb" data COPYONLY)
configure_file("data/Transformation2D.ipynb" data COPYONLY)
configure_file("data/TestMarkdownAttachment.ipynb" data COPYONLY)
configure_file("data/TestEntryLoad1.ipynb" data COPYONLY)
configure_file("data/TestEntryLoad2.ipynb" data COPYONLY)
configure_file("data/TestResultsLoad.ipynb" data COPYONLY)
configure_file("data/TestNotebookWithJson.ipynb" data COPYONLY)
configure_file("data/TestNotebookWithModJson.ipynb" data COPYONLY)
set(PATH_TO_TEST_NOTEBOOKS ${CMAKE_CURRENT_BINARY_DIR}/data)
configure_file (config-cantor-test.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-cantor-test.h )
add_executable( testworksheet ${worksheettest_SRCS})
add_test(NAME testworksheet COMMAND testworksheet)
target_link_libraries( testworksheet
cantorlibs
cantor_config
Qt5::Test
Qt5::PrintSupport
Qt5::Xml
${Qt5XmlPatterns_LIBRARIES}
KF5::TextEditor
Poppler::Qt5
)
if(LIBSPECTRE_FOUND)
target_link_libraries(testworksheet ${LIBSPECTRE_LIBRARY})
endif(LIBSPECTRE_FOUND)
if(Discount_FOUND)
target_link_libraries(testworksheet Discount::Lib)
endif(Discount_FOUND)
diff --git a/src/textentry.cpp b/src/textentry.cpp
index 453f89ce..f4a0681e 100644
--- a/src/textentry.cpp
+++ b/src/textentry.cpp
@@ -1,596 +1,598 @@
/*
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) 2009 Alexander Rieder <alexanderrieder@gmail.com>
Copyright (C) 2012 Martin Kuettler <martin.kuettler@gmail.com>
*/
#include "textentry.h"
#include "worksheettextitem.h"
#include "lib/renderer.h"
#include "latexrenderer.h"
#include "lib/jupyterutils.h"
#include "mathrender.h"
#include "settings.h"
#include <QScopedPointer>
#include <QGraphicsLinearLayout>
#include <QJsonValue>
#include <QJsonObject>
#include <QJsonArray>
#include <QDebug>
#include <KLocalizedString>
#include <KColorScheme>
#include <QStringList>
#include <QInputDialog>
QStringList standartRawCellTargetNames = {QLatin1String("None"), QLatin1String("LaTeX"), QLatin1String("reST"), QLatin1String("HTML"), QLatin1String("Markdown")};
QStringList standartRawCellTargetMimes = {QString(), QLatin1String("text/latex"), QLatin1String("text/restructuredtext"), QLatin1String("text/html"), QLatin1String("text/markdown")};
TextEntry::TextEntry(Worksheet* worksheet) : WorksheetEntry(worksheet)
, m_rawCell(false)
, m_convertTarget()
, m_targetActionGroup(nullptr)
, m_ownTarget{nullptr}
, m_targetMenu(nullptr)
, m_textItem(new WorksheetTextItem(this, Qt::TextEditorInteraction))
{
m_textItem->enableRichText(true);
connect(m_textItem, &WorksheetTextItem::moveToPrevious, this, &TextEntry::moveToPreviousEntry);
connect(m_textItem, &WorksheetTextItem::moveToNext, this, &TextEntry::moveToNextEntry);
// Modern syntax of signal/stots don't work on this connection (arguments don't match)
connect(m_textItem, SIGNAL(execute()), this, SLOT(evaluate()));
connect(m_textItem, &WorksheetTextItem::doubleClick, this, &TextEntry::resolveImagesAtCursor);
// Init raw cell target menus
// This used only for raw cells, but removing and creating this on conversion more complex
// that just create them always
m_targetActionGroup= new QActionGroup(this);
m_targetActionGroup->setExclusive(true);
connect(m_targetActionGroup, &QActionGroup::triggered, this, &TextEntry::convertTargetChanged);
m_targetMenu = new QMenu(i18n("Raw Cell Targets"));
for (const QString& key : standartRawCellTargetNames)
{
QAction* action = new QAction(key, m_targetActionGroup);
action->setCheckable(true);
m_targetMenu->addAction(action);
}
m_ownTarget = new QAction(i18n("Add custom target"), m_targetActionGroup);
m_ownTarget->setCheckable(true);
m_targetMenu->addAction(m_ownTarget);
}
TextEntry::~TextEntry()
{
m_targetMenu->deleteLater();
}
void TextEntry::populateMenu(QMenu* menu, QPointF pos)
{
if (m_rawCell)
{
menu->addAction(i18n("Convert to Text Entry"), this, &TextEntry::convertToTextEntry);
menu->addMenu(m_targetMenu);
}
else
{
menu->addAction(i18n("Convert to Raw Cell"), this, &TextEntry::convertToRawCell);
bool imageSelected = false;
QTextCursor cursor = m_textItem->textCursor();
const QChar repl = QChar::ObjectReplacementCharacter;
if (cursor.hasSelection())
{
QString selection = m_textItem->textCursor().selectedText();
imageSelected = selection.contains(repl);
}
else
{
// we need to try both the current cursor and the one after the that
cursor = m_textItem->cursorForPosition(pos);
for (int i = 2; i; --i)
{
int p = cursor.position();
if (m_textItem->document()->characterAt(p-1) == repl &&
cursor.charFormat().hasProperty(Cantor::Renderer::CantorFormula))
{
m_textItem->setTextCursor(cursor);
imageSelected = true;
break;
}
cursor.movePosition(QTextCursor::NextCharacter);
}
}
if (imageSelected)
{
menu->addAction(i18n("Show LaTeX code"), this, SLOT(resolveImagesAtCursor()));
}
}
menu->addSeparator();
WorksheetEntry::populateMenu(menu, pos);
}
bool TextEntry::isEmpty()
{
return m_textItem->document()->isEmpty();
}
int TextEntry::type() const
{
return Type;
}
bool TextEntry::acceptRichText()
{
return true;
}
bool TextEntry::focusEntry(int pos, qreal xCoord)
{
if (aboutToBeRemoved())
return false;
m_textItem->setFocusAt(pos, xCoord);
return true;
}
void TextEntry::setContent(const QString& content)
{
m_textItem->setPlainText(content);
}
void TextEntry::setContent(const QDomElement& content, const KZip& file)
{
Q_UNUSED(file);
if(content.firstChildElement(QLatin1String("body")).isNull())
return;
if (content.hasAttribute(QLatin1String("convertTarget")))
{
convertToRawCell();
m_convertTarget = content.attribute(QLatin1String("convertTarget"));
// Set current action status
int idx = standartRawCellTargetMimes.indexOf(m_convertTarget);
if (idx != -1)
m_targetMenu->actions()[idx]->setChecked(true);
else
addNewTarget(m_convertTarget);
}
else
convertToTextEntry();
QDomDocument doc = QDomDocument();
QDomNode n = doc.importNode(content.firstChildElement(QLatin1String("body")), true);
doc.appendChild(n);
QString html = doc.toString();
m_textItem->setHtml(html);
}
void TextEntry::setContentFromJupyter(const QJsonObject& cell)
{
if (Cantor::JupyterUtils::isRawCell(cell))
{
convertToRawCell();
const QJsonObject& metadata = Cantor::JupyterUtils::getMetadata(cell);
QJsonValue format = metadata.value(QLatin1String("format"));
// Also checks "raw_mimetype", because raw cell don't corresponds Jupyter Notebook specification
// See https://github.com/jupyter/notebook/issues/4730
if (format.isUndefined())
format = metadata.value(QLatin1String("raw_mimetype"));
m_convertTarget = format.toString(QString());
// Set current action status
int idx = standartRawCellTargetMimes.indexOf(m_convertTarget);
if (idx != -1)
m_targetMenu->actions()[idx]->setChecked(true);
else
addNewTarget(m_convertTarget);
m_textItem->setPlainText(Cantor::JupyterUtils::getSource(cell));
setJupyterMetadata(metadata);
}
else if (Cantor::JupyterUtils::isMarkdownCell(cell))
{
convertToTextEntry();
QJsonObject cantorMetadata = Cantor::JupyterUtils::getCantorMetadata(cell);
m_textItem->setHtml(cantorMetadata.value(QLatin1String("text_entry_content")).toString());
}
}
QJsonValue TextEntry::toJupyterJson()
{
// Simple logic:
// If convertTarget is empty, it's user maded cell and we convert it to a markdown
// If convertTarget set, it's raw cell from Jupyter and we convert it to Jupyter cell
QTextDocument* doc = m_textItem->document()->clone();
QTextCursor cursor = doc->find(QString(QChar::ObjectReplacementCharacter));
while(!cursor.isNull())
{
QTextCharFormat format = cursor.charFormat();
if (format.hasProperty(Cantor::Renderer::CantorFormula))
{
showLatexCode(cursor);
}
cursor = m_textItem->document()->find(QString(QChar::ObjectReplacementCharacter), cursor);
}
QJsonObject metadata(jupyterMetadata());
QString entryData;
QString entryType;
if (!m_rawCell)
{
entryType = QLatin1String("markdown");
// Add raw text of entry to metadata, for situation when
// Cantor opens .ipynb converted from our .cws format
QJsonObject cantorMetadata;
if (Settings::storeTextEntryFormatting())
{
entryData = doc->toHtml();
// Remove DOCTYPE from html
entryData.remove(QRegExp(QLatin1String("<!DOCTYPE[^>]*>\\n")));
cantorMetadata.insert(QLatin1String("text_entry_content"), entryData);
}
else
entryData = doc->toPlainText();
metadata.insert(Cantor::JupyterUtils::cantorMetadataKey, cantorMetadata);
// Replace our $$ formulas to $
entryData.replace(QLatin1String("$$"), QLatin1String("$"));
}
else
{
entryType = QLatin1String("raw");
metadata.insert(QLatin1String("format"), m_convertTarget);
entryData = doc->toPlainText();
}
QJsonObject entry;
entry.insert(QLatin1String("cell_type"), entryType);
entry.insert(QLatin1String("metadata"), metadata);
Cantor::JupyterUtils::setSource(entry, entryData);
return entry;
}
QDomElement TextEntry::toXml(QDomDocument& doc, KZip* archive)
{
Q_UNUSED(archive);
QScopedPointer<QTextDocument> document(m_textItem->document()->clone());
//make sure that the latex code is shown instead of the rendered formulas
QTextCursor cursor = document->find(QString(QChar::ObjectReplacementCharacter));
while(!cursor.isNull())
{
QTextCharFormat format = cursor.charFormat();
if (format.hasProperty(Cantor::Renderer::CantorFormula))
showLatexCode(cursor);
cursor = document->find(QString(QChar::ObjectReplacementCharacter), cursor);
}
const QString& html = document->toHtml();
QDomElement el = doc.createElement(QLatin1String("Text"));
QDomDocument myDoc = QDomDocument();
myDoc.setContent(html);
el.appendChild(myDoc.documentElement().firstChildElement(QLatin1String("body")));
if (m_rawCell)
el.setAttribute(QLatin1String("convertTarget"), m_convertTarget);
return el;
}
QString TextEntry::toPlain(const QString& commandSep, const QString& commentStartingSeq, const QString& commentEndingSeq)
{
Q_UNUSED(commandSep);
if (commentStartingSeq.isEmpty())
return QString();
/*
// would this be plain enough?
QTextCursor cursor = m_textItem->textCursor();
cursor.movePosition(QTextCursor::Start);
cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);
QString text = m_textItem->resolveImages(cursor);
text.replace(QChar::ParagraphSeparator, '\n');
text.replace(QChar::LineSeparator, '\n');
*/
QString text = m_textItem->toPlainText();
if (!commentEndingSeq.isEmpty())
return commentStartingSeq + text + commentEndingSeq + QLatin1String("\n");
return commentStartingSeq + text.replace(QLatin1String("\n"), QLatin1String("\n") + commentStartingSeq) + QLatin1String("\n");
}
void TextEntry::interruptEvaluation()
{
}
bool TextEntry::evaluate(EvaluationOption evalOp)
{
int i = 0;
if (worksheet()->embeddedMathEnabled() && !m_rawCell)
{
// Render math in $$...$$ via Latex
QTextCursor cursor = findLatexCode();
while (!cursor.isNull())
{
QString latexCode = cursor.selectedText();
qDebug()<<"found latex: " << latexCode;
latexCode.remove(0, 2);
latexCode.remove(latexCode.length() - 2, 2);
latexCode.replace(QChar::ParagraphSeparator, QLatin1Char('\n'));
latexCode.replace(QChar::LineSeparator, QLatin1Char('\n'));
MathRenderer* renderer = worksheet()->mathRenderer();
renderer->renderExpression(++i, latexCode, Cantor::LatexRenderer::InlineEquation, this, SLOT(handleMathRender(QSharedPointer<MathRenderResult>)));
cursor = findLatexCode(cursor);
}
}
evaluateNext(evalOp);
return true;
}
void TextEntry::updateEntry()
{
qDebug() << "update Entry";
QTextCursor cursor = m_textItem->document()->find(QString(QChar::ObjectReplacementCharacter));
while(!cursor.isNull())
{
QTextImageFormat format=cursor.charFormat().toImageFormat();
if (format.hasProperty(Cantor::Renderer::CantorFormula))
worksheet()->mathRenderer()->rerender(m_textItem->document(), format);
cursor = m_textItem->document()->find(QString(QChar::ObjectReplacementCharacter), cursor);
}
}
void TextEntry::resolveImagesAtCursor()
{
QTextCursor cursor = m_textItem->textCursor();
if (!cursor.hasSelection())
cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor);
cursor.insertText(m_textItem->resolveImages(cursor));
}
QTextCursor TextEntry::findLatexCode(const QTextCursor& cursor) const
{
QTextDocument *doc = m_textItem->document();
QTextCursor startCursor;
if (cursor.isNull())
startCursor = doc->find(QLatin1String("$$"));
else
startCursor = doc->find(QLatin1String("$$"), cursor);
if (startCursor.isNull())
return startCursor;
const QTextCursor endCursor = doc->find(QLatin1String("$$"), startCursor);
if (endCursor.isNull())
return endCursor;
startCursor.setPosition(startCursor.selectionStart());
startCursor.setPosition(endCursor.position(), QTextCursor::KeepAnchor);
return startCursor;
}
QString TextEntry::showLatexCode(QTextCursor& cursor)
{
QString latexCode = cursor.charFormat().property(Cantor::Renderer::Code).toString();
cursor.deletePreviousChar();
latexCode = QLatin1String("$$") + latexCode + QLatin1String("$$");
cursor.insertText(latexCode);
return latexCode;
}
int TextEntry::searchText(const QString& text, const QString& pattern,
QTextDocument::FindFlags qt_flags)
{
Qt::CaseSensitivity caseSensitivity;
if (qt_flags & QTextDocument::FindCaseSensitively)
caseSensitivity = Qt::CaseSensitive;
else
caseSensitivity = Qt::CaseInsensitive;
int position;
if (qt_flags & QTextDocument::FindBackward)
position = text.lastIndexOf(pattern, -1, caseSensitivity);
else
position = text.indexOf(pattern, 0, caseSensitivity);
return position;
}
WorksheetCursor TextEntry::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);
int position = 0;
QTextCursor latexCursor;
QString latex;
if (flags & WorksheetEntry::SearchLaTeX) {
const QString repl = QString(QChar::ObjectReplacementCharacter);
latexCursor = m_textItem->search(repl, qt_flags, pos);
while (!latexCursor.isNull()) {
latex = m_textItem->resolveImages(latexCursor);
position = searchText(latex, pattern, qt_flags);
if (position >= 0) {
break;
}
WorksheetCursor c(this, m_textItem, latexCursor);
latexCursor = m_textItem->search(repl, qt_flags, c);
}
}
if (latexCursor.isNull()) {
if (textCursor.isNull())
return WorksheetCursor();
else
return WorksheetCursor(this, m_textItem, textCursor);
} else {
if (textCursor.isNull() || latexCursor < textCursor) {
int start = latexCursor.selectionStart();
latexCursor.insertText(latex);
QTextCursor c = m_textItem->textCursor();
c.setPosition(start + position);
c.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor,
pattern.length());
return WorksheetCursor(this, m_textItem, c);
} else {
return WorksheetCursor(this, m_textItem, textCursor);
}
}
}
void TextEntry::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));
+ const qreal margin = worksheet()->isPrinting() ? 0 : RightMargin;
+
+ m_textItem->setGeometry(0, 0, w - margin);
+ setSize(QSizeF(0 + m_textItem->width() + margin, m_textItem->height() + VerticalMargin));
}
bool TextEntry::wantToEvaluate()
{
return !findLatexCode().isNull();
}
bool TextEntry::isConvertableToTextEntry(const QJsonObject& cell)
{
if (!Cantor::JupyterUtils::isMarkdownCell(cell))
return false;
QJsonObject cantorMetadata = Cantor::JupyterUtils::getCantorMetadata(cell);
const QJsonValue& textContentValue = cantorMetadata.value(QLatin1String("text_entry_content"));
if (!textContentValue.isString())
return false;
const QString& textContent = textContentValue.toString();
const QString& source = Cantor::JupyterUtils::getSource(cell);
return textContent == source;
}
void TextEntry::handleMathRender(QSharedPointer<MathRenderResult> result)
{
if (!result->successful)
{
qDebug() << "TextEntry: math render failed with message" << result->errorMessage;
return;
}
const QString& code = result->renderedMath.property(Cantor::Renderer::Code).toString();
const QString& delimiter = QLatin1String("$$");
QTextCursor cursor = m_textItem->document()->find(delimiter + code + delimiter);
if (!cursor.isNull())
{
m_textItem->document()->addResource(QTextDocument::ImageResource, result->uniqueUrl, QVariant(result->image));
result->renderedMath.setProperty(Cantor::Renderer::Delimiter, QLatin1String("$$"));
cursor.insertText(QString(QChar::ObjectReplacementCharacter), result->renderedMath);
}
}
void TextEntry::convertToRawCell()
{
m_rawCell = true;
m_targetMenu->actions().at(0)->setChecked(true);
KColorScheme scheme = KColorScheme(QPalette::Normal, KColorScheme::View);
m_textItem->setBackgroundColor(scheme.background(KColorScheme::AlternateBackground).color());
// Resolve all latex inserts
QTextCursor cursor(m_textItem->document());
cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);
cursor.insertText(m_textItem->resolveImages(cursor));
}
void TextEntry::convertToTextEntry()
{
m_rawCell = false;
m_convertTarget.clear();
KColorScheme scheme = KColorScheme(QPalette::Normal, KColorScheme::View);
m_textItem->setBackgroundColor(scheme.background(KColorScheme::NormalBackground).color());
}
void TextEntry::convertTargetChanged(QAction* action)
{
int index = standartRawCellTargetNames.indexOf(action->text());
if (index != -1)
{
m_convertTarget = standartRawCellTargetMimes[index];
}
else if (action == m_ownTarget)
{
bool ok;
const QString& target = QInputDialog::getText(worksheet()->worksheetView(), i18n("Cantor"), i18n("Target MIME type:"), QLineEdit::Normal, QString(), &ok);
if (ok && !target.isEmpty())
{
addNewTarget(target);
m_convertTarget = target;
}
}
else
{
m_convertTarget = action->text();
}
}
void TextEntry::addNewTarget(const QString& target)
{
QAction* action = new QAction(target, m_targetActionGroup);
action->setCheckable(true);
action->setChecked(true);
m_targetMenu->insertAction(m_targetMenu->actions().last(), action);
}
QString TextEntry::text() const
{
return m_textItem->toPlainText();
}
diff --git a/src/worksheet.cpp b/src/worksheet.cpp
index 95ee89fe..3f403678 100644
--- a/src/worksheet.cpp
+++ b/src/worksheet.cpp
@@ -1,2361 +1,2480 @@
/*
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) 2009 Alexander Rieder <alexanderrieder@gmail.com>
Copyright (C) 2012 Martin Kuettler <martin.kuettler@gmail.com>
*/
#include "worksheet.h"
#include <QtGlobal>
#include <QApplication>
#include <QBuffer>
#include <QDebug>
#include <QDrag>
#include <QGraphicsWidget>
#include <QPrinter>
#include <QTimer>
#include <QXmlQuery>
#include <QJsonArray>
#include <QJsonDocument>
#include <KMessageBox>
#include <KActionCollection>
#include <KFontAction>
#include <KFontSizeAction>
#include <KToggleAction>
#include <KLocalizedString>
#include "settings.h"
#include "commandentry.h"
#include "textentry.h"
#include "markdownentry.h"
#include "latexentry.h"
#include "imageentry.h"
#include "pagebreakentry.h"
#include "placeholderentry.h"
#include "lib/jupyterutils.h"
#include "lib/backend.h"
#include "lib/extension.h"
#include "lib/helpresult.h"
#include "lib/session.h"
#include "lib/defaulthighlighter.h"
#include <config-cantor.h>
const double Worksheet::LeftMargin = 4;
const double Worksheet::RightMargin = 4;
const double Worksheet::TopMargin = 12;
const double Worksheet::EntryCursorLength = 30;
const double Worksheet::EntryCursorWidth = 2;
Worksheet::Worksheet(Cantor::Backend* backend, QWidget* parent, bool useDeafultWorksheetParameters)
: QGraphicsScene(parent)
{
m_session = nullptr;
m_highlighter = nullptr;
m_firstEntry = nullptr;
m_lastEntry = nullptr;
m_lastFocusedTextItem = nullptr;
m_dragEntry = nullptr;
m_placeholderEntry = nullptr;
m_dragScrollTimer = nullptr;
m_choosenCursorEntry = nullptr;
m_isCursorEntryAfterLastEntry = false;
m_useDefaultWorksheetParameters = useDeafultWorksheetParameters;
m_viewWidth = 0;
m_maxWidth = 0;
m_entryCursorItem = addLine(0,0,0,0);
const QColor& color = (palette().color(QPalette::Base).lightness() < 128) ? Qt::white : Qt::black;
QPen pen(color);
pen.setWidth(EntryCursorWidth);
m_entryCursorItem->setPen(pen);
m_entryCursorItem->hide();
m_cursorItemTimer = new QTimer(this);
connect(m_cursorItemTimer, &QTimer::timeout, this, &Worksheet::animateEntryCursor);
m_cursorItemTimer->start(500);
m_jupyterMetadata = nullptr;
if (backend)
initSession(backend);
}
Worksheet::~Worksheet()
{
// This is necessary, because a SearchBar might access firstEntry()
// while the scene is deleted. Maybe there is a better solution to
// this problem, but I can't seem to find it.
m_firstEntry = nullptr;
//disconnect from everything, no need to react on session status changes
//in the logout() when deleting the worksheet
disconnect(m_session, nullptr, nullptr, nullptr);
if (m_session && m_session->status() != Cantor::Session::Disable)
m_session->logout();
if (m_session)
{
disconnect(m_session, nullptr, nullptr, nullptr);
if (m_session->status() != Cantor::Session::Disable)
m_session->logout();
m_session->deleteLater();
}
if (m_jupyterMetadata)
delete m_jupyterMetadata;
}
void Worksheet::loginToSession()
{
m_session->login();
#ifdef WITH_EPS
if (Cantor::LatexRenderer::isLatexAvailable())
session()->setTypesettingEnabled(Settings::self()->typesetDefault());
else
session()->setTypesettingEnabled(false);
#else
session()->setTypesettingEnabled(false);
#endif
}
void Worksheet::print(QPrinter* printer)
{
m_epsRenderer.useHighResolution(true);
m_mathRenderer.useHighResolution(true);
m_isPrinting = true;
QRect pageRect = printer->pageRect();
qreal scale = 1; // todo: find good scale for page size
// todo: use epsRenderer()->scale() for printing ?
const qreal width = pageRect.width()/scale;
const qreal height = pageRect.height()/scale;
setViewSize(width, height, scale, true);
QPainter painter(printer);
painter.scale(scale, scale);
painter.setRenderHint(QPainter::Antialiasing);
WorksheetEntry* entry = firstEntry();
qreal y = TopMargin;
while (entry) {
qreal h = 0;
do {
if (entry->type() == PageBreakEntry::Type) {
entry = entry->next();
break;
}
h += entry->size().height();
entry = entry->next();
} while (entry && h + entry->size().height() <= height);
render(&painter, QRectF(0, 0, width, height),
QRectF(0, y, width, h));
y += h;
if (entry)
printer->newPage();
}
//render(&painter);
painter.end();
m_isPrinting = false;
m_epsRenderer.useHighResolution(false);
m_mathRenderer.useHighResolution(false);
m_epsRenderer.setScale(-1); // force update in next call to setViewSize,
worksheetView()->updateSceneSize(); // ... which happens in here
}
bool Worksheet::isPrinting()
{
return m_isPrinting;
}
void Worksheet::setViewSize(qreal w, qreal h, qreal s, bool forceUpdate)
{
Q_UNUSED(h);
m_viewWidth = w;
if (s != m_epsRenderer.scale() || forceUpdate) {
m_epsRenderer.setScale(s);
m_mathRenderer.setScale(s);
for (WorksheetEntry *entry = firstEntry(); entry; entry = entry->next())
entry->updateEntry();
}
updateLayout();
}
void Worksheet::updateLayout()
{
bool cursorRectVisible = false;
bool atEnd = worksheetView()->isAtEnd();
if (currentTextItem()) {
QRectF cursorRect = currentTextItem()->sceneCursorRect();
cursorRectVisible = worksheetView()->isVisible(cursorRect);
}
const qreal w = m_viewWidth - LeftMargin - RightMargin;
qreal y = TopMargin;
const qreal x = LeftMargin;
for (WorksheetEntry *entry = firstEntry(); entry; entry = entry->next())
y += entry->setGeometry(x, y, w);
setSceneRect(QRectF(0, 0, sceneRect().width(), y));
if (cursorRectVisible)
makeVisible(worksheetCursor());
else if (atEnd)
worksheetView()->scrollToEnd();
drawEntryCursor();
}
void Worksheet::updateEntrySize(WorksheetEntry* entry)
{
bool cursorRectVisible = false;
bool atEnd = worksheetView()->isAtEnd();
if (currentTextItem()) {
QRectF cursorRect = currentTextItem()->sceneCursorRect();
cursorRectVisible = worksheetView()->isVisible(cursorRect);
}
qreal y = entry->y() + entry->size().height();
for (entry = entry->next(); entry; entry = entry->next()) {
entry->setY(y);
y += entry->size().height();
}
setSceneRect(QRectF(0, 0, sceneRect().width(), y));
if (cursorRectVisible)
makeVisible(worksheetCursor());
else if (atEnd)
worksheetView()->scrollToEnd();
drawEntryCursor();
}
void Worksheet::setRequestedWidth(QGraphicsObject* object, qreal width)
{
qreal oldWidth = m_itemWidths[object];
m_itemWidths[object] = width;
if (width > m_maxWidth || oldWidth == m_maxWidth)
{
m_maxWidth = width;
qreal y = lastEntry() ? lastEntry()->size().height() + lastEntry()->y() : 0;
setSceneRect(QRectF(0, 0, m_maxWidth + LeftMargin + RightMargin, y));
}
}
void Worksheet::removeRequestedWidth(QGraphicsObject* object)
{
if (!m_itemWidths.contains(object))
return;
qreal width = m_itemWidths[object];
m_itemWidths.remove(object);
if (width == m_maxWidth)
{
m_maxWidth = 0;
for (qreal width : m_itemWidths.values())
if (width > m_maxWidth)
m_maxWidth = width;
qreal y = lastEntry() ? lastEntry()->size().height() + lastEntry()->y() : 0;
setSceneRect(QRectF(0, 0, m_maxWidth + LeftMargin + RightMargin, y));
}
}
bool Worksheet::isEmpty()
{
return !m_firstEntry;
}
bool Worksheet::isLoadingFromFile()
{
return m_isLoadingFromFile;
}
void Worksheet::makeVisible(WorksheetEntry* entry)
{
QRectF r = entry->boundingRect();
r = entry->mapRectToScene(r);
r.adjust(0, -10, 0, 10);
worksheetView()->makeVisible(r);
}
void Worksheet::makeVisible(const WorksheetCursor& cursor)
{
if (cursor.textCursor().isNull()) {
if (cursor.entry())
makeVisible(cursor.entry());
return;
}
QRectF r = cursor.textItem()->sceneCursorRect(cursor.textCursor());
QRectF er = cursor.entry()->boundingRect();
er = cursor.entry()->mapRectToScene(er);
er.adjust(0, -10, 0, 10);
r.adjust(0, qMax(qreal(-100.0), er.top() - r.top()),
0, qMin(qreal(100.0), er.bottom() - r.bottom()));
worksheetView()->makeVisible(r);
}
WorksheetView* Worksheet::worksheetView()
{
return qobject_cast<WorksheetView*>(views().first());
}
void Worksheet::setModified()
{
if (!m_isLoadingFromFile)
emit modified();
}
WorksheetCursor Worksheet::worksheetCursor()
{
WorksheetEntry* entry = currentEntry();
WorksheetTextItem* item = currentTextItem();
if (!entry || !item)
return WorksheetCursor();
return WorksheetCursor(entry, item, item->textCursor());
}
void Worksheet::setWorksheetCursor(const WorksheetCursor& cursor)
{
if (!cursor.isValid())
return;
if (m_lastFocusedTextItem)
m_lastFocusedTextItem->clearSelection();
m_lastFocusedTextItem = cursor.textItem();
cursor.textItem()->setTextCursor(cursor.textCursor());
}
WorksheetEntry* Worksheet::currentEntry()
{
// Entry cursor activate
if (m_choosenCursorEntry || m_isCursorEntryAfterLastEntry)
return nullptr;
QGraphicsItem* item = focusItem();
if (!item /*&& !hasFocus()*/)
item = m_lastFocusedTextItem;
/*else
m_focusItem = item;*/
while (item && (item->type() < QGraphicsItem::UserType ||
item->type() >= QGraphicsItem::UserType + 100))
item = item->parentItem();
if (item) {
WorksheetEntry* entry = qobject_cast<WorksheetEntry*>(item->toGraphicsObject());
if (entry && entry->aboutToBeRemoved()) {
if (entry->isAncestorOf(m_lastFocusedTextItem))
m_lastFocusedTextItem = nullptr;
return nullptr;
}
return entry;
}
return nullptr;
}
WorksheetEntry* Worksheet::firstEntry()
{
return m_firstEntry;
}
WorksheetEntry* Worksheet::lastEntry()
{
return m_lastEntry;
}
void Worksheet::setFirstEntry(WorksheetEntry* entry)
{
if (m_firstEntry)
disconnect(m_firstEntry, SIGNAL(aboutToBeDeleted()),
this, SLOT(invalidateFirstEntry()));
m_firstEntry = entry;
if (m_firstEntry)
connect(m_firstEntry, SIGNAL(aboutToBeDeleted()),
this, SLOT(invalidateFirstEntry()), Qt::DirectConnection);
}
void Worksheet::setLastEntry(WorksheetEntry* entry)
{
if (m_lastEntry)
disconnect(m_lastEntry, SIGNAL(aboutToBeDeleted()),
this, SLOT(invalidateLastEntry()));
m_lastEntry = entry;
if (m_lastEntry)
connect(m_lastEntry, SIGNAL(aboutToBeDeleted()),
this, SLOT(invalidateLastEntry()), Qt::DirectConnection);
}
void Worksheet::invalidateFirstEntry()
{
if (m_firstEntry)
setFirstEntry(m_firstEntry->next());
}
void Worksheet::invalidateLastEntry()
{
if (m_lastEntry)
setLastEntry(m_lastEntry->previous());
}
WorksheetEntry* Worksheet::entryAt(qreal x, qreal y)
{
QGraphicsItem* item = itemAt(x, y, QTransform());
while (item && (item->type() <= QGraphicsItem::UserType ||
item->type() >= QGraphicsItem::UserType + 100))
item = item->parentItem();
if (item)
return qobject_cast<WorksheetEntry*>(item->toGraphicsObject());
return nullptr;
}
WorksheetEntry* Worksheet::entryAt(QPointF p)
{
return entryAt(p.x(), p.y());
}
void Worksheet::focusEntry(WorksheetEntry *entry)
{
if (!entry)
return;
entry->focusEntry();
resetEntryCursor();
//bool rt = entry->acceptRichText();
//setActionsEnabled(rt);
//setAcceptRichText(rt);
//ensureCursorVisible();
}
void Worksheet::startDrag(WorksheetEntry* entry, QDrag* drag)
{
if (m_readOnly)
return;
resetEntryCursor();
m_dragEntry = entry;
WorksheetEntry* prev = entry->previous();
WorksheetEntry* next = entry->next();
m_placeholderEntry = new PlaceHolderEntry(this, entry->size());
m_placeholderEntry->setPrevious(prev);
m_placeholderEntry->setNext(next);
if (prev)
prev->setNext(m_placeholderEntry);
else
setFirstEntry(m_placeholderEntry);
if (next)
next->setPrevious(m_placeholderEntry);
else
setLastEntry(m_placeholderEntry);
m_dragEntry->hide();
Qt::DropAction action = drag->exec();
qDebug() << action;
if (action == Qt::MoveAction && m_placeholderEntry) {
qDebug() << "insert in new position";
prev = m_placeholderEntry->previous();
next = m_placeholderEntry->next();
}
m_dragEntry->setPrevious(prev);
m_dragEntry->setNext(next);
if (prev)
prev->setNext(m_dragEntry);
else
setFirstEntry(m_dragEntry);
if (next)
next->setPrevious(m_dragEntry);
else
setLastEntry(m_dragEntry);
m_dragEntry->show();
m_dragEntry->focusEntry();
const QPointF scenePos = worksheetView()->sceneCursorPos();
if (entryAt(scenePos) != m_dragEntry)
m_dragEntry->hideActionBar();
updateLayout();
if (m_placeholderEntry) {
m_placeholderEntry->setPrevious(nullptr);
m_placeholderEntry->setNext(nullptr);
m_placeholderEntry->hide();
m_placeholderEntry->deleteLater();
m_placeholderEntry = nullptr;
}
m_dragEntry = nullptr;
}
void Worksheet::evaluate()
{
qDebug()<<"evaluate worksheet";
if (!m_readOnly && m_session && m_session->status() == Cantor::Session::Disable)
loginToSession();
firstEntry()->evaluate(WorksheetEntry::EvaluateNext);
setModified();
}
void Worksheet::evaluateCurrentEntry()
{
if (!m_readOnly && m_session && m_session->status() == Cantor::Session::Disable)
loginToSession();
WorksheetEntry* entry = currentEntry();
if(!entry)
return;
entry->evaluateCurrentItem();
}
bool Worksheet::completionEnabled()
{
return m_completionEnabled;
}
void Worksheet::showCompletion()
{
WorksheetEntry* current = currentEntry();
if (current)
current->showCompletion();
}
WorksheetEntry* Worksheet::appendEntry(const int type, bool focus)
{
WorksheetEntry* entry = WorksheetEntry::create(type, this);
if (entry)
{
qDebug() << "Entry Appended";
entry->setPrevious(lastEntry());
if (lastEntry())
lastEntry()->setNext(entry);
if (!firstEntry())
setFirstEntry(entry);
setLastEntry(entry);
if (!m_isLoadingFromFile)
{
updateLayout();
if (focus)
{
makeVisible(entry);
focusEntry(entry);
}
setModified();
}
}
return entry;
}
WorksheetEntry* Worksheet::appendCommandEntry()
{
return appendEntry(CommandEntry::Type);
}
WorksheetEntry* Worksheet::appendTextEntry()
{
return appendEntry(TextEntry::Type);
}
WorksheetEntry* Worksheet::appendMarkdownEntry()
{
return appendEntry(MarkdownEntry::Type);
}
WorksheetEntry* Worksheet::appendPageBreakEntry()
{
return appendEntry(PageBreakEntry::Type);
}
WorksheetEntry* Worksheet::appendImageEntry()
{
return appendEntry(ImageEntry::Type);
}
WorksheetEntry* Worksheet::appendLatexEntry()
{
return appendEntry(LatexEntry::Type);
}
void Worksheet::appendCommandEntry(const QString& text)
{
WorksheetEntry* entry = lastEntry();
if(!entry->isEmpty())
{
entry = appendCommandEntry();
}
if (entry)
{
focusEntry(entry);
entry->setContent(text);
evaluateCurrentEntry();
}
}
WorksheetEntry* Worksheet::insertEntry(const int type, WorksheetEntry* current)
{
if (!current)
current = currentEntry();
if (!current)
return appendEntry(type);
WorksheetEntry *next = current->next();
WorksheetEntry *entry = nullptr;
if (!next || next->type() != type || !next->isEmpty())
{
entry = WorksheetEntry::create(type, this);
entry->setPrevious(current);
entry->setNext(next);
current->setNext(entry);
if (next)
next->setPrevious(entry);
else
setLastEntry(entry);
updateLayout();
setModified();
} else {
entry = next;
}
focusEntry(entry);
makeVisible(entry);
return entry;
}
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);
}
WorksheetEntry* Worksheet::insertImageEntry(WorksheetEntry* current)
{
return insertEntry(ImageEntry::Type, current);
}
WorksheetEntry* Worksheet::insertPageBreakEntry(WorksheetEntry* current)
{
return insertEntry(PageBreakEntry::Type, current);
}
WorksheetEntry* Worksheet::insertLatexEntry(WorksheetEntry* current)
{
return insertEntry(LatexEntry::Type, current);
}
void Worksheet::insertCommandEntry(const QString& text)
{
WorksheetEntry* entry = insertCommandEntry();
if(entry&&!text.isNull())
{
entry->setContent(text);
evaluateCurrentEntry();
}
}
WorksheetEntry* Worksheet::insertEntryBefore(int type, WorksheetEntry* current)
{
if (!current)
current = currentEntry();
if (!current)
return nullptr;
WorksheetEntry *prev = current->previous();
WorksheetEntry *entry = nullptr;
if(!prev || prev->type() != type || !prev->isEmpty())
{
entry = WorksheetEntry::create(type, this);
entry->setNext(current);
entry->setPrevious(prev);
current->setPrevious(entry);
if (prev)
prev->setNext(entry);
else
setFirstEntry(entry);
updateLayout();
setModified();
}
else
entry = prev;
focusEntry(entry);
return entry;
}
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);
}
WorksheetEntry* Worksheet::insertPageBreakEntryBefore(WorksheetEntry* current)
{
return insertEntryBefore(PageBreakEntry::Type, current);
}
WorksheetEntry* Worksheet::insertImageEntryBefore(WorksheetEntry* current)
{
return insertEntryBefore(ImageEntry::Type, current);
}
WorksheetEntry* Worksheet::insertLatexEntryBefore(WorksheetEntry* current)
{
return insertEntryBefore(LatexEntry::Type, current);
}
void Worksheet::interrupt()
{
if (m_session->status() == Cantor::Session::Running)
{
m_session->interrupt();
emit updatePrompt();
}
}
void Worksheet::interruptCurrentEntryEvaluation()
{
currentEntry()->interruptEvaluation();
}
void Worksheet::highlightItem(WorksheetTextItem* item)
{
if (!m_highlighter)
return;
QTextDocument *oldDocument = m_highlighter->document();
QList<QVector<QTextLayout::FormatRange> > formats;
if (oldDocument)
{
for (QTextBlock b = oldDocument->firstBlock();
b.isValid(); b = b.next())
{
formats.append(b.layout()->formats());
}
}
// Not every highlighter is a Cantor::DefaultHighligther (e.g. the
// highlighter for KAlgebra)
Cantor::DefaultHighlighter* hl = qobject_cast<Cantor::DefaultHighlighter*>(m_highlighter);
if (hl) {
hl->setTextItem(item);
} else {
m_highlighter->setDocument(item->document());
}
if (oldDocument)
{
QTextCursor cursor(oldDocument);
cursor.beginEditBlock();
for (QTextBlock b = oldDocument->firstBlock();
b.isValid(); b = b.next())
{
b.layout()->setFormats(formats.first());
formats.pop_front();
}
cursor.endEditBlock();
}
}
void Worksheet::rehighlight()
{
if(m_highlighter)
{
// highlight every entry
WorksheetEntry* entry;
for (entry = firstEntry(); entry; entry = entry->next()) {
WorksheetTextItem* item = entry->highlightItem();
if (!item)
continue;
highlightItem(item);
m_highlighter->rehighlight();
}
entry = currentEntry();
WorksheetTextItem* textitem = entry ? entry->highlightItem() : nullptr;
if (textitem && textitem->hasFocus())
highlightItem(textitem);
} else
{
// remove highlighting from entries
WorksheetEntry* entry;
for (entry = firstEntry(); entry; entry = entry->next()) {
WorksheetTextItem* item = entry->highlightItem();
if (!item)
continue;
QTextCursor cursor(item->document());
cursor.beginEditBlock();
for (QTextBlock b = item->document()->firstBlock();
b.isValid(); b = b.next())
{
b.layout()->clearFormats();
}
cursor.endEditBlock();
}
update();
}
}
void Worksheet::enableHighlighting(bool highlight)
{
if(highlight)
{
if(m_highlighter)
m_highlighter->deleteLater();
if (!m_readOnly)
m_highlighter=session()->syntaxHighlighter(this);
else
m_highlighter=nullptr;
if(!m_highlighter)
m_highlighter=new Cantor::DefaultHighlighter(this);
connect(m_highlighter, SIGNAL(rulesChanged()), this, SLOT(rehighlight()));
}else
{
if(m_highlighter)
m_highlighter->deleteLater();
m_highlighter=nullptr;
}
rehighlight();
}
void Worksheet::enableCompletion(bool enable)
{
m_completionEnabled=enable;
}
Cantor::Session* Worksheet::session()
{
return m_session;
}
bool Worksheet::isRunning()
{
return m_session && m_session->status()==Cantor::Session::Running;
}
bool Worksheet::isReadOnly()
{
return m_readOnly;
}
bool Worksheet::showExpressionIds()
{
return m_showExpressionIds;
}
bool Worksheet::animationsEnabled()
{
return m_animationsEnabled;
}
void Worksheet::enableAnimations(bool enable)
{
m_animationsEnabled = enable;
}
bool Worksheet::embeddedMathEnabled()
{
return m_embeddedMathEnabled && m_mathRenderer.mathRenderAvailable();
}
void Worksheet::enableEmbeddedMath(bool enable)
{
m_embeddedMathEnabled = enable;
}
void Worksheet::enableExpressionNumbering(bool enable)
{
m_showExpressionIds=enable;
emit updatePrompt();
}
QDomDocument Worksheet::toXML(KZip* archive)
{
QDomDocument doc( QLatin1String("CantorWorksheet") );
QDomElement root=doc.createElement( QLatin1String("Worksheet") );
root.setAttribute(QLatin1String("backend"), (m_session ? m_session->backend()->name(): m_backendName));
doc.appendChild(root);
for( WorksheetEntry* entry = firstEntry(); entry; entry = entry->next())
{
QDomElement el = entry->toXml(doc, archive);
root.appendChild( el );
}
return doc;
}
QJsonDocument Worksheet::toJupyterJson()
{
QJsonDocument doc;
QJsonObject root;
QJsonObject metadata(m_jupyterMetadata ? *m_jupyterMetadata : QJsonObject());
QJsonObject kernalInfo;
if (m_session && m_session->backend())
kernalInfo = Cantor::JupyterUtils::getKernelspec(m_session->backend());
else
kernalInfo.insert(QLatin1String("name"), m_backendName);
metadata.insert(QLatin1String("kernelspec"), kernalInfo);
root.insert(QLatin1String("metadata"), metadata);
// Not sure, but it looks like we support nbformat version 4.5
root.insert(QLatin1String("nbformat"), 4);
root.insert(QLatin1String("nbformat_minor"), 5);
QJsonArray cells;
for( WorksheetEntry* entry = firstEntry(); entry; entry = entry->next())
{
const QJsonValue entryJson = entry->toJupyterJson();
if (!entryJson.isNull())
cells.append(entryJson);
}
root.insert(QLatin1String("cells"), cells);
doc.setObject(root);
return doc;
}
void Worksheet::save( const QString& filename )
{
QFile file(filename);
if ( !file.open(QIODevice::WriteOnly) )
{
KMessageBox::error( worksheetView(),
i18n( "Cannot write file %1." , filename ),
i18n( "Error - Cantor" ));
return;
}
save(&file);
}
QByteArray Worksheet::saveToByteArray()
{
QBuffer buffer;
save(&buffer);
return buffer.buffer();
}
void Worksheet::save( QIODevice* device)
{
qDebug()<<"saving to filename";
switch (m_type)
{
case CantorWorksheet:
{
KZip zipFile( device );
if ( !zipFile.open(QIODevice::WriteOnly) )
{
KMessageBox::error( worksheetView(),
i18n( "Cannot write file." ),
i18n( "Error - Cantor" ));
return;
}
QByteArray content = toXML(&zipFile).toByteArray();
zipFile.writeFile( QLatin1String("content.xml"), content.data());
break;
}
case JupyterNotebook:
{
if (!device->isWritable())
{
KMessageBox::error( worksheetView(),
i18n( "Cannot write file." ),
i18n( "Error - Cantor" ));
return;
}
const QJsonDocument& doc = toJupyterJson();
device->write(doc.toJson(QJsonDocument::Indented));
break;
}
}
}
void Worksheet::savePlain(const QString& filename)
{
QFile file(filename);
if(!file.open(QIODevice::WriteOnly))
{
KMessageBox::error(worksheetView(), i18n("Error saving file %1", filename), i18n("Error - Cantor"));
return;
}
QString cmdSep=QLatin1String(";\n");
QString commentStartingSeq = QLatin1String("");
QString commentEndingSeq = QLatin1String("");
if (!m_readOnly)
{
Cantor::Backend * const backend=session()->backend();
if (backend->extensions().contains(QLatin1String("ScriptExtension")))
{
Cantor::ScriptExtension* e=dynamic_cast<Cantor::ScriptExtension*>(backend->extension(QLatin1String(("ScriptExtension"))));
if (e)
{
cmdSep=e->commandSeparator();
commentStartingSeq = e->commentStartingSequence();
commentEndingSeq = e->commentEndingSequence();
}
}
}
else
KMessageBox::information(worksheetView(), i18n("In read-only mode Cantor couldn't guarantee, that the export will be valid for %1", m_backendName), i18n("Cantor"));
QTextStream stream(&file);
for(WorksheetEntry * entry = firstEntry(); entry; entry = entry->next())
{
const QString& str=entry->toPlain(cmdSep, commentStartingSeq, commentEndingSeq);
if(!str.isEmpty())
stream << str + QLatin1Char('\n');
}
file.close();
}
void Worksheet::saveLatex(const QString& filename)
{
qDebug()<<"exporting to Latex: " <<filename;
QFile file(filename);
if(!file.open(QIODevice::WriteOnly))
{
KMessageBox::error(worksheetView(), i18n("Error saving file %1", filename), i18n("Error - Cantor"));
return;
}
QString xml = toXML().toString();
QTextStream stream(&file);
QXmlQuery query(QXmlQuery::XSLT20);
query.setFocus(xml);
QString stylesheet = QStandardPaths::locate(QStandardPaths::DataLocation, QLatin1String("xslt/latex.xsl"));
if (stylesheet.isEmpty())
{
KMessageBox::error(worksheetView(), i18n("Error loading latex.xsl stylesheet"), i18n("Error - Cantor"));
return;
}
query.setQuery(QUrl(stylesheet));
QString out;
if (query.evaluateTo(&out))
// Transform HTML escaped special characters to valid LaTeX characters (&, <, >)
stream << out.replace(QLatin1String("&amp;"), QLatin1String("&"))
.replace(QLatin1String("&gt;"), QLatin1String(">"))
.replace(QLatin1String("&lt;"), QLatin1String("<"));
file.close();
}
bool Worksheet::load(const QString& filename )
{
qDebug() << "loading worksheet" << filename;
QFile file(filename);
if (!file.open(QIODevice::ReadOnly)) {
KMessageBox::error(worksheetView(), i18n("Couldn't open the file %1.", filename), i18n("Open File"));
return false;
}
bool rc = load(&file);
if (rc && !m_readOnly)
m_session->setWorksheetPath(filename);
return rc;
}
void Worksheet::load(QByteArray* data)
{
QBuffer buf(data);
buf.open(QIODevice::ReadOnly);
load(&buf);
}
bool Worksheet::load(QIODevice* device)
{
if (!device->isReadable())
{
QApplication::restoreOverrideCursor();
KMessageBox::error(worksheetView(), i18n("Couldn't open the selected file for reading."), i18n("Open File"));
return false;
}
KZip archive(device);
if (archive.open(QIODevice::ReadOnly))
return loadCantorWorksheet(archive);
else
{
qDebug() <<"not a zip file";
// Go to begin of data, we need read all data in second time
device->seek(0);
QJsonParseError error;
const QJsonDocument& doc = QJsonDocument::fromJson(device->readAll(), &error);
if (error.error != QJsonParseError::NoError)
{
qDebug()<<"not a json file, parsing failed with error: " << error.errorString();
QApplication::restoreOverrideCursor();
KMessageBox::error(worksheetView(), i18n("The selected file is not a valid Cantor or Jupyter project file."), i18n("Open File"));
return false;
}
else
return loadJupyterNotebook(doc);
}
}
bool Worksheet::loadCantorWorksheet(const KZip& archive)
{
m_type = Type::CantorWorksheet;
const KArchiveEntry* contentEntry=archive.directory()->entry(QLatin1String("content.xml"));
if (!contentEntry->isFile())
{
qDebug()<<"content.xml file not found in the zip archive";
QApplication::restoreOverrideCursor();
KMessageBox::error(worksheetView(), i18n("The selected file is not a valid Cantor project file."), i18n("Open File"));
return false;
}
const KArchiveFile* content = static_cast<const KArchiveFile*>(contentEntry);
QByteArray data = content->data();
QDomDocument doc;
doc.setContent(data);
QDomElement root=doc.documentElement();
m_backendName=root.attribute(QLatin1String("backend"));
Cantor::Backend* b=Cantor::Backend::getBackend(m_backendName);
if (!b)
{
QApplication::restoreOverrideCursor();
KMessageBox::information(worksheetView(), i18n("%1 backend was not found. Editing and executing entries is not possible.", m_backendName), i18n("Open File"));
m_readOnly = true;
}
else
m_readOnly = false;
if(!m_readOnly && !b->isEnabled())
{
QApplication::restoreOverrideCursor();
KMessageBox::information(worksheetView(), i18n("There are some problems with the %1 backend,\n"\
"please check your configuration or install the needed packages.\n"
"You will only be able to view this worksheet.", m_backendName), i18n("Open File"));
m_readOnly = true;
}
if (m_readOnly)
{
// TODO: Handle this here?
for (QAction* action : m_richTextActionList)
action->setEnabled(false);
}
m_isLoadingFromFile = true;
//cleanup the worksheet and all it contains
delete m_session;
m_session=nullptr;
//file can only be loaded in a worksheet that was not edited/modified yet (s.a. CantorShell::load())
//in this case on the default "first entry" is available -> delete it.
if (m_firstEntry) {
delete m_firstEntry;
m_firstEntry = nullptr;
}
resetEntryCursor();
m_itemWidths.clear();
m_maxWidth = 0;
if (!m_readOnly)
initSession(b);
qDebug()<<"loading entries";
QDomElement expressionChild = root.firstChildElement();
WorksheetEntry* entry = nullptr;
while (!expressionChild.isNull()) {
QString tag = expressionChild.tagName();
// Don't add focus on load
if (tag == QLatin1String("Expression"))
{
entry = appendEntry(CommandEntry::Type, false);
entry->setContent(expressionChild, archive);
} else if (tag == QLatin1String("Text"))
{
entry = appendEntry(TextEntry::Type, false);
entry->setContent(expressionChild, archive);
} else if (tag == QLatin1String("Markdown"))
{
entry = appendEntry(MarkdownEntry::Type, false);
entry->setContent(expressionChild, archive);
} else if (tag == QLatin1String("Latex"))
{
entry = appendEntry(LatexEntry::Type, false);
entry->setContent(expressionChild, archive);
} else if (tag == QLatin1String("PageBreak"))
{
entry = appendEntry(PageBreakEntry::Type, false);
entry->setContent(expressionChild, archive);
}
else if (tag == QLatin1String("Image"))
{
entry = appendEntry(ImageEntry::Type, false);
entry->setContent(expressionChild, archive);
}
if (m_readOnly && entry)
{
entry->setAcceptHoverEvents(false);
entry = nullptr;
}
expressionChild = expressionChild.nextSiblingElement();
}
if (m_readOnly)
clearFocus();
m_isLoadingFromFile = false;
updateLayout();
//Set the Highlighting, depending on the current state
//If the session isn't logged in, use the default
enableHighlighting( m_highlighter!=nullptr || Settings::highlightDefault() );
emit loaded();
return true;
}
void Worksheet::initSession(Cantor::Backend* backend)
{
m_session = backend->createSession();
if (m_useDefaultWorksheetParameters)
{
enableHighlighting(Settings::self()->highlightDefault());
enableCompletion(Settings::self()->completionDefault());
enableExpressionNumbering(Settings::self()->expressionNumberingDefault());
enableAnimations(Settings::self()->animationDefault());
enableEmbeddedMath(Settings::self()->embeddedMathDefault());
}
}
bool Worksheet::loadJupyterNotebook(const QJsonDocument& doc)
{
m_type = Type::JupyterNotebook;
int nbformatMajor, nbformatMinor;
if (!Cantor::JupyterUtils::isJupyterNotebook(doc))
{
// Two possibilities: old jupyter notebook (version <= 4.0.0 and a another scheme) or just not a notebook at all
std::tie(nbformatMajor, nbformatMinor) = Cantor::JupyterUtils::getNbformatVersion(doc.object());
if (nbformatMajor == 0 && nbformatMinor == 0)
{
QApplication::restoreOverrideCursor();
showInvalidNotebookSchemeError();
}
else
{
KMessageBox::error(worksheetView(),
i18n("Jupyter notebooks with versions lower than 4.5 (detected version %1.%2) are not supported.", nbformatMajor, nbformatMinor ),
i18n("Open File"));
}
return false;
}
QJsonObject notebookObject = doc.object();
std::tie(nbformatMajor, nbformatMinor) = Cantor::JupyterUtils::getNbformatVersion(notebookObject);
if (QT_VERSION_CHECK(nbformatMajor, nbformatMinor, 0) > QT_VERSION_CHECK(4,5,0))
{
QApplication::restoreOverrideCursor();
KMessageBox::error(
worksheetView(),
i18n("Jupyter notebooks with versions higher than 4.5 (detected version %1.%2) are not supported.", nbformatMajor, nbformatMinor),
i18n("Open File")
);
return false;
}
const QJsonArray& cells = Cantor::JupyterUtils::getCells(notebookObject);
const QJsonObject& metadata = Cantor::JupyterUtils::getMetadata(notebookObject);
if (m_jupyterMetadata)
delete m_jupyterMetadata;
m_jupyterMetadata = new QJsonObject(metadata);
const QJsonObject& kernalspec = metadata.value(QLatin1String("kernelspec")).toObject();
m_backendName = Cantor::JupyterUtils::getKernelName(kernalspec);
if (kernalspec.isEmpty() || m_backendName.isEmpty())
{
QApplication::restoreOverrideCursor();
showInvalidNotebookSchemeError();
return false;
}
Cantor::Backend* backend = Cantor::Backend::getBackend(m_backendName);
if (!backend)
{
QApplication::restoreOverrideCursor();
KMessageBox::information(worksheetView(),
i18n("%1 backend was not found. Editing and executing entries is not possible.", m_backendName),
i18n("Open File"));
m_readOnly = true;
}
else
m_readOnly = false;
if(!m_readOnly && !backend->isEnabled())
{
QApplication::restoreOverrideCursor();
KMessageBox::information(worksheetView(), i18n("There are some problems with the %1 backend,\n"\
"please check your configuration or install the needed packages.\n"
"You will only be able to view this worksheet.", m_backendName), i18n("Open File"));
m_readOnly = true;
}
if (m_readOnly)
{
for (QAction* action : m_richTextActionList)
action->setEnabled(false);
}
m_isLoadingFromFile = true;
if (m_session)
delete m_session;
m_session = nullptr;
if (m_firstEntry) {
delete m_firstEntry;
m_firstEntry = nullptr;
}
resetEntryCursor();
m_itemWidths.clear();
m_maxWidth = 0;
if (!m_readOnly)
initSession(backend);
qDebug() << "loading jupyter entries";
WorksheetEntry* entry = nullptr;
for (QJsonArray::const_iterator iter = cells.begin(); iter != cells.end(); iter++) {
if (!Cantor::JupyterUtils::isJupyterCell(*iter))
{
QApplication::restoreOverrideCursor();
QString explanation;
if (iter->isObject())
explanation = i18n("an object with keys: %1", iter->toObject().keys().join(QLatin1String(", ")));
else
explanation = i18n("non object JSON value");
m_isLoadingFromFile = false;
showInvalidNotebookSchemeError(i18n("found incorrect data (%1) that is not Jupyter cell", explanation));
return false;
}
const QJsonObject& cell = iter->toObject();
QString cellType = Cantor::JupyterUtils::getCellType(cell);
if (cellType == QLatin1String("code"))
{
if (LatexEntry::isConvertableToLatexEntry(cell))
{
entry = appendEntry(LatexEntry::Type, false);
entry->setContentFromJupyter(cell);
entry->evaluate(WorksheetEntry::InternalEvaluation);
}
else
{
entry = appendEntry(CommandEntry::Type, false);
entry->setContentFromJupyter(cell);
}
}
else if (cellType == QLatin1String("markdown"))
{
if (TextEntry::isConvertableToTextEntry(cell))
{
entry = appendEntry(TextEntry::Type, false);
entry->setContentFromJupyter(cell);
}
else
{
entry = appendEntry(MarkdownEntry::Type, false);
entry->setContentFromJupyter(cell);
entry->evaluate(WorksheetEntry::InternalEvaluation);
}
}
else if (cellType == QLatin1String("raw"))
{
if (PageBreakEntry::isConvertableToPageBreakEntry(cell))
entry = appendEntry(PageBreakEntry::Type, false);
else
entry = appendEntry(TextEntry::Type, false);
entry->setContentFromJupyter(cell);
}
if (m_readOnly && entry)
{
entry->setAcceptHoverEvents(false);
entry = nullptr;
}
}
if (m_readOnly)
clearFocus();
m_isLoadingFromFile = false;
updateLayout();
enableHighlighting( m_highlighter!=nullptr || Settings::highlightDefault() );
emit loaded();
return true;
}
void Worksheet::showInvalidNotebookSchemeError(QString additionalInfo)
{
if (additionalInfo.isEmpty())
KMessageBox::error(worksheetView(), i18n("The file is not valid Jupyter notebook"), i18n("Open File"));
else
KMessageBox::error(worksheetView(), i18n("Invalid Jupyter notebook scheme: %1", additionalInfo), i18n("Open File"));
}
void Worksheet::gotResult(Cantor::Expression* expr)
{
if(expr==nullptr)
expr=qobject_cast<Cantor::Expression*>(sender());
if(expr==nullptr)
return;
//We're only interested in help results, others are handled by the WorksheetEntry
for (auto* result : expr->results())
{
if(result && result->type()==Cantor::HelpResult::Type)
{
QString help = result->toHtml();
//Do some basic LaTeX replacing
help.replace(QRegExp(QLatin1String("\\\\code\\{([^\\}]*)\\}")), QLatin1String("<b>\\1</b>"));
help.replace(QRegExp(QLatin1String("\\$([^\\$])\\$")), QLatin1String("<i>\\1</i>"));
emit showHelp(help);
//TODO: break after the first help result found, not clear yet how to handle multiple requests for help within one single command (e.g. ??ev;??int).
break;
}
}
}
void Worksheet::removeCurrentEntry()
{
qDebug()<<"removing current entry";
WorksheetEntry* entry=currentEntry();
if(!entry)
return;
// In case we just removed this
if (entry->isAncestorOf(m_lastFocusedTextItem))
m_lastFocusedTextItem = nullptr;
entry->startRemoving();
}
Cantor::Renderer* Worksheet::renderer()
{
return &m_epsRenderer;
}
MathRenderer* Worksheet::mathRenderer()
{
return &m_mathRenderer;
}
QMenu* Worksheet::createContextMenu()
{
QMenu *menu = new QMenu(worksheetView());
connect(menu, SIGNAL(aboutToHide()), menu, SLOT(deleteLater()));
return menu;
}
void Worksheet::populateMenu(QMenu *menu, QPointF pos)
{
- WorksheetEntry* entry = entryAt(pos);
- if (entry && !entry->isAncestorOf(m_lastFocusedTextItem)) {
- WorksheetTextItem* item =
- qgraphicsitem_cast<WorksheetTextItem*>(itemAt(pos, QTransform()));
- if (item && item->isEditable())
- m_lastFocusedTextItem = item;
- }
+ // Two menu: for particular entry and for selection (multiple entry)
+ if (m_selectedEntries.isEmpty())
+ {
+ WorksheetEntry* entry = entryAt(pos);
+ if (entry && !entry->isAncestorOf(m_lastFocusedTextItem)) {
+ WorksheetTextItem* item =
+ qgraphicsitem_cast<WorksheetTextItem*>(itemAt(pos, QTransform()));
+ if (item && item->isEditable())
+ m_lastFocusedTextItem = item;
+ }
- if (!isRunning())
- menu->addAction(QIcon::fromTheme(QLatin1String("system-run")), i18n("Evaluate Worksheet"),
- this, SLOT(evaluate()), 0);
+ if (!isRunning())
+ menu->addAction(QIcon::fromTheme(QLatin1String("system-run")), i18n("Evaluate Worksheet"),
+ this, SLOT(evaluate()), 0);
+ else
+ menu->addAction(QIcon::fromTheme(QLatin1String("process-stop")), i18n("Interrupt"), this,
+ SLOT(interrupt()), 0);
+ menu->addSeparator();
+
+ if (entry) {
+ QMenu* convertTo = new QMenu(menu);
+ QMenu* insert = new QMenu(menu);
+ QMenu* insertBefore = new QMenu(menu);
+
+ convertTo->addAction(QIcon::fromTheme(QLatin1String("run-build")), i18n("Command Entry"), entry, &WorksheetEntry::convertToCommandEntry);
+ convertTo->addAction(QIcon::fromTheme(QLatin1String("draw-text")), i18n("Text Entry"), entry, &WorksheetEntry::convertToTextEntry);
+ #ifdef Discount_FOUND
+ convertTo->addAction(QIcon::fromTheme(QLatin1String("text-x-markdown")), i18n("Markdown Entry"), entry, &WorksheetEntry::convertToMarkdownEntry);
+ #endif
+ #ifdef WITH_EPS
+ convertTo->addAction(QIcon::fromTheme(QLatin1String("text-x-tex")), i18n("LaTeX Entry"), entry, &WorksheetEntry::convertToLatexEntry);
+ #endif
+ convertTo->addAction(QIcon::fromTheme(QLatin1String("image-x-generic")), i18n("Image"), entry, &WorksheetEntry::convertToImageEntry);
+ convertTo->addAction(QIcon::fromTheme(QLatin1String("go-next-view-page")), i18n("Page Break"), entry, &WorksheetEntry::converToPageBreakEntry);
+
+ insert->addAction(QIcon::fromTheme(QLatin1String("run-build")), i18n("Command Entry"), entry, SLOT(insertCommandEntry()));
+ insert->addAction(QIcon::fromTheme(QLatin1String("draw-text")), i18n("Text Entry"), entry, SLOT(insertTextEntry()));
+ #ifdef Discount_FOUND
+ insert->addAction(QIcon::fromTheme(QLatin1String("text-x-markdown")), i18n("Markdown Entry"), entry, SLOT(insertMarkdownEntry()));
+ #endif
+ #ifdef WITH_EPS
+ insert->addAction(QIcon::fromTheme(QLatin1String("text-x-tex")), i18n("LaTeX Entry"), entry, SLOT(insertLatexEntry()));
+ #endif
+ insert->addAction(QIcon::fromTheme(QLatin1String("image-x-generic")), i18n("Image"), entry, SLOT(insertImageEntry()));
+ insert->addAction(QIcon::fromTheme(QLatin1String("go-next-view-page")), i18n("Page Break"), entry, SLOT(insertPageBreakEntry()));
+
+ insertBefore->addAction(QIcon::fromTheme(QLatin1String("run-build")), i18n("Command Entry"), entry, SLOT(insertCommandEntryBefore()));
+ insertBefore->addAction(QIcon::fromTheme(QLatin1String("draw-text")), i18n("Text Entry"), entry, SLOT(insertTextEntryBefore()));
+ #ifdef Discount_FOUND
+ insertBefore->addAction(QIcon::fromTheme(QLatin1String("text-x-markdown")), i18n("Markdown Entry"), entry, SLOT(insertMarkdownEntryBefore()));
+ #endif
+ #ifdef WITH_EPS
+ insertBefore->addAction(QIcon::fromTheme(QLatin1String("text-x-tex")), i18n("LaTeX Entry"), entry, SLOT(insertLatexEntryBefore()));
+ #endif
+ insertBefore->addAction(QIcon::fromTheme(QLatin1String("image-x-generic")), i18n("Image"), entry, SLOT(insertImageEntryBefore()));
+ insertBefore->addAction(QIcon::fromTheme(QLatin1String("go-next-view-page")), i18n("Page Break"), entry, SLOT(insertPageBreakEntryBefore()));
+
+ convertTo->setTitle(i18n("Convert Entry To"));
+ convertTo->setIcon(QIcon::fromTheme(QLatin1String("gtk-convert")));
+ insert->setTitle(i18n("Insert Entry After"));
+ insert->setIcon(QIcon::fromTheme(QLatin1String("edit-table-insert-row-below")));
+ insertBefore->setTitle(i18n("Insert Entry Before"));
+ insertBefore->setIcon(QIcon::fromTheme(QLatin1String("edit-table-insert-row-above")));
+
+ menu->addMenu(convertTo);
+ menu->addMenu(insert);
+ menu->addMenu(insertBefore);
+ } else {
+ menu->addAction(QIcon::fromTheme(QLatin1String("run-build")), i18n("Insert Command Entry"), this, SLOT(appendCommandEntry()));
+ menu->addAction(QIcon::fromTheme(QLatin1String("draw-text")), i18n("Insert Text Entry"), this, SLOT(appendTextEntry()));
+ #ifdef Discount_FOUND
+ menu->addAction(QIcon::fromTheme(QLatin1String("text-x-markdown")), i18n("Insert Markdown Entry"), this, SLOT(appendMarkdownEntry()));
+ #endif
+ #ifdef WITH_EPS
+ menu->addAction(QIcon::fromTheme(QLatin1String("text-x-tex")), i18n("Insert LaTeX Entry"), this, SLOT(appendLatexEntry()));
+ #endif
+ menu->addAction(QIcon::fromTheme(QLatin1String("image-x-generic")), i18n("Insert Image"), this, SLOT(appendImageEntry()));
+ menu->addAction(QIcon::fromTheme(QLatin1String("go-next-view-page")), i18n("Insert Page Break"), this, SLOT(appendPageBreakEntry()));
+ }
+ }
else
- menu->addAction(QIcon::fromTheme(QLatin1String("process-stop")), i18n("Interrupt"), this,
- SLOT(interrupt()), 0);
- menu->addSeparator();
-
- if (entry) {
- QMenu* convertTo = new QMenu(menu);
- QMenu* insert = new QMenu(menu);
- QMenu* insertBefore = new QMenu(menu);
-
- convertTo->addAction(QIcon::fromTheme(QLatin1String("run-build")), i18n("Command Entry"), entry, &WorksheetEntry::convertToCommandEntry);
- convertTo->addAction(QIcon::fromTheme(QLatin1String("draw-text")), i18n("Text Entry"), entry, &WorksheetEntry::convertToTextEntry);
-#ifdef Discount_FOUND
- convertTo->addAction(QIcon::fromTheme(QLatin1String("text-x-markdown")), i18n("Markdown Entry"), entry, &WorksheetEntry::convertToMarkdownEntry);
-#endif
-#ifdef WITH_EPS
- convertTo->addAction(QIcon::fromTheme(QLatin1String("text-x-tex")), i18n("LaTeX Entry"), entry, &WorksheetEntry::convertToLatexEntry);
-#endif
- convertTo->addAction(QIcon::fromTheme(QLatin1String("image-x-generic")), i18n("Image"), entry, &WorksheetEntry::convertToImageEntry);
- convertTo->addAction(QIcon::fromTheme(QLatin1String("go-next-view-page")), i18n("Page Break"), entry, &WorksheetEntry::converToPageBreakEntry);
-
- insert->addAction(QIcon::fromTheme(QLatin1String("run-build")), i18n("Command Entry"), entry, SLOT(insertCommandEntry()));
- insert->addAction(QIcon::fromTheme(QLatin1String("draw-text")), i18n("Text Entry"), entry, SLOT(insertTextEntry()));
-#ifdef Discount_FOUND
- insert->addAction(QIcon::fromTheme(QLatin1String("text-x-markdown")), i18n("Markdown Entry"), entry, SLOT(insertMarkdownEntry()));
-#endif
-#ifdef WITH_EPS
- insert->addAction(QIcon::fromTheme(QLatin1String("text-x-tex")), i18n("LaTeX Entry"), entry, SLOT(insertLatexEntry()));
-#endif
- insert->addAction(QIcon::fromTheme(QLatin1String("image-x-generic")), i18n("Image"), entry, SLOT(insertImageEntry()));
- insert->addAction(QIcon::fromTheme(QLatin1String("go-next-view-page")), i18n("Page Break"), entry, SLOT(insertPageBreakEntry()));
-
- insertBefore->addAction(QIcon::fromTheme(QLatin1String("run-build")), i18n("Command Entry"), entry, SLOT(insertCommandEntryBefore()));
- insertBefore->addAction(QIcon::fromTheme(QLatin1String("draw-text")), i18n("Text Entry"), entry, SLOT(insertTextEntryBefore()));
-#ifdef Discount_FOUND
- insertBefore->addAction(QIcon::fromTheme(QLatin1String("text-x-markdown")), i18n("Markdown Entry"), entry, SLOT(insertMarkdownEntryBefore()));
-#endif
-#ifdef WITH_EPS
- insertBefore->addAction(QIcon::fromTheme(QLatin1String("text-x-tex")), i18n("LaTeX Entry"), entry, SLOT(insertLatexEntryBefore()));
-#endif
- insertBefore->addAction(QIcon::fromTheme(QLatin1String("image-x-generic")), i18n("Image"), entry, SLOT(insertImageEntryBefore()));
- insertBefore->addAction(QIcon::fromTheme(QLatin1String("go-next-view-page")), i18n("Page Break"), entry, SLOT(insertPageBreakEntryBefore()));
-
- convertTo->setTitle(i18n("Convert Entry To"));
- convertTo->setIcon(QIcon::fromTheme(QLatin1String("gtk-convert")));
- insert->setTitle(i18n("Insert Entry After"));
- insert->setIcon(QIcon::fromTheme(QLatin1String("edit-table-insert-row-below")));
- insertBefore->setTitle(i18n("Insert Entry Before"));
- insertBefore->setIcon(QIcon::fromTheme(QLatin1String("edit-table-insert-row-above")));
-
- menu->addMenu(convertTo);
- menu->addMenu(insert);
- menu->addMenu(insertBefore);
- } else {
- menu->addAction(QIcon::fromTheme(QLatin1String("run-build")), i18n("Insert Command Entry"), this, SLOT(appendCommandEntry()));
- menu->addAction(QIcon::fromTheme(QLatin1String("draw-text")), i18n("Insert Text Entry"), this, SLOT(appendTextEntry()));
-#ifdef Discount_FOUND
- menu->addAction(QIcon::fromTheme(QLatin1String("text-x-markdown")), i18n("Insert Markdown Entry"), this, SLOT(appendMarkdownEntry()));
-#endif
-#ifdef WITH_EPS
- menu->addAction(QIcon::fromTheme(QLatin1String("text-x-tex")), i18n("Insert LaTeX Entry"), this, SLOT(appendLatexEntry()));
-#endif
- menu->addAction(QIcon::fromTheme(QLatin1String("image-x-generic")), i18n("Insert Image"), this, SLOT(appendImageEntry()));
- menu->addAction(QIcon::fromTheme(QLatin1String("go-next-view-page")), i18n("Insert Page Break"), this, SLOT(appendPageBreakEntry()));
+ {
+ menu->clear();
+ menu->addAction(QIcon::fromTheme(QLatin1String("go-up")), i18n("Move Entries Up"), this, SLOT(selectionMoveUp()), 0);
+ menu->addAction(QIcon::fromTheme(QLatin1String("go-down")), i18n("Move Entries Down"), this, SLOT(selectionMoveDown()), 0);
+ menu->addAction(QIcon::fromTheme(QLatin1String("media-playback-start")), i18n("Evaluate Entries"), this, SLOT(selectionEvaluate()), 0);
+ menu->addAction(QIcon::fromTheme(QLatin1String("edit-delete")), i18n("Remove Entries"), this, SLOT(selectionRemove()), 0);
}
}
void Worksheet::contextMenuEvent(QGraphicsSceneContextMenuEvent *event)
{
if (m_readOnly)
return;
// forward the event to the items
QGraphicsScene::contextMenuEvent(event);
if (!event->isAccepted()) {
event->accept();
QMenu *menu = createContextMenu();
populateMenu(menu, event->scenePos());
menu->popup(event->screenPos());
}
}
void Worksheet::mousePressEvent(QGraphicsSceneMouseEvent* event)
{
- QGraphicsScene::mousePressEvent(event);
/*
if (event->button() == Qt::LeftButton && !focusItem() && lastEntry() &&
event->scenePos().y() > lastEntry()->y() + lastEntry()->size().height())
lastEntry()->focusEntry(WorksheetTextItem::BottomRight);
*/
- if (!m_readOnly)
- updateEntryCursor(event);
+
+ if (!m_readOnly && event->buttons() & Qt::LeftButton)
+ {
+ WorksheetEntry* selectedEntry = entryAt(event->scenePos());
+ if (event->modifiers() & Qt::ControlModifier)
+ {
+ clearFocus();
+ resetEntryCursor();
+
+ if (selectedEntry)
+ {
+ selectedEntry->setCellSelected(!selectedEntry->isCellSelected());
+ selectedEntry->update();
+
+ WorksheetEntry* lastSelectedEntry = m_circularFocusBuffer.size() > 0 ? m_circularFocusBuffer.last() : nullptr;
+ if (lastSelectedEntry)
+ {
+ lastSelectedEntry->setCellSelected(!lastSelectedEntry->isCellSelected());
+ lastSelectedEntry->update();
+ m_circularFocusBuffer.clear();
+ }
+
+ for (WorksheetEntry* entry : {selectedEntry, lastSelectedEntry})
+ if (entry)
+ {
+ if (entry->isCellSelected())
+ m_selectedEntries.append(entry);
+ else if (!entry->isCellSelected())
+ m_selectedEntries.removeOne(entry);
+ }
+ }
+ }
+ else
+ {
+ for (WorksheetEntry* entry : m_selectedEntries)
+ {
+ if(isValidEntry(entry))
+ {
+ entry->setCellSelected(false);
+ entry->update();
+ }
+ }
+ m_selectedEntries.clear();
+
+ if (selectedEntry)
+ notifyEntryFocus(selectedEntry);
+
+ updateEntryCursor(event);
+ }
+ }
+ QGraphicsScene::mousePressEvent(event);
}
void Worksheet::keyPressEvent(QKeyEvent *keyEvent)
{
if (m_readOnly)
return;
// If we choose entry by entry cursor and press text button (not modifiers, for example, like Control)
if ((m_choosenCursorEntry || m_isCursorEntryAfterLastEntry) && !keyEvent->text().isEmpty())
addEntryFromEntryCursor();
QGraphicsScene::keyPressEvent(keyEvent);
}
void Worksheet::createActions(KActionCollection* collection)
{
// Mostly copied from KRichTextWidget::createActions(KActionCollection*)
// It would be great if this wasn't necessary.
// Text color
QAction * action;
/* This is "format-stroke-color" in KRichTextWidget */
action = new QAction(QIcon::fromTheme(QLatin1String("format-text-color")),
i18nc("@action", "Text &Color..."), collection);
action->setIconText(i18nc("@label text color", "Color"));
action->setPriority(QAction::LowPriority);
m_richTextActionList.append(action);
collection->addAction(QLatin1String("format_text_foreground_color"), action);
connect(action, SIGNAL(triggered()), this, SLOT(setTextForegroundColor()));
// Text color
action = new QAction(QIcon::fromTheme(QLatin1String("format-fill-color")),
i18nc("@action", "Text &Highlight..."), collection);
action->setPriority(QAction::LowPriority);
m_richTextActionList.append(action);
collection->addAction(QLatin1String("format_text_background_color"), action);
connect(action, SIGNAL(triggered()), this, SLOT(setTextBackgroundColor()));
// Font Family
m_fontAction = new KFontAction(i18nc("@action", "&Font"), collection);
m_richTextActionList.append(m_fontAction);
collection->addAction(QLatin1String("format_font_family"), m_fontAction);
connect(m_fontAction, SIGNAL(triggered(QString)), this,
SLOT(setFontFamily(QString)));
// Font Size
m_fontSizeAction = new KFontSizeAction(i18nc("@action", "Font &Size"),
collection);
m_richTextActionList.append(m_fontSizeAction);
collection->addAction(QLatin1String("format_font_size"), m_fontSizeAction);
connect(m_fontSizeAction, SIGNAL(fontSizeChanged(int)), this,
SLOT(setFontSize(int)));
// Bold
m_boldAction = new KToggleAction(QIcon::fromTheme(QLatin1String("format-text-bold")),
i18nc("@action boldify selected text", "&Bold"),
collection);
m_boldAction->setPriority(QAction::LowPriority);
QFont bold;
bold.setBold(true);
m_boldAction->setFont(bold);
m_richTextActionList.append(m_boldAction);
collection->addAction(QLatin1String("format_text_bold"), m_boldAction);
collection->setDefaultShortcut(m_boldAction, Qt::CTRL + Qt::Key_B);
connect(m_boldAction, SIGNAL(triggered(bool)), this, SLOT(setTextBold(bool)));
// Italic
m_italicAction = new KToggleAction(QIcon::fromTheme(QLatin1String("format-text-italic")),
i18nc("@action italicize selected text",
"&Italic"),
collection);
m_italicAction->setPriority(QAction::LowPriority);
QFont italic;
italic.setItalic(true);
m_italicAction->setFont(italic);
m_richTextActionList.append(m_italicAction);
collection->addAction(QLatin1String("format_text_italic"), m_italicAction);
collection->setDefaultShortcut(m_italicAction, Qt::CTRL + Qt::Key_I);
connect(m_italicAction, SIGNAL(triggered(bool)), this, SLOT(setTextItalic(bool)));
// Underline
m_underlineAction = new KToggleAction(QIcon::fromTheme(QLatin1String("format-text-underline")),
i18nc("@action underline selected text",
"&Underline"),
collection);
m_underlineAction->setPriority(QAction::LowPriority);
QFont underline;
underline.setUnderline(true);
m_underlineAction->setFont(underline);
m_richTextActionList.append(m_underlineAction);
collection->addAction(QLatin1String("format_text_underline"), m_underlineAction);
collection->setDefaultShortcut(m_underlineAction, Qt::CTRL + Qt::Key_U);
connect(m_underlineAction, SIGNAL(triggered(bool)), this, SLOT(setTextUnderline(bool)));
// Strike
m_strikeOutAction = new KToggleAction(QIcon::fromTheme(QLatin1String("format-text-strikethrough")),
i18nc("@action", "&Strike Out"),
collection);
m_strikeOutAction->setPriority(QAction::LowPriority);
m_richTextActionList.append(m_strikeOutAction);
collection->addAction(QLatin1String("format_text_strikeout"), m_strikeOutAction);
collection->setDefaultShortcut(m_strikeOutAction, Qt::CTRL + Qt::Key_L);
connect(m_strikeOutAction, SIGNAL(triggered(bool)), this, SLOT(setTextStrikeOut(bool)));
// Alignment
QActionGroup *alignmentGroup = new QActionGroup(this);
// Align left
m_alignLeftAction = new KToggleAction(QIcon::fromTheme(QLatin1String("format-justify-left")),
i18nc("@action", "Align &Left"),
collection);
m_alignLeftAction->setPriority(QAction::LowPriority);
m_alignLeftAction->setIconText(i18nc("@label left justify", "Left"));
m_richTextActionList.append(m_alignLeftAction);
collection->addAction(QLatin1String("format_align_left"), m_alignLeftAction);
connect(m_alignLeftAction, SIGNAL(triggered()), this,
SLOT(setAlignLeft()));
alignmentGroup->addAction(m_alignLeftAction);
// Align center
m_alignCenterAction = new KToggleAction(QIcon::fromTheme(QLatin1String("format-justify-center")),
i18nc("@action", "Align &Center"),
collection);
m_alignCenterAction->setPriority(QAction::LowPriority);
m_alignCenterAction->setIconText(i18nc("@label center justify", "Center"));
m_richTextActionList.append(m_alignCenterAction);
collection->addAction(QLatin1String("format_align_center"), m_alignCenterAction);
connect(m_alignCenterAction, SIGNAL(triggered()), this,
SLOT(setAlignCenter()));
alignmentGroup->addAction(m_alignCenterAction);
// Align right
m_alignRightAction = new KToggleAction(QIcon::fromTheme(QLatin1String("format-justify-right")),
i18nc("@action", "Align &Right"),
collection);
m_alignRightAction->setPriority(QAction::LowPriority);
m_alignRightAction->setIconText(i18nc("@label right justify", "Right"));
m_richTextActionList.append(m_alignRightAction);
collection->addAction(QLatin1String("format_align_right"), m_alignRightAction);
connect(m_alignRightAction, SIGNAL(triggered()), this,
SLOT(setAlignRight()));
alignmentGroup->addAction(m_alignRightAction);
// Align justify
m_alignJustifyAction = new KToggleAction(QIcon::fromTheme(QLatin1String("format-justify-fill")),
i18nc("@action", "&Justify"),
collection);
m_alignJustifyAction->setPriority(QAction::LowPriority);
m_alignJustifyAction->setIconText(i18nc("@label justify fill", "Justify"));
m_richTextActionList.append(m_alignJustifyAction);
collection->addAction(QLatin1String("format_align_justify"), m_alignJustifyAction);
connect(m_alignJustifyAction, SIGNAL(triggered()), this,
SLOT(setAlignJustify()));
alignmentGroup->addAction(m_alignJustifyAction);
/*
// List style
KSelectAction* selAction;
selAction = new KSelectAction(QIcon::fromTheme("format-list-unordered"),
i18nc("@title:menu", "List Style"),
collection);
QStringList listStyles;
listStyles << i18nc("@item:inmenu no list style", "None")
<< i18nc("@item:inmenu disc list style", "Disc")
<< i18nc("@item:inmenu circle list style", "Circle")
<< i18nc("@item:inmenu square list style", "Square")
<< i18nc("@item:inmenu numbered lists", "123")
<< i18nc("@item:inmenu lowercase abc lists", "abc")
<< i18nc("@item:inmenu uppercase abc lists", "ABC");
selAction->setItems(listStyles);
selAction->setCurrentItem(0);
action = selAction;
m_richTextActionList.append(action);
collection->addAction("format_list_style", action);
connect(action, SIGNAL(triggered(int)),
this, SLOT(_k_setListStyle(int)));
connect(action, SIGNAL(triggered()),
this, SLOT(_k_updateMiscActions()));
// Indent
action = new QAction(QIcon::fromTheme("format-indent-more"),
i18nc("@action", "Increase Indent"), collection);
action->setPriority(QAction::LowPriority);
m_richTextActionList.append(action);
collection->addAction("format_list_indent_more", action);
connect(action, SIGNAL(triggered()),
this, SLOT(indentListMore()));
connect(action, SIGNAL(triggered()),
this, SLOT(_k_updateMiscActions()));
// Dedent
action = new QAction(QIcon::fromTheme("format-indent-less"),
i18nc("@action", "Decrease Indent"), collection);
action->setPriority(QAction::LowPriority);
m_richTextActionList.append(action);
collection->addAction("format_list_indent_less", action);
connect(action, SIGNAL(triggered()), this, SLOT(indentListLess()));
connect(action, SIGNAL(triggered()), this, SLOT(_k_updateMiscActions()));
*/
}
WorksheetTextItem* Worksheet::lastFocusedTextItem()
{
return m_lastFocusedTextItem;
}
void Worksheet::updateFocusedTextItem(WorksheetTextItem* newItem)
{
// No need update and emit signals about editing actions in readonly
// So support only copy action and reset selection
if (m_readOnly)
{
if (m_lastFocusedTextItem && m_lastFocusedTextItem != newItem)
{
disconnect(this, SIGNAL(copy()), m_lastFocusedTextItem, SLOT(copy()));
m_lastFocusedTextItem->clearSelection();
}
if (newItem && m_lastFocusedTextItem != newItem)
{
connect(this, SIGNAL(copy()), newItem, SLOT(copy()));
emit copyAvailable(newItem->isCopyAvailable());
}
else if (!newItem)
{
emit copyAvailable(false);
}
m_lastFocusedTextItem = newItem;
return;
}
if (m_lastFocusedTextItem && m_lastFocusedTextItem != newItem) {
disconnect(m_lastFocusedTextItem, SIGNAL(undoAvailable(bool)),
this, SIGNAL(undoAvailable(bool)));
disconnect(m_lastFocusedTextItem, SIGNAL(redoAvailable(bool)),
this, SIGNAL(redoAvailable(bool)));
disconnect(this, SIGNAL(undo()), m_lastFocusedTextItem, SLOT(undo()));
disconnect(this, SIGNAL(redo()), m_lastFocusedTextItem, SLOT(redo()));
disconnect(m_lastFocusedTextItem, SIGNAL(cutAvailable(bool)),
this, SIGNAL(cutAvailable(bool)));
disconnect(m_lastFocusedTextItem, SIGNAL(copyAvailable(bool)),
this, SIGNAL(copyAvailable(bool)));
disconnect(m_lastFocusedTextItem, SIGNAL(pasteAvailable(bool)),
this, SIGNAL(pasteAvailable(bool)));
disconnect(this, SIGNAL(cut()), m_lastFocusedTextItem, SLOT(cut()));
disconnect(this, SIGNAL(copy()), m_lastFocusedTextItem, SLOT(copy()));
m_lastFocusedTextItem->clearSelection();
}
if (newItem && m_lastFocusedTextItem != newItem) {
setAcceptRichText(newItem->richTextEnabled());
emit undoAvailable(newItem->isUndoAvailable());
emit redoAvailable(newItem->isRedoAvailable());
connect(newItem, SIGNAL(undoAvailable(bool)),
this, SIGNAL(undoAvailable(bool)));
connect(newItem, SIGNAL(redoAvailable(bool)),
this, SIGNAL(redoAvailable(bool)));
connect(this, SIGNAL(undo()), newItem, SLOT(undo()));
connect(this, SIGNAL(redo()), newItem, SLOT(redo()));
emit cutAvailable(newItem->isCutAvailable());
emit copyAvailable(newItem->isCopyAvailable());
emit pasteAvailable(newItem->isPasteAvailable());
connect(newItem, SIGNAL(cutAvailable(bool)),
this, SIGNAL(cutAvailable(bool)));
connect(newItem, SIGNAL(copyAvailable(bool)),
this, SIGNAL(copyAvailable(bool)));
connect(newItem, SIGNAL(pasteAvailable(bool)),
this, SIGNAL(pasteAvailable(bool)));
connect(this, SIGNAL(cut()), newItem, SLOT(cut()));
connect(this, SIGNAL(copy()), newItem, SLOT(copy()));
} else if (!newItem) {
emit undoAvailable(false);
emit redoAvailable(false);
emit cutAvailable(false);
emit copyAvailable(false);
emit pasteAvailable(false);
}
m_lastFocusedTextItem = newItem;
}
/*!
* handles the paste action triggered in cantor_part.
* Pastes into the last focused text item.
* In case the "new entry"-cursor is currently shown,
* a new entry is created first which the content will be pasted into.
*/
void Worksheet::paste() {
if (m_choosenCursorEntry || m_isCursorEntryAfterLastEntry)
addEntryFromEntryCursor();
m_lastFocusedTextItem->paste();
}
void Worksheet::setRichTextInformation(const RichTextInfo& info)
{
m_boldAction->setChecked(info.bold);
m_italicAction->setChecked(info.italic);
m_underlineAction->setChecked(info.underline);
m_strikeOutAction->setChecked(info.strikeOut);
m_fontAction->setFont(info.font);
if (info.fontSize > 0)
m_fontSizeAction->setFontSize(info.fontSize);
if (info.align & Qt::AlignLeft)
m_alignLeftAction->setChecked(true);
else if (info.align & Qt::AlignCenter)
m_alignCenterAction->setChecked(true);
else if (info.align & Qt::AlignRight)
m_alignRightAction->setChecked(true);
else if (info.align & Qt::AlignJustify)
m_alignJustifyAction->setChecked(true);
}
void Worksheet::setAcceptRichText(bool b)
{
if (!m_readOnly)
for(QAction * action : m_richTextActionList)
action->setEnabled(b);
}
WorksheetTextItem* Worksheet::currentTextItem()
{
QGraphicsItem* item = focusItem();
if (!item)
item = m_lastFocusedTextItem;
while (item && item->type() != WorksheetTextItem::Type)
item = item->parentItem();
return qgraphicsitem_cast<WorksheetTextItem*>(item);
}
void Worksheet::setTextForegroundColor()
{
WorksheetTextItem* item = currentTextItem();
if (item)
item->setTextForegroundColor();
}
void Worksheet::setTextBackgroundColor()
{
WorksheetTextItem* item = currentTextItem();
if (item)
item->setTextBackgroundColor();
}
void Worksheet::setTextBold(bool b)
{
WorksheetTextItem* item = currentTextItem();
if (item)
item->setTextBold(b);
}
void Worksheet::setTextItalic(bool b)
{
WorksheetTextItem* item = currentTextItem();
if (item)
item->setTextItalic(b);
}
void Worksheet::setTextUnderline(bool b)
{
WorksheetTextItem* item = currentTextItem();
if (item)
item->setTextUnderline(b);
}
void Worksheet::setTextStrikeOut(bool b)
{
WorksheetTextItem* item = currentTextItem();
if (item)
item->setTextStrikeOut(b);
}
void Worksheet::setAlignLeft()
{
WorksheetTextItem* item = currentTextItem();
if (item)
item->setAlignment(Qt::AlignLeft);
}
void Worksheet::setAlignRight()
{
WorksheetTextItem* item = currentTextItem();
if (item)
item->setAlignment(Qt::AlignRight);
}
void Worksheet::setAlignCenter()
{
WorksheetTextItem* item = currentTextItem();
if (item)
item->setAlignment(Qt::AlignCenter);
}
void Worksheet::setAlignJustify()
{
WorksheetTextItem* item = currentTextItem();
if (item)
item->setAlignment(Qt::AlignJustify);
}
void Worksheet::setFontFamily(const QString& font)
{
WorksheetTextItem* item = currentTextItem();
if (item)
item->setFontFamily(font);
}
void Worksheet::setFontSize(int size)
{
WorksheetTextItem* item = currentTextItem();
if (item)
item->setFontSize(size);
}
bool Worksheet::isShortcut(const QKeySequence& sequence)
{
return m_shortcuts.contains(sequence);
}
void Worksheet::registerShortcut(QAction* action)
{
for (auto& shortcut : action->shortcuts())
m_shortcuts.insert(shortcut, action);
connect(action, SIGNAL(changed()), this, SLOT(updateShortcut()));
}
void Worksheet::updateShortcut()
{
QAction* action = qobject_cast<QAction*>(sender());
if (!action)
return;
// delete the old shortcuts of this action
QList<QKeySequence> shortcuts = m_shortcuts.keys(action);
for (auto& shortcut : shortcuts)
m_shortcuts.remove(shortcut);
// add the new shortcuts
for (auto& shortcut : action->shortcuts())
m_shortcuts.insert(shortcut, action);
}
void Worksheet::dragEnterEvent(QGraphicsSceneDragDropEvent* event)
{
qDebug() << "enter";
if (m_dragEntry)
event->accept();
else
QGraphicsScene::dragEnterEvent(event);
}
void Worksheet::dragLeaveEvent(QGraphicsSceneDragDropEvent* event)
{
if (!m_dragEntry) {
QGraphicsScene::dragLeaveEvent(event);
return;
}
qDebug() << "leave";
event->accept();
if (m_placeholderEntry) {
m_placeholderEntry->startRemoving();
m_placeholderEntry = nullptr;
}
}
void Worksheet::dragMoveEvent(QGraphicsSceneDragDropEvent* event)
{
if (!m_dragEntry) {
QGraphicsScene::dragMoveEvent(event);
return;
}
QPointF pos = event->scenePos();
WorksheetEntry* entry = entryAt(pos);
WorksheetEntry* prev = nullptr;
WorksheetEntry* next = nullptr;
if (entry) {
if (pos.y() < entry->y() + entry->size().height()/2) {
prev = entry->previous();
next = entry;
} else if (pos.y() >= entry->y() + entry->size().height()/2) {
prev = entry;
next = entry->next();
}
} else {
WorksheetEntry* last = lastEntry();
if (last && pos.y() > last->y() + last->size().height()) {
prev = last;
next = nullptr;
}
}
if (prev || next) {
PlaceHolderEntry* oldPlaceHolder = m_placeholderEntry;
if (prev && prev->type() == PlaceHolderEntry::Type &&
(!prev->aboutToBeRemoved() || prev->stopRemoving())) {
m_placeholderEntry = qgraphicsitem_cast<PlaceHolderEntry*>(prev);
m_placeholderEntry->changeSize(m_dragEntry->size());
} else if (next && next->type() == PlaceHolderEntry::Type &&
(!next->aboutToBeRemoved() || next->stopRemoving())) {
m_placeholderEntry = qgraphicsitem_cast<PlaceHolderEntry*>(next);
m_placeholderEntry->changeSize(m_dragEntry->size());
} else {
m_placeholderEntry = new PlaceHolderEntry(this, QSizeF(0,0));
m_placeholderEntry->setPrevious(prev);
m_placeholderEntry->setNext(next);
if (prev)
prev->setNext(m_placeholderEntry);
else
setFirstEntry(m_placeholderEntry);
if (next)
next->setPrevious(m_placeholderEntry);
else
setLastEntry(m_placeholderEntry);
m_placeholderEntry->changeSize(m_dragEntry->size());
}
if (oldPlaceHolder && oldPlaceHolder != m_placeholderEntry)
oldPlaceHolder->startRemoving();
updateLayout();
}
const QPoint viewPos = worksheetView()->mapFromScene(pos);
const int viewHeight = worksheetView()->viewport()->height();
if ((viewPos.y() < 10 || viewPos.y() > viewHeight - 10) &&
!m_dragScrollTimer) {
m_dragScrollTimer = new QTimer(this);
m_dragScrollTimer->setSingleShot(true);
m_dragScrollTimer->setInterval(100);
connect(m_dragScrollTimer, SIGNAL(timeout()), this,
SLOT(updateDragScrollTimer()));
m_dragScrollTimer->start();
}
event->accept();
}
void Worksheet::dropEvent(QGraphicsSceneDragDropEvent* event)
{
if (!m_dragEntry)
QGraphicsScene::dropEvent(event);
event->accept();
}
void Worksheet::updateDragScrollTimer()
{
if (!m_dragScrollTimer)
return;
const QPoint viewPos = worksheetView()->viewCursorPos();
const QWidget* viewport = worksheetView()->viewport();
const int viewHeight = viewport->height();
if (!m_dragEntry || !(viewport->rect().contains(viewPos)) ||
(viewPos.y() >= 10 && viewPos.y() <= viewHeight - 10)) {
delete m_dragScrollTimer;
m_dragScrollTimer = nullptr;
return;
}
if (viewPos.y() < 10)
worksheetView()->scrollBy(-10*(10 - viewPos.y()));
else
worksheetView()->scrollBy(10*(viewHeight - viewPos.y()));
m_dragScrollTimer->start();
}
void Worksheet::updateEntryCursor(QGraphicsSceneMouseEvent* event)
{
// determine the worksheet entry near which the entry cursor will be shown
resetEntryCursor();
if (event->button() == Qt::LeftButton && !focusItem())
{
const qreal y = event->scenePos().y();
for (WorksheetEntry* entry = firstEntry(); entry; entry = entry->next())
{
if (entry == firstEntry() && y < entry->y() )
{
m_choosenCursorEntry = firstEntry();
break;
}
else if (entry->y() < y && (entry->next() && y < entry->next()->y()))
{
m_choosenCursorEntry = entry->next();
break;
}
else if (entry->y() < y && entry == lastEntry())
{
m_isCursorEntryAfterLastEntry = true;
break;
}
}
}
if (m_choosenCursorEntry || m_isCursorEntryAfterLastEntry)
drawEntryCursor();
}
void Worksheet::addEntryFromEntryCursor()
{
qDebug() << "Add new entry from entry cursor";
if (m_isCursorEntryAfterLastEntry)
insertCommandEntry(lastEntry());
else
insertCommandEntryBefore(m_choosenCursorEntry);
resetEntryCursor();
}
void Worksheet::animateEntryCursor()
{
if ((m_choosenCursorEntry || m_isCursorEntryAfterLastEntry) && m_entryCursorItem)
m_entryCursorItem->setVisible(!m_entryCursorItem->isVisible());
}
void Worksheet::resetEntryCursor()
{
m_choosenCursorEntry = nullptr;
m_isCursorEntryAfterLastEntry = false;
m_entryCursorItem->hide();
}
void Worksheet::drawEntryCursor()
{
if (m_entryCursorItem && (m_choosenCursorEntry || (m_isCursorEntryAfterLastEntry && lastEntry())))
{
qreal x;
qreal y;
if (m_isCursorEntryAfterLastEntry)
{
x = lastEntry()->x();
y = lastEntry()->y() + lastEntry()->size().height() - (EntryCursorWidth - 1);
}
else
{
x = m_choosenCursorEntry->x();
y = m_choosenCursorEntry->y();
}
m_entryCursorItem->setLine(x,y,x+EntryCursorLength,y);
m_entryCursorItem->show();
}
}
void Worksheet::setType(Worksheet::Type type)
{
m_type = type;
}
Worksheet::Type Worksheet::type() const
{
return m_type;
}
void Worksheet::changeEntryType(WorksheetEntry* target, int newType)
{
if (target && target->type() != newType)
{
bool animation_state = m_animationsEnabled;
m_animationsEnabled = false;
QString content;
switch(target->type())
{
case CommandEntry::Type:
content = static_cast<CommandEntry*>(target)->command();
break;
case MarkdownEntry::Type:
content = static_cast<MarkdownEntry*>(target)->plainText();
break;
case TextEntry::Type:
content = static_cast<TextEntry*>(target)->text();
break;
case LatexEntry::Type:
content = static_cast<LatexEntry*>(target)->plain();
}
WorksheetEntry* newEntry = WorksheetEntry::create(newType, this);
newEntry->setContent(content);
if (newEntry)
{
WorksheetEntry* tmp = target;
newEntry->setPrevious(tmp->previous());
newEntry->setNext(tmp->next());
tmp->setPrevious(nullptr);
tmp->setNext(nullptr);
tmp->clearFocus();
tmp->forceRemove();
if (newEntry->previous())
newEntry->previous()->setNext(newEntry);
else
setFirstEntry(newEntry);
if (newEntry->next())
newEntry->next()->setPrevious(newEntry);
else
setLastEntry(newEntry);
updateLayout();
makeVisible(newEntry);
focusEntry(newEntry);
setModified();
newEntry->focusEntry();
}
m_animationsEnabled = animation_state;
}
}
+
+bool Worksheet::isValidEntry(WorksheetEntry* entry)
+{
+ for (WorksheetEntry* iter = firstEntry(); iter; iter = iter->next())
+ if (entry == iter)
+ return true;
+
+ return false;
+}
+
+void Worksheet::selectionRemove()
+{
+ for (WorksheetEntry* entry : m_selectedEntries)
+ if (isValidEntry(entry))
+ entry->startRemoving();
+
+ m_selectedEntries.clear();
+}
+
+void Worksheet::selectionEvaluate()
+{
+ // run entries in worksheet order: from top to down
+ for (WorksheetEntry* entry = firstEntry(); entry; entry = entry->next())
+ if (m_selectedEntries.indexOf(entry) != -1)
+ entry->evaluate();
+}
+
+void Worksheet::selectionMoveUp()
+{
+ // movement up should have an order from top to down.
+ for(WorksheetEntry* entry = firstEntry(); entry; entry = entry->next())
+ if(m_selectedEntries.indexOf(entry) != -1)
+ if (entry->previous() && m_selectedEntries.indexOf(entry->previous()) == -1)
+ entry->moveToPrevious(false);
+ updateLayout();
+}
+
+void Worksheet::selectionMoveDown()
+{
+ // movement up should have an order from down to top.
+ for(WorksheetEntry* entry = lastEntry(); entry; entry = entry->previous())
+ if(m_selectedEntries.indexOf(entry) != -1)
+ if (entry->next() && m_selectedEntries.indexOf(entry->next()) == -1)
+ entry->moveToNext(false);
+ updateLayout();
+}
+
+void Worksheet::notifyEntryFocus(WorksheetEntry* entry)
+{
+ if (entry)
+ {
+ m_circularFocusBuffer.enqueue(entry);
+
+ if (m_circularFocusBuffer.size() > 2)
+ m_circularFocusBuffer.dequeue();
+ }
+ else
+ m_circularFocusBuffer.clear();
+}
diff --git a/src/worksheet.h b/src/worksheet.h
index fe1c4a4e..fb880c6c 100644
--- a/src/worksheet.h
+++ b/src/worksheet.h
@@ -1,340 +1,356 @@
/*
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) 2009 Alexander Rieder <alexanderrieder@gmail.com>
Copyright (C) 2012 Martin Kuettler <martin.kuettler@gmail.com>
*/
#ifndef WORKSHEET_H
#define WORKSHEET_H
+
#include <QGraphicsScene>
#include <QDomElement>
#include <QGraphicsLinearLayout>
#include <QSyntaxHighlighter>
#include <QGraphicsRectItem>
+#include <QVector>
+#include <QQueue>
#include <KZip>
#include <QMenu>
#include "worksheetview.h"
#include "lib/renderer.h"
#include "mathrender.h"
#include "worksheetcursor.h"
namespace Cantor {
class Backend;
class Session;
class Expression;
}
class WorksheetEntry;
class PlaceHolderEntry;
class WorksheetTextItem;
class QAction;
class QDrag;
class QPrinter;
class KActionCollection;
class KToggleAction;
class KFontAction;
class KFontSizeAction;
class Worksheet : public QGraphicsScene
{
Q_OBJECT
public:
enum Type {
CantorWorksheet,
JupyterNotebook
};
Worksheet(Cantor::Backend* backend, QWidget* parent, bool useDeafultWorksheetParameters = true);
~Worksheet() override;
Cantor::Session* session();
void loginToSession();
bool isRunning();
bool isReadOnly();
bool showExpressionIds();
bool animationsEnabled();
bool embeddedMathEnabled();
bool isPrinting();
WorksheetView* worksheetView();
void makeVisible(WorksheetEntry*);
void makeVisible(const WorksheetCursor&);
void setModified();
void startDrag(WorksheetEntry* entry, QDrag* drag);
void createActions(KActionCollection*);
QMenu* createContextMenu();
void populateMenu(QMenu* menu, QPointF pos);
Cantor::Renderer* renderer();
MathRenderer* mathRenderer();
bool isEmpty();
bool isLoadingFromFile();
WorksheetEntry* currentEntry();
WorksheetEntry* firstEntry();
WorksheetEntry* lastEntry();
WorksheetTextItem* currentTextItem();
WorksheetTextItem* lastFocusedTextItem();
WorksheetCursor worksheetCursor();
void setWorksheetCursor(const WorksheetCursor&);
// For WorksheetEntry::startDrag
void resetEntryCursor();
/**
* How it works:
* There are two information streams
* 1. WorksheetView -> Worksheet -> subelemenets (ex. entries) about view width
* View width used by some sub elements for better visual appearance (for example, entries with text often are fitted to width of view).
* 2. Subelements -> Worksheet
* Sub elements notify Worksheet about their needed widths and worksheet, used this information, set proper scene size.
*/
/// First information stream
void setViewSize(qreal w, qreal h, qreal s, bool forceUpdate = false);
/// Second information stream
void setRequestedWidth(QGraphicsObject* object, qreal width);
void removeRequestedWidth(QGraphicsObject* object);
bool isShortcut(const QKeySequence&);
void setType(Worksheet::Type type);
Worksheet::Type type() const;
+ void notifyEntryFocus(WorksheetEntry* entry);
+
// richtext
struct RichTextInfo {
bool bold;
bool italic;
bool underline;
bool strikeOut;
QString font;
qreal fontSize;
Qt::Alignment align;
};
public Q_SLOTS:
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);
void updateLayout();
void updateEntrySize(WorksheetEntry*);
void print(QPrinter*);
void paste();
void focusEntry(WorksheetEntry*);
void evaluate();
void evaluateCurrentEntry();
void interrupt();
void interruptCurrentEntryEvaluation();
bool completionEnabled();
//void showCompletion();
void highlightItem(WorksheetTextItem*);
void rehighlight();
void enableHighlighting(bool);
void enableCompletion(bool);
void enableExpressionNumbering(bool);
void enableAnimations(bool);
void enableEmbeddedMath(bool);
QDomDocument toXML(KZip* archive = nullptr);
void save(const QString& filename);
void save(QIODevice*);
QByteArray saveToByteArray();
void savePlain(const QString& filename);
void saveLatex(const QString& filename);
bool load(QIODevice*);
void load(QByteArray* data);
bool load(const QString& filename);
void gotResult(Cantor::Expression* expr = nullptr);
void removeCurrentEntry();
void setFirstEntry(WorksheetEntry*);
void setLastEntry(WorksheetEntry*);
void invalidateFirstEntry();
void invalidateLastEntry();
void updateFocusedTextItem(WorksheetTextItem*);
void updateDragScrollTimer();
void registerShortcut(QAction*);
void updateShortcut();
// richtext
void setRichTextInformation(const Worksheet::RichTextInfo&);
void setAcceptRichText(bool b);
void setTextForegroundColor();
void setTextBackgroundColor();
void setTextBold(bool b);
void setTextItalic(bool b);
void setTextUnderline(bool b);
void setTextStrikeOut(bool b);
void setAlignLeft();
void setAlignRight();
void setAlignCenter();
void setAlignJustify();
void setFontFamily(const QString&);
void setFontSize(int size);
void changeEntryType(WorksheetEntry* target, int newType);
Q_SIGNALS:
void modified();
void loaded();
void showHelp(const QString&);
void updatePrompt();
void undoAvailable(bool);
void redoAvailable(bool);
void undo();
void redo();
void cutAvailable(bool);
void copyAvailable(bool);
void pasteAvailable(bool);
void cut();
void copy();
protected:
void contextMenuEvent(QGraphicsSceneContextMenuEvent*) override;
void mousePressEvent(QGraphicsSceneMouseEvent*) override;
void dragEnterEvent(QGraphicsSceneDragDropEvent*) override;
void dragLeaveEvent(QGraphicsSceneDragDropEvent*) override;
void dragMoveEvent(QGraphicsSceneDragDropEvent*) override;
void dropEvent(QGraphicsSceneDragDropEvent*) override;
void keyPressEvent(QKeyEvent*) override;
QJsonDocument toJupyterJson();
+ bool isValidEntry(WorksheetEntry*);
+
private Q_SLOTS:
void showCompletion();
//void checkEntriesForSanity();
WorksheetEntry* appendEntry(int type, bool focus = true);
WorksheetEntry* insertEntry(int type, WorksheetEntry* current = nullptr);
WorksheetEntry* insertEntryBefore(int type, WorksheetEntry* current = nullptr);
+ //Actions for selection
+ void selectionRemove();
+ void selectionEvaluate();
+ void selectionMoveUp();
+ void selectionMoveDown();
+
void animateEntryCursor();
private:
WorksheetEntry* entryAt(qreal x, qreal y);
WorksheetEntry* entryAt(QPointF p);
WorksheetEntry* entryAt(int row);
void updateEntryCursor(QGraphicsSceneMouseEvent*);
void addEntryFromEntryCursor();
void drawEntryCursor();
int entryCount();
bool loadCantorWorksheet(const KZip& archive);
bool loadJupyterNotebook(const QJsonDocument& doc);
void showInvalidNotebookSchemeError(QString additionalInfo = QString());
void initSession(Cantor::Backend*);
private:
static const double LeftMargin;
static const double RightMargin;
static const double TopMargin;
static const double EntryCursorLength;
static const double EntryCursorWidth;
Cantor::Session *m_session;
QSyntaxHighlighter* m_highlighter;
Cantor::Renderer m_epsRenderer;
MathRenderer m_mathRenderer;
WorksheetEntry* m_firstEntry;
WorksheetEntry* m_lastEntry;
WorksheetEntry* m_dragEntry;
WorksheetEntry* m_choosenCursorEntry;
bool m_isCursorEntryAfterLastEntry;
QTimer* m_cursorItemTimer;
QGraphicsLineItem* m_entryCursorItem;
PlaceHolderEntry* m_placeholderEntry;
WorksheetTextItem* m_lastFocusedTextItem;
QTimer* m_dragScrollTimer;
qreal m_viewWidth;
QMap<QGraphicsObject*, qreal> m_itemWidths;
qreal m_maxWidth;
QMap<QKeySequence, QAction*> m_shortcuts;
QList<QAction *> m_richTextActionList;
KToggleAction* m_boldAction;
KToggleAction* m_italicAction;
KToggleAction* m_underlineAction;
KToggleAction* m_strikeOutAction;
KFontAction* m_fontAction;
KFontSizeAction* m_fontSizeAction;
KToggleAction* m_alignLeftAction;
KToggleAction* m_alignCenterAction;
KToggleAction* m_alignRightAction;
KToggleAction* m_alignJustifyAction;
bool m_useDefaultWorksheetParameters{true};
bool m_completionEnabled{false};
bool m_embeddedMathEnabled{false};
bool m_showExpressionIds{false};
bool m_animationsEnabled{false};
bool m_isPrinting{false};
bool m_isLoadingFromFile{false};
bool m_readOnly{false};
Type m_type = CantorWorksheet;
QString m_backendName;
QJsonObject* m_jupyterMetadata{nullptr};
+
+ QVector<WorksheetEntry*> m_selectedEntries;
+ QQueue<WorksheetEntry*> m_circularFocusBuffer;
};
#endif // WORKSHEET_H
diff --git a/src/worksheetcontrolitem.cpp b/src/worksheetcontrolitem.cpp
new file mode 100644
index 00000000..850950f6
--- /dev/null
+++ b/src/worksheetcontrolitem.cpp
@@ -0,0 +1,122 @@
+/*
+ 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) 2020 Sirgienko Nikita <warquark@gmail.com>
+ */
+
+#include "worksheetcontrolitem.h"
+
+#include <KColorScheme>
+#include <QApplication>
+#include <QDebug>
+
+#include "worksheet.h"
+#include "worksheetentry.h"
+
+WorksheetControlItem::WorksheetControlItem(Worksheet* worksheet, WorksheetEntry* parent): QGraphicsRectItem(parent)
+{
+ setAcceptDrops(true);
+ setAcceptHoverEvents(true);
+ setFlags(flags() | QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsFocusable);
+ m_worksheet = worksheet;
+}
+
+void WorksheetControlItem::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
+{
+ Q_UNUSED(option);
+ Q_UNUSED(widget);
+
+ if (m_worksheet->isPrinting())
+ return;
+
+ painter->setViewTransformEnabled(true);
+
+ if (m_isHovered)
+ painter->setPen(QPen(Qt::black, 2));
+ else
+ painter->setPen(QPen(Qt::black, 1));
+
+ qreal x = rect().x();
+ qreal y = rect().y();
+ qreal w = rect().width();
+ qreal h = rect().height();
+
+ painter->drawLine(x, y, x+w, y);
+ painter->drawLine(x+w, y, x+w, y+h);
+ painter->drawLine(x, y+h, x+w, y+h);
+
+ //For collabsable entries draw "collapsing triangle" (form will depends from collapse's state)
+ if (isCollapsable)
+ {
+ if (isCollapsed)
+ {
+ QBrush brush = painter->brush();
+ brush.setStyle(Qt::SolidPattern);
+ painter->setBrush(brush);
+
+ QPolygon triangle;
+ triangle << QPoint(x, y) << QPoint(x+w, y) << QPoint(x+w, y+w);
+
+ painter->drawPolygon(triangle);
+ }
+ else
+ painter->drawLine(x, y, x+w, y+w);
+ }
+
+ if (isSelected)
+ {
+ //Use theme colour for selection, but with transparent
+ QColor color = QApplication::palette().color(QPalette::Highlight);
+ color.setAlpha(192);
+
+ painter->fillRect(rect(), color);
+ }
+}
+
+void WorksheetControlItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent* event)
+{
+ emit doubleClick();
+ QGraphicsItem::mouseDoubleClickEvent(event);
+}
+
+void WorksheetControlItem::mouseMoveEvent(QGraphicsSceneMouseEvent* event)
+{
+ if (event->buttons() != Qt::LeftButton)
+ return;
+
+ const QPointF buttonDownPos = event->buttonDownPos(Qt::LeftButton);
+ if (contains(buttonDownPos) && (event->pos() - buttonDownPos).manhattanLength() >= QApplication::startDragDistance())
+ {
+ ungrabMouse();
+ emit drag(mapToParent(buttonDownPos), mapToParent(event->pos()));
+ event->accept();
+ }
+}
+
+void WorksheetControlItem::hoverEnterEvent(QGraphicsSceneHoverEvent* event)
+{
+ Q_UNUSED(event);
+ m_isHovered = true;
+ update();
+}
+
+void WorksheetControlItem::hoverLeaveEvent(QGraphicsSceneHoverEvent* event)
+{
+ Q_UNUSED(event);
+ m_isHovered = false;
+ update();
+}
diff --git a/src/worksheetcontrolitem.h b/src/worksheetcontrolitem.h
new file mode 100644
index 00000000..88b2951a
--- /dev/null
+++ b/src/worksheetcontrolitem.h
@@ -0,0 +1,56 @@
+/*
+ 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) 2020 Sirgienko Nikita <warquark@gmail.com>
+ */
+#ifndef WORKSHEETCONTROLITEM_H
+#define WORKSHEETCONTROLITEM_H
+
+#include <QObject>
+#include <QGraphicsRectItem>
+
+class WorksheetEntry;
+class Worksheet;
+
+class WorksheetControlItem: public QObject, public QGraphicsRectItem
+{
+ Q_OBJECT
+ public:
+ WorksheetControlItem(Worksheet* worksheet, WorksheetEntry* parent);
+
+ Q_SIGNALS:
+ void doubleClick();
+ void drag(const QPointF, const QPointF);
+
+ private:
+ void hoverEnterEvent(QGraphicsSceneHoverEvent * event) override;
+ void hoverLeaveEvent(QGraphicsSceneHoverEvent * event) override;
+ void mouseMoveEvent(QGraphicsSceneMouseEvent* event) override;
+ void mouseDoubleClickEvent(QGraphicsSceneMouseEvent * event) override;
+ void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget = nullptr) override;
+
+ public:
+ bool isSelected{false};
+ bool isCollapsable{false};
+ bool isCollapsed{false};
+
+ private:
+ Worksheet* m_worksheet{nullptr};
+ bool m_isHovered{false};
+};
+
+#endif // WORKSHEETCONTROLITEM_H
diff --git a/src/worksheetentry.cpp b/src/worksheetentry.cpp
index f1d4d19c..e3b106f3 100644
--- a/src/worksheetentry.cpp
+++ b/src/worksheetentry.cpp
@@ -1,864 +1,958 @@
/*
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) 2012 Martin Kuettler <martin.kuettler@gmail.com>
*/
#include "worksheetentry.h"
#include "commandentry.h"
#include "textentry.h"
#include "markdownentry.h"
#include "latexentry.h"
#include "imageentry.h"
#include "pagebreakentry.h"
#include "settings.h"
#include "actionbar.h"
#include "worksheettoolbutton.h"
#include <QDrag>
#include <QPropertyAnimation>
#include <QParallelAnimationGroup>
#include <QMetaMethod>
#include <QMimeData>
#include <QGraphicsProxyWidget>
#include <QBitmap>
#include <QJsonArray>
#include <QJsonObject>
+#include <KColorScheme>
#include <QIcon>
#include <KLocalizedString>
#include <QDebug>
struct AnimationData
{
QAnimationGroup* animation;
QPropertyAnimation* sizeAnimation;
QPropertyAnimation* opacAnimation;
QPropertyAnimation* posAnimation;
const char* slot;
QGraphicsObject* item;
};
const qreal WorksheetEntry::VerticalMargin = 4;
+const qreal WorksheetEntry::ControlElementWidth = 12;
+const qreal WorksheetEntry::ControlElementBorder = 4;
+const qreal WorksheetEntry::RightMargin = ControlElementWidth + 2*ControlElementBorder;
-WorksheetEntry::WorksheetEntry(Worksheet* worksheet) : QGraphicsObject()
+WorksheetEntry::WorksheetEntry(Worksheet* worksheet) : QGraphicsObject(), m_controlElement(worksheet, this)
{
m_next = nullptr;
m_prev = nullptr;
m_animation = nullptr;
m_actionBar = nullptr;
m_actionBarAnimation = nullptr;
m_aboutToBeRemoved = false;
m_jupyterMetadata = nullptr;
setAcceptHoverEvents(true);
worksheet->addItem(this);
+
+ connect(&m_controlElement, &WorksheetControlItem::drag, this, &WorksheetEntry::startDrag);
}
WorksheetEntry::~WorksheetEntry()
{
emit aboutToBeDeleted();
if (next())
next()->setPrevious(previous());
if (previous())
previous()->setNext(next());
if (m_animation) {
m_animation->animation->deleteLater();
delete m_animation;
}
if (m_jupyterMetadata)
delete m_jupyterMetadata;
}
int WorksheetEntry::type() const
{
return Type;
}
WorksheetEntry* WorksheetEntry::create(int t, Worksheet* worksheet)
{
switch(t)
{
case TextEntry::Type:
return new TextEntry(worksheet);
case MarkdownEntry::Type:
return new MarkdownEntry(worksheet);
case CommandEntry::Type:
return new CommandEntry(worksheet);
case ImageEntry::Type:
return new ImageEntry(worksheet);
case PageBreakEntry::Type:
return new PageBreakEntry(worksheet);
case LatexEntry::Type:
return new LatexEntry(worksheet);
default:
return nullptr;
}
}
void WorksheetEntry::insertCommandEntry()
{
worksheet()->insertCommandEntry(this);
}
void WorksheetEntry::insertTextEntry()
{
worksheet()->insertTextEntry(this);
}
void WorksheetEntry::insertMarkdownEntry()
{
worksheet()->insertMarkdownEntry(this);
}
void WorksheetEntry::insertLatexEntry()
{
worksheet()->insertLatexEntry(this);
}
void WorksheetEntry::insertImageEntry()
{
worksheet()->insertImageEntry(this);
}
void WorksheetEntry::insertPageBreakEntry()
{
worksheet()->insertPageBreakEntry(this);
}
void WorksheetEntry::insertCommandEntryBefore()
{
worksheet()->insertCommandEntryBefore(this);
}
void WorksheetEntry::insertTextEntryBefore()
{
worksheet()->insertTextEntryBefore(this);
}
void WorksheetEntry::insertMarkdownEntryBefore()
{
worksheet()->insertMarkdownEntryBefore(this);
}
void WorksheetEntry::insertLatexEntryBefore()
{
worksheet()->insertLatexEntryBefore(this);
}
void WorksheetEntry::insertImageEntryBefore()
{
worksheet()->insertImageEntryBefore(this);
}
void WorksheetEntry::insertPageBreakEntryBefore()
{
worksheet()->insertPageBreakEntryBefore(this);
}
void WorksheetEntry::convertToCommandEntry()
{
worksheet()->changeEntryType(this, CommandEntry::Type);
}
void WorksheetEntry::convertToTextEntry()
{
worksheet()->changeEntryType(this, TextEntry::Type);
}
void WorksheetEntry::convertToMarkdownEntry()
{
worksheet()->changeEntryType(this, MarkdownEntry::Type);
}
void WorksheetEntry::convertToLatexEntry()
{
worksheet()->changeEntryType(this, LatexEntry::Type);
}
void WorksheetEntry::convertToImageEntry()
{
worksheet()->changeEntryType(this, ImageEntry::Type);
}
void WorksheetEntry::converToPageBreakEntry()
{
worksheet()->changeEntryType(this, PageBreakEntry::Type);
}
void WorksheetEntry::showCompletion()
{
}
WorksheetEntry* WorksheetEntry::next() const
{
return m_next;
}
WorksheetEntry* WorksheetEntry::previous() const
{
return m_prev;
}
void WorksheetEntry::setNext(WorksheetEntry* n)
{
m_next = n;
}
void WorksheetEntry::setPrevious(WorksheetEntry* p)
{
m_prev = p;
}
void WorksheetEntry::startDrag(QPointF grabPos)
{
// We need reset entry cursor manually, because otherwise the entry cursor will be visible on dragable item
worksheet()->resetEntryCursor();
QDrag* drag = new QDrag(worksheetView());
qDebug() << size();
const qreal scale = worksheet()->renderer()->scale();
QPixmap pixmap((size()*scale).toSize());
pixmap.fill(QColor(255, 255, 255, 0));
QPainter painter(&pixmap);
const QRectF sceneRect = mapRectToScene(boundingRect());
worksheet()->render(&painter, pixmap.rect(), sceneRect);
painter.end();
QBitmap mask = pixmap.createMaskFromColor(QColor(255, 255, 255),
Qt::MaskInColor);
pixmap.setMask(mask);
drag->setPixmap(pixmap);
if (grabPos.isNull()) {
const QPointF scenePos = worksheetView()->sceneCursorPos();
drag->setHotSpot((mapFromScene(scenePos) * scale).toPoint());
} else {
drag->setHotSpot((grabPos * scale).toPoint());
}
drag->setMimeData(new QMimeData());
worksheet()->startDrag(this, drag);
}
QRectF WorksheetEntry::boundingRect() const
{
return QRectF(QPointF(0,0), m_size);
}
void WorksheetEntry::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
{
Q_UNUSED(painter);
Q_UNUSED(option);
Q_UNUSED(widget);
}
bool WorksheetEntry::focusEntry(int pos, qreal xCoord)
{
Q_UNUSED(pos);
Q_UNUSED(xCoord);
if (flags() & QGraphicsItem::ItemIsFocusable) {
setFocus();
return true;
}
return false;
}
void WorksheetEntry::moveToPreviousEntry(int pos, qreal x)
{
WorksheetEntry* entry = previous();
while (entry && !(entry->wantFocus() && entry->focusEntry(pos, x)))
entry = entry->previous();
}
void WorksheetEntry::moveToNextEntry(int pos, qreal x)
{
WorksheetEntry* entry = next();
while (entry && !(entry->wantFocus() && entry->focusEntry(pos, x)))
entry = entry->next();
}
Worksheet* WorksheetEntry::worksheet()
{
return qobject_cast<Worksheet*>(scene());
}
WorksheetView* WorksheetEntry::worksheetView()
{
return worksheet()->worksheetView();
}
WorksheetCursor WorksheetEntry::search(const QString& pattern, unsigned flags,
QTextDocument::FindFlags qt_flags,
const WorksheetCursor& pos)
{
Q_UNUSED(pattern);
Q_UNUSED(flags);
Q_UNUSED(qt_flags);
Q_UNUSED(pos);
return WorksheetCursor();
}
void WorksheetEntry::keyPressEvent(QKeyEvent* event)
{
// This event is used in Entries that set the ItemIsFocusable flag
switch(event->key()) {
case Qt::Key_Left:
case Qt::Key_Up:
if (event->modifiers() == Qt::NoModifier)
moveToPreviousEntry(WorksheetTextItem::BottomRight, 0);
break;
case Qt::Key_Right:
case Qt::Key_Down:
if (event->modifiers() == Qt::NoModifier)
moveToNextEntry(WorksheetTextItem::TopLeft, 0);
break;
/*case Qt::Key_Enter:
case Qt::Key_Return:
if (event->modifiers() == Qt::ShiftModifier)
evaluate();
else if (event->modifiers() == Qt::ControlModifier)
worksheet()->insertCommandEntry();
break;
case Qt::Key_Delete:
if (event->modifiers() == Qt::ShiftModifier)
startRemoving();
break;*/
default:
event->ignore();
}
}
void WorksheetEntry::contextMenuEvent(QGraphicsSceneContextMenuEvent *event)
{
QMenu *menu = worksheet()->createContextMenu();
populateMenu(menu, event->pos());
menu->popup(event->screenPos());
}
void WorksheetEntry::populateMenu(QMenu* menu, QPointF pos)
{
+ menu->addAction(QIcon::fromTheme(QLatin1String("go-up")), i18n("Move Up"), this, SLOT(moveToPrevious()), 0);
+ menu->addAction(QIcon::fromTheme(QLatin1String("go-down")), i18n("Move Down"), this, SLOT(moveToNext()), 0);
if (!worksheet()->isRunning() && wantToEvaluate())
menu->addAction(QIcon::fromTheme(QLatin1String("media-playback-start")), i18n("Evaluate Entry"), this, SLOT(evaluate()), 0);
menu->addAction(QIcon::fromTheme(QLatin1String("edit-delete")), i18n("Remove Entry"), this, SLOT(startRemoving()), 0);
menu->addSeparator();
worksheet()->populateMenu(menu, mapToScene(pos));
}
bool WorksheetEntry::evaluateCurrentItem()
{
// A default implementation that works well for most entries,
// because they have only one item.
return evaluate();
}
void WorksheetEntry::evaluateNext(EvaluationOption opt)
{
// For cases, when code want *just* evaluate
// the entry, for example, on load stage.
// This internal evaluation shouldn't marked as
// modifying change.
if (opt == InternalEvaluation)
return;
WorksheetEntry* entry = next();
while (entry && !entry->wantFocus())
entry = entry->next();
if (entry) {
if (opt == EvaluateNext || Settings::self()->autoEval()) {
entry->evaluate(EvaluateNext);
} else if (opt == FocusNext) {
worksheet()->setModified();
entry->focusEntry(WorksheetTextItem::BottomRight);
} else {
worksheet()->setModified();
}
} else if (opt != DoNothing) {
if (!worksheet()->isLoadingFromFile() && (!isEmpty() || type() != CommandEntry::Type))
worksheet()->appendCommandEntry();
else
focusEntry();
worksheet()->setModified();
}
}
qreal WorksheetEntry::setGeometry(qreal x, qreal y, qreal w)
{
setPos(x, y);
layOutForWidth(w);
+
+ recalculateControlGeometry();
+
return size().height();
}
void WorksheetEntry::recalculateSize()
{
qreal height = size().height();
layOutForWidth(size().width(), true);
if (height != size().height())
+ {
+ recalculateControlGeometry();
worksheet()->updateEntrySize(this);
+ }
}
QPropertyAnimation* WorksheetEntry::sizeChangeAnimation(QSizeF s)
{
QSizeF oldSize;
QSizeF newSize;
if (s.isValid()) {
oldSize = size();
newSize = s;
} else {
oldSize = size();
layOutForWidth(size().width(), true);
newSize = size();
}
QPropertyAnimation* sizeAn = new QPropertyAnimation(this, "size", this);
sizeAn->setDuration(200);
sizeAn->setStartValue(oldSize);
sizeAn->setEndValue(newSize);
sizeAn->setEasingCurve(QEasingCurve::InOutQuad);
connect(sizeAn, &QPropertyAnimation::valueChanged, this, &WorksheetEntry::sizeAnimated);
return sizeAn;
}
void WorksheetEntry::sizeAnimated()
{
+ recalculateControlGeometry();
worksheet()->updateEntrySize(this);
}
void WorksheetEntry::animateSizeChange()
{
if (!worksheet()->animationsEnabled()) {
recalculateSize();
return;
}
if (m_animation) {
layOutForWidth(size().width(), true);
return;
}
QPropertyAnimation* sizeAn = sizeChangeAnimation();
m_animation = new AnimationData;
m_animation->item = nullptr;
m_animation->slot = nullptr;
m_animation->opacAnimation = nullptr;
m_animation->posAnimation = nullptr;
m_animation->sizeAnimation = sizeAn;
m_animation->sizeAnimation->setEasingCurve(QEasingCurve::OutCubic);
m_animation->animation = new QParallelAnimationGroup(this);
m_animation->animation->addAnimation(m_animation->sizeAnimation);
connect(m_animation->animation, &QAnimationGroup::finished, this, &WorksheetEntry::endAnimation);
m_animation->animation->start();
}
void WorksheetEntry::fadeInItem(QGraphicsObject* item, const char* slot)
{
if (!worksheet()->animationsEnabled()) {
recalculateSize();
if (slot)
invokeSlotOnObject(slot, item);
return;
}
if (m_animation) {
// this calculates the new size and calls updateSizeAnimation
layOutForWidth(size().width(), true);
if (slot)
invokeSlotOnObject(slot, item);
return;
}
QPropertyAnimation* sizeAn = sizeChangeAnimation();
m_animation = new AnimationData;
m_animation->sizeAnimation = sizeAn;
m_animation->sizeAnimation->setEasingCurve(QEasingCurve::OutCubic);
m_animation->opacAnimation = new QPropertyAnimation(item, "opacity", this);
m_animation->opacAnimation->setDuration(200);
m_animation->opacAnimation->setStartValue(0);
m_animation->opacAnimation->setEndValue(1);
m_animation->opacAnimation->setEasingCurve(QEasingCurve::OutCubic);
m_animation->posAnimation = nullptr;
m_animation->animation = new QParallelAnimationGroup(this);
m_animation->item = item;
m_animation->slot = slot;
m_animation->animation->addAnimation(m_animation->sizeAnimation);
m_animation->animation->addAnimation(m_animation->opacAnimation);
connect(m_animation->animation, &QAnimationGroup::finished, this, &WorksheetEntry::endAnimation);
m_animation->animation->start();
}
void WorksheetEntry::fadeOutItem(QGraphicsObject* item, const char* slot)
{
// Note: The default value for slot is SLOT(deleteLater()), so item
// will be deleted after the animation.
if (!worksheet()->animationsEnabled()) {
recalculateSize();
if (slot)
invokeSlotOnObject(slot, item);
return;
}
if (m_animation) {
// this calculates the new size and calls updateSizeAnimation
layOutForWidth(size().width(), true);
if (slot)
invokeSlotOnObject(slot, item);
return;
}
QPropertyAnimation* sizeAn = sizeChangeAnimation();
m_animation = new AnimationData;
m_animation->sizeAnimation = sizeAn;
m_animation->opacAnimation = new QPropertyAnimation(item, "opacity", this);
m_animation->opacAnimation->setDuration(200);
m_animation->opacAnimation->setStartValue(1);
m_animation->opacAnimation->setEndValue(0);
m_animation->opacAnimation->setEasingCurve(QEasingCurve::OutCubic);
m_animation->posAnimation = nullptr;
m_animation->animation = new QParallelAnimationGroup(this);
m_animation->item = item;
m_animation->slot = slot;
m_animation->animation->addAnimation(m_animation->sizeAnimation);
m_animation->animation->addAnimation(m_animation->opacAnimation);
connect(m_animation->animation, &QAnimationGroup::finished, this, &WorksheetEntry::endAnimation);
m_animation->animation->start();
}
void WorksheetEntry::endAnimation()
{
if (!m_animation)
return;
QAnimationGroup* anim = m_animation->animation;
if (anim->state() == QAbstractAnimation::Running) {
anim->stop();
if (m_animation->sizeAnimation)
setSize(m_animation->sizeAnimation->endValue().toSizeF());
if (m_animation->opacAnimation) {
qreal opac = m_animation->opacAnimation->endValue().value<qreal>();
m_animation->item->setOpacity(opac);
}
if (m_animation->posAnimation) {
const QPointF& pos = m_animation->posAnimation->endValue().toPointF();
m_animation->item->setPos(pos);
}
// If the animation was connected to a slot, call it
if (m_animation->slot)
invokeSlotOnObject(m_animation->slot, m_animation->item);
}
m_animation->animation->deleteLater();
delete m_animation;
m_animation = nullptr;
}
bool WorksheetEntry::animationActive()
{
return m_animation;
}
void WorksheetEntry::updateSizeAnimation(QSizeF size)
{
// Update the current animation, so that the new ending will be size
if (!m_animation)
return;
if (m_aboutToBeRemoved)
// do not modify the remove-animation
return;
if (m_animation->sizeAnimation) {
QPropertyAnimation* sizeAn = m_animation->sizeAnimation;
qreal progress = static_cast<qreal>(sizeAn->currentTime()) /
sizeAn->totalDuration();
QEasingCurve curve = sizeAn->easingCurve();
qreal value = curve.valueForProgress(progress);
sizeAn->setEndValue(size);
QSizeF newStart = 1/(1-value)*(sizeAn->currentValue().toSizeF() - value*size);
sizeAn->setStartValue(newStart);
} else {
m_animation->sizeAnimation = sizeChangeAnimation(size);
int d = m_animation->animation->duration() -
m_animation->animation->currentTime();
m_animation->sizeAnimation->setDuration(d);
m_animation->animation->addAnimation(m_animation->sizeAnimation);
}
}
void WorksheetEntry::invokeSlotOnObject(const char* slot, QObject* obj)
{
const QMetaObject* metaObj = obj->metaObject();
const QByteArray normSlot = QMetaObject::normalizedSignature(slot);
const int slotIndex = metaObj->indexOfSlot(normSlot.constData());
if (slotIndex == -1)
qDebug() << "Warning: Tried to invoke an invalid slot:" << slot;
const QMetaMethod method = metaObj->method(slotIndex);
method.invoke(obj, Qt::DirectConnection);
}
bool WorksheetEntry::aboutToBeRemoved()
{
return m_aboutToBeRemoved;
}
void WorksheetEntry::startRemoving()
{
if (!worksheet()->animationsEnabled()) {
m_aboutToBeRemoved = true;
remove();
return;
}
if (m_aboutToBeRemoved)
return;
if (focusItem()) {
if (!next()) {
if (previous() && previous()->isEmpty() &&
!previous()->aboutToBeRemoved()) {
previous()->focusEntry();
} else {
WorksheetEntry* next = worksheet()->appendCommandEntry();
setNext(next);
next->focusEntry();
}
} else {
next()->focusEntry();
}
}
if (m_animation) {
endAnimation();
}
m_aboutToBeRemoved = true;
m_animation = new AnimationData;
m_animation->sizeAnimation = new QPropertyAnimation(this, "size", this);
m_animation->sizeAnimation->setDuration(300);
m_animation->sizeAnimation->setEndValue(QSizeF(size().width(), 0));
m_animation->sizeAnimation->setEasingCurve(QEasingCurve::InOutQuad);
connect(m_animation->sizeAnimation, &QPropertyAnimation::valueChanged, this, &WorksheetEntry::sizeAnimated);
connect(m_animation->sizeAnimation, &QPropertyAnimation::finished, this, &WorksheetEntry::remove);
m_animation->opacAnimation = new QPropertyAnimation(this, "opacity", this);
m_animation->opacAnimation->setDuration(300);
m_animation->opacAnimation->setEndValue(0);
m_animation->opacAnimation->setEasingCurve(QEasingCurve::OutCubic);
m_animation->posAnimation = nullptr;
m_animation->animation = new QParallelAnimationGroup(this);
m_animation->animation->addAnimation(m_animation->sizeAnimation);
m_animation->animation->addAnimation(m_animation->opacAnimation);
m_animation->animation->start();
}
bool WorksheetEntry::stopRemoving()
{
if (!m_aboutToBeRemoved)
return true;
if (m_animation->animation->state() == QAbstractAnimation::Stopped)
// we are too late to stop the deletion
return false;
m_aboutToBeRemoved = false;
m_animation->animation->stop();
m_animation->animation->deleteLater();
delete m_animation;
m_animation = nullptr;
return true;
}
void WorksheetEntry::remove()
{
if (!m_aboutToBeRemoved)
return;
if (previous() && previous()->next() == this)
previous()->setNext(next());
else
worksheet()->setFirstEntry(next());
if (next() && next()->previous() == this)
next()->setPrevious(previous());
else
worksheet()->setLastEntry(previous());
// make the entry invisible to QGraphicsScene's itemAt() function
forceRemove();
}
void WorksheetEntry::setSize(QSizeF size)
{
prepareGeometryChange();
if (m_actionBar && size != m_size)
m_actionBar->updatePosition();
m_size = size;
}
QSizeF WorksheetEntry::size()
{
return m_size;
}
bool WorksheetEntry::hasActionBar()
{
return m_actionBar;
}
void WorksheetEntry::showActionBar()
{
if (m_actionBar && !m_actionBarAnimation)
return;
if (m_actionBarAnimation) {
if (m_actionBarAnimation->endValue().toReal() == 1)
return;
m_actionBarAnimation->stop();
delete m_actionBarAnimation;
m_actionBarAnimation = nullptr;
}
if (!m_actionBar) {
m_actionBar = new ActionBar(this);
m_actionBar->addButton(QIcon::fromTheme(QLatin1String("edit-delete")), i18n("Remove Entry"),
this, SLOT(startRemoving()));
WorksheetToolButton* dragButton;
dragButton = m_actionBar->addButton(QIcon::fromTheme(QLatin1String("transform-move")),
i18n("Drag Entry"));
connect(dragButton, SIGNAL(pressed()), this, SLOT(startDrag()));
if (wantToEvaluate()) {
QString toolTip = i18n("Evaluate Entry");
m_actionBar->addButton(QIcon::fromTheme(QLatin1String("media-playback-start")), toolTip,
this, SLOT(evaluate()));
}
m_actionBar->addSpace();
addActionsToBar(m_actionBar);
}
if (worksheet()->animationsEnabled()) {
m_actionBarAnimation = new QPropertyAnimation(m_actionBar, "opacity",
this);
m_actionBarAnimation->setStartValue(0);
m_actionBarAnimation->setKeyValueAt(0.666, 0);
m_actionBarAnimation->setEndValue(1);
m_actionBarAnimation->setDuration(600);
connect(m_actionBarAnimation, &QPropertyAnimation::finished, this, &WorksheetEntry::deleteActionBarAnimation);
m_actionBarAnimation->start();
}
}
void WorksheetEntry::hideActionBar()
{
if (!m_actionBar)
return;
if (m_actionBarAnimation) {
if (m_actionBarAnimation->endValue().toReal() == 0)
return;
m_actionBarAnimation->stop();
delete m_actionBarAnimation;
m_actionBarAnimation = nullptr;
}
if (worksheet()->animationsEnabled()) {
m_actionBarAnimation = new QPropertyAnimation(m_actionBar, "opacity",
this);
m_actionBarAnimation->setEndValue(0);
m_actionBarAnimation->setEasingCurve(QEasingCurve::Linear);
m_actionBarAnimation->setDuration(200);
connect(m_actionBarAnimation, &QPropertyAnimation::finished, this, &WorksheetEntry::deleteActionBar);
m_actionBarAnimation->start();
} else {
deleteActionBar();
}
}
void WorksheetEntry::deleteActionBarAnimation()
{
if (m_actionBarAnimation) {
delete m_actionBarAnimation;
m_actionBarAnimation = nullptr;
}
}
void WorksheetEntry::deleteActionBar()
{
if (m_actionBar) {
delete m_actionBar;
m_actionBar = nullptr;
}
deleteActionBarAnimation();
}
void WorksheetEntry::addActionsToBar(ActionBar*)
{
}
void WorksheetEntry::hoverEnterEvent(QGraphicsSceneHoverEvent* event)
{
Q_UNUSED(event);
showActionBar();
}
void WorksheetEntry::hoverLeaveEvent(QGraphicsSceneHoverEvent* event)
{
Q_UNUSED(event);
hideActionBar();
}
WorksheetTextItem* WorksheetEntry::highlightItem()
{
return nullptr;
}
bool WorksheetEntry::wantFocus()
{
return true;
}
QJsonObject WorksheetEntry::jupyterMetadata() const
{
return m_jupyterMetadata ? *m_jupyterMetadata : QJsonObject();
}
void WorksheetEntry::setJupyterMetadata(QJsonObject metadata)
{
if (m_jupyterMetadata == nullptr)
m_jupyterMetadata = new QJsonObject();
*m_jupyterMetadata = metadata;
}
void WorksheetEntry::forceRemove()
{
hide();
worksheet()->updateLayout();
deleteLater();
}
+
+bool WorksheetEntry::isCellSelected()
+{
+ return m_controlElement.isSelected;
+}
+
+void WorksheetEntry::setCellSelected(bool val)
+{
+ m_controlElement.isSelected = val;
+}
+
+void WorksheetEntry::moveToNext(bool updateLayout)
+{
+ WorksheetEntry* next = this->next();
+ if (next)
+ {
+ if (next->next())
+ {
+ next->next()->setPrevious(this);
+ this->setNext(next->next());
+ }
+ else
+ {
+ worksheet()->setLastEntry(this);
+ this->setNext(nullptr);
+ }
+
+ next->setPrevious(this->previous());
+ next->setNext(this);
+
+ this->setPrevious(next);
+ if (next->previous())
+ next->previous()->setNext(next);
+ else
+ worksheet()->setFirstEntry(next);
+
+ if (updateLayout)
+ worksheet()->updateLayout();
+ }
+}
+
+void WorksheetEntry::moveToPrevious(bool updateLayout)
+{
+ WorksheetEntry* previous = this->previous();
+ if (previous)
+ {
+ if (previous->previous())
+ {
+ previous->previous()->setNext(this);
+ this->setPrevious(previous->previous());
+ }
+ else
+ {
+ worksheet()->setFirstEntry(this);
+ this->setPrevious(nullptr);
+ }
+
+ previous->setNext(this->next());
+ previous->setPrevious(this);
+
+ this->setNext(previous);
+ if (previous->next())
+ previous->next()->setPrevious(previous);
+ else
+ worksheet()->setLastEntry(previous);
+
+ if (updateLayout)
+ worksheet()->updateLayout();
+ }
+}
+
+void WorksheetEntry::recalculateControlGeometry()
+{
+ m_controlElement.setRect(
+ size().width() - ControlElementWidth - ControlElementBorder, 0, // x,y
+ ControlElementWidth, size().height() - VerticalMargin // w,h
+ );
+ m_controlElement.update();
+}
diff --git a/src/worksheetentry.h b/src/worksheetentry.h
index 893415d8..1ae760c7 100644
--- a/src/worksheetentry.h
+++ b/src/worksheetentry.h
@@ -1,207 +1,224 @@
/*
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) 2012 Martin Kuettler <martin.kuettler@gmail.com>
*/
#ifndef WORKSHEETENTRY_H
#define WORKSHEETENTRY_H
#include <QGraphicsObject>
+#include <QGraphicsRectItem>
#include <QGraphicsSceneContextMenuEvent>
#include "worksheet.h"
#include "worksheettextitem.h"
#include "worksheetcursor.h"
+#include "worksheetcontrolitem.h"
class TextEntry;
class MarkdownEntry;
class CommandEntry;
class ImageEntry;
class PageBreakEntry;
class LaTeXEntry;
class WorksheetTextItem;
class ActionBar;
class QPainter;
class QWidget;
class QPropertyAnimation;
class QJsonObject;
struct AnimationData;
class WorksheetEntry : public QGraphicsObject
{
Q_OBJECT
public:
explicit WorksheetEntry(Worksheet* worksheet);
~WorksheetEntry() override;
enum {Type = UserType};
int type() const override;
virtual bool isEmpty()=0;
static WorksheetEntry* create(int t, Worksheet* worksheet);
WorksheetEntry* next() const;
WorksheetEntry* previous() const;
void forceRemove();
void setNext(WorksheetEntry*);
void setPrevious(WorksheetEntry*);
QRectF boundingRect() const override;
void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget = nullptr) override;
virtual bool acceptRichText() = 0;
virtual void setContent(const QString& content)=0;
virtual void setContent(const QDomElement& content, const KZip& file)=0;
virtual void setContentFromJupyter(const QJsonObject& cell)=0;
virtual QDomElement toXml(QDomDocument& doc, KZip* archive)=0;
virtual QJsonValue toJupyterJson()=0;
virtual QString toPlain(const QString& commandSep, const QString& commentStartingSeq, const QString& commentEndingSeq)=0;
virtual void interruptEvaluation()=0;
virtual void showCompletion();
virtual bool focusEntry(int pos = WorksheetTextItem::TopLeft, qreal xCoord = 0);
virtual qreal setGeometry(qreal x, qreal y, qreal w);
virtual void layOutForWidth(qreal w, bool force = false) = 0;
QPropertyAnimation* sizeChangeAnimation(QSizeF s = QSizeF());
virtual void populateMenu(QMenu* menu, QPointF pos);
bool aboutToBeRemoved();
QSizeF size();
enum EvaluationOption {
InternalEvaluation, DoNothing, FocusNext, EvaluateNext
};
virtual WorksheetTextItem* highlightItem();
bool hasActionBar();
enum SearchFlag {SearchCommand=1, SearchResult=2, SearchError=4,
SearchText=8, SearchLaTeX=16, SearchAll=31};
virtual WorksheetCursor search(const QString& pattern, unsigned flags,
QTextDocument::FindFlags qt_flags,
const WorksheetCursor& pos = WorksheetCursor());
+ bool isCellSelected();
+ void setCellSelected(bool);
+
public Q_SLOTS:
virtual bool evaluate(WorksheetEntry::EvaluationOption evalOp = FocusNext) = 0;
virtual bool evaluateCurrentItem();
virtual void updateEntry() = 0;
void insertCommandEntry();
void insertTextEntry();
void insertMarkdownEntry();
void insertLatexEntry();
void insertImageEntry();
void insertPageBreakEntry();
void insertCommandEntryBefore();
void insertTextEntryBefore();
void insertMarkdownEntryBefore();
void insertLatexEntryBefore();
void insertImageEntryBefore();
void insertPageBreakEntryBefore();
void convertToCommandEntry();
void convertToTextEntry();
void convertToMarkdownEntry();
void convertToLatexEntry();
void convertToImageEntry();
void converToPageBreakEntry();
virtual void sizeAnimated();
virtual void startRemoving();
bool stopRemoving();
void moveToPreviousEntry(int pos = WorksheetTextItem::BottomRight, qreal x = 0);
void moveToNextEntry(int pos = WorksheetTextItem::TopLeft, qreal x = 0);
void recalculateSize();
// similar to recalculateSize, but the size change is animated
void animateSizeChange();
// animate the size change and the opacity of item
void fadeInItem(QGraphicsObject* item = nullptr, const char* slot = nullptr);
void fadeOutItem(QGraphicsObject* item = nullptr, const char* slot = "deleteLater()");
void endAnimation();
void showActionBar();
void hideActionBar();
void startDrag(QPointF grabPos = QPointF());
+ void moveToNext(bool updateLayout = true);
+ void moveToPrevious(bool updateLayout = true);
+
Q_SIGNALS:
void aboutToBeDeleted();
protected:
Worksheet* worksheet();
WorksheetView* worksheetView();
void contextMenuEvent(QGraphicsSceneContextMenuEvent *event) override;
void keyPressEvent(QKeyEvent *event) override;
void evaluateNext(EvaluationOption opt);
void hoverEnterEvent(QGraphicsSceneHoverEvent* event) override;
void hoverLeaveEvent(QGraphicsSceneHoverEvent* event) override;
void setSize(QSizeF size);
bool animationActive();
void updateSizeAnimation(QSizeF size);
void invokeSlotOnObject(const char* slot, QObject* obj);
virtual void addActionsToBar(ActionBar* actionBar);
virtual bool wantToEvaluate() = 0;
virtual bool wantFocus();
QJsonObject jupyterMetadata() const;
void setJupyterMetadata(QJsonObject metadata);
+ void recalculateControlGeometry();
+
protected Q_SLOTS:
virtual void remove();
void deleteActionBar();
void deleteActionBarAnimation();
- protected:
+ public:
static const qreal VerticalMargin;
+ static const qreal ControlElementWidth;
+ static const qreal ControlElementBorder;
+ static const qreal RightMargin;
+
+ protected:
+ WorksheetControlItem m_controlElement;
private:
QSizeF m_size;
WorksheetEntry* m_prev;
WorksheetEntry* m_next;
Q_PROPERTY(QSizeF size READ size WRITE setSize)
AnimationData* m_animation;
ActionBar* m_actionBar;
QPropertyAnimation* m_actionBarAnimation;
bool m_aboutToBeRemoved;
QJsonObject* m_jupyterMetadata;
+ bool m_isCellSelected{false};
};
#endif // WORKSHEETENTRY_H