(&QProcess::finished), this, &TeXDocumentWindow::processFinished);
}
else {
// Since the process didn't run, restart watching the output immediately
if (pdfDoc && pdfDoc->widget())
pdfDoc->widget()->setWatchForDocumentChangesOnDisk(true);
TWApp::instance()->typesetManager().stopTypesetting(this);
QMessageBox msgBox(QMessageBox::Critical, tr("Unable to execute %1").arg(e.name()),
QLatin1String("") + tr("The program \"%1\" was not found.").arg(e.program()) + QLatin1String("
") +
#if defined(Q_OS_WIN)
QLatin1String("") + tr("You need a TeX distribution like TeX Live or MiKTeX installed on your system to typeset your document.") + QLatin1String("
") +
#elif defined(Q_OS_DARWIN)
QLatin1String("") + tr("You need a TeX distribution like MacTeX installed on your system to typeset your document.") + QLatin1String("
") +
#else // defined(Q_OS_UNIX) && !defined(Q_OS_DARWIN)
QLatin1String("") + tr("You need a TeX distribution like TeX Live installed on your system to typeset your document. On most systems such a TeX distribution is available as prebuilt package.") + QLatin1String("
") +
#endif
QLatin1String("") + tr("When a TeX distribution is installed you may need to tell TeXworks where to find it in Edit -> Preferences -> Typesetting.") + QLatin1String("
"),
QMessageBox::Cancel, this);
msgBox.setDetailedText(
tr("Searched in directories:") + QChar::fromLatin1('\n') +
QLatin1String(" * ") + Engine::binPaths().join(QLatin1String("\n * ")) + QChar::fromLatin1('\n') +
tr("Check the configuration of the %1 tool and the path settings in the Preferences dialog.").arg(e.name()));
msgBox.exec();
}
}
void TeXDocumentWindow::interrupt()
{
if (process) {
userInterrupt = true;
process->kill();
// Start watching for changes in the pdf (again)
if (pdfDoc && pdfDoc->widget())
pdfDoc->widget()->setWatchForDocumentChangesOnDisk(true);
// Don't notify the TypesetManager that typesetting was stopped; this is
// delegated to processError() which will be called after process->kill()
// and which takes care of resetting the process before notifying the
// TypesetManager. This ensures that subsequent calls to isTypesetting()
// return the correct value
}
}
void TeXDocumentWindow::goToTypesettingWindow()
{
TeXDocumentWindow * owner = qobject_cast(TWApp::instance()->typesetManager().getOwnerForRootFile(textDoc()->getRootFilePath()));
if (owner) {
owner->raise();
owner->activateWindow();
}
}
void TeXDocumentWindow::updateTypesettingAction()
{
TeXDocumentWindow * owner = qobject_cast(TWApp::instance()->typesetManager().getOwnerForRootFile(textDoc()->getRootFilePath()));
disconnect(actionTypeset, &QAction::triggered, this, nullptr);
if (isTypesetting()) {
// We are currently running a typesetting process
// The button should allow the user to stop it
actionTypeset->setIcon(QIcon::fromTheme(QStringLiteral("process-stop")));
actionTypeset->setText(tr("Abort typesetting"));
connect(actionTypeset, &QAction::triggered, this, &TeXDocumentWindow::interrupt);
}
else if (owner != nullptr) {
// Someone else is typesetting "our" (root) document
// The button should take the user to the window from which the process
// was run
actionTypeset->setIcon(QIcon::fromTheme(QStringLiteral("go-jump")));
actionTypeset->setText(tr("Go to typesetting"));
connect(actionTypeset, &QAction::triggered, this, &TeXDocumentWindow::goToTypesettingWindow);
}
else {
// No process is currently running (for "our" root document)
// The button should allow the user to start one
actionTypeset->setIcon(QIcon::fromTheme(QStringLiteral("process-start")));
actionTypeset->setText(tr("Typeset"));
connect(actionTypeset, &QAction::triggered, this, &TeXDocumentWindow::typeset);
}
}
void TeXDocumentWindow::conditionallyEnableRemoveAuxFiles()
{
// In the following cases, we want to disable "Remove Aux Files"
// 1) There cannot be any aux files (as there is no root file; i.e., the
// file has never been saved)
// 2) A typesetting process is running for "our" root file which may be
// accessing the aux files
const bool enable = [&](){
QFileInfo rootFileInfo{textDoc()->getRootFilePath()};
if (!rootFileInfo.exists())
return false;
if (TWApp::instance()->typesetManager().isFileBeingTypeset(rootFileInfo.canonicalFilePath()))
return false;
return true;
}();
actionRemove_Aux_Files->setEnabled(enable);
}
void TeXDocumentWindow::processError(QProcess::ProcessError /*error*/)
{
if (userInterrupt)
textEdit_console->append(tr("Process interrupted by user"));
else
textEdit_console->append(process->errorString());
process->kill();
textEdit_console->setProcess(nullptr, false);
process->deleteLater();
process = nullptr;
inputLine->hide();
// Start watching for changes in the pdf (again)
if (pdfDoc && pdfDoc->widget())
pdfDoc->widget()->setWatchForDocumentChangesOnDisk(true);
TWApp::instance()->typesetManager().stopTypesetting(this);
}
void TeXDocumentWindow::processFinished(int exitCode, QProcess::ExitStatus exitStatus)
{
// Start watching for changes in the pdf (again)
if (pdfDoc && pdfDoc->widget())
pdfDoc->widget()->setWatchForDocumentChangesOnDisk(true);
textEdit_console->setProcess(nullptr, false);
if (exitStatus != QProcess::CrashExit) {
QString pdfName;
if (getPreviewFileName(pdfName)) {
actionGo_to_Preview->setEnabled(true);
if (QFileInfo(pdfName).lastModified() != oldPdfTime) {
// only open/refresh the PDF if it was changed by the typeset process
if (!pdfDoc || pdfName != pdfDoc->fileName()) {
if (showPdfWhenFinished && openPdfIfAvailable(true))
pdfDoc->selectWindow();
}
else {
pdfDoc->reload(); // always reload if it is loaded, we don't want a stale window
if (showPdfWhenFinished)
pdfDoc->selectWindow();
}
}
}
else
actionGo_to_Preview->setEnabled(true);
}
executeAfterTypesetHooks();
Tw::Settings settings;
bool shouldHideConsole = false;
QVariant hideConsoleSetting = settings.value(QString::fromLatin1("autoHideConsole"), kDefault_HideConsole);
// Backwards compatibility to Tw 0.4.0 and before
if (hideConsoleSetting.toString() == QString::fromLatin1("true") || hideConsoleSetting.toString() == QString::fromLatin1("false"))
hideConsoleSetting = (hideConsoleSetting.toBool() ? kDefault_HideConsole : 0);
switch(hideConsoleSetting.toInt()) {
case 0: // Never hide console
shouldHideConsole = false;
break;
case 1: // Hide console automatically
shouldHideConsole = (!keepConsoleOpen && exitCode == 0 && exitStatus != QProcess::CrashExit);
break;
case 2: // Always hide console on success
shouldHideConsole = (exitCode == 0 && exitStatus != QProcess::CrashExit);
break;
default: // Should never happen
;
}
if (shouldHideConsole)
hideConsole();
else
inputLine->hide();
if (process)
process->deleteLater();
process = nullptr;
TWApp::instance()->typesetManager().stopTypesetting(this);
}
void TeXDocumentWindow::executeAfterTypesetHooks()
{
TWScriptManager * scriptManager = TWApp::instance()->getScriptManager();
for (int i = consoleTabs->count() - 1; i > 0; --i)
consoleTabs->removeTab(i);
foreach (Tw::Scripting::ScriptObject *so, scriptManager->getHookScripts(QString::fromLatin1("AfterTypeset"))) {
QVariant result;
Tw::Scripting::ScriptAPI api(so, qApp, this, result);
bool success = so->run(api);
if (success && !result.isNull()) {
QString res = result.toString();
if (res.startsWith(QLatin1String(""), Qt::CaseInsensitive)) {
QTextBrowser *browser = new QTextBrowser(this);
// Use console font (which is customizable)
browser->setFont(textEdit_console->font());
browser->setOpenLinks(false);
connect(browser, &QTextBrowser::anchorClicked, this, &TeXDocumentWindow::anchorClicked);
browser->setHtml(res);
browser->setTextInteractionFlags(Qt::LinksAccessibleByKeyboard | Qt::LinksAccessibleByMouse | Qt::TextBrowserInteraction | Qt::TextSelectableByKeyboard | Qt::TextSelectableByMouse);
consoleTabs->addTab(browser, so->getTitle());
}
else {
QTextEdit *textEdit = new QTextEdit(this);
textEdit->setPlainText(res);
textEdit->setReadOnly(true);
textEdit->setTextInteractionFlags(Qt::LinksAccessibleByKeyboard | Qt::LinksAccessibleByMouse | Qt::TextBrowserInteraction | Qt::TextSelectableByKeyboard | Qt::TextSelectableByMouse);
consoleTabs->addTab(textEdit, so->getTitle());
}
}
}
}
void TeXDocumentWindow::anchorClicked(const QUrl& url)
{
if (url.scheme() == QString::fromLatin1("texworks")) {
int line = 0;
if (url.hasFragment()) {
line = url.fragment().toInt();
}
TeXDocumentWindow * target = openDocument(QFileInfo(textDoc()->getRootFilePath()).absoluteDir().filePath(url.path()), true, true, line);
if (target)
target->textEdit->setFocus(Qt::OtherFocusReason);
}
else {
TWApp::instance()->openUrl(url);
}
}
// showConsole() and hideConsole() are used internally to update the visibility;
// they must NOT change the keepConsoleOpen setting that records user choice
void TeXDocumentWindow::showConsole()
{
consoleTabs->show();
if (process)
inputLine->show();
actionShow_Hide_Console->setText(tr("Hide Console Output"));
}
void TeXDocumentWindow::hideConsole()
{
consoleTabs->hide();
inputLine->hide();
actionShow_Hide_Console->setText(tr("Show Console Output"));
}
// this is connected to the user command, so remember the choice
// for when typesetting finishes
void TeXDocumentWindow::toggleConsoleVisibility()
{
if (consoleTabs->isVisible()) {
hideConsole();
keepConsoleOpen = false;
}
else {
showConsole();
keepConsoleOpen = true;
}
}
void TeXDocumentWindow::acceptInputLine()
{
if (process) {
const QString str = inputLine->text() + QStringLiteral("\n");
textEdit_console->echo(str, inputLine->palette().text().color());
process->write(str.toUtf8());
inputLine->clear();
}
}
void TeXDocumentWindow::goToPreview()
{
if (pdfDoc)
pdfDoc->selectWindow();
else {
if (!openPdfIfAvailable(true)) {
// This should only fail if the user has done something sneaky like closing the
// preview window and then renaming the PDF file, since we opened the source
// and checked that it exists (otherwise Go to Preview would have been disabled).
// We could issue a status-bar warning here but it's a pretty obscure case...
// for now just disable the command.
actionGo_to_Preview->setEnabled(false);
actionSide_by_Side->setEnabled(false);
}
}
}
void TeXDocumentWindow::syncClick(int lineNo, int col)
{
if (!untitled()) {
// ensure that there is a pdf to receive our signal
goToPreview();
emit syncFromSource(textDoc()->absoluteFilePath(), lineNo, col, true);
}
}
void TeXDocumentWindow::handleModelineChange(QStringList changedKeys, QStringList removedKeys)
{
Q_UNUSED(removedKeys);
if (changedKeys.contains(QStringLiteral("program"))) {
QString name = _texDoc->getModeLineValue(QStringLiteral("program"));
int index = engine->findText(name, Qt::MatchFixedString);
if (index > -1) {
if (index != engine->currentIndex()) {
engine->setCurrentIndex(index);
emit asyncFlashStatusBarMessage(tr("Set engine to \"%1\"").arg(engine->currentText()), kStatusMessageDuration);
}
}
else {
emit asyncFlashStatusBarMessage(tr("Engine \"%1\" not defined").arg(name), kStatusMessageDuration);
}
}
if (changedKeys.contains(QStringLiteral("encoding"))) {
bool hasMetadata{false};
QString reqName;
QTextCursor curs(textDoc());
// (begin|end)EditBlock() is a workaround for QTBUG-24718 that causes
// movePosition() to crash the program under some circumstances.
// Since we don't change any text in the edit block, it should be a noop
// in the context of undo/redo.
curs.beginEditBlock();
curs.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, PEEK_LENGTH);
curs.endEditBlock();
QTextCodec *newCodec = scanForEncoding(curs.selectedText(), hasMetadata, reqName);
if (newCodec) {
codec = newCodec;
showEncodingSetting();
}
}
if (changedKeys.contains(QStringLiteral("spellcheck"))) {
setSpellcheckLanguage(_texDoc->getModeLineValue(QStringLiteral("spellcheck")));
}
}
void TeXDocumentWindow::goToTag(int index)
{
if (_texDoc && index < _texDoc->getTags().count()) {
textEdit->setTextCursor(_texDoc->getTags()[index].cursor);
textEdit->setFocus(Qt::OtherFocusReason);
}
}
bool TeXDocumentWindow::isTypesetting() const
{
return (process != nullptr || TWApp::instance()->typesetManager().getOwnerForRootFile(textDoc()->getRootFilePath()) == this);
}
void TeXDocumentWindow::removeAuxFiles()
{
const QString & rootFilePath = textDoc()->getRootFilePath();
if (rootFilePath.isEmpty())
return;
QFileInfo fileInfo(rootFilePath);
QString jobname = fileInfo.completeBaseName();
QDir dir(fileInfo.dir());
QStringList filterList = TWUtils::cleanupPatterns().split(QRegularExpression(QStringLiteral("\\s+")));
if (filterList.count() == 0)
return;
for (int i = 0; i < filterList.count(); ++i)
filterList[i].replace(QLatin1String("$jobname"), jobname);
dir.setNameFilters(filterList);
QStringList auxFileList = dir.entryList(QDir::Files | QDir::CaseSensitive, QDir::Name);
if (auxFileList.count() > 0)
Tw::UI::RemoveAuxFilesDialog::doConfirmDelete(dir, auxFileList);
else
(void)QMessageBox::information(this, tr("No files found"),
tr("No auxiliary files associated with this document at the moment."));
}
#if defined(Q_OS_DARWIN)
#define OPEN_FILE_IN_NEW_WINDOW Qt::MoveAction // unmodified drag appears as MoveAction on Mac OS X
#define INSERT_DOCUMENT_TEXT Qt::CopyAction
#define CREATE_INCLUDE_COMMAND Qt::LinkAction
#else
#define OPEN_FILE_IN_NEW_WINDOW Qt::CopyAction // ...but as CopyAction on X11
#define INSERT_DOCUMENT_TEXT Qt::MoveAction
#define CREATE_INCLUDE_COMMAND Qt::LinkAction
#endif
void TeXDocumentWindow::dragEnterEvent(QDragEnterEvent *event)
{
event->ignore();
if (event->mimeData()->hasUrls()) {
const QList urls = event->mimeData()->urls();
foreach (const QUrl& url, urls) {
if (url.scheme() == QString::fromLatin1("file")) {
event->acceptProposedAction();
break;
}
}
}
}
void TeXDocumentWindow::dragMoveEvent(QDragMoveEvent *event)
{
if (event->proposedAction() == INSERT_DOCUMENT_TEXT || event->proposedAction() == CREATE_INCLUDE_COMMAND) {
if (dragSavedCursor.isNull())
dragSavedCursor = textEdit->textCursor();
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
QTextCursor curs = textEdit->cursorForPosition(textEdit->mapFromGlobal(mapToGlobal(event->pos())));
#else
QTextCursor curs = textEdit->cursorForPosition(textEdit->mapFromGlobal(mapToGlobal(event->position().toPoint())));
#endif
textEdit->setTextCursor(curs);
}
else {
if (!dragSavedCursor.isNull()) {
textEdit->setTextCursor(dragSavedCursor);
dragSavedCursor = QTextCursor();
}
}
event->acceptProposedAction();
}
void TeXDocumentWindow::dragLeaveEvent(QDragLeaveEvent *event)
{
if (!dragSavedCursor.isNull()) {
textEdit->setTextCursor(dragSavedCursor);
dragSavedCursor = QTextCursor();
}
event->accept();
}
void TeXDocumentWindow::dropEvent(QDropEvent *event)
{
if (event->mimeData()->hasUrls()) {
Qt::DropAction action = event->proposedAction();
const QList urls = event->mimeData()->urls();
bool editBlockStarted = false;
QString text;
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
QTextCursor curs = textEdit->cursorForPosition(textEdit->mapFromGlobal(mapToGlobal(event->pos())));
#else
QTextCursor curs = textEdit->cursorForPosition(textEdit->mapFromGlobal(mapToGlobal(event->position().toPoint())));
#endif
foreach (const QUrl& url, urls) {
if (url.scheme() == QString::fromLatin1("file")) {
QString fileName = url.toLocalFile();
switch (action) {
case OPEN_FILE_IN_NEW_WINDOW:
if(!Tw::Document::isImageFile(fileName)) {
TWApp::instance()->openFile(fileName);
break;
}
// for graphic files, fall through (there's no point in
// trying to open binary files as text
// fall through
case INSERT_DOCUMENT_TEXT:
if (!Tw::Document::isPDFfile(fileName) && !Tw::Document::isImageFile(fileName) && !Tw::Document::isPostscriptFile(fileName)) {
QTextCodec * codecUsed{nullptr};
text = readFile(QFileInfo(fileName), &codecUsed);
if (!text.isNull()) {
if (!editBlockStarted) {
curs.beginEditBlock();
editBlockStarted = true;
}
textEdit->setTextCursor(curs);
curs.insertText(text);
}
break;
}
// for graphic files, fall through -- behave the same as the "link" action
// fall through
case CREATE_INCLUDE_COMMAND:
if (!editBlockStarted) {
curs.beginEditBlock();
editBlockStarted = true;
}
textEdit->setTextCursor(curs);
if (Tw::Document::isPDFfile(fileName))
text = TWUtils::includePdfCommand();
else if (Tw::Document::isImageFile(fileName))
text = TWUtils::includeImageCommand();
else if (Tw::Document::isPostscriptFile(fileName))
text = TWUtils::includePostscriptCommand();
else
text = TWUtils::includeTextCommand();
curs.insertText(text.arg(fileName));
break;
default:
// do nothing
break;
}
}
}
if (editBlockStarted)
curs.endEditBlock();
}
dragSavedCursor = QTextCursor();
event->accept();
}
void TeXDocumentWindow::detachPdf()
{
if (pdfDoc) {
disconnect(pdfDoc, &PDFDocumentWindow::destroyed, this, &TeXDocumentWindow::pdfClosed);
disconnect(this, &TeXDocumentWindow::destroyed, pdfDoc, &PDFDocumentWindow::texClosed);
pdfDoc = nullptr;
}
}