diff --git a/src/animationresultitem.cpp b/src/animationresultitem.cpp index 58909d04..d1f63adf 100644 --- a/src/animationresultitem.cpp +++ b/src/animationresultitem.cpp @@ -1,152 +1,152 @@ /* 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 "animationresultitem.h" #include "commandentry.h" #include "lib/result.h" #include "lib/animationresult.h" #include #include #include #include AnimationResultItem::AnimationResultItem(QGraphicsObject* parent, Cantor::Result* result) : WorksheetImageItem(parent), ResultItem(result), m_height(0), m_movie(nullptr) { update(); } double AnimationResultItem::setGeometry(double x, double y, double w) { Q_UNUSED(w); setPos(x,y); return m_height; } void AnimationResultItem::populateMenu(QMenu* menu, QPointF pos) { ResultItem::addCommonActions(this, menu); menu->addSeparator(); if (m_movie) { if (m_movie->state() == QMovie::Running) menu->addAction(QIcon::fromTheme(QLatin1String("media-playback-pause")), i18n("Pause"), this, SLOT(pauseMovie())); else menu->addAction(QIcon::fromTheme(QLatin1String("media-playback-start")), i18n("Start"), m_movie, SLOT(start())); if (m_movie->state() == QMovie::Running || m_movie->state() == QMovie::Paused) menu->addAction(QIcon::fromTheme(QLatin1String("media-playback-stop")), i18n("Stop"), this, SLOT(stopMovie())); } qDebug() << "populate Menu"; emit menuCreated(menu, mapToParent(pos)); } void AnimationResultItem::update() { Q_ASSERT(m_result->type() == Cantor::AnimationResult::Type); QMovie* mov; switch(m_result->type()) { case Cantor::AnimationResult::Type: mov = static_cast(m_result->data().value()); setMovie(mov); break; default: break; } } QRectF AnimationResultItem::boundingRect() const { return QRectF(0, 0, width(), height()); } double AnimationResultItem::width() const { return WorksheetImageItem::width(); } double AnimationResultItem::height() const { return WorksheetImageItem::height(); } void AnimationResultItem::setMovie(QMovie* movie) { if (m_movie) { m_movie->disconnect(this, SLOT(updateFrame())); m_movie->disconnect(this, SLOT(updateSize())); } m_movie = movie; m_height = 0; if (m_movie) { connect(m_movie, &QMovie::frameChanged, this, &AnimationResultItem::updateFrame); connect(m_movie, &QMovie::resized, this, &AnimationResultItem::updateSize); m_movie->start(); } } void AnimationResultItem::updateFrame() { setImage(m_movie->currentImage()); worksheet()->update(mapRectToScene(boundingRect())); } -void AnimationResultItem::updateSize(const QSize& size) +void AnimationResultItem::updateSize(QSize size) { if (m_height != size.height()) { m_height = size.height(); emit sizeChanged(); } } void AnimationResultItem::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 AnimationResultItem::stopMovie() { if (m_movie) { m_movie->stop(); m_movie->jumpToFrame(0); worksheet()->update(mapRectToScene(boundingRect())); } } void AnimationResultItem::pauseMovie() { if (m_movie) m_movie->setPaused(true); } void AnimationResultItem::deleteLater() { WorksheetImageItem::deleteLater(); } diff --git a/src/animationresultitem.h b/src/animationresultitem.h index a0f9834a..2694d0ae 100644 --- a/src/animationresultitem.h +++ b/src/animationresultitem.h @@ -1,70 +1,70 @@ /* 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 ANIMATIONRESULTITEM_H #define ANIMATIONRESULTITEM_H #include "resultitem.h" #include "worksheetimageitem.h" class QMovie; class CommandEntry; class WorksheetEntry; class AnimationResultItem : public WorksheetImageItem, public ResultItem { Q_OBJECT public: - explicit AnimationResultItem(QGraphicsObject* parent, Cantor::Result* result); + explicit AnimationResultItem(QGraphicsObject*, Cantor::Result*); ~AnimationResultItem() override = default; using WorksheetImageItem::setGeometry; double setGeometry(double x, double y, double w) override; - void populateMenu(QMenu* menu, QPointF pos) override; + void populateMenu(QMenu*, QPointF) override; void update() override; void deleteLater() override; QRectF boundingRect() const override; double width() const override; double height() const override; protected Q_SLOTS: void saveResult(); void stopMovie(); void pauseMovie(); private: - void setMovie(QMovie* movie); + void setMovie(QMovie*); private Q_SLOTS: void updateFrame(); - void updateSize(const QSize& size); + void updateSize(QSize); private: double m_height; QMovie* m_movie; }; #endif //ANIMATIONRESULTITEM_H diff --git a/src/backends/R/rcompletionobject.cpp b/src/backends/R/rcompletionobject.cpp index 3f80598c..104f7738 100644 --- a/src/backends/R/rcompletionobject.cpp +++ b/src/backends/R/rcompletionobject.cpp @@ -1,73 +1,72 @@ /* 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) 2010 Oleksiy Protas */ #include "rcompletionobject.h" #include "rkeywords.h" #include "rsession.h" RCompletionObject::RCompletionObject(const QString& command, int index, RSession* session) : Cantor::CompletionObject(session) { setLine(command, index); } RCompletionObject::~RCompletionObject() { // emit destroyed(this); } void RCompletionObject::fetchCompletions() { if (session()->status() == Cantor::Session::Disable) { QStringList allCompletions; allCompletions << RKeywords::instance()->keywords(); setCompletions(allCompletions); emit fetchingDone(); } else emit requestCompletion(command()); } void RCompletionObject::receiveCompletions(const QString& token,const QStringList& options) { /* Setting up both completion variants -and- the token R generously found for us */ //setCommand(token); //setCompletions(options); // TODO: investigate the empty token problem /* Not so fast, evidently KCompletion requires a nonempty token, hence this stub */ if (token.length()==0 && command().length()!=0) { /* Adding previous symbol to token, ugly but effective */ - QString lastchar(command()[command().length()-1]); + QString lastchar(command().at(command().length()-1)); setCommand(lastchar); - setCompletions(QStringList(options).replaceInStrings(QRegExp(QLatin1String("^")),lastchar)); + setCompletions(QStringList(options).replaceInStrings(QRegExp(QLatin1String("^")), lastchar)); } else { setCommand(token); setCompletions(options); } emit fetchingDone(); } - diff --git a/src/backends/python/pythonbackend.cpp b/src/backends/python/pythonbackend.cpp index 579284c5..5191ee63 100644 --- a/src/backends/python/pythonbackend.cpp +++ b/src/backends/python/pythonbackend.cpp @@ -1,62 +1,62 @@ /* 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 Filipe Saraiva */ #include "pythonbackend.h" #include "pythonsession.h" #include "pythonextensions.h" #include "ui_settings.h" #include #include -PythonBackend::PythonBackend(QObject* parent, const QList args) : Cantor::Backend(parent, args) +PythonBackend::PythonBackend(QObject* parent, const QList& args) : Cantor::Backend(parent, args) { qDebug()<<"Creating PythonBackend"; new PythonLinearAlgebraExtension(this); new PythonPackagingExtension(this); new PythonPlotExtension(this); new PythonScriptExtension(this); new PythonVariableManagementExtension(this); } PythonBackend::~PythonBackend() { qDebug()<<"Destroying PythonBackend"; } Cantor::Backend::Capabilities PythonBackend::capabilities() const { qDebug()<<"Requesting capabilities of PythonSession"; return Cantor::Backend::SyntaxHighlighting | Cantor::Backend::Completion | Cantor::Backend::SyntaxHelp | Cantor::Backend::VariableManagement; } QWidget* PythonBackend::settingsWidget(QWidget* parent) const { QWidget* widget=new QWidget(parent); Ui::PythonSettingsBase s; s.setupUi(widget); return widget; } diff --git a/src/backends/python/pythonbackend.h b/src/backends/python/pythonbackend.h index 0afc9efc..16c10fd8 100644 --- a/src/backends/python/pythonbackend.h +++ b/src/backends/python/pythonbackend.h @@ -1,41 +1,41 @@ /* 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 Filipe Saraiva */ #ifndef _PYTHONBACKEND_H #define _PYTHONBACKEND_H #include "backend.h" #include class CANTOR_PYTHONBACKEND_EXPORT PythonBackend : public Cantor::Backend { Q_OBJECT public: - explicit PythonBackend(QObject* parent = nullptr, const QList args = QList()); + explicit PythonBackend(QObject* parent = nullptr, const QList& args = QList()); ~PythonBackend() override; Cantor::Backend::Capabilities capabilities() const override; QWidget* settingsWidget(QWidget* parent) const override; KConfigSkeleton* config() const override = 0; }; #endif /* _PYTHONBACKEND_H */ diff --git a/src/backends/sage/sagesession.cpp b/src/backends/sage/sagesession.cpp index f83550f5..8d6a7786 100644 --- a/src/backends/sage/sagesession.cpp +++ b/src/backends/sage/sagesession.cpp @@ -1,500 +1,500 @@ /* 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 "sagesession.h" #include "sageexpression.h" #include "sagecompletionobject.h" #include "sagehighlighter.h" #include #include #include #include #include #include #include "settings.h" #ifndef Q_OS_WIN #include #endif const QByteArray SageSession::SagePrompt="sage: "; //Text, sage outputs after each command const QByteArray SageSession::SageAlternativePrompt="....: "; //Text, sage outputs when it expects further input //some commands that are run after login static QByteArray initCmd="os.environ['PAGER'] = 'cat' \n "\ "sage.misc.pager.EMBEDDED_MODE = True \n "\ "sage.misc.viewer.BROWSER='' \n "\ "sage.misc.viewer.viewer.png_viewer('false') \n" \ "sage.plot.plot3d.base.SHOW_DEFAULTS['viewer'] = 'tachyon' \n"\ "sage.misc.latex.EMBEDDED_MODE = True \n "\ "os.environ['PAGER'] = 'cat' \n "\ "%colors nocolor \n "\ "print '____TMP_DIR____', sage.misc.misc.SAGE_TMP\n"; static QByteArray newInitCmd= "__CANTOR_IPYTHON_SHELL__=get_ipython() \n "\ "__CANTOR_IPYTHON_SHELL__.autoindent=False\n "; static QByteArray legacyInitCmd= "__CANTOR_IPYTHON_SHELL__=__IPYTHON__ \n " \ "__CANTOR_IPYTHON_SHELL__.autoindent=False\n "; static QByteArray endOfInitMarker="print '____END_OF_INIT____'\n "; SageSession::VersionInfo::VersionInfo(int major, int minor) { m_major=major; m_minor=minor; } int SageSession::VersionInfo::majorVersion() const { return m_major; } int SageSession::VersionInfo::minorVersion() const { return m_minor; } bool SageSession::VersionInfo::operator==(VersionInfo other) const { return m_major==other.m_major&&m_minor==other.m_minor; } bool SageSession::VersionInfo::operator<(VersionInfo other) const { return (m_major!= -1 && other.m_major==-1) || ( ((m_major!=-1 && other.m_major!=-1) || (m_major==other.m_major && m_major==-1) ) && ( m_major(SageSession::VersionInfo other) const { return !( (*this <= other )); } bool SageSession::VersionInfo::operator>=(SageSession::VersionInfo other) const { return !( *this < other); } SageSession::SageSession(Cantor::Backend* backend) : Session(backend), m_process(nullptr), m_isInitialized(false), m_waitingForPrompt(false), m_haveSentInitCmd(false) { connect( &m_dirWatch, SIGNAL(created(QString)), this, SLOT(fileCreated(QString)) ); } void SageSession::login() { qDebug()<<"login"; emit loginStarted(); m_process=new KPtyProcess(this); updateSageVersion(); const QString& sageExecFile = SageSettings::self()->path().toLocalFile(); if (false) // Reenable when https://trac.sagemath.org/ticket/25363 is merged // if (m_sageVersion >= SageSession::VersionInfo(8, 4)) m_process->setProgram(sageExecFile, QStringList() << QLatin1String("--simple-prompt")); else { const QString& sageStartScript = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("cantor/sagebackend/cantor-execsage")); m_process->setProgram(sageStartScript, QStringList(sageExecFile)); } m_process->setOutputChannelMode(KProcess::SeparateChannels); m_process->setPtyChannels(KPtyProcess::AllChannels); m_process->pty()->setEcho(false); connect(m_process->pty(), SIGNAL(readyRead()), this, SLOT(readStdOut())); connect(m_process, SIGNAL(readyReadStandardError()), this, SLOT(readStdErr())); connect(m_process, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(processFinished(int,QProcess::ExitStatus))); connect(m_process, SIGNAL(error(QProcess::ProcessError)), this, SLOT(reportProcessError(QProcess::ProcessError))); m_process->start(); m_process->waitForStarted(); m_process->pty()->write(initCmd); if(!SageSettings::self()->autorunScripts().isEmpty()){ QString autorunScripts = SageSettings::self()->autorunScripts().join(QLatin1String("\n")); evaluateExpression(autorunScripts, SageExpression::DeleteOnFinish, true); } changeStatus(Session::Done); emit loginDone(); } void SageSession::logout() { qDebug()<<"logout"; disconnect(m_process, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(processFinished(int,QProcess::ExitStatus))); m_process->pty()->write("exit\n"); m_process->kill(); //Run sage-cleaner to kill all the orphans KProcess::startDetached(SageSettings::self()->path().toLocalFile(),QStringList()<setFinishingBehavior(behave); expr->setCommand(cmd); expr->evaluate(); return expr; } void SageSession::readStdOut() { m_outputCache.append(QString::fromUtf8(m_process->pty()->readAll())); qDebug()<<"out: "<= SageSession::VersionInfo(7,4)) { const QString message = i18n( "Sage version %1.%2 is unsupported. Please update your installation "\ "to the supported versions to make it work with Cantor.", m_sageVersion.majorVersion(), m_sageVersion.minorVersion()); KMessageBox::error(nullptr, message, i18n("Cantor")); interrupt(); logout(); } else if(m_sageVersion<=SageSession::VersionInfo(5, 7)) { qDebug()<<"using an old version of sage: "<pty()->write(legacyInitCmd); defineCustomFunctions(); m_process->pty()->write(endOfInitMarker); m_haveSentInitCmd=true; } } else { qDebug()<<"using the current set of commands"; if(!m_haveSentInitCmd) { m_process->pty()->write(newInitCmd); defineCustomFunctions(); m_process->pty()->write(endOfInitMarker); m_haveSentInitCmd=true; } } } } int indexOfEOI=m_outputCache.indexOf(QLatin1String("____END_OF_INIT____")); if(indexOfEOI!=-1&&m_outputCache.indexOf(QLatin1String(SagePrompt), indexOfEOI)!=-1) { qDebug()<<"initialized"; //out.remove("____END_OF_INIT____"); //out.remove(SagePrompt); m_isInitialized=true; m_waitingForPrompt=false; runFirstExpression(); changeStatus(Cantor::Session::Done); m_outputCache.clear(); } //If we are waiting for another prompt, drop every output //until a prompt is found if(m_isInitialized&&m_waitingForPrompt) { qDebug()<<"waiting for prompt"; if(m_outputCache.contains(QLatin1String(SagePrompt))) m_waitingForPrompt=false; m_outputCache.clear(); return; } if(m_isInitialized) { if (!expressionQueue().isEmpty()) { SageExpression* expr = static_cast(expressionQueue().first()); expr->parseOutput(m_outputCache); } m_outputCache.clear(); } } void SageSession::readStdErr() { qDebug()<<"reading stdErr"; QString out=QLatin1String(m_process->readAllStandardError()); qDebug()<<"err: "<(expressionQueue().first()); expr->parseError(out); } } void SageSession::currentExpressionChangedStatus(Cantor::Expression::Status status) { switch (status) { case Cantor::Expression::Done: case Cantor::Expression::Error: expressionQueue().removeFirst(); if (expressionQueue().isEmpty()) changeStatus(Done); else runFirstExpression(); break; default: break; } } void SageSession::processFinished(int exitCode, QProcess::ExitStatus exitStatus) { Q_UNUSED(exitCode); if(exitStatus==QProcess::CrashExit) { if(!expressionQueue().isEmpty()) { static_cast(expressionQueue().last()) ->onProcessError(i18n("The Sage process crashed while evaluating this expression")); }else { //We don't have an actual command. it crashed for some other reason, just show a plain error message box KMessageBox::error(nullptr, i18n("The Sage process crashed"), i18n("Cantor")); } }else { if(!expressionQueue().isEmpty()) { static_cast(expressionQueue().last()) ->onProcessError(i18n("The Sage process exited while evaluating this expression")); }else { //We don't have an actual command. it crashed for some other reason, just show a plain error message box KMessageBox::error(nullptr, i18n("The Sage process exited"), i18n("Cantor")); } } } void SageSession::reportProcessError(QProcess::ProcessError e) { if(e==QProcess::FailedToStart) { changeStatus(Cantor::Session::Done); emit error(i18n("Failed to start Sage")); } } void SageSession::runFirstExpression() { if(!expressionQueue().isEmpty()) { SageExpression* expr = static_cast(expressionQueue().first()); if (m_isInitialized) { connect(expr, SIGNAL(statusChanged(Cantor::Expression::Status)), this, SLOT(currentExpressionChangedStatus(Cantor::Expression::Status))); QString command=expr->command(); if(command.endsWith(QLatin1Char('?')) && !command.endsWith(QLatin1String("??"))) command=QLatin1String("help(")+command.left(command.size()-1)+QLatin1Char(')'); if(command.startsWith(QLatin1Char('?'))) command=QLatin1String("help(")+command.mid(1)+QLatin1Char(')'); command.append(QLatin1String("\n\n")); qDebug()<<"writing "<setStatus(Cantor::Expression::Computing); m_process->pty()->write(command.toUtf8()); } else if (expressionQueue().size() == 1) // If queue contains one expression, it means, what we run this expression immediately (drop setting queued status) // But we can't run expression before initializing, so mark expression, as queue expr->setStatus(Cantor::Expression::Queued); } } void SageSession::interrupt() { if(!expressionQueue().isEmpty()) { qDebug()<<"interrupting " << expressionQueue().first()->command(); if(m_process->state() != QProcess::NotRunning) { #ifndef Q_OS_WIN const int pid=m_process->pid(); kill(pid, SIGINT); #else ; //TODO: interrupt the process on windows #endif } expressionQueue().first()->interrupt(); expressionQueue().removeFirst(); foreach (Cantor::Expression* expression, expressionQueue()) expression->setStatus(Cantor::Expression::Done); expressionQueue().clear(); qDebug()<<"done interrupting"; } changeStatus(Cantor::Session::Done); m_outputCache.clear(); } void SageSession::sendInputToProcess(const QString& input) { m_process->pty()->write(input.toUtf8()); } void SageSession::fileCreated( const QString& path ) { qDebug()<<"got a file "<(expressionQueue().first()); if ( expr ) expr->addFileResult( path ); } } void SageSession::setTypesettingEnabled(bool enable) { Cantor::Session::setTypesettingEnabled(enable); // We have problems with Sage latex output (generates invalid code sometimes), so disable sage // latex output until this not be solved. Users can enable sage latex by hands using %display // sage magic. //tell the sage server to enable/disable pretty_print //const QString cmd=QLatin1String("__cantor_enable_typesetting(%1)"); //evaluateExpression(cmd.arg(enable ? QLatin1String("true"):QLatin1String("false")), Cantor::Expression::DeleteOnFinish); } void SageSession::setWorksheetPath(const QString& path) { //save the path to the worksheet as variable "__file__" //this variable is usually set by the "os" package when running a script //but when it is run in an interpreter (like sage server) it is not set const QString cmd = QLatin1String("__file__ = '%1'"); evaluateExpression(cmd.arg(path), Cantor::Expression::DeleteOnFinish, true); } Cantor::CompletionObject* SageSession::completionFor(const QString& command, int index) { return new SageCompletionObject(command, index, this); } QSyntaxHighlighter* SageSession::syntaxHighlighter(QObject* parent) { return new SageHighlighter(parent); } SageSession::VersionInfo SageSession::sageVersion() { return m_sageVersion; } void SageSession::defineCustomFunctions() { //typesetting QString cmd=QLatin1String("def __cantor_enable_typesetting(enable):\n"); if(m_sageVersion VersionInfo(5, 7) && m_sageVersion< VersionInfo(5, 12)) { cmd+=QLatin1String("\t sage.misc.latex.pretty_print_default(enable)\n\n"); }else { cmd+=QLatin1String("\t if(enable==true):\n "\ "\t \t %display typeset \n"\ "\t else: \n" \ "\t \t %display simple \n\n"); } sendInputToProcess(cmd); } bool SageSession::updateSageVersion() { QProcess get_sage_version; get_sage_version.setProgram(SageSettings::self()->path().toLocalFile()); get_sage_version.setArguments(QStringList()< */ #include "imageentry.h" #include "worksheetimageitem.h" #include "actionbar.h" #include #include #include #include ImageEntry::ImageEntry(Worksheet* worksheet) : WorksheetEntry(worksheet) { m_imageItem = nullptr; m_textItem = new WorksheetTextItem(this); m_imageWatcher = new QFileSystemWatcher(this); m_displaySize.width = -1; m_displaySize.height = -1; m_printSize.width = -1; m_printSize.height = -1; m_displaySize.widthUnit = ImageSize::Auto; m_displaySize.heightUnit = ImageSize::Auto; m_printSize.widthUnit = ImageSize::Auto; m_printSize.heightUnit = ImageSize::Auto; m_useDisplaySizeForPrinting = true; connect(m_imageWatcher, &QFileSystemWatcher::fileChanged, this, &ImageEntry::updateEntry); setFlag(QGraphicsItem::ItemIsFocusable); updateEntry(); startConfigDialog(); } void ImageEntry::populateMenu(QMenu* menu, QPointF pos) { menu->addAction(QIcon::fromTheme(QLatin1String("configure")), i18n("Configure Image"), this, SLOT(startConfigDialog())); menu->addSeparator(); WorksheetEntry::populateMenu(menu, pos); } bool ImageEntry::isEmpty() { return false; } int ImageEntry::type() const { return Type; } bool ImageEntry::acceptRichText() { return false; } void ImageEntry::setContent(const QString& content) { Q_UNUSED(content); return; } void ImageEntry::setContent(const QDomElement& content, const KZip& file) { Q_UNUSED(file); static QStringList unitNames; if (unitNames.isEmpty()) unitNames << QLatin1String("(auto)") << QLatin1String("px") << QLatin1String("%"); QDomElement pathElement = content.firstChildElement(QLatin1String("Path")); QDomElement displayElement = content.firstChildElement(QLatin1String("Display")); QDomElement printElement = content.firstChildElement(QLatin1String("Print")); m_imagePath = pathElement.text(); m_displaySize.width = displayElement.attribute(QLatin1String("width")).toDouble(); m_displaySize.height = displayElement.attribute(QLatin1String("height")).toDouble(); m_displaySize.widthUnit = unitNames.indexOf(displayElement.attribute(QLatin1String("widthUnit"))); m_displaySize.heightUnit = unitNames.indexOf(displayElement.attribute(QLatin1String("heightUnit"))); m_useDisplaySizeForPrinting = printElement.attribute(QLatin1String("useDisplaySize")).toInt(); m_printSize.width = printElement.attribute(QLatin1String("width")).toDouble(); m_printSize.height = printElement.attribute(QLatin1String("height")).toDouble(); m_printSize.widthUnit = unitNames.indexOf(printElement.attribute(QLatin1String("widthUnit"))); m_printSize.heightUnit = unitNames.indexOf(printElement.attribute(QLatin1String("heightUnit"))); updateEntry(); } QDomElement ImageEntry::toXml(QDomDocument& doc, KZip* archive) { Q_UNUSED(archive); static QStringList unitNames; if (unitNames.isEmpty()) unitNames << QLatin1String("(auto)") << QLatin1String("px") << QLatin1String("%"); QDomElement image = doc.createElement(QLatin1String("Image")); QDomElement path = doc.createElement(QLatin1String("Path")); QDomText pathText = doc.createTextNode(m_imagePath); path.appendChild(pathText); image.appendChild(path); QDomElement display = doc.createElement(QLatin1String("Display")); display.setAttribute(QLatin1String("width"), m_displaySize.width); display.setAttribute(QLatin1String("widthUnit"), unitNames[m_displaySize.widthUnit]); display.setAttribute(QLatin1String("height"), m_displaySize.height); display.setAttribute(QLatin1String("heightUnit"), unitNames[m_displaySize.heightUnit]); image.appendChild(display); QDomElement print = doc.createElement(QLatin1String("Print")); print.setAttribute(QLatin1String("useDisplaySize"), m_useDisplaySizeForPrinting); print.setAttribute(QLatin1String("width"), m_printSize.width); print.setAttribute(QLatin1String("widthUnit"), unitNames[m_printSize.widthUnit]); print.setAttribute(QLatin1String("height"), m_printSize.height); print.setAttribute(QLatin1String("heightUnit"), unitNames[m_printSize.heightUnit]); image.appendChild(print); // For the conversion to latex QDomElement latexSize = doc.createElement(QLatin1String("LatexSizeString")); QString sizeString; if (m_useDisplaySizeForPrinting) sizeString = latexSizeString(m_displaySize); else sizeString = latexSizeString(m_printSize); QDomText latexSizeString = doc.createTextNode(sizeString); latexSize.appendChild(latexSizeString); image.appendChild(latexSize); return image; } QString ImageEntry::toPlain(const QString& commandSep, const QString& commentStartingSeq, const QString& commentEndingSeq) { Q_UNUSED(commandSep); return commentStartingSeq + QLatin1String("image: ") + m_imagePath + commentEndingSeq; } QString ImageEntry::latexSizeString(const ImageSize& imgSize) { // We use the transformation 1 px = 1/72 in ( = 1 pt in Latex) QString sizeString=QLatin1String(""); if (imgSize.widthUnit == ImageSize::Auto && imgSize.heightUnit == ImageSize::Auto) return QLatin1String(""); if (imgSize.widthUnit == ImageSize::Percent) { if (imgSize.heightUnit == ImageSize::Auto || (imgSize.heightUnit == ImageSize::Percent && imgSize.width == imgSize.height)) return QLatin1String("[scale=") + QString::number(imgSize.width / 100) + QLatin1String("]"); // else? We could set the size based on the actual image size } else if (imgSize.widthUnit == ImageSize::Auto && imgSize.heightUnit == ImageSize::Percent) { return QLatin1String("[scale=") + QString::number(imgSize.height / 100) + QLatin1String("]"); } if (imgSize.heightUnit == ImageSize::Pixel) sizeString = QLatin1String("height=") + QString::number(imgSize.height) + QLatin1String("pt"); if (imgSize.widthUnit == ImageSize::Pixel) { if (!sizeString.isEmpty()) sizeString += QLatin1String(","); sizeString += QLatin1String("width=") + QString::number(imgSize.width) + QLatin1String("pt"); } return QLatin1String("[") + sizeString + QLatin1String("]"); } void ImageEntry::interruptEvaluation() { } bool ImageEntry::evaluate(EvaluationOption evalOp) { evaluateNext(evalOp); return true; } qreal ImageEntry::height() { if (m_imageItem && m_imageItem->isVisible()) return m_imageItem->height(); else return m_textItem->height(); } void ImageEntry::updateEntry() { qreal oldHeight = height(); if (m_imagePath.isEmpty()) { m_textItem->setPlainText(i18n("Right click here to insert image")); m_textItem->setVisible(true); if (m_imageItem) m_imageItem->setVisible(false); } else { if (!m_imageItem) m_imageItem = new WorksheetImageItem(this); - if (m_imagePath.toLower().endsWith(QLatin1String(".eps"))) { + if (m_imagePath.endsWith(QLatin1String(".eps"), Qt::CaseInsensitive)) { m_imageItem->setEps(QUrl::fromLocalFile(m_imagePath)); } else { QImage img(m_imagePath); m_imageItem->setImage(img); } if (!m_imageItem->imageIsValid()) { const QString msg = i18n("Cannot load image %1", m_imagePath); m_textItem->setPlainText(msg); m_textItem->setVisible(true); m_imageItem->setVisible(false); } else { QSizeF size; if (worksheet()->isPrinting() && ! m_useDisplaySizeForPrinting) size = imageSize(m_printSize); else size = imageSize(m_displaySize); // Hack: Eps images need to be scaled - if (m_imagePath.toLower().endsWith(QLatin1String(".eps"))) + if (m_imagePath.endsWith(QLatin1String(".eps"), Qt::CaseInsensitive)) size /= worksheet()->epsRenderer()->scale(); m_imageItem->setSize(size); qDebug() << size; m_textItem->setVisible(false); m_imageItem->setVisible(true); } } qDebug() << oldHeight << height(); if (oldHeight != height()) recalculateSize(); } QSizeF ImageEntry::imageSize(const ImageSize& imgSize) { const QSize& srcSize = m_imageItem->imageSize(); qreal w = 0.0; qreal h = 0.0; if (imgSize.heightUnit == ImageSize::Percent) h = srcSize.height() * imgSize.height / 100; else if (imgSize.heightUnit == ImageSize::Pixel) h = imgSize.height; if (imgSize.widthUnit == ImageSize::Percent) w = srcSize.width() * imgSize.width / 100; else if (imgSize.widthUnit == ImageSize::Pixel) w = imgSize.width; if (imgSize.widthUnit == ImageSize::Auto) { if (imgSize.heightUnit == ImageSize::Auto) return QSizeF(srcSize.width(), srcSize.height()); else if (h == 0) w = 0; else w = h / srcSize.height() * srcSize.width(); } else if (imgSize.heightUnit == ImageSize::Auto) { if (w == 0) h = 0; else h = w / srcSize.width() * srcSize.height(); } return QSizeF(w,h); } void ImageEntry::startConfigDialog() { ImageSettingsDialog* dialog = new ImageSettingsDialog(worksheet()->worksheetView()); dialog->setData(m_imagePath, m_displaySize, m_printSize, m_useDisplaySizeForPrinting); connect(dialog, &ImageSettingsDialog::dataChanged, this, &ImageEntry::setImageData); dialog->show(); } void ImageEntry::setImageData(const QString& path, const ImageSize& displaySize, const ImageSize& printSize, bool useDisplaySizeForPrinting) { if (path != m_imagePath) { m_imageWatcher->removePath(m_imagePath); m_imageWatcher->addPath(path); m_imagePath = path; } m_displaySize = displaySize; m_printSize = printSize; m_useDisplaySizeForPrinting = useDisplaySizeForPrinting; updateEntry(); } void ImageEntry::addActionsToBar(ActionBar* actionBar) { actionBar->addButton(QIcon::fromTheme(QLatin1String("configure")), i18n("Configure Image"), this, SLOT(startConfigDialog())); } void ImageEntry::layOutForWidth(qreal w, bool force) { if (size().width() == w && !force) return; double width; if (m_imageItem && m_imageItem->isVisible()) { m_imageItem->setGeometry(0, 0, w, true); width = m_imageItem->width(); } else { m_textItem->setGeometry(0, 0, w, true); width = m_textItem->width(); } setSize(QSizeF(width, height() + VerticalMargin)); } bool ImageEntry::wantToEvaluate() { return false; } bool ImageEntry::wantFocus() { return false; } diff --git a/src/worksheet.cpp b/src/worksheet.cpp index 1b134ea3..2d441b56 100644 --- a/src/worksheet.cpp +++ b/src/worksheet.cpp @@ -1,1942 +1,1942 @@ /* 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(); } 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()[0]); + 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(); //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(); current->showCompletion(); } WorksheetEntry* Worksheet::appendEntry(const int type) { 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(); 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(); } 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(); if (tag == QLatin1String("Expression")) { entry = appendCommandEntry(); entry->setContent(expressionChild, file); } else if (tag == QLatin1String("Text")) { entry = appendTextEntry(); entry->setContent(expressionChild, file); } else if (tag == QLatin1String("Markdown")) { entry = appendMarkdownEntry(); entry->setContent(expressionChild, file); } else if (tag == QLatin1String("Latex")) { entry = appendLatexEntry(); entry->setContent(expressionChild, file); } else if (tag == QLatin1String("PageBreak")) { entry = appendPageBreakEntry(); entry->setContent(expressionChild, file); } else if (tag == QLatin1String("Image")) { entry = appendImageEntry(); 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() { 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(); } }