diff --git a/src/imageresultitem.cpp b/src/imageresultitem.cpp index 1f129e60..d4199d39 100644 --- a/src/imageresultitem.cpp +++ b/src/imageresultitem.cpp @@ -1,111 +1,112 @@ /* 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 */ #include "imageresultitem.h" #include "commandentry.h" #include "lib/imageresult.h" #include "lib/epsresult.h" #include #include #include #include ImageResultItem::ImageResultItem(QGraphicsObject* parent, Cantor::Result* result) : WorksheetImageItem(parent), ResultItem(result) { update(); } double ImageResultItem::setGeometry(double x, double y, double w) { Q_UNUSED(w); setPos(x,y); return height(); } void ImageResultItem::populateMenu(QMenu* menu, QPointF pos) { ResultItem::addCommonActions(this, menu); menu->addSeparator(); qDebug() << "populate Menu"; emit menuCreated(menu, mapToParent(pos)); } void ImageResultItem::update() { Q_ASSERT(m_result->type() == Cantor::ImageResult::Type || m_result->type() == Cantor::EpsResult::Type); switch(m_result->type()) { case Cantor::ImageResult::Type: { QSize displaySize = static_cast(m_result)->displaySize(); if (displaySize.isValid()) setImage(m_result->data().value(), displaySize); else setImage(m_result->data().value()); } break; case Cantor::EpsResult::Type: { Cantor::EpsResult* epsResult = static_cast(m_result); #ifdef WITH_EPS - if (!epsResult->image().isNull() && worksheet()->renderer()->scale() == 1.0) + bool cacheVersionEnough = worksheet()->renderer()->scale() == 1.0 && !worksheet()->isPrinting(); + if (!epsResult->image().isNull() && cacheVersionEnough) setImage(epsResult->image()); else setEps(m_result->data().toUrl()); #else setImage(epsResult->image()); #endif } break; default: break; } } QRectF ImageResultItem::boundingRect() const { return QRectF(0, 0, width(), height()); } double ImageResultItem::width() const { return WorksheetImageItem::width(); } double ImageResultItem::height() const { return WorksheetImageItem::height(); } void ImageResultItem::saveResult() { Cantor::Result* res = result(); const QString& filename=QFileDialog::getSaveFileName(worksheet()->worksheetView(), i18n("Save result"), QString(), res->mimeType()); qDebug()<<"saving result to "<save(filename); } void ImageResultItem::deleteLater() { WorksheetImageItem::deleteLater(); } diff --git a/src/textentry.cpp b/src/textentry.cpp index 66aea486..88017397 100644 --- a/src/textentry.cpp +++ b/src/textentry.cpp @@ -1,593 +1,590 @@ /* 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 Copyright (C) 2012 Martin Kuettler */ #include "textentry.h" #include "worksheettextitem.h" #include "lib/renderer.h" #include "latexrenderer.h" #include "lib/jupyterutils.h" #include "mathrender.h" #include "settings.h" #include #include #include #include #include #include #include #include #include #include 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 convertation 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); - qDebug() << cursor.position(); 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(); - qDebug() << html; 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 setted, 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("]*>\\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 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(); - qDebug() << html; 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))); 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)); } 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 result) { if (!result->successful) { - qDebug() << "MarkdownEntry: math render failed with message" << result->errorMessage; + 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); } diff --git a/src/worksheet.cpp b/src/worksheet.cpp index 36fbf081..7c39bd5b 100644 --- a/src/worksheet.cpp +++ b/src/worksheet.cpp @@ -1,2256 +1,2258 @@ /* 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 Copyright (C) 2012 Martin Kuettler */ #include "worksheet.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 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) : QGraphicsScene(parent) { m_session = backend->createSession(); m_highlighter = nullptr; m_firstEntry = nullptr; m_lastEntry = nullptr; m_lastFocusedTextItem = nullptr; m_dragEntry = nullptr; m_placeholderEntry = nullptr; - m_viewWidth = 0; - m_protrusion = 0; m_dragScrollTimer = nullptr; m_choosenCursorEntry = nullptr; m_isCursorEntryAfterLastEntry = false; + m_viewWidth = 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_isPrinting = false; m_readOnly = false; m_isLoadingFromFile = false; m_jupyterMetadata = nullptr; enableHighlighting(Settings::self()->highlightDefault()); enableCompletion(Settings::self()->completionDefault()); enableExpressionNumbering(Settings::self()->expressionNumberingDefault()); enableAnimations(Settings::self()->animationDefault()); enableEmbeddedMath(Settings::self()->embeddedMathDefault()); } Worksheet::~Worksheet() { // This is necessary, because a SeachBar 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; if (m_session && m_session->status() != Cantor::Session::Disable) m_session->logout(); if (m_session) { disconnect(m_session, 0, 0, 0); if (m_session->status() != Cantor::Session::Disable) m_session->logout(); m_session->deleteLater(); m_session = nullptr; } if (m_jupyterMetadata) delete m_jupyterMetadata; } void Worksheet::loginToSession() { m_session->login(); #ifdef WITH_EPS session()->setTypesettingEnabled(Settings::self()->typesetDefault()); #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, m_viewWidth + m_protrusion, y)); + + 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, m_viewWidth + m_protrusion, y)); + setSceneRect(QRectF(0, 0, sceneRect().width(), y)); if (cursorRectVisible) makeVisible(worksheetCursor()); else if (atEnd) worksheetView()->scrollToEnd(); drawEntryCursor(); } -void Worksheet::addProtrusion(qreal width) +void Worksheet::addSubelementWidth(qreal width) { - if (m_itemProtrusions.contains(width)) - ++m_itemProtrusions[width]; + if (m_itemWidths.contains(width)) + ++m_itemWidths[width]; else - m_itemProtrusions.insert(width, 1); - if (width > m_protrusion) { - m_protrusion = width; + m_itemWidths.insert(width, 1); + + if (width > m_viewWidth) { qreal y = lastEntry() ? lastEntry()->size().height() + lastEntry()->y() : 0; - setSceneRect(QRectF(0, 0, m_viewWidth + m_protrusion, y)); + setSceneRect(QRectF(0, 0, width + LeftMargin + RightMargin, y)); } } -void Worksheet::updateProtrusion(qreal oldWidth, qreal newWidth) +void Worksheet::updateSubelementWidth(qreal oldWidth, qreal newWidth) { - removeProtrusion(oldWidth); - addProtrusion(newWidth); + if (oldWidth != newWidth) + { + removeSubelementWidth(oldWidth); + addSubelementWidth(newWidth); + } } -void Worksheet::removeProtrusion(qreal width) +void Worksheet::removeSubelementWidth(qreal width) { - if (--m_itemProtrusions[width] == 0) { - m_itemProtrusions.remove(width); - if (width == m_protrusion) { - qreal max = -1; - for (qreal p : m_itemProtrusions.keys()) { + if (m_itemWidths.contains(width) && --m_itemWidths[width] == 0) { + m_itemWidths.remove(width); + + if (width + LeftMargin + RightMargin == sceneRect().width()) + { + qreal max = 0; + for (qreal p : m_itemWidths.keys()) { if (p > max) max = p; } - m_protrusion = max; - qreal y = lastEntry()->size().height() + lastEntry()->y(); - setSceneRect(QRectF(0, 0, m_viewWidth + m_protrusion, y)); + qreal y = lastEntry() ? lastEntry()->size().height() + lastEntry()->y() : 0; + setSceneRect(QRectF(0, 0, max + 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(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() { QGraphicsItem* item = focusItem(); // Entry cursor activate if (m_choosenCursorEntry || m_isCursorEntryAfterLastEntry) return nullptr; 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(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(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); 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 > formats; if (oldDocument) { for (QTextBlock b = oldDocument->firstBlock(); b.isValid(); b = b.next()) { formats.append(b.layout()->additionalFormats()); } } // Not every highlighter is a Cantor::DefaultHighligther (e.g. the // highlighter for KAlgebra) Cantor::DefaultHighlighter* hl = qobject_cast(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()->setAdditionalFormats(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()->clearAdditionalFormats(); } 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(); - qDebug()<<"content: "<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(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: " <) stream << out.replace(QLatin1String("&"), QLatin1String("&")) .replace(QLatin1String(">"), QLatin1String(">")) .replace(QLatin1String("<"), 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("Cantor")); return false; } bool rc = load(&file); if (rc && !m_readOnly) m_session->setWorksheetPath(filename); return rc; } void Worksheet::load(QByteArray* data) { QBuffer buf(data); load(&buf); } bool Worksheet::load(QIODevice* device) { if (!device->isReadable()) { KMessageBox::error(worksheetView(), i18n("Couldn't open the selected file for reading"), i18n("Cantor")); 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("Cantor")); 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("Cantor")); return false; } const KArchiveFile* content = static_cast(contentEntry); QByteArray data = content->data(); -// qDebug()<<"read: "<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("Cantor")); 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 eidted/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(); if (!m_readOnly) m_session=b->createSession(); 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; //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; } bool Worksheet::loadJupyterNotebook(const QJsonDocument& doc) { m_type = Type::JupyterNotebook; int nbformatMajor, nbformatMinor; if (!Cantor::JupyterUtils::isJupyterNotebook(doc)) { // Two possiblities: 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("The file is old Jupyter notebook (found version %1.%2), which isn't supported by Cantor",nbformatMajor, nbformatMinor ), i18n("Cantor")); } 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("Cantor doesn't support Jupyter notebooks with version higher 4.5 (detected %1.%2)", nbformatMajor, nbformatMinor), i18n("Cantor") ); 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("Cantor")); 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("Cantor")); 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(); if (!m_readOnly) m_session=backend->createSession(); 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; 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("Cantor")); else KMessageBox::error(worksheetView(), i18n("Invalid Jupyter notebook scheme: %1", additionalInfo), i18n("Cantor")); } void Worksheet::gotResult(Cantor::Expression* expr) { if(expr==nullptr) expr=qobject_cast(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("\\1")); help.replace(QRegExp(QLatin1String("\\$([^\\$])\\$")), QLatin1String("\\1")); 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(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); else menu->addAction(QIcon::fromTheme(QLatin1String("process-stop")), i18n("Interrupt"), this, SLOT(interrupt()), 0); menu->addSeparator(); if (entry) { QMenu* insert = new QMenu(menu); QMenu* insertBefore = new QMenu(menu); 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())); 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(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())); } } 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); } 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(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(sender()); if (!action) return; // delete the old shortcuts of this action QList 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(prev); m_placeholderEntry->changeSize(m_dragEntry->size()); } else if (next && next->type() == PlaceHolderEntry::Type && (!next->aboutToBeRemoved() || next->stopRemoving())) { m_placeholderEntry = qgraphicsitem_cast(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; } diff --git a/src/worksheet.h b/src/worksheet.h index 02ce0df0..f33624d6 100644 --- a/src/worksheet.h +++ b/src/worksheet.h @@ -1,325 +1,334 @@ /* 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 Copyright (C) 2012 Martin Kuettler */ #ifndef WORKSHEET_H #define WORKSHEET_H #include #include #include #include #include #include #include #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); ~Worksheet() override; Cantor::Session* session(); void loginToSession(); bool isRunning(); bool isReadOnly(); bool showExpressionIds(); bool animationsEnabled(); bool embeddedMathEnabled(); bool isPrinting(); - void setViewSize(qreal w, qreal h, qreal s, bool forceUpdate = false); - 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(); - void addProtrusion(qreal width); - void updateProtrusion(qreal oldWidth, qreal newWidth); - void removeProtrusion(qreal width); + /** + * 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 propper scene size. + */ + /// First information stream + void setViewSize(qreal w, qreal h, qreal s, bool forceUpdate = false); + + /// Second information stream + void addSubelementWidth(qreal width); + void updateSubelementWidth(qreal oldWidth, qreal newWidth); + void removeSubelementWidth(qreal width); bool isShortcut(const QKeySequence&); void setType(Worksheet::Type type); Worksheet::Type type() const; // 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); 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(); 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); 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()); 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; double m_viewWidth; - double m_protrusion; - QMap m_itemProtrusions; + QMap m_itemWidths; QMap m_shortcuts; QList 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_completionEnabled; bool m_embeddedMathEnabled; bool m_showExpressionIds; bool m_animationsEnabled; bool m_isPrinting; bool m_isLoadingFromFile; bool m_readOnly; Type m_type = CantorWorksheet; QString m_backendName; QJsonObject* m_jupyterMetadata; }; #endif // WORKSHEET_H diff --git a/src/worksheetimageitem.cpp b/src/worksheetimageitem.cpp index 6f3301e5..4b667d26 100644 --- a/src/worksheetimageitem.cpp +++ b/src/worksheetimageitem.cpp @@ -1,172 +1,172 @@ /* 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 */ #include "worksheetimageitem.h" #include "worksheet.h" #include #include #include #include #include #include WorksheetImageItem::WorksheetImageItem(QGraphicsObject* parent) : QGraphicsObject(parent) { connect(this, SIGNAL(menuCreated(QMenu*,QPointF)), parent, SLOT(populateMenu(QMenu*,QPointF)), Qt::DirectConnection); - m_maxWidth = 0; } WorksheetImageItem::~WorksheetImageItem() { - if (worksheet() && m_maxWidth > 0 && width() > m_maxWidth) - worksheet()->removeProtrusion(width() - m_maxWidth); + qreal width = scenePos().x() + this->width(); + if (worksheet() && width > 0) + worksheet()->removeSubelementWidth(width); } int WorksheetImageItem::type() const { return Type; } bool WorksheetImageItem::imageIsValid() { return !m_pixmap.isNull(); } qreal WorksheetImageItem::setGeometry(qreal x, qreal y, qreal w, bool centered) { if (width() <= w && centered) { setPos(x + w/2 - width()/2, y); } else { + qreal oldNeededWidth = scenePos().x() + width(); + qreal newNeededWidth = scenePos().x() + w; setPos(x, y); - if (m_maxWidth < width()) - worksheet()->updateProtrusion(width() - m_maxWidth, width() - w); + if (newNeededWidth > 0) + worksheet()->updateSubelementWidth(oldNeededWidth , newNeededWidth); else - worksheet()->addProtrusion(width() - w); + worksheet()->addSubelementWidth(oldNeededWidth ); } - m_maxWidth = w; return height(); } qreal WorksheetImageItem::height() const { return m_size.height(); } qreal WorksheetImageItem::width() const { return m_size.width(); } QSizeF WorksheetImageItem::size() { return m_size; } void WorksheetImageItem::setSize(QSizeF size) { - qreal oldProtrusion = x() + m_size.width() - m_maxWidth; - qreal newProtrusion = x() + size.width() - m_maxWidth; - if (oldProtrusion > 0) { - if (newProtrusion > 0) - worksheet()->updateProtrusion(oldProtrusion, newProtrusion); + qreal oldNeededWidth = scenePos().x() + m_size.width(); + qreal newNeededWidth = scenePos().x() + size.width(); + if (oldNeededWidth > 0) { + if (newNeededWidth > 0) + worksheet()->updateSubelementWidth(oldNeededWidth, newNeededWidth); else - worksheet()->removeProtrusion(oldProtrusion); + worksheet()->removeSubelementWidth(oldNeededWidth); } else { - if (newProtrusion > 0) - worksheet()->addProtrusion(newProtrusion); + if (newNeededWidth > 0) + worksheet()->addSubelementWidth(newNeededWidth); } m_size = size; } QSize WorksheetImageItem::imageSize() { return m_pixmap.size(); } QRectF WorksheetImageItem::boundingRect() const { return QRectF(QPointF(0, 0), m_size); } #include void WorksheetImageItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { Q_UNUSED(option); Q_UNUSED(widget); painter->drawPixmap(QRectF(QPointF(0,0), m_size), m_pixmap, m_pixmap.rect()); } void WorksheetImageItem::setEps(const QUrl& url) { const QImage img = worksheet()->renderer()->renderToImage(url, Cantor::Renderer::EPS, &m_size); m_pixmap = QPixmap::fromImage(img.convertToFormat(QImage::Format_ARGB32)); } void WorksheetImageItem::setImage(QImage img) { m_pixmap = QPixmap::fromImage(img); setSize(m_pixmap.size()); } void WorksheetImageItem::setImage(QImage img, QSize displaySize) { m_pixmap = QPixmap::fromImage(img); - m_pixmap = m_pixmap.scaled(displaySize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); - setSize(m_pixmap.size()); + setSize(displaySize); } void WorksheetImageItem::setPixmap(QPixmap pixmap) { m_pixmap = pixmap; } QPixmap WorksheetImageItem::pixmap() const { return m_pixmap; } void WorksheetImageItem::populateMenu(QMenu* menu, QPointF pos) { emit menuCreated(menu, mapToParent(pos)); } void WorksheetImageItem::contextMenuEvent(QGraphicsSceneContextMenuEvent *event) { QMenu *menu = worksheet()->createContextMenu(); populateMenu(menu, event->pos()); menu->popup(event->screenPos()); } Worksheet* WorksheetImageItem::worksheet() { return qobject_cast(scene()); } diff --git a/src/worksheetimageitem.h b/src/worksheetimageitem.h index d6fd1ed1..53e284b2 100644 --- a/src/worksheetimageitem.h +++ b/src/worksheetimageitem.h @@ -1,80 +1,79 @@ /* 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 */ #ifndef WORKSHEETIMAGEITEM_H #define WORKSHEETIMAGEITEM_H #include #include class Worksheet; class QImage; class QGraphicsSceneContextMenuEvent; class QMenu; class WorksheetImageItem : public QGraphicsObject { Q_OBJECT public: explicit WorksheetImageItem(QGraphicsObject* parent); ~WorksheetImageItem() override; enum {Type = UserType + 101}; int type() const override; bool imageIsValid(); virtual qreal setGeometry(qreal x, qreal y, qreal w, bool centered=false); qreal height() const; qreal width() const; QSizeF size(); void setSize(QSizeF size); QSize imageSize(); QRectF boundingRect() const override; void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override; void setEps(const QUrl &url); void setImage(QImage img); void setImage(QImage img, QSize displaySize); void setPixmap(QPixmap pixmap); QPixmap pixmap() const; virtual void populateMenu(QMenu* menu, QPointF pos); Worksheet* worksheet(); Q_SIGNALS: void sizeChanged(); void menuCreated(QMenu*, QPointF); protected: void contextMenuEvent(QGraphicsSceneContextMenuEvent*) override; private: QPixmap m_pixmap; QSizeF m_size; - qreal m_maxWidth; }; #endif //WORKSHEETIMAGEITEM_H diff --git a/src/worksheettextitem.cpp b/src/worksheettextitem.cpp index 9e089af8..c65d493a 100644 --- a/src/worksheettextitem.cpp +++ b/src/worksheettextitem.cpp @@ -1,928 +1,926 @@ /* 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 */ #include "worksheettextitem.h" #include "worksheet.h" #include "worksheetentry.h" #include "lib/renderer.h" #include "worksheetcursor.h" #include "extended_document.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include WorksheetTextItem::WorksheetTextItem(QGraphicsObject* parent, Qt::TextInteractionFlags ti) : QGraphicsTextItem(parent) { setDocument(new ExtendedDocument(this)); setTextInteractionFlags(ti); if (ti & Qt::TextEditable) { setCursor(Qt::IBeamCursor); connect(this, SIGNAL(sizeChanged()), parent, SLOT(recalculateSize())); } m_completionEnabled = false; m_completionActive = false; m_itemDragable = false; m_richTextEnabled = false; m_size = document()->size();; - m_maxWidth = -1; setAcceptDrops(true); setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); connect(document(), SIGNAL(contentsChanged()), this, SLOT(testSize())); connect(this, SIGNAL(menuCreated(QMenu*,QPointF)), parent, SLOT(populateMenu(QMenu*,QPointF)), Qt::DirectConnection); connect(this, SIGNAL(deleteEntry()), parent, SLOT(startRemoving())); connect(this, &WorksheetTextItem::cursorPositionChanged, this, &WorksheetTextItem::updateRichTextActions); connect(document(), SIGNAL(undoAvailable(bool)), this, SIGNAL(undoAvailable(bool))); connect(document(), SIGNAL(redoAvailable(bool)), this, SIGNAL(redoAvailable(bool))); } WorksheetTextItem::~WorksheetTextItem() { if (worksheet() && this == worksheet()->lastFocusedTextItem()) worksheet()->updateFocusedTextItem(nullptr); - if (worksheet() && m_maxWidth > 0 && width() > m_maxWidth) - worksheet()->removeProtrusion(width() - m_maxWidth); + + qreal width = scenePos().x() + m_size.width() - 4; + if (worksheet() && width > 0) + worksheet()->removeSubelementWidth(width); } int WorksheetTextItem::type() const { return Type; } /* void WorksheetTextItem::setHeight() { m_height = height(); } */ void WorksheetTextItem::testSize() { qreal h = document()->size().height(); if (h != m_size.height()) { emit sizeChanged(); m_size.setHeight(h); } qreal w = document()->size().width(); if (w != m_size.width()) { - if (m_maxWidth > 0) { - qreal oldDiff = m_size.width() - m_maxWidth; - qreal newDiff = w - m_maxWidth; - if (w > m_maxWidth) { - if (m_size.width() > m_maxWidth) - worksheet()->updateProtrusion(oldDiff, newDiff); - else - worksheet()->addProtrusion(newDiff); - } else if (m_size.width() > m_maxWidth) { - worksheet()->removeProtrusion(oldDiff); - } + qreal oldWidth = scenePos().x() + m_size.width() - 4; + qreal newWidth = scenePos().x() + w - 4; + if (newWidth > 0) { + if (oldWidth > 0) + worksheet()->updateSubelementWidth(oldWidth, newWidth); + else + worksheet()->addSubelementWidth(newWidth); + } else if (oldWidth > 0) { + worksheet()->removeSubelementWidth(oldWidth); } + m_size.setWidth(w); } } qreal WorksheetTextItem::setGeometry(qreal x, qreal y, qreal w, bool centered) { if (m_size.width() < w && centered) setPos(x + w/2 - m_size.width()/2, y); else setPos(x,y); - qreal oldDiff = 0; - if (m_maxWidth > 0 && width() > m_maxWidth) - oldDiff = width() - m_maxWidth; - m_maxWidth = w; + // Strange: if I use the same logic as for ImageItem (with scenePos.x() + width) + // Cantor always have scrollbar for a few pixels + // So I always subtract the few pixels + qreal oldWidth = scenePos().x() + m_size.width() - 4; setTextWidth(w); m_size = document()->size(); + qreal newWidth = scenePos().x() + m_size.width() - 4; - if (oldDiff) { - if (m_size.width() > m_maxWidth) { - qreal newDiff = m_size.width() - m_maxWidth; - worksheet()->updateProtrusion(oldDiff, newDiff); - } else { - worksheet()->removeProtrusion(oldDiff); - } - } else if (m_size.width() > m_maxWidth) { - qreal newDiff = m_size.width() - m_maxWidth; - worksheet()->addProtrusion(newDiff); + if (newWidth > 0) { + if (oldWidth > 0) + worksheet()->updateSubelementWidth(oldWidth, newWidth); + else + worksheet()->addSubelementWidth(newWidth); + } else if (oldWidth > 0) { + worksheet()->removeSubelementWidth(oldWidth); } return m_size.height(); } void WorksheetTextItem::populateMenu(QMenu* menu, QPointF pos) { qDebug() << "populate Menu"; QAction * cut = KStandardAction::cut(this, SLOT(cut()), menu); QAction * copy = KStandardAction::copy(this, SLOT(copy()), menu); QAction * paste = KStandardAction::paste(this, SLOT(paste()), menu); if (!textCursor().hasSelection()) { cut->setEnabled(false); copy->setEnabled(false); } if (QApplication::clipboard()->text().isEmpty()) { paste->setEnabled(false); } bool actionAdded = false; if (isEditable()) { menu->addAction(cut); actionAdded = true; } if (!m_itemDragable && (flags() & Qt::TextSelectableByMouse)) { menu->addAction(copy); actionAdded = true; } if (isEditable()) { menu->addAction(paste); actionAdded = true; } if (actionAdded) menu->addSeparator(); emit menuCreated(menu, mapToParent(pos)); } QKeyEvent* WorksheetTextItem::eventForStandardAction(KStandardAction::StandardAction actionID) { // there must be a better way to get the shortcut... QAction * action = KStandardAction::create(actionID, this, SLOT(copy()), this); QKeySequence keySeq = action->shortcut(); // we do not support key sequences with multiple keys here int code = keySeq[0]; const int ModMask = Qt::ShiftModifier | Qt::ControlModifier | Qt::AltModifier | Qt::MetaModifier; const int KeyMask = ~ModMask; QKeyEvent* event = new QKeyEvent(QEvent::KeyPress, code & KeyMask, QFlags(code & ModMask)); delete action; return event; } void WorksheetTextItem::cut() { if (richTextEnabled()) { QKeyEvent* event = eventForStandardAction(KStandardAction::Cut); QApplication::sendEvent(worksheet(), event); delete event; } else { copy(); textCursor().removeSelectedText(); } } void WorksheetTextItem::paste() { if (richTextEnabled()) { QKeyEvent* event = eventForStandardAction(KStandardAction::Paste); QApplication::sendEvent(worksheet(), event); delete event; } else { textCursor().insertText(QApplication::clipboard()->text()); } } void WorksheetTextItem::copy() { if (richTextEnabled()) { QKeyEvent* event = eventForStandardAction(KStandardAction::Copy); QApplication::sendEvent(worksheet(), event); delete event; } else { if (!textCursor().hasSelection()) return; QString text = resolveImages(textCursor()); text.replace(QChar::ParagraphSeparator, QLatin1Char('\n')); text.replace(QChar::LineSeparator, QLatin1Char('\n')); QApplication::clipboard()->setText(text); } } void WorksheetTextItem::undo() { document()->undo(); } void WorksheetTextItem::redo() { document()->redo(); } void WorksheetTextItem::clipboardChanged() { if (isEditable()) emit pasteAvailable(!QApplication::clipboard()->text().isEmpty()); } void WorksheetTextItem::selectionChanged() { emit copyAvailable(textCursor().hasSelection()); if (isEditable()) emit cutAvailable(textCursor().hasSelection()); } QString WorksheetTextItem::resolveImages(const QTextCursor& cursor) { int start = cursor.selectionStart(); int end = cursor.selectionEnd(); const QString repl = QString(QChar::ObjectReplacementCharacter); QString result; QTextCursor cursor1 = textCursor(); cursor1.setPosition(start); QTextCursor cursor2 = document()->find(repl, cursor1); for (; !cursor2.isNull() && cursor2.selectionEnd() <= end; cursor2 = document()->find(repl, cursor1)) { cursor1.setPosition(cursor2.selectionStart(), QTextCursor::KeepAnchor); result += cursor1.selectedText(); QVariant var = cursor2.charFormat().property(Cantor::Renderer::Delimiter); QString delim; if (var.isValid()) delim = var.value(); else delim = QLatin1String(""); result += delim + cursor2.charFormat().property(Cantor::Renderer::Code).value() + delim; cursor1.setPosition(cursor2.selectionEnd()); } cursor1.setPosition(end, QTextCursor::KeepAnchor); result += cursor1.selectedText(); return result; } void WorksheetTextItem::setCursorPosition(const QPointF& pos) { QTextCursor cursor = cursorForPosition(pos); setTextCursor(cursor); emit cursorPositionChanged(cursor); //setLocalCursorPosition(mapFromParent(pos)); } QPointF WorksheetTextItem::cursorPosition() const { return mapToParent(localCursorPosition()); } void WorksheetTextItem::setLocalCursorPosition(const QPointF& pos) { int p = document()->documentLayout()->hitTest(pos, Qt::FuzzyHit); QTextCursor cursor = textCursor(); cursor.setPosition(p); setTextCursor(cursor); emit cursorPositionChanged(cursor); } QPointF WorksheetTextItem::localCursorPosition() const { QTextCursor cursor = textCursor(); QTextBlock block = cursor.block(); int p = cursor.position() - block.position(); QTextLine line = block.layout()->lineForTextPosition(p); if (!line.isValid()) // can this happen? return block.layout()->position(); return QPointF(line.cursorToX(p), line.y() + line.height()); } QRectF WorksheetTextItem::sceneCursorRect(QTextCursor cursor) const { return mapRectToScene(cursorRect(cursor)); } QRectF WorksheetTextItem::cursorRect(QTextCursor cursor) const { if (cursor.isNull()) cursor = textCursor(); QTextCursor startCursor = cursor; startCursor.setPosition(cursor.selectionStart()); QTextBlock block = startCursor.block(); if (!block.layout()) return mapRectToScene(boundingRect()); int p = startCursor.position() - block.position(); QTextLine line = block.layout()->lineForTextPosition(p); QRectF r1(line.cursorToX(p), line.y(), 1, line.height()+line.leading()); if (!cursor.hasSelection()) return r1; QTextCursor endCursor = cursor; endCursor.setPosition(cursor.selectionEnd()); block = endCursor.block(); p = endCursor.position() - block.position(); line = block.layout()->lineForTextPosition(p); QRectF r2(line.cursorToX(p), line.y(), 1, line.height()+line.leading()); if (r1.y() == r2.y()) return r1.united(r2); else return QRectF(x(), qMin(r1.y(), r2.y()), boundingRect().width(), qMax(r1.y() + r1.height(), r2.y() + r2.height())); } QTextCursor WorksheetTextItem::cursorForPosition(const QPointF& pos) const { QPointF lpos = mapFromParent(pos); int p = document()->documentLayout()->hitTest(lpos, Qt::FuzzyHit); QTextCursor cursor = textCursor(); cursor.setPosition(p); return cursor; } bool WorksheetTextItem::isEditable() { return textInteractionFlags() & Qt::TextEditable; } void WorksheetTextItem::setBackgroundColor(const QColor& color) { m_backgroundColor = color; } const QColor& WorksheetTextItem::backgroundColor() const { return m_backgroundColor; } bool WorksheetTextItem::richTextEnabled() { return m_richTextEnabled; } void WorksheetTextItem::enableCompletion(bool b) { m_completionEnabled = b; } void WorksheetTextItem::activateCompletion(bool b) { m_completionActive = b; } void WorksheetTextItem::setItemDragable(bool b) { m_itemDragable = b; } void WorksheetTextItem::enableRichText(bool b) { m_richTextEnabled = b; } void WorksheetTextItem::setFocusAt(int pos, qreal xCoord) { QTextCursor cursor = textCursor(); if (pos == TopLeft) { cursor.movePosition(QTextCursor::Start); } else if (pos == BottomRight) { cursor.movePosition(QTextCursor::End); } else { QTextLine line; if (pos == TopCoord) { line = document()->firstBlock().layout()->lineAt(0); } else { QTextLayout* layout = document()->lastBlock().layout(); qDebug() << document()->blockCount() << "blocks"; qDebug() << document()->lastBlock().lineCount() << "lines in last block"; line = layout->lineAt(document()->lastBlock().lineCount()-1); } qreal x = mapFromScene(xCoord, 0).x(); int p = line.xToCursor(x); cursor.setPosition(p); // Hack: The code for selecting the last line above does not work. // This is a workaround if (pos == BottomCoord) while (cursor.movePosition(QTextCursor::Down)) ; } setTextCursor(cursor); emit cursorPositionChanged(cursor); setFocus(); } Cantor::Session* WorksheetTextItem::session() { return worksheet()->session(); } void WorksheetTextItem::keyPressEvent(QKeyEvent *event) { switch (event->key()) { case Qt::Key_Left: if (event->modifiers() == Qt::NoModifier && textCursor().atStart()) { emit moveToPrevious(BottomRight, 0); qDebug()<<"Reached leftmost valid position"; return; } break; case Qt::Key_Right: if (event->modifiers() == Qt::NoModifier && textCursor().atEnd()) { emit moveToNext(TopLeft, 0); qDebug()<<"Reached rightmost valid position"; return; } break; case Qt::Key_Up: if (event->modifiers() == Qt::NoModifier && !textCursor().movePosition(QTextCursor::Up)) { qreal x = mapToScene(localCursorPosition()).x(); emit moveToPrevious(BottomCoord, x); qDebug()<<"Reached topmost valid position" << localCursorPosition().x(); return; } break; case Qt::Key_Down: if (event->modifiers() == Qt::NoModifier && !textCursor().movePosition(QTextCursor::Down)) { qreal x = mapToScene(localCursorPosition()).x(); emit moveToNext(TopCoord, x); qDebug()<<"Reached bottommost valid position" << localCursorPosition().x(); return; } break; case Qt::Key_Enter: case Qt::Key_Return: if (event->modifiers() == Qt::NoModifier && m_completionActive) { emit applyCompletion(); return; } break; case Qt::Key_Tab: qDebug() << "Tab"; break; default: break; } int p = textCursor().position(); bool b = textCursor().hasSelection(); QGraphicsTextItem::keyPressEvent(event); if (p != textCursor().position()) emit cursorPositionChanged(textCursor()); if (b != textCursor().hasSelection()) selectionChanged(); } bool WorksheetTextItem::sceneEvent(QEvent *event) { if (event->type() == QEvent::KeyPress) { // QGraphicsTextItem's TabChangesFocus feature prevents calls to // keyPressEvent for Tab, even when it's turned off. So we got to catch // that here. QKeyEvent* kev = static_cast(event); if (kev->key() == Qt::Key_Tab && kev->modifiers() == Qt::NoModifier) { emit tabPressed(); return true; } else if ((kev->key() == Qt::Key_Tab && kev->modifiers() == Qt::ShiftModifier) || kev->key() == Qt::Key_Backtab) { emit backtabPressed(); return true; } } else if (event->type() == QEvent::ShortcutOverride) { QKeyEvent* kev = static_cast(event); QKeySequence seq(kev->key() + kev->modifiers()); if (worksheet()->isShortcut(seq)) { qDebug() << "ShortcutOverride" << kev->key() << kev->modifiers(); kev->ignore(); return false; } } return QGraphicsTextItem::sceneEvent(event); } void WorksheetTextItem::focusInEvent(QFocusEvent *event) { QGraphicsTextItem::focusInEvent(event); //parentItem()->ensureVisible(QRectF(), 0, 0); WorksheetEntry* entry = qobject_cast(parentObject()); WorksheetCursor c(entry, this, textCursor()); // No need make the text item visible // if we just hide/show window, it it not necessary if (event->reason() != Qt::ActiveWindowFocusReason) worksheet()->makeVisible(c); worksheet()->updateFocusedTextItem(this); connect(QApplication::clipboard(), SIGNAL(dataChanged()), this, SLOT(clipboardChanged())); emit receivedFocus(this); emit cursorPositionChanged(textCursor()); } void WorksheetTextItem::focusOutEvent(QFocusEvent *event) { QGraphicsTextItem::focusOutEvent(event); emit cursorPositionChanged(QTextCursor()); } void WorksheetTextItem::mousePressEvent(QGraphicsSceneMouseEvent *event) { int p = textCursor().position(); bool b = textCursor().hasSelection(); QGraphicsTextItem::mousePressEvent(event); if (isEditable() && event->button() == Qt::MiddleButton && QApplication::clipboard()->supportsSelection() && !event->isAccepted()) event->accept(); if (m_itemDragable && event->button() == Qt::LeftButton) event->accept(); if (p != textCursor().position()) emit cursorPositionChanged(textCursor()); if (b != textCursor().hasSelection()) selectionChanged(); } void WorksheetTextItem::mouseMoveEvent(QGraphicsSceneMouseEvent* event) { const QPointF buttonDownPos = event->buttonDownPos(Qt::LeftButton); if (m_itemDragable && event->buttons() == Qt::LeftButton && contains(buttonDownPos) && (event->pos() - buttonDownPos).manhattanLength() >= QApplication::startDragDistance()) { ungrabMouse(); emit drag(mapToParent(buttonDownPos), mapToParent(event->pos())); event->accept(); } else { bool b = textCursor().hasSelection(); QGraphicsTextItem::mouseMoveEvent(event); if (b != textCursor().hasSelection()) selectionChanged(); } } void WorksheetTextItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { int p = textCursor().position(); // custom middle-click paste that does not copy rich text if (isEditable() && event->button() == Qt::MiddleButton && QApplication::clipboard()->supportsSelection() && !richTextEnabled()) { setLocalCursorPosition(mapFromScene(event->scenePos())); const QString& text = QApplication::clipboard()->text(QClipboard::Selection); textCursor().insertText(text); } else { QGraphicsTextItem::mouseReleaseEvent(event); } if (p != textCursor().position()) emit cursorPositionChanged(textCursor()); } void WorksheetTextItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) { QTextCursor cursor = textCursor(); const QChar repl = QChar::ObjectReplacementCharacter; if (!cursor.hasSelection()) { // We look at the current cursor and the next cursor for a // ObjectReplacementCharacter for (int i = 2; i; --i) { if (document()->characterAt(cursor.position()-1) == repl) { setTextCursor(cursor); emit doubleClick(); return; } cursor.movePosition(QTextCursor::NextCharacter); } } else if (cursor.selectedText().contains(repl)) { emit doubleClick(); return; } QGraphicsTextItem::mouseDoubleClickEvent(event); } void WorksheetTextItem::dragEnterEvent(QGraphicsSceneDragDropEvent* event) { if (isEditable() && event->mimeData()->hasFormat(QLatin1String("text/plain"))) { if (event->proposedAction() & (Qt::CopyAction | Qt::MoveAction)) { event->acceptProposedAction(); } else if (event->possibleActions() & Qt::CopyAction) { event->setDropAction(Qt::CopyAction); event->accept(); } else if (event->possibleActions() & Qt::MoveAction) { event->setDropAction(Qt::MoveAction); event->accept(); } else { event->ignore(); } } else { event->ignore(); } } void WorksheetTextItem::dragMoveEvent(QGraphicsSceneDragDropEvent* event) { if (isEditable() && event->mimeData()->hasFormat(QLatin1String("text/plain"))) setLocalCursorPosition(mapFromScene(event->scenePos())); } void WorksheetTextItem::dropEvent(QGraphicsSceneDragDropEvent* event) { if (isEditable()) { if (richTextEnabled() && event->mimeData()->hasFormat(QLatin1String("text/html"))) textCursor().insertHtml(event->mimeData()->html()); else textCursor().insertText(event->mimeData()->text()); event->accept(); } } void WorksheetTextItem::contextMenuEvent(QGraphicsSceneContextMenuEvent *event) { QMenu *menu = worksheet()->createContextMenu(); populateMenu(menu, event->pos()); menu->popup(event->screenPos()); } void WorksheetTextItem::insertTab() { QTextCursor cursor = textCursor(); cursor.clearSelection(); cursor.movePosition(QTextCursor::StartOfLine, QTextCursor::KeepAnchor); QString sel = cursor.selectedText(); bool spacesOnly = true; qDebug() << sel; for (QString::iterator it = sel.begin(); it != sel.end(); ++it) { if (! it->isSpace()) { spacesOnly = false; break; } } cursor.setPosition(cursor.selectionEnd()); if (spacesOnly) { while (document()->characterAt(cursor.position()) == QLatin1Char(' ')) cursor.movePosition(QTextCursor::NextCharacter); } QTextLayout *layout = textCursor().block().layout(); if (!layout) { cursor.insertText(QLatin1String(" ")); } else { cursor.movePosition(QTextCursor::StartOfLine, QTextCursor::KeepAnchor); int i = cursor.selectionEnd() - cursor.selectionStart(); i = ((i+4) & (~3)) - i; cursor.setPosition(cursor.selectionEnd()); QString insertBlankSpace = QLatin1String(" "); cursor.insertText(insertBlankSpace.repeated(i)); } setTextCursor(cursor); emit cursorPositionChanged(textCursor()); } void WorksheetTextItem::paint(QPainter* painter, const QStyleOptionGraphicsItem* o, QWidget* w) { if (m_backgroundColor.isValid()) { painter->setPen(QPen(Qt::NoPen)); painter->setBrush(m_backgroundColor); painter->drawRect(boundingRect()); } QGraphicsTextItem::paint(painter, o, w); } double WorksheetTextItem::width() const { return m_size.width(); } double WorksheetTextItem::height() const { return m_size.height(); } Worksheet* WorksheetTextItem::worksheet() { return qobject_cast(scene()); } WorksheetView* WorksheetTextItem::worksheetView() { return worksheet()->worksheetView(); } void WorksheetTextItem::clearSelection() { QTextCursor cursor = textCursor(); cursor.clearSelection(); setTextCursor(cursor); selectionChanged(); } bool WorksheetTextItem::isUndoAvailable() { return document()->isUndoAvailable(); } bool WorksheetTextItem::isRedoAvailable() { return document()->isRedoAvailable(); } bool WorksheetTextItem::isCutAvailable() { return isEditable() && textCursor().hasSelection(); } bool WorksheetTextItem::isCopyAvailable() { return !m_itemDragable && textCursor().hasSelection(); } bool WorksheetTextItem::isPasteAvailable() { return isEditable() && !QApplication::clipboard()->text().isEmpty(); } QTextCursor WorksheetTextItem::search(QString pattern, QTextDocument::FindFlags qt_flags, const WorksheetCursor& pos) { if (pos.isValid() && pos.textItem() != this) return QTextCursor(); QTextDocument* doc = document(); QTextCursor cursor; if (pos.isValid()) { cursor = doc->find(pattern, pos.textCursor(), qt_flags); } else { cursor = textCursor(); if (qt_flags & QTextDocument::FindBackward) cursor.movePosition(QTextCursor::End); else cursor.movePosition(QTextCursor::Start); cursor = doc->find(pattern, cursor, qt_flags); } return cursor; } // RichText void WorksheetTextItem::updateRichTextActions(QTextCursor cursor) { if (cursor.isNull()) return; Worksheet::RichTextInfo info; QTextCharFormat fmt = cursor.charFormat(); info.bold = (fmt.fontWeight() == QFont::Bold); info.italic = fmt.fontItalic(); info.underline = fmt.fontUnderline(); info.strikeOut = fmt.fontStrikeOut(); info.font = fmt.fontFamily(); info.fontSize = fmt.font().pointSize(); QTextBlockFormat bfmt = cursor.blockFormat(); info.align = bfmt.alignment(); worksheet()->setRichTextInformation(info); } void WorksheetTextItem::mergeFormatOnWordOrSelection(const QTextCharFormat &format) { qDebug() << format; QTextCursor cursor = textCursor(); QTextCursor wordStart(cursor); QTextCursor wordEnd(cursor); wordStart.movePosition(QTextCursor::StartOfWord); wordEnd.movePosition(QTextCursor::EndOfWord); //cursor.beginEditBlock(); if (!cursor.hasSelection() && cursor.position() != wordStart.position() && cursor.position() != wordEnd.position()) cursor.select(QTextCursor::WordUnderCursor); cursor.mergeCharFormat(format); //q->mergeCurrentCharFormat(format); //cursor.endEditBlock(); setTextCursor(cursor); } void WorksheetTextItem::setTextForegroundColor() { QTextCharFormat fmt = textCursor().charFormat(); QColor color = fmt.foreground().color(); color = QColorDialog::getColor(color, worksheetView()); if (!color.isValid()) color = KColorScheme(QPalette::Active, KColorScheme::View).foreground().color(); QTextCharFormat newFmt; newFmt.setForeground(color); mergeFormatOnWordOrSelection(newFmt); } void WorksheetTextItem::setTextBackgroundColor() { QTextCharFormat fmt = textCursor().charFormat(); QColor color = fmt.background().color(); color = QColorDialog::getColor(color, worksheetView()); if (!color.isValid()) color = KColorScheme(QPalette::Active, KColorScheme::View).background().color(); QTextCharFormat newFmt; newFmt.setBackground(color); mergeFormatOnWordOrSelection(newFmt); } void WorksheetTextItem::setTextBold(bool b) { QTextCharFormat fmt; fmt.setFontWeight(b ? QFont::Bold : QFont::Normal); mergeFormatOnWordOrSelection(fmt); } void WorksheetTextItem::setTextItalic(bool b) { QTextCharFormat fmt; fmt.setFontItalic(b); mergeFormatOnWordOrSelection(fmt); } void WorksheetTextItem::setTextUnderline(bool b) { QTextCharFormat fmt; fmt.setFontUnderline(b); mergeFormatOnWordOrSelection(fmt); } void WorksheetTextItem::setTextStrikeOut(bool b) { QTextCharFormat fmt; fmt.setFontStrikeOut(b); mergeFormatOnWordOrSelection(fmt); } void WorksheetTextItem::setAlignment(Qt::Alignment a) { QTextBlockFormat fmt; fmt.setAlignment(a); QTextCursor cursor = textCursor(); cursor.mergeBlockFormat(fmt); setTextCursor(cursor); } void WorksheetTextItem::setFontFamily(const QString& font) { if (!richTextEnabled()) return; QTextCharFormat fmt; fmt.setFontFamily(font); mergeFormatOnWordOrSelection(fmt); } void WorksheetTextItem::setFontSize(int size) { if (!richTextEnabled()) return; QTextCharFormat fmt; fmt.setFontPointSize(size); mergeFormatOnWordOrSelection(fmt); } void WorksheetTextItem::allowEditing() { setTextInteractionFlags(Qt::TextEditorInteraction); } void WorksheetTextItem::denyEditing() { setTextInteractionFlags(Qt::TextBrowserInteraction | Qt::TextSelectableByKeyboard); } diff --git a/src/worksheettextitem.h b/src/worksheettextitem.h index 932d9aeb..8584fa69 100644 --- a/src/worksheettextitem.h +++ b/src/worksheettextitem.h @@ -1,178 +1,177 @@ /* 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 */ #ifndef WORKSHEET_TEXT_ITEM_H #define WORKSHEET_TEXT_ITEM_H #include #include #include #include #include class Worksheet; class WorksheetView; class WorksheetCursor; namespace Cantor { class Session; } class QTextCharFormat; class WorksheetTextItem : public QGraphicsTextItem { Q_OBJECT public: explicit WorksheetTextItem(QGraphicsObject* parent, Qt::TextInteractionFlags ti = Qt::NoTextInteraction); ~WorksheetTextItem() override; void setCursorPosition(const QPointF& pos); QPointF cursorPosition() const; QTextCursor cursorForPosition(const QPointF& pos) const; QRectF sceneCursorRect(QTextCursor cursor = QTextCursor()) const; QRectF cursorRect(QTextCursor cursor = QTextCursor()) const; enum {TopLeft, BottomRight, TopCoord, BottomCoord}; enum {Type = UserType + 100}; int type() const override; void setFocusAt(int pos = TopLeft, qreal xCoord = 0); void enableCompletion(bool b); void activateCompletion(bool b); void setItemDragable(bool b); void enableRichText(bool b); virtual void populateMenu(QMenu* menu, QPointF pos); QString resolveImages(const QTextCursor& cursor); bool isEditable(); void allowEditing(); void denyEditing(); void setBackgroundColor(const QColor&); const QColor& backgroundColor() const; bool richTextEnabled(); double width() const; double height() const; virtual qreal setGeometry(qreal x, qreal y, qreal w, bool centered=false); Worksheet* worksheet(); WorksheetView* worksheetView(); void clearSelection(); bool isUndoAvailable(); bool isRedoAvailable(); bool isCutAvailable(); bool isCopyAvailable(); bool isPasteAvailable(); // richtext stuff void setTextForegroundColor(); void setTextBackgroundColor(); void setTextBold(bool b); void setTextItalic(bool b); void setTextUnderline(bool b); void setTextStrikeOut(bool b); void setAlignment(Qt::Alignment a); void setFontFamily(const QString& font); void setFontSize(int size); QTextCursor search(QString pattern, QTextDocument::FindFlags qt_flags, const WorksheetCursor& pos); Q_SIGNALS: void moveToPrevious(int pos, qreal xCoord); void moveToNext(int pos, qreal xCoord); void cursorPositionChanged(QTextCursor); void receivedFocus(WorksheetTextItem*); void tabPressed(); void backtabPressed(); void applyCompletion(); void doubleClick(); void execute(); void deleteEntry(); void sizeChanged(); void menuCreated(QMenu*, const QPointF&); void drag(const QPointF&, const QPointF&); void undoAvailable(bool); void redoAvailable(bool); void cutAvailable(bool); void copyAvailable(bool); void pasteAvailable(bool); public Q_SLOTS: void insertTab(); void cut(); void copy(); void paste(); void undo(); void redo(); void clipboardChanged(); void selectionChanged(); protected: void keyPressEvent(QKeyEvent *event) override; void focusInEvent(QFocusEvent *event) override; void focusOutEvent(QFocusEvent *event) override; void mousePressEvent(QGraphicsSceneMouseEvent *event) override; void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override; void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override; void mouseMoveEvent(QGraphicsSceneMouseEvent* event) override; void contextMenuEvent(QGraphicsSceneContextMenuEvent *event) override; void dragEnterEvent(QGraphicsSceneDragDropEvent* event) override; //void dragLeaveEvent(QGraphicsSceneDragDropEvent* event); void dragMoveEvent(QGraphicsSceneDragDropEvent* event) override; void dropEvent(QGraphicsSceneDragDropEvent* event) override; bool sceneEvent(QEvent *event) override; void paint(QPainter* painter, const QStyleOptionGraphicsItem* o, QWidget* w) override; private Q_SLOTS: //void setHeight(); void testSize(); void updateRichTextActions(QTextCursor cursor); private: void setLocalCursorPosition(const QPointF& pos); QPointF localCursorPosition() const; QKeyEvent* eventForStandardAction(KStandardAction::StandardAction actionID); Cantor::Session* session(); // richtext void mergeFormatOnWordOrSelection(const QTextCharFormat &format); private: QSizeF m_size; - qreal m_maxWidth; bool m_completionEnabled; bool m_completionActive; bool m_itemDragable; bool m_richTextEnabled; QColor m_backgroundColor; }; #endif // WORKSHEET_TEXT_ITEM_H