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