]> git.lyx.org Git - lyx.git/blob - src/frontends/qt4/GuiApplication.cpp
266ae8886748414e18b86a9ff59d495c82e7947d
[lyx.git] / src / frontends / qt4 / GuiApplication.cpp
1 /**
2  * \file GuiApplication.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author unknown
7  * \author John Levon
8  * \author Abdelrazak Younes
9  *
10  * Full author contact details are available in file CREDITS.
11  */
12
13 #include <config.h>
14
15 #include "GuiApplication.h"
16
17 #include "ColorCache.h"
18 #include "ColorSet.h"
19 #include "GuiClipboard.h"
20 #include "GuiImage.h"
21 #include "GuiKeySymbol.h"
22 #include "GuiSelection.h"
23 #include "GuiView.h"
24 #include "Menus.h"
25 #include "qt_helpers.h"
26 #include "Toolbars.h"
27
28 #include "frontends/alert.h"
29 #include "frontends/Application.h"
30 #include "frontends/FontLoader.h"
31 #include "frontends/FontMetrics.h"
32
33 #include "Buffer.h"
34 #include "BufferList.h"
35 #include "BufferView.h"
36 #include "Color.h"
37 #include "Font.h"
38 #include "FuncRequest.h"
39 #include "FuncStatus.h"
40 #include "Intl.h"
41 #include "KeyMap.h"
42 #include "Language.h"
43 #include "LaTeXFeatures.h"
44 #include "Lexer.h"
45 #include "LyX.h"
46 #include "LyXAction.h"
47 #include "LyXFunc.h"
48 #include "LyXRC.h"
49 #include "Server.h"
50 #include "Session.h"
51 #include "SpellChecker.h"
52 #include "version.h"
53
54 #include "support/lassert.h"
55 #include "support/debug.h"
56 #include "support/ExceptionMessage.h"
57 #include "support/FileName.h"
58 #include "support/filetools.h"
59 #include "support/foreach.h"
60 #include "support/ForkedCalls.h"
61 #include "support/gettext.h"
62 #include "support/lstrings.h"
63 #include "support/lyxalgo.h" // sorted
64 #include "support/Messages.h"
65 #include "support/os.h"
66 #include "support/Package.h"
67 #include "support/Path.h"
68 #include "support/Systemcall.h"
69
70 #ifdef Q_WS_MACX
71 #include "support/linkback/LinkBackProxy.h"
72 #endif
73
74 #include <queue>
75
76 #include <QByteArray>
77 #include <QClipboard>
78 #include <QDateTime>
79 #include <QDir>
80 #include <QEventLoop>
81 #include <QFileOpenEvent>
82 #include <QFileInfo>
83 #include <QHash>
84 #include <QIcon>
85 #include <QImageReader>
86 #include <QLocale>
87 #include <QLibraryInfo>
88 #include <QList>
89 #include <QMacPasteboardMime>
90 #include <QMenuBar>
91 #include <QMimeData>
92 #include <QObject>
93 #include <QPixmap>
94 #include <QPixmapCache>
95 #include <QRegExp>
96 #include <QSessionManager>
97 #include <QSettings>
98 #include <QSocketNotifier>
99 #include <QSortFilterProxyModel>
100 #include <QStandardItemModel>
101 #include <QTextCodec>
102 #include <QTimer>
103 #include <QTranslator>
104 #include <QWidget>
105
106 #ifdef Q_WS_X11
107 #include <X11/Xatom.h>
108 #include <X11/Xlib.h>
109 #undef CursorShape
110 #undef None
111 #endif
112
113 #ifdef Q_WS_WIN
114 #include <QWindowsMime>
115 #ifdef Q_CC_GNU
116 #include <wtypes.h>
117 #endif
118 #include <objidl.h>
119 #endif // Q_WS_WIN
120
121 #include <boost/bind.hpp>
122 #include <boost/crc.hpp>
123
124 #include <exception>
125 #include <sstream>
126 #include <vector>
127
128 using namespace std;
129 using namespace lyx::support;
130
131
132 static void initializeResources()
133 {
134         static bool initialized = false;
135         if (!initialized) {
136                 Q_INIT_RESOURCE(Resources); 
137                 initialized = true;
138         }
139 }
140
141
142 namespace lyx {
143
144 frontend::Application * createApplication(int & argc, char * argv[])
145 {
146 #ifndef Q_WS_X11
147         // prune -geometry argument(s) by shifting
148         // the following ones 2 places down.
149         for (int i = 0 ; i < argc ; ++i) {
150                 if (strcmp(argv[i], "-geometry") == 0) {
151                         int const remove = (i+1) < argc ? 2 : 1;
152                         argc -= remove;
153                         for (int j = i; j < argc; ++j)
154                                 argv[j] = argv[j + remove];
155                         --i;
156                 }
157         }
158 #endif
159         return new frontend::GuiApplication(argc, argv);
160 }
161
162 namespace frontend {
163
164
165 /// Return the list of loadable formats.
166 vector<string> loadableImageFormats()
167 {
168         vector<string> fmts;
169
170         QList<QByteArray> qt_formats = QImageReader::supportedImageFormats();
171
172         LYXERR(Debug::GRAPHICS,
173                 "\nThe image loader can load the following directly:\n");
174
175         if (qt_formats.empty())
176                 LYXERR(Debug::GRAPHICS, "\nQt4 Problem: No Format available!");
177
178         for (QList<QByteArray>::const_iterator it = qt_formats.begin(); it != qt_formats.end(); ++it) {
179
180                 LYXERR(Debug::GRAPHICS, (const char *) *it << ", ");
181
182                 string ext = ascii_lowercase((const char *) *it);
183                 // special case
184                 if (ext == "jpeg")
185                         ext = "jpg";
186                 fmts.push_back(ext);
187         }
188
189         return fmts;
190 }
191
192
193 ////////////////////////////////////////////////////////////////////////
194 //
195 // Icon loading support code
196 //
197 ////////////////////////////////////////////////////////////////////////
198
199 namespace {
200
201 struct PngMap {
202         QString key;
203         QString value;
204 };
205
206
207 bool operator<(PngMap const & lhs, PngMap const & rhs)
208 {
209         return lhs.key < rhs.key;
210 }
211
212
213 class CompareKey {
214 public:
215         CompareKey(QString const & name) : name_(name) {}
216         bool operator()(PngMap const & other) const { return other.key == name_; }
217 private:
218         QString const name_;
219 };
220
221
222 // this must be sorted alphabetically
223 // Upper case comes before lower case
224 PngMap sorted_png_map[] = {
225         { "Bumpeq", "bumpeq2" },
226         { "Cap", "cap2" },
227         { "Cup", "cup2" },
228         { "Delta", "delta2" },
229         { "Downarrow", "downarrow2" },
230         { "Gamma", "gamma2" },
231         { "Lambda", "lambda2" },
232         { "Leftarrow", "leftarrow2" },
233         { "Leftrightarrow", "leftrightarrow2" },
234         { "Longleftarrow", "longleftarrow2" },
235         { "Longleftrightarrow", "longleftrightarrow2" },
236         { "Longrightarrow", "longrightarrow2" },
237         { "Omega", "omega2" },
238         { "Phi", "phi2" },
239         { "Pi", "pi2" },
240         { "Psi", "psi2" },
241         { "Rightarrow", "rightarrow2" },
242         { "Sigma", "sigma2" },
243         { "Subset", "subset2" },
244         { "Supset", "supset2" },
245         { "Theta", "theta2" },
246         { "Uparrow", "uparrow2" },
247         { "Updownarrow", "updownarrow2" },
248         { "Upsilon", "upsilon2" },
249         { "Vdash", "vdash3" },
250         { "Vert", "vert2" },
251         { "Xi", "xi2" },
252         { "nLeftarrow", "nleftarrow2" },
253         { "nLeftrightarrow", "nleftrightarrow2" },
254         { "nRightarrow", "nrightarrow2" },
255         { "nVDash", "nvdash3" },
256         { "nvDash", "nvdash2" },
257         { "textrm \\AA", "textrm_AA"},
258         { "textrm \\O", "textrm_O"},
259         { "vDash", "vdash2" }
260 };
261
262 size_t const nr_sorted_png_map = sizeof(sorted_png_map) / sizeof(PngMap);
263
264
265 QString findPng(QString const & name)
266 {
267         PngMap const * const begin = sorted_png_map;
268         PngMap const * const end = begin + nr_sorted_png_map;
269         LASSERT(sorted(begin, end), /**/);
270
271         PngMap const * const it = find_if(begin, end, CompareKey(name));
272
273         QString png_name;
274         if (it != end) {
275                 png_name = it->value;
276         } else {
277                 png_name = name;
278                 png_name.replace('_', "underscore");
279                 png_name.replace(' ', '_');
280
281                 // This way we can have "math-delim { }" on the toolbar.
282                 png_name.replace('(', "lparen");
283                 png_name.replace(')', "rparen");
284                 png_name.replace('[', "lbracket");
285                 png_name.replace(']', "rbracket");
286                 png_name.replace('{', "lbrace");
287                 png_name.replace('}', "rbrace");
288                 png_name.replace('|', "bars");
289                 png_name.replace(',', "thinspace");
290                 png_name.replace(':', "mediumspace");
291                 png_name.replace(';', "thickspace");
292                 png_name.replace('!', "negthinspace");
293         }
294
295         LYXERR(Debug::GUI, "findPng(" << name << ")\n"
296                 << "Looking for math PNG called \"" << png_name << '"');
297         return png_name;
298 }
299
300 } // namespace anon
301
302
303 QString iconName(FuncRequest const & f, bool unknown)
304 {
305         initializeResources();
306         QString name1;
307         QString name2;
308         QString path;
309         switch (f.action) {
310         case LFUN_MATH_INSERT:
311                 if (!f.argument().empty()) {
312                         path = "math/";
313                         name1 = findPng(toqstr(f.argument()).mid(1));
314                 }
315                 break;
316         case LFUN_MATH_DELIM:
317         case LFUN_MATH_BIGDELIM:
318                 path = "math/";
319                 name1 = findPng(toqstr(f.argument()));
320                 break;
321         case LFUN_CALL:
322                 path = "commands/";
323                 name1 = toqstr(f.argument());
324                 break;
325         case LFUN_COMMAND_ALTERNATIVES: {
326                 // use the first of the alternative commands
327                 docstring firstcom;
328                 docstring dummy = split(f.argument(), firstcom, ';');
329                 name1 = toqstr(firstcom);
330                 name1.replace(' ', '_');
331                 break;
332         }
333         default:
334                 name2 = toqstr(lyxaction.getActionName(f.action));
335                 name1 = name2;
336
337                 if (!f.argument().empty()) {
338                         name1 = name2 + ' ' + toqstr(f.argument());
339                         name1.replace(' ', '_');
340                         name1.replace('\\', "backslash");
341                 }
342         }
343
344         FileName fname = libFileSearch("images/" + path, name1, "png");
345         if (fname.exists())
346                 return toqstr(fname.absFilename());
347
348         fname = libFileSearch("images/" + path, name2, "png");
349         if (fname.exists())
350                 return toqstr(fname.absFilename());
351
352         path = ":/images/" + path;
353         QDir res(path);
354         if (!res.exists()) {
355                 LYXERR0("Directory " << path << " not found in resource!"); 
356                 return QString();
357         }
358         name1 += ".png";
359         if (res.exists(name1))
360                 return path + name1;
361
362         name2 += ".png";
363         if (res.exists(name2))
364                 return path + name2;
365
366         LYXERR(Debug::GUI, "Cannot find icon with filename "
367                            << "\"" << name1 << "\""
368                            << " or filename "
369                            << "\"" << name2 << "\"" 
370                            << " for command \""
371                            << lyxaction.getActionName(f.action)
372                            << '(' << to_utf8(f.argument()) << ")\"");
373
374         if (unknown) {
375                 fname = libFileSearch(QString("images/"), "unknown", "png");
376                 if (fname.exists())
377                         return toqstr(fname.absFilename());
378                 return QString(":/images/unknown.png");
379         }
380
381         return QString();
382 }
383
384 QPixmap getPixmap(QString const & path, QString const & name, QString const & ext)
385 {
386         QPixmap pixmap;
387         FileName fname = libFileSearch(path, name, ext);
388         QString path1 = toqstr(fname.absFilename());
389         QString path2 = ":/" + path + name + "." + ext;
390
391         if (pixmap.load(path1)) {
392                 return pixmap;
393         }
394         else if (pixmap.load(path2)) {
395                 return pixmap;
396         }
397
398         LYXERR0("Cannot load pixmap \""
399                 << path << name << '.' << ext
400                 << "\", please verify resource system!");
401
402         return QPixmap();
403 }
404
405 QIcon getIcon(FuncRequest const & f, bool unknown)
406 {
407         QString icon = iconName(f, unknown);
408         if (icon.isEmpty())
409                 return QIcon();
410
411         //LYXERR(Debug::GUI, "Found icon: " << icon);
412         QPixmap pm;
413         if (!pm.load(icon)) {
414                 LYXERR0("Cannot load icon " << icon << " please verify resource system!");
415                 return QIcon();
416         }
417
418         return QIcon(pm);
419 }
420
421
422 ////////////////////////////////////////////////////////////////////////
423 //
424 // LyX server support code.
425 //
426 ////////////////////////////////////////////////////////////////////////
427
428 class SocketNotifier : public QSocketNotifier
429 {
430 public:
431         /// connect a connection notification from the LyXServerSocket
432         SocketNotifier(QObject * parent, int fd, Application::SocketCallback func)
433                 : QSocketNotifier(fd, QSocketNotifier::Read, parent), func_(func)
434         {}
435
436 public:
437         /// The callback function
438         Application::SocketCallback func_;
439 };
440
441
442 ////////////////////////////////////////////////////////////////////////
443 //
444 // Mac specific stuff goes here...
445 //
446 ////////////////////////////////////////////////////////////////////////
447
448 class MenuTranslator : public QTranslator
449 {
450 public:
451         MenuTranslator(QObject * parent)
452                 : QTranslator(parent)
453         {}
454
455         QString translate(const char * /*context*/, 
456           const char * sourceText, 
457           const char * /*comment*/ = 0) 
458         {
459                 string const s = sourceText;
460                 if (s == N_("About %1") || s == N_("Preferences") 
461                                 || s == N_("Reconfigure") || s == N_("Quit %1"))
462                         return qt_(s);
463                 else 
464                         return QString();
465         }
466 };
467
468 class GlobalMenuBar : public QMenuBar
469 {
470 public:
471         ///
472         GlobalMenuBar() : QMenuBar(0) {}
473         
474         ///
475         bool event(QEvent * e)
476         {
477                 if (e->type() == QEvent::ShortcutOverride) {
478                         //          && activeWindow() == 0) {
479                         QKeyEvent * ke = static_cast<QKeyEvent*>(e);
480                         KeySymbol sym;
481                         setKeySymbol(&sym, ke);
482                         guiApp->processKeySym(sym, q_key_state(ke->modifiers()));
483                         e->accept();
484                         return true;
485                 }
486                 return false;
487         }
488 };
489
490 #ifdef Q_WS_MACX
491 // QMacPasteboardMimeGraphics can only be compiled on Mac.
492
493 class QMacPasteboardMimeGraphics : public QMacPasteboardMime
494 {
495 public:
496         QMacPasteboardMimeGraphics()
497                 : QMacPasteboardMime(MIME_QT_CONVERTOR|MIME_ALL)
498         {}
499
500         QString convertorName() { return "Graphics"; }
501
502         QString flavorFor(QString const & mime)
503         {
504                 LYXERR(Debug::ACTION, "flavorFor " << mime);
505                 if (mime == pdfMimeType())
506                         return QLatin1String("com.adobe.pdf");
507                 return QString();
508         }
509
510         QString mimeFor(QString flav)
511         {
512                 LYXERR(Debug::ACTION, "mimeFor " << flav);
513                 if (flav == QLatin1String("com.adobe.pdf"))
514                         return pdfMimeType();
515                 return QString();
516         }
517
518         bool canConvert(QString const & mime, QString flav)
519         {
520                 return mimeFor(flav) == mime;
521         }
522
523         QVariant convertToMime(QString const & /*mime*/, QList<QByteArray> data,
524                 QString /*flav*/)
525         {
526                 if(data.count() > 1)
527                         qWarning("QMacPasteboardMimeGraphics: Cannot handle multiple member data");
528                 return data.first();
529         }
530
531         QList<QByteArray> convertFromMime(QString const & /*mime*/,
532                 QVariant data, QString /*flav*/)
533         {
534                 QList<QByteArray> ret;
535                 ret.append(data.toByteArray());
536                 return ret;
537         }
538 };
539 #endif
540
541 ///////////////////////////////////////////////////////////////
542 //
543 // You can find more platform specific stuff at the end of this file...
544 //
545 ///////////////////////////////////////////////////////////////
546
547 ////////////////////////////////////////////////////////////////////////
548 // Windows specific stuff goes here...
549
550 #ifdef Q_WS_WIN
551 // QWindowsMimeMetafile can only be compiled on Windows.
552
553 static FORMATETC cfFromMime(QString const & mimetype)
554 {
555         FORMATETC formatetc;
556         if (mimetype == emfMimeType()) {
557                 formatetc.cfFormat = CF_ENHMETAFILE;
558                 formatetc.tymed = TYMED_ENHMF;
559         } else if (mimetype == wmfMimeType()) {
560                 formatetc.cfFormat = CF_METAFILEPICT;
561                 formatetc.tymed = TYMED_MFPICT;
562         }
563         formatetc.ptd = 0;
564         formatetc.dwAspect = DVASPECT_CONTENT;
565         formatetc.lindex = -1;
566         return formatetc;
567 }
568
569
570 class QWindowsMimeMetafile : public QWindowsMime {
571 public:
572         QWindowsMimeMetafile() {}
573
574         bool canConvertFromMime(FORMATETC const & formatetc,
575                 QMimeData const * mimedata) const
576         {
577                 return false;
578         }
579
580         bool canConvertToMime(QString const & mimetype,
581                 IDataObject * pDataObj) const
582         {
583                 if (mimetype != emfMimeType() && mimetype != wmfMimeType())
584                         return false;
585                 FORMATETC formatetc = cfFromMime(mimetype);
586                 return pDataObj->QueryGetData(&formatetc) == S_OK;
587         }
588
589         bool convertFromMime(FORMATETC const & formatetc,
590                 const QMimeData * mimedata, STGMEDIUM * pmedium) const
591         {
592                 return false;
593         }
594
595         QVariant convertToMime(QString const & mimetype, IDataObject * pDataObj,
596                 QVariant::Type preferredType) const
597         {
598                 QByteArray data;
599                 if (!canConvertToMime(mimetype, pDataObj))
600                         return data;
601
602                 FORMATETC formatetc = cfFromMime(mimetype);
603                 STGMEDIUM s;
604                 if (pDataObj->GetData(&formatetc, &s) != S_OK)
605                         return data;
606
607                 int dataSize;
608                 if (s.tymed == TYMED_ENHMF) {
609                         dataSize = GetEnhMetaFileBits(s.hEnhMetaFile, 0, 0);
610                         data.resize(dataSize);
611                         dataSize = GetEnhMetaFileBits(s.hEnhMetaFile, dataSize,
612                                 (LPBYTE)data.data());
613                 } else if (s.tymed == TYMED_MFPICT) {
614                         dataSize = GetMetaFileBitsEx((HMETAFILE)s.hMetaFilePict, 0, 0);
615                         data.resize(dataSize);
616                         dataSize = GetMetaFileBitsEx((HMETAFILE)s.hMetaFilePict, dataSize,
617                                 (LPBYTE)data.data());
618                 }
619                 data.detach();
620                 ReleaseStgMedium(&s);
621
622                 return data;
623         }
624
625
626         QVector<FORMATETC> formatsForMime(QString const & mimetype,
627                 QMimeData const * mimedata) const
628         {
629                 QVector<FORMATETC> formats;
630                 if (mimetype == emfMimeType() || mimetype == wmfMimeType())
631                         formats += cfFromMime(mimetype);
632                 return formats;
633         }
634
635         QString mimeForFormat(FORMATETC const & formatetc) const
636         {
637                 switch (formatetc.cfFormat) {
638                 case CF_ENHMETAFILE:
639                         return emfMimeType(); 
640                 case CF_METAFILEPICT:
641                         return wmfMimeType();
642                 }
643                 return QString();
644         }
645 };
646
647 #endif // Q_WS_WIN
648
649 ////////////////////////////////////////////////////////////////////////
650 // GuiApplication::Private definition and implementation.
651 ////////////////////////////////////////////////////////////////////////
652
653 struct GuiApplication::Private
654 {
655         Private(): language_model_(0), meta_fake_bit(NoModifier),
656                 global_menubar_(0)
657         {
658         #ifdef Q_WS_WIN
659                 /// WMF Mime handler for Windows clipboard.
660                 wmf_mime_ = new QWindowsMimeMetafile();
661         #endif
662                 initKeySequences(&theTopLevelKeymap());
663         }
664
665         void initKeySequences(KeyMap * kb)
666         {
667                 keyseq = KeySequence(kb, kb);
668                 cancel_meta_seq = KeySequence(kb, kb);
669         }
670
671         ///
672         QSortFilterProxyModel * language_model_;
673         ///
674         GuiClipboard clipboard_;
675         ///
676         GuiSelection selection_;
677         ///
678         FontLoader font_loader_;
679         ///
680         ColorCache color_cache_;
681         ///
682         QTranslator qt_trans_;
683         ///
684         QHash<int, SocketNotifier *> socket_notifiers_;
685         ///
686         Menus menus_;
687         ///
688         /// The global instance
689         Toolbars toolbars_;
690
691         /// this timer is used for any regular events one wants to
692         /// perform. at present it is used to check if forked processes
693         /// are done.
694         QTimer general_timer_;
695
696         /// delayed FuncRequests
697         std::queue<FuncRequest> func_request_queue_;
698
699         ///
700         KeySequence keyseq;
701         ///
702         KeySequence cancel_meta_seq;
703         ///
704         KeyModifier meta_fake_bit;
705
706         /// Multiple views container.
707         /**
708         * Warning: This must not be a smart pointer as the destruction of the
709         * object is handled by Qt when the view is closed
710         * \sa Qt::WA_DeleteOnClose attribute.
711         */
712         QHash<int, GuiView *> views_;
713
714         /// Only used on mac.
715         GlobalMenuBar * global_menubar_;
716
717 #ifdef Q_WS_MACX
718         /// Linkback mime handler for MacOSX.
719         QMacPasteboardMimeGraphics mac_pasteboard_mime_;
720 #endif
721
722 #ifdef Q_WS_WIN
723         /// WMF Mime handler for Windows clipboard.
724         QWindowsMimeMetafile * wmf_mime_;
725 #endif
726 };
727
728
729 GuiApplication * guiApp;
730
731 GuiApplication::~GuiApplication()
732 {
733 #ifdef Q_WS_MACX
734         closeAllLinkBackLinks();
735 #endif
736         delete d;
737 }
738
739
740 GuiApplication::GuiApplication(int & argc, char ** argv)
741         : QApplication(argc, argv), current_view_(0),
742           d(new GuiApplication::Private)
743 {
744         QString app_name = "LyX";
745         QCoreApplication::setOrganizationName(app_name);
746         QCoreApplication::setOrganizationDomain("lyx.org");
747         QCoreApplication::setApplicationName(lyx_package);
748
749         // Install translator for GUI elements.
750         installTranslator(&d->qt_trans_);
751
752         // FIXME: quitOnLastWindowClosed is true by default. We should have a
753         // lyxrc setting for this in order to let the application stay resident.
754         // But then we need some kind of dock icon, at least on Windows.
755         /*
756         if (lyxrc.quit_on_last_window_closed)
757                 setQuitOnLastWindowClosed(false);
758         */
759 #ifdef Q_WS_MACX
760         // FIXME: Do we need a lyxrc setting for this on Mac? This behaviour
761         // seems to be the default case for applications like LyX.
762         setQuitOnLastWindowClosed(false);
763
764         // This allows to translate the strings that appear in the LyX menu.
765         /// A translator suitable for the entries in the LyX menu.
766         /// Only needed with Qt/Mac.
767         installTranslator(new MenuTranslator(this));
768 #endif
769         
770 #ifdef Q_WS_X11
771         // doubleClickInterval() is 400 ms on X11 which is just too long.
772         // On Windows and Mac OS X, the operating system's value is used.
773         // On Microsoft Windows, calling this function sets the double
774         // click interval for all applications. So we don't!
775         QApplication::setDoubleClickInterval(300);
776 #endif
777
778         connect(this, SIGNAL(lastWindowClosed()), this, SLOT(onLastWindowClosed()));
779
780         // needs to be done before reading lyxrc
781         QWidget w;
782         lyxrc.dpi = (w.logicalDpiX() + w.logicalDpiY()) / 2;
783
784         guiApp = this;
785
786         // Set the cache to 5120 kilobytes which corresponds to screen size of
787         // 1280 by 1024 pixels with a color depth of 32 bits.
788         QPixmapCache::setCacheLimit(5120);
789
790         // Initialize RC Fonts
791         if (lyxrc.roman_font_name.empty())
792                 lyxrc.roman_font_name = fromqstr(romanFontName());
793
794         if (lyxrc.sans_font_name.empty())
795                 lyxrc.sans_font_name = fromqstr(sansFontName());
796
797         if (lyxrc.typewriter_font_name.empty())
798                 lyxrc.typewriter_font_name = fromqstr(typewriterFontName());
799
800         d->general_timer_.setInterval(500);
801         connect(&d->general_timer_, SIGNAL(timeout()),
802                 this, SLOT(handleRegularEvents()));
803         d->general_timer_.start();
804 }
805
806
807 GuiApplication * theGuiApp()
808 {
809         return dynamic_cast<GuiApplication *>(theApp());
810 }
811
812
813 void GuiApplication::clearSession()
814 {
815         QSettings settings;
816         settings.clear();
817 }
818
819
820 docstring GuiApplication::iconName(FuncRequest const & f, bool unknown)
821 {
822         return qstring_to_ucs4(lyx::frontend::iconName(f, unknown));
823 }
824
825
826 LyXView * GuiApplication::currentWindow() 
827 {
828 #ifdef Q_WS_MACX
829         /* In LyX/Mac, when a dialog is open, the menus of the
830            application can still be accessed without giving focus to
831            the main window. In this case, we want to disable the menu
832            entries that are buffer or view-related.
833         */
834         if (current_view_ && activeWindow() != current_view_)
835                 return 0;
836 #endif
837         return current_view_;
838 }
839
840
841 bool GuiApplication::getStatus(FuncRequest const & cmd, FuncStatus & flag) const
842 {
843         bool enable = true;
844
845         switch(cmd.action) {
846
847         case LFUN_WINDOW_CLOSE:
848                 enable = d->views_.size() > 0;
849                 break;
850
851         case LFUN_BUFFER_NEW:
852         case LFUN_BUFFER_NEW_TEMPLATE:
853         case LFUN_FILE_OPEN:
854         case LFUN_HELP_OPEN:
855         case LFUN_SCREEN_FONT_UPDATE:
856         case LFUN_SET_COLOR:
857         case LFUN_WINDOW_NEW:
858         case LFUN_LYX_QUIT:
859         case LFUN_LYXRC_APPLY:
860         case LFUN_COMMAND_PREFIX:
861         case LFUN_CANCEL:
862         case LFUN_META_PREFIX:
863         case LFUN_RECONFIGURE:
864         case LFUN_SERVER_GET_FILENAME:
865         case LFUN_SERVER_NOTIFY:
866                 enable = true;
867                 break;
868
869         default:
870                 return false;
871         }
872
873         if (!enable)
874                 flag.setEnabled(false);
875
876         return true;
877 }
878
879
880 // This function runs "configure" and then rereads lyx.defaults to
881 // reconfigure the automatic settings.
882 static void reconfigure(LyXView * lv, string const & option)
883 {
884         // emit message signal.
885         if (lv)
886                 lv->message(_("Running configure..."));
887
888         // Run configure in user lyx directory
889         PathChanger p(package().user_support());
890         string configure_command = package().configure_command();
891         configure_command += option;
892         Systemcall one;
893         int ret = one.startscript(Systemcall::Wait, configure_command);
894         p.pop();
895         // emit message signal.
896         if (lv)
897                 lv->message(_("Reloading configuration..."));
898         lyxrc.read(libFileSearch(QString(), "lyxrc.defaults"));
899         // Re-read packages.lst
900         LaTeXFeatures::getAvailable();
901
902         if (ret)
903                 Alert::information(_("System reconfiguration failed"),
904                            _("The system reconfiguration has failed.\n"
905                                   "Default textclass is used but LyX may "
906                                   "not be able to work properly.\n"
907                                   "Please reconfigure again if needed."));
908         else
909
910                 Alert::information(_("System reconfigured"),
911                            _("The system has been reconfigured.\n"
912                              "You need to restart LyX to make use of any\n"
913                              "updated document class specifications."));
914 }
915
916
917 void GuiApplication::dispatch(FuncRequest const & cmd, DispatchResult & dr)
918 {
919         switch (cmd.action) {
920
921         case LFUN_WINDOW_NEW:
922                 createView(toqstr(cmd.argument()));
923                 break;
924
925         case LFUN_WINDOW_CLOSE:
926                 // update bookmark pit of the current buffer before window close
927                 for (size_t i = 0; i < theSession().bookmarks().size(); ++i)
928                         theLyXFunc().gotoBookmark(i+1, false, false);
929                 // clear the last opened list, because
930                 // maybe this will end the session
931                 theSession().lastOpened().clear();
932                 current_view_->close();
933                 break;
934
935         case LFUN_LYX_QUIT:
936                 // quitting is triggered by the gui code
937                 // (leaving the event loop).
938                 if (current_view_)
939                         current_view_->message(from_utf8(N_("Exiting.")));
940                 if (closeAllViews())
941                         quit();
942                 break;
943
944         case LFUN_SCREEN_FONT_UPDATE: {
945                 // handle the screen font changes.
946                 d->font_loader_.update();
947                 // Backup current_view_
948                 GuiView * view = current_view_;
949                 // Set current_view_ to zero to forbid GuiWorkArea::redraw()
950                 // to skip the refresh.
951                 current_view_ = 0;
952                 BufferList::iterator it = theBufferList().begin();
953                 BufferList::iterator const end = theBufferList().end();
954                 for (; it != end; ++it)
955                         (*it)->changed();
956                 // Restore current_view_
957                 current_view_ = view;
958                 break;
959         }
960
961         case LFUN_BUFFER_NEW:
962                 if (d->views_.empty()
963                     || (!lyxrc.open_buffers_in_tabs && current_view_->documentBufferView() != 0)) {
964                         createView(QString(), false); // keep hidden
965                         current_view_->newDocument(to_utf8(cmd.argument()), false);
966                         current_view_->show();
967                         setActiveWindow(current_view_);
968                 } else {
969                         current_view_->newDocument(to_utf8(cmd.argument()), false);
970                 }
971                 break;
972
973         case LFUN_BUFFER_NEW_TEMPLATE:
974                 if (d->views_.empty()
975                     || (!lyxrc.open_buffers_in_tabs && current_view_->documentBufferView() != 0)) {
976                         createView();
977                         current_view_->newDocument(to_utf8(cmd.argument()), true);
978                         if (!current_view_->documentBufferView())
979                                 current_view_->close();
980                 } else {
981                         current_view_->newDocument(to_utf8(cmd.argument()), true);
982                 }
983                 break;
984
985         case LFUN_FILE_OPEN:
986                 // FIXME: create a new method shared with LFUN_HELP_OPEN.
987                 if (d->views_.empty()
988                     || (!lyxrc.open_buffers_in_tabs && current_view_->documentBufferView() != 0)) {
989                         string const fname = to_utf8(cmd.argument());
990                         // We want the ui session to be saved per document and not per
991                         // window number. The filename crc is a good enough identifier.
992                         boost::crc_32_type crc;
993                         crc = for_each(fname.begin(), fname.end(), crc);
994                         createView(crc.checksum());
995                         current_view_->openDocument(fname);
996                         if (current_view_ && !current_view_->documentBufferView())
997                                 current_view_->close();
998                 } else
999                         current_view_->openDocument(to_utf8(cmd.argument()));
1000                 break;
1001
1002         case LFUN_HELP_OPEN: {
1003                 // FIXME: create a new method shared with LFUN_FILE_OPEN.
1004                 if (current_view_ == 0)
1005                         createView();
1006                 string const arg = to_utf8(cmd.argument());
1007                 if (arg.empty()) {
1008                         current_view_->message(_("Missing argument"));
1009                         break;
1010                 }
1011                 FileName fname = i18nLibFileSearch("doc", arg, "lyx");
1012                 if (fname.empty()) 
1013                         fname = i18nLibFileSearch("examples", arg, "lyx");
1014
1015                 if (fname.empty()) {
1016                         lyxerr << "LyX: unable to find documentation file `"
1017                                 << arg << "'. Bad installation?" << endl;
1018                         break;
1019                 }
1020                 current_view_->message(bformat(_("Opening help file %1$s..."),
1021                         makeDisplayPath(fname.absFilename())));
1022                 Buffer * buf = current_view_->loadDocument(fname, false);
1023                 if (buf) {
1024                         current_view_->setBuffer(buf);
1025                         buf->updateLabels();
1026                         buf->errors("Parse");
1027                 }
1028                 break;
1029         }
1030
1031         case LFUN_SET_COLOR: {
1032                 string lyx_name;
1033                 string const x11_name = split(to_utf8(cmd.argument()), lyx_name, ' ');
1034                 if (lyx_name.empty() || x11_name.empty()) {
1035                         current_view_->message(
1036                                 _("Syntax: set-color <lyx_name> <x11_name>"));
1037                         break;
1038                 }
1039
1040                 string const graphicsbg = lcolor.getLyXName(Color_graphicsbg);
1041                 bool const graphicsbg_changed = lyx_name == graphicsbg
1042                         && x11_name != graphicsbg;
1043                 if (graphicsbg_changed) {
1044                         // FIXME: The graphics cache no longer has a changeDisplay method.
1045 #if 0
1046                         graphics::GCache::get().changeDisplay(true);
1047 #endif
1048                 }
1049
1050                 if (!lcolor.setColor(lyx_name, x11_name)) {
1051                         current_view_->message(
1052                                         bformat(_("Set-color \"%1$s\" failed "
1053                                                                "- color is undefined or "
1054                                                                "may not be redefined"),
1055                                                                    from_utf8(lyx_name)));
1056                         break;
1057                 }
1058                 // Make sure we don't keep old colors in cache.
1059                 d->color_cache_.clear();
1060                 break;
1061         }
1062
1063         case LFUN_LYXRC_APPLY: {
1064                 // reset active key sequences, since the bindings
1065                 // are updated (bug 6064)
1066                 d->keyseq.reset();
1067                 LyXRC const lyxrc_orig = lyxrc;
1068
1069                 istringstream ss(to_utf8(cmd.argument()));
1070                 bool const success = lyxrc.read(ss) == 0;
1071
1072                 if (!success) {
1073                         lyxerr << "Warning in LFUN_LYXRC_APPLY!\n"
1074                                 << "Unable to read lyxrc data"
1075                                 << endl;
1076                         break;
1077                 }
1078
1079                 actOnUpdatedPrefs(lyxrc_orig, lyxrc);
1080                 setSpellChecker();
1081                 resetGui();
1082
1083                 break;
1084         }
1085
1086         case LFUN_COMMAND_PREFIX:
1087                 lyx::dispatch(FuncRequest(LFUN_MESSAGE, d->keyseq.printOptions(true)));
1088                 break;
1089
1090         case LFUN_CANCEL: {
1091                 d->keyseq.reset();
1092                 d->meta_fake_bit = NoModifier;
1093                 GuiView * gv = currentView();
1094                 if (gv && gv->currentBufferView())
1095                         // cancel any selection
1096                         lyx::dispatch(FuncRequest(LFUN_MARK_OFF));
1097                 dr.setMessage(from_ascii(N_("Cancel")));
1098                 break;
1099         }
1100         case LFUN_META_PREFIX:
1101                 d->meta_fake_bit = AltModifier;
1102                 dr.setMessage(d->keyseq.print(KeySequence::ForGui));
1103                 break;
1104
1105         // --- Menus -----------------------------------------------
1106         case LFUN_RECONFIGURE:
1107                 // argument is any additional parameter to the configure.py command
1108                 reconfigure(currentView(), to_utf8(cmd.argument()));
1109                 break;
1110
1111         // --- lyxserver commands ----------------------------
1112         case LFUN_SERVER_GET_FILENAME: {
1113                 GuiView * lv = currentView();
1114                 LASSERT(lv && lv->documentBufferView(), return);
1115                 docstring const fname = from_utf8(
1116                         lv->documentBufferView()->buffer().absFileName());
1117                 dr.setMessage(fname);
1118                 LYXERR(Debug::INFO, "FNAME[" << fname << ']');
1119                 break;
1120         }
1121         case LFUN_SERVER_NOTIFY: {
1122                 docstring const dispatch_buffer = d->keyseq.print(KeySequence::Portable);
1123                 dr.setMessage(dispatch_buffer);
1124                 theServer().notifyClient(to_utf8(dispatch_buffer));
1125                 break;
1126         }
1127         default:
1128                 // Notify the caller that the action has not been dispatched.
1129                 dr.dispatched(false);
1130                 return;
1131         }
1132
1133         // The action has been dispatched.
1134         dr.dispatched(true);
1135 }
1136
1137
1138 docstring GuiApplication::viewStatusMessage()
1139 {
1140         // When meta-fake key is pressed, show the key sequence so far + "M-".
1141         if (d->meta_fake_bit != NoModifier)
1142                 return d->keyseq.print(KeySequence::ForGui) + "M-";
1143
1144         // Else, when a non-complete key sequence is pressed,
1145         // show the available options.
1146         if (d->keyseq.length() > 0 && !d->keyseq.deleted())
1147                 return d->keyseq.printOptions(true);
1148
1149         return docstring();
1150 }
1151
1152
1153 void GuiApplication::handleKeyFunc(FuncCode action)
1154 {
1155         char_type c = 0;
1156
1157         if (d->keyseq.length())
1158                 c = 0;
1159         GuiView * gv = currentView();
1160         LASSERT(gv && gv->currentBufferView(), return);
1161         BufferView * bv = gv->currentBufferView();
1162         bv->getIntl().getTransManager().deadkey(
1163                 c, get_accent(action).accent, bv->cursor().innerText(),
1164                 bv->cursor());
1165         // Need to clear, in case the minibuffer calls these
1166         // actions
1167         d->keyseq.clear();
1168         // copied verbatim from do_accent_char
1169         bv->cursor().resetAnchor();
1170         bv->processUpdateFlags(Update::FitCursor);
1171 }
1172
1173
1174 void GuiApplication::processKeySym(KeySymbol const & keysym, KeyModifier state)
1175 {
1176         LYXERR(Debug::KEY, "KeySym is " << keysym.getSymbolName());
1177
1178         GuiView * lv = currentView();
1179
1180         // Do nothing if we have nothing (JMarc)
1181         if (!keysym.isOK()) {
1182                 LYXERR(Debug::KEY, "Empty kbd action (probably composing)");
1183                 lv->restartCursor();
1184                 return;
1185         }
1186
1187         if (keysym.isModifier()) {
1188                 LYXERR(Debug::KEY, "isModifier true");
1189                 if (lv)
1190                         lv->restartCursor();
1191                 return;
1192         }
1193
1194         char_type encoded_last_key = keysym.getUCSEncoded();
1195
1196         // Do a one-deep top-level lookup for
1197         // cancel and meta-fake keys. RVDK_PATCH_5
1198         d->cancel_meta_seq.reset();
1199
1200         FuncRequest func = d->cancel_meta_seq.addkey(keysym, state);
1201         LYXERR(Debug::KEY, "action first set to [" << func.action << ']');
1202
1203         // When not cancel or meta-fake, do the normal lookup.
1204         // Note how the meta_fake Mod1 bit is OR-ed in and reset afterwards.
1205         // Mostly, meta_fake_bit = NoModifier. RVDK_PATCH_5.
1206         if ((func.action != LFUN_CANCEL) && (func.action != LFUN_META_PREFIX)) {
1207                 // remove Caps Lock and Mod2 as a modifiers
1208                 func = d->keyseq.addkey(keysym, (state | d->meta_fake_bit));
1209                 LYXERR(Debug::KEY, "action now set to [" << func.action << ']');
1210         }
1211
1212         // Dont remove this unless you know what you are doing.
1213         d->meta_fake_bit = NoModifier;
1214
1215         // Can this happen now ?
1216         if (func.action == LFUN_NOACTION)
1217                 func = FuncRequest(LFUN_COMMAND_PREFIX);
1218
1219         LYXERR(Debug::KEY, " Key [action=" << func.action << "]["
1220                 << d->keyseq.print(KeySequence::Portable) << ']');
1221
1222         // already here we know if it any point in going further
1223         // why not return already here if action == -1 and
1224         // num_bytes == 0? (Lgb)
1225
1226         if (d->keyseq.length() > 1)
1227                 lv->message(d->keyseq.print(KeySequence::ForGui));
1228
1229
1230         // Maybe user can only reach the key via holding down shift.
1231         // Let's see. But only if shift is the only modifier
1232         if (func.action == LFUN_UNKNOWN_ACTION && state == ShiftModifier) {
1233                 LYXERR(Debug::KEY, "Trying without shift");
1234                 func = d->keyseq.addkey(keysym, NoModifier);
1235                 LYXERR(Debug::KEY, "Action now " << func.action);
1236         }
1237
1238         if (func.action == LFUN_UNKNOWN_ACTION) {
1239                 // Hmm, we didn't match any of the keysequences. See
1240                 // if it's normal insertable text not already covered
1241                 // by a binding
1242                 if (keysym.isText() && d->keyseq.length() == 1) {
1243                         LYXERR(Debug::KEY, "isText() is true, inserting.");
1244                         func = FuncRequest(LFUN_SELF_INSERT,
1245                                            FuncRequest::KEYBOARD);
1246                 } else {
1247                         LYXERR(Debug::KEY, "Unknown, !isText() - giving up");
1248                         lv->message(_("Unknown function."));
1249                         lv->restartCursor();
1250                         return;
1251                 }
1252         }
1253
1254         if (func.action == LFUN_SELF_INSERT) {
1255                 if (encoded_last_key != 0) {
1256                         docstring const arg(1, encoded_last_key);
1257                         lyx::dispatch(FuncRequest(LFUN_SELF_INSERT, arg,
1258                                              FuncRequest::KEYBOARD));
1259                         LYXERR(Debug::KEY, "SelfInsert arg[`" << to_utf8(arg) << "']");
1260                 }
1261         } else {
1262                 lyx::dispatch(func);
1263                 if (!lv)
1264                         return;
1265         }
1266 }
1267
1268
1269 void GuiApplication::dispatchDelayed(FuncRequest const & func)
1270 {
1271         d->func_request_queue_.push(func);
1272         QTimer::singleShot(0, this, SLOT(processFuncRequestQueue()));
1273 }
1274
1275
1276 void GuiApplication::resetGui()
1277 {
1278         // Set the language defined by the user.
1279         setGuiLanguage();
1280
1281         // Read menus
1282         if (!readUIFile(toqstr(lyxrc.ui_file)))
1283                 // Gives some error box here.
1284                 return;
1285
1286         if (d->global_menubar_)
1287                 d->menus_.fillMenuBar(d->global_menubar_, 0, false);
1288
1289         QHash<int, GuiView *>::iterator it;
1290         for (it = d->views_.begin(); it != d->views_.end(); ++it) {
1291                 GuiView * gv = *it;
1292                 setCurrentView(gv);
1293                 gv->setLayoutDirection(layoutDirection());
1294                 gv->resetDialogs();
1295         }
1296
1297         lyx::dispatch(FuncRequest(LFUN_SCREEN_FONT_UPDATE));
1298 }
1299
1300
1301 void GuiApplication::createView(int view_id)
1302 {
1303         createView(QString(), true, view_id);
1304 }
1305
1306
1307 void GuiApplication::createView(QString const & geometry_arg, bool autoShow,
1308         int view_id)
1309 {
1310         // release the keyboard which might have been grabed by the global
1311         // menubar on Mac to catch shortcuts even without any GuiView.
1312         if (d->global_menubar_)
1313                 d->global_menubar_->releaseKeyboard();
1314
1315         // create new view
1316         int id = view_id;
1317         while (d->views_.find(id) != d->views_.end())
1318                 id++;
1319
1320         LYXERR(Debug::GUI, "About to create new window with ID " << id);
1321         GuiView * view = new GuiView(id);
1322         // register view
1323         d->views_[id] = view;
1324
1325         if (autoShow) {
1326                 view->show();
1327                 setActiveWindow(view);
1328         }
1329
1330         if (!geometry_arg.isEmpty()) {
1331 #ifdef Q_WS_WIN
1332                 int x, y;
1333                 int w, h;
1334                 QRegExp re( "[=]*(?:([0-9]+)[xX]([0-9]+)){0,1}[ ]*(?:([+-][0-9]*)([+-][0-9]*)){0,1}" );
1335                 re.indexIn(geometry_arg);
1336                 w = re.cap(1).toInt();
1337                 h = re.cap(2).toInt();
1338                 x = re.cap(3).toInt();
1339                 y = re.cap(4).toInt();
1340                 view->setGeometry(x, y, w, h);
1341 #endif
1342         }
1343         view->setFocus();
1344 }
1345
1346
1347 Clipboard & GuiApplication::clipboard()
1348 {
1349         return d->clipboard_;
1350 }
1351
1352
1353 Selection & GuiApplication::selection()
1354 {
1355         return d->selection_;
1356 }
1357
1358
1359 FontLoader & GuiApplication::fontLoader() 
1360 {
1361         return d->font_loader_;
1362 }
1363
1364
1365 Toolbars const & GuiApplication::toolbars() const 
1366 {
1367         return d->toolbars_;
1368 }
1369
1370
1371 Toolbars & GuiApplication::toolbars()
1372 {
1373         return d->toolbars_; 
1374 }
1375
1376
1377 Menus const & GuiApplication::menus() const 
1378 {
1379         return d->menus_;
1380 }
1381
1382
1383 Menus & GuiApplication::menus()
1384 {
1385         return d->menus_; 
1386 }
1387
1388
1389 QList<int> GuiApplication::viewIds() const
1390 {
1391         return d->views_.keys();
1392 }
1393
1394
1395 ColorCache & GuiApplication::colorCache()
1396 {
1397         return d->color_cache_;
1398 }
1399
1400
1401 int GuiApplication::exec()
1402 {
1403         // asynchronously handle batch commands. This event will be in
1404         // the event queue in front of other asynchronous events. Hence,
1405         // we can assume in the latter that the gui is setup already.
1406         QTimer::singleShot(0, this, SLOT(execBatchCommands()));
1407
1408         return QApplication::exec();
1409 }
1410
1411
1412 void GuiApplication::exit(int status)
1413 {
1414         QApplication::exit(status);
1415 }
1416
1417
1418 void GuiApplication::setGuiLanguage()
1419 {
1420         // Set the language defined by the user.
1421         setRcGuiLanguage();
1422
1423         QString const default_language = toqstr(Messages::defaultLanguage());
1424         LYXERR(Debug::LOCALE, "Tring to set default locale to: " << default_language);
1425         QLocale const default_locale(default_language);
1426         QLocale::setDefault(default_locale);
1427
1428         // install translation file for Qt built-in dialogs
1429         QString const language_name = QString("qt_") + default_locale.name();
1430
1431         // language_name can be short (e.g. qt_zh) or long (e.g. qt_zh_CN). 
1432         // Short-named translator can be loaded from a long name, but not the
1433         // opposite. Therefore, long name should be used without truncation.
1434         // c.f. http://doc.trolltech.com/4.1/qtranslator.html#load
1435         if (!d->qt_trans_.load(language_name,
1436                         QLibraryInfo::location(QLibraryInfo::TranslationsPath))) {
1437                 LYXERR(Debug::LOCALE, "Could not find  Qt translations for locale "
1438                         << language_name);
1439         } else {
1440                 LYXERR(Debug::LOCALE, "Successfully installed Qt translations for locale "
1441                         << language_name);
1442         }
1443
1444         switch (default_locale.language()) {
1445         case QLocale::Arabic :
1446         case QLocale::Hebrew :
1447         case QLocale::Persian :
1448         case QLocale::Urdu :
1449         setLayoutDirection(Qt::RightToLeft);
1450                 break;
1451         default:
1452         setLayoutDirection(Qt::LeftToRight);
1453         }
1454 }
1455
1456
1457 void GuiApplication::processFuncRequestQueue()
1458 {
1459         while (!d->func_request_queue_.empty()) {
1460                 lyx::dispatch(d->func_request_queue_.back());
1461                 d->func_request_queue_.pop();
1462         }
1463 }
1464
1465
1466 void GuiApplication::execBatchCommands()
1467 {
1468         setGuiLanguage();
1469
1470         // Read menus
1471         if (!readUIFile(toqstr(lyxrc.ui_file)))
1472                 // Gives some error box here.
1473                 return;
1474
1475 #ifdef Q_WS_MACX
1476         // Create the global default menubar which is shown for the dialogs
1477         // and if no GuiView is visible.
1478         // This must be done after the session was recovered to know the "last files".
1479         d->global_menubar_ = new GlobalMenuBar();
1480         d->menus_.fillMenuBar(d->global_menubar_, 0, true);
1481 #endif
1482
1483         lyx::execBatchCommands();
1484 }
1485
1486
1487 QAbstractItemModel * GuiApplication::languageModel()
1488 {
1489         if (d->language_model_)
1490                 return d->language_model_;
1491
1492         QStandardItemModel * lang_model = new QStandardItemModel(this);
1493         lang_model->insertColumns(0, 1);
1494         int current_row;
1495         Languages::const_iterator it = languages.begin();
1496         Languages::const_iterator end = languages.end();
1497         for (; it != end; ++it) {
1498                 current_row = lang_model->rowCount();
1499                 lang_model->insertRows(current_row, 1);
1500                 QModelIndex item = lang_model->index(current_row, 0);
1501                 lang_model->setData(item, qt_(it->second.display()), Qt::DisplayRole);
1502                 lang_model->setData(item, toqstr(it->second.lang()), Qt::UserRole);
1503         }
1504         d->language_model_ = new QSortFilterProxyModel(this);
1505         d->language_model_->setSourceModel(lang_model);
1506 #if QT_VERSION >= 0x040300
1507         d->language_model_->setSortLocaleAware(true);
1508 #endif
1509         return d->language_model_;
1510 }
1511
1512
1513 void GuiApplication::restoreGuiSession()
1514 {
1515         if (!lyxrc.load_session)
1516                 return;
1517
1518         Session & session = theSession();
1519         LastOpenedSection::LastOpened const & lastopened = 
1520                 session.lastOpened().getfiles();
1521
1522         FileName active_file;
1523         // do not add to the lastfile list since these files are restored from
1524         // last session, and should be already there (regular files), or should
1525         // not be added at all (help files).
1526         for (size_t i = 0; i < lastopened.size(); ++i) {
1527                 FileName const & file_name = lastopened[i].file_name;
1528                 if (d->views_.empty() || (!lyxrc.open_buffers_in_tabs
1529                           && current_view_->documentBufferView() != 0)) {
1530                         boost::crc_32_type crc;
1531                         string const & fname = file_name.absFilename();
1532                         crc = for_each(fname.begin(), fname.end(), crc);
1533                         createView(crc.checksum());
1534                 }
1535                 current_view_->loadDocument(file_name, false);
1536
1537                 if (lastopened[i].active)
1538                         active_file = file_name;
1539         }
1540
1541         // Restore last active buffer
1542         Buffer * buffer = theBufferList().getBuffer(active_file);
1543         if (buffer)
1544                 current_view_->setBuffer(buffer);
1545
1546         // clear this list to save a few bytes of RAM
1547         session.lastOpened().clear();
1548 }
1549
1550
1551 QString const GuiApplication::romanFontName()
1552 {
1553         QFont font;
1554         font.setKerning(false);
1555         font.setStyleHint(QFont::Serif);
1556         font.setFamily("serif");
1557
1558         return QFontInfo(font).family();
1559 }
1560
1561
1562 QString const GuiApplication::sansFontName()
1563 {
1564         QFont font;
1565         font.setKerning(false);
1566         font.setStyleHint(QFont::SansSerif);
1567         font.setFamily("sans");
1568
1569         return QFontInfo(font).family();
1570 }
1571
1572
1573 QString const GuiApplication::typewriterFontName()
1574 {
1575         QFont font;
1576         font.setKerning(false);
1577         font.setStyleHint(QFont::TypeWriter);
1578         font.setFamily("monospace");
1579
1580         return QFontInfo(font).family();
1581 }
1582
1583
1584 void GuiApplication::handleRegularEvents()
1585 {
1586         ForkedCallsController::handleCompletedProcesses();
1587 }
1588
1589
1590 bool GuiApplication::event(QEvent * e)
1591 {
1592         switch(e->type()) {
1593         case QEvent::FileOpen: {
1594                 // Open a file; this happens only on Mac OS X for now.
1595                 //
1596                 // We do this asynchronously because on startup the batch
1597                 // commands are not executed here yet and the gui is not ready
1598                 // therefore.
1599                 QFileOpenEvent * foe = static_cast<QFileOpenEvent *>(e);
1600                 dispatchDelayed(FuncRequest(LFUN_FILE_OPEN, qstring_to_ucs4(foe->file())));
1601                 e->accept();
1602                 return true;
1603         }
1604         default:
1605                 return QApplication::event(e);
1606         }
1607 }
1608
1609
1610 bool GuiApplication::notify(QObject * receiver, QEvent * event)
1611 {
1612         try {
1613                 return QApplication::notify(receiver, event);
1614         }
1615         catch (ExceptionMessage const & e) {
1616                 switch(e.type_) { 
1617                 case ErrorException:
1618                         emergencyCleanup();
1619                         setQuitOnLastWindowClosed(false);
1620                         closeAllViews();
1621                         Alert::error(e.title_, e.details_);
1622 #ifndef NDEBUG
1623                         // Properly crash in debug mode in order to get a useful backtrace.
1624                         abort();
1625 #endif
1626                         // In release mode, try to exit gracefully.
1627                         this->exit(1);
1628
1629                 case BufferException: {
1630                         if (!current_view_->documentBufferView())
1631                                 return false;
1632                         Buffer * buf = &current_view_->documentBufferView()->buffer();
1633                         docstring details = e.details_ + '\n';
1634                         details += buf->emergencyWrite();
1635                         theBufferList().release(buf);
1636                         details += "\n" + _("The current document was closed.");
1637                         Alert::error(e.title_, details);
1638                         return false;
1639                 }
1640                 case WarningException:
1641                         Alert::warning(e.title_, e.details_);
1642                         return false;
1643                 }
1644         }
1645         catch (exception const & e) {
1646                 docstring s = _("LyX has caught an exception, it will now "
1647                         "attempt to save all unsaved documents and exit."
1648                         "\n\nException: ");
1649                 s += from_ascii(e.what());
1650                 Alert::error(_("Software exception Detected"), s);
1651                 lyx_exit(1);
1652         }
1653         catch (...) {
1654                 docstring s = _("LyX has caught some really weird exception, it will "
1655                         "now attempt to save all unsaved documents and exit.");
1656                 Alert::error(_("Software exception Detected"), s);
1657                 lyx_exit(1);
1658         }
1659
1660         return false;
1661 }
1662
1663
1664 bool GuiApplication::getRgbColor(ColorCode col, RGBColor & rgbcol)
1665 {
1666         QColor const & qcol = d->color_cache_.get(col);
1667         if (!qcol.isValid()) {
1668                 rgbcol.r = 0;
1669                 rgbcol.g = 0;
1670                 rgbcol.b = 0;
1671                 return false;
1672         }
1673         rgbcol.r = qcol.red();
1674         rgbcol.g = qcol.green();
1675         rgbcol.b = qcol.blue();
1676         return true;
1677 }
1678
1679
1680 string const GuiApplication::hexName(ColorCode col)
1681 {
1682         return ltrim(fromqstr(d->color_cache_.get(col).name()), "#");
1683 }
1684
1685
1686 void GuiApplication::registerSocketCallback(int fd, SocketCallback func)
1687 {
1688         SocketNotifier * sn = new SocketNotifier(this, fd, func);
1689         d->socket_notifiers_[fd] = sn;
1690         connect(sn, SIGNAL(activated(int)), this, SLOT(socketDataReceived(int)));
1691 }
1692
1693
1694 void GuiApplication::socketDataReceived(int fd)
1695 {
1696         d->socket_notifiers_[fd]->func_();
1697 }
1698
1699
1700 void GuiApplication::unregisterSocketCallback(int fd)
1701 {
1702         d->socket_notifiers_.take(fd)->setEnabled(false);
1703 }
1704
1705
1706 void GuiApplication::commitData(QSessionManager & sm)
1707 {
1708         /// The implementation is required to avoid an application exit
1709         /// when session state save is triggered by session manager.
1710         /// The default implementation sends a close event to all
1711         /// visible top level widgets when session managment allows
1712         /// interaction.
1713         /// We are changing that to close all wiew one by one.
1714         /// FIXME: verify if the default implementation is enough now.
1715         if (sm.allowsInteraction() && !closeAllViews())
1716                 sm.cancel();
1717 }
1718
1719
1720 void GuiApplication::unregisterView(GuiView * gv)
1721 {
1722         LASSERT(d->views_[gv->id()] == gv, /**/);
1723         d->views_.remove(gv->id());
1724         if (current_view_ == gv)
1725                 current_view_ = 0;
1726 }
1727
1728
1729 bool GuiApplication::closeAllViews()
1730 {
1731         if (d->views_.empty())
1732                 return true;
1733
1734         // When a view/window was closed before without quitting LyX, there
1735         // are already entries in the lastOpened list.
1736         theSession().lastOpened().clear();
1737
1738         QList<GuiView *> views = d->views_.values();
1739         foreach (GuiView * view, views) {
1740                 if (!view->close())
1741                         return false;
1742         }
1743
1744         d->views_.clear();
1745         return true;
1746 }
1747
1748
1749 GuiView & GuiApplication::view(int id) const
1750 {
1751         LASSERT(d->views_.contains(id), /**/);
1752         return *d->views_.value(id);
1753 }
1754
1755
1756 void GuiApplication::hideDialogs(string const & name, Inset * inset) const
1757 {
1758         QList<GuiView *> views = d->views_.values();
1759         foreach (GuiView * view, views)
1760                 view->hideDialog(name, inset);
1761 }
1762
1763
1764 Buffer const * GuiApplication::updateInset(Inset const * inset) const
1765 {
1766         Buffer const * buffer_ = 0;
1767         QHash<int, GuiView *>::iterator end = d->views_.end();
1768         for (QHash<int, GuiView *>::iterator it = d->views_.begin(); it != end; ++it) {
1769                 if (Buffer const * ptr = (*it)->updateInset(inset))
1770                         buffer_ = ptr;
1771         }
1772         return buffer_;
1773 }
1774
1775
1776 bool GuiApplication::searchMenu(FuncRequest const & func,
1777         docstring_list & names) const
1778 {
1779         return d->menus_.searchMenu(func, names);
1780 }
1781
1782
1783 bool GuiApplication::readUIFile(QString const & name, bool include)
1784 {
1785         LYXERR(Debug::INIT, "About to read " << name << "...");
1786
1787         FileName ui_path;
1788         if (include) {
1789                 ui_path = libFileSearch("ui", name, "inc");
1790                 if (ui_path.empty())
1791                         ui_path = libFileSearch("ui", changeExtension(name, "inc"));
1792         } else {
1793                 ui_path = libFileSearch("ui", name, "ui");
1794         }
1795
1796         if (ui_path.empty()) {
1797                 static const QString defaultUIFile = "default";
1798                 LYXERR(Debug::INIT, "Could not find " << name);
1799                 if (include) {
1800                         Alert::warning(_("Could not find UI definition file"),
1801                                 bformat(_("Error while reading the included file\n%1$s\n"
1802                                         "Please check your installation."), qstring_to_ucs4(name)));
1803                         return false;
1804                 }
1805                 if (name == defaultUIFile) {
1806                         LYXERR(Debug::INIT, "Could not find default UI file!!");
1807                         Alert::warning(_("Could not find default UI file"),
1808                                 _("LyX could not find the default UI file!\n"
1809                                   "Please check your installation."));
1810                         return false;
1811                 }
1812                 Alert::warning(_("Could not find UI definition file"),
1813                 bformat(_("Error while reading the configuration file\n%1$s\n"
1814                         "Falling back to default.\n"
1815                         "Please look under Tools>Preferences>User Interface and\n"
1816                         "check which User Interface file you are using."), qstring_to_ucs4(name)));
1817                 return readUIFile(defaultUIFile, false);
1818         }
1819
1820         // Ensure that a file is read only once (prevents include loops)
1821         static QStringList uifiles;
1822         QString const uifile = toqstr(ui_path.absFilename());
1823         if (uifiles.contains(uifile)) {
1824                 if (!include) {
1825                         // We are reading again the top uifile so reset the safeguard:
1826                         uifiles.clear();
1827                         d->menus_.reset();
1828                         d->toolbars_.reset();
1829                 } else {
1830                         LYXERR(Debug::INIT, "UI file '" << name << "' has been read already. "
1831                                 << "Is this an include loop?");
1832                         return false;
1833                 }
1834         }
1835         uifiles.push_back(uifile);
1836
1837         LYXERR(Debug::INIT, "Found " << name << " in " << ui_path);
1838
1839         enum {
1840                 ui_menuset = 1,
1841                 ui_toolbars,
1842                 ui_toolbarset,
1843                 ui_include,
1844                 ui_last
1845         };
1846
1847         LexerKeyword uitags[] = {
1848                 { "include", ui_include },
1849                 { "menuset", ui_menuset },
1850                 { "toolbars", ui_toolbars },
1851                 { "toolbarset", ui_toolbarset }
1852         };
1853
1854         Lexer lex(uitags);
1855         lex.setFile(ui_path);
1856         if (!lex.isOK()) {
1857                 lyxerr << "Unable to set LyXLeX for ui file: " << ui_path
1858                        << endl;
1859         }
1860
1861         if (lyxerr.debugging(Debug::PARSER))
1862                 lex.printTable(lyxerr);
1863
1864         // store which ui files define Toolbars
1865         static QStringList toolbar_uifiles;
1866
1867         while (lex.isOK()) {
1868                 switch (lex.lex()) {
1869                 case ui_include: {
1870                         lex.next(true);
1871                         QString const file = toqstr(lex.getString());
1872                         if (!readUIFile(file, true))
1873                                 return false;
1874                         break;
1875                 }
1876                 case ui_menuset:
1877                         d->menus_.read(lex);
1878                         break;
1879
1880                 case ui_toolbarset:
1881                         d->toolbars_.readToolbars(lex);
1882                         break;
1883
1884                 case ui_toolbars:
1885                         d->toolbars_.readToolbarSettings(lex);
1886                         toolbar_uifiles.push_back(uifile);
1887                         break;
1888
1889                 default:
1890                         if (!rtrim(lex.getString()).empty())
1891                                 lex.printError("LyX::ReadUIFile: "
1892                                                "Unknown menu tag: `$$Token'");
1893                         break;
1894                 }
1895         }
1896
1897         if (include)
1898                 return true;
1899
1900         QSettings settings;
1901         settings.beginGroup("ui_files");
1902         bool touched = false;
1903         for (int i = 0; i != uifiles.size(); ++i) {
1904                 QFileInfo fi(uifiles[i]);
1905                 QDateTime const date_value = fi.lastModified();
1906                 QString const name_key = QString::number(i);
1907                 // if an ui file which defines Toolbars has changed,
1908                 // we have to reset the settings
1909                 if (toolbar_uifiles.contains(uifiles[i])
1910                  && (!settings.contains(name_key)
1911                  || settings.value(name_key).toString() != uifiles[i]
1912                  || settings.value(name_key + "/date").toDateTime() != date_value)) {
1913                         touched = true;
1914                         settings.setValue(name_key, uifiles[i]);
1915                         settings.setValue(name_key + "/date", date_value);
1916                 }
1917         }
1918         settings.endGroup();
1919         if (touched)
1920                 settings.remove("views");
1921
1922         return true;
1923 }
1924
1925
1926 void GuiApplication::onLastWindowClosed()
1927 {
1928         if (d->global_menubar_)
1929                 d->global_menubar_->grabKeyboard();
1930 }
1931
1932
1933 ////////////////////////////////////////////////////////////////////////
1934 //
1935 // X11 specific stuff goes here...
1936
1937 #ifdef Q_WS_X11
1938 bool GuiApplication::x11EventFilter(XEvent * xev)
1939 {
1940         if (!current_view_)
1941                 return false;
1942
1943         switch (xev->type) {
1944         case SelectionRequest: {
1945                 if (xev->xselectionrequest.selection != XA_PRIMARY)
1946                         break;
1947                 LYXERR(Debug::SELECTION, "X requested selection.");
1948                 BufferView * bv = current_view_->currentBufferView();
1949                 if (bv) {
1950                         docstring const sel = bv->requestSelection();
1951                         if (!sel.empty())
1952                                 d->selection_.put(sel);
1953                 }
1954                 break;
1955         }
1956         case SelectionClear: {
1957                 if (xev->xselectionclear.selection != XA_PRIMARY)
1958                         break;
1959                 LYXERR(Debug::SELECTION, "Lost selection.");
1960                 BufferView * bv = current_view_->currentBufferView();
1961                 if (bv)
1962                         bv->clearSelection();
1963                 break;
1964         }
1965         }
1966         return false;
1967 }
1968 #endif
1969
1970 } // namespace frontend
1971
1972
1973 void hideDialogs(std::string const & name, Inset * inset)
1974 {
1975         if (theApp())
1976                 theApp()->hideDialogs(name, inset);
1977 }
1978
1979
1980 ////////////////////////////////////////////////////////////////////
1981 //
1982 // Font stuff
1983 //
1984 ////////////////////////////////////////////////////////////////////
1985
1986 frontend::FontLoader & theFontLoader()
1987 {
1988         LASSERT(frontend::guiApp, /**/);
1989         return frontend::guiApp->fontLoader();
1990 }
1991
1992
1993 frontend::FontMetrics const & theFontMetrics(Font const & f)
1994 {
1995         return theFontMetrics(f.fontInfo());
1996 }
1997
1998
1999 frontend::FontMetrics const & theFontMetrics(FontInfo const & f)
2000 {
2001         LASSERT(frontend::guiApp, /**/);
2002         return frontend::guiApp->fontLoader().metrics(f);
2003 }
2004
2005
2006 ////////////////////////////////////////////////////////////////////
2007 //
2008 // Misc stuff
2009 //
2010 ////////////////////////////////////////////////////////////////////
2011
2012 frontend::Clipboard & theClipboard()
2013 {
2014         LASSERT(frontend::guiApp, /**/);
2015         return frontend::guiApp->clipboard();
2016 }
2017
2018
2019 frontend::Selection & theSelection()
2020 {
2021         LASSERT(frontend::guiApp, /**/);
2022         return frontend::guiApp->selection();
2023 }
2024
2025
2026 } // namespace lyx
2027
2028 #include "moc_GuiApplication.cpp"