Subversion Repositories Scribus

Rev

Rev 22600 | Rev 22716 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

/*
For general Scribus (>=1.3.2) copyright and licensing information please refer
to the COPYING file provided with the program. Following this notice may exist
a copyright and/or license notice that predates the release of Scribus 1.3.2
for which a new license (GPL+exception) is in place.
*/

/***************************************************************************
                                                pageitem_latexframe.cpp  -  description
                                                -------------------
        begin                : Mon May 28 2007
        copyright            : (C) 2007 by Hermann Kraus
        email                : hermann@physiklaborant.de
***************************************************************************/


/***************************************************************************
*                                                                         *
*   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.                                   *
*                                                                         *
***************************************************************************/


#include "pageitem_latexframe.h"

#include <QDebug>
#include <QFontInfo>
#include <QMessageBox>
#include <QSignalBlocker>
#include <QTemporaryFile>

#include "prefsmanager.h"
#include "scpainter.h"
#include "scraction.h"
#include "scribus.h"
#include "scribusdoc.h"
#include "undomanager.h"
#include "undostate.h"
#include "ui/latexeditor.h"
#include "latexhelpers.h"
#include "util.h"

PageItem_LatexFrame::PageItem_LatexFrame(ScribusDoc *pa, double x, double y, double w, double h, double w2, const QString& fill, const QString& outline)
                : PageItem_ImageFrame(pa, x, y, w, h, w2, fill, outline)
{
        setUPixmap(Um::ILatexFrame);
        AnName = tr("Render") + QString::number(m_Doc->TotalItems);
        setUName(AnName);
       
        m_imgValid = false;
        m_usePreamble = true;
        m_err = 0;
        internalEditor = nullptr;
        m_killed = false;
       
        config = nullptr;
        if (PrefsManager::instance()->latexConfigs().count() > 0)
                setConfigFile(PrefsManager::instance()->latexConfigs()[0]);

        latex = new QProcess();
        connect(latex, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(updateImage(int,QProcess::ExitStatus)));
        connect(latex, SIGNAL(error(QProcess::ProcessError)), this, SLOT(latexError(QProcess::ProcessError)));
        latex->setProcessChannelMode(QProcess::MergedChannels);
       
        QTemporaryFile *tempfile = new QTemporaryFile(QDir::tempPath() + "/scribus_temp_render_XXXXXX");
        tempfile->open();
        tempFileBase = getLongPathName(tempfile->fileName());
        tempfile->setAutoRemove(false);
        tempfile->close();
        delete tempfile;
        Q_ASSERT(!tempFileBase.isEmpty());
       
        m_dpi = 0;
        m_lastWidth = m_lastHeight = 0;
        m_lastDpi = realDpi();

        m_imageXScale *= (72.0 / realDpi());
        m_imageYScale *= (72.0 / realDpi());
}

PageItem_LatexFrame::~PageItem_LatexFrame()
{
        delete internalEditor;
       
        latex->disconnect();
        if (latex->state() != QProcess::NotRunning)
        {
                m_killed = true;
                latex->terminate();
                latex->waitForFinished(500);
                if (latex->state() != QProcess::NotRunning)
                {
                        latex->kill();
                        latex->waitForFinished(500);
                }
        }
        deleteImageFile();
        delete latex;
}

void PageItem_LatexFrame::clearContents()
{
        PageItem_ImageFrame::clearContents();
        formulaText = "";
        appStdout = "";
        m_err = 0;
        m_imgValid = false;
}

void PageItem_LatexFrame::deleteImageFile()
{
        QFileInfo fi(tempFileBase);
        QDir dir = fi.absoluteDir();
        QStringList filter;
       
        filter << fi.fileName() + "*";
        Q_ASSERT(!fi.fileName().isEmpty());
        Q_ASSERT(!fi.fileName().contains("/"));
        Q_ASSERT(!fi.fileName().contains("\\"));

        const QStringList files = dir.entryList(filter);
        for (const QString& file : files)
        {
                Q_ASSERT(file.startsWith("scribus_temp"));
                dir.remove(file);
        }
        m_imgValid = false;
}

