1 /*
2 	This is part of TeXworks, an environment for working with TeX documents
3 	Copyright (C) 2007-2017  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 "TWApp.h"
23 #include "TWUtils.h"
24 #include "TeXDocument.h"
25 #include "PDFDocument.h"
26 #include "PrefsDialog.h"
27 #include "DefaultPrefs.h"
28 #include "TemplateDialog.h"
29 #include "TWSystemCmd.h"
30 
31 #include "TWVersion.h"
32 #include "ResourcesDialog.h"
33 #include "TWTextCodecs.h"
34 
35 #if defined(Q_OS_WIN)
36 #include "DefaultBinaryPathsWin.h"
37 #else
38 #include "DefaultBinaryPaths.h"
39 #endif
40 
41 #include <QMessageBox>
42 #include <QFileDialog>
43 #include <QString>
44 #include <QMenuBar>
45 #include <QMenu>
46 #include <QAction>
47 #include <QSettings>
48 #include <QStringList>
49 #include <QEvent>
50 #include <QKeyEvent>
51 #include <QKeySequence>
52 #include <QDesktopWidget>
53 #include <QTextCodec>
54 #include <QLocale>
55 #include <QTranslator>
56 #include <QUrl>
57 #include <QDesktopServices>
58 #include <QLibraryInfo>
59 
60 #if defined(HAVE_POPPLER_XPDF_HEADERS) && (defined(Q_OS_DARWIN) || defined(Q_OS_WIN))
61 #include "poppler-config.h"
62 #include "GlobalParams.h"
63 #endif
64 
65 #if defined(Q_OS_DARWIN)
66 #include <CoreServices/CoreServices.h>
67 #endif
68 
69 #if defined(Q_OS_WIN)
70 #include <windows.h>
71 #ifndef VER_SUITE_WH_SERVER /* not defined in my mingw system */
72 #define VER_SUITE_WH_SERVER 0x00008000
73 #endif
74 #endif
75 
76 #define SETUP_FILE_NAME "texworks-setup.ini"
77 
78 const int kDefaultMaxRecentFiles = 20;
79 
80 TWApp *TWApp::theAppInstance = NULL;
81 
82 const QEvent::Type TWDocumentOpenEvent::type = static_cast<QEvent::Type>(QEvent::registerEventType());
83 
84 
TWApp(int & argc,char ** argv)85 TWApp::TWApp(int &argc, char **argv)
86 	: ConfigurableApp(argc, argv)
87 	, defaultCodec(NULL)
88 	, binaryPaths(NULL)
89 	, defaultBinPaths(NULL)
90 	, engineList(NULL)
91 	, defaultEngineIndex(0)
92 	, scriptManager(NULL)
93 #if defined(Q_OS_WIN)
94 	, messageTargetWindow(NULL)
95 #endif
96 {
97 	init();
98 }
99 
~TWApp()100 TWApp::~TWApp()
101 {
102 	if (scriptManager) {
103 		scriptManager->saveDisabledList();
104 		delete scriptManager;
105 	}
106 }
107 
init()108 void TWApp::init()
109 {
110 	customTextCodecs << new MacCentralEurRomanCodec();
111 
112 	QIcon appIcon;
113 #if defined(Q_OS_UNIX) && !defined(Q_OS_DARWIN)
114 	// The Compiz window manager doesn't seem to support icons larger than
115 	// 128x128, so we add a suitable one first
116 	appIcon.addFile(":/images/images/TeXworks-128.png");
117 #endif
118 	appIcon.addFile(":/images/images/TeXworks.png");
119 	setWindowIcon(appIcon);
120 
121 	setOrganizationName("TUG");
122 	setOrganizationDomain("tug.org");
123 	setApplicationName(TEXWORKS_NAME);
124 
125 	// <Check for portable mode>
126 #if defined(Q_OS_DARWIN)
127 	QDir appDir(applicationDirPath() + "/../../.."); // move up to dir containing the .app package
128 #else
129 	QDir appDir(applicationDirPath());
130 #endif
131 	QDir iniPath(appDir.absolutePath());
132 	QDir libPath(appDir.absolutePath());
133 	if (appDir.exists(SETUP_FILE_NAME)) {
134 		QSettings portable(appDir.filePath(SETUP_FILE_NAME), QSettings::IniFormat);
135 		if (portable.contains("inipath")) {
136 			if (iniPath.cd(portable.value("inipath").toString())) {
137 				setSettingsFormat(QSettings::IniFormat);
138 				QSettings::setPath(QSettings::IniFormat, QSettings::UserScope, iniPath.absolutePath());
139 			}
140 		}
141 		if (portable.contains("libpath")) {
142 			if (libPath.cd(portable.value("libpath").toString())) {
143 				portableLibPath = libPath.absolutePath();
144 			}
145 		}
146 		if (portable.contains("defaultbinpaths")) {
147 			defaultBinPaths = new QStringList;
148 			*defaultBinPaths = portable.value("defaultbinpaths").toString().split(PATH_LIST_SEP, QString::SkipEmptyParts);
149 		}
150 	}
151 	QString envPath = QString::fromLocal8Bit(getenv("TW_INIPATH"));
152 	if (envPath != NULL && iniPath.cd(envPath)) {
153 		setSettingsFormat(QSettings::IniFormat);
154 		QSettings::setPath(QSettings::IniFormat, QSettings::UserScope, iniPath.absolutePath());
155 	}
156 	envPath = QString::fromLocal8Bit(getenv("TW_LIBPATH"));
157 	if (envPath != NULL && libPath.cd(envPath)) {
158 		portableLibPath = libPath.absolutePath();
159 	}
160 	// </Check for portable mode>
161 
162 #if defined(HAVE_POPPLER_XPDF_HEADERS) && (defined(Q_OS_DARWIN) || defined(Q_OS_WIN))
163 	// for Mac and Windows, support "local" poppler-data directory
164 	// (requires patched poppler-qt4 lib to be effective,
165 	// otherwise the GlobalParams gets overwritten when a
166 	// document is opened)
167 #if defined(Q_OS_DARWIN)
168 	QDir popplerDataDir(applicationDirPath() + "/../poppler-data");
169 #else
170 	QDir popplerDataDir(applicationDirPath() + "/poppler-data");
171 #endif
172 	if (popplerDataDir.exists()) {
173 		globalParams = new GlobalParams(popplerDataDir.canonicalPath().toUtf8().data());
174 	}
175 	else {
176 		globalParams = new GlobalParams();
177 	}
178 #endif
179 
180 	// Required for TWUtils::getLibraryPath()
181 	theAppInstance = this;
182 
183 	QSETTINGS_OBJECT(settings);
184 
185 	QString locale = settings.value("locale", QLocale::system().name()).toString();
186 	applyTranslation(locale);
187 
188 	recentFilesLimit = settings.value("maxRecentFiles", kDefaultMaxRecentFiles).toInt();
189 
190 	QString codecName = settings.value("defaultEncoding", "UTF-8").toString();
191 	defaultCodec = QTextCodec::codecForName(codecName.toLatin1());
192 	if (defaultCodec == NULL)
193 		defaultCodec = QTextCodec::codecForName("UTF-8");
194 
195 	TWUtils::readConfig();
196 
197 	scriptManager = new TWScriptManager;
198 
199 #if defined(Q_OS_DARWIN)
200 	setQuitOnLastWindowClosed(false);
201 	setAttribute(Qt::AA_DontShowIconsInMenus);
202 
203 	menuBar = new QMenuBar;
204 
205 	menuFile = menuBar->addMenu(tr("File"));
206 
207 	actionNew = new QAction(tr("New"), this);
208 	actionNew->setIcon(QIcon(":/images/tango/document-new.png"));
209 	menuFile->addAction(actionNew);
210 	connect(actionNew, SIGNAL(triggered()), this, SLOT(newFile()));
211 
212 	actionNew_from_Template = new QAction(tr("New from Template..."), this);
213 	menuFile->addAction(actionNew_from_Template);
214 	connect(actionNew_from_Template, SIGNAL(triggered()), this, SLOT(newFromTemplate()));
215 
216 	actionPreferences = new QAction(tr("Preferences..."), this);
217 	actionPreferences->setIcon(QIcon(":/images/tango/preferences-system.png"));
218 	actionPreferences->setMenuRole(QAction::PreferencesRole);
219 	menuFile->addAction(actionPreferences);
220 	connect(actionPreferences, SIGNAL(triggered()), this, SLOT(preferences()));
221 
222 	actionOpen = new QAction(tr("Open..."), this);
223 	actionOpen->setIcon(QIcon(":/images/tango/document-open.png"));
224 	menuFile->addAction(actionOpen);
225 	connect(actionOpen, SIGNAL(triggered()), this, SLOT(open()));
226 
227 	menuRecent = new QMenu(tr("Open Recent"));
228 	actionClear_Recent_Files = menuRecent->addAction(tr("Clear Recent Files"));
229 	actionClear_Recent_Files->setEnabled(false);
230 	connect(actionClear_Recent_Files, SIGNAL(triggered()), this, SLOT(clearRecentFiles()));
231 	updateRecentFileActions();
232 	menuFile->addMenu(menuRecent);
233 
234 	actionQuit = new QAction(tr("Quit TeXworks"), this);
235 	actionQuit->setMenuRole(QAction::QuitRole);
236 	menuFile->addAction(actionQuit);
237 	connect(actionQuit, SIGNAL(triggered()), this, SLOT(quit()));
238 
239 	menuHelp = menuBar->addMenu(tr("Help"));
240 
241 	homePageAction = new QAction(tr("Go to TeXworks home page"), this);
242 	menuHelp->addAction(homePageAction);
243 	connect(homePageAction, SIGNAL(triggered()), this, SLOT(goToHomePage()));
244 	mailingListAction = new QAction(tr("Email to the mailing list"), this);
245 	menuHelp->addAction(mailingListAction);
246 	connect(mailingListAction, SIGNAL(triggered()), this, SLOT(writeToMailingList()));
247 	QAction* sep = new QAction(this);
248 	sep->setSeparator(true);
249 	menuHelp->addAction(sep);
250 	aboutAction = new QAction(tr("About " TEXWORKS_NAME "..."), this);
251 	aboutAction->setMenuRole(QAction::AboutRole);
252 	menuHelp->addAction(aboutAction);
253 	connect(aboutAction, SIGNAL(triggered()), this, SLOT(about()));
254 
255 	TWUtils::insertHelpMenuItems(menuHelp);
256 
257 	connect(this, SIGNAL(updatedTranslators()), this, SLOT(changeLanguage()));
258 	changeLanguage();
259 #endif
260 }
261 
maybeQuit()262 void TWApp::maybeQuit()
263 {
264 #if defined(Q_OS_DARWIN)
265 	setQuitOnLastWindowClosed(true);
266 #endif
267 	closeAllWindows();
268 #if defined(Q_OS_DARWIN)
269 	setQuitOnLastWindowClosed(false);
270 #endif
271 }
272 
changeLanguage()273 void TWApp::changeLanguage()
274 {
275 #if defined(Q_OS_DARWIN)
276 	menuFile->setTitle(tr("File"));
277 	actionNew->setText(tr("New"));
278 	actionNew->setShortcut(QKeySequence(tr("Ctrl+N")));
279 	actionNew_from_Template->setText(tr("New from Template..."));
280 	actionNew_from_Template->setShortcut(QKeySequence(tr("Ctrl+Shift+N")));
281 	actionOpen->setText(tr("Open..."));
282 	actionOpen->setShortcut(QKeySequence(tr("Ctrl+O")));
283 	actionQuit->setText(tr("Quit TeXworks"));
284 	actionQuit->setShortcut(QKeySequence("Ctrl+Q"));
285 
286 	menuRecent->setTitle(tr("Open Recent"));
287 
288 	menuHelp->setTitle(tr("Help"));
289 	aboutAction->setText(tr("About " TEXWORKS_NAME "..."));
290 	homePageAction->setText(tr("Go to TeXworks home page"));
291 	mailingListAction->setText(tr("Email to the mailing list"));
292 	TWUtils::insertHelpMenuItems(menuHelp);
293 #endif
294 }
295 
about()296 void TWApp::about()
297 {
298 	QString aboutText = tr("<p>%1 is a simple environment for editing, typesetting, and previewing TeX documents.</p>").arg(TEXWORKS_NAME);
299 	aboutText += "<small>";
300 	aboutText += "<p>&#xA9; 2007-2017  Jonathan Kew, Stefan L&#xF6;ffler, Charlie Sharpsteen";
301 	if (TWUtils::isGitInfoAvailable())
302 		aboutText += tr("<br>Version %1 (%2) [r.%3, %4]").arg(TEXWORKS_VERSION).arg(TW_BUILD_ID_STR).arg(TWUtils::gitCommitHash()).arg(TWUtils::gitCommitDate().toLocalTime().toString(Qt::SystemLocaleShortDate));
303 	else
304 		aboutText += tr("<br>Version %1 (%2)").arg(TEXWORKS_VERSION).arg(TW_BUILD_ID_STR);
305 	aboutText += tr("<p>Distributed under the <a href=\"http://www.gnu.org/licenses/gpl-2.0.html\">GNU General Public License</a>, version 2 or (at your option) any later version.");
306 	aboutText += tr("<p><a href=\"http://www.qt.io/\">Qt application framework</a> v%1 by The Qt Company.").arg(qVersion());
307 	aboutText += tr("<br><a href=\"http://poppler.freedesktop.org/\">Poppler</a> PDF rendering library by Kristian H&#xF8;gsberg, Albert Astals Cid and others.");
308 	aboutText += tr("<br><a href=\"http://hunspell.github.io/\">Hunspell</a> spell checker by L&#xE1;szl&#xF3; N&#xE9;meth.");
309 	aboutText += tr("<br>Concept and resources from <a href=\"http://www.uoregon.edu/~koch/texshop/\">TeXShop</a> by Richard Koch.");
310 	aboutText += tr("<br><a href=\"http://itexmac.sourceforge.net/SyncTeX.html\">SyncTeX</a> technology by J&#xE9;r&#xF4;me Laurens.");
311 	aboutText += tr("<br>Some icons used are from the <a href=\"http://tango.freedesktop.org/\">Tango Desktop Project</a>.");
312 	QString trText = tr("<p>%1 translation kindly contributed by %2.").arg(tr("[language name]")).arg(tr("[translator's name/email]"));
313 	if (!trText.contains("[language name]"))
314 		aboutText += trText;	// omit this if it hasn't been translated!
315 	aboutText += "</small>";
316 	QMessageBox::about(NULL, tr("About %1").arg(TEXWORKS_NAME), aboutText);
317 }
318 
openUrl(const QUrl & url)319 void TWApp::openUrl(const QUrl& url)
320 {
321 	if (!QDesktopServices::openUrl(url))
322 		QMessageBox::warning(NULL, TEXWORKS_NAME,
323 							 tr("Unable to access \"%1\"; perhaps your browser or mail application is not properly configured?")
324 							 .arg(url.toString()));
325 }
326 
goToHomePage()327 void TWApp::goToHomePage()
328 {
329 	openUrl(QUrl("http://www.tug.org/texworks/"));
330 }
331 
332 #if defined(Q_OS_WIN)
333 /* based on MSDN sample code from http://msdn.microsoft.com/en-us/library/ms724429(VS.85).aspx */
334 typedef void (WINAPI *PGNSI)(LPSYSTEM_INFO);
335 
GetWindowsVersionString()336 QString TWApp::GetWindowsVersionString()
337 {
338 	OSVERSIONINFOEXA osvi;
339 	SYSTEM_INFO si;
340 	PGNSI pGNSI;
341 	BOOL bOsVersionInfoEx;
342 	QString result("(unknown version)");
343 
344 	memset(&si, 0, sizeof(SYSTEM_INFO));
345 	memset(&osvi, 0, sizeof(OSVERSIONINFOEXA));
346 
347 	osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEXA);
348 	if ( !(bOsVersionInfoEx = GetVersionExA ((OSVERSIONINFOA *) &osvi)) )
349 		return result;
350 
351 	// Call GetNativeSystemInfo if supported or GetSystemInfo otherwise.
352 	pGNSI = (PGNSI) GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "GetNativeSystemInfo");
353 	if (NULL != pGNSI)
354 		pGNSI(&si);
355 	else
356 		GetSystemInfo(&si);
357 
358 	if ( VER_PLATFORM_WIN32_NT == osvi.dwPlatformId && osvi.dwMajorVersion > 4 ) {
359 		if ( osvi.dwMajorVersion == 10 ) {
360 			if ( osvi.dwMinorVersion == 0 ) {
361 				if ( osvi.wProductType == VER_NT_WORKSTATION )
362 					result = "10";
363 				else
364 					result = "Server 2016";
365 			}
366 		}
367 		else if ( osvi.dwMajorVersion == 6 ) {
368 			if ( osvi.dwMinorVersion == 0 ) {
369 				if ( osvi.wProductType == VER_NT_WORKSTATION )
370 					result = "Vista";
371 				else
372 					result = "Server 2008";
373 			}
374 			else if ( osvi.dwMinorVersion == 1 ) {
375 				if( osvi.wProductType == VER_NT_WORKSTATION )
376 					result = "7";
377 				else
378 					result = "Server 2008 R2";
379 			}
380 			else if ( osvi.dwMinorVersion == 2 ) {
381 				if( osvi.wProductType == VER_NT_WORKSTATION )
382 					result = "8";
383 				else
384 					result = "Server 2012";
385 			}
386 			else if ( osvi.dwMinorVersion == 3 ) {
387 				if( osvi.wProductType == VER_NT_WORKSTATION )
388 					result = "8.1";
389 				else
390 					result = "Server 2012 R2";
391 			}
392 		}
393 		else if ( osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 2 ) {
394 			if ( GetSystemMetrics(SM_SERVERR2) )
395 				result = "Server 2003 R2";
396 			else if ( osvi.wSuiteMask & VER_SUITE_STORAGE_SERVER )
397 				result = "Storage Server 2003";
398 			else if ( osvi.wSuiteMask & VER_SUITE_WH_SERVER )
399 				result = "Home Server";
400 			else if ( osvi.wProductType == VER_NT_WORKSTATION &&
401 					si.wProcessorArchitecture==PROCESSOR_ARCHITECTURE_AMD64)
402 				result = "XP Professional x64 Edition";
403 			else
404 				result = "Server 2003";
405 		}
406 		else if ( osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 1 ) {
407 			result = "XP ";
408 			if ( osvi.wSuiteMask & VER_SUITE_PERSONAL )
409 				result += "Home Edition";
410 			else
411 				result += "Professional";
412 		}
413 		else if ( osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 0 ) {
414 			result = "2000 ";
415 
416 			if ( osvi.wProductType == VER_NT_WORKSTATION ) {
417 				result += "Professional";
418 			}
419 			else {
420 				if ( osvi.wSuiteMask & VER_SUITE_DATACENTER )
421 					result += "Datacenter Server";
422 				else if ( osvi.wSuiteMask & VER_SUITE_ENTERPRISE )
423 					result += "Advanced Server";
424 				else
425 					result += "Server";
426 			}
427 		}
428 
429 		if ( strlen(osvi.szCSDVersion) > 0 ) {
430 			result += " ";
431 			result += osvi.szCSDVersion;
432 		}
433 
434 		if ( osvi.dwMajorVersion >= 6 ) {
435 			if ( si.wProcessorArchitecture==PROCESSOR_ARCHITECTURE_AMD64 )
436 				result += ", 64-bit";
437 			else if (si.wProcessorArchitecture==PROCESSOR_ARCHITECTURE_INTEL )
438 				result += ", 32-bit";
439 		}
440 	}
441 
442 	return result;
443 }
444 
GetWindowsVersion()445 unsigned int TWApp::GetWindowsVersion()
446 {
447 	OSVERSIONINFOEXA osvi;
448 
449 	memset(&osvi, 0, sizeof(OSVERSIONINFOEXA));
450 
451 	osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEXA);
452 	if ( !GetVersionExA ((OSVERSIONINFOA *) &osvi) )
453 		return 0;
454 
455 	return (osvi.dwMajorVersion << 24) | (osvi.dwMinorVersion << 16) | (osvi.wServicePackMajor << 8) | (osvi.wServicePackMinor << 0);
456 }
457 #endif
458 
getBinaryPaths(QStringList & systemEnvironment)459 const QStringList TWApp::getBinaryPaths(QStringList& systemEnvironment)
460 {
461 #if defined(Q_OS_WIN)
462 #define PATH_CASE_SENSITIVE	Qt::CaseInsensitive
463 #else
464 #define PATH_CASE_SENSITIVE	Qt::CaseSensitive
465 #endif
466 	QStringList binPaths = getPrefsBinaryPaths();
467 	QMutableStringListIterator envIter(systemEnvironment);
468 	while (envIter.hasNext()) {
469 		QString& envVar = envIter.next();
470 		if (envVar.startsWith("PATH=", PATH_CASE_SENSITIVE)) {
471 			foreach (const QString& s, envVar.mid(5).split(QChar(PATH_LIST_SEP), QString::SkipEmptyParts)) {
472 				if (!binPaths.contains(s)) {
473 					binPaths.append(s);
474 				}
475 			}
476 			envVar = envVar.left(5) + binPaths.join(QChar(PATH_LIST_SEP));
477 			break;
478 		}
479 	}
480 	return binPaths;
481 }
482 
findProgram(const QString & program,const QStringList & binPaths)483 QString TWApp::findProgram(const QString& program, const QStringList& binPaths)
484 {
485 	QStringListIterator pathIter(binPaths);
486 	bool found = false;
487 	QFileInfo fileInfo;
488 #if defined(Q_OS_WIN)
489 	QStringList executableTypes = QStringList() << "exe" << "com" << "cmd" << "bat";
490 #endif
491 	while (pathIter.hasNext() && !found) {
492 		QString path = pathIter.next();
493 		fileInfo = QFileInfo(path, program);
494 		found = fileInfo.exists() && fileInfo.isExecutable();
495 #if defined(Q_OS_WIN)
496 		// try adding common executable extensions, if one was not already present
497 		if (!found && !executableTypes.contains(fileInfo.suffix())) {
498 			QStringListIterator extensions(executableTypes);
499 			while (extensions.hasNext() && !found) {
500 				fileInfo = QFileInfo(path, program + "." + extensions.next());
501 				found = fileInfo.exists() && fileInfo.isExecutable();
502 			}
503 		}
504 #endif
505 	}
506 	return found ? fileInfo.absoluteFilePath() : QString();
507 }
508 
writeToMailingList()509 void TWApp::writeToMailingList()
510 {
511 	// The strings here are deliberately NOT localizable!
512 	QString address("texworks@tug.org");
513 	QString body("Thank you for taking the time to write an email to the TeXworks mailing list. Please read the instructions below carefully as following them will greatly facilitate the communication.\n\nInstructions:\n-) Please write your message in English (it's in your own best interest; otherwise, many people will not be able to understand it and therefore will not answer).\n\n-) Please type something meaningful in the subject line.\n\n-) If you are having a problem, please describe it step-by-step in detail.\n\n-) After reading, please delete these instructions (up to the \"configuration info\" below which we may need to find the source of problems).\n\n\n\n----- configuration info -----\n");
514 
515 	body += "TeXworks version : " TEXWORKS_VERSION "r." + TWUtils::gitCommitHash() + " (" TW_BUILD_ID_STR ")\n";
516 #if defined(Q_OS_DARWIN)
517 	body += "Install location : " + QDir(applicationDirPath() + "/../..").absolutePath() + "\n";
518 #else
519 	body += "Install location : " + applicationFilePath() + "\n";
520 #endif
521 	body += "Library path     : " + TWUtils::getLibraryPath(QString()) + "\n";
522 
523 	QStringList sysEnv(QProcess::systemEnvironment());
524 	const QStringList binPaths = getBinaryPaths(sysEnv);
525 	QString pdftex = findProgram("pdftex", binPaths);
526 	if (pdftex.isEmpty())
527 		pdftex = "not found";
528 	else {
529 		QFileInfo info(pdftex);
530 		pdftex = info.canonicalFilePath();
531 	}
532 
533 	body += "pdfTeX location  : " + pdftex + "\n";
534 
535 	body += "Operating system : ";
536 #if defined(Q_OS_WIN)
537 	body += "Windows " + GetWindowsVersionString() + "\n";
538 #else
539 #if defined(Q_OS_DARWIN)
540 #define UNAME_CMDLINE "uname -v"
541 #else
542 #define UNAME_CMDLINE "uname -a"
543 #endif
544 	QString unameResult("unknown");
545 	TWSystemCmd unameCmd(this, true);
546 	unameCmd.setProcessChannelMode(QProcess::MergedChannels);
547 	unameCmd.start(UNAME_CMDLINE);
548 	if (unameCmd.waitForStarted(1000) && unameCmd.waitForFinished(1000))
549 		unameResult = unameCmd.getResult().trimmed();
550 #if defined(Q_OS_DARWIN)
551 	SInt32 major = 0, minor = 0, bugfix = 0;
552 	Gestalt(gestaltSystemVersionMajor, &major);
553 	Gestalt(gestaltSystemVersionMinor, &minor);
554 	Gestalt(gestaltSystemVersionBugFix, &bugfix);
555 	body += QString("Mac OS X %1.%2.%3").arg(major).arg(minor).arg(bugfix);
556 	body += " (" + unameResult + ")\n";
557 #else
558 	body += unameResult + "\n";
559 #endif
560 #endif
561 
562 	body += "Qt version       : " QT_VERSION_STR " (build) / ";
563 	body += qVersion();
564 	body += " (runtime)\n";
565 	body += "------------------------------\n";
566 
567 #if defined(Q_OS_WIN)
568 	body.replace('\n', "\r\n");
569 #endif
570 
571 	openUrl(QUrl(QString("mailto:%1?subject=&body=%2").arg(address).arg(QString(QUrl::toPercentEncoding(body)))));
572 }
573 
launchAction()574 void TWApp::launchAction()
575 {
576 	scriptManager->runHooks("TeXworksLaunched");
577 
578 	if (TeXDocument::documentList().size() > 0 || PDFDocument::documentList().size() > 0)
579 		return;
580 
581 	QSETTINGS_OBJECT(settings);
582 	int launchOption = settings.value("launchOption", 1).toInt();
583 	switch (launchOption) {
584 		case 1: // Blank document
585 			newFile();
586 			break;
587 		case 2: // New from Template
588 			newFromTemplate();
589 			break;
590 		case 3: // Open File
591 			open();
592 			break;
593 	}
594 #if !defined(Q_OS_DARWIN)
595 	// on Mac OS, it's OK to end up with no document (we still have the app menu bar)
596 	// but on W32 and X11 we need a window otherwise the user can't interact at all
597 	if (TeXDocument::documentList().size() == 0 && PDFDocument::documentList().size() == 0) {
598 		newFile();
599 		if (TeXDocument::documentList().size() == 0) {
600 			// something went wrong, give up!
601 			(void)QMessageBox::critical(NULL, tr("Unable to create window"),
602 					tr("Something is badly wrong; %1 was unable to create a document window. "
603 					   "The application will now quit.").arg(TEXWORKS_NAME),
604 					QMessageBox::Close, QMessageBox::Close);
605 			quit();
606 		}
607 	}
608 #endif
609 }
610 
newFile() const611 QObject * TWApp::newFile() const
612 {
613 	TeXDocument *doc = new TeXDocument;
614 	doc->show();
615 	doc->editor()->updateLineNumberAreaWidth(0);
616 	doc->runHooks("NewFile");
617 	return doc;
618 }
619 
newFromTemplate() const620 QObject * TWApp::newFromTemplate() const
621 {
622 	QString templateName = TemplateDialog::doTemplateDialog();
623 	if (!templateName.isEmpty()) {
624 		TeXDocument *doc = new TeXDocument(templateName, true);
625 		if (doc != NULL) {
626 			doc->makeUntitled();
627 			doc->selectWindow();
628 			doc->editor()->updateLineNumberAreaWidth(0);
629 			doc->runHooks("NewFromTemplate");
630 			return doc;
631 		}
632 	}
633 	return NULL;
634 }
635 
openRecentFile()636 void TWApp::openRecentFile()
637 {
638 	QAction *action = qobject_cast<QAction *>(sender());
639 	if (action)
640 		openFile(action->data().toString());
641 }
642 
getOpenFileNames(QString selectedFilter)643 QStringList TWApp::getOpenFileNames(QString selectedFilter)
644 {
645 	QFileDialog::Options	options = 0;
646 #if defined(Q_OS_WIN)
647 	if(TWApp::GetWindowsVersion() < 0x06000000) options |= QFileDialog::DontUseNativeDialog;
648 #endif
649 	QSETTINGS_OBJECT(settings);
650 	QString lastOpenDir = settings.value("openDialogDir").toString();
651 	QStringList filters = *TWUtils::filterList();
652 	if (!selectedFilter.isNull() && !filters.contains(selectedFilter))
653 		filters.prepend(selectedFilter);
654 	return QFileDialog::getOpenFileNames(NULL, QString(tr("Open File")), lastOpenDir,
655 										 filters.join(";;"), &selectedFilter, options);
656 }
657 
getOpenFileName(QString selectedFilter)658 QString TWApp::getOpenFileName(QString selectedFilter)
659 {
660 	QFileDialog::Options	options = 0;
661 #if defined(Q_OS_WIN)
662 	if(TWApp::GetWindowsVersion() < 0x06000000) options |= QFileDialog::DontUseNativeDialog;
663 #endif
664 	QSETTINGS_OBJECT(settings);
665 	QString lastOpenDir = settings.value("openDialogDir").toString();
666 	QStringList filters = *TWUtils::filterList();
667 	if (!selectedFilter.isNull() && !filters.contains(selectedFilter))
668 		filters.prepend(selectedFilter);
669 	return QFileDialog::getOpenFileName(NULL, QString(tr("Open File")), lastOpenDir,
670 										filters.join(";;"), &selectedFilter, options);
671 }
672 
getSaveFileName(const QString & defaultName)673 QString TWApp::getSaveFileName(const QString& defaultName)
674 {
675 	QFileDialog::Options	options = 0;
676 #if defined(Q_OS_WIN)
677 	if(TWApp::GetWindowsVersion() < 0x06000000) options |= QFileDialog::DontUseNativeDialog;
678 #endif
679 	QString selectedFilter;
680 	if (!TWUtils::filterList()->isEmpty())
681 		selectedFilter = TWUtils::chooseDefaultFilter(defaultName, *(TWUtils::filterList()));
682 
683 	QString fileName = QFileDialog::getSaveFileName(NULL, tr("Save File"), defaultName,
684 													TWUtils::filterList()->join(";;"),
685 													&selectedFilter, options);
686 	if (!fileName.isEmpty()) {
687 		// add extension from the selected filter, if unique and not already present
688 		QRegExp re("\\(\\*(\\.[^ ]+)\\)");
689 		if (re.indexIn(selectedFilter) >= 0) {
690 			QString ext = re.cap(1);
691 			if (!fileName.endsWith(ext, Qt::CaseInsensitive) && !fileName.endsWith("."))
692 				fileName.append(ext);
693 		}
694 	}
695 	return fileName;
696 }
697 
open()698 void TWApp::open()
699 {
700 	QSETTINGS_OBJECT(settings);
701 	QStringList files = getOpenFileNames();
702 	foreach (QString fileName, files) {
703 		if (!fileName.isEmpty()) {
704 			QFileInfo info(fileName);
705 			settings.setValue("openDialogDir", info.canonicalPath());
706 			openFile(fileName);
707 		}
708 	}
709 }
710 
openFile(const QString & fileName,int pos)711 QObject* TWApp::openFile(const QString &fileName, int pos /* = 0 */)
712 {
713 	if (TWUtils::isPDFfile(fileName)) {
714 		PDFDocument *doc = PDFDocument::findDocument(fileName);
715 		if (doc == NULL)
716 			doc = new PDFDocument(fileName);
717 		if (doc != NULL) {
718 			if (pos > 0)
719 				doc->widget()->goToPage(pos - 1);
720 			doc->selectWindow();
721 			return doc;
722 		}
723 		return NULL;
724 	}
725 	else
726 		return TeXDocument::openDocument(fileName, true, true, pos, 0, 0);
727 }
728 
preferences()729 void TWApp::preferences()
730 {
731 	PrefsDialog::doPrefsDialog(activeWindow());
732 }
733 
emitHighlightLineOptionChanged()734 void TWApp::emitHighlightLineOptionChanged()
735 {
736 	emit highlightLineOptionChanged();
737 }
738 
maxRecentFiles() const739 int TWApp::maxRecentFiles() const
740 {
741 	return recentFilesLimit;
742 }
743 
setMaxRecentFiles(int value)744 void TWApp::setMaxRecentFiles(int value)
745 {
746 	if (value < 1)
747 		value = 1;
748 	else if (value > 100)
749 		value = 100;
750 
751 	if (value != recentFilesLimit) {
752 		recentFilesLimit = value;
753 
754 		QSETTINGS_OBJECT(settings);
755 		settings.setValue("maxRecentFiles", value);
756 
757 		updateRecentFileActions();
758 	}
759 }
760 
updateRecentFileActions()761 void TWApp::updateRecentFileActions()
762 {
763 #if defined(Q_OS_DARWIN)
764 	TWUtils::updateRecentFileActions(this, recentFileActions, menuRecent, actionClear_Recent_Files);
765 #endif
766 	emit recentFileActionsChanged();
767 }
768 
updateWindowMenus()769 void TWApp::updateWindowMenus()
770 {
771 	emit windowListChanged();
772 }
773 
stackWindows()774 void TWApp::stackWindows()
775 {
776 	arrangeWindows(TWUtils::stackWindowsInRect);
777 }
778 
tileWindows()779 void TWApp::tileWindows()
780 {
781 	arrangeWindows(TWUtils::tileWindowsInRect);
782 }
783 
arrangeWindows(TWUtils::WindowArrangementFunction func)784 void TWApp::arrangeWindows(TWUtils::WindowArrangementFunction func)
785 {
786 	QDesktopWidget *desktop = QApplication::desktop();
787 	for (int screenIndex = 0; screenIndex < desktop->numScreens(); ++screenIndex) {
788 		QWidgetList windows;
789 		foreach (TeXDocument* texDoc, TeXDocument::documentList())
790 			if (desktop->screenNumber(texDoc) == screenIndex)
791 				windows << texDoc;
792 		foreach (PDFDocument* pdfDoc, PDFDocument::documentList())
793 			if (desktop->screenNumber(pdfDoc) == screenIndex)
794 				windows << pdfDoc;
795 		if (windows.size() > 0)
796 			(*func)(windows, desktop->availableGeometry(screenIndex));
797 	}
798 }
799 
event(QEvent * event)800 bool TWApp::event(QEvent *event)
801 {
802 	if (event->type() == TWDocumentOpenEvent::type) {
803 		TWDocumentOpenEvent * e = static_cast<TWDocumentOpenEvent*>(event);
804 		openFile(e->filename, e->pos);
805 		return true;
806 	}
807 	switch (event->type()) {
808 		case QEvent::FileOpen:
809 			openFile(static_cast<QFileOpenEvent *>(event)->file());
810 			return true;
811 		default:
812 			return QApplication::event(event);
813 	}
814 }
815 
setDefaultPaths()816 void TWApp::setDefaultPaths()
817 {
818 	QDir appDir(applicationDirPath());
819 	if (binaryPaths == NULL)
820 		binaryPaths = new QStringList;
821 	else
822 		binaryPaths->clear();
823 	if (defaultBinPaths)
824 		*binaryPaths = *defaultBinPaths;
825 #if !defined(Q_OS_DARWIN)
826 	// on OS X, this will be the path to {TW_APP_PACKAGE}/Contents/MacOS/
827 	// which doesn't make any sense as a search dir for TeX binaries
828 	if (!binaryPaths->contains(appDir.absolutePath()))
829 		binaryPaths->append(appDir.absolutePath());
830 #endif
831 	QString envPath = QString::fromLocal8Bit(getenv("PATH"));
832 	if (!envPath.isEmpty())
833 		foreach (const QString& s, envPath.split(PATH_LIST_SEP, QString::SkipEmptyParts))
834 		if (!binaryPaths->contains(s))
835 			binaryPaths->append(s);
836 	if (!defaultBinPaths) {
837 		foreach (const QString& s, QString(DEFAULT_BIN_PATHS).split(PATH_LIST_SEP, QString::SkipEmptyParts)) {
838 			if (!binaryPaths->contains(s))
839 				binaryPaths->append(s);
840 		}
841 	}
842 	for (int i = binaryPaths->count() - 1; i >= 0; --i) {
843 		QDir dir(binaryPaths->at(i));
844 		if (!dir.exists())
845 			binaryPaths->removeAt(i);
846 	}
847 	if (binaryPaths->count() == 0) {
848 		QMessageBox::warning(NULL, tr("No default binary directory found"),
849 			tr("None of the predefined directories for TeX-related programs could be found."
850 				"<p><small>To run any processes, you will need to set the binaries directory (or directories) "
851 				"for your TeX distribution using the Typesetting tab of the Preferences dialog.</small>"));
852 	}
853 }
854 
getPrefsBinaryPaths()855 const QStringList TWApp::getPrefsBinaryPaths()
856 {
857 	if (binaryPaths == NULL) {
858 		binaryPaths = new QStringList;
859 		QSETTINGS_OBJECT(settings);
860 		if (settings.contains("binaryPaths"))
861 			*binaryPaths = settings.value("binaryPaths").toStringList();
862 		else
863 			setDefaultPaths();
864 	}
865 	return *binaryPaths;
866 }
867 
setBinaryPaths(const QStringList & paths)868 void TWApp::setBinaryPaths(const QStringList& paths)
869 {
870 	if (binaryPaths == NULL)
871 		binaryPaths = new QStringList;
872 	*binaryPaths = paths;
873 	QSETTINGS_OBJECT(settings);
874 	settings.setValue("binaryPaths", paths);
875 }
876 
setDefaultEngineList()877 void TWApp::setDefaultEngineList()
878 {
879 	if (engineList == NULL)
880 		engineList = new QList<Engine>;
881 	else
882 		engineList->clear();
883 	*engineList
884 //		<< Engine("LaTeXmk", "latexmk" EXE, QStringList("-e") <<
885 //				  "$pdflatex=q/pdflatex -synctex=1 %O %S/" << "-pdf" << "$fullname", true)
886 		<< Engine("pdfTeX", "pdftex" EXE, QStringList("$synctexoption") << "$fullname", true)
887 		<< Engine("pdfLaTeX", "pdflatex" EXE, QStringList("$synctexoption") << "$fullname", true)
888 		<< Engine("LuaTeX", "luatex" EXE, QStringList("$synctexoption") << "$fullname", true)
889 		<< Engine("LuaLaTeX", "lualatex" EXE, QStringList("$synctexoption") << "$fullname", true)
890 		<< Engine("XeTeX", "xetex" EXE, QStringList("$synctexoption") << "$fullname", true)
891 		<< Engine("XeLaTeX", "xelatex" EXE, QStringList("$synctexoption") << "$fullname", true)
892 		<< Engine("ConTeXt (LuaTeX)", "context" EXE, QStringList("--synctex") << "$fullname", true)
893 		<< Engine("ConTeXt (pdfTeX)", "texexec" EXE, QStringList("--synctex") << "$fullname", true)
894 		<< Engine("ConTeXt (XeTeX)", "texexec" EXE, QStringList("--synctex") << "--xtx" << "$fullname", true)
895 		<< Engine("BibTeX", "bibtex" EXE, QStringList("$basename"), false)
896 		<< Engine("Biber", "biber" EXE, QStringList("$basename"), false)
897 		<< Engine("MakeIndex", "makeindex" EXE, QStringList("$basename"), false);
898 	defaultEngineIndex = 1;
899 }
900 
getEngineList()901 const QList<Engine> TWApp::getEngineList()
902 {
903 	if (engineList == NULL) {
904 		engineList = new QList<Engine>;
905 		bool foundList = false;
906 		// check for old engine list in Preferences
907 		QSETTINGS_OBJECT(settings);
908 		int count = settings.beginReadArray("engines");
909 		if (count > 0) {
910 			for (int i = 0; i < count; ++i) {
911 				settings.setArrayIndex(i);
912 				Engine eng;
913 				eng.setName(settings.value("name").toString());
914 				eng.setProgram(settings.value("program").toString());
915 				eng.setArguments(settings.value("arguments").toStringList());
916 				eng.setShowPdf(settings.value("showPdf").toBool());
917 				engineList->append(eng);
918 				settings.remove("");
919 			}
920 			foundList = true;
921 			saveEngineList();
922 		}
923 		settings.endArray();
924 		settings.remove("engines");
925 
926 		if (!foundList) { // read engine list from config file
927 			QDir configDir(TWUtils::getLibraryPath("configuration"));
928 			QFile toolsFile(configDir.filePath("tools.ini"));
929 			if (toolsFile.exists()) {
930 				QSettings toolsSettings(toolsFile.fileName(), QSettings::IniFormat);
931 				QStringList toolNames = toolsSettings.childGroups();
932 				foreach (const QString& n, toolNames) {
933 					toolsSettings.beginGroup(n);
934 					Engine eng;
935 					eng.setName(toolsSettings.value("name").toString());
936 					eng.setProgram(toolsSettings.value("program").toString());
937 					eng.setArguments(toolsSettings.value("arguments").toStringList());
938 					eng.setShowPdf(toolsSettings.value("showPdf").toBool());
939 					engineList->append(eng);
940 					toolsSettings.endGroup();
941 				}
942 				foundList = true;
943 			}
944 		}
945 
946 		if (!foundList)
947 			setDefaultEngineList();
948 		setDefaultEngine(settings.value("defaultEngine", DEFAULT_ENGINE_NAME).toString());
949 	}
950 	return *engineList;
951 }
952 
saveEngineList()953 void TWApp::saveEngineList()
954 {
955 	QDir configDir(TWUtils::getLibraryPath("configuration"));
956 	QFile toolsFile(configDir.filePath("tools.ini"));
957 	QSettings toolsSettings(toolsFile.fileName(), QSettings::IniFormat);
958 	toolsSettings.clear();
959 	int n = 0;
960 	foreach (const Engine& e, *engineList) {
961 		toolsSettings.beginGroup(QString("%1").arg(++n, 3, 10, QChar('0')));
962 		toolsSettings.setValue("name", e.name());
963 		toolsSettings.setValue("program", e.program());
964 		toolsSettings.setValue("arguments", e.arguments());
965 		toolsSettings.setValue("showPdf", e.showPdf());
966 		toolsSettings.endGroup();
967 	}
968 }
969 
setEngineList(const QList<Engine> & engines)970 void TWApp::setEngineList(const QList<Engine>& engines)
971 {
972 	if (engineList == NULL)
973 		engineList = new QList<Engine>;
974 	*engineList = engines;
975 	saveEngineList();
976 	QSETTINGS_OBJECT(settings);
977 	settings.setValue("defaultEngine", getDefaultEngine().name());
978 	emit engineListChanged();
979 }
980 
getDefaultEngine()981 const Engine TWApp::getDefaultEngine()
982 {
983 	const QList<Engine> engines = getEngineList();
984 	if (defaultEngineIndex < engines.count())
985 		return engines[defaultEngineIndex];
986 	defaultEngineIndex = 0;
987 	if (engines.empty())
988 		return Engine();
989 	else
990 		return engines[0];
991 }
992 
setDefaultEngine(const QString & name)993 void TWApp::setDefaultEngine(const QString& name)
994 {
995 	const QList<Engine> engines = getEngineList();
996 	int i;
997 	for (i = 0; i < engines.count(); ++i) {
998 		if (engines[i].name() == name) {
999 			QSETTINGS_OBJECT(settings);
1000 			settings.setValue("defaultEngine", name);
1001 			break;
1002 		}
1003 	}
1004 	// If the engine was not found (e.g., if it has been deleted)
1005 	// try the DEFAULT_ENGINE_NAME instead (should not happen, unless the config
1006 	// was edited manually (or by an updater, copy/paste'ing, etc.)
1007 	if (i == engines.count() && name != DEFAULT_ENGINE_NAME) {
1008 		for (i = 0; i < engines.count(); ++i) {
1009 			if (engines[i].name() == DEFAULT_ENGINE_NAME)
1010 				break;
1011 		}
1012 	}
1013 	// if neither the passed engine name nor DEFAULT_ENGINE_NAME was found,
1014 	// fall back to selecting the first engine
1015 	if (i == engines.count())
1016 		i = 0;
1017 
1018 	defaultEngineIndex = i;
1019 }
1020 
getNamedEngine(const QString & name)1021 const Engine TWApp::getNamedEngine(const QString& name)
1022 {
1023 	const QList<Engine> engines = getEngineList();
1024 	foreach (const Engine& e, engines) {
1025 		if (e.name().compare(name, Qt::CaseInsensitive) == 0)
1026 			return e;
1027 	}
1028 	return Engine();
1029 }
1030 
getDefaultCodec()1031 QTextCodec *TWApp::getDefaultCodec()
1032 {
1033 	return defaultCodec;
1034 }
1035 
setDefaultCodec(QTextCodec * codec)1036 void TWApp::setDefaultCodec(QTextCodec *codec)
1037 {
1038 	if (codec == NULL)
1039 		return;
1040 
1041 	if (codec != defaultCodec) {
1042 		defaultCodec = codec;
1043 		QSETTINGS_OBJECT(settings);
1044 		settings.setValue("defaultEncoding", codec->name());
1045 	}
1046 }
1047 
activatedWindow(QWidget * theWindow)1048 void TWApp::activatedWindow(QWidget* theWindow)
1049 {
1050 	emit hideFloatersExcept(theWindow);
1051 }
1052 
applyTranslation(const QString & locale)1053 void TWApp::applyTranslation(const QString& locale)
1054 {
1055 	foreach (QTranslator* t, translators) {
1056 		removeTranslator(t);
1057 		delete t;
1058 	}
1059 	translators.clear();
1060 
1061 	if (!locale.isEmpty()) {
1062 		// According to the Qt docs, translators are searched in reverse order
1063 		// (the last installed one is tried first). Here, we use the following
1064 		// search order (1. is tried first):
1065 		// 1. The user's files in <resources>/translations
1066 		// 2. The system-wide translation
1067 		// 3. The bundled translation
1068 		// Note that the bundled translations are not copied to <resources>, so
1069 		// this search order is not messed up.
1070 		QStringList names, directories;
1071 		names << QString::fromLatin1("qt_") + locale \
1072 					<< QString::fromLatin1("QtPDF_") + locale \
1073 					<< QString::fromLatin1(TEXWORKS_NAME) + QString::fromLatin1("_") + locale;
1074 		directories << QString::fromLatin1(":/resfiles/translations") \
1075 								<< QLibraryInfo::location(QLibraryInfo::TranslationsPath) \
1076 								<< TWUtils::getLibraryPath("translations");
1077 
1078 		foreach (QString name, names) {
1079 			foreach (QString dir, directories) {
1080 				QTranslator * t = new QTranslator(this);
1081 				if (t->load(name, dir)) {
1082 					installTranslator(t);
1083 					translators.append(t);
1084 				}
1085 				else
1086 					delete t;
1087 			}
1088 		}
1089 	}
1090 
1091 	emit updatedTranslators();
1092 }
1093 
addToRecentFiles(const QMap<QString,QVariant> & fileProperties)1094 void TWApp::addToRecentFiles(const QMap<QString,QVariant>& fileProperties)
1095 {
1096 	QSETTINGS_OBJECT(settings);
1097 
1098 	QString fileName = fileProperties.value("path").toString();
1099 	if (fileName.isEmpty())
1100 		return;
1101 
1102 	QList<QVariant> fileList = settings.value("recentFiles").toList();
1103 	QList<QVariant>::iterator i = fileList.begin();
1104 	while (i != fileList.end()) {
1105 		QMap<QString,QVariant> h = i->toMap();
1106 		if (h.value("path").toString() == fileName)
1107 			i = fileList.erase(i);
1108 		else
1109 			++i;
1110 	}
1111 
1112 	fileList.prepend(fileProperties);
1113 
1114 	while (fileList.size() > maxRecentFiles())
1115 		fileList.removeLast();
1116 
1117 	settings.setValue("recentFiles", QVariant::fromValue(fileList));
1118 
1119 	updateRecentFileActions();
1120 }
1121 
clearRecentFiles()1122 void TWApp::clearRecentFiles()
1123 {
1124 	QSETTINGS_OBJECT(settings);
1125 	QList<QVariant> fileList;
1126 	settings.setValue("recentFiles", QVariant::fromValue(fileList));
1127 	updateRecentFileActions();
1128 }
1129 
getFileProperties(const QString & path)1130 QMap<QString,QVariant> TWApp::getFileProperties(const QString& path)
1131 {
1132 	QSETTINGS_OBJECT(settings);
1133 	QList<QVariant> fileList = settings.value("recentFiles").toList();
1134 	QList<QVariant>::iterator i = fileList.begin();
1135 	while (i != fileList.end()) {
1136 		QMap<QString,QVariant> h = i->toMap();
1137 		if (h.value("path").toString() == path)
1138 			return h;
1139 		++i;
1140 	}
1141 	return QMap<QString,QVariant>();
1142 }
1143 
openHelpFile(const QString & helpDirName)1144 void TWApp::openHelpFile(const QString& helpDirName)
1145 {
1146 	QDir helpDir(helpDirName);
1147 	if (helpDir.exists("index.html"))
1148 		openUrl(QUrl::fromLocalFile(helpDir.absoluteFilePath("index.html")));
1149 	else
1150 		QMessageBox::warning(NULL, TEXWORKS_NAME, tr("Unable to find help file."));
1151 }
1152 
updateScriptsList()1153 void TWApp::updateScriptsList()
1154 {
1155 	scriptManager->reloadScripts();
1156 
1157 	emit scriptListChanged();
1158 }
1159 
showScriptsFolder()1160 void TWApp::showScriptsFolder()
1161 {
1162 	QDesktopServices::openUrl(QUrl::fromLocalFile(TWUtils::getLibraryPath("scripts")));
1163 }
1164 
1165 #if defined(Q_OS_WIN) // support for the Windows single-instance code
1166 #include <windows.h>
1167 
TW_HiddenWindowProc(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam)1168 LRESULT CALLBACK TW_HiddenWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
1169 {
1170 	switch (uMsg)
1171 	{
1172 		case WM_COPYDATA:
1173 			{
1174 				const COPYDATASTRUCT* pcds = (const COPYDATASTRUCT*)lParam;
1175 				if (pcds->dwData == TW_OPEN_FILE_MSG) {
1176 					if (TWApp::instance() != NULL) {
1177 						QStringList data = QString::fromUtf8((const char*)pcds->lpData, pcds->cbData).split('\n');
1178 						if (data.size() == 1)
1179 							TWApp::instance()->openFile(data[0]);
1180 						else
1181 							TWApp::instance()->openFile(data[0], data[1].toInt());
1182 					}
1183 				}
1184 			}
1185 			return 0;
1186 
1187 		default:
1188 			return DefWindowProc(hwnd, uMsg, wParam, lParam);
1189 	}
1190 	return 0;
1191 }
1192 
createMessageTarget(QWidget * aWindow)1193 void TWApp::createMessageTarget(QWidget* aWindow)
1194 {
1195 	if (messageTargetWindow != NULL)
1196 		return;
1197 
1198 	if (QCoreApplication::startingUp())
1199 		return;
1200 
1201 	if (!aWindow || !aWindow->isWindow())
1202 		return;
1203 
1204 	HINSTANCE hInstance = (HINSTANCE)GetWindowLongPtr((HWND)aWindow->winId(), GWLP_HINSTANCE);
1205 	if (hInstance == NULL)
1206 		return;
1207 
1208 	WNDCLASSA myWindowClass;
1209 	myWindowClass.style = 0;
1210 	myWindowClass.lpfnWndProc = &TW_HiddenWindowProc;
1211 	myWindowClass.cbClsExtra = 0;
1212 	myWindowClass.cbWndExtra = 0;
1213 	myWindowClass.hInstance = hInstance;
1214 	myWindowClass.hIcon = NULL;
1215 	myWindowClass.hCursor = NULL;
1216 	myWindowClass.hbrBackground = NULL;
1217 	myWindowClass.lpszMenuName = NULL;
1218 	myWindowClass.lpszClassName = TW_HIDDEN_WINDOW_CLASS;
1219 
1220 	ATOM atom = RegisterClassA(&myWindowClass);
1221 	if (atom == 0)
1222 		return;
1223 
1224 	messageTargetWindow = CreateWindowA(TW_HIDDEN_WINDOW_CLASS, TEXWORKS_NAME, WS_OVERLAPPEDWINDOW,
1225 					CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
1226 					HWND_MESSAGE, NULL, hInstance, NULL);
1227 }
1228 #endif
1229 
bringToFront()1230 void TWApp::bringToFront()
1231 {
1232 	foreach (QWidget* widget, topLevelWidgets()) {
1233 		QMainWindow* window = qobject_cast<QMainWindow*>(widget);
1234 		if (window != NULL) {
1235 			window->raise();
1236 			window->activateWindow();
1237 		}
1238 	}
1239 }
1240 
getOpenWindows() const1241 QList<QVariant> TWApp::getOpenWindows() const
1242 {
1243 	QList<QVariant> result;
1244 
1245 	foreach (QWidget *widget, QApplication::topLevelWidgets()) {
1246 		if (qobject_cast<TWScriptable*>(widget))
1247 			result << QVariant::fromValue(qobject_cast<QObject*>(widget));
1248 	}
1249 	return result;
1250 }
1251 
setGlobal(const QString & key,const QVariant & val)1252 void TWApp::setGlobal(const QString& key, const QVariant& val)
1253 {
1254 	QVariant v = val;
1255 
1256 	if (key.isEmpty())
1257 		return;
1258 
1259 	// For objects on the heap make sure we are notified when their lifetimes
1260 	// end so that we can remove them from our hash accordingly
1261 	switch ((QMetaType::Type)val.type()) {
1262 		case QMetaType::QObjectStar:
1263 			connect(v.value<QObject*>(), SIGNAL(destroyed(QObject*)), this, SLOT(globalDestroyed(QObject*)));
1264 			break;
1265 		#if QT_VERSION < 0x050000
1266 		case QMetaType::QWidgetStar:
1267 			connect((QWidget*)v.data(), SIGNAL(destroyed(QObject*)), this, SLOT(globalDestroyed(QObject*)));
1268 			break;
1269 		#endif
1270 		default: break;
1271 	}
1272 	m_globals[key] = v;
1273 }
1274 
globalDestroyed(QObject * obj)1275 void TWApp::globalDestroyed(QObject * obj)
1276 {
1277 	QHash<QString, QVariant>::iterator i = m_globals.begin();
1278 
1279 	while (i != m_globals.end()) {
1280 		switch ((QMetaType::Type)i.value().type()) {
1281 			case QMetaType::QObjectStar:
1282 				if (i.value().value<QObject*>() == obj)
1283 					i = m_globals.erase(i);
1284 				else
1285 					++i;
1286 				break;
1287 			#if QT_VERSION < 0x050000
1288 			case QMetaType::QWidgetStar:
1289 				if (i.value().value<QWidget*>() == obj)
1290 					i = m_globals.erase(i);
1291 				else
1292 					++i;
1293 				break;
1294 			#endif
1295 			default:
1296 				++i;
1297 				break;
1298 		}
1299 	}
1300 }
1301 
1302 /*Q_INVOKABLE static*/
getVersion()1303 int TWApp::getVersion()
1304 {
1305 	return (VER_MAJOR << 16) | (VER_MINOR << 8) | VER_BUGFIX;
1306 }
1307 
1308 //Q_INVOKABLE
openFileFromScript(const QString & fileName,QObject * scriptApiObj,const int pos,const bool askUser)1309 QMap<QString, QVariant> TWApp::openFileFromScript(const QString& fileName, QObject * scriptApiObj, const int pos /* = -1 */, const bool askUser /* = false */)
1310 {
1311 	QSETTINGS_OBJECT(settings);
1312 	QMap<QString, QVariant> retVal;
1313 	QObject * doc = NULL;
1314 	TWScript * script;
1315 	QFileInfo fi(fileName);
1316 	TWScriptAPI * scriptApi = qobject_cast<TWScriptAPI*>(scriptApiObj);
1317 
1318 	retVal["status"] = TWScriptAPI::SystemAccess_PermissionDenied;
1319 
1320 	// for absolute paths and full reading permissions, we don't have to care
1321 	// about peculiarities of the script; in that case, this even succeeds
1322 	// if no valid scriptApi is passed; otherwise, we need to investigate further
1323 	if (fi.isRelative() || !settings.value("allowScriptFileReading", kDefault_AllowScriptFileReading).toBool()) {
1324 		if (!scriptApi)
1325 			return retVal;
1326 		script = qobject_cast<TWScript*>(scriptApi->GetScript());
1327 		if (!script)
1328 			return retVal; // this should never happen
1329 
1330 		// relative paths are taken to be relative to the folder containing the
1331 		// executing script's file
1332 		QDir scriptDir(QFileInfo(script->getFilename()).dir());
1333 		QString path = scriptDir.absoluteFilePath(fileName);
1334 
1335 		if (!script->mayReadFile(path, scriptApi->GetTarget())) {
1336 			// Possibly ask user to override the permissions
1337 			if (!askUser)
1338 				return retVal;
1339 			if (QMessageBox::warning(qobject_cast<QWidget*>(scriptApi->GetTarget()),
1340 				tr("Permission request"),
1341 				tr("The script \"%1\" is trying to open the file \"%2\" without sufficient permissions. Do you want to open the file?")\
1342 					.arg(script->getTitle()).arg(path),
1343 				QMessageBox::Yes | QMessageBox::No, QMessageBox::No
1344 			) != QMessageBox::Yes)
1345 				return retVal;
1346 		}
1347 	}
1348 	doc = openFile(fileName, pos);
1349 	retVal["result"] = QVariant::fromValue(doc);
1350 	retVal["status"] = (doc != NULL ? TWScriptAPI::SystemAccess_OK : TWScriptAPI::SystemAccess_Failed);
1351 	return retVal;
1352 }
1353 
doResourcesDialog() const1354 void TWApp::doResourcesDialog() const
1355 {
1356 	ResourcesDialog::doResourcesDialog(NULL);
1357 }
1358 
reloadSpellchecker()1359 void TWApp::reloadSpellchecker()
1360 {
1361 	// save the current language and deactivate the spell checker for all open
1362 	// TeXDocument windows
1363 	QHash<TeXDocument*, QString> oldLangs;
1364 	foreach (QWidget *widget, QApplication::topLevelWidgets()) {
1365 		TeXDocument * texDoc = qobject_cast<TeXDocument*>(widget);
1366 		if (texDoc) {
1367 			oldLangs[texDoc] = texDoc->spellcheckLanguage();
1368 			texDoc->setSpellcheckLanguage(QString());
1369 		}
1370 	}
1371 
1372 	// reset dictionaries (getDictionaryList(true) automatically updates all
1373 	// spell checker menus)
1374 	TWUtils::clearDictionaries();
1375 	TWUtils::getDictionaryList(true);
1376 
1377 	// reenable spell checker
1378 	for (QHash<TeXDocument*, QString>::iterator it = oldLangs.begin(); it != oldLangs.end(); ++it) {
1379 		it.key()->setSpellcheckLanguage(it.value());
1380 	}
1381 }
1382 
1383