File indexing completed on 2023-11-26 03:46:54
0001 /* 0002 SPDX-License-Identifier: GPL-2.0-or-later 0003 SPDX-FileCopyrightText: 2020 Nikita Sirgienko <warquark@gmail.com> 0004 */ 0005 0006 #include "hierarchyentry.h" 0007 #include "settings.h" 0008 #include "worksheetview.h" 0009 #include "lib/jupyterutils.h" 0010 0011 #include <QJsonObject> 0012 #include <QRegularExpression> 0013 #include <QDrag> 0014 #include <QBitmap> 0015 #include <QMimeData> 0016 #include <QPainter> 0017 #include <QDebug> 0018 #include <QActionGroup> 0019 0020 #include <KLocalizedString> 0021 0022 static QStringList hierarchyLevelNames = {i18n("Chapter"), i18n("Subchapter"), i18n("Section"), i18n("Subsection"), i18n("Paragraph"), i18n("Subparagraph")}; 0023 0024 HierarchyEntry::HierarchyEntry(Worksheet* worksheet) : WorksheetEntry(worksheet) 0025 , m_hierarchyLevelItem(new WorksheetTextItem(this, Qt::NoTextInteraction)) 0026 , m_textItem(new WorksheetTextItem(this, Qt::TextEditorInteraction)) 0027 , m_depth(HierarchyLevel::Chapter) 0028 , m_hierarchyNumber(1) 0029 , m_hidedSubentries(nullptr) 0030 { 0031 // Font and sizes should be regulated from future Settings "Styles" option 0032 m_textItem->enableRichText(false); 0033 0034 connect(m_textItem, &WorksheetTextItem::moveToPrevious, this, &HierarchyEntry::moveToPreviousEntry); 0035 connect(m_textItem, &WorksheetTextItem::moveToNext, this, &HierarchyEntry::moveToNextEntry); 0036 // Modern syntax of signal/stots don't work on this connection (arguments don't match) 0037 connect(m_textItem, SIGNAL(execute()), this, SLOT(evaluate())); 0038 0039 connect(this, &HierarchyEntry::hierarhyEntryNameChange, worksheet, &Worksheet::hierarhyEntryNameChange); 0040 0041 connect(&m_controlElement, &WorksheetControlItem::doubleClick, this, &HierarchyEntry::handleControlElementDoubleClick); 0042 0043 m_setLevelActionGroup = new QActionGroup(this); 0044 m_setLevelActionGroup->setExclusive(true); 0045 connect(m_setLevelActionGroup, &QActionGroup::triggered, this, &HierarchyEntry::setLevelTriggered); 0046 0047 Q_ASSERT(hierarchyLevelNames.size() == (int)HierarchyLevel::EndValue-1); 0048 m_setLevelMenu = new QMenu(i18n("Set Hierarchy Level")); 0049 for (int i = 1; i < (int)HierarchyLevel::EndValue; i++) 0050 { 0051 QAction* action = new QAction(hierarchyLevelNames[i-1], m_setLevelActionGroup); 0052 action->setCheckable(true); 0053 m_setLevelMenu->addAction(action); 0054 } 0055 0056 updateFonts(true); 0057 } 0058 0059 HierarchyEntry::~HierarchyEntry() 0060 { 0061 m_setLevelMenu->deleteLater(); 0062 } 0063 0064 void HierarchyEntry::populateMenu(QMenu* menu, QPointF pos) 0065 { 0066 menu->addMenu(m_setLevelMenu); 0067 //menu->addSeparator(); 0068 WorksheetEntry::populateMenu(menu, pos); 0069 } 0070 0071 bool HierarchyEntry::isEmpty() 0072 { 0073 return m_textItem->document()->isEmpty(); 0074 } 0075 0076 int HierarchyEntry::type() const 0077 { 0078 return Type; 0079 } 0080 0081 bool HierarchyEntry::acceptRichText() 0082 { 0083 return false; 0084 } 0085 0086 bool HierarchyEntry::focusEntry(int pos, qreal xCoord) 0087 { 0088 if (aboutToBeRemoved()) 0089 return false; 0090 m_textItem->setFocusAt(pos, xCoord); 0091 return true; 0092 } 0093 0094 0095 void HierarchyEntry::setContent(const QString& content) 0096 { 0097 m_textItem->setPlainText(content); 0098 } 0099 0100 void HierarchyEntry::setContent(const QDomElement& content, const KZip& file) 0101 { 0102 Q_UNUSED(file); 0103 if(content.firstChildElement(QLatin1String("body")).isNull()) 0104 return; 0105 0106 m_textItem->setPlainText(content.firstChildElement(QLatin1String("body")).text()); 0107 0108 const QDomElement& subentriesMainElem = content.firstChildElement(QLatin1String("HidedSubentries")); 0109 if (!subentriesMainElem.isNull()) 0110 { 0111 m_controlElement.isCollapsable = true; 0112 m_controlElement.isCollapsed = true; 0113 0114 const QDomNodeList& entries = subentriesMainElem.childNodes(); 0115 0116 WorksheetEntry* tail = nullptr; 0117 for (int i = 0; i < entries.size(); i++) 0118 { 0119 const QDomElement& entryElem = entries.at(i).toElement(); 0120 int type = Worksheet::typeForTagName(entryElem.tagName()); 0121 Q_ASSERT(type != 0); 0122 0123 WorksheetEntry* entry = WorksheetEntry::create(type, worksheet()); 0124 entry->setContent(entryElem, file); 0125 entry->hide(); 0126 0127 // set m_hidedSubentries to head element 0128 if (!m_hidedSubentries) 0129 m_hidedSubentries = entry; 0130 0131 if (tail) 0132 { 0133 entry->setPrevious(tail); 0134 tail->setNext(entry); 0135 tail = entry; 0136 } 0137 else 0138 { 0139 entry->setPrevious(nullptr); 0140 tail = entry; 0141 } 0142 } 0143 } 0144 0145 m_depth = (HierarchyLevel)content.attribute(QLatin1String("level")).toInt(); 0146 m_hierarchyNumber = content.attribute(QLatin1String("level-number")).toInt(); 0147 0148 updateFonts(true); 0149 } 0150 0151 void HierarchyEntry::setContentFromJupyter(const QJsonObject& cell) 0152 { 0153 if (Cantor::JupyterUtils::isMarkdownCell(cell)) 0154 { 0155 QJsonObject cantorMetadata = Cantor::JupyterUtils::getCantorMetadata(cell); 0156 m_textItem->setPlainText(cantorMetadata.value(QLatin1String("hierarchy_entry_content")).toString()); 0157 0158 m_depth = (HierarchyLevel)cantorMetadata.value(QLatin1String("level")).toInt(); 0159 m_hierarchyNumber= cantorMetadata.value(QLatin1String("level-number")).toInt(); 0160 0161 updateFonts(true); 0162 } 0163 } 0164 0165 QJsonValue HierarchyEntry::toJupyterJson() 0166 { 0167 QTextDocument* doc = m_textItem->document(); 0168 0169 QJsonObject metadata(jupyterMetadata()); 0170 0171 QString entryData; 0172 QString entryType; 0173 0174 entryType = QLatin1String("markdown"); 0175 0176 // Add raw text of entry to metadata, for situation when 0177 // Cantor opens .ipynb converted from our .cws format 0178 QJsonObject cantorMetadata; 0179 0180 if (Settings::storeTextEntryFormatting()) 0181 { 0182 entryData = doc->toPlainText(); 0183 0184 cantorMetadata.insert(QLatin1String("hierarchy_entry_content"), entryData); 0185 } 0186 else 0187 entryData = doc->toPlainText(); 0188 0189 cantorMetadata.insert(QLatin1String("level"), (int)m_depth); 0190 cantorMetadata.insert(QLatin1String("level-number"), m_hierarchyNumber); 0191 0192 // Don't store subentriesMainElem, because actually too complex 0193 // Maybe this is a place for future work 0194 0195 metadata.insert(Cantor::JupyterUtils::cantorMetadataKey, cantorMetadata); 0196 0197 QJsonObject entry; 0198 entry.insert(QLatin1String("cell_type"), entryType); 0199 entry.insert(QLatin1String("metadata"), metadata); 0200 Cantor::JupyterUtils::setSource(entry, entryData); 0201 0202 return entry; 0203 } 0204 0205 bool HierarchyEntry::isConvertableToHierarchyEntry(const QJsonObject& cell) 0206 { 0207 if (!Cantor::JupyterUtils::isMarkdownCell(cell)) 0208 return false; 0209 0210 QJsonObject cantorMetadata = Cantor::JupyterUtils::getCantorMetadata(cell); 0211 const QJsonValue& textContentValue = cantorMetadata.value(QLatin1String("hierarchy_entry_content")); 0212 0213 if (!textContentValue.isString()) 0214 return false; 0215 0216 const QString& textContent = textContentValue.toString(); 0217 const QString& source = Cantor::JupyterUtils::getSource(cell); 0218 0219 return textContent == source; 0220 } 0221 0222 QDomElement HierarchyEntry::toXml(QDomDocument& doc, KZip* archive) 0223 { 0224 Q_UNUSED(archive); 0225 QDomElement el = doc.createElement(QLatin1String("Hierarchy")); 0226 0227 QDomElement textBodyEl = doc.createElement(QLatin1String("body")); 0228 const QString& text = m_textItem->document()->toPlainText(); 0229 textBodyEl.appendChild(doc.createTextNode(text)); 0230 el.appendChild(textBodyEl); 0231 0232 if(m_hidedSubentries) 0233 { 0234 QDomElement entriesElem = doc.createElement(QLatin1String("HidedSubentries")); 0235 for (WorksheetEntry* entry = m_hidedSubentries; entry; entry = entry->next()) 0236 entriesElem.appendChild(entry->toXml(doc, archive)); 0237 el.appendChild(entriesElem); 0238 } 0239 0240 el.setAttribute(QLatin1String("level"), (int)m_depth); 0241 el.setAttribute(QLatin1String("level-number"), m_hierarchyNumber); 0242 0243 return el; 0244 } 0245 0246 QString HierarchyEntry::toPlain(const QString& commandSep, const QString& commentStartingSeq, const QString& commentEndingSeq) 0247 { 0248 Q_UNUSED(commandSep); 0249 0250 if (commentStartingSeq.isEmpty()) 0251 return QString(); 0252 0253 QString text = m_hierarchyLevelItem->toPlainText() + QLatin1String(" ") + m_textItem->toPlainText(); 0254 if (!commentEndingSeq.isEmpty()) 0255 return commentStartingSeq + text + commentEndingSeq + QLatin1String("\n"); 0256 return commentStartingSeq + text.replace(QLatin1String("\n"), QLatin1String("\n") + commentStartingSeq) + QLatin1String("\n"); 0257 0258 } 0259 0260 bool HierarchyEntry::evaluate(EvaluationOption evalOp) 0261 { 0262 emit hierarhyEntryNameChange(text(), hierarchyText(), ((int)m_depth)-1); 0263 evaluateNext(evalOp); 0264 0265 return true; 0266 } 0267 0268 void HierarchyEntry::updateEntry() 0269 { 0270 } 0271 0272 int HierarchyEntry::searchText(const QString& text, const QString& pattern, 0273 QTextDocument::FindFlags qt_flags) 0274 { 0275 Qt::CaseSensitivity caseSensitivity; 0276 if (qt_flags & QTextDocument::FindCaseSensitively) 0277 caseSensitivity = Qt::CaseSensitive; 0278 else 0279 caseSensitivity = Qt::CaseInsensitive; 0280 0281 int position; 0282 if (qt_flags & QTextDocument::FindBackward) 0283 position = text.lastIndexOf(pattern, -1, caseSensitivity); 0284 else 0285 position = text.indexOf(pattern, 0, caseSensitivity); 0286 0287 return position; 0288 } 0289 0290 WorksheetCursor HierarchyEntry::search(const QString& pattern, unsigned flags, 0291 QTextDocument::FindFlags qt_flags, 0292 const WorksheetCursor& pos) 0293 { 0294 if (!(flags & WorksheetEntry::SearchText) || 0295 (pos.isValid() && pos.entry() != this)) 0296 return WorksheetCursor(); 0297 0298 QTextCursor textCursor = m_textItem->search(pattern, qt_flags, pos); 0299 0300 if (textCursor.isNull()) 0301 return WorksheetCursor(); 0302 else 0303 return WorksheetCursor(this, m_textItem, textCursor); 0304 } 0305 0306 0307 void HierarchyEntry::layOutForWidth(qreal entry_zone_x, qreal w, bool force) 0308 { 0309 if (size().width() == w && m_textItem->pos().x() == entry_zone_x && !force) 0310 return; 0311 0312 const qreal margin = worksheet()->isPrinting() ? 0 : RightMargin; 0313 0314 m_hierarchyLevelItem->setPos(entry_zone_x - m_hierarchyLevelItem->width() - HorizontalSpacing, 0); 0315 0316 m_textItem->setGeometry(entry_zone_x, 0, w - margin - entry_zone_x); 0317 setSize(QSizeF(m_textItem->width() + margin + entry_zone_x, std::max(m_textItem->height(), m_hierarchyLevelItem->height()) + VerticalMargin)); 0318 } 0319 0320 bool HierarchyEntry::wantToEvaluate() 0321 { 0322 return false; 0323 } 0324 0325 QString HierarchyEntry::text() const 0326 { 0327 return m_textItem->toPlainText(); 0328 } 0329 0330 QString HierarchyEntry::hierarchyText() const 0331 { 0332 return m_hierarchyLevelItem->toPlainText(); 0333 } 0334 0335 HierarchyEntry::HierarchyLevel HierarchyEntry::level() const 0336 { 0337 return m_depth; 0338 } 0339 0340 void HierarchyEntry::setLevel(HierarchyEntry::HierarchyLevel depth) 0341 { 0342 m_depth = depth; 0343 } 0344 0345 int HierarchyEntry::hierarchyNumber() const 0346 { 0347 return m_hierarchyNumber; 0348 } 0349 0350 void HierarchyEntry::updateControlElementForHierarchy(qreal responsibilityZoneYEnd, int maxHierarchyDepth, bool haveSubElements) 0351 { 0352 if (!m_hidedSubentries) 0353 m_controlElement.isCollapsable = haveSubElements; 0354 0355 qreal controlZoneStart = m_textItem->x() + m_textItem->width() + HorizontalSpacing; 0356 qreal controlElemenXPos = controlZoneStart + static_cast<double>(maxHierarchyDepth - (int)m_depth + 1) * (ControlElementWidth + ControlElementBorder); 0357 0358 m_controlElement.setRect( 0359 controlElemenXPos, 0, 0360 ControlElementWidth, responsibilityZoneYEnd-y() 0361 ); 0362 m_controlElement.update(); 0363 update(); 0364 } 0365 0366 0367 void HierarchyEntry::updateHierarchyLevel(std::vector<int>& currectNumbers) 0368 { 0369 HierarchyLevel nextLevel = (HierarchyLevel)(currectNumbers.size()+1); 0370 if (m_depth >= nextLevel) 0371 { 0372 for (int i = (int)nextLevel; i <= (int)m_depth; i++) 0373 currectNumbers.push_back(1); 0374 m_hierarchyNumber = 1; 0375 } 0376 else 0377 { 0378 int idx = (int)m_depth - 1; 0379 size_t size = currectNumbers.size(); 0380 for (size_t i = (size_t)idx+1; i < size; i++) 0381 currectNumbers.pop_back(); 0382 currectNumbers[idx] += 1; 0383 m_hierarchyNumber = currectNumbers[idx]; 0384 } 0385 0386 QString s; 0387 Q_ASSERT(currectNumbers.size() != 0); 0388 s += QString::number(currectNumbers.front()); 0389 for (size_t i = 1U; i < currectNumbers.size(); i++) 0390 s += QLatin1String(".") + QString::number(currectNumbers[i]); 0391 0392 qreal previousWidth = m_hierarchyLevelItem->width(); 0393 m_hierarchyLevelItem->setPlainText(s); 0394 m_hierarchyLevelItem->setPos(m_hierarchyLevelItem->x() - (m_hierarchyLevelItem->width() - previousWidth), 0); 0395 updateFonts(); 0396 } 0397 0398 qreal HierarchyEntry::hierarchyItemWidth() 0399 { 0400 return m_hierarchyLevelItem->width() + HorizontalSpacing; 0401 } 0402 0403 void HierarchyEntry::setLevelTriggered(QAction* action) 0404 { 0405 int idx = m_setLevelActionGroup->actions().indexOf(action); 0406 m_depth = (HierarchyLevel)(idx + 1); 0407 0408 worksheet()->updateHierarchyLayout(); 0409 worksheet()->updateLayout(); 0410 } 0411 0412 void HierarchyEntry::recalculateControlGeometry() 0413 { 0414 // do nothing, update the control elements will be done in ;;updateControlElementForHierarchy 0415 } 0416 0417 void HierarchyEntry::startDrag(QPointF grabPos) 0418 { 0419 // We need reset entry cursor manually, because otherwise the entry cursor will be visible on dragable item 0420 worksheet()->resetEntryCursor(); 0421 0422 QDrag* drag = new QDrag(worksheetView()); 0423 const qreal scale = worksheet()->renderer()->scale(); 0424 0425 QRectF hierarchyBound(boundingRect().x(), boundingRect().y(), boundingRect().width(), m_controlElement.boundingRect().height()); 0426 QSizeF hierarchyZoneSize(size().width(), m_controlElement.boundingRect().height()); 0427 0428 QPixmap pixmap((hierarchyZoneSize*scale).toSize()); 0429 pixmap.fill(QColor(255, 255, 255, 0)); 0430 0431 QPainter painter(&pixmap); 0432 const QRectF sceneRect = mapRectToScene(hierarchyBound); 0433 worksheet()->render(&painter, pixmap.rect(), sceneRect); 0434 painter.end(); 0435 0436 QBitmap mask = pixmap.createMaskFromColor(QColor(255, 255, 255), Qt::MaskInColor); 0437 pixmap.setMask(mask); 0438 0439 drag->setPixmap(pixmap); 0440 if (grabPos.isNull()) { 0441 const QPointF scenePos = worksheetView()->sceneCursorPos(); 0442 drag->setHotSpot((mapFromScene(scenePos) * scale).toPoint()); 0443 } else { 0444 drag->setHotSpot((grabPos * scale).toPoint()); 0445 } 0446 drag->setMimeData(new QMimeData()); 0447 0448 worksheet()->startDragWithHierarchy(this, drag, hierarchyZoneSize); 0449 } 0450 0451 void HierarchyEntry::updateFonts(bool force) 0452 { 0453 QFont font; 0454 switch(m_depth) 0455 { 0456 case HierarchyLevel::Chapter: 0457 font = Settings::chapterFontFamily(); 0458 font.setPointSize(Settings::chapterFontSize()); 0459 font.setItalic(Settings::chapterFontItalic()); 0460 font.setBold(Settings::chapterFontBold()); 0461 break; 0462 0463 case HierarchyLevel::Subchapter: 0464 font = Settings::subchapterFontFamily(); 0465 font.setPointSize(Settings::subchapterFontSize()); 0466 font.setItalic(Settings::subchapterFontItalic()); 0467 font.setBold(Settings::subchapterFontBold()); 0468 break; 0469 0470 case HierarchyLevel::Section: 0471 font = Settings::sectionFontFamily(); 0472 font.setPointSize(Settings::sectionFontSize()); 0473 font.setItalic(Settings::sectionFontItalic()); 0474 font.setBold(Settings::sectionFontBold()); 0475 break; 0476 0477 case HierarchyLevel::Subsection: 0478 font = Settings::subsectionFontFamily(); 0479 font.setPointSize(Settings::subsectionFontSize()); 0480 font.setItalic(Settings::subsectionFontItalic()); 0481 font.setBold(Settings::subsectionFontBold()); 0482 break; 0483 0484 case HierarchyLevel::Paragraph: 0485 font = Settings::paragraphFontFamily(); 0486 font.setPointSize(Settings::paragraphFontSize()); 0487 font.setItalic(Settings::paragraphFontItalic()); 0488 font.setBold(Settings::paragraphFontBold()); 0489 break; 0490 0491 case HierarchyLevel::Subparagraph: 0492 font = Settings::subparagraphFontFamily(); 0493 font.setPointSize(Settings::subparagraphFontSize()); 0494 font.setItalic(Settings::subparagraphFontItalic()); 0495 font.setBold(Settings::subparagraphFontBold()); 0496 break; 0497 0498 default: 0499 Q_ASSERT(false); 0500 break; 0501 } 0502 0503 const QFont& currectFont = m_textItem->font(); 0504 bool isSameFont = 0505 currectFont.family() == font.family() 0506 && currectFont.pointSize() == font.pointSize() 0507 && currectFont.bold() == font.bold() 0508 && currectFont.italic() == font.italic(); 0509 0510 if (force || !isSameFont) 0511 { 0512 m_hierarchyLevelItem->setFont(font); 0513 m_hierarchyLevelItem->testSize(); 0514 0515 m_textItem->setFont(font); 0516 // And update current text of item 0517 QTextCursor cursor = m_textItem->textCursor(); 0518 cursor.select(QTextCursor::Document); 0519 QTextCharFormat format = cursor.charFormat(); 0520 format.setFont(font); 0521 cursor.setCharFormat(format); 0522 m_textItem->testSize(); 0523 0524 // Recalculate size (because it can changed due font changes) and update worksheet layout 0525 recalculateSize(); 0526 worksheet()->updateEntrySize(this); 0527 } 0528 } 0529 0530 0531 void HierarchyEntry::handleControlElementDoubleClick() 0532 { 0533 qDebug() << "HierarchyEntry::handleControlElementDoubleClick"; 0534 if (m_controlElement.isCollapsed) 0535 { 0536 worksheet()->insertSubentriesForHierarchy(this, m_hidedSubentries); 0537 m_controlElement.isCollapsed = false; 0538 } 0539 else 0540 { 0541 m_hidedSubentries = worksheet()->cutSubentriesForHierarchy(this); 0542 m_controlElement.isCollapsed = true; 0543 } 0544 0545 m_controlElement.update(); 0546 0547 worksheet()->updateLayout(); 0548 worksheet()->updateHierarchyLayout(); 0549 } 0550 0551 void HierarchyEntry::updateAfterSettingsChanges() 0552 { 0553 updateFonts(); 0554 }