void PageItem_LatexFrame::DrawObj_Item(ScPainter *p, QRectF e)
{
        layout();
        if (!m_imgValid && !m_err)
        {
                //Draw indicator that latex is running
                p->setBrush(Qt::white);
                p->setPen(Qt::green, 1, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin);
                p->drawLine(FPoint(0, 0), FPoint(m_width, m_height));
                const QFont &font = QApplication::font();
                p->setFont(PrefsManager::instance()->appPrefs.fontPrefs.AvailFonts.findFont(font.family(), QFontInfo(font).styleName()), font.pointSizeF());
                p->drawText(QRectF(0.0, 0.0, m_width, m_height), tr("Rendering..."));
        }
        else if (m_err)
        {
                //Draw error indicator
                p->setBrush(Qt::white);
                p->setPen(Qt::blue, 1, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin);
                p->drawLine(FPoint(0, 0), FPoint(m_width, m_height));
                p->drawLine(FPoint(0, m_height), FPoint(m_width, 0));
                const QFont &font = QApplication::font();
                p->setFont(PrefsManager::instance()->appPrefs.fontPrefs.AvailFonts.findFont(font.family(), QFontInfo(font).styleName()), font.pointSizeF());
                p->drawText(QRectF(0.0, 0.0, m_width, m_height), tr("Render Error"));
        }
        else
        {
                //Just pass it to ImageFrame
                PageItem_ImageFrame::DrawObj_Item(p, e);
        }
}

void PageItem_LatexFrame::updateImage(int exitCode, QProcess::ExitStatus exitStatus)
{
        appStdout = latex->readAllStandardOutput();
        invalid = false;
        m_err = exitCode;
       
        emit latexFinished();
        emit stateChanged(latex->state());
       
        static bool firstWarning = true;
        if (exitCode) {
                m_imgValid = false;
                if (firstWarning && !m_killed)
                {
                        bool editorRunning = internalEditor && internalEditor->isVisible();
                        ScMessageBox msgBox;
                        msgBox.setText(tr("Running the external application failed!"));
                        QString informativeText = tr("This is usually a problem with your input. Please check the program's output.");
                        if (!editorRunning) {
                                informativeText += " "+tr("Do you want to open the editor to fix the problem?");
                                msgBox.setStandardButtons(QMessageBox::Yes|QMessageBox::No);
                                msgBox.setDefaultButton(QMessageBox::Yes);
                        } else {
                                msgBox.setStandardButtons(QMessageBox::Ok);
                                msgBox.setDefaultButton(QMessageBox::Ok);
                        }
                        msgBox.setIcon(QMessageBox::Warning);
                        msgBox.setInformativeText(informativeText);
                        msgBox.setDetailedText(output());
                        if ((msgBox.exec() == QMessageBox::Yes) && !editorRunning) {
                                runEditor();
                        }
                        firstWarning = false;
                }
                qCritical() << "RENDER FRAME: updateImage():" << tr("Running the external application failed!");
                m_killed = false;
                update(); //Show error marker
                return;
        }
        firstWarning = true;
        m_imgValid = true;

        //Save state and restore afterwards
        double xres, yres;
        if (imageIsAvailable)
        {
                xres = pixm.imgInfo.xres;
                yres = pixm.imgInfo.yres;
        }
        else
        {
                xres = yres = realDpi();
        }
        double scaleX = m_imageXScale * xres;
        double scaleY = m_imageYScale * yres;
        double offX   = m_imageXOffset / xres;
        double offY   = m_imageYOffset / yres;
        PageItem_ImageFrame::loadImage(imageFile, true, realDpi());
        if (PrefsManager::instance()->latexForceDpi())
        {
                pixm.imgInfo.xres = pixm.imgInfo.yres = realDpi();
        }
        m_lastDpi = realDpi();
       
        //Restore parameters, account for dpi changes
        m_imageXScale = scaleX / pixm.imgInfo.xres;
        m_imageYScale = scaleY / pixm.imgInfo.yres;
        m_imageXOffset = offX  * pixm.imgInfo.xres;
        m_imageYOffset = offY  * pixm.imgInfo.yres;
        //emit imageOffsetScale(LocalScX, LocalScY, LocalX, LocalY);
        update();
}


