1 /*
2 	This is part of TeXworks, an environment for working with TeX documents
3 	Copyright (C) 2007-2016  Jonathan Kew, Stefan Löffler, Charlie Sharpsteen
4 
5 	This program is free software; you can redistribute it and/or modify
6 	it under the terms of the GNU General Public License as published by
7 	the Free Software Foundation; either version 2 of the License, or
8 	(at your option) any later version.
9 
10 	This program is distributed in the hope that it will be useful,
11 	but WITHOUT ANY WARRANTY; without even the implied warranty of
12 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 	GNU General Public License for more details.
14 
15 	You should have received a copy of the GNU General Public License
16 	along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 
18 	For links to further information, or to contact the authors,
19 	see <http://www.tug.org/texworks/>.
20 */
21 
22 #include "TeXDocument.h"
23 #include "TeXHighlighter.h"
24 #include "TeXDocks.h"
25 #include "FindDialog.h"
26 #include "TemplateDialog.h"
27 #include "TWApp.h"
28 #include "TWUtils.h"
29 #include "PDFDocument.h"
30 #include "ConfirmDelete.h"
31 #include "HardWrapDialog.h"
32 #include "DefaultPrefs.h"
33 
34 #include <QCloseEvent>
35 #include <QFileDialog>
36 #include <QMessageBox>
37 #include <QTextStream>
38 #include <QStatusBar>
39 #include <QFontDialog>
40 #include <QInputDialog>
41 #include <QDesktopWidget>
42 #include <QClipboard>
43 #include <QStringList>
44 #include <QUrl>
45 #include <QComboBox>
46 #include <QRegExp>
47 #include <QProcess>
48 #include <QAbstractItemView>
49 #include <QScrollBar>
50 #include <QActionGroup>
51 #include <QTextCodec>
52 #include <QSignalMapper>
53 #include <QDockWidget>
54 #include <QAbstractButton>
55 #include <QPushButton>
56 #include <QFileSystemWatcher>
57 #include <QTextBrowser>
58 #include <QAbstractTextDocumentLayout>
59 
60 #if defined(Q_OS_WIN)
61 #include <windows.h>
62 #endif
63 
64 #define kLineEnd_Mask   0x00FF
65 #define kLineEnd_LF     0x0000
66 #define kLineEnd_CRLF   0x0001
67 #define kLineEnd_CR     0x0002
68 
69 #define kLineEnd_Flags_Mask  0xFF00
70 #define kLineEnd_Mixed       0x0100
71 
72 const int kHardWrapDefaultWidth = 64;
73 
74 QList<TeXDocument*> TeXDocument::docList;
75 
TeXDocument()76 TeXDocument::TeXDocument()
77 {
78 	init();
79 	statusBar()->showMessage(tr("New document"), kStatusMessageDuration);
80 }
81 
TeXDocument(const QString & fileName,bool asTemplate)82 TeXDocument::TeXDocument(const QString &fileName, bool asTemplate)
83 {
84 	init();
85 	loadFile(fileName, asTemplate);
86 }
87 
~TeXDocument()88 TeXDocument::~TeXDocument()
89 {
90 	docList.removeAll(this);
91 	updateWindowMenu();
92 }
93 
dictActionLessThan(const QAction * a1,const QAction * a2)94 static bool dictActionLessThan(const QAction * a1, const QAction * a2) {
95 	return a1->text().toLower() < a2->text().toLower();
96 }
97 
init()98 void TeXDocument::init()
99 {
100 	codec = TWApp::instance()->getDefaultCodec();
101 	pdfDoc = NULL;
102 	process = NULL;
103 	highlighter = NULL;
104 	pHunspell = NULL;
105 	utf8BOM = false;
106 #if defined(Q_OS_WIN)
107 	lineEndings = kLineEnd_CRLF;
108 #else
109 	lineEndings = kLineEnd_LF;
110 #endif
111 
112 	setupUi(this);
113 #if defined(Q_OS_WIN)
114 	TWApp::instance()->createMessageTarget(this);
115 #endif
116 
117 	setAttribute(Qt::WA_DeleteOnClose, true);
118 	setAttribute(Qt::WA_MacNoClickThrough, true);
119 
120 	setContextMenuPolicy(Qt::NoContextMenu);
121 
122 	makeUntitled();
123 	hideConsole();
124 	keepConsoleOpen = false;
125 	connect(consoleTabs, SIGNAL(requestClose()), actionShow_Hide_Console, SLOT(trigger()));
126 
127 	statusBar()->addPermanentWidget(lineEndingLabel = new ClickableLabel());
128 	lineEndingLabel->setFrameStyle(QFrame::StyledPanel);
129 	lineEndingLabel->setFont(statusBar()->font());
130 	connect(lineEndingLabel, SIGNAL(mouseLeftClick(QMouseEvent*)), this, SLOT(lineEndingLabelClick(QMouseEvent*)));
131 	showLineEndingSetting();
132 
133 	statusBar()->addPermanentWidget(encodingLabel = new ClickableLabel());
134 	encodingLabel->setFrameStyle(QFrame::StyledPanel);
135 	encodingLabel->setFont(statusBar()->font());
136 	connect(encodingLabel, SIGNAL(mouseLeftClick(QMouseEvent*)), this, SLOT(encodingLabelClick(QMouseEvent*)));
137 	showEncodingSetting();
138 
139 	statusBar()->addPermanentWidget(lineNumberLabel = new ClickableLabel());
140 	lineNumberLabel->setFrameStyle(QFrame::StyledPanel);
141 	lineNumberLabel->setFont(statusBar()->font());
142 	connect(lineNumberLabel, SIGNAL(mouseLeftClick(QMouseEvent*)), this, SLOT(doLineDialog()));
143 	showCursorPosition();
144 
145 	engineActions = new QActionGroup(this);
146 	connect(engineActions, SIGNAL(triggered(QAction*)), this, SLOT(selectedEngine(QAction*)));
147 
148 	codec = TWApp::instance()->getDefaultCodec();
149 	engineName = TWApp::instance()->getDefaultEngine().name();
150 	engine = new QComboBox(this);
151 	engine->setEditable(false);
152 	engine->setFocusPolicy(Qt::NoFocus);
153 	engine->setSizeAdjustPolicy(QComboBox::AdjustToContents);
154 #if defined(Q_OS_DARWIN) && (QT_VERSION >= 0x040600)
155 	engine->setStyleSheet("padding:4px;");
156 	engine->setMinimumWidth(150);
157 #endif
158 	toolBar_run->addWidget(engine);
159 	updateEngineList();
160 	connect(engine, SIGNAL(currentIndexChanged(const QString&)), this, SLOT(selectedEngine(const QString&)));
161 
162 	connect(TWApp::instance(), SIGNAL(engineListChanged()), this, SLOT(updateEngineList()));
163 
164 	connect(actionNew, SIGNAL(triggered()), this, SLOT(newFile()));
165 	connect(actionNew_from_Template, SIGNAL(triggered()), this, SLOT(newFromTemplate()));
166 	connect(actionOpen, SIGNAL(triggered()), this, SLOT(open()));
167 	connect(actionAbout_TW, SIGNAL(triggered()), qApp, SLOT(about()));
168 	connect(actionSettings_and_Resources, SIGNAL(triggered()), qApp, SLOT(doResourcesDialog()));
169 	connect(actionGoToHomePage, SIGNAL(triggered()), qApp, SLOT(goToHomePage()));
170 	connect(actionWriteToMailingList, SIGNAL(triggered()), qApp, SLOT(writeToMailingList()));
171 
172 	connect(actionSave, SIGNAL(triggered()), this, SLOT(save()));
173 	connect(actionSave_As, SIGNAL(triggered()), this, SLOT(saveAs()));
174 	connect(actionSave_All, SIGNAL(triggered()), this, SLOT(saveAll()));
175 	connect(actionRevert_to_Saved, SIGNAL(triggered()), this, SLOT(revert()));
176 	connect(actionClose, SIGNAL(triggered()), this, SLOT(close()));
177 
178 	connect(actionRemove_Aux_Files, SIGNAL(triggered()), this, SLOT(removeAuxFiles()));
179 
180 	connect(actionQuit_TeXworks, SIGNAL(triggered()), TWApp::instance(), SLOT(maybeQuit()));
181 
182 	connect(actionClear, SIGNAL(triggered()), this, SLOT(clear()));
183 
184 	connect(actionFont, SIGNAL(triggered()), this, SLOT(doFontDialog()));
185 	connect(actionGo_to_Line, SIGNAL(triggered()), this, SLOT(doLineDialog()));
186 	connect(actionFind, SIGNAL(triggered()), this, SLOT(doFindDialog()));
187 	connect(actionFind_Again, SIGNAL(triggered()), this, SLOT(doFindAgain()));
188 	connect(actionReplace, SIGNAL(triggered()), this, SLOT(doReplaceDialog()));
189 	connect(actionReplace_Again, SIGNAL(triggered()), this, SLOT(doReplaceAgain()));
190 
191 	connect(actionCopy_to_Find, SIGNAL(triggered()), this, SLOT(copyToFind()));
192 	connect(actionCopy_to_Replace, SIGNAL(triggered()), this, SLOT(copyToReplace()));
193 	connect(actionFind_Selection, SIGNAL(triggered()), this, SLOT(findSelection()));
194 
195 	connect(actionShow_Selection, SIGNAL(triggered()), this, SLOT(showSelection()));
196 
197 	connect(actionIndent, SIGNAL(triggered()), this, SLOT(doIndent()));
198 	connect(actionUnindent, SIGNAL(triggered()), this, SLOT(doUnindent()));
199 
200 	connect(actionComment, SIGNAL(triggered()), this, SLOT(doComment()));
201 	connect(actionUncomment, SIGNAL(triggered()), this, SLOT(doUncomment()));
202 
203 	connect(actionHard_Wrap, SIGNAL(triggered()), this, SLOT(doHardWrapDialog()));
204 
205 	connect(actionTo_Uppercase, SIGNAL(triggered()), this, SLOT(toUppercase()));
206 	connect(actionTo_Lowercase, SIGNAL(triggered()), this, SLOT(toLowercase()));
207 	connect(actionToggle_Case, SIGNAL(triggered()), this, SLOT(toggleCase()));
208 
209 	connect(actionBalance_Delimiters, SIGNAL(triggered()), this, SLOT(balanceDelimiters()));
210 
211 	connect(textEdit->document(), SIGNAL(modificationChanged(bool)), this, SLOT(setWindowModified(bool)));
212 	connect(textEdit->document(), SIGNAL(modificationChanged(bool)), this, SLOT(maybeEnableSaveAndRevert(bool)));
213 	connect(textEdit->document(), SIGNAL(contentsChange(int,int,int)), this, SLOT(contentsChanged(int,int,int)));
214 	connect(textEdit, SIGNAL(cursorPositionChanged()), this, SLOT(showCursorPosition()));
215 	connect(textEdit, SIGNAL(selectionChanged()), this, SLOT(showCursorPosition()));
216 	connect(textEdit, SIGNAL(syncClick(int, int)), this, SLOT(syncClick(int, int)));
217 	connect(this, SIGNAL(syncFromSource(const QString&, int, int, bool)), qApp, SIGNAL(syncPdf(const QString&, int, int, bool)));
218 
219 	connect(QApplication::clipboard(), SIGNAL(dataChanged()), this, SLOT(clipboardChanged()));
220 	clipboardChanged();
221 
222 	connect(actionTypeset, SIGNAL(triggered()), this, SLOT(typeset()));
223 
224 	updateRecentFileActions();
225 	connect(qApp, SIGNAL(recentFileActionsChanged()), this, SLOT(updateRecentFileActions()));
226 	connect(qApp, SIGNAL(windowListChanged()), this, SLOT(updateWindowMenu()));
227 	connect(actionClear_Recent_Files, SIGNAL(triggered()), TWApp::instance(), SLOT(clearRecentFiles()));
228 
229 	connect(qApp, SIGNAL(hideFloatersExcept(QWidget*)), this, SLOT(hideFloatersUnlessThis(QWidget*)));
230 	connect(this, SIGNAL(activatedWindow(QWidget*)), qApp, SLOT(activatedWindow(QWidget*)));
231 
232 	connect(actionStack, SIGNAL(triggered()), qApp, SLOT(stackWindows()));
233 	connect(actionTile, SIGNAL(triggered()), qApp, SLOT(tileWindows()));
234 	connect(actionSide_by_Side, SIGNAL(triggered()), this, SLOT(sideBySide()));
235 	connect(actionPlace_on_Left, SIGNAL(triggered()), this, SLOT(placeOnLeft()));
236 	connect(actionPlace_on_Right, SIGNAL(triggered()), this, SLOT(placeOnRight()));
237 	connect(actionShow_Hide_Console, SIGNAL(triggered()), this, SLOT(toggleConsoleVisibility()));
238 	connect(actionGo_to_Preview, SIGNAL(triggered()), this, SLOT(goToPreview()));
239 
240 	connect(this, SIGNAL(destroyed()), qApp, SLOT(updateWindowMenus()));
241 
242 	connect(actionPreferences, SIGNAL(triggered()), qApp, SLOT(preferences()));
243 
244 	connect(menuEdit, SIGNAL(aboutToShow()), this, SLOT(editMenuAboutToShow()));
245 
246 #if defined(Q_OS_DARWIN)
247 	textEdit->installEventFilter(CmdKeyFilter::filter());
248 #endif
249 
250 	connect(inputLine, SIGNAL(returnPressed()), this, SLOT(acceptInputLine()));
251 
252 	QSETTINGS_OBJECT(settings);
253 	TWUtils::applyToolbarOptions(this, settings.value("toolBarIconSize", 2).toInt(), settings.value("toolBarShowText", false).toBool());
254 
255 	QFont font = textEdit->font();
256 	if (settings.contains("font")) {
257 		QString fontString = settings.value("font").toString();
258 		if (fontString != "") {
259 			font.fromString(fontString);
260 			textEdit->setFont(font);
261 		}
262 	}
263 	font.setPointSize(font.pointSize() - 1);
264 	inputLine->setFont(font);
265 	inputLine->setLayoutDirection(Qt::LeftToRight);
266 	textEdit_console->setFont(font);
267 	textEdit_console->setLayoutDirection(Qt::LeftToRight);
268 
269 	bool b = settings.value("wrapLines", true).toBool();
270 	actionWrap_Lines->setChecked(b);
271 	setWrapLines(b);
272 
273 	b = settings.value("lineNumbers", false).toBool();
274 	actionLine_Numbers->setChecked(b);
275 	setLineNumbers(b);
276 
277 	QStringList options = TeXHighlighter::syntaxOptions();
278 
279 	QSignalMapper *syntaxMapper = new QSignalMapper(this);
280 	connect(syntaxMapper, SIGNAL(mapped(int)), this, SLOT(setSyntaxColoring(int)));
281 	syntaxMapper->setMapping(actionSyntaxColoring_None, -1);
282 	connect(actionSyntaxColoring_None, SIGNAL(triggered()), syntaxMapper, SLOT(map()));
283 
284 	QActionGroup *syntaxGroup = new QActionGroup(this);
285 	syntaxGroup->addAction(actionSyntaxColoring_None);
286 
287 	int index = 0;
288 	foreach (const QString& opt, options) {
289 		QAction *action = menuSyntax_Coloring->addAction(opt, syntaxMapper, SLOT(map()));
290 		action->setCheckable(true);
291 		syntaxGroup->addAction(action);
292 		syntaxMapper->setMapping(action, index);
293 		++index;
294 	}
295 
296 	// kDefault_TabWidth is defined in DefaultPrefs.h
297 	textEdit->setTabStopWidth(settings.value("tabWidth", kDefault_TabWidth).toInt());
298 
299 	// It is VITAL that this connection is queued! Calling showMessage directly
300 	// from TeXDocument::contentsChanged would otherwise result in a seg fault
301 	// (for whatever reason)
302 	connect(this, SIGNAL(asyncFlashStatusBarMessage(QString, int)), statusBar(), SLOT(showMessage(QString, int)), Qt::QueuedConnection);
303 
304 	QString indentOption = settings.value("autoIndent").toString();
305 	options = CompletingEdit::autoIndentModes();
306 
307 	QSignalMapper *indentMapper = new QSignalMapper(this);
308 	connect(indentMapper, SIGNAL(mapped(int)), textEdit, SLOT(setAutoIndentMode(int)));
309 	indentMapper->setMapping(actionAutoIndent_None, -1);
310 	connect(actionAutoIndent_None, SIGNAL(triggered()), indentMapper, SLOT(map()));
311 
312 	QActionGroup *indentGroup = new QActionGroup(this);
313 	indentGroup->addAction(actionAutoIndent_None);
314 
315 	index = 0;
316 	foreach (const QString& opt, options) {
317 		QAction *action = menuAuto_indent_Mode->addAction(opt, indentMapper, SLOT(map()));
318 		action->setCheckable(true);
319 		indentGroup->addAction(action);
320 		indentMapper->setMapping(action, index);
321 		if (opt == indentOption) {
322 			action->setChecked(true);
323 			textEdit->setAutoIndentMode(index);
324 		}
325 		++index;
326 	}
327 
328 	QString quotesOption = settings.value("smartQuotes").toString();
329 	options = CompletingEdit::smartQuotesModes();
330 
331 	QSignalMapper *quotesMapper = new QSignalMapper(this);
332 	connect(quotesMapper, SIGNAL(mapped(int)), textEdit, SLOT(setSmartQuotesMode(int)));
333 	quotesMapper->setMapping(actionSmartQuotes_None, -1);
334 	connect(actionSmartQuotes_None, SIGNAL(triggered()), quotesMapper, SLOT(map()));
335 
336 	QActionGroup *quotesGroup = new QActionGroup(this);
337 	quotesGroup->addAction(actionSmartQuotes_None);
338 
339 	menuSmart_Quotes_Mode->removeAction(actionApply_to_Selection);
340 	index = 0;
341 	foreach (const QString& opt, options) {
342 		QAction *action = menuSmart_Quotes_Mode->addAction(opt, quotesMapper, SLOT(map()));
343 		action->setCheckable(true);
344 		quotesGroup->addAction(action);
345 		quotesMapper->setMapping(action, index);
346 		if (opt == quotesOption) {
347 			action->setChecked(true);
348 			textEdit->setSmartQuotesMode(index);
349 		}
350 		++index;
351 	}
352 	if (options.size() > 0)
353 		menuSmart_Quotes_Mode->addSeparator();
354 	menuSmart_Quotes_Mode->addAction(actionApply_to_Selection);
355 	connect(actionApply_to_Selection, SIGNAL(triggered()), textEdit, SLOT(smartenQuotes()));
356 
357 	connect(actionLine_Numbers, SIGNAL(triggered(bool)), this, SLOT(setLineNumbers(bool)));
358 	connect(actionWrap_Lines, SIGNAL(triggered(bool)), this, SLOT(setWrapLines(bool)));
359 
360 	connect(actionNone, SIGNAL(triggered()), &dictSignalMapper, SLOT(map()));
361 	dictSignalMapper.setMapping(actionNone, QString());
362 	connect(&dictSignalMapper, SIGNAL(mapped(const QString&)), this, SLOT(setLangInternal(const QString&)));
363 
364 	QActionGroup *group = new QActionGroup(this);
365 	group->addAction(actionNone);
366 
367 	reloadSpellcheckerMenu();
368 	connect(TWApp::instance(), SIGNAL(dictionaryListChanged()), this, SLOT(reloadSpellcheckerMenu()));
369 
370 	menuShow->addAction(toolBar_run->toggleViewAction());
371 	menuShow->addAction(toolBar_edit->toggleViewAction());
372 	menuShow->addSeparator();
373 
374 	TWUtils::zoomToHalfScreen(this);
375 
376 	QDockWidget *dw = new TagsDock(this);
377 	dw->hide();
378 	addDockWidget(Qt::LeftDockWidgetArea, dw);
379 	menuShow->addAction(dw->toggleViewAction());
380 	deferTagListChanges = false;
381 
382 	watcher = new QFileSystemWatcher(this);
383 	connect(watcher, SIGNAL(fileChanged(const QString&)), this, SLOT(reloadIfChangedOnDisk()), Qt::QueuedConnection);
384 	connect(watcher, SIGNAL(directoryChanged(const QString&)), this, SLOT(reloadIfChangedOnDisk()), Qt::QueuedConnection);
385 
386 	docList.append(this);
387 
388 	TWApp::instance()->updateWindowMenus();
389 
390 	initScriptable(menuScripts, actionAbout_Scripts, actionManage_Scripts,
391 				   actionUpdate_Scripts, actionShow_Scripts_Folder);
392 
393 	TWUtils::insertHelpMenuItems(menuHelp);
394 	TWUtils::installCustomShortcuts(this);
395 #if QT_VERSION < 0x050000
396 	QTimer::singleShot(1000, this, SLOT(delayedInit()));
397 #else
398 	delayedInit();
399 #endif
400 }
401 
changeEvent(QEvent * event)402 void TeXDocument::changeEvent(QEvent *event)
403 {
404 	if (event->type() == QEvent::LanguageChange) {
405 		QString title = windowTitle();
406 		retranslateUi(this);
407 		TWUtils::insertHelpMenuItems(menuHelp);
408 		setWindowTitle(title);
409 		showCursorPosition();
410 	}
411 	else if (event->type() == QEvent::ActivationChange) {
412 		// If this window was activated, inform the linked pdf (if any) of it
413 		// so that future "Goto Source" actions point here.
414 		if (this == QApplication::activeWindow() && pdfDoc)
415 			pdfDoc->texActivated(this);
416 	}
417 	QMainWindow::changeEvent(event);
418 }
419 
setLangInternal(const QString & lang)420 void TeXDocument::setLangInternal(const QString& lang)
421 {
422 	// called internally by the spelling menu actions;
423 	// not for use from scripts as it won't update the menu
424 	QTextCodec *spellingCodec;
425 	Hunhandle* pOldHunspell = pHunspell;
426 	pHunspell = TWUtils::getDictionary(lang);
427 	// if the dictionary hasn't change, don't reset the spell checker as that
428 	// can result in a serious delay for long documents
429 	// NB: Don't delete the hunspell handles; the pointers are kept by TWUtils
430 	if (pOldHunspell == pHunspell)
431 		return;
432 
433 	if (pHunspell != NULL) {
434 		spellingCodec = QTextCodec::codecForName(Hunspell_get_dic_encoding(pHunspell));
435 		if (spellingCodec == NULL)
436 			spellingCodec = QTextCodec::codecForLocale(); // almost certainly wrong, if we couldn't find the actual name!
437 	}
438 	else
439 		spellingCodec = NULL;
440 	textEdit->setSpellChecker(pHunspell, spellingCodec);
441 	if (highlighter)
442 		highlighter->setSpellChecker(pHunspell, spellingCodec);
443 }
444 
setSpellcheckLanguage(const QString & lang)445 void TeXDocument::setSpellcheckLanguage(const QString& lang)
446 {
447 	// this is called by the %!TEX spellcheck... line, or by scripts;
448 	// it searches the menu for the given language code, and triggers it if available
449 
450 	// Determine all aliases for the specified lang
451 	QList<QString> langAliases;
452 	foreach (const QString& dictKey, TWUtils::getDictionaryList()->uniqueKeys()) {
453 		if(TWUtils::getDictionaryList()->values(dictKey).contains(lang))
454 			langAliases += TWUtils::getDictionaryList()->values(dictKey);
455 	}
456 	langAliases.removeAll(lang);
457 	langAliases.prepend(lang);
458 
459 	bool found = false;
460 	if (menuSpelling) {
461 		QAction *chosen = menuSpelling->actions()[0]; // default is None
462 		foreach (QAction *act, menuSpelling->actions()) {
463 			foreach(QString alias, langAliases) {
464 				if (act->text() == alias || act->text().contains("(" + alias + ")")) {
465 					chosen = act;
466 					found = true;
467 					break;
468 				}
469 			}
470 			if(found) break;
471 		}
472 		chosen->trigger();
473 	}
474 }
475 
spellcheckLanguage() const476 QString TeXDocument::spellcheckLanguage() const
477 {
478 	return TWUtils::getLanguageForDictionary(pHunspell);
479 }
480 
reloadSpellcheckerMenu()481 void TeXDocument::reloadSpellcheckerMenu()
482 {
483 	Q_ASSERT(menuSpelling != NULL);
484 	Q_ASSERT(menuSpelling->actions().size() > 0);
485 
486 	QActionGroup * group = menuSpelling->actions()[0]->actionGroup();
487 	Q_ASSERT(group != NULL);
488 
489 	// Remove all but the first menu item ("None") from the action group
490 	int i = 0;
491 	QString oldSelected;
492 	foreach (QAction * act, group->actions()) {
493 		if (act->isChecked())
494 			oldSelected = act->text();
495 		if (i > 0) {
496 			group->removeAction(act);
497 			act->deleteLater();
498 		}
499 		++i;
500 	}
501 
502 	QList<QAction*> dictActions;
503 	foreach (const QString& dictKey, TWUtils::getDictionaryList()->uniqueKeys()) {
504 		QAction *act;
505 		QString dict, label;
506 		QLocale loc;
507 
508 		foreach (dict, TWUtils::getDictionaryList()->values(dictKey)) {
509 			loc = QLocale(dict);
510 			if (loc.language() != QLocale::C) break;
511 		}
512 
513 		if (loc.language() == QLocale::C)
514 			label = dict;
515 		else {
516 			label = QLocale::languageToString(loc.language());
517 			QLocale::Country country = loc.country();
518 			if (country != QLocale::AnyCountry)
519 				label += " - " + QLocale::countryToString(country);
520 			label += " (" + dict + ")";
521 		}
522 
523 		act = new QAction(label, NULL);
524 		act->setCheckable(true);
525 		if (!oldSelected.isEmpty() && label == oldSelected)
526 			act->setChecked(true);
527 		connect(act, SIGNAL(triggered()), &dictSignalMapper, SLOT(map()));
528 		dictSignalMapper.setMapping(act, dict);
529 		group->addAction(act);
530 		dictActions << act;
531 	}
532 	qSort(dictActions.begin(), dictActions.end(), dictActionLessThan);
533 	foreach (QAction* dictAction, dictActions)
534 		menuSpelling->addAction(dictAction);
535 }
536 
clipboardChanged()537 void TeXDocument::clipboardChanged()
538 {
539 	actionPaste->setEnabled(textEdit->canPaste());
540 }
541 
editMenuAboutToShow()542 void TeXDocument::editMenuAboutToShow()
543 {
544 //	undoAction->setText(tr("Undo ") + undoStack->undoText());
545 //	redoAction->setText(tr("Redo ") + undoStack->redoText());
546 	actionSelect_All->setEnabled(!textEdit->document()->isEmpty());
547 }
548 
newFile()549 void TeXDocument::newFile()
550 {
551 	TeXDocument *doc = new TeXDocument;
552 	doc->selectWindow();
553 	doc->textEdit->updateLineNumberAreaWidth(0);
554 	doc->runHooks("NewFile");
555 }
556 
newFromTemplate()557 void TeXDocument::newFromTemplate()
558 {
559 	QString templateName = TemplateDialog::doTemplateDialog();
560 	if (!templateName.isEmpty()) {
561 		TeXDocument *doc = NULL;
562 		if (isUntitled && textEdit->document()->isEmpty() && !isWindowModified()) {
563 			loadFile(templateName, true);
564 			doc = this;
565 		}
566 		else {
567 			doc = new TeXDocument(templateName, true);
568 		}
569 		if (doc != NULL) {
570 			doc->makeUntitled();
571 			doc->selectWindow();
572 			doc->textEdit->updateLineNumberAreaWidth(0);
573 			doc->runHooks("NewFromTemplate");
574 		}
575 	}
576 }
577 
makeUntitled()578 void TeXDocument::makeUntitled()
579 {
580 	setCurrentFile("");
581 	actionRemove_Aux_Files->setEnabled(false);
582 }
583 
open()584 void TeXDocument::open()
585 {
586 	QFileDialog::Options options = 0;
587 #if defined(Q_OS_DARWIN)
588 		/* use a sheet if we're calling Open from an empty, untitled, untouched window; otherwise use a separate dialog */
589 	if (!(isUntitled && textEdit->document()->isEmpty() && !isWindowModified()))
590 		options = QFileDialog::DontUseSheet;
591 #elif defined(Q_OS_WIN)
592 	if(TWApp::GetWindowsVersion() < 0x06000000) options |= QFileDialog::DontUseNativeDialog;
593 #endif
594 	QSETTINGS_OBJECT(settings);
595 	QString lastOpenDir = settings.value("openDialogDir").toString();
596 	if (lastOpenDir.isEmpty())
597 		lastOpenDir = QDir::homePath();
598 	QStringList files = QFileDialog::getOpenFileNames(this, QString(tr("Open File")), lastOpenDir, TWUtils::filterList()->join(";;"), NULL, options);
599 	foreach (QString fileName, files) {
600 		if (!fileName.isEmpty()) {
601 			TWApp::instance()->openFile(fileName); // not TeXDocument::open() - give the app a chance to open as PDF
602 		}
603 	}
604 }
605 
open(const QString & fileName)606 TeXDocument* TeXDocument::open(const QString &fileName)
607 {
608 	TeXDocument *doc = NULL;
609 	if (!fileName.isEmpty()) {
610 		doc = findDocument(fileName);
611 		if (doc == NULL) {
612 			if (isUntitled && textEdit->document()->isEmpty() && !isWindowModified()) {
613 				loadFile(fileName);
614 				doc = this;
615 			}
616 			else {
617 				doc = new TeXDocument(fileName);
618 				if (doc->isUntitled) {
619 					delete doc;
620 					doc = NULL;
621 				}
622 			}
623 		}
624 	}
625 	if (doc != NULL)
626 		doc->selectWindow();
627 	return doc;
628 }
629 
openDocument(const QString & fileName,bool activate,bool raiseWindow,int lineNo,int selStart,int selEnd)630 TeXDocument* TeXDocument::openDocument(const QString &fileName, bool activate, bool raiseWindow, int lineNo, int selStart, int selEnd) // static
631 {
632 	TeXDocument *doc = findDocument(fileName);
633 	if (doc == NULL) {
634 		if (docList.count() == 1) {
635 			doc = docList[0];
636 			doc = doc->open(fileName); // open into existing window if untitled/empty
637 		}
638 		else {
639 			doc = new TeXDocument(fileName);
640 			if (doc->isUntitled) {
641 				delete doc;
642 				doc = NULL;
643 			}
644 		}
645 	}
646 	if (doc != NULL) {
647 		if (activate)
648 			doc->selectWindow();
649 		else {
650 			doc->show();
651 			if (raiseWindow) {
652 				doc->raise();
653 				if (doc->isMinimized())
654 					doc->showNormal();
655 			}
656 		}
657 		if (lineNo > 0)
658 			doc->goToLine(lineNo, selStart, selEnd);
659 	}
660 	return doc;
661 }
662 
closeEvent(QCloseEvent * event)663 void TeXDocument::closeEvent(QCloseEvent *event)
664 {
665 	if (process != NULL) {
666 		if (QMessageBox::question(this, tr("Abort typesetting?"), tr("A typesetting process is still running and must be stopped before closing this window.\nDo you want to stop it now?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::No) {
667 			event->ignore();
668 			return;
669 		}
670 		else
671 			interrupt();
672 	}
673 
674 	if (maybeSave()) {
675 		event->accept();
676 		saveRecentFileInfo();
677 		deleteLater();
678 	}
679 	else
680 		event->ignore();
681 }
682 
event(QEvent * event)683 bool TeXDocument::event(QEvent *event) // based on example at doc.trolltech.com/qq/qq18-macfeatures.html
684 {
685 	switch (event->type()) {
686 		case QEvent::IconDrag:
687 			if (isActiveWindow()) {
688 				event->accept();
689 				Qt::KeyboardModifiers mods = qApp->keyboardModifiers();
690 				if (mods == Qt::NoModifier) {
691 					QDrag *drag = new QDrag(this);
692 					QMimeData *data = new QMimeData();
693 					data->setUrls(QList<QUrl>() << QUrl::fromLocalFile(curFile));
694 					drag->setMimeData(data);
695 					QPixmap dragIcon(":/images/images/TeXworks-doc-48.png");
696 					drag->setPixmap(dragIcon);
697 					drag->setHotSpot(QPoint(dragIcon.width() - 5, 5));
698 					drag->start(Qt::LinkAction | Qt::CopyAction);
699 				}
700 				else if (mods == Qt::ShiftModifier) {
701 					QMenu menu(this);
702 					connect(&menu, SIGNAL(triggered(QAction*)), this, SLOT(openAt(QAction*)));
703 					QFileInfo info(curFile);
704 					QAction *action = menu.addAction(info.fileName());
705 					action->setIcon(QIcon(":/images/images/TeXworks-doc.png"));
706 					QStringList folders = info.absolutePath().split('/');
707 					QStringListIterator it(folders);
708 					it.toBack();
709 					while (it.hasPrevious()) {
710 						QString str = it.previous();
711 						QIcon icon;
712 						if (!str.isEmpty()) {
713 							icon = style()->standardIcon(QStyle::SP_DirClosedIcon, 0, this);
714 						}
715 						else {
716 							str = "/";
717 							icon = style()->standardIcon(QStyle::SP_DriveHDIcon, 0, this);
718 						}
719 						action = menu.addAction(str);
720 						action->setIcon(icon);
721 #if defined(Q_OS_DARWIN)
722 						action->setIconVisibleInMenu(true);
723 #endif
724 					}
725 					QPoint pos(QCursor::pos().x() - 20, frameGeometry().y());
726 					menu.exec(pos);
727 				}
728 				else {
729 					event->ignore();
730 				}
731 				return true;
732 			}
733 
734 		case QEvent::WindowActivate:
735 			showFloaters();
736 			emit activatedWindow(this);
737 			break;
738 
739 		default:
740 			break;
741 	}
742 	return QMainWindow::event(event);
743 }
744 
openAt(QAction * action)745 void TeXDocument::openAt(QAction *action)
746 {
747 	QString path = curFile.left(curFile.indexOf(action->text())) + action->text();
748 	if (path == curFile)
749 		return;
750 	QProcess proc;
751 	proc.start("/usr/bin/open", QStringList() << path, QIODevice::ReadOnly);
752 	proc.waitForFinished();
753 }
754 
save()755 bool TeXDocument::save()
756 {
757 	if (isUntitled)
758 		return saveAs();
759 	else
760 		return saveFile(curFile);
761 }
762 
saveAll()763 bool TeXDocument::saveAll()
764 {
765 	bool savedAll = true;
766 	foreach (TeXDocument* doc, docList) {
767 		if (doc->textEdit->document()->isModified()) {
768 			if (!doc->save()) {
769 				savedAll = false;
770 			}
771 		}
772 	}
773 	return savedAll;
774 }
775 
saveAs()776 bool TeXDocument::saveAs()
777 {
778 	QFileDialog::Options	options = 0;
779 #if defined(Q_OS_WIN)
780 	if(TWApp::GetWindowsVersion() < 0x06000000) options |= QFileDialog::DontUseNativeDialog;
781 #endif
782 	QString selectedFilter = TWUtils::chooseDefaultFilter(curFile, *(TWUtils::filterList()));;
783 
784 	// for untitled docs, default to the last dir used, or $HOME if no saved value
785 	QSETTINGS_OBJECT(settings);
786 	QString lastSaveDir = settings.value("saveDialogDir").toString();
787 	if (lastSaveDir.isEmpty() || !QDir(lastSaveDir).exists())
788 		lastSaveDir = QDir::homePath();
789 	QString fileName = QFileDialog::getSaveFileName(this, tr("Save File"),
790 													isUntitled ? lastSaveDir + "/" + curFile : curFile,
791 													TWUtils::filterList()->join(";;"),
792 													&selectedFilter, options);
793 	if (fileName.isEmpty())
794 		return false;
795 
796 	// save the old document in "Recent Files"
797 	saveRecentFileInfo();
798 
799 	// add extension from the selected filter, if unique and not already present
800 	QRegExp re("\\(\\*(\\.[^ *]+)\\)");
801 	if (re.indexIn(selectedFilter) >= 0) {
802 		QString ext = re.cap(1);
803 		if (!fileName.endsWith(ext, Qt::CaseInsensitive) && !fileName.endsWith("."))
804 			fileName.append(ext);
805 	}
806 
807 	if (fileName != curFile && pdfDoc) {
808 		// For the pdf, it is as if it's source doc was closed
809 		// Note that this may result in the pdf being closed!
810 		pdfDoc->texClosed(this);
811 		// The pdf connection is no longer (necessarily) valid. Detach it for
812 		// now (the correct connection will be reestablished on next typeset).
813 		detachPdf();
814 	}
815 
816 	QFileInfo info(fileName);
817 	settings.setValue("saveDialogDir", info.absolutePath());
818 
819 	return saveFile(fileName);
820 }
821 
maybeSave()822 bool TeXDocument::maybeSave()
823 {
824 	if (textEdit->document()->isModified()) {
825 		QMessageBox::StandardButton ret;
826 		QMessageBox msgBox(QMessageBox::Warning, tr(TEXWORKS_NAME),
827 						   tr("The document \"%1\" has been modified.\n"
828 							  "Do you want to save your changes?")
829 						   .arg(TWUtils::strippedName(curFile)),
830 						   QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel,
831 						   this);
832 		msgBox.button(QMessageBox::Discard)->setShortcut(QKeySequence(tr("Ctrl+D", "shortcut: Don't Save")));
833 		msgBox.setWindowModality(Qt::WindowModal);
834 		ret = (QMessageBox::StandardButton)msgBox.exec();
835 		if (ret == QMessageBox::Save)
836 			return save();
837 		else if (ret == QMessageBox::Cancel)
838 			return false;
839 	}
840 	return true;
841 }
842 
saveFilesHavingRoot(const QString & aRootFile)843 bool TeXDocument::saveFilesHavingRoot(const QString& aRootFile)
844 {
845 	foreach (TeXDocument* doc, docList) {
846 		if (doc->getRootFilePath() == aRootFile) {
847 			if (doc->textEdit->document()->isModified() && !doc->save())
848 				return false;
849 		}
850 	}
851 	return true;
852 }
853 
getRootFilePath()854 const QString& TeXDocument::getRootFilePath()
855 {
856 	findRootFilePath();
857 	return rootFilePath;
858 }
859 
revert()860 void TeXDocument::revert()
861 {
862 	if (!isUntitled) {
863 		QMessageBox	messageBox(QMessageBox::Warning, tr(TEXWORKS_NAME),
864 					tr("Do you want to discard all changes to the document \"%1\", and revert to the last saved version?")
865 					   .arg(TWUtils::strippedName(curFile)), QMessageBox::Cancel, this);
866 		QAbstractButton *revertButton = messageBox.addButton(tr("Revert"), QMessageBox::DestructiveRole);
867 		revertButton->setShortcut(QKeySequence(tr("Ctrl+R", "shortcut: Revert")));
868 		messageBox.setDefaultButton(QMessageBox::Cancel);
869 		messageBox.setWindowModality(Qt::WindowModal);
870 		messageBox.exec();
871 		if (messageBox.clickedButton() == revertButton)
872 			loadFile(curFile);
873 	}
874 }
875 
maybeEnableSaveAndRevert(bool modified)876 void TeXDocument::maybeEnableSaveAndRevert(bool modified)
877 {
878 	actionSave->setEnabled(modified || isUntitled);
879 	actionRevert_to_Saved->setEnabled(modified && !isUntitled);
880 }
881 
882 static const char* texshopSynonyms[] = {
883 	"MacOSRoman",		"Apple Roman",
884 	"IsoLatin",			"ISO 8859-1",
885 	"IsoLatin2",		"ISO 8859-2",
886 	"IsoLatin5",		"ISO 8859-5",
887 	"IsoLatin9",		"ISO 8859-9",
888 //	"MacJapanese",		"",
889 //	"DOSJapanese",		"",
890 	"SJIS_X0213",		"Shift-JIS",
891 	"EUC_JP",			"EUC-JP",
892 //	"JISJapanese",		"",
893 //	"MacKorean",		"",
894 	"UTF-8 Unicode",	"UTF-8",
895 	"Standard Unicode",	"UTF-16",
896 //	"Mac Cyrillic",		"",
897 //	"DOS Cyrillic",		"",
898 //	"DOS Russian",		"",
899 	"Windows Cyrillic",	"Windows-1251",
900 	"KOI8_R",			"KOI8-R",
901 //	"Mac Chinese Traditional",	"",
902 //	"Mac Chinese Simplified",	"",
903 //	"DOS Chinese Traditional",	"",
904 //	"DOS Chinese Simplified",	"",
905 //	"GBK",				"",
906 //	"GB 2312",			"",
907 	"GB 18030",			"GB18030-0",
908 	NULL
909 };
910 
scanForEncoding(const QString & peekStr,bool & hasMetadata,QString & reqName)911 QTextCodec *TeXDocument::scanForEncoding(const QString &peekStr, bool &hasMetadata, QString &reqName)
912 {
913 	// peek at the file for %!TEX encoding = ....
914 	QRegExp re("% *!TEX +encoding *= *([^\\r\\n\\x2029]+)[\\r\\n\\x2029]", Qt::CaseInsensitive);
915 	int pos = re.indexIn(peekStr);
916 	QTextCodec *reqCodec = NULL;
917 	if (pos > -1) {
918 		hasMetadata = true;
919 		reqName = re.cap(1).trimmed();
920 		reqCodec = QTextCodec::codecForName(reqName.toLatin1());
921 		if (reqCodec == NULL) {
922 			static QHash<QString,QString> *synonyms = NULL;
923 			if (synonyms == NULL) {
924 				synonyms = new QHash<QString,QString>;
925 				for (int i = 0; texshopSynonyms[i] != NULL; i += 2)
926 					synonyms->insert(QString(texshopSynonyms[i]).toLower(), texshopSynonyms[i+1]);
927 			}
928 			if (synonyms->contains(reqName.toLower()))
929 				reqCodec = QTextCodec::codecForName(synonyms->value(reqName.toLower()).toLatin1());
930 		}
931 	}
932 	else
933 		hasMetadata = false;
934 	return reqCodec;
935 }
936 
937 #define PEEK_LENGTH 1024
938 
readFile(const QString & fileName,QTextCodec ** codecUsed,int * lineEndings,QTextCodec * forceCodec)939 QString TeXDocument::readFile(const QString &fileName,
940 							  QTextCodec **codecUsed,
941 							  int *lineEndings,
942 							  QTextCodec * forceCodec)
943 	// reads the text from a file, after checking for %!TEX encoding.... metadata
944 	// sets codecUsed to the QTextCodec used to read the text
945 	// returns a null (not just empty) QString on failure
946 {
947 	if (lineEndings != NULL) {
948 		// initialize to default for the platform
949 #if defined(Q_OS_WIN)
950 		*lineEndings = kLineEnd_CRLF;
951 #else
952 		*lineEndings = kLineEnd_LF;
953 #endif
954 	}
955 
956 	utf8BOM = false;
957 	QFile file(fileName);
958 	// Not using QFile::Text because this prevents us reading "classic" Mac files
959 	// with CR-only line endings. See issue #242.
960 	if (!file.open(QFile::ReadOnly)) {
961 		QMessageBox::warning(this, tr(TEXWORKS_NAME),
962 							 tr("Cannot read file \"%1\":\n%2")
963 							 .arg(fileName)
964 							 .arg(file.errorString()));
965 		return QString();
966 	}
967 
968 	QByteArray peekBytes(file.peek(PEEK_LENGTH));
969 
970 	QString reqName;
971 	bool hasMetadata;
972 	if (forceCodec)
973 		*codecUsed = forceCodec;
974 	else {
975 		*codecUsed = scanForEncoding(QString::fromUtf8(peekBytes), hasMetadata, reqName);
976 		if (*codecUsed == NULL) {
977 			*codecUsed = TWApp::instance()->getDefaultCodec();
978 			if (hasMetadata) {
979 				if (QMessageBox::warning(this, tr("Unrecognized encoding"),
980 						tr("The text encoding %1 used in %2 is not supported.\n\n"
981 						   "It will be interpreted as %3 instead, which may result in incorrect text.")
982 							.arg(reqName)
983 							.arg(fileName)
984 							.arg(QString::fromLatin1((*codecUsed)->name())),
985 						QMessageBox::Ok | QMessageBox::Cancel, QMessageBox::Ok) == QMessageBox::Cancel)
986 					return QString();
987 			}
988 		}
989 	}
990 
991 	// When using the UTF-8 codec (mib = 106), byte order marks (BOMs) are
992 	// ignored during reading and not produced when writing. To keep them in
993 	// files that have them, we need to check for them ourselves.
994 	if ((*codecUsed)->mibEnum() == 106 && peekBytes.size() >= 3 && peekBytes[0] == '\xEF' && peekBytes[1] == '\xBB' && peekBytes[2] == '\xBF')
995 		utf8BOM = true;
996 
997 	if (file.atEnd())
998 		return QString("");
999 	else {
1000 		QTextStream in(&file);
1001 		in.setCodec(*codecUsed);
1002 		QString text = in.readAll();
1003 
1004 		if (lineEndings != NULL) {
1005 			if (text.contains("\r\n")) {
1006 				text.replace("\r\n", "\n");
1007 				*lineEndings = kLineEnd_CRLF;
1008 			}
1009 			else if (text.contains("\r") && !text.contains("\n")) {
1010 				text.replace("\r", "\n");
1011 				*lineEndings = kLineEnd_CR;
1012 			}
1013 			else
1014 				*lineEndings = kLineEnd_LF;
1015 
1016 			if (text.contains("\r")) {
1017 				text.replace("\r", "\n");
1018 				*lineEndings |= kLineEnd_Mixed;
1019 			}
1020 		}
1021 
1022 		return text;
1023 	}
1024 }
1025 
loadFile(const QString & fileName,bool asTemplate,bool inBackground,bool reload,QTextCodec * forceCodec)1026 void TeXDocument::loadFile(const QString &fileName, bool asTemplate /* = false */, bool inBackground /* = false */, bool reload /* = false */, QTextCodec * forceCodec /* = NULL */)
1027 {
1028 	QString fileContents = readFile(fileName, &codec, &lineEndings, forceCodec);
1029 	showLineEndingSetting();
1030 	showEncodingSetting();
1031 
1032 	if (fileContents.isNull())
1033 		return;
1034 
1035 	QApplication::setOverrideCursor(Qt::WaitCursor);
1036 
1037 	deferTagListChanges = true;
1038 	tagListChanged = false;
1039 	textEdit->setPlainText(fileContents);
1040 	deferTagListChanges = false;
1041 	if (tagListChanged)
1042 		emit tagListUpdated();
1043 
1044 	// Ensure the window is shown early (before setPlainText()).
1045 	// - this ensures it is shown before the PDF (if opening a new doc)
1046 	// - this avoids problems during layouting (which can be broken if the
1047 	//   geometry, highlighting, ... is changed before the window is shown)
1048 	if (!reload)
1049 		show();
1050 	QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
1051 
1052 	{
1053 		// Try to work around QTBUG-20354
1054 		// It seems that adding additionalFormats (as is done automatically on
1055 		// setPlainText() by the syntax highlighter) can disturb the layouting
1056 		// process, leaving some blocks with size zero. This causes the
1057 		// corresponding lines to "disappear" and can even crash the application
1058 		// in connection with the "highlight current line" feature.
1059 		QTextDocument * doc = textEdit->document();
1060 		Q_ASSERT(doc != NULL);
1061 		QAbstractTextDocumentLayout * docLayout = doc->documentLayout();
1062 		Q_ASSERT(docLayout != NULL);
1063 
1064 		int tries;
1065 		for (tries = 0; tries < 10; ++tries) {
1066 			bool isLayoutOK = true;
1067 			for (QTextBlock b = doc->firstBlock(); b.isValid(); b = b.next()) {
1068 				if (docLayout->	blockBoundingRect(b).isEmpty()) {
1069 					isLayoutOK = false;
1070 					break;
1071 				}
1072 			}
1073 			if (isLayoutOK) break;
1074 			// Re-setting the document content naturally triggers a relayout
1075 			// (also a rehighlight). Note that layouting only works sensibly
1076 			// once show() was called, or else there is no valid widget geometry
1077 			// to act as bounding box.
1078 			doc->setPlainText(doc->toPlainText());
1079 			QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
1080 		}
1081 		if (tries >= 10) {
1082 			QMessageBox::warning(this, tr("Layout Problem"), tr("A problem occured while laying out the loaded document in the editor. This is caused by an issue in the underlying Qt framework and can cause TeXworks to crash under certain circumstances. The symptoms of this problem are hidden or overlapping lines. To work around this, please try one of the following:\n -) Turn syntax highlighting off and on\n -) Turn line numbers off and on\n -) Resize the window\n\nWe are sorry for the inconvenience."));
1083 		}
1084 	}
1085 
1086 	QApplication::restoreOverrideCursor();
1087 
1088 	if (asTemplate) {
1089 		lastModified = QDateTime();
1090 	}
1091 	else {
1092 		setCurrentFile(fileName);
1093 		if (!reload) {
1094 			QSETTINGS_OBJECT(settings);
1095 			if (!inBackground && settings.value("openPDFwithTeX", kDefault_OpenPDFwithTeX).toBool()) {
1096 				openPdfIfAvailable(false);
1097 				// Note: openPdfIfAvailable() enables/disables actionGo_to_Preview
1098 				// automatically.
1099 			}
1100 			else {
1101 				QString previewFileName;
1102 				actionGo_to_Preview->setEnabled(getPreviewFileName(previewFileName));
1103 			}
1104 			// set openDialogDir after openPdfIfAvailable as we want the .tex file's
1105 			// path to end up in that variable (which might be touched/changed when
1106 			// loading the pdf
1107 			QFileInfo info(fileName);
1108 			settings.setValue("openDialogDir", info.canonicalPath());
1109 		}
1110 
1111 		statusBar()->showMessage(tr("File \"%1\" loaded").arg(TWUtils::strippedName(curFile)),
1112 								 kStatusMessageDuration);
1113 		setupFileWatcher();
1114 	}
1115 	maybeEnableSaveAndRevert(false);
1116 
1117 	if (!reload) {
1118 		bool autoPlace = true;
1119 		QMap<QString,QVariant> properties = TWApp::instance()->getFileProperties(curFile);
1120 		if (properties.contains("geometry")) {
1121 			restoreGeometry(properties.value("geometry").toByteArray());
1122 			autoPlace = false;
1123 		}
1124 		if (properties.contains("state"))
1125 			restoreState(properties.value("state").toByteArray(), kTeXWindowStateVersion);
1126 
1127 		if (properties.contains("selStart")) {
1128 			QTextCursor c(textEdit->document());
1129 			c.setPosition(properties.value("selStart").toInt());
1130 			c.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, properties.value("selLength", 0).toInt());
1131 			textEdit->setTextCursor(c);
1132 		}
1133 
1134 		if (properties.contains("quotesMode"))
1135 			setSmartQuotesMode(properties.value("quotesMode").toString());
1136 		if (properties.contains("indentMode"))
1137 			setAutoIndentMode(properties.value("indentMode").toString());
1138 		if (properties.contains("syntaxMode"))
1139 			setSyntaxColoringMode(properties.value("syntaxMode").toString());
1140 		if (properties.contains("wrapLines"))
1141 			setWrapLines(properties.value("wrapLines").toBool());
1142 		if (properties.contains("lineNumbers"))
1143 			setLineNumbers(properties.value("lineNumbers").toBool());
1144 
1145 		if (pdfDoc) {
1146 			if (properties.contains("pdfgeometry")) {
1147 				pdfDoc->restoreGeometry(properties.value("pdfgeometry").toByteArray());
1148 				autoPlace = false;
1149 			}
1150 			if (properties.contains("pdfstate"))
1151 				pdfDoc->restoreState(properties.value("pdfstate").toByteArray(), kPDFWindowStateVersion);
1152 		}
1153 
1154 		if (autoPlace)
1155 			sideBySide();
1156 
1157 		if (pdfDoc)
1158 			pdfDoc->show();
1159 
1160 		selectWindow();
1161 		saveRecentFileInfo();
1162 	}
1163 
1164 	editor()->updateLineNumberAreaWidth(0);
1165 
1166 	runHooks("LoadFile");
1167 }
1168 
delayedInit()1169 void TeXDocument::delayedInit()
1170 {
1171 	if (!highlighter) {
1172 		QSETTINGS_OBJECT(settings);
1173 
1174 		highlighter = new TeXHighlighter(textEdit->document(), this);
1175 		connect(textEdit, SIGNAL(rehighlight()), highlighter, SLOT(rehighlight()));
1176 
1177 		// set up syntax highlighting
1178 		// First, use the current file's syntaxMode property (if available)
1179 		QMap<QString,QVariant> properties = TWApp::instance()->getFileProperties(curFile);
1180 		if (properties.contains("syntaxMode"))
1181 			setSyntaxColoringMode(properties.value("syntaxMode").toString());
1182 		// Secondly, try the global settings
1183 		else if (settings.contains("syntaxColoring"))
1184 				setSyntaxColoringMode(settings.value("syntaxColoring").toString());
1185 		// Lastly, use the default setting
1186 		else {
1187 			// This should mimick the code in PrefsDialog::doPrefsDialog()
1188 			QStringList syntaxOptions = TeXHighlighter::syntaxOptions();
1189 			if (kDefault_SyntaxColoring < syntaxOptions.count())
1190 				setSyntaxColoringMode(syntaxOptions[kDefault_SyntaxColoring]);
1191 			else
1192 				setSyntaxColoringMode("");
1193 		}
1194 
1195 		// set the default spell checking language
1196 		setSpellcheckLanguage(settings.value("language").toString());
1197 
1198 		// contentsChanged() parses the modlines (thus possibly overrinding the spell checking language)
1199 		contentsChanged(0, 0, 0);
1200 	}
1201 }
1202 
1203 #define FILE_MODIFICATION_ACCURACY	1000	// in msec
reloadIfChangedOnDisk()1204 void TeXDocument::reloadIfChangedOnDisk()
1205 {
1206 	if (isUntitled || !lastModified.isValid())
1207 		return;
1208 
1209 	QDateTime fileModified = QFileInfo(curFile).lastModified();
1210 	if (!fileModified.isValid() || fileModified == lastModified)
1211 		return;
1212 
1213 	clearFileWatcher(); // stop watching until next save or reload
1214 	if (textEdit->document()->isModified()) {
1215 		if (QMessageBox::warning(this, tr("File changed on disk"),
1216 								 tr("%1 has been modified by another program.\n\n"
1217 									"Do you want to discard your current changes, and reload the file from disk?")
1218 								 .arg(curFile),
1219 								 QMessageBox::Ok | QMessageBox::Cancel, QMessageBox::Cancel) == QMessageBox::Cancel) {
1220 			lastModified = QDateTime();	// invalidate the timestamp
1221 			return;
1222 		}
1223 	}
1224 	// user chose to discard, or there were no local changes
1225 	// save the current cursor position
1226 	QTextCursor cur;
1227 	int oldSelStart, oldSelEnd, oldBlockStart, oldBlockEnd;
1228 	int xPos = 0, yPos = 0;
1229 	QString oldSel;
1230 
1231 	// Store the selection (note that oldSelStart == oldSelEnd if there is
1232 	// no selection)
1233 	cur = textEdit->textCursor();
1234 	oldSelStart = cur.selectionStart();
1235 	oldSelEnd = cur.selectionEnd();
1236 	oldSel = cur.selectedText();
1237 
1238 	// Get the block number and the offset in the block of the start of the
1239 	// selection
1240 	cur.setPosition(oldSelStart);
1241 	oldBlockStart = cur.blockNumber();
1242 	oldSelStart -= cur.block().position();
1243 
1244 	// Get the block number and the offset in the block of the end of the
1245 	// selection
1246 	cur.setPosition(oldSelEnd);
1247 	oldBlockEnd = cur.blockNumber();
1248 	oldSelEnd -= cur.block().position();
1249 
1250 	// Get the values of the scroll bars so we can later restore the view
1251 	if (textEdit->horizontalScrollBar())
1252 		xPos = textEdit->horizontalScrollBar()->value();
1253 	if (textEdit->verticalScrollBar())
1254 		yPos = textEdit->verticalScrollBar()->value();
1255 
1256 	// Reload the file from the disk
1257 	// Note that the file may change again before the system watcher is enabled
1258 	// again, so we should catch that case (this sometimes occurs with version
1259 	// control systems during commits)
1260 	unsigned int i;
1261 	// Limit this to avoid infinite loops
1262 	for (i = 0; i < 10; ++i) {
1263 		clearFileWatcher(); // stop watching until next save or reload
1264 		// Only reload files at full seconds to avoid problems with limited
1265 		// accuracy of the file system modification timestamps (if the file changes
1266 		// twice in one second, the modification timestamp is not altered and we may
1267 		// miss the second change otherwise)
1268 		while (QDateTime::currentDateTime() <= QFileInfo(curFile).lastModified().addMSecs(FILE_MODIFICATION_ACCURACY))
1269 			; // do nothing
1270 		loadFile(curFile, false, true, true);
1271 		// one final safety check - if the file has not changed, we can safely end this
1272 		if (QDateTime::currentDateTime() > QFileInfo(curFile).lastModified().addMSecs(FILE_MODIFICATION_ACCURACY))
1273 			break;
1274 	}
1275 	if (i == 10) { // the file has been changing constantly - give up and inform the user
1276 		QMessageBox::information(this, tr("File changed on disk"),
1277 								 tr("%1 is constantly being modified by another program.\n\n"
1278 									"Please use \"File > Revert to Saved\" manually when the external process has finished.")
1279 								 .arg(curFile),
1280 								 QMessageBox::Ok, QMessageBox::Ok);
1281 	}
1282 
1283 	// restore the cursor position
1284 	cur = textEdit->textCursor();
1285 
1286 	// move the cursor to the beginning (this should actually be the case,
1287 	// but one never knows)
1288 	cur.setPosition(0);
1289 
1290 	// move the cursor to the starting block
1291 	cur.movePosition(QTextCursor::NextBlock, QTextCursor::MoveAnchor, oldBlockStart);
1292 	cur.movePosition(QTextCursor::StartOfBlock, QTextCursor::MoveAnchor);
1293 	cur.movePosition(QTextCursor::NextCharacter, QTextCursor::MoveAnchor, oldSelStart);
1294 
1295 	// move the cursor to the end block
1296 	cur.movePosition(QTextCursor::NextBlock, QTextCursor::KeepAnchor, oldBlockEnd - oldBlockStart);
1297 	cur.movePosition(QTextCursor::StartOfBlock, QTextCursor::KeepAnchor);
1298 	cur.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, oldSelEnd);
1299 
1300 	// if the current selection doesn't match the stored selection, collapse
1301 	// to the beginning position
1302 	if (cur.selectedText() != oldSel)
1303 		cur.setPosition(cur.selectionStart());
1304 
1305 	textEdit->setTextCursor(cur);
1306 
1307 	// restore the view
1308 	if (textEdit->horizontalScrollBar())
1309 		textEdit->horizontalScrollBar()->setValue(xPos);
1310 	if (textEdit->verticalScrollBar())
1311 		textEdit->verticalScrollBar()->setValue(yPos);
1312 }
1313 
1314 // get expected name of the Preview file, and return whether it exists
getPreviewFileName(QString & pdfName)1315 bool TeXDocument::getPreviewFileName(QString &pdfName)
1316 {
1317 	findRootFilePath();
1318 	if (rootFilePath == "")
1319 		return false;
1320 	QFileInfo fi(rootFilePath);
1321 	pdfName = fi.canonicalPath() + "/" + fi.completeBaseName() + ".pdf";
1322 	fi.setFile(pdfName);
1323 	return fi.exists();
1324 }
1325 
openPdfIfAvailable(bool show)1326 bool TeXDocument::openPdfIfAvailable(bool show)
1327 {
1328 	detachPdf();
1329 	actionSide_by_Side->setEnabled(false);
1330 	actionGo_to_Preview->setEnabled(false);
1331 
1332 	QString pdfName;
1333 	if (getPreviewFileName(pdfName)) {
1334 		PDFDocument *existingPdf = PDFDocument::findDocument(pdfName);
1335 		if (existingPdf != NULL) {
1336 			pdfDoc = existingPdf;
1337 			pdfDoc->selectWindow();
1338 			pdfDoc->linkToSource(this);
1339 		}
1340 		else {
1341 			pdfDoc = new PDFDocument(pdfName, this);
1342 			if (show)
1343 				pdfDoc->show();
1344 		}
1345 	}
1346 
1347 	if (pdfDoc != NULL) {
1348 		actionSide_by_Side->setEnabled(true);
1349 		actionGo_to_Preview->setEnabled(true);
1350 		connect(pdfDoc, SIGNAL(destroyed()), this, SLOT(pdfClosed()));
1351 		connect(this, SIGNAL(destroyed(QObject*)), pdfDoc, SLOT(texClosed(QObject*)));
1352 		return true;
1353 	}
1354 
1355 	return false;
1356 }
1357 
pdfClosed()1358 void TeXDocument::pdfClosed()
1359 {
1360 	pdfDoc = NULL;
1361 	actionSide_by_Side->setEnabled(false);
1362 }
1363 
saveFile(const QString & fileName)1364 bool TeXDocument::saveFile(const QString &fileName)
1365 {
1366 	QFileInfo fileInfo(fileName);
1367 	QDateTime fileModified = fileInfo.lastModified();
1368 	if (fileName == curFile && fileModified.isValid() && fileModified != lastModified) {
1369 		if (QMessageBox::warning(this, tr("File changed on disk"),
1370 								 tr("%1 has been modified by another program.\n\n"
1371 									"Do you want to proceed with saving this file, overwriting the version on disk?")
1372 								 .arg(fileName),
1373 								 QMessageBox::Ok | QMessageBox::Cancel, QMessageBox::Cancel) == QMessageBox::Cancel) {
1374 			notSaved:
1375 				statusBar()->showMessage(tr("Document \"%1\" was not saved")
1376 										 .arg(TWUtils::strippedName(curFile)),
1377 										 kStatusMessageDuration);
1378 				return false;
1379 		}
1380 	}
1381 
1382 	QString theText = textEdit->toPlainText();
1383 	switch (lineEndings & kLineEnd_Mask) {
1384 		case kLineEnd_CR:
1385 			theText.replace("\n", "\r");
1386 			break;
1387 		case kLineEnd_LF:
1388 			break;
1389 		case kLineEnd_CRLF:
1390 			theText.replace("\n", "\r\n");
1391 			break;
1392 	}
1393 
1394 	if (!codec)
1395 		codec = TWApp::instance()->getDefaultCodec();
1396 	if (!codec->canEncode(theText)) {
1397 		if (QMessageBox::warning(this, tr("Text cannot be converted"),
1398 				tr("This document contains characters that cannot be represented in the encoding %1.\n\n"
1399 				   "If you proceed, they will be replaced with default codes. "
1400 				   "Alternatively, you may wish to use a different encoding (such as UTF-8) to avoid loss of data.")
1401 					.arg(QString(codec->name())),
1402 				QMessageBox::Ok | QMessageBox::Cancel, QMessageBox::Cancel) == QMessageBox::Cancel)
1403 			goto notSaved;
1404 	}
1405 
1406 	clearFileWatcher();
1407 
1408 	{
1409 		QFile file(fileName);
1410 		if (!file.open(QFile::WriteOnly)) {
1411 			QMessageBox::warning(this, tr(TEXWORKS_NAME),
1412 								 tr("Cannot write file \"%1\":\n%2")
1413 								 .arg(fileName)
1414 								 .arg(file.errorString()));
1415 			setupFileWatcher();
1416 			goto notSaved;
1417 		}
1418 
1419 		QApplication::setOverrideCursor(Qt::WaitCursor);
1420 
1421 		// When using the UTF-8 codec (mib = 106), byte order marks (BOMs) are
1422 		// ignored during reading and not produced when writing. To keep them in
1423 		// files that have them (or the user wants them), we need to write them
1424 		// ourselves.
1425 		if (codec->mibEnum() == 106 && utf8BOM)
1426 			file.write("\xEF\xBB\xBF");
1427 
1428 		if (file.write(codec->fromUnicode(theText)) == -1) {
1429 			QApplication::restoreOverrideCursor();
1430 			QMessageBox::warning(this, tr("Error writing file"),
1431 								 tr("An error may have occurred while saving the file. "
1432 									"You might like to save a copy in a different location."),
1433 								 QMessageBox::Ok);
1434 			goto notSaved;
1435 		}
1436 		QApplication::restoreOverrideCursor();
1437 	}
1438 
1439 	setCurrentFile(fileName);
1440 	statusBar()->showMessage(tr("File \"%1\" saved")
1441 								.arg(TWUtils::strippedName(curFile)),
1442 								kStatusMessageDuration);
1443 
1444 	QTimer::singleShot(0, this, SLOT(setupFileWatcher()));
1445 	return true;
1446 }
1447 
clearFileWatcher()1448 void TeXDocument::clearFileWatcher()
1449 {
1450 	const QStringList files = watcher->files();
1451 	if (files.count() > 0)
1452 		watcher->removePaths(files);
1453 	const QStringList dirs = watcher->directories();
1454 	if (dirs.count() > 0)
1455 		watcher->removePaths(dirs);
1456 }
1457 
setupFileWatcher()1458 void TeXDocument::setupFileWatcher()
1459 {
1460 	clearFileWatcher();
1461 	if (!isUntitled) {
1462 		QFileInfo info(curFile);
1463 		lastModified = info.lastModified();
1464 		watcher->addPath(curFile);
1465 		watcher->addPath(info.canonicalPath());
1466 	}
1467 }
1468 
setCurrentFile(const QString & fileName)1469 void TeXDocument::setCurrentFile(const QString &fileName)
1470 {
1471 	static int sequenceNumber = 1;
1472 
1473 	curFile = QFileInfo(fileName).canonicalFilePath();
1474 	isUntitled = curFile.isEmpty();
1475 	if (isUntitled) {
1476 		curFile = tr("untitled-%1.tex").arg(sequenceNumber++);
1477 		setWindowIcon(QApplication::windowIcon());
1478 	}
1479 	else {
1480 		QIcon winIcon;
1481 #if defined(Q_OS_UNIX) && !defined(Q_OS_DARWIN)
1482 		// The Compiz window manager doesn't seem to support icons larger than
1483 		// 128x128, so we add a suitable one first
1484 		winIcon.addFile(":/images/images/TeXworks-doc-128.png");
1485 #endif
1486 		winIcon.addFile(":/images/images/TeXworks-doc.png");
1487 		setWindowIcon(winIcon);
1488 	}
1489 
1490 	textEdit->document()->setModified(false);
1491 	setWindowModified(false);
1492 
1493 	setWindowTitle(tr("%1[*] - %2").arg(TWUtils::strippedName(curFile)).arg(tr(TEXWORKS_NAME)));
1494 
1495 	actionRemove_Aux_Files->setEnabled(!isUntitled);
1496 
1497 	TWApp::instance()->updateWindowMenus();
1498 }
1499 
saveRecentFileInfo()1500 void TeXDocument::saveRecentFileInfo()
1501 {
1502 	if (isUntitled)
1503 		return;
1504 
1505 	QMap<QString,QVariant> fileProperties;
1506 
1507 	fileProperties.insert("path", curFile);
1508 	fileProperties.insert("geometry", saveGeometry());
1509 	fileProperties.insert("state", saveState(kTeXWindowStateVersion));
1510 	fileProperties.insert("selStart", selectionStart());
1511 	fileProperties.insert("selLength", selectionLength());
1512 	fileProperties.insert("quotesMode", textEdit->getQuotesMode());
1513 	fileProperties.insert("indentMode", textEdit->getIndentMode());
1514 	if (highlighter)
1515 		fileProperties.insert("syntaxMode", highlighter->getSyntaxMode());
1516 	fileProperties.insert("lineNumbers", textEdit->getLineNumbersVisible());
1517 	fileProperties.insert("wrapLines", textEdit->wordWrapMode() == QTextOption::WordWrap);
1518 
1519 	if (pdfDoc) {
1520 		fileProperties.insert("pdfgeometry", pdfDoc->saveGeometry());
1521 		fileProperties.insert("pdfstate", pdfDoc->saveState(kPDFWindowStateVersion));
1522 	}
1523 
1524 	TWApp::instance()->addToRecentFiles(fileProperties);
1525 }
1526 
updateRecentFileActions()1527 void TeXDocument::updateRecentFileActions()
1528 {
1529 	TWUtils::updateRecentFileActions(this, recentFileActions, menuOpen_Recent, actionClear_Recent_Files);
1530 }
1531 
updateWindowMenu()1532 void TeXDocument::updateWindowMenu()
1533 {
1534 	TWUtils::updateWindowMenu(this, menuWindow);
1535 }
1536 
updateEngineList()1537 void TeXDocument::updateEngineList()
1538 {
1539 	engine->disconnect(this);
1540 	while (menuRun->actions().count() > 2)
1541 		menuRun->removeAction(menuRun->actions().last());
1542 	while (engineActions->actions().count() > 0)
1543 		engineActions->removeAction(engineActions->actions().last());
1544 	engine->clear();
1545 	foreach (Engine e, TWApp::instance()->getEngineList()) {
1546 		QAction *newAction = new QAction(e.name(), engineActions);
1547 		newAction->setCheckable(true);
1548 		menuRun->addAction(newAction);
1549 		engine->addItem(e.name());
1550 	}
1551 	connect(engine, SIGNAL(currentIndexChanged(const QString&)), this, SLOT(selectedEngine(const QString&)));
1552 	int index = engine->findText(engineName, Qt::MatchFixedString);
1553 	if (index < 0)
1554 		index = engine->findText(TWApp::instance()->getDefaultEngine().name(), Qt::MatchFixedString);
1555 	if (index >= 0)
1556 		engine->setCurrentIndex(index);
1557 }
1558 
selectedEngine(QAction * engineAction)1559 void TeXDocument::selectedEngine(QAction* engineAction) // sent by actions in menubar menu; update toolbar combo box
1560 {
1561 	engineName = engineAction->text();
1562 	for (int i = 0; i < engine->count(); ++i)
1563 		if (engine->itemText(i) == engineName) {
1564 			engine->setCurrentIndex(i);
1565 			break;
1566 		}
1567 }
1568 
selectedEngine(const QString & name)1569 void TeXDocument::selectedEngine(const QString& name) // sent by toolbar combo box; need to update menu
1570 {
1571 	engineName = name;
1572 	foreach (QAction *act, engineActions->actions()) {
1573 		if (act->text() == name) {
1574 			act->setChecked(true);
1575 			break;
1576 		}
1577 	}
1578 }
1579 
showCursorPosition()1580 void TeXDocument::showCursorPosition()
1581 {
1582 	QTextCursor cursor = textEdit->textCursor();
1583 	cursor.setPosition(cursor.selectionStart());
1584 	int line = cursor.blockNumber() + 1;
1585 	int total = textEdit->document()->blockCount();
1586 	int col = cursor.position() - textEdit->document()->findBlock(cursor.selectionStart()).position();
1587 	lineNumberLabel->setText(tr("Line %1 of %2; col %3").arg(line).arg(total).arg(col));
1588 	if (actionAuto_Follow_Focus->isChecked())
1589 		emit syncFromSource(curFile, line, col, false);
1590 }
1591 
showLineEndingSetting()1592 void TeXDocument::showLineEndingSetting()
1593 {
1594 	QString lineEndStr;
1595 	switch (lineEndings & kLineEnd_Mask) {
1596 		case kLineEnd_LF:
1597 			lineEndStr = "LF";
1598 			break;
1599 		case kLineEnd_CRLF:
1600 			lineEndStr = "CRLF";
1601 			break;
1602 		case kLineEnd_CR:
1603 			lineEndStr = "CR";
1604 			break;
1605 	}
1606 	if ((lineEndings & kLineEnd_Mixed) != 0)
1607 		lineEndStr += "*";
1608 	lineEndingLabel->setText(lineEndStr);
1609 }
1610 
lineEndingPopup(const QPoint loc)1611 void TeXDocument::lineEndingPopup(const QPoint loc)
1612 {
1613 	QMenu menu;
1614 	QAction *cr, *lf, *crlf;
1615 	menu.addAction(lf = new QAction("LF (Unix, Mac OS X)", &menu));
1616 	menu.addAction(crlf = new QAction("CRLF (Windows)", &menu));
1617 	menu.addAction(cr = new QAction("CR (Mac Classic)", &menu));
1618 	QAction *result = menu.exec(lineEndingLabel->mapToGlobal(loc));
1619 	int newSetting = (lineEndings & kLineEnd_Mask);
1620 	if (result == lf)
1621 		newSetting = kLineEnd_LF;
1622 	else if (result == crlf)
1623 		newSetting = kLineEnd_CRLF;
1624 	else if (result == cr)
1625 		newSetting = kLineEnd_CR;
1626 	if (newSetting != (lineEndings & kLineEnd_Mask)) {
1627 		lineEndings = newSetting;
1628 		showLineEndingSetting();
1629 		textEdit->document()->setModified();
1630 	}
1631 }
1632 
showEncodingSetting()1633 void TeXDocument::showEncodingSetting()
1634 {
1635 	encodingLabel->setText(codec ? codec->name() : "");
1636 }
1637 
encodingPopup(const QPoint loc)1638 void TeXDocument::encodingPopup(const QPoint loc)
1639 {
1640 	QMenu menu;
1641 	//: Item in the encoding popup menu
1642 	QAction * reloadAction = new QAction(tr("Reload using selected encoding"), &menu);
1643 	//: Tooltip for "Reload using selected encoding"
1644 	reloadAction->setToolTip(tr("Reloads the current file with the encoding selected from this menu.\n\nThe selected encoding replaces the default one and overrides all \"%!TEX encoding\" lines."));
1645 	QAction * BOMAction = new QAction(tr("Write UTF-8 byte order mark"), &menu);
1646 	BOMAction->setCheckable(true);
1647 	BOMAction->setChecked(utf8BOM);
1648 	// Only enable this option if we are currently using the UTF-8 codec
1649 	BOMAction->setEnabled(codec && codec->mibEnum() == 106);
1650 	QAction * a;
1651 
1652 	if (!isUntitled)
1653 		menu.addAction(reloadAction);
1654 	menu.addAction(BOMAction);
1655 	menu.addSeparator();
1656 
1657 	foreach (QTextCodec *codec, *TWUtils::findCodecs()) {
1658 		a = new QAction(codec->name(), &menu);
1659 		a->setCheckable(true);
1660 		if (codec == this->codec)
1661 			a->setChecked(true);
1662 		menu.addAction(a);
1663 	}
1664 	QAction *result = menu.exec(encodingLabel->mapToGlobal(loc));
1665 	if (result) {
1666 		if (result == reloadAction) {
1667 			if (textEdit->document()->isModified()) {
1668 				if (QMessageBox::warning(this, tr("Unsaved changes"),
1669 										 tr("The file you are trying to reload has unsaved changes.\n\n"
1670 											"Do you want to discard your current changes, and reload the file from disk with the encoding %1?")
1671 										 .arg(QString(codec->name())),
1672 										 QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::No) {
1673 					return;
1674 				}
1675 			}
1676 			clearFileWatcher(); // stop watching until next save or reload
1677 			loadFile(curFile, false, true, true, codec);
1678 			; // FIXME
1679 		}
1680 		else if (result == BOMAction) {
1681 			utf8BOM = BOMAction->isChecked();
1682 			// If the UTF-8 codec is selected, changing utf8BOM actually
1683 			// modifies how the file is saved. In all other cases, it does not
1684 			// take effect until the UTF-8 codec is selected (in which case the
1685 			// modified flag is set anyway).
1686 			if (codec && codec->mibEnum() == 106)
1687 				textEdit->document()->setModified();
1688 		}
1689 		else {
1690 			QTextCodec *newCodec = QTextCodec::codecForName(result->text().toLatin1());
1691 			if (newCodec && newCodec != codec) {
1692 				codec = newCodec;
1693 				showEncodingSetting();
1694 				textEdit->document()->setModified();
1695 			}
1696 		}
1697 	}
1698 }
1699 
sideBySide()1700 void TeXDocument::sideBySide()
1701 {
1702 	if (pdfDoc != NULL) {
1703 		TWUtils::sideBySide(this, pdfDoc);
1704 		pdfDoc->selectWindow(false);
1705 		selectWindow();
1706 	}
1707 	else
1708 		placeOnLeft();
1709 }
1710 
findDocument(const QString & fileName)1711 TeXDocument *TeXDocument::findDocument(const QString &fileName)
1712 {
1713 	QString canonicalFilePath = QFileInfo(fileName).canonicalFilePath();
1714 	if (canonicalFilePath.isEmpty())
1715 		canonicalFilePath = fileName;
1716 			// file doesn't exist (probably from find-results in a new untitled doc),
1717 			// so just use the name as-is
1718 
1719 	foreach (QWidget *widget, qApp->topLevelWidgets()) {
1720 		TeXDocument *theDoc = qobject_cast<TeXDocument*>(widget);
1721 		if (theDoc && theDoc->curFile == canonicalFilePath)
1722 			return theDoc;
1723 	}
1724 	return NULL;
1725 }
1726 
clear()1727 void TeXDocument::clear()
1728 {
1729 	textEdit->textCursor().removeSelectedText();
1730 }
1731 
getLineText(int lineNo) const1732 QString TeXDocument::getLineText(int lineNo) const
1733 {
1734 	QTextDocument* doc = textEdit->document();
1735 	if (lineNo < 1 || lineNo > doc->blockCount())
1736 		return QString();
1737 #if QT_VERSION >= 0x040400
1738 	return doc->findBlockByNumber(lineNo - 1).text();
1739 #else
1740 	QTextBlock block = doc->findBlock(0);
1741 	while (--lineNo > 0)
1742 		block = block.next();
1743 	return block.text();
1744 #endif
1745 }
1746 
goToLine(int lineNo,int selStart,int selEnd)1747 void TeXDocument::goToLine(int lineNo, int selStart, int selEnd)
1748 {
1749 	QTextDocument* doc = textEdit->document();
1750 	if (lineNo < 1 || lineNo > doc->blockCount())
1751 		return;
1752 	int oldScrollValue = -1;
1753 	if (textEdit->verticalScrollBar() != NULL)
1754 		oldScrollValue = textEdit->verticalScrollBar()->value();
1755 #if QT_VERSION >= 0x040400
1756 	QTextCursor cursor(doc->findBlockByNumber(lineNo - 1));
1757 #else
1758 	QTextBlock block = doc->findBlock(0);
1759 	while (--lineNo > 0)
1760 		block = block.next();
1761 	QTextCursor cursor(block);
1762 #endif
1763 	if (selStart >= 0 && selEnd >= selStart) {
1764 		cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::MoveAnchor, selStart);
1765 		cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, selEnd - selStart);
1766 	}
1767 	else
1768 		cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
1769 	textEdit->setTextCursor(cursor);
1770 	maybeCenterSelection(oldScrollValue);
1771 }
1772 
maybeCenterSelection(int oldScrollValue)1773 void TeXDocument::maybeCenterSelection(int oldScrollValue)
1774 {
1775 	if (oldScrollValue != -1 && textEdit->verticalScrollBar() != NULL) {
1776 		int newScrollValue = textEdit->verticalScrollBar()->value();
1777 		if (newScrollValue != oldScrollValue) {
1778 			int delta = (textEdit->height() - textEdit->cursorRect().height()) / 2;
1779 			if (newScrollValue < oldScrollValue)
1780 				delta = -delta;
1781 			textEdit->verticalScrollBar()->setValue(newScrollValue + delta);
1782 		}
1783 	}
1784 }
1785 
doFontDialog()1786 void TeXDocument::doFontDialog()
1787 {
1788 	bool ok;
1789 	QFont font = QFontDialog::getFont(&ok, textEdit->font());
1790 	if (ok) {
1791 		textEdit->setFont(font);
1792 		font.setPointSize(font.pointSize() - 1);
1793 		textEdit_console->setFont(font);
1794 		inputLine->setFont(font);
1795 	}
1796 }
1797 
doLineDialog()1798 void TeXDocument::doLineDialog()
1799 {
1800 	QTextCursor cursor = textEdit->textCursor();
1801 	cursor.setPosition(cursor.selectionStart());
1802 	bool ok;
1803 	#if QT_VERSION >= 0x050000
1804 	int lineNo = QInputDialog::getInt(this, tr("Go to Line"),
1805 									tr("Line number:"), cursor.blockNumber() + 1,
1806 									1, textEdit->document()->blockCount(), 1, &ok);
1807 	#else
1808 	int lineNo = QInputDialog::getInteger(this, tr("Go to Line"),
1809 									tr("Line number:"), cursor.blockNumber() + 1,
1810 									1, textEdit->document()->blockCount(), 1, &ok);
1811 	#endif
1812 	if (ok)
1813 		goToLine(lineNo);
1814 }
1815 
doFindDialog()1816 void TeXDocument::doFindDialog()
1817 {
1818 	if (FindDialog::doFindDialog(textEdit) == QDialog::Accepted)
1819 		doFindAgain(true);
1820 }
1821 
doReplaceDialog()1822 void TeXDocument::doReplaceDialog()
1823 {
1824 	ReplaceDialog::DialogCode result;
1825 	if ((result = ReplaceDialog::doReplaceDialog(textEdit)) != ReplaceDialog::Cancel)
1826 		doReplace(result);
1827 }
1828 
prefixLines(const QString & prefix)1829 void TeXDocument::prefixLines(const QString &prefix)
1830 {
1831 	QTextCursor cursor = textEdit->textCursor();
1832 	cursor.beginEditBlock();
1833 	int selStart = cursor.selectionStart();
1834 	int selEnd = cursor.selectionEnd();
1835 	cursor.setPosition(selStart);
1836 	if (!cursor.atBlockStart()) {
1837 		cursor.movePosition(QTextCursor::StartOfBlock);
1838 		selStart = cursor.position();
1839 	}
1840 	cursor.setPosition(selEnd);
1841 	if (!cursor.atBlockStart() || selEnd == selStart) {
1842 		cursor.movePosition(QTextCursor::NextBlock);
1843 		selEnd = cursor.position();
1844 	}
1845 	if (selEnd == selStart)
1846 		goto handle_end_of_doc;	// special case - cursor in blank line at end of doc
1847 	if (!cursor.atBlockStart()) {
1848 		cursor.movePosition(QTextCursor::StartOfBlock);
1849 		goto handle_end_of_doc; // special case - unterminated last line
1850 	}
1851 	while (cursor.position() > selStart) {
1852 		cursor.movePosition(QTextCursor::PreviousBlock);
1853 	handle_end_of_doc:
1854 		cursor.insertText(prefix);
1855 		cursor.movePosition(QTextCursor::StartOfBlock);
1856 		selEnd += prefix.length();
1857 	}
1858 	cursor.setPosition(selStart);
1859 	cursor.setPosition(selEnd, QTextCursor::KeepAnchor);
1860 	textEdit->setTextCursor(cursor);
1861 	cursor.endEditBlock();
1862 }
1863 
doIndent()1864 void TeXDocument::doIndent()
1865 {
1866 	prefixLines("\t");
1867 }
1868 
doComment()1869 void TeXDocument::doComment()
1870 {
1871 	prefixLines("%");
1872 }
1873 
unPrefixLines(const QString & prefix)1874 void TeXDocument::unPrefixLines(const QString &prefix)
1875 {
1876 	QTextCursor cursor = textEdit->textCursor();
1877 	cursor.beginEditBlock();
1878 	int selStart = cursor.selectionStart();
1879 	int selEnd = cursor.selectionEnd();
1880 	cursor.setPosition(selStart);
1881 	if (!cursor.atBlockStart()) {
1882 		cursor.movePosition(QTextCursor::StartOfBlock);
1883 		selStart = cursor.position();
1884 	}
1885 	cursor.setPosition(selEnd);
1886 	if (!cursor.atBlockStart() || selEnd == selStart) {
1887 		cursor.movePosition(QTextCursor::NextBlock);
1888 		selEnd = cursor.position();
1889 	}
1890 	if (!cursor.atBlockStart()) {
1891 		cursor.movePosition(QTextCursor::StartOfBlock);
1892 		goto handle_end_of_doc; // special case - unterminated last line
1893 	}
1894 	while (cursor.position() > selStart) {
1895 		cursor.movePosition(QTextCursor::PreviousBlock);
1896 	handle_end_of_doc:
1897 		cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
1898 		QString		str = cursor.selectedText();
1899 		if (str == prefix) {
1900 			cursor.removeSelectedText();
1901 			selEnd -= prefix.length();
1902 		}
1903 		else
1904 			cursor.movePosition(QTextCursor::PreviousCharacter);
1905 	}
1906 	cursor.setPosition(selStart);
1907 	cursor.setPosition(selEnd, QTextCursor::KeepAnchor);
1908 	textEdit->setTextCursor(cursor);
1909 	cursor.endEditBlock();
1910 }
1911 
doUnindent()1912 void TeXDocument::doUnindent()
1913 {
1914 	unPrefixLines("\t");
1915 }
1916 
doUncomment()1917 void TeXDocument::doUncomment()
1918 {
1919 	unPrefixLines("%");
1920 }
1921 
toUppercase()1922 void TeXDocument::toUppercase()
1923 {
1924 	replaceSelection(textEdit->textCursor().selectedText().toUpper());
1925 }
1926 
toLowercase()1927 void TeXDocument::toLowercase()
1928 {
1929 	replaceSelection(textEdit->textCursor().selectedText().toLower());
1930 }
1931 
toggleCase()1932 void TeXDocument::toggleCase()
1933 {
1934 	QString theText = textEdit->textCursor().selectedText();
1935 	for (int i = 0; i < theText.length(); ++i) {
1936 		QCharRef ch = theText[i];
1937 		if (ch.isLower())
1938 			ch = ch.toUpper();
1939 		else
1940 			ch = ch.toLower();
1941 	}
1942 	replaceSelection(theText);
1943 }
1944 
replaceSelection(const QString & newText)1945 void TeXDocument::replaceSelection(const QString& newText)
1946 {
1947 	QTextCursor cursor = textEdit->textCursor();
1948 	int start = cursor.selectionStart();
1949 	cursor.insertText(newText);
1950 	int end = cursor.selectionEnd();
1951 	cursor.setPosition(start);
1952 	cursor.setPosition(end, QTextCursor::KeepAnchor);
1953 	textEdit->setTextCursor(cursor);
1954 }
1955 
selectRange(int start,int length)1956 void TeXDocument::selectRange(int start, int length)
1957 {
1958 	QTextCursor c = textCursor();
1959 	c.setPosition(start);
1960 	c.setPosition(start + length, QTextCursor::KeepAnchor);
1961 	editor()->setTextCursor(c);
1962 }
1963 
insertText(const QString & text)1964 void TeXDocument::insertText(const QString& text)
1965 {
1966 	textCursor().insertText(text);
1967 }
1968 
balanceDelimiters()1969 void TeXDocument::balanceDelimiters()
1970 {
1971 	const QString text = textEdit->toPlainText();
1972 	QTextCursor cursor = textEdit->textCursor();
1973 	int openPos = TWUtils::findOpeningDelim(text, cursor.selectionStart());
1974 	if (openPos >= 0 && openPos < text.length() - 1) {
1975 		do {
1976 			int closePos = TWUtils::balanceDelim(text, openPos + 1, TWUtils::closerMatching(text[openPos]), 1);
1977 			if (closePos < 0)
1978 				break;
1979 			if (closePos >= cursor.selectionEnd()) {
1980 				cursor.setPosition(openPos);
1981 				cursor.setPosition(closePos + 1, QTextCursor::KeepAnchor);
1982 				textEdit->setTextCursor(cursor);
1983 				return;
1984 			}
1985 			if (openPos > 0)
1986 				openPos = TWUtils::findOpeningDelim(text, openPos - 1);
1987 			else
1988 				break;
1989 		} while (openPos >= 0);
1990 	}
1991 	QApplication::beep();
1992 }
1993 
doHardWrapDialog()1994 void TeXDocument::doHardWrapDialog()
1995 {
1996 	HardWrapDialog dlg(this);
1997 
1998 	dlg.show();
1999 	if (dlg.exec()) {
2000 		dlg.saveSettings();
2001 		doHardWrap(dlg.mode(), dlg.lineWidth(), dlg.rewrap());
2002 	}
2003 }
2004 
doHardWrap(int mode,int lineWidth,bool rewrap)2005 void TeXDocument::doHardWrap(int mode, int lineWidth, bool rewrap)
2006 {
2007 	if (mode == kHardWrapMode_Window) {
2008 		// fudge this for now.... not accurate with proportional fonts, ignores tabs,....
2009 		QFontMetrics fm(textEdit->currentFont());
2010 		lineWidth = textEdit->width() / fm.averageCharWidth();
2011 	}
2012 	else if (mode == kHardWrapMode_Unwrap) {
2013 		lineWidth = INT_MAX;
2014 		rewrap = true;
2015 	}
2016 	if (lineWidth == 0)
2017 		return;
2018 
2019 	QTextCursor cur = textEdit->textCursor();
2020 	if (!cur.hasSelection())
2021 		cur.select(QTextCursor::Document);
2022 
2023 	int selStart = cur.selectionStart();
2024 	int selEnd = cur.selectionEnd();
2025 
2026 	cur.setPosition(selStart);
2027 	if (!cur.atBlockStart()) {
2028 		cur.movePosition(QTextCursor::StartOfBlock);
2029 		selStart = cur.position();
2030 	}
2031 
2032 	cur.setPosition(selEnd);
2033 	if (!cur.atBlockStart()) {
2034 		cur.movePosition(QTextCursor::NextBlock);
2035 		selEnd = cur.position();
2036 	}
2037 
2038 	cur.setPosition(selStart);
2039 	cur.setPosition(selEnd, QTextCursor::KeepAnchor);
2040 
2041 	QString oldText = cur.selectedText();
2042 	QRegExp breakPattern("\\s+");
2043 	QString newText;
2044 
2045 	while (!oldText.isEmpty()) {
2046 		int eol = oldText.indexOf(QChar::ParagraphSeparator);
2047 		if (eol == -1)
2048 			eol = oldText.length();
2049 		else
2050 			eol += 1;
2051 		QString line = oldText.left(eol);
2052 		oldText.remove(0, eol);
2053 
2054 		if (rewrap && line.trimmed().length() > 0) {
2055 			while (!oldText.isEmpty()) {
2056 				eol = oldText.indexOf(QChar::ParagraphSeparator);
2057 				if (eol == -1)
2058 					eol = oldText.length();
2059 				QString nextLine = oldText.left(eol).trimmed();
2060 				if (nextLine.isEmpty())
2061 					break;
2062 				line = line.trimmed().append(QChar(' ')).append(nextLine);
2063 				oldText.remove(0, eol + 1);
2064 			}
2065 		}
2066 
2067 		if (line.length() <= lineWidth) {
2068 			newText.append(line);
2069 			continue;
2070 		}
2071 
2072 		line = line.trimmed();
2073 		if (line.length() <= lineWidth) {
2074 			newText.append(line);
2075 			continue;
2076 		}
2077 
2078 		int curLength = 0;
2079 		while (!line.isEmpty()) {
2080 			int breakPoint = line.indexOf(breakPattern);
2081 			int matchLen = breakPattern.matchedLength();
2082 			if (breakPoint == -1) {
2083 				breakPoint = line.length();
2084 				matchLen = 0;
2085 			}
2086 			if (curLength > 0 && curLength + breakPoint >= lineWidth) {
2087 				newText.append(QChar::ParagraphSeparator);
2088 				curLength = 0;
2089 			}
2090 			if (curLength > 0) {
2091 				newText.append(QChar(' '));
2092 				curLength += 1;
2093 			}
2094 			newText.append(line.left(breakPoint));
2095 			curLength += breakPoint;
2096 			line.remove(0, breakPoint + matchLen);
2097 		}
2098 		newText.append(QChar::ParagraphSeparator);
2099 	}
2100 
2101 	cur.insertText(newText);
2102 
2103 	selEnd = cur.position();
2104 	cur.setPosition(selStart);
2105 	cur.setPosition(selEnd, QTextCursor::KeepAnchor);
2106 	textEdit->setTextCursor(cur);
2107 }
2108 
2109 
setLineNumbers(bool displayNumbers)2110 void TeXDocument::setLineNumbers(bool displayNumbers)
2111 {
2112 	actionLine_Numbers->setChecked(displayNumbers);
2113 	textEdit->setLineNumberDisplay(displayNumbers);
2114 }
2115 
setWrapLines(bool wrap)2116 void TeXDocument::setWrapLines(bool wrap)
2117 {
2118 	actionWrap_Lines->setChecked(wrap);
2119 	textEdit->setWordWrapMode(wrap ? QTextOption::WordWrap : QTextOption::NoWrap);
2120 }
2121 
setSyntaxColoring(int index)2122 void TeXDocument::setSyntaxColoring(int index)
2123 {
2124 	if (highlighter)
2125 		highlighter->setActiveIndex(index);
2126 }
2127 
setSyntaxColoringMode(const QString & mode)2128 void TeXDocument::setSyntaxColoringMode(const QString& mode)
2129 {
2130 	QList<QAction*> actionList = menuSyntax_Coloring->actions();
2131 
2132 	if (mode == "") {
2133 		Q_ASSERT(actionSyntaxColoring_None != NULL);
2134 		actionSyntaxColoring_None->trigger();
2135 		return;
2136 	}
2137 	for (int i = 0; i < actionList.count(); ++i) {
2138 		if (actionList[i]->isCheckable() && actionList[i]->text().compare(mode, Qt::CaseInsensitive) == 0) {
2139 			actionList[i]->trigger();
2140 			return;
2141 		}
2142 	}
2143 }
2144 
setSmartQuotesMode(const QString & mode)2145 void TeXDocument::setSmartQuotesMode(const QString& mode)
2146 {
2147 	QList<QAction*> actionList = menuSmart_Quotes_Mode->actions();
2148 	for (int i = 0; i < actionList.count(); ++i) {
2149 		if (actionList[i]->isCheckable() && actionList[i]->text().compare(mode, Qt::CaseInsensitive) == 0) {
2150 			actionList[i]->trigger();
2151 			return;
2152 		}
2153 	}
2154 	if (mode.isEmpty()) {
2155 		actionSmartQuotes_None->trigger();
2156 		return;
2157 	}
2158 }
2159 
setAutoIndentMode(const QString & mode)2160 void TeXDocument::setAutoIndentMode(const QString& mode)
2161 {
2162 	QList<QAction*> actionList = menuAuto_indent_Mode->actions();
2163 	for (int i = 0; i < actionList.count(); ++i) {
2164 		if (actionList[i]->isCheckable() && actionList[i]->text().compare(mode, Qt::CaseInsensitive) == 0) {
2165 			actionList[i]->trigger();
2166 			return;
2167 		}
2168 	}
2169 	if (mode.isEmpty()) {
2170 		actionAutoIndent_None->trigger();
2171 		return;
2172 	}
2173 }
2174 
doFindAgain(bool fromDialog)2175 void TeXDocument::doFindAgain(bool fromDialog)
2176 {
2177 	QSETTINGS_OBJECT(settings);
2178 	QString	searchText = settings.value("searchText").toString();
2179 	if (searchText.isEmpty())
2180 		return;
2181 
2182 	QTextDocument::FindFlags flags = (QTextDocument::FindFlags)settings.value("searchFlags").toInt();
2183 
2184 	QRegExp	*regex = NULL;
2185 	if (settings.value("searchRegex").toBool()) {
2186 		regex = new QRegExp(searchText, ((flags & QTextDocument::FindCaseSensitively) != 0)
2187 										? Qt::CaseSensitive : Qt::CaseInsensitive);
2188 		if (!regex->isValid()) {
2189 			qApp->beep();
2190 			statusBar()->showMessage(tr("Invalid regular expression"), kStatusMessageDuration);
2191 			delete regex;
2192 			return;
2193 		}
2194 	}
2195 
2196 	if (fromDialog && (settings.value("searchFindAll").toBool() || settings.value("searchAllFiles").toBool())) {
2197 		bool singleFile = true;
2198 		QList<SearchResult> results;
2199 		flags &= ~QTextDocument::FindBackward;
2200 		int docListIndex = 0;
2201 		TeXDocument* theDoc = this;
2202 		while (1) {
2203 			QTextCursor curs(theDoc->textDoc());
2204 			curs.movePosition(QTextCursor::End);
2205 			int rangeStart = 0;
2206 			int rangeEnd = curs.position();
2207 			while (1) {
2208 				curs = doSearch(theDoc->textDoc(), searchText, regex, flags, rangeStart, rangeEnd);
2209 				if (curs.isNull())
2210 					break;
2211 				int blockStart = curs.block().position();
2212 				results.append(SearchResult(theDoc, curs.blockNumber() + 1,
2213 								curs.selectionStart() - blockStart, curs.selectionEnd() - blockStart));
2214 				if ((flags & QTextDocument::FindBackward) != 0)
2215 					rangeEnd = curs.selectionStart();
2216 				else
2217 					rangeStart = curs.selectionEnd();
2218 			}
2219 
2220 			if (settings.value("searchAllFiles").toBool() == false)
2221 				break;
2222 			// go to next document
2223 		next_doc:
2224 			if (docList[docListIndex] == theDoc)
2225 				docListIndex++;
2226 			if (docListIndex == docList.count())
2227 				break;
2228 			theDoc = docList[docListIndex];
2229 			if (theDoc == this)
2230 				goto next_doc;
2231 			singleFile = false;
2232 		}
2233 
2234 		if (results.count() == 0) {
2235 			qApp->beep();
2236 			statusBar()->showMessage(tr("Not found"), kStatusMessageDuration);
2237 		}
2238 		else {
2239 			SearchResults::presentResults(searchText, results, this, singleFile);
2240 			statusBar()->showMessage(tr("Found %n occurrence(s)", "", results.count()), kStatusMessageDuration);
2241 		}
2242 	}
2243 	else {
2244 		QTextCursor	curs = textEdit->textCursor();
2245 		if (settings.value("searchSelection").toBool() && curs.hasSelection()) {
2246 			int rangeStart = curs.selectionStart();
2247 			int rangeEnd = curs.selectionEnd();
2248 			curs = doSearch(textEdit->document(), searchText, regex, flags, rangeStart, rangeEnd);
2249 		}
2250 		else {
2251 			if ((flags & QTextDocument::FindBackward) != 0) {
2252 				int rangeStart = 0;
2253 				int rangeEnd = curs.selectionStart();
2254 				curs = doSearch(textEdit->document(), searchText, regex, flags, rangeStart, rangeEnd);
2255 				if (curs.isNull() && settings.value("searchWrap").toBool()) {
2256 					curs = QTextCursor(textEdit->document());
2257 					curs.movePosition(QTextCursor::End);
2258 					curs = doSearch(textEdit->document(), searchText, regex, flags, 0, curs.position());
2259 				}
2260 			}
2261 			else {
2262 				int rangeStart = curs.selectionEnd();
2263 				curs.movePosition(QTextCursor::End);
2264 				int rangeEnd = curs.position();
2265 				curs = doSearch(textEdit->document(), searchText, regex, flags, rangeStart, rangeEnd);
2266 				if (curs.isNull() && settings.value("searchWrap").toBool())
2267 					curs = doSearch(textEdit->document(), searchText, regex, flags, 0, rangeEnd);
2268 			}
2269 		}
2270 
2271 		if (curs.isNull()) {
2272 			qApp->beep();
2273 			statusBar()->showMessage(tr("Not found"), kStatusMessageDuration);
2274 		}
2275 		else
2276 			textEdit->setTextCursor(curs);
2277 	}
2278 
2279 	if (regex != NULL)
2280 		delete regex;
2281 }
2282 
doReplaceAgain()2283 void TeXDocument::doReplaceAgain()
2284 {
2285 	doReplace(ReplaceDialog::ReplaceOne);
2286 }
2287 
doReplace(ReplaceDialog::DialogCode mode)2288 void TeXDocument::doReplace(ReplaceDialog::DialogCode mode)
2289 {
2290 	QSETTINGS_OBJECT(settings);
2291 
2292 	QString	searchText = settings.value("searchText").toString();
2293 	if (searchText.isEmpty())
2294 		return;
2295 
2296 	QTextDocument::FindFlags flags = (QTextDocument::FindFlags)settings.value("searchFlags").toInt();
2297 
2298 	QRegExp	*regex = NULL;
2299 	if (settings.value("searchRegex").toBool()) {
2300 		regex = new QRegExp(searchText, ((flags & QTextDocument::FindCaseSensitively) != 0)
2301 										? Qt::CaseSensitive : Qt::CaseInsensitive);
2302 		if (!regex->isValid()) {
2303 			qApp->beep();
2304 			statusBar()->showMessage(tr("Invalid regular expression"), kStatusMessageDuration);
2305 			delete regex;
2306 			return;
2307 		}
2308 	}
2309 
2310 	QString	replacement = settings.value("replaceText").toString();
2311 	if (regex != NULL) {
2312 		QRegExp escapedChar("\\\\([nt\\\\]|x([0-9A-Fa-f]{4}))");
2313 		int index = -1;
2314 		while ((index = replacement.indexOf(escapedChar, index + 1)) >= 0) {
2315 			QChar ch;
2316 			if (escapedChar.cap(1).length() == 1) {
2317 				// single-char escape code newline/tab/backslash
2318 				ch = escapedChar.cap(1)[0];
2319 				switch (ch.unicode()) {
2320 					case 'n':
2321 						ch = '\n';
2322 						break;
2323 					case 't':
2324 						ch = '\t';
2325 						break;
2326 					case '\\':
2327 						ch = '\\';
2328 						break;
2329 					default:
2330 						// should not happen!
2331 						break;
2332 				}
2333 			}
2334 			else {
2335 				// Unicode char number \xHHHH
2336 				bool ok;
2337 				ch = (QChar)escapedChar.cap(2).toUInt(&ok, 16);
2338 			}
2339 			replacement.replace(index, escapedChar.matchedLength(), ch);
2340 		}
2341 	}
2342 
2343 	bool allFiles = (mode == ReplaceDialog::ReplaceAll) && settings.value("searchAllFiles").toBool();
2344 
2345 	bool searchWrap = settings.value("searchWrap").toBool();
2346 	bool searchSel = settings.value("searchSelection").toBool();
2347 
2348 	int rangeStart, rangeEnd;
2349 	QTextCursor searchRange = textCursor();
2350 	if (allFiles) {
2351 		searchRange.select(QTextCursor::Document);
2352 		rangeStart = searchRange.selectionStart();
2353 		rangeEnd = searchRange.selectionEnd();
2354 	}
2355 	else if (searchSel) {
2356 		rangeStart = searchRange.selectionStart();
2357 		rangeEnd = searchRange.selectionEnd();
2358 	}
2359 	else {
2360 		// Note: searchWrap is handled separately below
2361 		if ((flags & QTextDocument::FindBackward) != 0) {
2362 			rangeStart = 0;
2363 			rangeEnd = searchRange.selectionEnd();
2364 		}
2365 		else {
2366 			rangeStart = searchRange.selectionStart();
2367 			searchRange.select(QTextCursor::Document);
2368 			rangeEnd = searchRange.selectionEnd();
2369 		}
2370 	}
2371 
2372 	if (mode == ReplaceDialog::ReplaceOne) {
2373 		QTextCursor curs = doSearch(textEdit->document(), searchText, regex, flags, rangeStart, rangeEnd);
2374 		if (curs.isNull() && searchWrap) {
2375 			// If we haven't found anything and wrapping is enabled, try again
2376 			// with a "wrapped" search range
2377 			if ((flags & QTextDocument::FindBackward) != 0) {
2378 				rangeStart = rangeEnd;
2379 				searchRange.select(QTextCursor::Document);
2380 				rangeEnd = searchRange.selectionEnd();
2381 			}
2382 			else {
2383 				rangeEnd = rangeStart;
2384 				rangeStart = 0;
2385 			}
2386 			curs = doSearch(textEdit->document(), searchText, regex, flags, rangeStart, rangeEnd);
2387 		}
2388 		if (curs.isNull()) {
2389 			qApp->beep();
2390 			statusBar()->showMessage(tr("Not found"), kStatusMessageDuration);
2391 		}
2392 		else {
2393 			// do replacement
2394 			QString target;
2395 			if (regex != NULL)
2396 				target = textEdit->document()->toPlainText()
2397 							.mid(curs.selectionStart(), curs.selectionEnd() - curs.selectionStart()).replace(*regex, replacement);
2398 			else
2399 				target = replacement;
2400 			curs.insertText(target);
2401 			textEdit->setTextCursor(curs);
2402 		}
2403 	}
2404 	else if (mode == ReplaceDialog::ReplaceAll) {
2405 		if (allFiles) {
2406 			int replacements = 0;
2407 			foreach (TeXDocument* doc, docList)
2408 				replacements += doc->doReplaceAll(searchText, regex, replacement, flags);
2409 			QString numOccurrences = tr("%n occurrence(s)", "", replacements);
2410 			QString numDocuments = tr("%n documents", "", docList.count());
2411 			QString message = tr("Replaced %1 in %2").arg(numOccurrences).arg(numDocuments);
2412 			statusBar()->showMessage(message, kStatusMessageDuration);
2413 		}
2414 		else {
2415 			if (!searchSel) {
2416 				// If we are not searching within a selection, we implicitly
2417 				// search the whole document with ReplaceAll
2418 				searchRange.select(QTextCursor::Document);
2419 				rangeStart = searchRange.selectionStart();
2420 				rangeEnd = searchRange.selectionEnd();
2421 			}
2422 			int replacements = doReplaceAll(searchText, regex, replacement, flags, rangeStart, rangeEnd);
2423 			statusBar()->showMessage(tr("Replaced %n occurrence(s)", "", replacements), kStatusMessageDuration);
2424 		}
2425 	}
2426 
2427 	if (regex != NULL)
2428 		delete regex;
2429 }
2430 
doReplaceAll(const QString & searchText,QRegExp * regex,const QString & replacement,QTextDocument::FindFlags flags,int rangeStart,int rangeEnd)2431 int TeXDocument::doReplaceAll(const QString& searchText, QRegExp* regex, const QString& replacement,
2432 								QTextDocument::FindFlags flags, int rangeStart, int rangeEnd)
2433 {
2434 	QTextCursor searchRange = textCursor();
2435 	searchRange.select(QTextCursor::Document);
2436 	if (rangeStart < 0)
2437 		rangeStart = searchRange.selectionStart();
2438 	if (rangeEnd < 0)
2439 		rangeEnd = searchRange.selectionEnd();
2440 
2441 	int replacements = 0;
2442 	bool first = true;
2443 	while (1) {
2444 		QTextCursor curs = doSearch(textEdit->document(), searchText, regex, flags, rangeStart, rangeEnd);
2445 		if (curs.isNull()) {
2446 			if (!first)
2447 				searchRange.endEditBlock();
2448 			break;
2449 		}
2450 		if (first) {
2451 			searchRange.beginEditBlock();
2452 			first = false;
2453 		}
2454 		QString target;
2455 		int oldLen = curs.selectionEnd() - curs.selectionStart();
2456 		if (regex != NULL)
2457 			target = textEdit->document()->toPlainText().mid(curs.selectionStart(), oldLen).replace(*regex, replacement);
2458 		else
2459 			target = replacement;
2460 		int newLen = target.length();
2461 		if ((flags & QTextDocument::FindBackward) != 0)
2462 			rangeEnd = curs.selectionStart();
2463 		else {
2464 			rangeStart = curs.selectionEnd() - oldLen + newLen;
2465 			rangeEnd += newLen - oldLen;
2466 		}
2467 		searchRange.setPosition(curs.selectionStart());
2468 		searchRange.setPosition(curs.selectionEnd(), QTextCursor::KeepAnchor);
2469 		searchRange.insertText(target);
2470 		++replacements;
2471 	}
2472 	if (!first) {
2473 		searchRange.setPosition(rangeStart);
2474 		textEdit->setTextCursor(searchRange);
2475 	}
2476 	return replacements;
2477 }
2478 
doSearch(QTextDocument * theDoc,const QString & searchText,const QRegExp * regex,QTextDocument::FindFlags flags,int s,int e)2479 QTextCursor TeXDocument::doSearch(QTextDocument *theDoc, const QString& searchText, const QRegExp *regex, QTextDocument::FindFlags flags, int s, int e)
2480 {
2481 	QTextCursor curs;
2482 	const QString& docText = theDoc->toPlainText();
2483 
2484 	if ((flags & QTextDocument::FindBackward) != 0) {
2485 		if (regex != NULL) {
2486 			// this doesn't seem to match \n or even \x2029 for newline
2487 			// curs = theDoc->find(*regex, e, flags);
2488 			int offset = regex->lastIndexIn(docText, e, QRegExp::CaretAtZero);
2489 			while (offset >= s && offset + regex->matchedLength() > e)
2490 				offset = regex->lastIndexIn(docText, offset - 1, QRegExp::CaretAtZero);
2491 			if (offset >= s) {
2492 				curs = QTextCursor(theDoc);
2493 				curs.setPosition(offset);
2494 				curs.setPosition(offset + regex->matchedLength(), QTextCursor::KeepAnchor);
2495 			}
2496 		}
2497 		else {
2498 			curs = theDoc->find(searchText, e, flags);
2499 			if (!curs.isNull()) {
2500 				if (curs.selectionEnd() > e)
2501 					curs = theDoc->find(searchText, curs, flags);
2502 				if (curs.selectionStart() < s)
2503 					curs = QTextCursor();
2504 			}
2505 		}
2506 	}
2507 	else {
2508 		if (regex != NULL) {
2509 			// this doesn't seem to match \n or even \x2029 for newline
2510 			// curs = theDoc->find(*regex, s, flags);
2511 			int offset = regex->indexIn(docText, s, QRegExp::CaretAtZero);
2512 			if (offset >= 0) {
2513 				curs = QTextCursor(theDoc);
2514 				curs.setPosition(offset);
2515 				curs.setPosition(offset + regex->matchedLength(), QTextCursor::KeepAnchor);
2516 			}
2517 		}
2518 		else {
2519 			curs = theDoc->find(searchText, s, flags);
2520 		}
2521 		if (curs.selectionEnd() > e)
2522 			curs = QTextCursor();
2523 	}
2524 	return curs;
2525 }
2526 
copyToFind()2527 void TeXDocument::copyToFind()
2528 {
2529 	if (textEdit->textCursor().hasSelection()) {
2530 		QString searchText = textEdit->textCursor().selectedText();
2531 		searchText.replace(QString(0x2029), "\n");
2532 		QSETTINGS_OBJECT(settings);
2533 		// Note: To search for multi-line strings, we currently need regex
2534 		// enabled (since we only have a single search line). If it was not
2535 		// enabled, we also need to ensure that the replaceText is escaped
2536 		// properly
2537 		bool isMultiLine = searchText.contains("\n");
2538 		if (isMultiLine && !settings.value("searchRegex").toBool()) {
2539 			settings.setValue("searchRegex", true);
2540 			settings.setValue("replaceText", QRegExp::escape(settings.value("replaceText").toString()));
2541 		}
2542 		if (settings.value("searchRegex").toBool()) {
2543 			if (isMultiLine)
2544 				settings.setValue("searchText", QRegExp::escape(searchText).replace("\n", "\\n"));
2545 			else
2546 				settings.setValue("searchText", QRegExp::escape(searchText));
2547 		}
2548 		else
2549 			settings.setValue("searchText", searchText);
2550 	}
2551 }
2552 
copyToReplace()2553 void TeXDocument::copyToReplace()
2554 {
2555 	if (textEdit->textCursor().hasSelection()) {
2556 		QString replaceText = textEdit->textCursor().selectedText();
2557 		replaceText.replace(QString(0x2029), "\n");
2558 		QSETTINGS_OBJECT(settings);
2559 		// Note: To do multi-line replacements, we currently need regex enabled
2560 		// (since we only have a single replace line). If it was not enabled, we
2561 		// also need to ensure that the searchText is escaped properly
2562 		bool isMultiLine = replaceText.contains("\n");
2563 		if (isMultiLine && !settings.value("searchRegex").toBool()) {
2564 			settings.setValue("searchRegex", true);
2565 			settings.setValue("searchText", QRegExp::escape(settings.value("searchText").toString()));
2566 		}
2567 		if (settings.value("searchRegex").toBool()) {
2568 			if (isMultiLine)
2569 				settings.setValue("replaceText", QRegExp::escape(replaceText).replace("\n", "\\n"));
2570 			else
2571 				settings.setValue("replaceText", QRegExp::escape(replaceText));
2572 		}
2573 		else
2574 			settings.setValue("replaceText", replaceText);
2575 	}
2576 }
2577 
findSelection()2578 void TeXDocument::findSelection()
2579 {
2580 	copyToFind();
2581 	doFindAgain();
2582 }
2583 
showSelection()2584 void TeXDocument::showSelection()
2585 {
2586 	int oldScrollValue = -1;
2587 	if (textEdit->verticalScrollBar() != NULL)
2588 		oldScrollValue = textEdit->verticalScrollBar()->value();
2589 	textEdit->ensureCursorVisible();
2590 	maybeCenterSelection(oldScrollValue);
2591 }
2592 
zoomToLeft(QWidget * otherWindow)2593 void TeXDocument::zoomToLeft(QWidget *otherWindow)
2594 {
2595 	QDesktopWidget *desktop = QApplication::desktop();
2596 	QRect screenRect = desktop->availableGeometry(otherWindow == NULL ? this : otherWindow);
2597 	screenRect.setTop(screenRect.top() + 22);
2598 	screenRect.setLeft(screenRect.left() + 1);
2599 	screenRect.setBottom(screenRect.bottom() - 1);
2600 	screenRect.setRight((screenRect.left() + screenRect.right()) / 2 - 1);
2601 	setGeometry(screenRect);
2602 }
2603 
typeset()2604 void TeXDocument::typeset()
2605 {
2606 	if (process)
2607 		return;	// this shouldn't happen if we disable the command at the right time
2608 
2609 	if (isUntitled || textEdit->document()->isModified())
2610 		if (!save()) {
2611 			statusBar()->showMessage(tr("Cannot process unsaved document"), kStatusMessageDuration);
2612 			return;
2613 		}
2614 
2615 	findRootFilePath();
2616 	if (!saveFilesHavingRoot(rootFilePath))
2617 		return;
2618 
2619 	QFileInfo fileInfo(rootFilePath);
2620 	if (!fileInfo.isReadable()) {
2621 		statusBar()->showMessage(tr("Root document %1 is not readable").arg(rootFilePath), kStatusMessageDuration);
2622 		return;
2623 	}
2624 
2625 	Engine e = TWApp::instance()->getNamedEngine(engine->currentText());
2626 	if (e.program() == "") {
2627 		statusBar()->showMessage(tr("%1 is not properly configured").arg(engine->currentText()), kStatusMessageDuration);
2628 		return;
2629 	}
2630 
2631 	process = new QProcess(this);
2632 	updateTypesettingAction();
2633 
2634 	QString workingDir = fileInfo.canonicalPath();	// Note that fileInfo refers to the root file
2635 #if defined(Q_OS_WIN)
2636 	// files in the root directory of the current drive have to be handled specially
2637 	// because QFileInfo::canonicalPath() returns a path without trailing slash
2638 	// (i.e., a bare drive letter)
2639 	if (workingDir.length() == 2 && workingDir.endsWith(':'))
2640 		workingDir.append('/');
2641 #endif
2642 	process->setWorkingDirectory(workingDir);
2643 
2644 	QStringList env = QProcess::systemEnvironment();
2645 	QStringList binPaths = TWApp::instance()->getBinaryPaths(env);
2646 
2647 	QString exeFilePath = TWApp::instance()->findProgram(e.program(), binPaths);
2648 
2649 #if !defined(Q_OS_DARWIN) // not supported on OS X yet :(
2650 	// Add a (customized) TEXEDIT environment variable
2651 	env << QString("TEXEDIT=%1 --position=%d %s").arg(QCoreApplication::applicationFilePath());
2652 
2653 	#if defined(Q_OS_WIN) // MiKTeX apparently uses it's own variable
2654 	env << QString("MIKTEX_EDITOR=%1 --position=%l \"%f\"").arg(QCoreApplication::applicationFilePath());
2655 	#endif
2656 #endif
2657 
2658 	if (!exeFilePath.isEmpty()) {
2659 		QStringList args = e.arguments();
2660 
2661 		// for old MikTeX versions: delete $synctexoption if it causes an error
2662 		static bool checkedForSynctex = false;
2663 		static bool synctexSupported = true;
2664 		if (!checkedForSynctex) {
2665 			QString pdftex = TWApp::instance()->findProgram("pdftex", binPaths);
2666 			if (!pdftex.isEmpty()) {
2667 				int result = QProcess::execute(pdftex, QStringList() << "-synctex=1" << "-version");
2668 				synctexSupported = (result == 0);
2669 			}
2670 			checkedForSynctex = true;
2671 		}
2672 		if (!synctexSupported)
2673 			args.removeAll("$synctexoption");
2674 
2675 		args.replaceInStrings("$synctexoption", "-synctex=1");
2676 		args.replaceInStrings("$fullname", fileInfo.fileName());
2677 		args.replaceInStrings("$basename", fileInfo.completeBaseName());
2678 		args.replaceInStrings("$suffix", fileInfo.suffix());
2679 		args.replaceInStrings("$directory", fileInfo.absoluteDir().absolutePath());
2680 
2681 		textEdit_console->clear();
2682 		if (consoleTabs->isHidden()) {
2683 			keepConsoleOpen = false;
2684 			showConsole();
2685 		}
2686 		else {
2687 			inputLine->show();
2688 		}
2689 		// ensure the window is visible - otherwise we can't see the output
2690 		// panel (and the typeset process appears to hang in case of an error)
2691 		consoleTabs->setCurrentIndex(0);
2692 		raise();
2693 
2694 		inputLine->setFocus(Qt::OtherFocusReason);
2695 		showPdfWhenFinished = e.showPdf();
2696 		userInterrupt = false;
2697 
2698 		process->setEnvironment(env);
2699 		process->setProcessChannelMode(QProcess::MergedChannels);
2700 
2701 		connect(process, SIGNAL(readyReadStandardOutput()), this, SLOT(processStandardOutput()));
2702 		connect(process, SIGNAL(error(QProcess::ProcessError)), this, SLOT(processError(QProcess::ProcessError)));
2703 		connect(process, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(processFinished(int, QProcess::ExitStatus)));
2704 
2705 		QString pdfName;
2706 		if (getPreviewFileName(pdfName))
2707 			oldPdfTime = QFileInfo(pdfName).lastModified();
2708 		else
2709 			oldPdfTime = QDateTime();
2710 
2711 		// Stop watching the pdf document while it is being changed to avoid
2712 		// interference
2713 		if (pdfDoc && pdfDoc->widget())
2714 			pdfDoc->widget()->setWatchForDocumentChangesOnDisk(false);
2715 
2716 		process->start(exeFilePath, args);
2717 	}
2718 	else {
2719 		process->deleteLater();
2720 		process = NULL;
2721 		QMessageBox msgBox(QMessageBox::Critical, tr("Unable to execute %1").arg(e.name()),
2722 							  "<p>" + tr("The program \"%1\" was not found.").arg(e.program()) + "</p>" +
2723 #if defined(Q_OS_WIN)
2724 							  "<p>" + tr("You need a <b>TeX distribution</b> like <a href=\"http://tug.org/texlive/\">TeX Live</a> or <a href=\"http://miktex.org/\">MiKTeX</a> installed on your system to typeset your document.") + "</p>" +
2725 #elif defined(Q_OS_DARWIN)
2726 							  "<p>" + tr("You need a <b>TeX distribution</b> like <a href=\"http://www.tug.org/mactex/\">MacTeX</a> installed on your system to typeset your document.") + "</p>" +
2727 #else // defined(Q_OS_UNIX) && !defined(Q_OS_DARWIN)
2728 							  "<p>" + tr("You need a <b>TeX distribution</b> like <a href=\"http://tug.org/texlive/\">TeX Live</a> installed on your system to typeset your document. On most systems such a TeX distribution is available as prebuilt package.") + "</p>" +
2729 #endif
2730 							  "<p>" + tr("When a TeX distribution is installed you may need to tell TeXworks where to find it in Edit -> Preferences -> Typesetting.") + "</p>",
2731 							  QMessageBox::Cancel, this);
2732 		msgBox.setDetailedText(
2733 							  tr("Searched in directories:") + "\n" +
2734 							  " * " + binPaths.join("\n * ") + "\n" +
2735 							  tr("Check the configuration of the %1 tool and the path settings in the Preferences dialog.").arg(e.name()));
2736 		msgBox.exec();
2737 		updateTypesettingAction();
2738 	}
2739 }
2740 
interrupt()2741 void TeXDocument::interrupt()
2742 {
2743 	if (process != NULL) {
2744 		userInterrupt = true;
2745 		process->kill();
2746 
2747 		// Start watching for changes in the pdf (again)
2748 		if (pdfDoc && pdfDoc->widget())
2749 			pdfDoc->widget()->setWatchForDocumentChangesOnDisk(true);
2750 	}
2751 }
2752 
updateTypesettingAction()2753 void TeXDocument::updateTypesettingAction()
2754 {
2755 	if (process == NULL) {
2756 		disconnect(actionTypeset, SIGNAL(triggered()), this, SLOT(interrupt()));
2757 		actionTypeset->setIcon(QIcon(":/images/images/runtool.png"));
2758 		actionTypeset->setText(tr("Typeset"));
2759 		connect(actionTypeset, SIGNAL(triggered()), this, SLOT(typeset()));
2760 		if (pdfDoc != NULL)
2761 			pdfDoc->updateTypesettingAction(false);
2762 	}
2763 	else {
2764 		disconnect(actionTypeset, SIGNAL(triggered()), this, SLOT(typeset()));
2765 		actionTypeset->setIcon(QIcon(":/images/tango/process-stop.png"));
2766 		actionTypeset->setText(tr("Abort typesetting"));
2767 		connect(actionTypeset, SIGNAL(triggered()), this, SLOT(interrupt()));
2768 		if (pdfDoc != NULL)
2769 			pdfDoc->updateTypesettingAction(true);
2770 	}
2771 }
2772 
processStandardOutput()2773 void TeXDocument::processStandardOutput()
2774 {
2775 	QByteArray bytes = process->readAllStandardOutput();
2776 	QTextCursor cursor(textEdit_console->document());
2777 	cursor.select(QTextCursor::Document);
2778 	cursor.setPosition(cursor.selectionEnd());
2779 	cursor.insertText(QString::fromUtf8(bytes));
2780 	textEdit_console->setTextCursor(cursor);
2781 }
2782 
processError(QProcess::ProcessError)2783 void TeXDocument::processError(QProcess::ProcessError /*error*/)
2784 {
2785 	if (userInterrupt)
2786 		textEdit_console->append(tr("Process interrupted by user"));
2787 	else
2788 		textEdit_console->append(process->errorString());
2789 	process->kill();
2790 	process->deleteLater();
2791 	process = NULL;
2792 	inputLine->hide();
2793 	updateTypesettingAction();
2794 
2795 	// Start watching for changes in the pdf (again)
2796 	if (pdfDoc && pdfDoc->widget())
2797 		pdfDoc->widget()->setWatchForDocumentChangesOnDisk(true);
2798 }
2799 
processFinished(int exitCode,QProcess::ExitStatus exitStatus)2800 void TeXDocument::processFinished(int exitCode, QProcess::ExitStatus exitStatus)
2801 {
2802 	// Start watching for changes in the pdf (again)
2803 	if (pdfDoc && pdfDoc->widget())
2804 		pdfDoc->widget()->setWatchForDocumentChangesOnDisk(true);
2805 
2806 	if (exitStatus != QProcess::CrashExit) {
2807 		QString pdfName;
2808 		if (getPreviewFileName(pdfName)) {
2809 			actionGo_to_Preview->setEnabled(true);
2810 			if (QFileInfo(pdfName).lastModified() != oldPdfTime) {
2811 				// only open/refresh the PDF if it was changed by the typeset process
2812 				if (pdfDoc == NULL || pdfName != pdfDoc->fileName()) {
2813 					if (showPdfWhenFinished && openPdfIfAvailable(true))
2814 						pdfDoc->selectWindow();
2815 				}
2816 				else {
2817 					pdfDoc->reload(); // always reload if it is loaded, we don't want a stale window
2818 					if (showPdfWhenFinished)
2819 						pdfDoc->selectWindow();
2820 				}
2821 			}
2822 		}
2823 		else
2824 			actionGo_to_Preview->setEnabled(true);
2825 	}
2826 
2827 	executeAfterTypesetHooks();
2828 
2829 	QSETTINGS_OBJECT(settings);
2830 
2831 	bool shouldHideConsole = false;
2832 	QVariant hideConsoleSetting = settings.value("autoHideConsole", kDefault_HideConsole);
2833 	// Backwards compatibility to Tw 0.4.0 and before
2834 	if (hideConsoleSetting.toString() == "true" || hideConsoleSetting.toString() == "false")
2835 		hideConsoleSetting = (hideConsoleSetting.toBool() ? kDefault_HideConsole : 0);
2836 
2837 	switch(hideConsoleSetting.toInt()) {
2838 		case 0: // Never hide console
2839 			shouldHideConsole = false;
2840 			break;
2841 		case 1: // Hide console automatically
2842 			shouldHideConsole = (!keepConsoleOpen && exitCode == 0 && exitStatus != QProcess::CrashExit);
2843 			break;
2844 		case 2: // Always hide console on success
2845 			shouldHideConsole = (exitCode == 0 && exitStatus != QProcess::CrashExit);
2846 			break;
2847 		default: // Should never happen
2848 			;
2849 	}
2850 
2851 	if (shouldHideConsole)
2852 		hideConsole();
2853 	else
2854 		inputLine->hide();
2855 
2856 	if (process)
2857 		process->deleteLater();
2858 	process = NULL;
2859 	updateTypesettingAction();
2860 }
2861 
executeAfterTypesetHooks()2862 void TeXDocument::executeAfterTypesetHooks()
2863 {
2864 	TWScriptManager * scriptManager = TWApp::instance()->getScriptManager();
2865 
2866 	for (int i = consoleTabs->count() - 1; i > 0; --i)
2867 		consoleTabs->removeTab(i);
2868 
2869 	foreach (TWScript *s, scriptManager->getHookScripts("AfterTypeset")) {
2870 		QVariant result;
2871 		bool success = s->run(this, result);
2872 		if (success && !result.isNull()) {
2873 			QString res = result.toString();
2874 			if (res.startsWith("<html>", Qt::CaseInsensitive)) {
2875 				QTextBrowser *browser = new QTextBrowser(this);
2876 				browser->setOpenLinks(false);
2877 				connect(browser, SIGNAL(anchorClicked(const QUrl&)), this, SLOT(anchorClicked(const QUrl&)));
2878 				browser->setHtml(res);
2879 				consoleTabs->addTab(browser, s->getTitle());
2880 			}
2881 			else {
2882 				QTextEdit *textEdit = new QTextEdit(this);
2883 				textEdit->setPlainText(res);
2884 				textEdit->setReadOnly(true);
2885 				consoleTabs->addTab(textEdit, s->getTitle());
2886 			}
2887 		}
2888 	}
2889 }
2890 
anchorClicked(const QUrl & url)2891 void TeXDocument::anchorClicked(const QUrl& url)
2892 {
2893 	if (url.scheme() == "texworks") {
2894 		int line = 0;
2895 		if (url.hasFragment()) {
2896 			line = url.fragment().toLong();
2897 		}
2898 		TeXDocument * target = openDocument(QFileInfo(getRootFilePath()).absoluteDir().filePath(url.path()), true, true, line);
2899 		if (target)
2900 			target->textEdit->setFocus(Qt::OtherFocusReason);
2901 	}
2902 	else {
2903 		TWApp::instance()->openUrl(url);
2904 	}
2905 }
2906 
2907 // showConsole() and hideConsole() are used internally to update the visibility;
2908 // they must NOT change the keepConsoleOpen setting that records user choice
showConsole()2909 void TeXDocument::showConsole()
2910 {
2911 	consoleTabs->show();
2912 	if (process != NULL)
2913 		inputLine->show();
2914 	actionShow_Hide_Console->setText(tr("Hide Console Output"));
2915 }
2916 
hideConsole()2917 void TeXDocument::hideConsole()
2918 {
2919 	consoleTabs->hide();
2920 	inputLine->hide();
2921 	actionShow_Hide_Console->setText(tr("Show Console Output"));
2922 }
2923 
2924 // this is connected to the user command, so remember the choice
2925 // for when typesetting finishes
toggleConsoleVisibility()2926 void TeXDocument::toggleConsoleVisibility()
2927 {
2928 	if (consoleTabs->isVisible()) {
2929 		hideConsole();
2930 		keepConsoleOpen = false;
2931 	}
2932 	else {
2933 		showConsole();
2934 		keepConsoleOpen = true;
2935 	}
2936 }
2937 
acceptInputLine()2938 void TeXDocument::acceptInputLine()
2939 {
2940 	if (process != NULL) {
2941 		QString	str = inputLine->text();
2942 		QTextCursor	curs(textEdit_console->document());
2943 		curs.setPosition(textEdit_console->toPlainText().length());
2944 		textEdit_console->setTextCursor(curs);
2945 		QTextCharFormat	consoleFormat = textEdit_console->currentCharFormat();
2946 		QTextCharFormat inputFormat(consoleFormat);
2947 		inputFormat.setForeground(inputLine->palette().text());
2948 		str.append("\n");
2949 		textEdit_console->insertPlainText(str);
2950 		curs.movePosition(QTextCursor::PreviousCharacter);
2951 		curs.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor, str.length() - 1);
2952 		curs.setCharFormat(inputFormat);
2953 		process->write(str.toUtf8());
2954 		inputLine->clear();
2955 	}
2956 }
2957 
goToPreview()2958 void TeXDocument::goToPreview()
2959 {
2960 	if (pdfDoc != NULL)
2961 		pdfDoc->selectWindow();
2962 	else {
2963 		if (!openPdfIfAvailable(true)) {
2964 			// This should only fail if the user has done something sneaky like closing the
2965 			// preview window and then renaming the PDF file, since we opened the source
2966 			// and checked that it exists (otherwise Go to Preview would have been disabled).
2967 			// We could issue a status-bar warning here but it's a pretty obscure case...
2968 			// for now just disable the command.
2969 			actionGo_to_Preview->setEnabled(false);
2970 			actionSide_by_Side->setEnabled(false);
2971 		}
2972 	}
2973 }
2974 
syncClick(int lineNo,int col)2975 void TeXDocument::syncClick(int lineNo, int col)
2976 {
2977 	if (!isUntitled) {
2978 		// ensure that there is a pdf to receive our signal
2979 		goToPreview();
2980 		emit syncFromSource(curFile, lineNo, col, true);
2981 	}
2982 }
2983 
contentsChanged(int position,int,int)2984 void TeXDocument::contentsChanged(int position, int /*charsRemoved*/, int /*charsAdded*/)
2985 {
2986 	if (position < PEEK_LENGTH) {
2987 		int pos;
2988 		QTextCursor curs(textEdit->document());
2989 		// (begin|end)EditBlock() is a workaround for QTBUG-24718 that causes
2990 		// movePosition() to crash the program under some circumstances.
2991 		// Since we don't change any text in the edit block, it should be a noop
2992 		// in the context of undo/redo.
2993 		curs.beginEditBlock();
2994 		curs.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, PEEK_LENGTH);
2995 		curs.endEditBlock();
2996 
2997 		QString peekStr = curs.selectedText();
2998 
2999 		/* Search for engine specification */
3000 		QRegExp re("% *!TEX +(?:TS-)?program *= *([^\\x2029]+)\\x2029", Qt::CaseInsensitive);
3001 		pos = re.indexIn(peekStr);
3002 		if (pos > -1) {
3003 			QString name = re.cap(1).trimmed();
3004 			int index = engine->findText(name, Qt::MatchFixedString);
3005 			if (index > -1) {
3006 				if (index != engine->currentIndex()) {
3007 					engine->setCurrentIndex(index);
3008 					emit asyncFlashStatusBarMessage(tr("Set engine to \"%1\"").arg(engine->currentText()), kStatusMessageDuration);
3009 				}
3010 			}
3011 			else {
3012 				emit asyncFlashStatusBarMessage(tr("Engine \"%1\" not defined").arg(name), kStatusMessageDuration);
3013 			}
3014 		}
3015 
3016 		/* Search for encoding specification */
3017 		bool hasMetadata;
3018 		QString reqName;
3019 		QTextCodec *newCodec = scanForEncoding(peekStr, hasMetadata, reqName);
3020 		if (newCodec != NULL) {
3021 			codec = newCodec;
3022 			showEncodingSetting();
3023 		}
3024 
3025 		/* Search for spellcheck specification */
3026 		QRegExp reSpell("% *!TEX +spellcheck *= *([^\\x2029]+)\\x2029", Qt::CaseInsensitive);
3027 		pos = reSpell.indexIn(peekStr);
3028 		if (pos > -1) {
3029 			QString lang = reSpell.cap(1).trimmed();
3030 			setSpellcheckLanguage(lang);
3031 		}
3032 	}
3033 }
3034 
findRootFilePath()3035 void TeXDocument::findRootFilePath()
3036 {
3037 	if (isUntitled) {
3038 		rootFilePath = "";
3039 		return;
3040 	}
3041 	QFileInfo fileInfo(curFile);
3042 	QString rootName;
3043 	QTextCursor curs(textEdit->document());
3044 	curs.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, PEEK_LENGTH);
3045 	QString peekStr = curs.selectedText();
3046 	QRegExp re("% *!TEX +root *= *([^\\x2029]+)\\x2029", Qt::CaseInsensitive);
3047 	int pos = re.indexIn(peekStr);
3048 	if (pos > -1) {
3049 		rootName = re.cap(1).trimmed();
3050 		QFileInfo rootFileInfo(fileInfo.canonicalPath() + "/" + rootName);
3051 		if (rootFileInfo.exists())
3052 			rootFilePath = rootFileInfo.canonicalFilePath();
3053 		else
3054 			rootFilePath = rootFileInfo.filePath();
3055 	}
3056 	else
3057 		rootFilePath = fileInfo.canonicalFilePath();
3058 }
3059 
addTag(const QTextCursor & cursor,int level,const QString & text)3060 void TeXDocument::addTag(const QTextCursor& cursor, int level, const QString& text)
3061 {
3062 	int index = 0;
3063 	while (index < tags.size()) {
3064 		if (tags[index].cursor.selectionStart() > cursor.selectionStart())
3065 			break;
3066 		++index;
3067 	}
3068 	tags.insert(index, Tag(cursor, level, text));
3069 }
3070 
removeTags(int offset,int len)3071 int TeXDocument::removeTags(int offset, int len)
3072 {
3073 	int removed = 0;
3074 	for (int index = tags.count() - 1; index >= 0; --index) {
3075 		if (tags[index].cursor.selectionStart() < offset)
3076 			break;
3077 		if (tags[index].cursor.selectionStart() < offset + len) {
3078 			tags.removeAt(index);
3079 			++removed;
3080 		}
3081 	}
3082 	return removed;
3083 }
3084 
goToTag(int index)3085 void TeXDocument::goToTag(int index)
3086 {
3087 	if (index < tags.count()) {
3088 		textEdit->setTextCursor(tags[index].cursor);
3089 		textEdit->setFocus(Qt::OtherFocusReason);
3090 	}
3091 }
3092 
tagsChanged()3093 void TeXDocument::tagsChanged()
3094 {
3095 	if (deferTagListChanges)
3096 		tagListChanged = true;
3097 	else
3098 		emit tagListUpdated();
3099 }
3100 
removeAuxFiles()3101 void TeXDocument::removeAuxFiles()
3102 {
3103 	findRootFilePath();
3104 	if (rootFilePath.isEmpty())
3105 		return;
3106 
3107 	QFileInfo fileInfo(rootFilePath);
3108 	QString jobname = fileInfo.completeBaseName();
3109 	QDir dir(fileInfo.dir());
3110 
3111 	QStringList filterList = TWUtils::cleanupPatterns().split(QRegExp("\\s+"));
3112 	if (filterList.count() == 0)
3113 		return;
3114 	for (int i = 0; i < filterList.count(); ++i)
3115 		filterList[i].replace("$jobname", jobname);
3116 
3117 	dir.setNameFilters(filterList);
3118 	QStringList auxFileList = dir.entryList(QDir::Files | QDir::CaseSensitive, QDir::Name);
3119 	if (auxFileList.count() > 0)
3120 		ConfirmDelete::doConfirmDelete(dir, auxFileList);
3121 	else
3122 		(void)QMessageBox::information(this, tr("No files found"),
3123 									   tr("No auxiliary files associated with this document at the moment."));
3124 }
3125 
3126 #if defined(Q_OS_DARWIN)
3127 #define OPEN_FILE_IN_NEW_WINDOW	Qt::MoveAction // unmodified drag appears as MoveAction on Mac OS X
3128 #define INSERT_DOCUMENT_TEXT	Qt::CopyAction
3129 #define CREATE_INCLUDE_COMMAND	Qt::LinkAction
3130 #else
3131 #define OPEN_FILE_IN_NEW_WINDOW	Qt::CopyAction // ...but as CopyAction on X11
3132 #define INSERT_DOCUMENT_TEXT	Qt::MoveAction
3133 #define CREATE_INCLUDE_COMMAND	Qt::LinkAction
3134 #endif
3135 
dragEnterEvent(QDragEnterEvent * event)3136 void TeXDocument::dragEnterEvent(QDragEnterEvent *event)
3137 {
3138 	event->ignore();
3139 	if (event->mimeData()->hasUrls()) {
3140 		const QList<QUrl> urls = event->mimeData()->urls();
3141 		foreach (const QUrl& url, urls) {
3142 			if (url.scheme() == "file") {
3143 				event->acceptProposedAction();
3144 				break;
3145 			}
3146 		}
3147 	}
3148 }
3149 
dragMoveEvent(QDragMoveEvent * event)3150 void TeXDocument::dragMoveEvent(QDragMoveEvent *event)
3151 {
3152 	if (event->proposedAction() == INSERT_DOCUMENT_TEXT || event->proposedAction() == CREATE_INCLUDE_COMMAND) {
3153 		if (dragSavedCursor.isNull())
3154 			dragSavedCursor = textEdit->textCursor();
3155 		QTextCursor curs = textEdit->cursorForPosition(textEdit->mapFromGlobal(mapToGlobal(event->pos())));
3156 		textEdit->setTextCursor(curs);
3157 	}
3158 	else {
3159 		if (!dragSavedCursor.isNull()) {
3160 			textEdit->setTextCursor(dragSavedCursor);
3161 			dragSavedCursor = QTextCursor();
3162 		}
3163 	}
3164 	event->acceptProposedAction();
3165 }
3166 
dragLeaveEvent(QDragLeaveEvent * event)3167 void TeXDocument::dragLeaveEvent(QDragLeaveEvent *event)
3168 {
3169 	if (!dragSavedCursor.isNull()) {
3170 		textEdit->setTextCursor(dragSavedCursor);
3171 		dragSavedCursor = QTextCursor();
3172 	}
3173 	event->accept();
3174 }
3175 
dropEvent(QDropEvent * event)3176 void TeXDocument::dropEvent(QDropEvent *event)
3177 {
3178 	if (event->mimeData()->hasUrls()) {
3179 		Qt::DropAction action = event->proposedAction();
3180 		const QList<QUrl> urls = event->mimeData()->urls();
3181 		bool editBlockStarted = false;
3182 		QString text;
3183 		QTextCursor curs = textEdit->cursorForPosition(textEdit->mapFromGlobal(mapToGlobal(event->pos())));
3184 		foreach (const QUrl& url, urls) {
3185 			if (url.scheme() == "file") {
3186 				QString fileName = url.toLocalFile();
3187 				switch (action) {
3188 					case OPEN_FILE_IN_NEW_WINDOW:
3189 						if(!TWUtils::isImageFile(fileName)) {
3190 							TWApp::instance()->openFile(fileName);
3191 							break;
3192 						}
3193 						// for graphic files, fall through (there's no point in
3194 						// trying to open binary files as text
3195 
3196 					case INSERT_DOCUMENT_TEXT:
3197 						if (!TWUtils::isPDFfile(fileName) && !TWUtils::isImageFile(fileName) && !TWUtils::isPostscriptFile(fileName)) {
3198 							QTextCodec *codecUsed;
3199 							text = readFile(fileName, &codecUsed);
3200 							if (!text.isNull()) {
3201 								if (!editBlockStarted) {
3202 									curs.beginEditBlock();
3203 									editBlockStarted = true;
3204 								}
3205 								textEdit->setTextCursor(curs);
3206 								curs.insertText(text);
3207 							}
3208 							break;
3209 						}
3210 						// for graphic files, fall through -- behave the same as the "link" action
3211 
3212 					case CREATE_INCLUDE_COMMAND:
3213 						if (!editBlockStarted) {
3214 							curs.beginEditBlock();
3215 							editBlockStarted = true;
3216 						}
3217 						textEdit->setTextCursor(curs);
3218 						if (TWUtils::isPDFfile(fileName))
3219 							text = TWUtils::includePdfCommand();
3220 						else if (TWUtils::isImageFile(fileName))
3221 							text = TWUtils::includeImageCommand();
3222 						else if (TWUtils::isPostscriptFile(fileName))
3223 							text = TWUtils::includePostscriptCommand();
3224 						else
3225 							text = TWUtils::includeTextCommand();
3226 						curs.insertText(text.arg(fileName));
3227 						break;
3228 					default:
3229 						// do nothing
3230 						break;
3231 				}
3232 			}
3233 		}
3234 		if (editBlockStarted)
3235 			curs.endEditBlock();
3236 	}
3237 	dragSavedCursor = QTextCursor();
3238 	event->accept();
3239 }
3240 
detachPdf()3241 void TeXDocument::detachPdf()
3242 {
3243 	if (pdfDoc != NULL) {
3244 		disconnect(pdfDoc, SIGNAL(destroyed()), this, SLOT(pdfClosed()));
3245 		disconnect(this, SIGNAL(destroyed(QObject*)), pdfDoc, SLOT(texClosed(QObject*)));
3246 		pdfDoc = NULL;
3247 	}
3248 }
3249