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>© 2007-2017 Jonathan Kew, Stefan Lö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øgsberg, Albert Astals Cid and others.");
308 aboutText += tr("<br><a href=\"http://hunspell.github.io/\">Hunspell</a> spell checker by László Né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érô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