void PageItem_LatexFrame::runApplication()
{
        m_imgValid = false;
        m_err = 0;
        m_killed = false;
       
        static bool firstWarningTmpfile = true;
        static bool firstWarningLatexMissing = true;

        if (!config)
        {
                qCritical() << "RENDER FRAME:" << tr("No configuration defined to run the application!");
                return;
        }
       
        QFile tempfile(tempFileBase);
        if (!tempfile.open(QIODevice::Truncate|QIODevice::WriteOnly)) {
                m_err = 0xffff;
                update(); //Show error marker
                if (firstWarningTmpfile)
                {
                        ScMessageBox::critical(nullptr, tr("Error"), "<qt>" +
                                                                  tr("Could not create a temporary file to run the application!")
                                                                  + "</qt>");
                        firstWarningTmpfile = false;
                }
                qCritical() << "RENDER FRAME:" << tr("Could not create a temporary file to run the application!");
                //Don't know how to continue as it's impossible to create tempfile
                return;
        }
        firstWarningTmpfile = true;
       
        QString full_command = config->executable();
        if (full_command.isEmpty())
        {
                m_err = 0xffff;
                update(); //Show error marker
                if (firstWarningLatexMissing)
                {
                        ScMessageBox::critical(nullptr, tr("Error"),
                                                                         "<qt>" + tr("The config file didn't specify a executable path!") +
                                                                         "</qt>");
                        firstWarningLatexMissing = false;
                }
        qCritical() << "RENDER FRAME:" << tr("The config file didn't specify a executable path!");
                return;
        }
        firstWarningLatexMissing = true;

        full_command.replace("%dpi", QString::number(realDpi()));
        if (full_command.contains("%file"))
                full_command.replace("%file", QString("\"%1\"").arg(QDir::toNativeSeparators(tempFileBase)));
        else
                full_command = full_command + QString(" \"%1\"").arg(QDir::toNativeSeparators(tempFileBase));

        full_command.replace("%dir", QString("\"%1\"").arg(QDir::toNativeSeparators(QDir::tempPath())));
        latex->setWorkingDirectory(QDir::tempPath());

        double lDpi = realDpi()/72.0;
        full_command.replace("$scribus_height_px$", QString::number(qRound(m_height*lDpi)));
        full_command.replace("$scribus_width_px$",  QString::number(qRound(m_width*lDpi)));
        QMapIterator<QString, QString> i(editorProperties);
        while (i.hasNext())
        {
                i.next();
                full_command.replace("$scribus_"+i.key()+"$", i.value());
        }
       
        imageFile = tempFileBase + config->imageExtension();

        writeFileContents(&tempfile);
        tempfile.close();
       
        latex->start(full_command);
        emit stateChanged(QProcess::Starting);
}

void PageItem_LatexFrame::runEditor()
{
        if (!internalEditor) {
                internalEditor = new LatexEditor(this);
                internalEditor->startEditor();
        } else if (internalEditor->isVisible()) {
                internalEditor->activateWindow();
                internalEditor->raise();
        } else {
                internalEditor->startEditor();
        }
}

void PageItem_LatexFrame::rerunApplication(bool updateDisplay)
{
        if (latex->state() != QProcess::NotRunning)
        {
                QSignalBlocker sigBloker(latex);
                m_killed = true;
                latex->terminate();
                latex->waitForFinished(500);
                if (latex->state() != QProcess::NotRunning)
                {
                        //Still not terminated?
                        latex->kill();
                        latex->waitForFinished(500);
                }
        }
        runApplication();
        if (updateDisplay)
                this->update();
}


