diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2dbfb0bb..07db45bd 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,112 +1,105 @@ ######################################################################### # Subdirectories ######################################################################### add_subdirectory(lib) add_subdirectory(scripteditor) include_directories( lib ${CMAKE_CURRENT_BINARY_DIR}/lib) if(BUILD_TESTING) include(ECMMarkAsTest) include_directories( lib/test ) endif(BUILD_TESTING) -if(LIBSPECTRE_FOUND) - include_directories(${LIBSPECTRE_INCLUDE_DIR}) -endif(LIBSPECTRE_FOUND) add_subdirectory(backends) add_subdirectory(assistants) add_subdirectory(xslt) add_subdirectory(panelplugins) #build the config object in a separate library, shared between shell and part kconfig_add_kcfg_files(config_SRCS settings.kcfgc) add_library( cantor_config SHARED ${config_SRCS} ) target_link_libraries( cantor_config KF5::Parts KF5::NewStuff ) install( TARGETS cantor_config ${KDE_INSTALL_TARGETS_DEFAULT_ARGS} ) set(cantor_SRCS main.cpp cantor.cpp backendchoosedialog.cpp ) install(FILES cantor.kcfg DESTINATION ${KDE_INSTALL_KCFGDIR}) ki18n_wrap_ui(cantor_SRCS settings.ui) ki18n_wrap_ui(cantor_SRCS backendchooser.ui) file(GLOB ICONS_SRCS "${CMAKE_CURRENT_SOURCE_DIR}/../icons/*-apps-cantor.png") ecm_add_app_icon(cantor_SRCS ICONS ${ICONS_SRCS}) add_executable(cantor ${cantor_SRCS}) target_link_libraries(cantor KF5::Parts KF5::NewStuff KF5::ConfigCore KF5::CoreAddons KF5::ConfigGui KF5::Crash KF5::XmlGui cantorlibs cantor_config) ########### install files ############### install(TARGETS cantor ${KDE_INSTALL_TARGETS_DEFAULT_ARGS} ) install( PROGRAMS org.kde.cantor.desktop DESTINATION ${KDE_INSTALL_APPDIR} ) install( FILES cantor_shell.rc DESTINATION ${KDE_INSTALL_KXMLGUI5DIR}/cantor ) install( FILES cantor.knsrc DESTINATION ${KDE_INSTALL_CONFDIR} ) ######################################################################### # KPART SECTION ######################################################################### set(cantor_PART_SRCS cantor_part.cpp worksheet.cpp worksheetview.cpp worksheetentry.cpp worksheettextitem.cpp worksheetimageitem.cpp commandentry.cpp textentry.cpp markdownentry.cpp pagebreakentry.cpp imageentry.cpp latexentry.cpp placeholderentry.cpp worksheetcursor.cpp searchbar.cpp actionbar.cpp worksheettoolbutton.cpp imagesettingsdialog.cpp scripteditor/scripteditorwidget.cpp resultitem.cpp textresultitem.cpp imageresultitem.cpp animationresultitem.cpp loadedexpression.cpp animation.cpp - epsrenderer.cpp ) ki18n_wrap_ui(cantor_PART_SRCS imagesettings.ui) ki18n_wrap_ui(cantor_PART_SRCS standardsearchbar.ui) ki18n_wrap_ui(cantor_PART_SRCS extendedsearchbar.ui) string(CONCAT PATH_TO_CANTOR_BACKENDS ${CMAKE_INSTALL_PREFIX} "/${PLUGIN_INSTALL_DIR}") configure_file (config-cantor.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-cantor.h ) kcoreaddons_add_plugin(cantorpart SOURCES ${cantor_PART_SRCS} JSON "cantor_part.json" INSTALL_NAMESPACE ".") set_target_properties(cantorpart PROPERTIES PREFIX "${CMAKE_SHARED_LIBRARY_PREFIX}") target_link_libraries(cantorpart KF5::Parts KF5::NewStuff KF5::TextEditor ${Qt5XmlPatterns_LIBRARIES} KF5::KIOCore KF5::KIOFileWidgets KF5::KIOWidgets Qt5::PrintSupport cantorlibs cantor_config ) -if(LIBSPECTRE_FOUND) - target_link_libraries(cantorpart ${LIBSPECTRE_LIBRARY}) -endif(LIBSPECTRE_FOUND) if(Discount_FOUND) target_link_libraries(cantorpart Discount::Lib) endif(Discount_FOUND) install( FILES cantor_part.rc DESTINATION ${KDE_INSTALL_KXMLGUI5DIR}/cantor ) install( FILES cantor_scripteditor.rc DESTINATION ${KDE_INSTALL_KXMLGUI5DIR}/cantor ) diff --git a/src/config-cantor.h.cmake b/src/config-cantor.h.cmake index 7877895a..ff2228df 100644 --- a/src/config-cantor.h.cmake +++ b/src/config-cantor.h.cmake @@ -1,9 +1,7 @@ #include #define CANTOR_VERSION "${KDE_APPLICATIONS_VERSION}" -#cmakedefine LIBSPECTRE_FOUND 1 - #cmakedefine Discount_FOUND 1 #define PATH_TO_CANTOR_PLUGINS "${PATH_TO_CANTOR_BACKENDS}" diff --git a/src/imageresultitem.cpp b/src/imageresultitem.cpp index 2e0ace80..09849f97 100644 --- a/src/imageresultitem.cpp +++ b/src/imageresultitem.cpp @@ -1,98 +1,105 @@ /* 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: setImage(m_result->data().value()); break; case Cantor::EpsResult::Type: - setEps(m_result->data().toUrl()); + { + Cantor::EpsResult* epsResult = static_cast(m_result); +#ifdef WITH_EPS + if (!epsResult->image().isNull() && worksheet()->epsRenderer()->scale() == 1.0) + 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(); } - -EpsRenderer* ImageResultItem::epsRenderer() -{ - return qobject_cast(scene())->epsRenderer(); -} diff --git a/src/imageresultitem.h b/src/imageresultitem.h index 7eff23bf..ce827078 100644 --- a/src/imageresultitem.h +++ b/src/imageresultitem.h @@ -1,54 +1,53 @@ /* 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 IMAGERESULTITEM_H #define IMAGERESULTITEM_H #include "resultitem.h" #include "worksheetimageitem.h" class CommandEntry; class EpsRenderer; class ImageResultItem : public WorksheetImageItem, public ResultItem { Q_OBJECT public: explicit ImageResultItem(QGraphicsObject* parent, Cantor::Result* result); ~ImageResultItem() override = default; using WorksheetImageItem::setGeometry; double setGeometry(double x, double y, double w) override; void populateMenu(QMenu* menu, QPointF pos) override; void update() override; QRectF boundingRect() const override; double width() const override; double height() const override; void deleteLater() override; - EpsRenderer* epsRenderer(); protected Q_SLOTS: void saveResult(); }; #endif // IMAGERESULTITEM_H diff --git a/src/latexentry.cpp b/src/latexentry.cpp index c17769e1..860b8753 100644 --- a/src/latexentry.cpp +++ b/src/latexentry.cpp @@ -1,440 +1,440 @@ /* 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 "latexentry.h" #include "worksheetentry.h" #include "worksheet.h" -#include "epsrenderer.h" +#include "lib/epsrenderer.h" #include "lib/defaulthighlighter.h" #include "lib/latexrenderer.h" #include "config-cantor.h" #include #include #include #include #include #include #include LatexEntry::LatexEntry(Worksheet* worksheet) : WorksheetEntry(worksheet), m_textItem(new WorksheetTextItem(this, Qt::TextEditorInteraction)) { m_textItem->installEventFilter(this); connect(m_textItem, &WorksheetTextItem::moveToPrevious, this, &LatexEntry::moveToPreviousEntry); connect(m_textItem, &WorksheetTextItem::moveToNext, this, &LatexEntry::moveToNextEntry); connect(m_textItem, SIGNAL(execute()), this, SLOT(evaluate())); } void LatexEntry::populateMenu(QMenu* menu, QPointF pos) { bool imageSelected = false; QTextCursor cursor = m_textItem->textCursor(); const QChar repl = QChar::ObjectReplacementCharacter; if (cursor.hasSelection()) { QString selection = m_textItem->textCursor().selectedText(); imageSelected = selection.contains(repl); } else { // we need to try both the current cursor and the one after the that cursor = m_textItem->cursorForPosition(pos); for (int i = 2; i; --i) { int p = cursor.position(); if (m_textItem->document()->characterAt(p-1) == repl && - cursor.charFormat().hasProperty(EpsRenderer::CantorFormula)) { + cursor.charFormat().hasProperty(Cantor::EpsRenderer::CantorFormula)) { m_textItem->setTextCursor(cursor); imageSelected = true; break; } cursor.movePosition(QTextCursor::NextCharacter); } } if (imageSelected) { menu->addAction(i18n("Show LaTeX code"), this, SLOT(resolveImagesAtCursor())); menu->addSeparator(); } WorksheetEntry::populateMenu(menu, pos); } int LatexEntry::type() const { return Type; } bool LatexEntry::isEmpty() { return m_textItem->document()->isEmpty(); } bool LatexEntry::acceptRichText() { return false; } bool LatexEntry::focusEntry(int pos, qreal xCoord) { if (aboutToBeRemoved()) return false; m_textItem->setFocusAt(pos, xCoord); return true; } void LatexEntry::setContent(const QString& content) { m_latex = content; m_textItem->setPlainText(m_latex); } void LatexEntry::setContent(const QDomElement& content, const KZip& file) { m_latex = content.text(); qDebug() << m_latex; m_textItem->document()->clear(); QTextCursor cursor = m_textItem->textCursor(); cursor.movePosition(QTextCursor::Start); QString imagePath; bool useLatexCode = true; if(content.hasAttribute(QLatin1String("filename"))) { const KArchiveEntry* imageEntry=file.directory()->entry(content.attribute(QLatin1String("filename"))); if (imageEntry&&imageEntry->isFile()) { const KArchiveFile* imageFile=static_cast(imageEntry); const QString& dir=QStandardPaths::writableLocation(QStandardPaths::TempLocation); imageFile->copyTo(dir); imagePath = dir + QDir::separator() + imageFile->name(); #ifdef LIBSPECTRE_FOUND m_renderedFormat = worksheet()->epsRenderer()->render(m_textItem->document(), QUrl::fromLocalFile(imagePath)); qDebug()<<"rendering successful? " << !m_renderedFormat.name().isEmpty(); - m_renderedFormat.setProperty(EpsRenderer::CantorFormula, EpsRenderer::LatexFormula); - m_renderedFormat.setProperty(EpsRenderer::ImagePath, imagePath); - m_renderedFormat.setProperty(EpsRenderer::Code, m_latex); + m_renderedFormat.setProperty(Cantor::EpsRenderer::CantorFormula, Cantor::EpsRenderer::LatexFormula); + m_renderedFormat.setProperty(Cantor::EpsRenderer::ImagePath, imagePath); + m_renderedFormat.setProperty(Cantor::EpsRenderer::Code, m_latex); cursor.insertText(QString(QChar::ObjectReplacementCharacter), m_renderedFormat); useLatexCode = false; m_textItem->denyEditing(); #endif } } if (useLatexCode && content.hasAttribute(QLatin1String("image"))) { const QByteArray& ba = QByteArray::fromBase64(content.attribute(QLatin1String("image")).toLatin1()); QImage image; if (image.loadFromData(ba)) { // Create unique internal url for this loaded image QUrl internal; internal.setScheme(QLatin1String("internal")); internal.setPath(QUuid::createUuid().toString()); m_textItem->document()->addResource(QTextDocument::ImageResource, internal, QVariant(image)); m_renderedFormat.setName(internal.url()); m_renderedFormat.setWidth(image.width()); m_renderedFormat.setHeight(image.height()); - m_renderedFormat.setProperty(EpsRenderer::CantorFormula, EpsRenderer::LatexFormula); + m_renderedFormat.setProperty(Cantor::EpsRenderer::CantorFormula, Cantor::EpsRenderer::LatexFormula); if (!imagePath.isEmpty()) - m_renderedFormat.setProperty(EpsRenderer::ImagePath, imagePath); - m_renderedFormat.setProperty(EpsRenderer::Code, m_latex); + m_renderedFormat.setProperty(Cantor::EpsRenderer::ImagePath, imagePath); + m_renderedFormat.setProperty(Cantor::EpsRenderer::Code, m_latex); cursor.insertText(QString(QChar::ObjectReplacementCharacter), m_renderedFormat); useLatexCode = false; m_textItem->denyEditing(); } } if (useLatexCode) cursor.insertText(m_latex); } QDomElement LatexEntry::toXml(QDomDocument& doc, KZip* archive) { QDomElement el = doc.createElement(QLatin1String("Latex")); el.appendChild( doc.createTextNode( latexCode() )); QTextCursor cursor = m_textItem->document()->find(QString(QChar::ObjectReplacementCharacter)); if (!cursor.isNull()) { QTextImageFormat format=cursor.charFormat().toImageFormat(); - QString fileName = format.property(EpsRenderer::ImagePath).toString(); + QString fileName = format.property(Cantor::EpsRenderer::ImagePath).toString(); // Check, if eps file exists, and if not true, rerender latex code bool isEpsFileExists = QFile::exists(fileName); #ifdef LIBSPECTRE_FOUND if (!isEpsFileExists && renderLatexCode()) { cursor = m_textItem->document()->find(QString(QChar::ObjectReplacementCharacter)); format=cursor.charFormat().toImageFormat(); - fileName = format.property(EpsRenderer::ImagePath).toString(); + fileName = format.property(Cantor::EpsRenderer::ImagePath).toString(); isEpsFileExists = QFile::exists(fileName); } #endif if (isEpsFileExists && archive) { const QUrl& url=QUrl::fromLocalFile(fileName); archive->addLocalFile(url.toLocalFile(), url.fileName()); el.setAttribute(QLatin1String("filename"), url.fileName()); } // Save also rendered QImage, if exist. QUrl internal; internal.setUrl(format.name()); const QImage& image = m_textItem->document()->resource(QTextDocument::ImageResource, internal).value(); if (!image.isNull()) { QByteArray ba; QBuffer buffer(&ba); buffer.open(QIODevice::WriteOnly); image.save(&buffer, "PNG"); el.setAttribute(QLatin1String("image"), QString::fromLatin1(ba.toBase64())); } } return el; } QString LatexEntry::toPlain(const QString& commandSep, const QString& commentStartingSeq, const QString& commentEndingSeq) { Q_UNUSED(commandSep); if (commentStartingSeq.isEmpty()) return QString(); QString text = latexCode(); if (!commentEndingSeq.isEmpty()) return commentStartingSeq + text + commentEndingSeq + QLatin1String("\n"); return commentStartingSeq + text.replace(QLatin1String("\n"), QLatin1String("\n") + commentStartingSeq) + QLatin1String("\n"); } void LatexEntry::interruptEvaluation() { } bool LatexEntry::evaluate(EvaluationOption evalOp) { bool success = false; if (isOneImageOnly()) { success = true; } else { if (m_latex == latexCode()) { QTextCursor cursor = m_textItem->textCursor(); cursor.movePosition(QTextCursor::Start); cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor); cursor.insertText(QString(QChar::ObjectReplacementCharacter), m_renderedFormat); m_textItem->denyEditing(); } else { m_latex = latexCode(); success = renderLatexCode(); } } qDebug()<<"rendering successful? "<document()->find(QString(QChar::ObjectReplacementCharacter)); while (!cursor.isNull()) { qDebug()<<"found a formula... rendering the eps..."; QTextImageFormat format=cursor.charFormat().toImageFormat(); - const QUrl& url=QUrl::fromLocalFile(format.property(EpsRenderer::ImagePath).toString()); + const QUrl& url=QUrl::fromLocalFile(format.property(Cantor::EpsRenderer::ImagePath).toString()); QSizeF s = worksheet()->epsRenderer()->renderToResource(m_textItem->document(), url, QUrl(format.name())); qDebug()<<"rendering successful? "<< s.isValid(); cursor.movePosition(QTextCursor::NextCharacter); cursor = m_textItem->document()->find(QString(QChar::ObjectReplacementCharacter), cursor); } } bool LatexEntry::eventFilter(QObject* object, QEvent* event) { if(object == m_textItem && event->type() == QEvent::GraphicsSceneMouseDoubleClick) { // One image if we have rendered entry if (isOneImageOnly()) { QTextCursor cursor = m_textItem->textCursor(); if (!cursor.hasSelection()) cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor); cursor.insertText(m_textItem->resolveImages(cursor)); m_textItem->allowEditing(); return true; } } return false; } QString LatexEntry::latexCode() { QTextCursor cursor = m_textItem->textCursor(); cursor.movePosition(QTextCursor::Start); cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor); QString code = m_textItem->resolveImages(cursor); code.replace(QChar::ParagraphSeparator, QLatin1Char('\n')); //Replace the U+2029 paragraph break by a Normal Newline code.replace(QChar::LineSeparator, QLatin1Char('\n')); //Replace the line break by a Normal Newline return code; } bool LatexEntry::isOneImageOnly() { QTextCursor cursor = m_textItem->textCursor(); cursor.movePosition(QTextCursor::Start); cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor); return (cursor.selectionEnd() == 1 && cursor.selectedText() == QString(QChar::ObjectReplacementCharacter)); } int LatexEntry::searchText(const QString& text, const QString& pattern, QTextDocument::FindFlags qt_flags) { Qt::CaseSensitivity caseSensitivity; if (qt_flags & QTextDocument::FindCaseSensitively) caseSensitivity = Qt::CaseSensitive; else caseSensitivity = Qt::CaseInsensitive; int position; if (qt_flags & QTextDocument::FindBackward) position = text.lastIndexOf(pattern, -1, caseSensitivity); else position = text.indexOf(pattern, 0, caseSensitivity); return position; } WorksheetCursor LatexEntry::search(const QString& pattern, unsigned flags, QTextDocument::FindFlags qt_flags, const WorksheetCursor& pos) { if (!(flags & WorksheetEntry::SearchLaTeX)) return WorksheetCursor(); if (pos.isValid() && (pos.entry() != this || pos.textItem() != m_textItem)) return WorksheetCursor(); QTextCursor textCursor = m_textItem->search(pattern, qt_flags, pos); int position = 0; QString latex; const QString repl = QString(QChar::ObjectReplacementCharacter); QTextCursor latexCursor = m_textItem->search(repl, qt_flags, pos); while (!latexCursor.isNull()) { latex = m_textItem->resolveImages(latexCursor); position = searchText(latex, pattern, qt_flags); if (position >= 0) { break; } WorksheetCursor c(this, m_textItem, latexCursor); latexCursor = m_textItem->search(repl, qt_flags, c); } if (latexCursor.isNull()) { if (textCursor.isNull()) return WorksheetCursor(); else return WorksheetCursor(this, m_textItem, textCursor); } else { if (textCursor.isNull() || latexCursor < textCursor) { int start = latexCursor.selectionStart(); latexCursor.insertText(latex); QTextCursor c = m_textItem->textCursor(); c.setPosition(start + position); c.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, pattern.length()); return WorksheetCursor(this, m_textItem, c); } else { return WorksheetCursor(this, m_textItem, textCursor); } } } void LatexEntry::layOutForWidth(qreal w, bool force) { if (size().width() == w && !force) return; m_textItem->setGeometry(0, 0, w); setSize(QSizeF(m_textItem->width(), m_textItem->height() + VerticalMargin)); } bool LatexEntry::wantToEvaluate() { return !isOneImageOnly(); } bool LatexEntry::renderLatexCode() { bool success = false; QString latex = latexCode(); Cantor::LatexRenderer* renderer = new Cantor::LatexRenderer(this); renderer->setLatexCode(latex); renderer->setEquationOnly(false); renderer->setMethod(Cantor::LatexRenderer::LatexMethod); renderer->renderBlocking(); if (renderer->renderingSuccessful()) { - EpsRenderer* epsRend = worksheet()->epsRenderer(); + Cantor::EpsRenderer* epsRend = worksheet()->epsRenderer(); m_renderedFormat = epsRend->render(m_textItem->document(), renderer); success = !m_renderedFormat.name().isEmpty(); } if(success) { QTextCursor cursor = m_textItem->textCursor(); cursor.movePosition(QTextCursor::Start); cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor); cursor.insertText(QString(QChar::ObjectReplacementCharacter), m_renderedFormat); m_textItem->denyEditing(); } delete renderer; return success; } void LatexEntry::resolveImagesAtCursor() { QTextCursor cursor = m_textItem->textCursor(); if (!cursor.hasSelection()) cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor); cursor.insertText(m_textItem->resolveImages(cursor)); } diff --git a/src/lib/CMakeLists.txt b/src/lib/CMakeLists.txt index aed2458e..da63680d 100644 --- a/src/lib/CMakeLists.txt +++ b/src/lib/CMakeLists.txt @@ -1,99 +1,112 @@ +if(LIBSPECTRE_FOUND) + include_directories(${LIBSPECTRE_INCLUDE_DIR}) +endif(LIBSPECTRE_FOUND) + set( cantor_LIB_SRCS session.cpp expression.cpp backend.cpp result.cpp textresult.cpp imageresult.cpp epsresult.cpp latexresult.cpp latexrenderer.cpp + epsrenderer.cpp helpresult.cpp animationresult.cpp extension.cpp assistant.cpp completionobject.cpp syntaxhelpobject.cpp defaulthighlighter.cpp defaultvariablemodel.cpp panelplugin.cpp panelpluginhandler.cpp worksheetaccess.cpp directives/plotdirectives.cpp ) Set( cantor_LIB_HDRS cantor_macros.h #base classes backend.h session.h expression.h extension.h syntaxhelpobject.h completionobject.h #results animationresult.h epsresult.h helpresult.h imageresult.h latexresult.h + epsrenderer.h result.h textresult.h #helper classes defaulthighlighter.h defaultvariablemodel.h worksheetaccess.h ) ki18n_wrap_ui(cantor_LIB_SRCS directives/axisrange.ui directives/plottitle.ui) kconfig_add_kcfg_files(cantor_LIB_SRCS settings.kcfgc) install(FILES cantor_libs.kcfg DESTINATION ${KDE_INSTALL_KCFGDIR}) configure_file (config-cantorlib.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-cantorlib.h ) add_library( cantorlibs SHARED ${cantor_LIB_SRCS} ) generate_export_header(cantorlibs BASE_NAME cantor) kcoreaddons_desktop_to_json(cantorlibs cantor_assistant.desktop DEFAULT_SERVICE_TYPE) kcoreaddons_desktop_to_json(cantorlibs cantor_backend.desktop DEFAULT_SERVICE_TYPE) kcoreaddons_desktop_to_json(cantorlibs cantor_panelplugin.desktop DEFAULT_SERVICE_TYPE) target_link_libraries( cantorlibs KF5::Completion KF5::IconThemes KF5::KIOCore KF5::KIOFileWidgets KF5::KIOWidgets KF5::Archive KF5::ConfigCore KF5::ConfigGui KF5::I18n KF5::XmlGui ${QT5_LIBRARIES} Qt5::Xml ) +<<<<<<< HEAD +======= +if(LIBSPECTRE_FOUND) + target_link_libraries(cantorlibs ${LIBSPECTRE_LIBRARY}) +endif(LIBSPECTRE_FOUND) + +>>>>>>> 66757510... Move EpsRenderer from CantorPart to CantorLibs and add support EpsRender result on Cantor without Libspectre (via prerendered QImage) set (CANTORLIBS_SOVERSION 24) set_target_properties( cantorlibs PROPERTIES VERSION ${KDE_APPLICATIONS_VERSION} SOVERSION ${CANTORLIBS_SOVERSION}) ecm_setup_version(${KDE_APPLICATIONS_VERSION} VARIABLE_PREFIX CANTOR SOVERSION ${CANTORLIBS_SOVERSION} VERSION_HEADER ${CMAKE_CURRENT_BINARY_DIR}/cantorlibs_version.h ) install( TARGETS cantorlibs EXPORT CantorTargets ${KDE_INSTALL_TARGETS_DEFAULT_ARGS} ) install( FILES ${cantor_LIB_HDRS} ${CMAKE_CURRENT_BINARY_DIR}/cantor_export.h ${CMAKE_CURRENT_BINARY_DIR}/cantorlibs_version.h DESTINATION ${KDE_INSTALL_INCLUDEDIR}/cantor COMPONENT Devel ) if(BUILD_TESTING) add_subdirectory(test) endif() diff --git a/src/lib/config-cantorlib.h.cmake b/src/lib/config-cantorlib.h.cmake index 70dfdd8a..0003dd2b 100644 --- a/src/lib/config-cantorlib.h.cmake +++ b/src/lib/config-cantorlib.h.cmake @@ -1 +1,3 @@ #cmakedefine WITH_EPS 1 + +#cmakedefine LIBSPECTRE_FOUND 1 diff --git a/src/epsrenderer.cpp b/src/lib/epsrenderer.cpp similarity index 81% rename from src/epsrenderer.cpp rename to src/lib/epsrenderer.cpp index fcb2f7f0..9331d6b4 100644 --- a/src/epsrenderer.cpp +++ b/src/lib/epsrenderer.cpp @@ -1,149 +1,162 @@ /* 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 "epsrenderer.h" -#include +#include #ifdef LIBSPECTRE_FOUND #include "libspectre/spectre.h" #endif #include #include -EpsRenderer::EpsRenderer() : m_scale(1), m_useHighRes(false) +using namespace Cantor; + +class Cantor::EpsRendererPrivate{ + public: + double scale{1}; + bool useHighRes{false}; +}; + +EpsRenderer::EpsRenderer() : d(new EpsRendererPrivate()) { } void EpsRenderer::setScale(qreal scale) { - m_scale = scale; + d->scale = scale; } qreal EpsRenderer::scale() { - return m_scale; + return d->scale; } void EpsRenderer::useHighResolution(bool b) { - m_useHighRes = b; + d->useHighRes = b; } QTextImageFormat EpsRenderer::render(QTextDocument *document, const QUrl &url) { QTextImageFormat epsCharFormat; QUrl internal; internal.setScheme(QLatin1String("internal")); QString path = QUuid::createUuid().toString(); // Remove { and } path.remove(0, 1); path.chop(1); internal.setPath(path); QSizeF s = renderToResource(document, url, internal); if(s.isValid()) { epsCharFormat.setName(internal.url()); epsCharFormat.setWidth(s.width()); epsCharFormat.setHeight(s.height()); } return epsCharFormat; } QTextImageFormat EpsRenderer::render(QTextDocument *document, const Cantor::LatexRenderer* latex) { QTextImageFormat format = render(document, QUrl::fromLocalFile(latex->imagePath())); if (!format.name().isEmpty()) { format.setProperty(CantorFormula, latex->method()); format.setProperty(ImagePath, latex->imagePath()); format.setProperty(Code, latex->latexCode()); } return format; } QSizeF EpsRenderer::renderToResource(QTextDocument *document, const QUrl &url, const QUrl& internal) { QSizeF size; QImage img = renderToImage(url, &size); qDebug() << internal; document->addResource(QTextDocument::ImageResource, internal, QVariant(img) ); return size; } -QImage EpsRenderer::renderToImage(const QUrl& url, QSizeF* size) +QImage EpsRenderer::renderToImage(const QUrl& url, double scale, bool useHighRes, QSizeF* size) { #ifdef LIBSPECTRE_FOUND SpectreDocument* doc = spectre_document_new(); SpectreRenderContext* rc = spectre_render_context_new(); qDebug() << "rendering eps file: " << url; QByteArray local_file = url.toLocalFile().toUtf8(); spectre_document_load(doc, local_file.data()); bool isEps = spectre_document_is_eps(doc); if (!isEps) qDebug() << "Error: spectre document is not eps! It means, that url is invalid"; int wdoc, hdoc; qreal w, h; - double scale; + double realScale; spectre_document_get_page_size(doc, &wdoc, &hdoc); - if(m_useHighRes) { - scale=1.2*4.0; //1.2 scaling factor, to make it look nice, 4x for high resolution + if(useHighRes) { + realScale = 1.2*4.0; //1.2 scaling factor, to make it look nice, 4x for high resolution w = 1.2 * wdoc; h = 1.2 * hdoc; } else { - scale=1.8*m_scale; + realScale=1.8*scale; w = 1.8 * wdoc; h = 1.8 * hdoc; } - qDebug()<<"scale: "<scale, d->useHighRes, size); +} diff --git a/src/epsrenderer.h b/src/lib/epsrenderer.h similarity index 87% rename from src/epsrenderer.h rename to src/lib/epsrenderer.h index fd603aed..9b7dea9c 100644 --- a/src/epsrenderer.h +++ b/src/lib/epsrenderer.h @@ -1,59 +1,66 @@ /* 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 EPSRENDERER_H #define EPSRENDERER_H #include #include #include #include #include -#include "lib/latexrenderer.h" +#include "latexrenderer.h" -class EpsRenderer +namespace Cantor +{ +class EpsRendererPrivate; + +class CANTOR_EXPORT EpsRenderer { public: EpsRenderer(); ~EpsRenderer() = default; enum FormulaProperties {CantorFormula = 1, ImagePath = 2, Code = 3, Delimiter = 4}; enum FormulaType {LatexFormula = Cantor::LatexRenderer::LatexMethod, MmlFormula = Cantor::LatexRenderer::MmlMethod}; QTextImageFormat render(QTextDocument *document, const QUrl& url); QTextImageFormat render(QTextDocument *document, const Cantor::LatexRenderer* latex); void setScale(qreal scale); qreal scale(); void useHighResolution(bool b); QSizeF renderToResource(QTextDocument *document, const QUrl& url, const QUrl& internal); + QImage renderToImage(const QUrl& url, QSizeF* size = nullptr); + static QImage renderToImage(const QUrl& url, double scale, bool useHighRes, QSizeF* size = nullptr); private: - double m_scale; - bool m_useHighRes; + EpsRendererPrivate* d; }; +} + #endif //EPSRENDERER_H diff --git a/src/lib/epsresult.cpp b/src/lib/epsresult.cpp index 32148c21..c7e60ac7 100644 --- a/src/lib/epsresult.cpp +++ b/src/lib/epsresult.cpp @@ -1,99 +1,128 @@ /* 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 */ #include "epsresult.h" using namespace Cantor; #include #include #include #include +#include + +#include "epsrenderer.h" class Cantor::EpsResultPrivate{ public: QUrl url; + QImage image; }; -EpsResult::EpsResult(const QUrl& url) : d(new EpsResultPrivate) +EpsResult::EpsResult(const QUrl& url, const QImage& image) : d(new EpsResultPrivate) { d->url=url; -#ifndef WITH_EPS - qDebug()<<"Creating an EpsResult in an environment compiled without EPS support!"; -#endif + d->image = image; } EpsResult::~EpsResult() { delete d; } QString EpsResult::toHtml() { return QStringLiteral("").arg(d->url.url()); } QString EpsResult::toLatex() { return QStringLiteral(" \\begin{center} \n \\includegraphics[width=12cm]{%1}\n \\end{center}").arg(d->url.fileName()); } QVariant EpsResult::data() { return QVariant(d->url); } QUrl EpsResult::url() { return d->url; } +QImage Cantor::EpsResult::image() +{ + return d->image; +} + int EpsResult::type() { return EpsResult::Type; } QString EpsResult::mimeType() { return QStringLiteral("image/x-eps"); } QDomElement EpsResult::toXml(QDomDocument& doc) { qDebug()<<"saving imageresult "<url.fileName()); - qDebug()<<"done"; +#ifdef WITH_EPS + const QImage& image = EpsRenderer::renderToImage(d->url, 1.0, false); + qDebug() << image.size() << image.isNull(); + if (!image.isNull()) + { + QByteArray ba; + QBuffer buffer(&ba); + buffer.open(QIODevice::WriteOnly); + image.save(&buffer, "PNG"); + e.setAttribute(QLatin1String("image"), QString::fromLatin1(ba.toBase64())); + } +#else + if (!d->image.isNull()) + { + QByteArray ba; + QBuffer buffer(&ba); + buffer.open(QIODevice::WriteOnly); + d->image.save(&buffer, "PNG"); + e.setAttribute(QLatin1String("image"), QString::fromLatin1(ba.toBase64())); + } +#endif + + qDebug()<<"done"; return e; } void EpsResult::saveAdditionalData(KZip* archive) { archive->addLocalFile(d->url.toLocalFile(), d->url.fileName()); } void EpsResult::save(const QString& filename) { //just copy over the eps file.. KIO::file_copy(d->url, QUrl::fromLocalFile(filename), -1, KIO::HideProgressInfo); } diff --git a/src/lib/epsresult.h b/src/lib/epsresult.h index 66bc4e66..d4f3ce04 100644 --- a/src/lib/epsresult.h +++ b/src/lib/epsresult.h @@ -1,58 +1,60 @@ /* 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 */ #ifndef _EPSRESULT_H #define _EPSRESULT_H #include "result.h" #include "cantor_export.h" #include +#include namespace Cantor { class EpsResultPrivate; class CANTOR_EXPORT EpsResult : public Result { public: enum {Type=5}; - explicit EpsResult( const QUrl& url); + explicit EpsResult( const QUrl& url, const QImage& image = QImage()); ~EpsResult() override; QString toHtml() override; QString toLatex() override; QVariant data() override; QUrl url() override; + QImage image(); int type() override; QString mimeType() override; QDomElement toXml(QDomDocument& doc) override; void saveAdditionalData(KZip* archive) override; void save(const QString& filename) override; private: EpsResultPrivate* d; }; } #endif /* _EPSRESULT_H */ diff --git a/src/loadedexpression.cpp b/src/loadedexpression.cpp index 2339af08..08a1d1a7 100644 --- a/src/loadedexpression.cpp +++ b/src/loadedexpression.cpp @@ -1,94 +1,97 @@ /* 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 */ #include "loadedexpression.h" #include "lib/imageresult.h" #include "lib/epsresult.h" #include "lib/textresult.h" #include "lib/latexresult.h" #include "lib/animationresult.h" #include #include LoadedExpression::LoadedExpression( Cantor::Session* session ) : Cantor::Expression( session, false, -1) { } void LoadedExpression::interrupt() { //Do nothing } void LoadedExpression::evaluate() { //Do nothing } void LoadedExpression::loadFromXml(const QDomElement& xml, const KZip& file) { setCommand(xml.firstChildElement(QLatin1String("Command")).text()); const QDomNodeList& results = xml.elementsByTagName(QLatin1String("Result")); for (int i = 0; i < results.size(); i++) { const QDomElement& resultElement = results.at(i).toElement(); const QString& type = resultElement.attribute(QLatin1String("type")); if ( type == QLatin1String("text")) { addResult(new Cantor::TextResult(resultElement.text())); } else if (type == QLatin1String("image") || type == QLatin1String("latex") || type == QLatin1String("animation")) { const KArchiveEntry* imageEntry=file.directory()->entry(resultElement.attribute(QLatin1String("filename"))); if (imageEntry&&imageEntry->isFile()) { const KArchiveFile* imageFile=static_cast(imageEntry); QString dir=QStandardPaths::writableLocation(QStandardPaths::TempLocation); imageFile->copyTo(dir); QUrl imageUrl = QUrl::fromLocalFile(QDir(dir).absoluteFilePath(imageFile->name())); if(type==QLatin1String("latex")) { addResult(new Cantor::LatexResult(resultElement.text(), imageUrl)); }else if(type==QLatin1String("animation")) { addResult(new Cantor::AnimationResult(imageUrl)); }else if(imageFile->name().endsWith(QLatin1String(".eps"))) { - addResult(new Cantor::EpsResult(imageUrl)); + const QByteArray& ba = QByteArray::fromBase64(resultElement.attribute(QLatin1String("image")).toLatin1()); + QImage image; + image.loadFromData(ba); + addResult(new Cantor::EpsResult(imageUrl, image)); }else { addResult(new Cantor::ImageResult(imageUrl, resultElement.text())); } } } } const QDomElement& errElem = xml.firstChildElement(QLatin1String("Error")); if (!errElem.isNull()) { setErrorMessage(errElem.text()); setStatus(Error); } else setStatus(Done); } diff --git a/src/markdownentry.cpp b/src/markdownentry.cpp index 9693c19e..87953132 100644 --- a/src/markdownentry.cpp +++ b/src/markdownentry.cpp @@ -1,287 +1,287 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. --- Copyright (C) 2018 Yifei Wu */ #include "markdownentry.h" #include #ifdef Discount_FOUND extern "C" { #include } #endif #include #include MarkdownEntry::MarkdownEntry(Worksheet* worksheet) : WorksheetEntry(worksheet), m_textItem(new WorksheetTextItem(this, Qt::TextEditorInteraction)), rendered(false) { m_textItem->enableRichText(false); m_textItem->installEventFilter(this); connect(m_textItem, &WorksheetTextItem::moveToPrevious, this, &MarkdownEntry::moveToPreviousEntry); connect(m_textItem, &WorksheetTextItem::moveToNext, this, &MarkdownEntry::moveToNextEntry); connect(m_textItem, SIGNAL(execute()), this, SLOT(evaluate())); } void MarkdownEntry::populateMenu(QMenu* menu, QPointF pos) { 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(EpsRenderer::CantorFormula)) { + cursor.charFormat().hasProperty(Cantor::EpsRenderer::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 MarkdownEntry::isEmpty() { return m_textItem->document()->isEmpty(); } int MarkdownEntry::type() const { return Type; } bool MarkdownEntry::acceptRichText() { return false; } bool MarkdownEntry::focusEntry(int pos, qreal xCoord) { if (aboutToBeRemoved()) return false; m_textItem->setFocusAt(pos, xCoord); return true; } void MarkdownEntry::setContent(const QString& content) { rendered = false; plain = content; setPlainText(plain); } void MarkdownEntry::setContent(const QDomElement& content, const KZip& file) { Q_UNUSED(file); rendered = content.attribute(QLatin1String("rendered"), QLatin1String("1")) == QLatin1String("1"); QDomElement htmlEl = content.firstChildElement(QLatin1String("HTML")); if(!htmlEl.isNull()) html = htmlEl.text(); else { html = QLatin1String(""); rendered = false; // No html provided. Assume that it hasn't been rendered. } QDomElement plainEl = content.firstChildElement(QLatin1String("Plain")); if(!plainEl.isNull()) plain = plainEl.text(); else { plain = QLatin1String(""); html = QLatin1String(""); // No plain text provided. The entry shouldn't render anything, or the user can't re-edit it. } if(rendered) setRenderedHtml(html); else setPlainText(plain); } QDomElement MarkdownEntry::toXml(QDomDocument& doc, KZip* archive) { Q_UNUSED(archive); if(!rendered) plain = m_textItem->toPlainText(); QDomElement el = doc.createElement(QLatin1String("Markdown")); el.setAttribute(QLatin1String("rendered"), (int)rendered); QDomElement plainEl = doc.createElement(QLatin1String("Plain")); plainEl.appendChild(doc.createTextNode(plain)); el.appendChild(plainEl); QDomElement htmlEl = doc.createElement(QLatin1String("HTML")); htmlEl.appendChild(doc.createTextNode(html)); el.appendChild(htmlEl); return el; } QString MarkdownEntry::toPlain(const QString& commandSep, const QString& commentStartingSeq, const QString& commentEndingSeq) { Q_UNUSED(commandSep); if (commentStartingSeq.isEmpty()) return QString(); if(!rendered) plain = m_textItem->toPlainText(); QString text = QString(plain); if (!commentEndingSeq.isEmpty()) return commentStartingSeq + text + commentEndingSeq + QLatin1String("\n"); return commentStartingSeq + text.replace(QLatin1String("\n"), QLatin1String("\n") + commentStartingSeq) + QLatin1String("\n"); } void MarkdownEntry::interruptEvaluation() { } bool MarkdownEntry::evaluate(EvaluationOption evalOp) { if(!rendered) { if (m_textItem->toPlainText() == plain && !html.isEmpty()) { setRenderedHtml(html); rendered = true; } else { plain = m_textItem->toPlainText(); rendered = renderMarkdown(plain); } } evaluateNext(evalOp); return true; } bool MarkdownEntry::renderMarkdown(QString& plain) { #ifdef Discount_FOUND QByteArray mdCharArray = plain.toUtf8(); MMIOT* mdHandle = mkd_string(mdCharArray.data(), mdCharArray.size()+1, 0); if(!mkd_compile(mdHandle, MKD_FENCEDCODE | MKD_GITHUBTAGS)) { qDebug()<<"Failed to compile the markdown document"; mkd_cleanup(mdHandle); return false; } char *htmlDocument; int htmlSize = mkd_document(mdHandle, &htmlDocument); html = QString::fromUtf8(htmlDocument, htmlSize); mkd_cleanup(mdHandle); setRenderedHtml(html); return true; #else Q_UNUSED(plain); return false; #endif } void MarkdownEntry::updateEntry() { } WorksheetCursor MarkdownEntry::search(const QString& pattern, unsigned flags, QTextDocument::FindFlags qt_flags, const WorksheetCursor& pos) { if (!(flags & WorksheetEntry::SearchText) || (pos.isValid() && pos.entry() != this)) return WorksheetCursor(); QTextCursor textCursor = m_textItem->search(pattern, qt_flags, pos); if (textCursor.isNull()) return WorksheetCursor(); else return WorksheetCursor(this, m_textItem, textCursor); } void MarkdownEntry::layOutForWidth(qreal w, bool force) { if (size().width() == w && !force) return; m_textItem->setGeometry(0, 0, w); setSize(QSizeF(m_textItem->width(), m_textItem->height() + VerticalMargin)); } bool MarkdownEntry::eventFilter(QObject* object, QEvent* event) { if(object == m_textItem) { if(event->type() == QEvent::GraphicsSceneMouseDoubleClick) { QGraphicsSceneMouseEvent* mouseEvent = dynamic_cast(event); if(!mouseEvent) return false; if(mouseEvent->button() == Qt::LeftButton) { if (rendered) { setPlainText(plain); m_textItem->setCursorPosition(mouseEvent->pos()); m_textItem->textCursor().clearSelection(); rendered = false; return true; } } } } return false; } bool MarkdownEntry::wantToEvaluate() { return !rendered; } void MarkdownEntry::setRenderedHtml(const QString& html) { m_textItem->setHtml(html); m_textItem->denyEditing(); } void MarkdownEntry::setPlainText(const QString& plain) { QTextDocument* doc = m_textItem->document(); doc->setPlainText(plain); m_textItem->setDocument(doc); m_textItem->allowEditing(); } diff --git a/src/textentry.cpp b/src/textentry.cpp index 7fb9a647..502a1062 100644 --- a/src/textentry.cpp +++ b/src/textentry.cpp @@ -1,354 +1,354 @@ /* 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 "epsrenderer.h" +#include "lib/epsrenderer.h" #include "latexrenderer.h" #include #include #include #include TextEntry::TextEntry(Worksheet* worksheet) : WorksheetEntry(worksheet), 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); connect(m_textItem, SIGNAL(execute()), this, SLOT(evaluate())); connect(m_textItem, &WorksheetTextItem::doubleClick, this, &TextEntry::resolveImagesAtCursor); } void TextEntry::populateMenu(QMenu* menu, QPointF pos) { bool imageSelected = false; QTextCursor cursor = m_textItem->textCursor(); const QChar repl = QChar::ObjectReplacementCharacter; if (cursor.hasSelection()) { QString selection = m_textItem->textCursor().selectedText(); imageSelected = selection.contains(repl); } else { // we need to try both the current cursor and the one after the that cursor = m_textItem->cursorForPosition(pos); qDebug() << cursor.position(); for (int i = 2; i; --i) { int p = cursor.position(); if (m_textItem->document()->characterAt(p-1) == repl && - cursor.charFormat().hasProperty(EpsRenderer::CantorFormula)) { + cursor.charFormat().hasProperty(Cantor::EpsRenderer::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; 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); } 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(EpsRenderer::CantorFormula)) + if (format.hasProperty(Cantor::EpsRenderer::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"))); 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) { QTextCursor cursor = findLatexCode(); while (!cursor.isNull()) { QString latexCode = cursor.selectedText(); qDebug()<<"found latex: "<setLatexCode(latexCode); renderer->setEquationOnly(true); renderer->setEquationType(Cantor::LatexRenderer::InlineEquation); renderer->setMethod(Cantor::LatexRenderer::LatexMethod); renderer->renderBlocking(); bool success; QTextImageFormat formulaFormat; if (renderer->renderingSuccessful()) { - EpsRenderer* epsRend = worksheet()->epsRenderer(); + Cantor::EpsRenderer* epsRend = worksheet()->epsRenderer(); formulaFormat = epsRend->render(m_textItem->document(), renderer); success = !formulaFormat.name().isEmpty(); } else { success = false; } qDebug()<<"rendering successful? "<document()->find(QString(QChar::ObjectReplacementCharacter)); while(!cursor.isNull()) { QTextImageFormat format=cursor.charFormat().toImageFormat(); - if (format.hasProperty(EpsRenderer::CantorFormula)) + if (format.hasProperty(Cantor::EpsRenderer::CantorFormula)) { qDebug() << "found a formula... rendering the eps..."; - const QUrl& url=QUrl::fromLocalFile(format.property(EpsRenderer::ImagePath).toString()); + const QUrl& url=QUrl::fromLocalFile(format.property(Cantor::EpsRenderer::ImagePath).toString()); QSizeF s = worksheet()->epsRenderer()->renderToResource(m_textItem->document(), url, QUrl(format.name())); qDebug() << "rendering successful? " << s.isValid(); //cursor.deletePreviousChar(); //cursor.insertText(QString(QChar::ObjectReplacementCharacter), 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(EpsRenderer::Code).toString(); + QString latexCode = cursor.charFormat().property(Cantor::EpsRenderer::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(); } diff --git a/src/textresultitem.cpp b/src/textresultitem.cpp index 763fc714..ffb0ebff 100644 --- a/src/textresultitem.cpp +++ b/src/textresultitem.cpp @@ -1,163 +1,160 @@ /* 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 "textresultitem.h" #include "commandentry.h" #include "lib/result.h" #include "lib/textresult.h" #include "lib/latexresult.h" +#include "lib/epsrenderer.h" #include #include #include #include #include TextResultItem::TextResultItem(QGraphicsObject* parent, Cantor::Result* result) : WorksheetTextItem(parent), ResultItem(result) { setTextInteractionFlags(Qt::TextSelectableByMouse); update(); } double TextResultItem::setGeometry(double x, double y, double w) { return WorksheetTextItem::setGeometry(x, y, w); } void TextResultItem::populateMenu(QMenu* menu, QPointF pos) { QAction * copy = KStandardAction::copy(this, SLOT(copy()), menu); if (!textCursor().hasSelection()) copy->setEnabled(false); menu->addAction(copy); ResultItem::addCommonActions(this, menu); Cantor::Result* res = result(); if (res->type() == Cantor::LatexResult::Type) { QAction* showCodeAction = nullptr; Cantor::LatexResult* lres = dynamic_cast(res); if (lres->isCodeShown()) showCodeAction = menu->addAction(i18n("Show Rendered")); else showCodeAction = menu->addAction(i18n("Show Code")); connect(showCodeAction, &QAction::triggered, this, &TextResultItem::toggleLatexCode); } menu->addSeparator(); qDebug() << "populate Menu"; emit menuCreated(menu, mapToParent(pos)); } void TextResultItem::update() { Q_ASSERT(m_result->type() == Cantor::TextResult::Type || m_result->type() == Cantor::LatexResult::Type); switch(m_result->type()) { case Cantor::TextResult::Type: { QTextCursor cursor = textCursor(); cursor.movePosition(QTextCursor::Start); cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor); QString html = m_result->toHtml(); if (html.isEmpty()) cursor.removeSelectedText(); else cursor.insertHtml(html); } break; case Cantor::LatexResult::Type: setLatex(dynamic_cast(m_result)); break; default: break; } } void TextResultItem::setLatex(Cantor::LatexResult* result) { QTextCursor cursor = textCursor(); cursor.movePosition(QTextCursor::Start); cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor); QString latex = result->toLatex().trimmed(); if (latex.startsWith(QLatin1String("\\begin{eqnarray*}")) && latex.endsWith(QLatin1String("\\end{eqnarray*}"))) { latex = latex.mid(17); latex = latex.left(latex.size() - 15); } if (result->isCodeShown()) { if (latex.isEmpty()) cursor.removeSelectedText(); else cursor.insertText(latex); } else { QTextImageFormat format; - format = epsRenderer()->render(cursor.document(), + Cantor::EpsRenderer* renderer = qobject_cast(scene())->epsRenderer();; + format = renderer->render(cursor.document(), result->data().toUrl()); - format.setProperty(EpsRenderer::CantorFormula, - EpsRenderer::LatexFormula); - format.setProperty(EpsRenderer::Code, latex); - format.setProperty(EpsRenderer::Delimiter, QLatin1String("$$")); + format.setProperty(Cantor::EpsRenderer::CantorFormula, + Cantor::EpsRenderer::LatexFormula); + format.setProperty(Cantor::EpsRenderer::Code, latex); + format.setProperty(Cantor::EpsRenderer::Delimiter, QLatin1String("$$")); if(format.isValid()) cursor.insertText(QString(QChar::ObjectReplacementCharacter), format); else cursor.insertText(i18n("Cannot render Eps file. You may need additional packages")); } } double TextResultItem::width() const { return WorksheetTextItem::width(); } double TextResultItem::height() const { return WorksheetTextItem::height(); } void TextResultItem::toggleLatexCode() { Cantor::LatexResult* lr = dynamic_cast(result()); if(lr->isCodeShown()) lr->showRendered(); else lr->showCode(); parentEntry()->updateEntry(); } void TextResultItem::saveResult() { Cantor::Result* res = result(); const QString& filename = QFileDialog::getSaveFileName(worksheet()->worksheetView(), i18n("Save result"), QString(), res->mimeType()); qDebug() << "saving result to " << filename; res->save(filename); } void TextResultItem::deleteLater() { WorksheetTextItem::deleteLater(); } - -EpsRenderer* TextResultItem::epsRenderer() const -{ - return qobject_cast(scene())->epsRenderer(); -} diff --git a/src/textresultitem.h b/src/textresultitem.h index 39d3a221..f29aa560 100644 --- a/src/textresultitem.h +++ b/src/textresultitem.h @@ -1,57 +1,55 @@ /* 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 TEXTRESULTITEM_H #define TEXTRESULTITEM_H #include "resultitem.h" #include "worksheettextitem.h" #include "worksheetentry.h" -#include "epsrenderer.h" #include "lib/latexresult.h" class TextResultItem : public WorksheetTextItem, public ResultItem { Q_OBJECT public: explicit TextResultItem(QGraphicsObject* parent, Cantor::Result* result); ~TextResultItem() override = default; using WorksheetTextItem::setGeometry; double setGeometry(double x, double y, double w) override; void populateMenu(QMenu* menu, QPointF pos) override; void update() override; void setLatex(Cantor::LatexResult* result); double width() const override; double height() const override; void deleteLater() override; - EpsRenderer* epsRenderer() const; protected Q_SLOTS: void toggleLatexCode(); void saveResult(); }; #endif //TEXTRESULTITEM_H diff --git a/src/worksheet.cpp b/src/worksheet.cpp index 18b23731..c2e83fd2 100644 --- a/src/worksheet.cpp +++ b/src/worksheet.cpp @@ -1,1958 +1,1958 @@ /* 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 "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/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_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_loginDone = false; m_readOnly = false; m_isLoadingFromFile = false; enableHighlighting(Settings::self()->highlightDefault()); enableCompletion(Settings::self()->completionDefault()); enableExpressionNumbering(Settings::self()->expressionNumberingDefault()); enableAnimations(Settings::self()->animationDefault()); } 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_loginDone) 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; } } void Worksheet::loginToSession() { m_session->login(); #ifdef WITH_EPS session()->setTypesettingEnabled(Settings::self()->typesetDefault()); #else session()->setTypesettingEnabled(false); #endif m_loginDone = true; } void Worksheet::print(QPrinter* printer) { m_epsRenderer.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_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); 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)); 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)); if (cursorRectVisible) makeVisible(worksheetCursor()); else if (atEnd) worksheetView()->scrollToEnd(); drawEntryCursor(); } void Worksheet::addProtrusion(qreal width) { if (m_itemProtrusions.contains(width)) ++m_itemProtrusions[width]; else m_itemProtrusions.insert(width, 1); if (width > m_protrusion) { m_protrusion = width; qreal y = lastEntry()->size().height() + lastEntry()->y(); setSceneRect(QRectF(0, 0, m_viewWidth + m_protrusion, y)); } } void Worksheet::updateProtrusion(qreal oldWidth, qreal newWidth) { removeProtrusion(oldWidth); addProtrusion(newWidth); } void Worksheet::removeProtrusion(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 (p > max) max = p; } m_protrusion = max; qreal y = lastEntry()->size().height() + lastEntry()->y(); setSceneRect(QRectF(0, 0, m_viewWidth + m_protrusion, 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() { 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_loginDone && !m_readOnly) loginToSession(); firstEntry()->evaluate(WorksheetEntry::EvaluateNext); emit modified(); } void Worksheet::evaluateCurrentEntry() { if (!m_loginDone && !m_readOnly) 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); } emit modified(); } 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(); emit modified(); } 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(); emit modified(); } 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; } 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; } 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"; 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: "<backend(); if (backend->extensions().contains(QLatin1String("ScriptExtension"))) { Cantor::ScriptExtension* e=dynamic_cast(backend->extension(QLatin1String(("ScriptExtension")))); 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 ) { 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) { KZip file(device); if (!file.open(QIODevice::ReadOnly)) { qDebug()<<"not a zip file"; QApplication::restoreOverrideCursor(); KMessageBox::error(worksheetView(), i18n("The selected file is not a valid Cantor project file."), i18n("Cantor")); return false; } const KArchiveEntry* contentEntry=file.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; m_loginDone = false; //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, file); } else if (tag == QLatin1String("Text")) { entry = appendEntry(TextEntry::Type, false); entry->setContent(expressionChild, file); } else if (tag == QLatin1String("Markdown")) { entry = appendEntry(MarkdownEntry::Type, false); entry->setContent(expressionChild, file); } else if (tag == QLatin1String("Latex")) { entry = appendEntry(LatexEntry::Type, false); entry->setContent(expressionChild, file); } else if (tag == QLatin1String("PageBreak")) { entry = appendEntry(PageBreakEntry::Type, false); entry->setContent(expressionChild, file); } else if (tag == QLatin1String("Image")) { entry = appendEntry(ImageEntry::Type, false); entry->setContent(expressionChild, file); } 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; } 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(); } -EpsRenderer* Worksheet::epsRenderer() +Cantor::EpsRenderer* Worksheet::epsRenderer() { return &m_epsRenderer; } 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(); } } diff --git a/src/worksheet.h b/src/worksheet.h index 77c3993c..446ea7a6 100644 --- a/src/worksheet.h +++ b/src/worksheet.h @@ -1,304 +1,304 @@ /* 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 "epsrenderer.h" +#include "lib/epsrenderer.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: Worksheet(Cantor::Backend* backend, QWidget* parent); ~Worksheet() override; Cantor::Session* session(); void loginToSession(); bool isRunning(); bool isReadOnly(); bool showExpressionIds(); bool animationsEnabled(); 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); - EpsRenderer* epsRenderer(); + Cantor::EpsRenderer* epsRenderer(); 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); bool isShortcut(const QKeySequence&); // 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); 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; 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(); 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; - EpsRenderer m_epsRenderer; + Cantor::EpsRenderer m_epsRenderer; 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_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_showExpressionIds; bool m_animationsEnabled; bool m_loginDone; bool m_isPrinting; bool m_isLoadingFromFile; bool m_readOnly; QString m_backendName; }; #endif // WORKSHEET_H diff --git a/src/worksheettextitem.cpp b/src/worksheettextitem.cpp index 17397964..c7590e8d 100644 --- a/src/worksheettextitem.cpp +++ b/src/worksheettextitem.cpp @@ -1,926 +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 "epsrenderer.h" +#include "lib/epsrenderer.h" #include "worksheetcursor.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) { 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); } 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); } } 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; setTextWidth(w); m_size = document()->size(); 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); } 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(EpsRenderer::Delimiter); + QVariant var = cursor2.charFormat().property(Cantor::EpsRenderer::Delimiter); QString delim; if (var.isValid()) delim = var.value(); else delim = QLatin1String(""); - result += delim + cursor2.charFormat().property(EpsRenderer::Code).value() + delim; + result += delim + cursor2.charFormat().property(Cantor::EpsRenderer::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 = dynamic_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 = dynamic_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::TextSelectableByMouse | Qt::TextSelectableByKeyboard); }