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