void PageItem_LatexFrame::writeFileContents(QFile *tempfile)
{
        QString tmp(formulaText);
        double scaleX, scaleY, realW, realH, offsetX, offsetY;
        double lDpi = realDpi() / 72.0;
        scaleX = m_imageXScale * lDpi;
        scaleY = m_imageYScale * lDpi;
        offsetX = m_imageXOffset * m_imageXScale;
        offsetY = m_imageYOffset * m_imageYScale;
        realW = m_width  - m_imageXOffset / lDpi;
        realH = m_height - m_imageYOffset / lDpi;
        if (!tmp.contains("$scribus_noprepost$") && m_usePreamble) {
                tmp = config->preamble() + tmp + config->postamble();
        }
        tmp.replace(QString("$scribus_width$"), QString::number(m_width));
        tmp.replace(QString("$scribus_width_px$"), QString::number(qRound(m_width*lDpi)));
        tmp.replace(QString("$scribus_width_inch$"), QString::number(m_width/72.0));
        tmp.replace(QString("$scribus_height$"), QString::number(m_height));
        tmp.replace(QString("$scribus_height_px$"), QString::number(qRound(m_height*lDpi)));
        tmp.replace(QString("$scribus_height_inch$"), QString::number(m_height/72.0));
        tmp.replace(QString("$scribus_realwidth$"), QString::number(realW));
        tmp.replace(QString("$scribus_realwidth_px$"), QString::number(qRound(realW*lDpi)));
        tmp.replace(QString("$scribus_realheight$"), QString::number(realH));
        tmp.replace(QString("$scribus_realheight_px$"), QString::number(qRound(realH*lDpi)));
        tmp.replace(QString("$scribus_offsetX$"), QString::number(offsetX));
        tmp.replace(QString("$scribus_offsetX_px$"), QString::number(qRound(offsetX*lDpi)));
        tmp.replace(QString("$scribus_offsetY$"), QString::number(offsetY));
        tmp.replace(QString("$scribus_offsetY$"), QString::number(qRound(offsetY*lDpi)));
        tmp.replace(QString("$scribus_scaleX$"), QString::number(scaleX));
        tmp.replace(QString("$scribus_scaleY$"), QString::number(scaleY));
        tmp.replace(QString("$scribus_dpi$"), QString::number(realDpi()));
        tmp.replace(QString("$scribus_file$"), tempFileBase);
        tmp.replace(QString("$scribus_dir$"), QDir::tempPath());
        QMapIterator<QString, QString> i(editorProperties);
        while (i.hasNext()) {
                i.next();
                tmp.replace("$scribus_"+i.key()+"$", i.value());
        }
        tempfile->write(tmp.toUtf8());
}

bool PageItem_LatexFrame::setFormula(const QString& formula, bool undoable)
{
        if (formula == formulaText) {
                //Nothing changed
                return false;
        }
        m_imgValid = false;
        m_err = 0;
        if (UndoManager::undoEnabled() && undoable)
        {
                SimpleState *ss = new SimpleState(Um::ChangeFormula, "", Um::IChangeFormula);
                ss->set("CHANGE_FORMULA");
                ss->set("OLD_FORMULA", formulaText);
                ss->set("NEW_FORMULA", formula);
                undoManager->action(this, ss);
        } else if (!undoable) {
                emit formulaAutoUpdate(formulaText, formula);
        }
        formulaText = formula;
        return true;
}

void PageItem_LatexFrame::latexError(QProcess::ProcessError error)
{
        m_err = 0xffff;
        update(); //Show error marker
        static bool firstWarning = true;
        if (m_killed) {
                m_killed = false;
                update();
                //Don't show errors caused by killing processes
                return;
        }
        if (firstWarning)
        {
                if (latex->error() == QProcess::FailedToStart) {
                        ScMessageBox::critical(nullptr, tr("Error"), "<qt>" +
                                                                  tr("The application \"%1\" failed to start! Please check the path: ").
                                                                  arg(config->executable())
                                                                  + "</qt>");
                } else {
                        ScMessageBox::critical(nullptr, tr("Error"), "<qt>" +
                                        tr("The application \"%1\" crashed!").arg(config->executable())
                                        + "</qt>");
                }
                firstWarning = false;
        }
        qCritical() << "RENDER FRAME: latexError():" <<
                        tr("Running the application \"%1\" failed!").arg(config->executable()) << latex->error();
        emit stateChanged(QProcess::NotRunning);
}


QString PageItem_LatexFrame::application() const
{
        return config ? config->executable() : tr("No application defined");
}

QString PageItem_LatexFrame::configFile() const
{
        return configFilename;
}

int PageItem_LatexFrame::realDpi() const
{
        if (m_dpi)
                return m_dpi;
        return PrefsManager::instance()->latexResolution();
}

void PageItem_LatexFrame::setDpi(int newDpi)
{
        m_dpi = newDpi;
}

