Commit 08e8f40b authored by Nikita Sirgienko's avatar Nikita Sirgienko
Browse files

[T12843][GSoC 2020] Add new entry type - HorizontalRuleEntry. This entry is a...

[T12843][GSoC 2020] Add new entry type - HorizontalRuleEntry. This entry is a horizontal rule with support of changing width, color and style of the rule.
Also, some minor refactoring for Command Entry background color menu.
parent e6ea7e49
......@@ -64,6 +64,7 @@ set(cantor_PART_SRCS
imageentry.cpp
latexentry.cpp
placeholderentry.cpp
horizontalruleentry.cpp
worksheetcursor.cpp
searchbar.cpp
actionbar.cpp
......
......@@ -52,18 +52,6 @@ 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)),
......@@ -135,18 +123,6 @@ int CommandEntry::type() const
}
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);
......
/*
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 "horizontalruleentry.h"
#include <QPropertyAnimation>
#include <QJsonObject>
#include <QApplication>
#include <QDebug>
#include <KLocalizedString>
#include <jupyterutils.h>
const qreal HorizontalRuleEntry::LineVerticalMargin = 10;
const QString HorizontalRuleEntry::styleNames[] = {i18n("Solid Line Style"), i18n("Dash Line Style"), i18n("Dot Line Style"), i18n("Dash Dot Line Style"), i18n("Dash Dot Dot Line Style")};
const Qt::PenStyle HorizontalRuleEntry::styles[] = {Qt::SolidLine, Qt::DashLine, Qt::DotLine, Qt::DashDotLine, Qt::DashDotDotLine};
HorizontalRuleEntry::HorizontalRuleEntry(Worksheet* worksheet)
: WorksheetEntry(worksheet), m_type(LineType::Medium), m_color(QApplication::palette().color(QPalette::Text)), m_entry_zone_offset_x(0), m_width(0), m_style(Qt::SolidLine),
m_menusInitialized(false), m_lineTypeActionGroup(nullptr), m_lineTypeMenu(nullptr), m_lineColorCustom(false), m_lineColorActionGroup(nullptr), m_lineColorMenu(nullptr),
m_lineStyleActionGroup(nullptr), m_lineStyleMenu(nullptr)
{
}
HorizontalRuleEntry::~HorizontalRuleEntry()
{
if (m_menusInitialized)
{
m_lineColorActionGroup->deleteLater();
m_lineColorMenu->deleteLater();
m_lineTypeActionGroup->deleteLater();
m_lineTypeMenu->deleteLater();
m_lineStyleActionGroup->deleteLater();
m_lineStyleMenu->deleteLater();
}
}
int HorizontalRuleEntry::type() const
{
return Type;
}
void HorizontalRuleEntry::setLineType(HorizontalRuleEntry::LineType type)
{
m_type = type;
setSize(QSizeF(m_width, lineWidth(m_type) + 2*LineVerticalMargin));
}
int HorizontalRuleEntry::lineWidth(HorizontalRuleEntry::LineType type)
{
return type == LineType::Thick ? 4 : ((int)type) + 1;
}
void HorizontalRuleEntry::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
{
Q_UNUSED(option);
Q_UNUSED(widget);
painter->setPen(QPen(m_color, lineWidth(m_type), m_style));
const qreal margin = worksheet()->isPrinting() ? 0 : RightMargin;
painter->drawLine(m_entry_zone_offset_x, LineVerticalMargin, m_width - margin, LineVerticalMargin);
}
bool HorizontalRuleEntry::isEmpty()
{
return true;
}
bool HorizontalRuleEntry::acceptRichText()
{
return false;
}
void HorizontalRuleEntry::setContent(const QString&)
{
}
void HorizontalRuleEntry::setContent(const QDomElement& content, const KZip& archive)
{
Q_UNUSED(archive);
m_type = (LineType)(content.attribute(QLatin1String("thickness"), QString::number((int)LineType::Medium)).toInt());
m_style = (Qt::PenStyle)(content.attribute(QLatin1String("style"), QString::number((int)Qt::SolidLine)).toInt());
QDomElement backgroundElem = content.firstChildElement(QLatin1String("lineColor"));
if (!backgroundElem.isNull())
{
m_color.setRed(backgroundElem.attribute(QLatin1String("red")).toInt());
m_color.setGreen(backgroundElem.attribute(QLatin1String("green")).toInt());
m_color.setBlue(backgroundElem.attribute(QLatin1String("blue")).toInt());
m_lineColorCustom = true;
}
}
void HorizontalRuleEntry::setContentFromJupyter(const QJsonObject& cell)
{
QJsonObject cantorMetadata = Cantor::JupyterUtils::getCantorMetadata(cell);
QJsonValue typeValue = cantorMetadata.value(QLatin1String("type"));
if (typeValue.isDouble())
setLineType(static_cast<LineType>(static_cast<int>(typeValue.toDouble())));
QJsonValue styleValue = cantorMetadata.value(QLatin1String("style"));
if (styleValue.isDouble())
m_style = static_cast<Qt::PenStyle>(static_cast<int>(styleValue.toDouble()));
QJsonValue colorValue = cantorMetadata.value(QLatin1String("lineColor"));
if (colorValue.isObject())
{
m_color.setRed(colorValue.toObject().value(QLatin1String("red")).toInt());
m_color.setGreen(colorValue.toObject().value(QLatin1String("green")).toInt());
m_color.setBlue(colorValue.toObject().value(QLatin1String("blue")).toInt());
m_lineColorCustom = true;
}
setJupyterMetadata(Cantor::JupyterUtils::getMetadata(cell));
}
QJsonValue HorizontalRuleEntry::toJupyterJson()
{
QJsonObject entry;
entry.insert(QLatin1String("cell_type"), QLatin1String("markdown"));
QJsonObject metadata(jupyterMetadata());
QJsonObject cantor;
cantor.insert(QLatin1String("type"), m_type);
cantor.insert(QLatin1String("style"), m_style);
if (m_lineColorCustom)
{
QJsonObject color;
color.insert(QLatin1String("red"), m_color.red());
color.insert(QLatin1String("green"), m_color.green());
color.insert(QLatin1String("blue"), m_color.blue());
cantor.insert(QLatin1String("lineColor"), color);
}
metadata.insert(Cantor::JupyterUtils::cantorMetadataKey, cantor);
entry.insert(Cantor::JupyterUtils::metadataKey, metadata);
Cantor::JupyterUtils::setSource(entry, QLatin1String("----"));
return entry;
}
QDomElement HorizontalRuleEntry::toXml(QDomDocument& doc, KZip* archive)
{
Q_UNUSED(archive);
QDomElement el = doc.createElement(QLatin1String("HorizontalRule"));
el.setAttribute(QLatin1String("thickness"), (int)m_type);
el.setAttribute(QLatin1String("style"), (int)m_style);
if (m_lineColorCustom)
{
QColor backgroundColor = m_color;
QDomElement colorElem = doc.createElement( QLatin1String("lineColor") );
colorElem.setAttribute(QLatin1String("red"), QString::number(backgroundColor.red()));
colorElem.setAttribute(QLatin1String("green"), QString::number(backgroundColor.green()));
colorElem.setAttribute(QLatin1String("blue"), QString::number(backgroundColor.blue()));
el.appendChild(colorElem);
}
return el;
}
QString HorizontalRuleEntry::toPlain(const QString&, const QString&, const QString&){
return QString();
}
void HorizontalRuleEntry::interruptEvaluation()
{
return;
}
void HorizontalRuleEntry::layOutForWidth(qreal entry_zone_x, qreal w, bool force)
{
Q_UNUSED(force);
m_entry_zone_offset_x = entry_zone_x;
m_width = w;
setSize(QSizeF(w, lineWidth(m_type) + 2*LineVerticalMargin));
}
bool HorizontalRuleEntry::evaluate(EvaluationOption evalOp)
{
evaluateNext(evalOp);
return true;
}
void HorizontalRuleEntry::updateEntry()
{
}
bool HorizontalRuleEntry::wantToEvaluate()
{
return false;
}
void HorizontalRuleEntry::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);
}
void HorizontalRuleEntry::populateMenu(QMenu* menu, QPointF pos)
{
if (!m_menusInitialized)
{
initMenus();
m_menusInitialized = true;
}
menu->addMenu(m_lineTypeMenu);
menu->addMenu(m_lineColorMenu);
menu->addMenu(m_lineStyleMenu);
WorksheetEntry::populateMenu(menu, pos);
}
void HorizontalRuleEntry::lineTypeChanged(QAction* action)
{
int index = m_lineTypeActionGroup->actions().indexOf(action);
setLineType((LineType)(index % LineType::Count));
}
bool HorizontalRuleEntry::isConvertableToHorizontalRuleEntry(const QJsonObject& cell)
{
if (!Cantor::JupyterUtils::isMarkdownCell(cell))
return false;
const QString& trimmedSource = Cantor::JupyterUtils::getSource(cell).trimmed();
int sourceLength = trimmedSource.length();
if (sourceLength < 3)
return false;
int hyphensCount = trimmedSource.count(QLatin1Char('-'));
int asteriksCount = trimmedSource.count(QLatin1Char('*'));
int underscoreCount = trimmedSource.count(QLatin1Char('_'));
return sourceLength == hyphensCount || sourceLength == asteriksCount || sourceLength == underscoreCount;
}
void HorizontalRuleEntry::lineColorChanged(QAction* action) {
int index = m_lineColorActionGroup->actions().indexOf(action);
if (index == -1 || index>=colorsCount)
index = 0;
if (index == 0)
{
m_color = QApplication::palette().color(QPalette::Text);
m_lineColorCustom = false;
}
else
{
m_color = colors[index-1];
m_lineColorCustom = true;
}
update();
}
void HorizontalRuleEntry::lineStyleChanged(QAction* action)
{
int index = m_lineStyleActionGroup->actions().indexOf(action);
m_style = styles[index];
update();
}
void HorizontalRuleEntry::initMenus()
{
m_lineTypeActionGroup = new QActionGroup(this);
m_lineTypeActionGroup->setExclusive(true);
connect(m_lineTypeActionGroup, &QActionGroup::triggered, this, &HorizontalRuleEntry::lineTypeChanged);
m_lineTypeMenu = new QMenu(i18n("Line Thickness"));
QAction* action = new QAction(i18n("Thin"), m_lineTypeActionGroup);
action->setCheckable(true);
m_lineTypeMenu->addAction(action);
action = new QAction(i18n("Medium"), m_lineTypeActionGroup);
action->setCheckable(true);
m_lineTypeMenu->addAction(action);
action = new QAction(i18n("Thick"), m_lineTypeActionGroup);
action->setCheckable(true);
m_lineTypeMenu->addAction(action);
// Set default menu value
m_lineTypeActionGroup->actions()[(int)m_type]->setChecked(true);
m_lineColorActionGroup = new QActionGroup(this);
m_lineColorActionGroup->setExclusive(true);
connect(m_lineColorActionGroup, &QActionGroup::triggered, this, &HorizontalRuleEntry::lineColorChanged);
m_lineColorMenu = new QMenu(i18n("Line Color"));
m_lineColorMenu->setIcon(QIcon::fromTheme(QLatin1String("format-fill-color")));
QPixmap pix(16,16);
QPainter p(&pix);
// Create default action
p.fillRect(pix.rect(), QApplication::palette().color(QPalette::Text));
action = new QAction(QIcon(pix), i18n("Default"), m_lineColorActionGroup);
action->setCheckable(true);
m_lineColorMenu->addAction(action);
if (!m_lineColorCustom)
action->setChecked(true);
for (int i=0; i<colorsCount; ++i) {
p.fillRect(pix.rect(), colors[i]);
action = new QAction(QIcon(pix), colorNames[i], m_lineColorActionGroup);
action->setCheckable(true);
m_lineColorMenu->addAction(action);
if (m_lineColorCustom && m_color == colors[i])
action->setChecked(true);
}
m_lineStyleActionGroup = new QActionGroup(this);
m_lineStyleActionGroup->setExclusive(true);
connect(m_lineStyleActionGroup, &QActionGroup::triggered, this, &HorizontalRuleEntry::lineStyleChanged);
m_lineStyleMenu = new QMenu(i18n("Line Style"));
for (unsigned int i = 0; i < styleCount; i++)
{
action = new QAction(styleNames[i], m_lineStyleActionGroup);
action->setCheckable(true);
m_lineStyleMenu->addAction(action);
if (styles[i] == m_style)
action->setChecked(true);
}
}
/*
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 HORIZONTALLINEENTRY_H
#define HORIZONTALLINEENTRY_H
#include "worksheetentry.h"
class HorizontalRuleEntry : public WorksheetEntry
{
public:
enum LineType {Thin = 0, Medium = 1, Thick = 2, Count = 3};
HorizontalRuleEntry(Worksheet* worksheet);
~HorizontalRuleEntry() override;
enum {Type = UserType + 8};
int type() const override;
void setLineType(LineType type);
void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget = nullptr) override;
static bool isConvertableToHorizontalRuleEntry(const QJsonObject& cell);
bool isEmpty() override;
bool acceptRichText() override;
void setContent(const QString&) override;
void setContent(const QDomElement&, const KZip&) override;
void setContentFromJupyter(const QJsonObject & cell) override;
QDomElement toXml(QDomDocument&, KZip*) override;
QJsonValue toJupyterJson() override;
QString toPlain(const QString&, const QString&, const QString&) override;
void interruptEvaluation() override;
void layOutForWidth(qreal entry_zone_x, qreal w, bool force = false) override;
void populateMenu(QMenu* menu, QPointF pos) override;
public Q_SLOTS:
bool evaluate(WorksheetEntry::EvaluationOption evalOp = FocusNext) override;
void updateEntry() override;
void changeSize(QSizeF s);
protected:
bool wantToEvaluate() override;
private:
static int lineWidth(LineType type);
void initMenus();
private Q_SLOTS:
void lineTypeChanged(QAction* action);
void lineColorChanged(QAction* action);
void lineStyleChanged(QAction* action);
public:
static const qreal LineVerticalMargin;
static constexpr unsigned int styleCount = 5;
static const QString styleNames[styleCount];
static const Qt::PenStyle styles[styleCount];
private:
LineType m_type;
QColor m_color;
qreal m_entry_zone_offset_x;
qreal m_width;
Qt::PenStyle m_style;
bool m_menusInitialized;
QActionGroup* m_lineTypeActionGroup;
QMenu* m_lineTypeMenu;
bool m_lineColorCustom;
QActionGroup* m_lineColorActionGroup;
QMenu* m_lineColorMenu;
QActionGroup* m_lineStyleActionGroup;
QMenu* m_lineStyleMenu;
};
#endif //HORIZONTALLINEENTRY_H
......@@ -11,6 +11,7 @@ set(worksheettest_SRCS
../imageentry.cpp
../latexentry.cpp
../placeholderentry.cpp
../horizontalruleentry.cpp
../worksheetcursor.cpp
../searchbar.cpp
../actionbar.cpp
......
......@@ -49,6 +49,7 @@
#include "imageentry.h"
#include "pagebreakentry.h"
#include "placeholderentry.h"
#include "horizontalruleentry.h"
#include "lib/jupyterutils.h"
#include "lib/backend.h"
#include "lib/extension.h"
......@@ -618,6 +619,12 @@ void Worksheet::appendCommandEntry(const QString& text)
}
}
WorksheetEntry * Worksheet::appendHorizontalRuleEntry()
{
return appendEntry(HorizontalRuleEntry::Type);
}
WorksheetEntry* Worksheet::insertEntry(const int type, WorksheetEntry* current)
{
if (!current)
......@@ -690,6 +697,11 @@ void Worksheet::insertCommandEntry(const QString& text)
}
}
WorksheetEntry * Worksheet::insertHorizontalRuleEntry(WorksheetEntry* current)
{
return insertEntry(HorizontalRuleEntry::Type, current);
}
WorksheetEntry* Worksheet::insertEntryBefore(int type, WorksheetEntry* current)
{
if (!current)
......@@ -751,6 +763,11 @@ WorksheetEntry* Worksheet::insertLatexEntryBefore(WorksheetEntry* current)
return insertEntryBefore(LatexEntry::Type, current);
}
WorksheetEntry * Worksheet::insertHorizontalRuleEntryBefore(WorksheetEntry* current)
{
return insertEntryBefore(HorizontalRuleEntry::Type, current);
}
void Worksheet::interrupt()
{
if (m_session->status() == Cantor::Session::Running)
......@@ -1281,6 +1298,11 @@ bool Worksheet::loadCantorWorksheet(const KZip& archive)
entry = appendEntry(ImageEntry::Type, false);
entry->setContent(expressionChild, archive);
}
else if (tag == QLatin1String("HorizontalRule"))
{
entry = appendEntry(HorizontalRuleEntry::Type, false);
entry->setContent(expressionChild, archive);
}
if (m_readOnly && entry)
{
......@@ -1475,6 +1497,11 @@ bool Worksheet::loadJupyterNotebook(const QJsonDocument& doc)
entry = appendEntry(TextEntry::Type, false);
entry->setContentFromJupyter(cell);
}
else if (HorizontalRuleEntry::isConvertableToHorizontalRuleEntry(cell))
{
entry = appendEntry(HorizontalRuleEntry::Type, false);
entry->setContentFromJupyter(cell);
}
else
{
entry = appendEntry(MarkdownEntry::Type, false);
......@@ -1612,6 +1639,7 @@ void Worksheet::populateMenu(QMenu *menu, QPointF pos)
#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);
convertTo->addAction(QIcon(), i18n("Horizontal Line"), entry, &WorksheetEntry::convertToHorizontalRuleEntry);
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()));
......@@ -1623,6 +1651,7 @@ void Worksheet::populateMenu(QMenu *menu, QPointF pos)
#endif