results;
flags &= ~QTextDocument::FindBackward;
int docListIndex = 0;
TeXDocument* theDoc = this;
while (1) {
QTextCursor curs(theDoc->textDoc());
curs.movePosition(QTextCursor::End);
int rangeStart = 0;
int rangeEnd = curs.position();
while (1) {
curs = doSearch(theDoc->textDoc(), searchText, regex, flags, rangeStart, rangeEnd);
if (curs.isNull())
break;
int blockStart = curs.block().position();
results.append(SearchResult(theDoc, curs.blockNumber() + 1,
curs.selectionStart() - blockStart, curs.selectionEnd() - blockStart));
if ((flags & QTextDocument::FindBackward) != 0)
rangeEnd = curs.selectionStart();
else
rangeStart = curs.selectionEnd();
}
if (settings.value("searchAllFiles").toBool() == false)
break;
// go to next document
next_doc:
if (docList[docListIndex] == theDoc)
docListIndex++;
if (docListIndex == docList.count())
break;
theDoc = docList[docListIndex];
if (theDoc == this)
goto next_doc;
singleFile = false;
}
if (results.count() == 0) {
qApp->beep();
statusBar()->showMessage(tr("Not found"), kStatusMessageDuration);
}
else {
SearchResults::presentResults(searchText, results, this, singleFile);
statusBar()->showMessage(tr("Found %n occurrence(s)", "", results.count()), kStatusMessageDuration);
}
}
else {
QTextCursor curs = textEdit->textCursor();
if (settings.value("searchSelection").toBool() && curs.hasSelection()) {
int rangeStart = curs.selectionStart();
int rangeEnd = curs.selectionEnd();
curs = doSearch(textEdit->document(), searchText, regex, flags, rangeStart, rangeEnd);
}
else {
if ((flags & QTextDocument::FindBackward) != 0) {
int rangeStart = 0;
int rangeEnd = curs.selectionStart();
curs = doSearch(textEdit->document(), searchText, regex, flags, rangeStart, rangeEnd);
if (curs.isNull() && settings.value("searchWrap").toBool()) {
curs = QTextCursor(textEdit->document());
curs.movePosition(QTextCursor::End);
curs = doSearch(textEdit->document(), searchText, regex, flags, 0, curs.position());
}
}
else {
int rangeStart = curs.selectionEnd();
curs.movePosition(QTextCursor::End);
int rangeEnd = curs.position();
curs = doSearch(textEdit->document(), searchText, regex, flags, rangeStart, rangeEnd);
if (curs.isNull() && settings.value("searchWrap").toBool())
curs = doSearch(textEdit->document(), searchText, regex, flags, 0, rangeEnd);
}
}
if (curs.isNull()) {
qApp->beep();
statusBar()->showMessage(tr("Not found"), kStatusMessageDuration);
}
else
textEdit->setTextCursor(curs);
}
if (regex != NULL)
delete regex;
}
void TeXDocument::doReplaceAgain()
{
doReplace(ReplaceDialog::ReplaceOne);
}
void TeXDocument::doReplace(ReplaceDialog::DialogCode mode)
{
QSETTINGS_OBJECT(settings);
QString searchText = settings.value("searchText").toString();
if (searchText.isEmpty())
return;
QTextDocument::FindFlags flags = (QTextDocument::FindFlags)settings.value("searchFlags").toInt();
QRegExp *regex = NULL;
if (settings.value("searchRegex").toBool()) {
regex = new QRegExp(searchText, ((flags & QTextDocument::FindCaseSensitively) != 0)
? Qt::CaseSensitive : Qt::CaseInsensitive);
if (!regex->isValid()) {
qApp->beep();
statusBar()->showMessage(tr("Invalid regular expression"), kStatusMessageDuration);
delete regex;
return;
}
}
QString replacement = settings.value("replaceText").toString();
if (regex != NULL) {
QRegExp escapedChar("\\\\([nt\\\\]|x([0-9A-Fa-f]{4}))");
int index = -1;
while ((index = replacement.indexOf(escapedChar, index + 1)) >= 0) {
QChar ch;
if (escapedChar.cap(1).length() == 1) {
// single-char escape code newline/tab/backslash
ch = escapedChar.cap(1)[0];
switch (ch.unicode()) {
case 'n':
ch = '\n';
break;
case 't':
ch = '\t';
break;
case '\\':
ch = '\\';
break;
default:
// should not happen!
break;
}
}
else {
// Unicode char number \xHHHH
bool ok;
ch = (QChar)escapedChar.cap(2).toUInt(&ok, 16);
}
replacement.replace(index, escapedChar.matchedLength(), ch);
}
}
bool allFiles = (mode == ReplaceDialog::ReplaceAll) && settings.value("searchAllFiles").toBool();
bool searchWrap = settings.value("searchWrap").toBool();
bool searchSel = settings.value("searchSelection").toBool();
int rangeStart, rangeEnd;
QTextCursor searchRange = textCursor();
if (allFiles) {
searchRange.select(QTextCursor::Document);
rangeStart = searchRange.selectionStart();
rangeEnd = searchRange.selectionEnd();
}
else if (searchSel) {
rangeStart = searchRange.selectionStart();
rangeEnd = searchRange.selectionEnd();
}
else {
if (searchWrap) {
searchRange.select(QTextCursor::Document);
rangeStart = searchRange.selectionStart();
rangeEnd = searchRange.selectionEnd();
}
else {
if ((flags & QTextDocument::FindBackward) != 0) {
rangeStart = 0;
rangeEnd = searchRange.selectionEnd();
}
else {
rangeStart = searchRange.selectionStart();
searchRange.select(QTextCursor::Document);
rangeEnd = searchRange.selectionEnd();
}
}
}
if (mode == ReplaceDialog::ReplaceOne) {
QTextCursor curs = doSearch(textEdit->document(), searchText, regex, flags, rangeStart, rangeEnd);
if (curs.isNull()) {
qApp->beep();
statusBar()->showMessage(tr("Not found"), kStatusMessageDuration);
}
else {
// do replacement
QString target;
if (regex != NULL)
target = textEdit->document()->toPlainText()
.mid(curs.selectionStart(), curs.selectionEnd() - curs.selectionStart()).replace(*regex, replacement);
else
target = replacement;
curs.insertText(target);
textEdit->setTextCursor(curs);
}
}
else if (mode == ReplaceDialog::ReplaceAll) {
if (allFiles) {
int replacements = 0;
foreach (TeXDocument* doc, docList)
replacements += doc->doReplaceAll(searchText, regex, replacement, flags);
QString numOccurrences = tr("%n occurrence(s)", "", replacements);
QString numDocuments = tr("%n documents", "", docList.count());
QString message = tr("Replaced %1 in %2").arg(numOccurrences).arg(numDocuments);
statusBar()->showMessage(message, kStatusMessageDuration);
}
else {
int replacements = doReplaceAll(searchText, regex, replacement, flags, rangeStart, rangeEnd);
statusBar()->showMessage(tr("Replaced %n occurrence(s)", "", replacements), kStatusMessageDuration);
}
}
if (regex != NULL)
delete regex;
}
int TeXDocument::doReplaceAll(const QString& searchText, QRegExp* regex, const QString& replacement,
QTextDocument::FindFlags flags, int rangeStart, int rangeEnd)
{
QTextCursor searchRange = textCursor();
searchRange.select(QTextCursor::Document);
if (rangeStart < 0)
rangeStart = searchRange.selectionStart();
if (rangeEnd < 0)
rangeEnd = searchRange.selectionEnd();
int replacements = 0;
bool first = true;
while (1) {
QTextCursor curs = doSearch(textEdit->document(), searchText, regex, flags, rangeStart, rangeEnd);
if (curs.isNull()) {
if (!first)
searchRange.endEditBlock();
break;
}
if (first) {
searchRange.beginEditBlock();
first = false;
}
QString target;
int oldLen = curs.selectionEnd() - curs.selectionStart();
if (regex != NULL)
target = textEdit->document()->toPlainText().mid(curs.selectionStart(), oldLen).replace(*regex, replacement);
else
target = replacement;
int newLen = target.length();
if ((flags & QTextDocument::FindBackward) != 0)
rangeEnd = curs.selectionStart();
else {
rangeStart = curs.selectionEnd() - oldLen + newLen;
rangeEnd += newLen - oldLen;
}
searchRange.setPosition(curs.selectionStart());
searchRange.setPosition(curs.selectionEnd(), QTextCursor::KeepAnchor);
searchRange.insertText(target);
++replacements;
}
if (!first) {
searchRange.setPosition(rangeStart);
textEdit->setTextCursor(searchRange);
}
return replacements;
}
QTextCursor TeXDocument::doSearch(QTextDocument *theDoc, const QString& searchText, const QRegExp *regex, QTextDocument::FindFlags flags, int s, int e)
{
QTextCursor curs;
const QString& docText = theDoc->toPlainText();
if ((flags & QTextDocument::FindBackward) != 0) {
if (regex != NULL) {
// this doesn't seem to match \n or even \x2029 for newline
// curs = theDoc->find(*regex, e, flags);
int offset = regex->lastIndexIn(docText, e, QRegExp::CaretAtZero);
while (offset >= s && offset + regex->matchedLength() > e)
offset = regex->lastIndexIn(docText, offset - 1, QRegExp::CaretAtZero);
if (offset >= s) {
curs = QTextCursor(theDoc);
curs.setPosition(offset);
curs.setPosition(offset + regex->matchedLength(), QTextCursor::KeepAnchor);
}
}
else {
curs = theDoc->find(searchText, e, flags);
if (!curs.isNull()) {
if (curs.selectionEnd() > e)
curs = theDoc->find(searchText, curs, flags);
if (curs.selectionStart() < s)
curs = QTextCursor();
}
}
}
else {
if (regex != NULL) {
// this doesn't seem to match \n or even \x2029 for newline
// curs = theDoc->find(*regex, s, flags);
int offset = regex->indexIn(docText, s, QRegExp::CaretAtZero);
if (offset >= 0) {
curs = QTextCursor(theDoc);
curs.setPosition(offset);
curs.setPosition(offset + regex->matchedLength(), QTextCursor::KeepAnchor);
}
}
else {
curs = theDoc->find(searchText, s, flags);
}
if (curs.selectionEnd() > e)
curs = QTextCursor();
}
return curs;
}
void TeXDocument::copyToFind()
{
if (textEdit->textCursor().hasSelection()) {
QString searchText = textEdit->textCursor().selectedText();
searchText.replace(QString(0x2029), "\n");
QSETTINGS_OBJECT(settings);
if (settings.value("searchRegex").toBool())
searchText = QRegExp::escape(searchText);
settings.setValue("searchText", searchText);
}
}
void TeXDocument::copyToReplace()
{
if (textEdit->textCursor().hasSelection()) {
QString replaceText = textEdit->textCursor().selectedText();
replaceText.replace(QString(0x2029), "\n");
QSETTINGS_OBJECT(settings);
settings.setValue("replaceText", replaceText);
}
}
void TeXDocument::findSelection()
{
copyToFind();
doFindAgain();
}
void TeXDocument::showSelection()
{
int oldScrollValue = -1;
if (textEdit->verticalScrollBar() != NULL)
oldScrollValue = textEdit->verticalScrollBar()->value();
textEdit->ensureCursorVisible();
maybeCenterSelection(oldScrollValue);
}
void TeXDocument::zoomToLeft(QWidget *otherWindow)
{
QDesktopWidget *desktop = QApplication::desktop();
QRect screenRect = desktop->availableGeometry(otherWindow == NULL ? this : otherWindow);
screenRect.setTop(screenRect.top() + 22);
screenRect.setLeft(screenRect.left() + 1);
screenRect.setBottom(screenRect.bottom() - 1);
screenRect.setRight((screenRect.left() + screenRect.right()) / 2 - 1);
setGeometry(screenRect);
}
void TeXDocument::typeset()
{
if (process)
return; // this shouldn't happen if we disable the command at the right time
if (isUntitled || textEdit->document()->isModified())
if (!save()) {
statusBar()->showMessage(tr("Cannot process unsaved document"), kStatusMessageDuration);
return;
}
findRootFilePath();
if (!saveFilesHavingRoot(rootFilePath))
return;
QFileInfo fileInfo(rootFilePath);
if (!fileInfo.isReadable()) {
statusBar()->showMessage(tr("Root document %1 is not readable").arg(rootFilePath), kStatusMessageDuration);
return;
}
Engine e = TWApp::instance()->getNamedEngine(engine->currentText());
if (e.program() == "") {
statusBar()->showMessage(tr("%1 is not properly configured").arg(engine->currentText()), kStatusMessageDuration);
return;
}
process = new QProcess(this);
updateTypesettingAction();
QString workingDir = fileInfo.canonicalPath(); // Note that fileInfo refers to the root file
#ifdef Q_WS_WIN
// files in the root directory of the current drive have to be handled specially
// because QFileInfo::canonicalPath() returns a path without trailing slash
// (i.e., a bare drive letter)
if (workingDir.length() == 2 && workingDir.endsWith(':'))
workingDir.append('/');
#endif
process->setWorkingDirectory(workingDir);
#ifdef Q_WS_WIN
#define PATH_CASE_SENSITIVE Qt::CaseInsensitive
#else
#define PATH_CASE_SENSITIVE Qt::CaseSensitive
#endif
QStringList binPaths = TWApp::instance()->getBinaryPaths();
QStringList env = QProcess::systemEnvironment();
QMutableStringListIterator envIter(env);
while (envIter.hasNext()) {
QString& envVar = envIter.next();
if (envVar.startsWith("PATH=", PATH_CASE_SENSITIVE)) {
foreach (const QString& s, envVar.mid(5).split(QChar(PATH_LIST_SEP), QString::SkipEmptyParts))
if (!binPaths.contains(s))
binPaths.append(s);
envVar = envVar.left(5) + binPaths.join(QChar(PATH_LIST_SEP));
break;
}
}
bool foundCommand = false;
QFileInfo exeFileInfo;
QStringListIterator pathIter(binPaths);
#ifdef Q_WS_WIN
QStringList executableTypes = QStringList() << "exe" << "com" << "cmd" << "bat";
#endif
while (pathIter.hasNext() && !foundCommand) {
QString path = pathIter.next();
exeFileInfo = QFileInfo(path, e.program());
foundCommand = exeFileInfo.exists() && exeFileInfo.isExecutable();
#ifdef Q_WS_WIN
// try adding common executable extensions, if one was not already present
if (!foundCommand && !executableTypes.contains(exeFileInfo.suffix())) {
QStringListIterator extensions(executableTypes);
while (extensions.hasNext() && !foundCommand) {
exeFileInfo = QFileInfo(path, e.program() + "." + extensions.next());
foundCommand = exeFileInfo.exists() && exeFileInfo.isExecutable();
}
}
#endif
}
if (foundCommand) {
QStringList args = e.arguments();
// for old MikTeX versions: delete $synctexoption if it causes an error
static bool checkedForSynctex = false;
static bool synctexSupported = true;
if (!checkedForSynctex) {
QStringListIterator pi(binPaths);
QFileInfo chkFileInfo;
bool found = false;
while (pi.hasNext() && !found) {
QString path = pi.next();
chkFileInfo = QFileInfo(path, "pdftex" EXE);
if (chkFileInfo.exists())
found = true;
}
if (found) {
int result = QProcess::execute(chkFileInfo.absoluteFilePath(), QStringList() << "-synctex=1" << "-version");
synctexSupported = (result == 0);
}
checkedForSynctex = true;
}
if (!synctexSupported)
args.removeAll("$synctexoption");
args.replaceInStrings("$synctexoption", "-synctex=1");
args.replaceInStrings("$fullname", fileInfo.fileName());
args.replaceInStrings("$basename", fileInfo.completeBaseName());
args.replaceInStrings("$suffix", fileInfo.suffix());
args.replaceInStrings("$directory", fileInfo.absoluteDir().absolutePath());
textEdit_console->clear();
if (consoleTabs->isHidden()) {
consoleWasHidden = true;
showConsole();
}
else {
consoleWasHidden = false;
inputLine->show();
}
inputLine->setFocus(Qt::OtherFocusReason);
showPdfWhenFinished = e.showPdf();
userInterrupt = false;
process->setEnvironment(env);
process->setProcessChannelMode(QProcess::MergedChannels);
connect(process, SIGNAL(readyReadStandardOutput()), this, SLOT(processStandardOutput()));
connect(process, SIGNAL(error(QProcess::ProcessError)), this, SLOT(processError(QProcess::ProcessError)));
connect(process, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(processFinished(int, QProcess::ExitStatus)));
QString pdfName;
if (getPreviewFileName(pdfName))
oldPdfTime = QFileInfo(pdfName).lastModified();
else
oldPdfTime = QDateTime();
process->start(exeFileInfo.absoluteFilePath(), args);
}
else {
process->deleteLater();
process = NULL;
QMessageBox::critical(this, tr("Unable to execute %1").arg(e.name()),
"" + tr("The program \"%1\" was not found.").arg(e.program()) +
"
" + tr("Searched in directories:") +
"- " + binPaths.join("
- ") + "
" +
"
" + tr("Check configuration of the %1 tool and path settings in the Preferences dialog.").arg(e.name()),
QMessageBox::Cancel);
updateTypesettingAction();
}
}
void TeXDocument::interrupt()
{
if (process != NULL) {
userInterrupt = true;
process->kill();
}
}
void TeXDocument::updateTypesettingAction()
{
if (process == NULL) {
disconnect(actionTypeset, SIGNAL(triggered()), this, SLOT(interrupt()));
actionTypeset->setIcon(QIcon(":/images/images/runtool.png"));
connect(actionTypeset, SIGNAL(triggered()), this, SLOT(typeset()));
if (pdfDoc != NULL)
pdfDoc->updateTypesettingAction(false);
}
else {
disconnect(actionTypeset, SIGNAL(triggered()), this, SLOT(typeset()));
actionTypeset->setIcon(QIcon(":/images/tango/process-stop.png"));
connect(actionTypeset, SIGNAL(triggered()), this, SLOT(interrupt()));
if (pdfDoc != NULL)
pdfDoc->updateTypesettingAction(true);
}
}
void TeXDocument::processStandardOutput()
{
QByteArray bytes = process->readAllStandardOutput();
QTextCursor cursor(textEdit_console->document());
cursor.select(QTextCursor::Document);
cursor.setPosition(cursor.selectionEnd());
cursor.insertText(QString::fromUtf8(bytes));
textEdit_console->setTextCursor(cursor);
}
void TeXDocument::processError(QProcess::ProcessError /*error*/)
{
if (userInterrupt)
textEdit_console->append(tr("Process interrupted by user"));
else
textEdit_console->append(process->errorString());
process->kill();
process->deleteLater();
process = NULL;
inputLine->hide();
updateTypesettingAction();
}
void TeXDocument::processFinished(int exitCode, QProcess::ExitStatus exitStatus)
{
if (exitStatus != QProcess::CrashExit) {
QString pdfName;
if (getPreviewFileName(pdfName) && QFileInfo(pdfName).lastModified() != oldPdfTime) {
// only open/refresh the PDF if it was changed by the typeset process
if (pdfDoc == NULL) {
if (showPdfWhenFinished && showPdfIfAvailable())
pdfDoc->selectWindow();
}
else {
pdfDoc->reload(); // always reload if it is loaded, we don't want a stale window
if (showPdfWhenFinished)
pdfDoc->selectWindow();
}
}
}
executeAfterTypesetHooks();
QSETTINGS_OBJECT(settings);
if (consoleWasHidden && exitCode == 0 && exitStatus != QProcess::CrashExit && settings.value("autoHideConsole", true).toBool())
hideConsole();
else
inputLine->hide();
process->deleteLater();
process = NULL;
updateTypesettingAction();
}
void TeXDocument::executeAfterTypesetHooks()
{
for (int i = consoleTabs->count() - 1; i > 0; --i)
consoleTabs->removeTab(i);
foreach (TWScript *s, TWApp::instance()->getScriptManager().getHookScripts("AfterTypeset")) {
QVariant result;
bool success = s->run(this, result);
if (success && !result.isNull()) {
if (result.type() == QVariant::List) {
const QVariantList list = result.toList();
int columns = 1;
QTableWidget *table = new QTableWidget(list.count(), columns, this);
table->horizontalHeader()->setResizeMode(QHeaderView::ResizeToContents);
table->horizontalHeader()->setStretchLastSection(true);
table->horizontalHeader()->hide();
table->verticalHeader()->setResizeMode(QHeaderView::ResizeToContents);
table->setSelectionBehavior(QAbstractItemView::SelectRows);
table->setSelectionMode(QAbstractItemView::SingleSelection);
table->setEditTriggers(QAbstractItemView::NoEditTriggers);
connect(table, SIGNAL(itemDoubleClicked(QTableWidgetItem*)), this, SLOT(errorLineClicked(QTableWidgetItem*)));
for (int i = 0; i < list.count(); ++i) {
const QVariant item = list.at(i);
if (item.type() == QVariant::List) {
const QVariantList rowList = item.toList();
if (rowList.count() > columns) {
columns = rowList.count();
table->setColumnCount(columns);
}
for (int j = 0; j < rowList.count(); ++j) {
table->setItem(i, j, new QTableWidgetItem(rowList.at(j).toString()));
}
}
else {
table->setItem(i, 0, new QTableWidgetItem(item.toString()));
table->setSpan(i, 0, 1, columns);
}
}
consoleTabs->addTab(table, s->getTitle());
}
else {
QTextEdit *textEdit = new QTextEdit(this);
textEdit->setPlainText(result.toString());
textEdit->setReadOnly(true);
consoleTabs->addTab(textEdit, s->getTitle());
}
}
}
}
void TeXDocument::errorLineClicked(QTableWidgetItem * i)
{
QTableWidget * table = i->tableWidget();
int row = i->row();
QString filename = table->item(row, 0)->text();
int line = table->item(row, 1)->text().toInt();
openDocument(QFileInfo(curFile).absoluteDir().filePath(filename), true, true, line);
}
void TeXDocument::showConsole()
{
consoleTabs->show();
if (process != NULL)
inputLine->show();
actionShow_Hide_Console->setText(tr("Hide Output Panel"));
}
void TeXDocument::hideConsole()
{
consoleTabs->hide();
inputLine->hide();
actionShow_Hide_Console->setText(tr("Show Output Panel"));
}
void TeXDocument::toggleConsoleVisibility()
{
if (consoleTabs->isVisible())
hideConsole();
else
showConsole();
}
void TeXDocument::acceptInputLine()
{
if (process != NULL) {
QString str = inputLine->text();
QTextCursor curs(textEdit_console->document());
curs.setPosition(textEdit_console->toPlainText().length());
textEdit_console->setTextCursor(curs);
QTextCharFormat consoleFormat = textEdit_console->currentCharFormat();
QTextCharFormat inputFormat(consoleFormat);
inputFormat.setForeground(inputLine->palette().text());
str.append("\n");
textEdit_console->insertPlainText(str);
curs.movePosition(QTextCursor::PreviousCharacter);
curs.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor, str.length() - 1);
curs.setCharFormat(inputFormat);
process->write(str.toUtf8());
inputLine->clear();
}
}
void TeXDocument::goToPreview()
{
if (pdfDoc != NULL)
pdfDoc->selectWindow();
else {
if (!showPdfIfAvailable()) {
// 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 TeXDocument::syncClick(int lineNo)
{
if (!isUntitled) {
// ensure that there is a pdf to receive our signal
goToPreview();
emit syncFromSource(curFile, lineNo);
}
}
void TeXDocument::contentsChanged(int position, int /*charsRemoved*/, int /*charsAdded*/)
{
if (position < PEEK_LENGTH) {
int pos;
QTextCursor curs(textEdit->document());
curs.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, PEEK_LENGTH);
QString peekStr = curs.selectedText();
/* Search for engine specification */
QRegExp re("% *!TEX +(?:TS-)?program *= *([^\\x2029]+)\\x2029", Qt::CaseInsensitive);
pos = re.indexIn(peekStr);
if (pos > -1) {
QString name = re.cap(1).trimmed();
int index = engine->findText(name, Qt::MatchFixedString);
if (index > -1) {
if (index != engine->currentIndex()) {
engine->setCurrentIndex(index);
statusBar()->showMessage(tr("Set engine to \"%1\"").arg(engine->currentText()), kStatusMessageDuration);
}
else
statusBar()->clearMessage();
}
else {
statusBar()->showMessage(tr("Engine \"%1\" not defined").arg(name), kStatusMessageDuration);
}
}
/* Search for spellcheck specification */
QRegExp reSpell("% *!TEX +spellcheck *= *([^\\x2029]+)\\x2029", Qt::CaseInsensitive);
pos = reSpell.indexIn(peekStr);
if (pos > -1) {
QString lang = reSpell.cap(1).trimmed();
setSpellcheckLanguage(lang);
}
}
}
void TeXDocument::findRootFilePath()
{
if (isUntitled) {
rootFilePath = "";
return;
}
QFileInfo fileInfo(curFile);
QString rootName;
QTextCursor curs(textEdit->document());
curs.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, PEEK_LENGTH);
QString peekStr = curs.selectedText();
QRegExp re("% *!TEX +root *= *([^\\x2029]+)\\x2029", Qt::CaseInsensitive);
int pos = re.indexIn(peekStr);
if (pos > -1) {
rootName = re.cap(1).trimmed();
QFileInfo rootFileInfo(fileInfo.canonicalPath() + "/" + rootName);
if (rootFileInfo.exists())
rootFilePath = rootFileInfo.canonicalFilePath();
else
rootFilePath = rootFileInfo.filePath();
}
else
rootFilePath = fileInfo.canonicalFilePath();
}
void TeXDocument::addTag(const QTextCursor& cursor, int level, const QString& text)
{
int index = 0;
while (index < tags.size()) {
if (tags[index].cursor.selectionStart() > cursor.selectionStart())
break;
++index;
}
tags.insert(index, Tag(cursor, level, text));
}
int TeXDocument::removeTags(int offset, int len)
{
int removed = 0;
for (int index = tags.count() - 1; index >= 0; --index) {
if (tags[index].cursor.selectionStart() < offset)
break;
if (tags[index].cursor.selectionStart() < offset + len) {
tags.removeAt(index);
++removed;
}
}
return removed;
}
void TeXDocument::goToTag(int index)
{
if (index < tags.count()) {
textEdit->setTextCursor(tags[index].cursor);
textEdit->setFocus(Qt::OtherFocusReason);
}
}
void TeXDocument::tagsChanged()
{
if (deferTagListChanges)
tagListChanged = true;
else
emit tagListUpdated();
}
void TeXDocument::removeAuxFiles()
{
findRootFilePath();
if (rootFilePath.isEmpty())
return;
QFileInfo fileInfo(rootFilePath);
QString jobname = fileInfo.completeBaseName();
QDir dir(fileInfo.dir());
QStringList filterList = TWUtils::cleanupPatterns().split(QRegExp("\\s+"));
if (filterList.count() == 0)
return;
for (int i = 0; i < filterList.count(); ++i)
filterList[i].replace("$jobname", jobname);
dir.setNameFilters(filterList);
QStringList auxFileList = dir.entryList(QDir::Files | QDir::CaseSensitive, QDir::Name);
if (auxFileList.count() > 0)
ConfirmDelete::doConfirmDelete(dir, auxFileList);
else
(void)QMessageBox::information(this, tr("No files found"),
tr("No auxiliary files associated with this document at the moment."));
}
#ifdef Q_WS_MAC
#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 TeXDocument::dragEnterEvent(QDragEnterEvent *event)
{
event->ignore();
if (event->mimeData()->hasUrls()) {
const QList urls = event->mimeData()->urls();
foreach (const QUrl& url, urls) {
if (url.scheme() == "file") {
event->acceptProposedAction();
break;
}
}
}
}
void TeXDocument::dragMoveEvent(QDragMoveEvent *event)
{
if (event->proposedAction() == INSERT_DOCUMENT_TEXT || event->proposedAction() == CREATE_INCLUDE_COMMAND) {
if (dragSavedCursor.isNull())
dragSavedCursor = textEdit->textCursor();
QTextCursor curs = textEdit->cursorForPosition(textEdit->mapFromGlobal(mapToGlobal(event->pos())));
textEdit->setTextCursor(curs);
}
else {
if (!dragSavedCursor.isNull()) {
textEdit->setTextCursor(dragSavedCursor);
dragSavedCursor = QTextCursor();
}
}
event->acceptProposedAction();
}
void TeXDocument::dragLeaveEvent(QDragLeaveEvent *event)
{
if (!dragSavedCursor.isNull()) {
textEdit->setTextCursor(dragSavedCursor);
dragSavedCursor = QTextCursor();
}
event->accept();
}
void TeXDocument::dropEvent(QDropEvent *event)
{
if (event->mimeData()->hasUrls()) {
Qt::DropAction action = event->proposedAction();
const QList urls = event->mimeData()->urls();
bool editBlockStarted = false;
QString text;
QTextCursor curs = textEdit->cursorForPosition(textEdit->mapFromGlobal(mapToGlobal(event->pos())));
foreach (const QUrl& url, urls) {
if (url.scheme() == "file") {
QString fileName = url.toLocalFile();
switch (action) {
case OPEN_FILE_IN_NEW_WINDOW:
TWApp::instance()->openFile(fileName);
break;
case INSERT_DOCUMENT_TEXT:
if (!TWUtils::isPDFfile(fileName) && !TWUtils::isImageFile(fileName) && !TWUtils::isPostscriptFile(fileName)) {
QTextCodec *codecUsed;
text = readFile(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
case CREATE_INCLUDE_COMMAND:
if (!editBlockStarted) {
curs.beginEditBlock();
editBlockStarted = true;
}
textEdit->setTextCursor(curs);
if (TWUtils::isPDFfile(fileName))
text = TWUtils::includePdfCommand();
else if (TWUtils::isImageFile(fileName))
text = TWUtils::includeImageCommand();
else if (TWUtils::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 TeXDocument::detachPdf()
{
if (pdfDoc != NULL) {
disconnect(pdfDoc, SIGNAL(destroyed()), this, SLOT(pdfClosed()));
disconnect(this, SIGNAL(destroyed(QObject*)), pdfDoc, SLOT(texClosed(QObject*)));
pdfDoc = NULL;
}
}