void PageItem_LatexFrame::setConfigFile(QString newConfig, bool relative)
{
        // Try to interpret a config file as a relative path even when it is given with a full path
        if (relative)
        {
                QFileInfo fi;
                const QStringList configs = PrefsManager::instance()->latexConfigs();
                for (const QString& config : configs)
                {
                        fi.setFile(config);
                        if (newConfig == fi.fileName())
                        {
                                newConfig = config;
                                break;
                        }
                }
        }

        if (configFilename == newConfig) return;
       
        bool unchanged = formulaText.isEmpty();
        if (config && (formulaText == config->emptyFrameText())) {
                unchanged = true;
        }
       
        configFilename = newConfig;
        config = LatexConfigCache::instance()->parser(configFilename);
        //Initialize with default values
        QString key;
        QMapIterator<QString, QString> i(config->properties);
        while (i.hasNext()) {
                i.next();
                key = i.key();
                if (!editorProperties.contains(key)) {
                        editorProperties[key] = i.value();
                }
        }
        QString newFormula;
        if (unchanged) {
                if (PrefsManager::instance()->latexStartWithEmptyFrames()) {
                        newFormula = "";
                } else {
                        newFormula = config->emptyFrameText();
                }
                emit formulaAutoUpdate(formulaText, newFormula);
                formulaText = newFormula;
        }
       
        emit applicationChanged();
}

void PageItem_LatexFrame::killProcess()
{
        m_killed = true;
        latex->kill();
}

void PageItem_LatexFrame::restore(UndoState *state, bool isUndo)
{
        SimpleState *ss = dynamic_cast<SimpleState*>(state);
        if (!ss)
        {
                PageItem_ImageFrame::restore(state, isUndo);
                return;
        }
        if (ss->contains("CHANGE_FORMULA"))
        {
                if (isUndo)
                        setFormula(ss->get("OLD_FORMULA"), false);
                else
                        setFormula(ss->get("NEW_FORMULA"), false);
                rerunApplication(true);
        }
        else
                PageItem_ImageFrame::restore(state, isUndo);
}


void PageItem_LatexFrame::setUsePreamble(bool useP)
{
        m_usePreamble = useP;
}

void PageItem_LatexFrame::layout()
{
        if (!invalid) return;
        invalid = false;
       
        if (m_width == m_lastWidth && m_height == m_lastHeight && m_lastDpi == realDpi())
                return;
        m_lastWidth = m_width;
        m_lastHeight = m_height;
        m_lastDpi = realDpi();
       
        rerunApplication(false);
}

void PageItem_LatexFrame::applicableActions(QStringList & actionList)
{
        actionList << "itemImageIsVisible";
        actionList << "itemPreviewFull";
        actionList << "itemPreviewLow";
        actionList << "itemPreviewNormal";
        actionList << "itemUpdateImage";
        actionList << "editEditRenderSource";
        if (imageIsAvailable)
        {
                /*if (!isTableItem)
                        actionList << "itemAdjustFrameToImage";*/

                actionList << "editClearContents";
                actionList << "editCopyContents";
        }
        if (doc()->scMW()->contentsBuffer.sourceType==PageItem::LatexFrame)
        {
                actionList << "editPasteContents";
                actionList << "editPasteContentsAbs";
        }
}

QString PageItem_LatexFrame::infoDescription()
{
        QString htmlText;
        htmlText.append("<h2>"+tr("Render Frame") + "</h2><table>");
        htmlText.append("<tr><th align=\"right\">" + tr("Application") + ": </th><td>" + application());
        htmlText.append("</td></tr><tr><th align=\"right\">" + tr("DPI") + ": </th><td>" +
                         QString::number(realDpi()));
        htmlText.append("</td></tr><tr><th align=\"right\">" + tr("State") + ": </th><td>");
        if (latex->state() == QProcess::NotRunning) {
                if (error()) {
                        htmlText.append(tr("Error"));
                } else {
                        htmlText.append(tr("Finished"));
                }
        } else {
                htmlText.append(tr("Running"));
        }
        htmlText.append("</td></tr></table><hr>");
        htmlText.append(PageItem::infoDescription());
        return htmlText;
}