1 /*
2     SPDX-License-Identifier: GPL-2.0-or-later
3     SPDX-FileCopyrightText: 2020 Sirgienko Nikita <warquark@gmail.com>
4 */
5 
6 #include "horizontalruleentry.h"
7 
8 #include <QApplication>
9 #include <QPainter>
10 #include <QPropertyAnimation>
11 #include <QJsonObject>
12 
13 #include <KLocalizedString>
14 
15 #include <jupyterutils.h>
16 
17 const qreal HorizontalRuleEntry::LineVerticalMargin = 10;
18 
19 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")};
20 const Qt::PenStyle HorizontalRuleEntry::styles[] = {Qt::SolidLine, Qt::DashLine, Qt::DotLine, Qt::DashDotLine, Qt::DashDotDotLine};
21 
HorizontalRuleEntry(Worksheet * worksheet)22 HorizontalRuleEntry::HorizontalRuleEntry(Worksheet* worksheet)
23     : 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),
24       m_menusInitialized(false), m_lineTypeActionGroup(nullptr), m_lineTypeMenu(nullptr), m_lineColorCustom(false), m_lineColorActionGroup(nullptr), m_lineColorMenu(nullptr),
25       m_lineStyleActionGroup(nullptr), m_lineStyleMenu(nullptr)
26 {
27 }
28 
~HorizontalRuleEntry()29 HorizontalRuleEntry::~HorizontalRuleEntry()
30 {
31     if (m_menusInitialized)
32     {
33         m_lineColorActionGroup->deleteLater();
34         m_lineColorMenu->deleteLater();
35         m_lineTypeActionGroup->deleteLater();
36         m_lineTypeMenu->deleteLater();
37         m_lineStyleActionGroup->deleteLater();
38         m_lineStyleMenu->deleteLater();
39     }
40 }
41 
type() const42 int HorizontalRuleEntry::type() const
43 {
44     return Type;
45 }
46 
setLineType(HorizontalRuleEntry::LineType type)47 void HorizontalRuleEntry::setLineType(HorizontalRuleEntry::LineType type)
48 {
49     m_type = type;
50 
51     setSize(QSizeF(m_width, lineWidth(m_type) + 2*LineVerticalMargin));
52 }
53 
lineWidth(HorizontalRuleEntry::LineType type)54 int HorizontalRuleEntry::lineWidth(HorizontalRuleEntry::LineType type)
55 {
56     return type == LineType::Thick ? 4 : ((int)type) + 1;
57 }
58 
paint(QPainter * painter,const QStyleOptionGraphicsItem * option,QWidget * widget)59 void HorizontalRuleEntry::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
60 {
61     Q_UNUSED(option);
62     Q_UNUSED(widget);
63 
64     painter->setPen(QPen(m_color, lineWidth(m_type), m_style));
65 
66     const qreal margin = worksheet()->isPrinting() ? 0 : RightMargin;
67 
68     painter->drawLine(m_entry_zone_offset_x, LineVerticalMargin, m_width - margin, LineVerticalMargin);
69 }
70 
isEmpty()71 bool HorizontalRuleEntry::isEmpty()
72 {
73     return true;
74 }
75 
acceptRichText()76 bool HorizontalRuleEntry::acceptRichText()
77 {
78     return false;
79 }
80 
setContent(const QString &)81 void HorizontalRuleEntry::setContent(const QString&)
82 {
83 }
84 
setContent(const QDomElement & content,const KZip & archive)85 void HorizontalRuleEntry::setContent(const QDomElement& content, const KZip& archive)
86 {
87     Q_UNUSED(archive);
88 
89     m_type = (LineType)(content.attribute(QLatin1String("thickness"), QString::number((int)LineType::Medium)).toInt());
90     m_style = (Qt::PenStyle)(content.attribute(QLatin1String("style"), QString::number((int)Qt::SolidLine)).toInt());
91 
92     QDomElement backgroundElem = content.firstChildElement(QLatin1String("lineColor"));
93     if (!backgroundElem.isNull())
94     {
95         m_color.setRed(backgroundElem.attribute(QLatin1String("red")).toInt());
96         m_color.setGreen(backgroundElem.attribute(QLatin1String("green")).toInt());
97         m_color.setBlue(backgroundElem.attribute(QLatin1String("blue")).toInt());
98         m_lineColorCustom = true;
99     }
100 }
101 
setContentFromJupyter(const QJsonObject & cell)102 void HorizontalRuleEntry::setContentFromJupyter(const QJsonObject& cell)
103 {
104     QJsonObject cantorMetadata = Cantor::JupyterUtils::getCantorMetadata(cell);
105     QJsonValue typeValue = cantorMetadata.value(QLatin1String("type"));
106     if (typeValue.isDouble())
107         setLineType(static_cast<LineType>(static_cast<int>(typeValue.toDouble())));
108 
109     QJsonValue styleValue = cantorMetadata.value(QLatin1String("style"));
110     if (styleValue.isDouble())
111         m_style = static_cast<Qt::PenStyle>(static_cast<int>(styleValue.toDouble()));
112 
113     QJsonValue colorValue = cantorMetadata.value(QLatin1String("lineColor"));
114     if (colorValue.isObject())
115     {
116         m_color.setRed(colorValue.toObject().value(QLatin1String("red")).toInt());
117         m_color.setGreen(colorValue.toObject().value(QLatin1String("green")).toInt());
118         m_color.setBlue(colorValue.toObject().value(QLatin1String("blue")).toInt());
119         m_lineColorCustom = true;
120     }
121 
122     setJupyterMetadata(Cantor::JupyterUtils::getMetadata(cell));
123 }
124 
toJupyterJson()125 QJsonValue HorizontalRuleEntry::toJupyterJson()
126 {
127     QJsonObject entry;
128 
129     entry.insert(QLatin1String("cell_type"), QLatin1String("markdown"));
130     QJsonObject metadata(jupyterMetadata());
131 
132     QJsonObject cantor;
133     cantor.insert(QLatin1String("type"), m_type);
134     cantor.insert(QLatin1String("style"), m_style);
135 
136     if (m_lineColorCustom)
137     {
138         QJsonObject color;
139         color.insert(QLatin1String("red"), m_color.red());
140         color.insert(QLatin1String("green"), m_color.green());
141         color.insert(QLatin1String("blue"), m_color.blue());
142         cantor.insert(QLatin1String("lineColor"), color);
143     }
144 
145     metadata.insert(Cantor::JupyterUtils::cantorMetadataKey, cantor);
146 
147     entry.insert(Cantor::JupyterUtils::metadataKey, metadata);
148 
149     Cantor::JupyterUtils::setSource(entry, QLatin1String("----"));
150     return entry;
151 }
152 
153 
toXml(QDomDocument & doc,KZip * archive)154 QDomElement HorizontalRuleEntry::toXml(QDomDocument& doc, KZip* archive)
155 {
156     Q_UNUSED(archive);
157 
158     QDomElement el = doc.createElement(QLatin1String("HorizontalRule"));
159     el.setAttribute(QLatin1String("thickness"), (int)m_type);
160     el.setAttribute(QLatin1String("style"), (int)m_style);
161 
162     if (m_lineColorCustom)
163     {
164         QColor backgroundColor = m_color;
165         QDomElement colorElem = doc.createElement( QLatin1String("lineColor") );
166         colorElem.setAttribute(QLatin1String("red"), QString::number(backgroundColor.red()));
167         colorElem.setAttribute(QLatin1String("green"), QString::number(backgroundColor.green()));
168         colorElem.setAttribute(QLatin1String("blue"), QString::number(backgroundColor.blue()));
169         el.appendChild(colorElem);
170     }
171 
172     return el;
173 }
174 
toPlain(const QString &,const QString &,const QString &)175 QString HorizontalRuleEntry::toPlain(const QString&, const QString&, const QString&){
176     return QString();
177 }
178 
layOutForWidth(qreal entry_zone_x,qreal w,bool force)179 void HorizontalRuleEntry::layOutForWidth(qreal entry_zone_x, qreal w, bool force)
180 {
181     Q_UNUSED(force);
182 
183     m_entry_zone_offset_x = entry_zone_x;
184     m_width = w;
185 
186     setSize(QSizeF(w, lineWidth(m_type) + 2*LineVerticalMargin));
187 }
188 
evaluate(EvaluationOption evalOp)189 bool HorizontalRuleEntry::evaluate(EvaluationOption evalOp)
190 {
191     evaluateNext(evalOp);
192     return true;
193 }
194 
updateEntry()195 void HorizontalRuleEntry::updateEntry()
196 {
197 }
198 
wantToEvaluate()199 bool HorizontalRuleEntry::wantToEvaluate()
200 {
201     return false;
202 }
203 
changeSize(QSizeF s)204 void HorizontalRuleEntry::changeSize(QSizeF s)
205 {
206     if (!worksheet()->animationsEnabled()) {
207         setSize(s);
208         worksheet()->updateEntrySize(this);
209         return;
210     }
211     if (aboutToBeRemoved())
212         return;
213 
214     if (animationActive())
215         endAnimation();
216 
217     QPropertyAnimation* sizeAn = sizeChangeAnimation(s);
218 
219     sizeAn->setEasingCurve(QEasingCurve::InOutQuad);
220     sizeAn->start(QAbstractAnimation::DeleteWhenStopped);
221 }
222 
populateMenu(QMenu * menu,QPointF pos)223 void HorizontalRuleEntry::populateMenu(QMenu* menu, QPointF pos)
224 {
225     if (!m_menusInitialized)
226     {
227         initMenus();
228         m_menusInitialized = true;
229     }
230 
231     menu->addMenu(m_lineTypeMenu);
232     menu->addMenu(m_lineColorMenu);
233     menu->addMenu(m_lineStyleMenu);
234     WorksheetEntry::populateMenu(menu, pos);
235 }
236 
lineTypeChanged(QAction * action)237 void HorizontalRuleEntry::lineTypeChanged(QAction* action)
238 {
239     int index = m_lineTypeActionGroup->actions().indexOf(action);
240     setLineType((LineType)(index % LineType::Count));
241 }
242 
isConvertableToHorizontalRuleEntry(const QJsonObject & cell)243 bool HorizontalRuleEntry::isConvertableToHorizontalRuleEntry(const QJsonObject& cell)
244 {
245     if (!Cantor::JupyterUtils::isMarkdownCell(cell))
246         return false;
247 
248     const QString& trimmedSource = Cantor::JupyterUtils::getSource(cell).trimmed();
249 
250     int sourceLength = trimmedSource.length();
251     if (sourceLength < 3)
252         return false;
253 
254     int hyphensCount = trimmedSource.count(QLatin1Char('-'));
255     int asteriksCount = trimmedSource.count(QLatin1Char('*'));
256     int underscoreCount = trimmedSource.count(QLatin1Char('_'));
257 
258     return sourceLength == hyphensCount || sourceLength == asteriksCount || sourceLength == underscoreCount;
259 }
260 
lineColorChanged(QAction * action)261 void HorizontalRuleEntry::lineColorChanged(QAction* action) {
262     int index = m_lineColorActionGroup->actions().indexOf(action);
263     if (index == -1 || index>=colorsCount)
264         index = 0;
265 
266     if (index == 0)
267     {
268         m_color = QApplication::palette().color(QPalette::Text);
269         m_lineColorCustom = false;
270     }
271     else
272     {
273         m_color = colors[index-1];
274         m_lineColorCustom = true;
275     }
276     update();
277 }
278 
lineStyleChanged(QAction * action)279 void HorizontalRuleEntry::lineStyleChanged(QAction* action)
280 {
281     unsigned int index = static_cast<unsigned int>(m_lineStyleActionGroup->actions().indexOf(action));
282     if (index > 0 && index < styleCount)
283     {
284         m_style = styles[index];
285         update();
286     }
287 }
288 
289 
initMenus()290 void HorizontalRuleEntry::initMenus()
291 {
292     m_lineTypeActionGroup = new QActionGroup(this);
293     m_lineTypeActionGroup->setExclusive(true);
294     connect(m_lineTypeActionGroup, &QActionGroup::triggered, this, &HorizontalRuleEntry::lineTypeChanged);
295 
296     m_lineTypeMenu = new QMenu(i18n("Line Thickness"));
297 
298     QAction* action = new QAction(i18n("Thin"), m_lineTypeActionGroup);
299     action->setCheckable(true);
300     m_lineTypeMenu->addAction(action);
301 
302     action = new QAction(i18n("Medium"), m_lineTypeActionGroup);
303     action->setCheckable(true);
304     m_lineTypeMenu->addAction(action);
305 
306     action = new QAction(i18n("Thick"), m_lineTypeActionGroup);
307     action->setCheckable(true);
308     m_lineTypeMenu->addAction(action);
309 
310     // Set default menu value
311     m_lineTypeActionGroup->actions()[(int)m_type]->setChecked(true);
312 
313 
314 
315     m_lineColorActionGroup = new QActionGroup(this);
316     m_lineColorActionGroup->setExclusive(true);
317     connect(m_lineColorActionGroup, &QActionGroup::triggered, this, &HorizontalRuleEntry::lineColorChanged);
318 
319     m_lineColorMenu = new QMenu(i18n("Line Color"));
320     m_lineColorMenu->setIcon(QIcon::fromTheme(QLatin1String("format-fill-color")));
321 
322     QPixmap pix(16,16);
323     QPainter p(&pix);
324 
325     // Create default action
326     p.fillRect(pix.rect(), QApplication::palette().color(QPalette::Text));
327     action = new QAction(QIcon(pix), i18n("Default"), m_lineColorActionGroup);
328     action->setCheckable(true);
329     m_lineColorMenu->addAction(action);
330     if (!m_lineColorCustom)
331         action->setChecked(true);
332 
333 
334     for (int i=0; i<colorsCount; ++i) {
335         p.fillRect(pix.rect(), colors[i]);
336         action = new QAction(QIcon(pix), colorNames[i], m_lineColorActionGroup);
337         action->setCheckable(true);
338         m_lineColorMenu->addAction(action);
339 
340         if (m_lineColorCustom && m_color == colors[i])
341             action->setChecked(true);
342     }
343 
344 
345 
346     m_lineStyleActionGroup = new QActionGroup(this);
347     m_lineStyleActionGroup->setExclusive(true);
348     connect(m_lineStyleActionGroup, &QActionGroup::triggered, this, &HorizontalRuleEntry::lineStyleChanged);
349 
350     m_lineStyleMenu = new QMenu(i18n("Line Style"));
351 
352     for (unsigned int i = 0; i < styleCount; i++)
353     {
354         action = new QAction(styleNames[i], m_lineStyleActionGroup);
355         action->setCheckable(true);
356         m_lineStyleMenu->addAction(action);
357         if (styles[i] == m_style)
358             action->setChecked(true);
359     }
360 }
361