]> git.lyx.org Git - features.git/blob - src/frontends/qt4/GuiApplication.cpp
c319054803854e40f1089eb80df683a50756c1b0
[features.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 "CmdDef.h"
37 #include "Color.h"
38 #include "Font.h"
39 #include "FuncRequest.h"
40 #include "FuncStatus.h"
41 #include "GuiWorkArea.h"
42 #include "Intl.h"
43 #include "KeyMap.h"
44 #include "Language.h"
45 #include "LaTeXPackages.h"
46 #include "Lexer.h"
47 #include "LyX.h"
48 #include "LyXAction.h"
49 #include "LyXRC.h"
50 #include "Paragraph.h"
51 #include "Server.h"
52 #include "Session.h"
53 #include "SpellChecker.h"
54 #include "Thesaurus.h"
55 #include "version.h"
56
57 #include "support/convert.h"
58 #include "support/debug.h"
59 #include "support/ExceptionMessage.h"
60 #include "support/FileName.h"
61 #include "support/filetools.h"
62 #include "support/foreach.h"
63 #include "support/ForkedCalls.h"
64 #include "support/gettext.h"
65 #include "support/lassert.h"
66 #include "support/lstrings.h"
67 #include "support/lyxalgo.h" // sorted
68 #include "support/Messages.h"
69 #include "support/os.h"
70 #include "support/Package.h"
71 #include "support/PathChanger.h"
72 #include "support/Systemcall.h"
73
74 #ifdef Q_WS_MACX
75 #include "support/AppleScript.h"
76 #include "support/linkback/LinkBackProxy.h"
77 #endif
78
79 #include <queue>
80
81 #include <QByteArray>
82 #include <QClipboard>
83 #include <QDateTime>
84 #include <QDir>
85 #include <QEvent>
86 #include <QEventLoop>
87 #include <QFileOpenEvent>
88 #include <QFileInfo>
89 #include <QHash>
90 #include <QIcon>
91 #include <QImageReader>
92 #include <QKeyEvent>
93 #include <QLocale>
94 #include <QLibraryInfo>
95 #include <QList>
96 #include <QMenuBar>
97 #include <QMimeData>
98 #include <QObject>
99 #include <QPixmap>
100 #include <QPixmapCache>
101 #include <QRegExp>
102 #include <QSessionManager>
103 #include <QSettings>
104 #include <QShowEvent>
105 #include <QSocketNotifier>
106 #include <QSortFilterProxyModel>
107 #include <QStandardItemModel>
108 #include <QTextCodec>
109 #include <QTimer>
110 #include <QTranslator>
111 #include <QThreadPool>
112 #include <QWidget>
113
114 #ifdef Q_WS_X11
115 #include <X11/Xatom.h>
116 #include <X11/Xlib.h>
117 #undef CursorShape
118 #undef None
119 #endif
120
121 #ifdef Q_WS_WIN
122 #include <QWindowsMime>
123 #ifdef Q_CC_GNU
124 #include <wtypes.h>
125 #endif
126 #include <objidl.h>
127 #endif // Q_WS_WIN
128
129 #ifdef Q_WS_MACX
130 #include <QMacPasteboardMime>
131 #endif // Q_WS_MACX
132
133 #include "support/bind.h"
134 #include <boost/crc.hpp>
135
136 #include <exception>
137 #include <sstream>
138 #include <vector>
139
140 using namespace std;
141 using namespace lyx::support;
142
143
144 static void initializeResources()
145 {
146         static bool initialized = false;
147         if (!initialized) {
148                 Q_INIT_RESOURCE(Resources);
149                 initialized = true;
150         }
151 }
152
153
154 namespace lyx {
155
156 frontend::Application * createApplication(int & argc, char * argv[])
157 {
158 #ifndef Q_WS_X11
159         // prune -geometry argument(s) by shifting
160         // the following ones 2 places down.
161         for (int i = 0 ; i < argc ; ++i) {
162                 if (strcmp(argv[i], "-geometry") == 0) {
163                         int const remove = (i+1) < argc ? 2 : 1;
164                         argc -= remove;
165                         for (int j = i; j < argc; ++j)
166                                 argv[j] = argv[j + remove];
167                         --i;
168                 }
169         }
170 #endif
171         frontend::GuiApplication * guiApp = new frontend::GuiApplication(argc, argv);
172         // I'd rather do that in the constructor, but I do not think that
173         // the palette is accessible there.
174         guiApp->colorCache().setPalette(guiApp->palette());
175         return guiApp;
176 }
177
178 namespace frontend {
179
180
181 /// Return the list of loadable formats.
182 vector<string> loadableImageFormats()
183 {
184         vector<string> fmts;
185
186         QList<QByteArray> qt_formats = QImageReader::supportedImageFormats();
187
188         LYXERR(Debug::GRAPHICS,
189                 "\nThe image loader can load the following directly:\n");
190
191         if (qt_formats.empty())
192                 LYXERR(Debug::GRAPHICS, "\nQt4 Problem: No Format available!");
193
194         for (QList<QByteArray>::const_iterator it = qt_formats.begin(); it != qt_formats.end(); ++it) {
195
196                 LYXERR(Debug::GRAPHICS, (const char *) *it << ", ");
197
198                 string ext = ascii_lowercase((const char *) *it);
199                 // special case
200                 if (ext == "jpeg")
201                         ext = "jpg";
202                 fmts.push_back(ext);
203         }
204
205         return fmts;
206 }
207
208
209 ////////////////////////////////////////////////////////////////////////
210 //
211 // Icon loading support code
212 //
213 ////////////////////////////////////////////////////////////////////////
214
215 namespace {
216
217 struct PngMap {
218         QString key;
219         QString value;
220 };
221
222
223 bool operator<(PngMap const & lhs, PngMap const & rhs)
224 {
225         return lhs.key < rhs.key;
226 }
227
228
229 class CompareKey {
230 public:
231         CompareKey(QString const & name) : name_(name) {}
232         bool operator()(PngMap const & other) const { return other.key == name_; }
233 private:
234         QString const name_;
235 };
236
237
238 // this must be sorted alphabetically
239 // Upper case comes before lower case
240 // Please don't change the formatting, this list is parsed by
241 // development/tools/generate_symbols_images.py.
242 PngMap sorted_png_map[] = {
243         { "Arrownot", "arrownot2"},
244         { "Arrowvert", "arrowvert2"},
245         { "Bowtie", "bowtie2" },
246         { "Box", "box2" },
247         { "Bumpeq", "bumpeq2" },
248         { "CIRCLE", "circle3" },
249         { "Cap", "cap2" },
250         { "CheckedBox", "checkedbox2" },
251         { "Circle", "circle2" },
252         { "Colonapprox", "colonapprox2" },
253         { "Coloneq", "coloneq2" },
254         { "Coloneqq", "coloneqq2" },
255         { "Colonsim", "colonsim2" },
256         { "Cup", "cup2" },
257         { "DOWNarrow", "downarrow3" },
258         { "Delta", "delta2" },
259         { "Diamond", "diamond2" },
260         { "Doteq", "doteq2" },
261         { "Downarrow", "downarrow2" },
262         { "Eqcolon", "eqcolon2" },
263         { "Eqqcolon", "eqqcolon2" },
264         { "Gamma", "gamma2" },
265         { "Join", "join2" },
266         { "LEFTCIRCLE", "leftcircle3" },
267         { "LEFTarrow", "leftarrow3" },
268         { "LEFTcircle", "leftcircle4" },
269         { "LHD", "lhd2" },
270         { "Lambda", "lambda2" },
271         { "Lbag", "lbag2"},
272         { "Leftarrow", "leftarrow2" },
273         { "Leftcircle", "leftcircle2" },
274         { "Leftrightarrow", "leftrightarrow2" },
275         { "Longarrownot", "longarrownot2"},
276         { "Longleftarrow", "longleftarrow2" },
277         { "Longleftrightarrow", "longleftrightarrow2" },
278         { "Longmapsfrom", "longmapsfrom2"},
279         { "Longmapsto", "longmapsto2"},
280         { "Longrightarrow", "longrightarrow2" },
281         { "Mapsfrom", "mapsfrom2"},
282         { "Mapsfromchar", "mapsfromchar2"},
283         { "Mapsto", "mapsto2"},
284         { "Mapstochar", "mapstochar2"},
285         { "Omega", "omega2" },
286         { "Phi", "phi2" },
287         { "Pi", "pi2" },
288         { "Psi", "psi2" },
289         { "RHD", "rhd2" },
290         { "RIGHTCIRCLE", "rightcircle3" },
291         { "RIGHTarrow", "rightarrow3" },
292         { "RIGHTcircle", "rightcircle4" },
293         { "Rbag", "rbag2"},
294         { "Rightarrow", "rightarrow2" },
295         { "Rightcircle", "rightcircle2" },
296         { "Sigma", "sigma2" },
297         { "Square", "square2" },
298         { "Subset", "subset2" },
299         { "Supset", "supset2" },
300         { "Theta", "theta2" },
301         { "Thorn", "thorn2" },
302         { "UParrow", "uparrow3" },
303         { "Uparrow", "uparrow2" },
304         { "Updownarrow", "updownarrow2" },
305         { "Upsilon", "upsilon2" },
306         { "Vdash", "vdash3" },
307         { "Vert", "vert2" },
308         { "XBox", "xbox3" },
309         { "Xbox", "xbox2" },
310         { "Xi", "xi2" },
311         { "nLeftarrow", "nleftarrow2" },
312         { "nLeftrightarrow", "nleftrightarrow2" },
313         { "nRightarrow", "nrightarrow2" },
314         { "nVDash", "nvdash3" },
315         { "nVdash", "nvdash4" },
316         { "nvDash", "nvdash2" },
317         { "textrm \\AA", "textrm_AA"},
318         { "textrm \\O", "textrm_O"},
319         { "vDash", "vdash2" },
320         { "varDelta", "vardelta2" },
321         { "varGamma", "vargamma2" },
322         { "varLambda", "varlambda2" },
323         { "varOmega", "varomega2" },
324         { "varPhi", "varphi2" },
325         { "varPi", "varpi2" },
326         { "varPsi", "varpsi2" },
327         { "varSigma", "varsigma2" },
328         { "varTheta", "vartheta2" },
329         { "varUpsilon", "varupsilon2" },
330         { "varXi", "varxi2" }
331 };
332
333
334 size_t const nr_sorted_png_map = sizeof(sorted_png_map) / sizeof(PngMap);
335
336 // This list specifies which system's theme icon is related to which lyx
337 // command. It was based on:
338 // http://standards.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html
339 // this must be sorted alphabetically
340 // Upper case comes before lower case
341 PngMap sorted_theme_icon_map[] = {
342         { "bookmark-goto 0", "go-jump" },
343         { "buffer-new", "document-new" },
344         { "buffer-print", "document-print" },
345         { "buffer-write", "document-save" },
346         { "buffer-write-as", "document-save-as" },
347         { "buffer-zoom-in", "zoom-in" },
348         { "buffer-zoom-out", "zoom-out" },
349         { "copy", "edit-copy" },
350         { "cut", "edit-cut" },
351         { "depth-decrement", "format-indent-less" },
352         { "depth-increment", "format-indent-more" },
353         { "dialog-show print", "document-print" },
354         { "dialog-show spellchecker", "tools-check-spelling" },
355         { "dialog-show-new-inset graphics", "insert-image" },
356         { "dialog-toggle findreplaceadv", "edit-find-replace" },
357         { "file-open", "document-open" },
358         { "font-bold", "format-text-bold" },
359         { "font-ital", "format-text-italic" },
360         { "font-strikeout", "format-text-strikethrough" },
361         { "font-underline", "format-text-underline" },
362         { "lyx-quit", "application-exit" },
363         { "paste", "edit-paste" },
364         { "redo", "edit-redo" },
365         { "undo", "edit-undo" },
366         { "window-close", "window-close" },
367         { "window-new", "window-new" }
368 };
369
370 size_t const nr_sorted_theme_icon_map = sizeof(sorted_theme_icon_map) / sizeof(PngMap);
371
372
373 QString findPng(QString const & name)
374 {
375         PngMap const * const begin = sorted_png_map;
376         PngMap const * const end = begin + nr_sorted_png_map;
377         LATTEST(sorted(begin, end));
378
379         PngMap const * const it = find_if(begin, end, CompareKey(name));
380
381         QString png_name;
382         if (it != end) {
383                 png_name = it->value;
384         } else {
385                 png_name = name;
386                 png_name.replace('_', "underscore");
387                 png_name.replace(' ', '_');
388
389                 // This way we can have "math-delim { }" on the toolbar.
390                 png_name.replace('(', "lparen");
391                 png_name.replace(')', "rparen");
392                 png_name.replace('[', "lbracket");
393                 png_name.replace(']', "rbracket");
394                 png_name.replace('{', "lbrace");
395                 png_name.replace('}', "rbrace");
396                 png_name.replace('|', "bars");
397                 png_name.replace(',', "thinspace");
398                 png_name.replace(':', "mediumspace");
399                 png_name.replace(';', "thickspace");
400                 png_name.replace('!', "negthinspace");
401         }
402
403         LYXERR(Debug::GUI, "findPng(" << name << ")\n"
404                 << "Looking for math PNG called \"" << png_name << '"');
405         return png_name;
406 }
407
408 } // namespace anon
409
410
411 QString themeIconName(QString const & action)
412 {
413         PngMap const * const begin = sorted_theme_icon_map;
414         PngMap const * const end = begin + nr_sorted_theme_icon_map;
415         LASSERT(sorted(begin, end), /**/);
416
417         PngMap const * const it = find_if(begin, end, CompareKey(action));
418
419         if (it != end)
420                 return it->value;
421         return QString();
422 }
423
424
425 QString iconName(FuncRequest const & f, bool unknown)
426 {
427         initializeResources();
428         QString name1;
429         QString name2;
430         QString path;
431         switch (f.action()) {
432         case LFUN_MATH_INSERT:
433                 if (!f.argument().empty()) {
434                         path = "math/";
435                         name1 = findPng(toqstr(f.argument()).mid(1));
436                 }
437                 break;
438         case LFUN_MATH_DELIM:
439         case LFUN_MATH_BIGDELIM:
440                 path = "math/";
441                 name1 = findPng(toqstr(f.argument()));
442                 break;
443         case LFUN_CALL:
444                 path = "commands/";
445                 name1 = toqstr(f.argument());
446                 break;
447         case LFUN_COMMAND_ALTERNATIVES: {
448                 // use the first of the alternative commands
449                 docstring firstcom;
450                 docstring dummy = split(f.argument(), firstcom, ';');
451                 name1 = toqstr(firstcom);
452                 // FIXME: we should rename the icons to tabular-xxx instead of
453                 // "tabular-feature-xxx"
454                 name1.replace("inset-modify tabular", "tabular-feature");
455                 name1.replace(' ', '_');
456                 break;
457         }
458         case LFUN_INSET_MODIFY: {
459                 // FIXME: we should rename the icons to tabular-xxx instead of
460                 // "tabular-feature-xxx" and generalize this naming to all
461                 // insets, not to tabular using ones.
462                 string inset_name;
463                 string const command = split(to_utf8(f.argument()), inset_name, ' ');
464                 if (insetCode(inset_name) == TABULAR_CODE) {
465                         name1 = "tabular-feature "+ toqstr(command);
466                         name1.replace(' ', '_');
467                         break;
468                 }
469         }
470         default:
471                 name2 = toqstr(lyxaction.getActionName(f.action()));
472                 name1 = name2;
473
474                 if (!f.argument().empty()) {
475                         name1 = name2 + ' ' + toqstr(f.argument());
476                         name1.replace(' ', '_');
477                         name1.replace('\\', "backslash");
478                 }
479         }
480
481         QStringList imagedirs;
482         imagedirs << "images/" << "images/ipa/";
483         for (int i = 0; i < imagedirs.size(); ++i) { 
484                 QString imagedir = imagedirs.at(i) + path;
485                 FileName fname = imageLibFileSearch(imagedir, name1, "png");
486                 if (fname.exists())
487                         return toqstr(fname.absFileName());
488
489                 fname = imageLibFileSearch(imagedir, name2, "png");
490                 if (fname.exists())
491                         return toqstr(fname.absFileName());
492         }
493
494         path = ":/images/" + path;
495         QDir res(path);
496         if (!res.exists()) {
497                 LYXERR0("Directory " << path << " not found in resource!");
498                 return QString();
499         }
500         name1 += ".png";
501         if (res.exists(name1))
502                 return path + name1;
503
504         name2 += ".png";
505         if (res.exists(name2))
506                 return path + name2;
507
508         LYXERR(Debug::GUI, "Cannot find icon with filename "
509                            << "\"" << name1 << "\""
510                            << " or filename "
511                            << "\"" << name2 << "\""
512                            << " for command \""
513                            << lyxaction.getActionName(f.action())
514                            << '(' << to_utf8(f.argument()) << ")\"");
515
516         if (unknown) {
517                 QString imagedir = "images/";
518                 FileName fname = imageLibFileSearch(imagedir, "unknown", "png");
519                 if (fname.exists())
520                         return toqstr(fname.absFileName());
521                 return QString(":/images/unknown.png");
522         }
523
524         return QString();
525 }
526
527 QPixmap getPixmap(QString const & path, QString const & name, QString const & ext)
528 {
529         QPixmap pixmap;
530         QString imagedir = path;
531         FileName fname = imageLibFileSearch(imagedir, name, ext);
532         QString path1 = toqstr(fname.absFileName());
533         QString path2 = ":/" + path + name + "." + ext;
534
535         if (pixmap.load(path1)) {
536                 return pixmap;
537         }
538         else if (pixmap.load(path2)) {
539                 return pixmap;
540         }
541
542         LYXERR0("Cannot load pixmap \""
543                 << path << name << '.' << ext
544                 << "\", please verify resource system!");
545
546         return QPixmap();
547 }
548
549 QIcon getIcon(FuncRequest const & f, bool unknown)
550 {
551 #if (QT_VERSION >= 0x040600)
552         if (lyxrc.use_system_theme_icons) {
553                 QString action = toqstr(lyxaction.getActionName(f.action()));
554                 if (!f.argument().empty())
555                         action += " " + toqstr(f.argument());
556                 QString const theme_icon = themeIconName(action);
557                 if (QIcon::hasThemeIcon(theme_icon))
558                         return QIcon::fromTheme(theme_icon);
559         }
560 #endif
561
562         QString icon = iconName(f, unknown);
563         if (icon.isEmpty())
564                 return QIcon();
565
566         //LYXERR(Debug::GUI, "Found icon: " << icon);
567         QPixmap pm;
568         if (!pm.load(icon)) {
569                 LYXERR0("Cannot load icon " << icon << " please verify resource system!");
570                 return QIcon();
571         }
572
573         return QIcon(pm);
574 }
575
576
577 ////////////////////////////////////////////////////////////////////////
578 //
579 // LyX server support code.
580 //
581 ////////////////////////////////////////////////////////////////////////
582
583 class SocketNotifier : public QSocketNotifier
584 {
585 public:
586         /// connect a connection notification from the LyXServerSocket
587         SocketNotifier(QObject * parent, int fd, Application::SocketCallback func)
588                 : QSocketNotifier(fd, QSocketNotifier::Read, parent), func_(func)
589         {}
590
591 public:
592         /// The callback function
593         Application::SocketCallback func_;
594 };
595
596
597 ////////////////////////////////////////////////////////////////////////
598 //
599 // Mac specific stuff goes here...
600 //
601 ////////////////////////////////////////////////////////////////////////
602
603 class MenuTranslator : public QTranslator
604 {
605 public:
606         MenuTranslator(QObject * parent)
607                 : QTranslator(parent)
608         {}
609
610         QString translate(const char * /*context*/,
611           const char * sourceText,
612           const char * /*comment*/ = 0) const
613         {
614                 string const s = sourceText;
615                 if (s == N_("About %1") || s == N_("Preferences")
616                                 || s == N_("Reconfigure") || s == N_("Quit %1"))
617                         return qt_(s);
618                 else
619                         return QString();
620         }
621 };
622
623 class GlobalMenuBar : public QMenuBar
624 {
625 public:
626         ///
627         GlobalMenuBar() : QMenuBar(0) {}
628
629         ///
630         bool event(QEvent * e)
631         {
632                 if (e->type() == QEvent::ShortcutOverride) {
633                         //          && activeWindow() == 0) {
634                         QKeyEvent * ke = static_cast<QKeyEvent*>(e);
635                         KeySymbol sym;
636                         setKeySymbol(&sym, ke);
637                         guiApp->processKeySym(sym, q_key_state(ke->modifiers()));
638                         e->accept();
639                         return true;
640                 }
641                 return false;
642         }
643 };
644
645 #ifdef Q_WS_MACX
646 // QMacPasteboardMimeGraphics can only be compiled on Mac.
647
648 class QMacPasteboardMimeGraphics : public QMacPasteboardMime
649 {
650 public:
651         QMacPasteboardMimeGraphics()
652                 : QMacPasteboardMime(MIME_QT_CONVERTOR|MIME_ALL)
653         {}
654
655         QString convertorName() { return "Graphics"; }
656
657         QString flavorFor(QString const & mime)
658         {
659                 LYXERR(Debug::ACTION, "flavorFor " << mime);
660                 if (mime == pdfMimeType())
661                         return QLatin1String("com.adobe.pdf");
662                 return QString();
663         }
664
665         QString mimeFor(QString flav)
666         {
667                 LYXERR(Debug::ACTION, "mimeFor " << flav);
668                 if (flav == QLatin1String("com.adobe.pdf"))
669                         return pdfMimeType();
670                 return QString();
671         }
672
673         bool canConvert(QString const & mime, QString flav)
674         {
675                 return mimeFor(flav) == mime;
676         }
677
678         QVariant convertToMime(QString const & /*mime*/, QList<QByteArray> data,
679                 QString /*flav*/)
680         {
681                 if(data.count() > 1)
682                         qWarning("QMacPasteboardMimeGraphics: Cannot handle multiple member data");
683                 return data.first();
684         }
685
686         QList<QByteArray> convertFromMime(QString const & /*mime*/,
687                 QVariant data, QString /*flav*/)
688         {
689                 QList<QByteArray> ret;
690                 ret.append(data.toByteArray());
691                 return ret;
692         }
693 };
694 #endif
695
696 ///////////////////////////////////////////////////////////////
697 //
698 // You can find more platform specific stuff at the end of this file...
699 //
700 ///////////////////////////////////////////////////////////////
701
702 ////////////////////////////////////////////////////////////////////////
703 // Windows specific stuff goes here...
704
705 #ifdef Q_WS_WIN
706 // QWindowsMimeMetafile can only be compiled on Windows.
707
708 static FORMATETC cfFromMime(QString const & mimetype)
709 {
710         FORMATETC formatetc;
711         if (mimetype == emfMimeType()) {
712                 formatetc.cfFormat = CF_ENHMETAFILE;
713                 formatetc.tymed = TYMED_ENHMF;
714         } else if (mimetype == wmfMimeType()) {
715                 formatetc.cfFormat = CF_METAFILEPICT;
716                 formatetc.tymed = TYMED_MFPICT;
717         }
718         formatetc.ptd = 0;
719         formatetc.dwAspect = DVASPECT_CONTENT;
720         formatetc.lindex = -1;
721         return formatetc;
722 }
723
724
725 class QWindowsMimeMetafile : public QWindowsMime {
726 public:
727         QWindowsMimeMetafile() {}
728
729         bool canConvertFromMime(FORMATETC const & formatetc,
730                 QMimeData const * mimedata) const
731         {
732                 return false;
733         }
734
735         bool canConvertToMime(QString const & mimetype,
736                 IDataObject * pDataObj) const
737         {
738                 if (mimetype != emfMimeType() && mimetype != wmfMimeType())
739                         return false;
740                 FORMATETC formatetc = cfFromMime(mimetype);
741                 return pDataObj->QueryGetData(&formatetc) == S_OK;
742         }
743
744         bool convertFromMime(FORMATETC const & formatetc,
745                 const QMimeData * mimedata, STGMEDIUM * pmedium) const
746         {
747                 return false;
748         }
749
750         QVariant convertToMime(QString const & mimetype, IDataObject * pDataObj,
751                 QVariant::Type preferredType) const
752         {
753                 QByteArray data;
754                 if (!canConvertToMime(mimetype, pDataObj))
755                         return data;
756
757                 FORMATETC formatetc = cfFromMime(mimetype);
758                 STGMEDIUM s;
759                 if (pDataObj->GetData(&formatetc, &s) != S_OK)
760                         return data;
761
762                 int dataSize;
763                 if (s.tymed == TYMED_ENHMF) {
764                         dataSize = GetEnhMetaFileBits(s.hEnhMetaFile, 0, 0);
765                         data.resize(dataSize);
766                         dataSize = GetEnhMetaFileBits(s.hEnhMetaFile, dataSize,
767                                 (LPBYTE)data.data());
768                 } else if (s.tymed == TYMED_MFPICT) {
769                         dataSize = GetMetaFileBitsEx((HMETAFILE)s.hMetaFilePict, 0, 0);
770                         data.resize(dataSize);
771                         dataSize = GetMetaFileBitsEx((HMETAFILE)s.hMetaFilePict, dataSize,
772                                 (LPBYTE)data.data());
773                 }
774                 data.detach();
775                 ReleaseStgMedium(&s);
776
777                 return data;
778         }
779
780
781         QVector<FORMATETC> formatsForMime(QString const & mimetype,
782                 QMimeData const * mimedata) const
783         {
784                 QVector<FORMATETC> formats;
785                 if (mimetype == emfMimeType() || mimetype == wmfMimeType())
786                         formats += cfFromMime(mimetype);
787                 return formats;
788         }
789
790         QString mimeForFormat(FORMATETC const & formatetc) const
791         {
792                 switch (formatetc.cfFormat) {
793                 case CF_ENHMETAFILE:
794                         return emfMimeType();
795                 case CF_METAFILEPICT:
796                         return wmfMimeType();
797                 }
798                 return QString();
799         }
800 };
801
802 #endif // Q_WS_WIN
803
804
805 /// Allows to check whether ESC was pressed during a long operation
806 class KeyChecker : public QObject {
807 private:
808         bool pressed_;
809         bool started_;
810 public:
811         KeyChecker() : pressed_(false), started_(false) {}
812
813         void start() {
814                 QCoreApplication::instance()->installEventFilter(this);
815                 pressed_ = false;
816                 started_ = true;
817         }
818         void stop() {
819                 QCoreApplication::instance()->removeEventFilter(this);
820                 started_ = false;
821         }
822         bool pressed() {
823                 QCoreApplication::processEvents();
824                 return pressed_;
825         }
826         bool started() const {
827                 return started_;
828         }
829         bool eventFilter(QObject *obj, QEvent *event) {
830                 LYXERR(Debug::ACTION, "Event Type: " << event->type());
831                 switch (event->type()) {
832                 case QEvent::Show:
833                 case QEvent::Hide:
834                 case QEvent::Resize:
835                         return QObject::eventFilter(obj, event);
836                 default:
837                         QKeyEvent *keyEvent = dynamic_cast<QKeyEvent*>(event);
838                         if (keyEvent && keyEvent->key() == Qt::Key_Escape)
839                                 pressed_ = true;
840                         return true;
841                 }
842         }
843 };
844
845
846 ////////////////////////////////////////////////////////////////////////
847 // GuiApplication::Private definition and implementation.
848 ////////////////////////////////////////////////////////////////////////
849
850 struct GuiApplication::Private
851 {
852         Private(): language_model_(0), meta_fake_bit(NoModifier),
853                 global_menubar_(0)
854         {
855         #ifdef Q_WS_WIN
856                 /// WMF Mime handler for Windows clipboard.
857                 wmf_mime_ = new QWindowsMimeMetafile;
858         #endif
859                 initKeySequences(&theTopLevelKeymap());
860         }
861
862         void initKeySequences(KeyMap * kb)
863         {
864                 keyseq = KeySequence(kb, kb);
865                 cancel_meta_seq = KeySequence(kb, kb);
866         }
867
868         ///
869         QSortFilterProxyModel * language_model_;
870         ///
871         GuiClipboard clipboard_;
872         ///
873         GuiSelection selection_;
874         ///
875         FontLoader font_loader_;
876         ///
877         ColorCache color_cache_;
878         ///
879         QTranslator qt_trans_;
880         ///
881         QHash<int, SocketNotifier *> socket_notifiers_;
882         ///
883         Menus menus_;
884         ///
885         /// The global instance
886         Toolbars toolbars_;
887
888         /// this timer is used for any regular events one wants to
889         /// perform. at present it is used to check if forked processes
890         /// are done.
891         QTimer general_timer_;
892
893         /// delayed FuncRequests
894         std::queue<FuncRequest> func_request_queue_;
895
896         ///
897         KeySequence keyseq;
898         ///
899         KeySequence cancel_meta_seq;
900         ///
901         KeyModifier meta_fake_bit;
902
903         /// Multiple views container.
904         /**
905         * Warning: This must not be a smart pointer as the destruction of the
906         * object is handled by Qt when the view is closed
907         * \sa Qt::WA_DeleteOnClose attribute.
908         */
909         QHash<int, GuiView *> views_;
910
911         /// Only used on mac.
912         GlobalMenuBar * global_menubar_;
913
914 #ifdef Q_WS_MACX
915         /// Linkback mime handler for MacOSX.
916         QMacPasteboardMimeGraphics mac_pasteboard_mime_;
917 #endif
918
919 #ifdef Q_WS_WIN
920         /// WMF Mime handler for Windows clipboard.
921         QWindowsMimeMetafile * wmf_mime_;
922 #endif
923
924         /// Allows to check whether ESC was pressed during a long operation
925         KeyChecker key_checker_;
926 };
927
928
929 GuiApplication * guiApp;
930
931 GuiApplication::~GuiApplication()
932 {
933 #ifdef Q_WS_MACX
934         closeAllLinkBackLinks();
935 #endif
936         delete d;
937 }
938
939
940 GuiApplication::GuiApplication(int & argc, char ** argv)
941         : QApplication(argc, argv), current_view_(0),
942           d(new GuiApplication::Private)
943 {
944         QString app_name = "LyX";
945         QCoreApplication::setOrganizationName(app_name);
946         QCoreApplication::setOrganizationDomain("lyx.org");
947         QCoreApplication::setApplicationName(lyx_package);
948
949         qsrand(QDateTime::currentDateTime().toTime_t());
950
951         // Install translator for GUI elements.
952         installTranslator(&d->qt_trans_);
953
954         // FIXME: quitOnLastWindowClosed is true by default. We should have a
955         // lyxrc setting for this in order to let the application stay resident.
956         // But then we need some kind of dock icon, at least on Windows.
957         /*
958         if (lyxrc.quit_on_last_window_closed)
959                 setQuitOnLastWindowClosed(false);
960         */
961 #ifdef Q_WS_MACX
962         // FIXME: Do we need a lyxrc setting for this on Mac? This behaviour
963         // seems to be the default case for applications like LyX.
964         setQuitOnLastWindowClosed(false);
965         // This allows to translate the strings that appear in the LyX menu.
966         /// A translator suitable for the entries in the LyX menu.
967         /// Only needed with Qt/Mac.
968         installTranslator(new MenuTranslator(this));
969         ///
970     setupApplescript();
971 #endif
972
973 #ifdef Q_WS_X11
974         // doubleClickInterval() is 400 ms on X11 which is just too long.
975         // On Windows and Mac OS X, the operating system's value is used.
976         // On Microsoft Windows, calling this function sets the double
977         // click interval for all applications. So we don't!
978         QApplication::setDoubleClickInterval(300);
979 #endif
980
981         connect(this, SIGNAL(lastWindowClosed()), this, SLOT(onLastWindowClosed()));
982
983         // needs to be done before reading lyxrc
984         QWidget w;
985         lyxrc.dpi = (w.logicalDpiX() + w.logicalDpiY()) / 2;
986
987         guiApp = this;
988
989         // Set the cache to 5120 kilobytes which corresponds to screen size of
990         // 1280 by 1024 pixels with a color depth of 32 bits.
991         QPixmapCache::setCacheLimit(5120);
992
993         // Initialize RC Fonts
994         if (lyxrc.roman_font_name.empty())
995                 lyxrc.roman_font_name = fromqstr(romanFontName());
996
997         if (lyxrc.sans_font_name.empty())
998                 lyxrc.sans_font_name = fromqstr(sansFontName());
999
1000         if (lyxrc.typewriter_font_name.empty())
1001                 lyxrc.typewriter_font_name = fromqstr(typewriterFontName());
1002
1003         d->general_timer_.setInterval(500);
1004         connect(&d->general_timer_, SIGNAL(timeout()),
1005                 this, SLOT(handleRegularEvents()));
1006         d->general_timer_.start();
1007
1008         // maxThreadCount() defaults in general to 2 on single or dual-processor.
1009         // This is clearly not enough in a time where we use threads for
1010         // document preview and/or export. 20 should be OK.
1011         QThreadPool::globalInstance()->setMaxThreadCount(20);
1012 }
1013
1014
1015 GuiApplication * theGuiApp()
1016 {
1017         return dynamic_cast<GuiApplication *>(theApp());
1018 }
1019
1020
1021 void GuiApplication::clearSession()
1022 {
1023         QSettings settings;
1024         settings.clear();
1025 }
1026
1027
1028 docstring Application::iconName(FuncRequest const & f, bool unknown)
1029 {
1030         return qstring_to_ucs4(lyx::frontend::iconName(f, unknown));
1031 }
1032
1033
1034 FuncStatus GuiApplication::getStatus(FuncRequest const & cmd) const
1035 {
1036         FuncStatus status;
1037
1038         BufferView * bv = 0;
1039         BufferView * doc_bv = 0;
1040
1041         if (cmd.action() == LFUN_NOACTION) {
1042                 status.message(from_utf8(N_("Nothing to do")));
1043                 status.setEnabled(false);
1044         }
1045
1046         else if (cmd.action() == LFUN_UNKNOWN_ACTION) {
1047                 status.setUnknown(true);
1048                 status.message(from_utf8(N_("Unknown action")));
1049                 status.setEnabled(false);
1050         }
1051
1052         // Does the GuiApplication know something?
1053         else if (getStatus(cmd, status)) { }
1054
1055         // If we do not have a GuiView, then other functions are disabled
1056         else if (!current_view_)
1057                 status.setEnabled(false);
1058
1059         // Does the GuiView know something?
1060         else if (current_view_->getStatus(cmd, status)) { }
1061
1062         // In LyX/Mac, when a dialog is open, the menus of the
1063         // application can still be accessed without giving focus to
1064         // the main window. In this case, we want to disable the menu
1065         // entries that are buffer or view-related.
1066         //FIXME: Abdel (09/02/10) This has very bad effect on Linux, don't know why...
1067         /*
1068         else if (cmd.origin() == FuncRequest::MENU && !current_view_->hasFocus())
1069                 status.setEnabled(false);
1070         */
1071
1072         // If we do not have a BufferView, then other functions are disabled
1073         else if (!(bv = current_view_->currentBufferView()))
1074                 status.setEnabled(false);
1075
1076         // Does the current BufferView know something?
1077         else if (bv->getStatus(cmd, status)) { }
1078
1079         // Does the current Buffer know something?
1080         else if (bv->buffer().getStatus(cmd, status)) { }
1081
1082         // If we do not have a document BufferView, different from the
1083         // current BufferView, then other functions are disabled
1084         else if (!(doc_bv = current_view_->documentBufferView()) || doc_bv == bv)
1085                 status.setEnabled(false);
1086
1087         // Does the document Buffer know something?
1088         else if (doc_bv->buffer().getStatus(cmd, status)) { }
1089
1090         else {
1091                 LYXERR(Debug::ACTION, "LFUN not handled in getStatus(): " << cmd);
1092                 status.message(from_utf8(N_("Command not handled")));
1093                 status.setEnabled(false);
1094         }
1095
1096         // the default error message if we disable the command
1097         if (!status.enabled() && status.message().empty())
1098                 status.message(from_utf8(N_("Command disabled")));
1099
1100         return status;
1101 }
1102
1103
1104 bool GuiApplication::getStatus(FuncRequest const & cmd, FuncStatus & flag) const
1105 {
1106         // I would really like to avoid having this switch and rather try to
1107         // encode this in the function itself.
1108         // -- And I'd rather let an inset decide which LFUNs it is willing
1109         // to handle (Andre')
1110         bool enable = true;
1111         switch (cmd.action()) {
1112
1113         // This could be used for the no-GUI version. The GUI version is handled in
1114         // GuiView::getStatus(). See above.
1115         /*
1116         case LFUN_BUFFER_WRITE:
1117         case LFUN_BUFFER_WRITE_AS: {
1118                 Buffer * b = theBufferList().getBuffer(FileName(cmd.getArg(0)));
1119                 enable = b && (b->isUnnamed() || !b->isClean());
1120                 break;
1121         }
1122         */
1123
1124         case LFUN_BOOKMARK_GOTO: {
1125                 const unsigned int num = convert<unsigned int>(to_utf8(cmd.argument()));
1126                 enable = theSession().bookmarks().isValid(num);
1127                 break;
1128         }
1129
1130         case LFUN_BOOKMARK_CLEAR:
1131                 enable = theSession().bookmarks().hasValid();
1132                 break;
1133
1134         // this one is difficult to get right. As a half-baked
1135         // solution, we consider only the first action of the sequence
1136         case LFUN_COMMAND_SEQUENCE: {
1137                 // argument contains ';'-terminated commands
1138                 string const firstcmd = token(to_utf8(cmd.argument()), ';', 0);
1139                 FuncRequest func(lyxaction.lookupFunc(firstcmd));
1140                 func.setOrigin(cmd.origin());
1141                 flag = getStatus(func);
1142                 break;
1143         }
1144
1145         // we want to check if at least one of these is enabled
1146         case LFUN_COMMAND_ALTERNATIVES: {
1147                 // argument contains ';'-terminated commands
1148                 string arg = to_utf8(cmd.argument());
1149                 while (!arg.empty()) {
1150                         string first;
1151                         arg = split(arg, first, ';');
1152                         FuncRequest func(lyxaction.lookupFunc(first));
1153                         func.setOrigin(cmd.origin());
1154                         flag = getStatus(func);
1155                         // if this one is enabled, the whole thing is
1156                         if (flag.enabled())
1157                                 break;
1158                 }
1159                 break;
1160         }
1161
1162         case LFUN_CALL: {
1163                 FuncRequest func;
1164                 string name = to_utf8(cmd.argument());
1165                 if (theTopLevelCmdDef().lock(name, func)) {
1166                         func.setOrigin(cmd.origin());
1167                         flag = getStatus(func);
1168                         theTopLevelCmdDef().release(name);
1169                 } else {
1170                         // catch recursion or unknown command
1171                         // definition. all operations until the
1172                         // recursion or unknown command definition
1173                         // occurs are performed, so set the state to
1174                         // enabled
1175                         enable = true;
1176                 }
1177                 break;
1178         }
1179
1180         case LFUN_CURSOR_FOLLOWS_SCROLLBAR_TOGGLE:
1181         case LFUN_REPEAT:
1182         case LFUN_PREFERENCES_SAVE:
1183         case LFUN_BUFFER_SAVE_AS_DEFAULT:
1184         case LFUN_DEBUG_LEVEL_SET:
1185                 // these are handled in our dispatch()
1186                 break;
1187
1188         case LFUN_WINDOW_CLOSE:
1189                 enable = d->views_.size() > 0;
1190                 break;
1191
1192         case LFUN_BUFFER_NEW:
1193         case LFUN_BUFFER_NEW_TEMPLATE:
1194         case LFUN_FILE_OPEN:
1195         case LFUN_HELP_OPEN:
1196         case LFUN_SCREEN_FONT_UPDATE:
1197         case LFUN_SET_COLOR:
1198         case LFUN_WINDOW_NEW:
1199         case LFUN_LYX_QUIT:
1200         case LFUN_LYXRC_APPLY:
1201         case LFUN_COMMAND_PREFIX:
1202         case LFUN_CANCEL:
1203         case LFUN_META_PREFIX:
1204         case LFUN_RECONFIGURE:
1205         case LFUN_SERVER_GET_FILENAME:
1206         case LFUN_SERVER_NOTIFY:
1207                 enable = true;
1208                 break;
1209
1210         case LFUN_BUFFER_FORALL: {
1211                 if (theBufferList().empty()) {
1212                         flag.message(from_utf8(N_("Command not allowed without a buffer open")));
1213                         flag.setEnabled(false);
1214                         break;
1215                 }
1216
1217                 FuncRequest const cmdToPass = lyxaction.lookupFunc(cmd.getLongArg(0));
1218                 if (cmdToPass.action() == LFUN_UNKNOWN_ACTION) {
1219                         flag.message(from_utf8(N_("the <LFUN-COMMAND> argument of buffer-forall is not valid")));
1220                         flag.setEnabled(false);
1221                 }
1222                 break;
1223         }
1224
1225
1226         default:
1227                 return false;
1228         }
1229
1230         if (!enable)
1231                 flag.setEnabled(false);
1232         return true;
1233 }
1234
1235 /// make a post-dispatch status message
1236 static docstring makeDispatchMessage(docstring const & msg,
1237                                      FuncRequest const & cmd)
1238 {
1239         const bool verbose = (cmd.origin() == FuncRequest::MENU
1240                               || cmd.origin() == FuncRequest::TOOLBAR
1241                               || cmd.origin() == FuncRequest::COMMANDBUFFER);
1242
1243         if (cmd.action() == LFUN_SELF_INSERT || !verbose) {
1244                 LYXERR(Debug::ACTION, "dispatch msg is " << msg);
1245                 return msg;
1246         }
1247
1248         docstring dispatch_msg = msg;
1249         if (!dispatch_msg.empty())
1250                 dispatch_msg += ' ';
1251
1252         docstring comname = from_utf8(lyxaction.getActionName(cmd.action()));
1253
1254         bool argsadded = false;
1255
1256         if (!cmd.argument().empty()) {
1257                 if (cmd.action() != LFUN_UNKNOWN_ACTION) {
1258                         comname += ' ' + cmd.argument();
1259                         argsadded = true;
1260                 }
1261         }
1262         docstring const shortcuts = theTopLevelKeymap().
1263                 printBindings(cmd, KeySequence::ForGui);
1264
1265         if (!shortcuts.empty())
1266                 comname += ": " + shortcuts;
1267         else if (!argsadded && !cmd.argument().empty())
1268                 comname += ' ' + cmd.argument();
1269
1270         if (!comname.empty()) {
1271                 comname = rtrim(comname);
1272                 dispatch_msg += '(' + rtrim(comname) + ')';
1273         }
1274         LYXERR(Debug::ACTION, "verbose dispatch msg " << to_utf8(dispatch_msg));
1275         return dispatch_msg;
1276 }
1277
1278
1279 void GuiApplication::dispatch(FuncRequest const & cmd)
1280 {
1281         Buffer * buffer = 0;
1282         if (current_view_ && current_view_->currentBufferView()) {
1283                 current_view_->currentBufferView()->cursor().saveBeforeDispatchPosXY();
1284                 buffer = &current_view_->currentBufferView()->buffer();
1285                 if (buffer)
1286                         buffer->undo().beginUndoGroup();
1287         }
1288
1289         DispatchResult dr;
1290         // redraw the screen at the end (first of the two drawing steps).
1291         // This is done unless explicitly requested otherwise
1292         dr.screenUpdate(Update::FitCursor);
1293         dispatch(cmd, dr);
1294         updateCurrentView(cmd, dr);
1295
1296         // the buffer may have been closed by one action
1297         if (theBufferList().isLoaded(buffer))
1298                 buffer->undo().endUndoGroup();
1299 }
1300
1301
1302 void GuiApplication::updateCurrentView(FuncRequest const & cmd, DispatchResult & dr)
1303 {
1304         if (!current_view_)
1305                 return;
1306
1307         BufferView * bv = current_view_->currentBufferView();
1308         if (bv) {
1309                 if (dr.needBufferUpdate()) {
1310                         bv->cursor().clearBufferUpdate();
1311                         bv->buffer().updateBuffer();
1312                 }
1313                 // BufferView::update() updates the ViewMetricsInfo and
1314                 // also initializes the position cache for all insets in
1315                 // (at least partially) visible top-level paragraphs.
1316                 // We will redraw the screen only if needed.
1317                 bv->processUpdateFlags(dr.screenUpdate());
1318
1319                 // Do we have a selection?
1320                 theSelection().haveSelection(bv->cursor().selection());
1321
1322                 // update gui
1323                 current_view_->restartCursor();
1324         }
1325         if (dr.needMessageUpdate()) {
1326                 // Some messages may already be translated, so we cannot use _()
1327                 current_view_->message(makeDispatchMessage(
1328                                 translateIfPossible(dr.message()), cmd));
1329         }
1330 }
1331
1332
1333 void GuiApplication::gotoBookmark(unsigned int idx, bool openFile,
1334         bool switchToBuffer)
1335 {
1336         if (!theSession().bookmarks().isValid(idx))
1337                 return;
1338         BookmarksSection::Bookmark const & bm =
1339                 theSession().bookmarks().bookmark(idx);
1340         LASSERT(!bm.filename.empty(), return);
1341         string const file = bm.filename.absFileName();
1342         // if the file is not opened, open it.
1343         if (!theBufferList().exists(bm.filename)) {
1344                 if (openFile)
1345                         dispatch(FuncRequest(LFUN_FILE_OPEN, file));
1346                 else
1347                         return;
1348         }
1349         // open may fail, so we need to test it again
1350         if (!theBufferList().exists(bm.filename))
1351                 return;
1352
1353         // bm can be changed when saving
1354         BookmarksSection::Bookmark tmp = bm;
1355
1356         // Special case idx == 0 used for back-from-back jump navigation
1357         if (idx == 0)
1358                 dispatch(FuncRequest(LFUN_BOOKMARK_SAVE, "0"));
1359
1360         // if the current buffer is not that one, switch to it.
1361         BufferView * doc_bv = current_view_ ?
1362                 current_view_->documentBufferView() : 0;
1363         if (!doc_bv || doc_bv->buffer().fileName() != tmp.filename) {
1364                 if (switchToBuffer) {
1365                         dispatch(FuncRequest(LFUN_BUFFER_SWITCH, file));
1366                         if (!current_view_)
1367                                 return;
1368                         doc_bv = current_view_->documentBufferView();
1369                 } else
1370                         return;
1371         }
1372
1373         // moveToPosition try paragraph id first and then paragraph (pit, pos).
1374         if (!doc_bv->moveToPosition(
1375                         tmp.bottom_pit, tmp.bottom_pos, tmp.top_id, tmp.top_pos))
1376                 return;
1377
1378         // bm changed
1379         if (idx == 0)
1380                 return;
1381
1382         // Cursor jump succeeded!
1383         Cursor const & cur = doc_bv->cursor();
1384         pit_type new_pit = cur.pit();
1385         pos_type new_pos = cur.pos();
1386         int new_id = cur.paragraph().id();
1387
1388         // if bottom_pit, bottom_pos or top_id has been changed, update bookmark
1389         // see http://www.lyx.org/trac/ticket/3092
1390         if (bm.bottom_pit != new_pit || bm.bottom_pos != new_pos
1391                 || bm.top_id != new_id) {
1392                 const_cast<BookmarksSection::Bookmark &>(bm).updatePos(
1393                         new_pit, new_pos, new_id);
1394         }
1395 }
1396
1397 // This function runs "configure" and then rereads lyx.defaults to
1398 // reconfigure the automatic settings.
1399 void GuiApplication::reconfigure(string const & option)
1400 {
1401         // emit message signal.
1402         if (current_view_)
1403                 current_view_->message(_("Running configure..."));
1404
1405         // Run configure in user lyx directory
1406         PathChanger p(package().user_support());
1407         string configure_command = package().configure_command();
1408         configure_command += option;
1409         Systemcall one;
1410         int const ret = one.startscript(Systemcall::Wait, configure_command);
1411         p.pop();
1412         // emit message signal.
1413         if (current_view_)
1414                 current_view_->message(_("Reloading configuration..."));
1415         lyxrc.read(libFileSearch(QString(), "lyxrc.defaults"), false);
1416         // Re-read packages.lst
1417         LaTeXPackages::getAvailable();
1418
1419         if (ret)
1420                 Alert::information(_("System reconfiguration failed"),
1421                            _("The system reconfiguration has failed.\n"
1422                                   "Default textclass is used but LyX may\n"
1423                                   "not be able to work properly.\n"
1424                                   "Please reconfigure again if needed."));
1425         else
1426                 Alert::information(_("System reconfigured"),
1427                            _("The system has been reconfigured.\n"
1428                              "You need to restart LyX to make use of any\n"
1429                              "updated document class specifications."));
1430 }
1431
1432 void GuiApplication::validateCurrentView()
1433 {
1434         if (!d->views_.empty() && !current_view_) {
1435                 // currently at least one view exists but no view has the focus.
1436                 // choose the last view to make it current.
1437                 // a view without any open document is preferred.
1438                 GuiView * candidate = 0;
1439                 QHash<int, GuiView *>::const_iterator it = d->views_.begin();
1440                 QHash<int, GuiView *>::const_iterator end = d->views_.end();
1441                 for (; it != end; ++it) {
1442                         candidate = *it;
1443                         if (!candidate->documentBufferView())
1444                                 break;
1445                 }
1446                 setCurrentView(candidate);
1447         }
1448 }
1449
1450 void GuiApplication::dispatch(FuncRequest const & cmd, DispatchResult & dr)
1451 {
1452         string const argument = to_utf8(cmd.argument());
1453         FuncCode const action = cmd.action();
1454
1455         LYXERR(Debug::ACTION, "cmd: " << cmd);
1456
1457         // we have not done anything wrong yet.
1458         dr.setError(false);
1459
1460         FuncStatus const flag = getStatus(cmd);
1461         if (!flag.enabled()) {
1462                 // We cannot use this function here
1463                 LYXERR(Debug::ACTION, "action "
1464                        << lyxaction.getActionName(action)
1465                        << " [" << action << "] is disabled at this location");
1466                 dr.setMessage(flag.message());
1467                 dr.setError(true);
1468                 dr.dispatched(false);
1469                 dr.screenUpdate(Update::None);
1470                 dr.clearBufferUpdate();
1471                 return;
1472         };
1473
1474         if (cmd.origin() == FuncRequest::LYXSERVER) {
1475                 if (current_view_ && current_view_->currentBufferView())
1476                         current_view_->currentBufferView()->cursor().saveBeforeDispatchPosXY();
1477                 // we will also need to redraw the screen at the end
1478                 dr.screenUpdate(Update::FitCursor);
1479         }
1480
1481         // Assumes that the action will be dispatched.
1482         dr.dispatched(true);
1483
1484         switch (cmd.action()) {
1485
1486         case LFUN_WINDOW_NEW:
1487                 createView(toqstr(cmd.argument()));
1488                 break;
1489
1490         case LFUN_WINDOW_CLOSE:
1491                 // update bookmark pit of the current buffer before window close
1492                 for (size_t i = 0; i < theSession().bookmarks().size(); ++i)
1493                         gotoBookmark(i+1, false, false);
1494                 // clear the last opened list, because
1495                 // maybe this will end the session
1496                 theSession().lastOpened().clear();
1497                 // check for valid current_view_
1498                 validateCurrentView();
1499                 if (current_view_)
1500                         current_view_->closeScheduled();
1501                 break;
1502
1503         case LFUN_LYX_QUIT:
1504                 // quitting is triggered by the gui code
1505                 // (leaving the event loop).
1506                 if (current_view_)
1507                         current_view_->message(from_utf8(N_("Exiting.")));
1508                 if (closeAllViews())
1509                         quit();
1510                 break;
1511
1512         case LFUN_SCREEN_FONT_UPDATE: {
1513                 // handle the screen font changes.
1514                 d->font_loader_.update();
1515                 // Backup current_view_
1516                 GuiView * view = current_view_;
1517                 // Set current_view_ to zero to forbid GuiWorkArea::redraw()
1518                 // to skip the refresh.
1519                 current_view_ = 0;
1520                 theBufferList().changed(false);
1521                 // Restore current_view_
1522                 current_view_ = view;
1523                 break;
1524         }
1525
1526         case LFUN_BUFFER_NEW:
1527                 validateCurrentView();
1528                 if (d->views_.empty()
1529                    || (!lyxrc.open_buffers_in_tabs && current_view_->documentBufferView() != 0)) {
1530                         createView(QString(), false); // keep hidden
1531                         current_view_->newDocument(to_utf8(cmd.argument()), false);
1532                         current_view_->show();
1533                         setActiveWindow(current_view_);
1534                 } else {
1535                         current_view_->newDocument(to_utf8(cmd.argument()), false);
1536                 }
1537                 break;
1538
1539         case LFUN_BUFFER_NEW_TEMPLATE:
1540                 validateCurrentView();
1541                 if (d->views_.empty()
1542                    || (!lyxrc.open_buffers_in_tabs && current_view_->documentBufferView() != 0)) {
1543                         createView();
1544                         current_view_->newDocument(to_utf8(cmd.argument()), true);
1545                         if (!current_view_->documentBufferView())
1546                                 current_view_->close();
1547                 } else {
1548                         current_view_->newDocument(to_utf8(cmd.argument()), true);
1549                 }
1550                 break;
1551
1552         case LFUN_FILE_OPEN: {
1553                 validateCurrentView();
1554                 // FIXME: create a new method shared with LFUN_HELP_OPEN.
1555                 string const fname = to_utf8(cmd.argument());
1556                 if (d->views_.empty() || (!lyxrc.open_buffers_in_tabs
1557                           && current_view_->documentBufferView() != 0)) {
1558                         // We want the ui session to be saved per document and not per
1559                         // window number. The filename crc is a good enough identifier.
1560                         boost::crc_32_type crc;
1561                         crc = for_each(fname.begin(), fname.end(), crc);
1562                         createView(crc.checksum());
1563                         current_view_->openDocument(fname);
1564                         if (current_view_ && !current_view_->documentBufferView())
1565                                 current_view_->close();
1566                 } else
1567                         current_view_->openDocument(fname);
1568                 break;
1569         }
1570
1571         case LFUN_HELP_OPEN: {
1572                 // FIXME: create a new method shared with LFUN_FILE_OPEN.
1573                 if (current_view_ == 0)
1574                         createView();
1575                 string const arg = to_utf8(cmd.argument());
1576                 if (arg.empty()) {
1577                         current_view_->message(_("Missing argument"));
1578                         break;
1579                 }
1580                 FileName fname = i18nLibFileSearch("doc", arg, "lyx");
1581                 if (fname.empty())
1582                         fname = i18nLibFileSearch("examples", arg, "lyx");
1583
1584                 if (fname.empty()) {
1585                         lyxerr << "LyX: unable to find documentation file `"
1586                                << arg << "'. Bad installation?" << endl;
1587                         break;
1588                 }
1589                 current_view_->message(bformat(_("Opening help file %1$s..."),
1590                                                makeDisplayPath(fname.absFileName())));
1591                 Buffer * buf = current_view_->loadDocument(fname, false);
1592
1593 #ifndef DEVEL_VERSION
1594                 if (buf)
1595                         buf->setReadonly(true);
1596 #else
1597                 (void) buf;
1598 #endif
1599                 break;
1600         }
1601
1602         case LFUN_SET_COLOR: {
1603                 string lyx_name;
1604                 string const x11_name = split(to_utf8(cmd.argument()), lyx_name, ' ');
1605                 if (lyx_name.empty() || x11_name.empty()) {
1606                         if (current_view_)
1607                                 current_view_->message(
1608                                         _("Syntax: set-color <lyx_name> <x11_name>"));
1609                         break;
1610                 }
1611
1612 #if 0
1613                 // FIXME: The graphics cache no longer has a changeDisplay method.
1614                 string const graphicsbg = lcolor.getLyXName(Color_graphicsbg);
1615                 bool const graphicsbg_changed =
1616                                 lyx_name == graphicsbg && x11_name != graphicsbg;
1617                 if (graphicsbg_changed)
1618                         graphics::GCache::get().changeDisplay(true);
1619 #endif
1620
1621                 if (!lcolor.setColor(lyx_name, x11_name)) {
1622                         if (current_view_)
1623                                 current_view_->message(
1624                                         bformat(_("Set-color \"%1$s\" failed "
1625                                         "- color is undefined or "
1626                                         "may not be redefined"),
1627                                         from_utf8(lyx_name)));
1628                         break;
1629                 }
1630                 // Make sure we don't keep old colors in cache.
1631                 d->color_cache_.clear();
1632                 break;
1633         }
1634
1635         case LFUN_LYXRC_APPLY: {
1636                 // reset active key sequences, since the bindings
1637                 // are updated (bug 6064)
1638                 d->keyseq.reset();
1639                 LyXRC const lyxrc_orig = lyxrc;
1640
1641                 istringstream ss(to_utf8(cmd.argument()));
1642                 bool const success = lyxrc.read(ss);
1643
1644                 if (!success) {
1645                         lyxerr << "Warning in LFUN_LYXRC_APPLY!\n"
1646                                         << "Unable to read lyxrc data"
1647                                         << endl;
1648                         break;
1649                 }
1650
1651                 actOnUpdatedPrefs(lyxrc_orig, lyxrc);
1652                 resetGui();
1653
1654                 break;
1655         }
1656
1657         case LFUN_COMMAND_PREFIX:
1658                 dispatch(FuncRequest(LFUN_MESSAGE, d->keyseq.printOptions(true)));
1659                 break;
1660
1661         case LFUN_CANCEL: {
1662                 d->keyseq.reset();
1663                 d->meta_fake_bit = NoModifier;
1664                 GuiView * gv = currentView();
1665                 if (gv && gv->currentBufferView())
1666                         // cancel any selection
1667                         processFuncRequest(FuncRequest(LFUN_MARK_OFF));
1668                 dr.setMessage(from_ascii(N_("Cancel")));
1669                 break;
1670         }
1671         case LFUN_META_PREFIX:
1672                 d->meta_fake_bit = AltModifier;
1673                 dr.setMessage(d->keyseq.print(KeySequence::ForGui));
1674                 break;
1675
1676         // --- Menus -----------------------------------------------
1677         case LFUN_RECONFIGURE:
1678                 // argument is any additional parameter to the configure.py command
1679                 reconfigure(to_utf8(cmd.argument()));
1680                 break;
1681
1682         // --- lyxserver commands ----------------------------
1683         case LFUN_SERVER_GET_FILENAME: {
1684                 if (current_view_ && current_view_->documentBufferView()) {
1685                         docstring const fname = from_utf8(
1686                                 current_view_->documentBufferView()->buffer().absFileName());
1687                         dr.setMessage(fname);
1688                         LYXERR(Debug::INFO, "FNAME[" << fname << ']');
1689                 } else {
1690                         dr.setMessage(docstring());
1691                         LYXERR(Debug::INFO, "No current file for LFUN_SERVER_GET_FILENAME");
1692                 }
1693                 break;
1694         }
1695
1696         case LFUN_SERVER_NOTIFY: {
1697                 docstring const dispatch_buffer = d->keyseq.print(KeySequence::Portable);
1698                 dr.setMessage(dispatch_buffer);
1699                 theServer().notifyClient(to_utf8(dispatch_buffer));
1700                 break;
1701         }
1702
1703         case LFUN_CURSOR_FOLLOWS_SCROLLBAR_TOGGLE:
1704                 lyxrc.cursor_follows_scrollbar = !lyxrc.cursor_follows_scrollbar;
1705                 break;
1706
1707         case LFUN_REPEAT: {
1708                 // repeat command
1709                 string countstr;
1710                 string rest = split(argument, countstr, ' ');
1711                 istringstream is(countstr);
1712                 int count = 0;
1713                 is >> count;
1714                 //lyxerr << "repeat: count: " << count << " cmd: " << rest << endl;
1715                 for (int i = 0; i < count; ++i)
1716                         dispatch(lyxaction.lookupFunc(rest));
1717                 break;
1718         }
1719
1720         case LFUN_COMMAND_SEQUENCE: {
1721                 // argument contains ';'-terminated commands
1722                 string arg = argument;
1723                 // FIXME: this LFUN should also work without any view.
1724                 Buffer * buffer = (current_view_ && current_view_->documentBufferView())
1725                                   ? &(current_view_->documentBufferView()->buffer()) : 0;
1726                 if (buffer)
1727                         buffer->undo().beginUndoGroup();
1728                 while (!arg.empty()) {
1729                         string first;
1730                         arg = split(arg, first, ';');
1731                         FuncRequest func(lyxaction.lookupFunc(first));
1732                         func.setOrigin(cmd.origin());
1733                         dispatch(func);
1734                 }
1735                 // the buffer may have been closed by one action
1736                 if (theBufferList().isLoaded(buffer))
1737                         buffer->undo().endUndoGroup();
1738                 break;
1739         }
1740
1741         case LFUN_BUFFER_FORALL: {
1742                 FuncRequest const funcToRun = lyxaction.lookupFunc(cmd.getLongArg(0));
1743
1744                 map<Buffer *, GuiView *> views_lVisible;
1745                 map<GuiView *, Buffer *> activeBuffers;
1746
1747                 QList<GuiView *> allViews = d->views_.values();
1748
1749                 // this foreach does not modify any buffer. It just collects info on local visibility of buffers
1750                 // and on which buffer is active in each view.
1751                 Buffer * const last = theBufferList().last();
1752                 foreach (GuiView * view, allViews) {
1753                         // all of the buffers might be locally hidden. That is, there is no active buffer.
1754                         if (!view || !view->currentBufferView() || !&view->currentBufferView()->buffer())
1755                                 activeBuffers[view] = 0;
1756                         else
1757                                 activeBuffers[view] = &view->currentBufferView()->buffer();
1758
1759                         // find out if each is locally visible or locally hidden.
1760                         // we don't use a for loop as the buffer list cycles.
1761                         Buffer * b = theBufferList().first();
1762                         while (true) {
1763                                 bool const locallyVisible = view && view->workArea(*b);
1764                                 if (locallyVisible) {
1765                                         bool const exists_ = (views_lVisible.find(b) != views_lVisible.end());
1766                                         // only need to overwrite/add if we don't already know a buffer is globally
1767                                         // visible or we do know but we would prefer to dispatch LFUN from the
1768                                         // current view because of cursor position issues.
1769                                         if (!exists_ || (exists_ && views_lVisible[b] != current_view_))
1770                                                 views_lVisible[b] = view;
1771                                 }
1772                                 if (b == last)
1773                                         break;
1774                                 b = theBufferList().next(b);
1775                         }
1776                 }
1777
1778                 GuiView * const homeView = currentView();
1779                 Buffer * b = theBufferList().first();
1780                 Buffer * nextBuf = 0;
1781                 int numProcessed = 0;
1782                 while (true) {
1783                         if (b != last)
1784                                 nextBuf = theBufferList().next(b); // get next now bc LFUN might close current.
1785
1786                         bool const visible = (views_lVisible.find(b) != views_lVisible.end());
1787                         if (visible) {
1788                                 // first change to a view where b is locally visible, preferably current_view_.
1789                                 GuiView * const vLv = views_lVisible[b];
1790                                 vLv->setBuffer(b);
1791                                 lyx::dispatch(funcToRun);
1792                                 numProcessed++;
1793                         }
1794                         if (b == last)
1795                                 break;
1796                         b = nextBuf;
1797                 }
1798
1799                 // put things back to how they were (if possible).
1800                 foreach (GuiView * view, allViews) {
1801                         Buffer * originalBuf = activeBuffers[view];
1802                         // there might not have been an active buffer in this view or it might have been closed by the LFUN.
1803                         if (theBufferList().isLoaded(originalBuf))
1804                                 view->setBuffer(originalBuf);
1805                 }
1806                 homeView->setFocus();
1807
1808                 dr.setMessage(bformat(_("Applied \"%1$s\" to %2$d buffer(s)"), from_utf8(cmd.getLongArg(0)), numProcessed));
1809                 break;
1810         }
1811
1812         case LFUN_COMMAND_ALTERNATIVES: {
1813                 // argument contains ';'-terminated commands
1814                 string arg = argument;
1815                 while (!arg.empty()) {
1816                         string first;
1817                         arg = split(arg, first, ';');
1818                         FuncRequest func(lyxaction.lookupFunc(first));
1819                         func.setOrigin(cmd.origin());
1820                         FuncStatus const stat = getStatus(func);
1821                         if (stat.enabled()) {
1822                                 dispatch(func);
1823                                 break;
1824                         }
1825                 }
1826                 break;
1827         }
1828
1829         case LFUN_CALL: {
1830                 FuncRequest func;
1831                 if (theTopLevelCmdDef().lock(argument, func)) {
1832                         func.setOrigin(cmd.origin());
1833                         dispatch(func);
1834                         theTopLevelCmdDef().release(argument);
1835                 } else {
1836                         if (func.action() == LFUN_UNKNOWN_ACTION) {
1837                                 // unknown command definition
1838                                 lyxerr << "Warning: unknown command definition `"
1839                                                 << argument << "'"
1840                                                 << endl;
1841                         } else {
1842                                 // recursion detected
1843                                 lyxerr << "Warning: Recursion in the command definition `"
1844                                                 << argument << "' detected"
1845                                                 << endl;
1846                         }
1847                 }
1848                 break;
1849         }
1850
1851         case LFUN_PREFERENCES_SAVE:
1852                 lyxrc.write(support::makeAbsPath("preferences",
1853                         package().user_support().absFileName()), false);
1854                 break;
1855
1856         case LFUN_BUFFER_SAVE_AS_DEFAULT: {
1857                 string const fname = addName(addPath(package().user_support().absFileName(),
1858                         "templates/"), "defaults.lyx");
1859                 Buffer defaults(fname);
1860
1861                 istringstream ss(argument);
1862                 Lexer lex;
1863                 lex.setStream(ss);
1864                 int const unknown_tokens = defaults.readHeader(lex);
1865
1866                 if (unknown_tokens != 0) {
1867                         lyxerr << "Warning in LFUN_BUFFER_SAVE_AS_DEFAULT!\n"
1868                                << unknown_tokens << " unknown token"
1869                                << (unknown_tokens == 1 ? "" : "s")
1870                                << endl;
1871                 }
1872
1873                 if (defaults.writeFile(FileName(defaults.absFileName())))
1874                         dr.setMessage(bformat(_("Document defaults saved in %1$s"),
1875                                               makeDisplayPath(fname)));
1876                 else {
1877                         dr.setError(true);
1878                         dr.setMessage(from_ascii(N_("Unable to save document defaults")));
1879                 }
1880                 break;
1881         }
1882
1883         case LFUN_BOOKMARK_GOTO:
1884                 // go to bookmark, open unopened file and switch to buffer if necessary
1885                 gotoBookmark(convert<unsigned int>(to_utf8(cmd.argument())), true, true);
1886                 dr.screenUpdate(Update::Force | Update::FitCursor);
1887                 break;
1888
1889         case LFUN_BOOKMARK_CLEAR:
1890                 theSession().bookmarks().clear();
1891                 break;
1892
1893         case LFUN_DEBUG_LEVEL_SET:
1894                 lyxerr.setLevel(Debug::value(to_utf8(cmd.argument())));
1895                 break;
1896
1897         default:
1898                 // The LFUN must be for one of GuiView, BufferView, Buffer or Cursor;
1899                 // let's try that:
1900                 if (current_view_)
1901                         current_view_->dispatch(cmd, dr);
1902                 break;
1903         }
1904
1905         if (cmd.origin() == FuncRequest::LYXSERVER)
1906                 updateCurrentView(cmd, dr);
1907 }
1908
1909
1910 docstring GuiApplication::viewStatusMessage()
1911 {
1912         // When meta-fake key is pressed, show the key sequence so far + "M-".
1913         if (d->meta_fake_bit != NoModifier)
1914                 return d->keyseq.print(KeySequence::ForGui) + "M-";
1915
1916         // Else, when a non-complete key sequence is pressed,
1917         // show the available options.
1918         if (d->keyseq.length() > 0 && !d->keyseq.deleted())
1919                 return d->keyseq.printOptions(true);
1920
1921         return docstring();
1922 }
1923
1924
1925 void GuiApplication::handleKeyFunc(FuncCode action)
1926 {
1927         char_type c = 0;
1928
1929         if (d->keyseq.length())
1930                 c = 0;
1931         GuiView * gv = currentView();
1932         LASSERT(gv && gv->currentBufferView(), return);
1933         BufferView * bv = gv->currentBufferView();
1934         bv->getIntl().getTransManager().deadkey(
1935                 c, get_accent(action).accent, bv->cursor().innerText(),
1936                 bv->cursor());
1937         // Need to clear, in case the minibuffer calls these
1938         // actions
1939         d->keyseq.clear();
1940         // copied verbatim from do_accent_char
1941         bv->cursor().resetAnchor();
1942 }
1943
1944
1945 void GuiApplication::processKeySym(KeySymbol const & keysym, KeyModifier state)
1946 {
1947         LYXERR(Debug::KEY, "KeySym is " << keysym.getSymbolName());
1948
1949         // Do nothing if we have nothing (JMarc)
1950         if (!keysym.isOK()) {
1951                 LYXERR(Debug::KEY, "Empty kbd action (probably composing)");
1952                 if (current_view_)
1953                         current_view_->restartCursor();
1954                 return;
1955         }
1956
1957         if (keysym.isModifier()) {
1958                 if (current_view_)
1959                         current_view_->restartCursor();
1960                 return;
1961         }
1962
1963         char_type encoded_last_key = keysym.getUCSEncoded();
1964
1965         // Do a one-deep top-level lookup for
1966         // cancel and meta-fake keys. RVDK_PATCH_5
1967         d->cancel_meta_seq.reset();
1968
1969         FuncRequest func = d->cancel_meta_seq.addkey(keysym, state);
1970         LYXERR(Debug::KEY, "action first set to [" << func.action() << ']');
1971
1972         // When not cancel or meta-fake, do the normal lookup.
1973         // Note how the meta_fake Mod1 bit is OR-ed in and reset afterwards.
1974         // Mostly, meta_fake_bit = NoModifier. RVDK_PATCH_5.
1975         if ((func.action() != LFUN_CANCEL) && (func.action() != LFUN_META_PREFIX)) {
1976                 // remove Caps Lock and Mod2 as a modifiers
1977                 func = d->keyseq.addkey(keysym, (state | d->meta_fake_bit));
1978                 LYXERR(Debug::KEY, "action now set to [" << func.action() << ']');
1979         }
1980
1981         // Dont remove this unless you know what you are doing.
1982         d->meta_fake_bit = NoModifier;
1983
1984         // Can this happen now ?
1985         if (func.action() == LFUN_NOACTION)
1986                 func = FuncRequest(LFUN_COMMAND_PREFIX);
1987
1988         LYXERR(Debug::KEY, " Key [action=" << func.action() << "]["
1989                 << d->keyseq.print(KeySequence::Portable) << ']');
1990
1991         // already here we know if it any point in going further
1992         // why not return already here if action == -1 and
1993         // num_bytes == 0? (Lgb)
1994
1995         if (d->keyseq.length() > 1 && current_view_)
1996                 current_view_->message(d->keyseq.print(KeySequence::ForGui));
1997
1998
1999         // Maybe user can only reach the key via holding down shift.
2000         // Let's see. But only if shift is the only modifier
2001         if (func.action() == LFUN_UNKNOWN_ACTION && state == ShiftModifier) {
2002                 LYXERR(Debug::KEY, "Trying without shift");
2003                 func = d->keyseq.addkey(keysym, NoModifier);
2004                 LYXERR(Debug::KEY, "Action now " << func.action());
2005         }
2006
2007         if (func.action() == LFUN_UNKNOWN_ACTION) {
2008                 // Hmm, we didn't match any of the keysequences. See
2009                 // if it's normal insertable text not already covered
2010                 // by a binding
2011                 if (keysym.isText() && d->keyseq.length() == 1) {
2012                         LYXERR(Debug::KEY, "isText() is true, inserting.");
2013                         func = FuncRequest(LFUN_SELF_INSERT,
2014                                            FuncRequest::KEYBOARD);
2015                 } else {
2016                         LYXERR(Debug::KEY, "Unknown, !isText() - giving up");
2017                         if (current_view_) {
2018                                 current_view_->message(_("Unknown function."));
2019                                 current_view_->restartCursor();
2020                         }
2021                         return;
2022                 }
2023         }
2024
2025         if (func.action() == LFUN_SELF_INSERT) {
2026                 if (encoded_last_key != 0) {
2027                         docstring const arg(1, encoded_last_key);
2028                         processFuncRequest(FuncRequest(LFUN_SELF_INSERT, arg,
2029                                              FuncRequest::KEYBOARD));
2030                         LYXERR(Debug::KEY, "SelfInsert arg[`" << to_utf8(arg) << "']");
2031                 }
2032         } else
2033                 processFuncRequest(func);
2034 }
2035
2036
2037 void GuiApplication::processFuncRequest(FuncRequest const & func)
2038 {
2039         lyx::dispatch(func);
2040 }
2041
2042
2043 void GuiApplication::processFuncRequestAsync(FuncRequest const & func)
2044 {
2045         addToFuncRequestQueue(func);
2046         processFuncRequestQueueAsync();
2047 }
2048
2049
2050 void GuiApplication::processFuncRequestQueue()
2051 {
2052         while (!d->func_request_queue_.empty()) {
2053                 processFuncRequest(d->func_request_queue_.front());
2054                 d->func_request_queue_.pop();
2055         }
2056 }
2057
2058
2059 void GuiApplication::processFuncRequestQueueAsync()
2060 {
2061         QTimer::singleShot(0, this, SLOT(slotProcessFuncRequestQueue()));
2062 }
2063
2064
2065 void GuiApplication::addToFuncRequestQueue(FuncRequest const & func)
2066 {
2067         d->func_request_queue_.push(func);
2068 }
2069
2070
2071 void GuiApplication::resetGui()
2072 {
2073         // Set the language defined by the user.
2074         setGuiLanguage();
2075
2076         // Read menus
2077         if (!readUIFile(toqstr(lyxrc.ui_file)))
2078                 // Gives some error box here.
2079                 return;
2080
2081         if (d->global_menubar_)
2082                 d->menus_.fillMenuBar(d->global_menubar_, 0, false);
2083
2084         QHash<int, GuiView *>::iterator it;
2085         for (it = d->views_.begin(); it != d->views_.end(); ++it) {
2086                 GuiView * gv = *it;
2087                 setCurrentView(gv);
2088                 gv->setLayoutDirection(layoutDirection());
2089                 gv->resetDialogs();
2090         }
2091
2092         processFuncRequest(FuncRequest(LFUN_SCREEN_FONT_UPDATE));
2093 }
2094
2095
2096 void GuiApplication::createView(int view_id)
2097 {
2098         createView(QString(), true, view_id);
2099 }
2100
2101
2102 void GuiApplication::createView(QString const & geometry_arg, bool autoShow,
2103         int view_id)
2104 {
2105         // release the keyboard which might have been grabed by the global
2106         // menubar on Mac to catch shortcuts even without any GuiView.
2107         if (d->global_menubar_)
2108                 d->global_menubar_->releaseKeyboard();
2109
2110         // create new view
2111         int id = view_id;
2112         while (d->views_.find(id) != d->views_.end())
2113                 id++;
2114
2115         LYXERR(Debug::GUI, "About to create new window with ID " << id);
2116         GuiView * view = new GuiView(id);
2117         // register view
2118         d->views_[id] = view;
2119
2120         if (autoShow) {
2121                 view->show();
2122                 setActiveWindow(view);
2123         }
2124
2125         if (!geometry_arg.isEmpty()) {
2126 #ifdef Q_WS_WIN
2127                 int x, y;
2128                 int w, h;
2129                 QRegExp re( "[=]*(?:([0-9]+)[xX]([0-9]+)){0,1}[ ]*(?:([+-][0-9]*)([+-][0-9]*)){0,1}" );
2130                 re.indexIn(geometry_arg);
2131                 w = re.cap(1).toInt();
2132                 h = re.cap(2).toInt();
2133                 x = re.cap(3).toInt();
2134                 y = re.cap(4).toInt();
2135                 view->setGeometry(x, y, w, h);
2136 #endif
2137         }
2138         view->setFocus();
2139 }
2140
2141
2142 Clipboard & GuiApplication::clipboard()
2143 {
2144         return d->clipboard_;
2145 }
2146
2147
2148 Selection & GuiApplication::selection()
2149 {
2150         return d->selection_;
2151 }
2152
2153
2154 FontLoader & GuiApplication::fontLoader()
2155 {
2156         return d->font_loader_;
2157 }
2158
2159
2160 Toolbars const & GuiApplication::toolbars() const
2161 {
2162         return d->toolbars_;
2163 }
2164
2165
2166 Toolbars & GuiApplication::toolbars()
2167 {
2168         return d->toolbars_;
2169 }
2170
2171
2172 Menus const & GuiApplication::menus() const
2173 {
2174         return d->menus_;
2175 }
2176
2177
2178 Menus & GuiApplication::menus()
2179 {
2180         return d->menus_;
2181 }
2182
2183
2184 QList<int> GuiApplication::viewIds() const
2185 {
2186         return d->views_.keys();
2187 }
2188
2189
2190 ColorCache & GuiApplication::colorCache()
2191 {
2192         return d->color_cache_;
2193 }
2194
2195
2196 int GuiApplication::exec()
2197 {
2198         // asynchronously handle batch commands. This event will be in
2199         // the event queue in front of other asynchronous events. Hence,
2200         // we can assume in the latter that the gui is setup already.
2201         QTimer::singleShot(0, this, SLOT(execBatchCommands()));
2202
2203         return QApplication::exec();
2204 }
2205
2206
2207 void GuiApplication::exit(int status)
2208 {
2209         QApplication::exit(status);
2210 }
2211
2212
2213 void GuiApplication::setGuiLanguage()
2214 {
2215         QString const default_language = toqstr(getGuiMessages().language());
2216         LYXERR(Debug::LOCALE, "Trying to set default locale to: " << default_language);
2217         QLocale const default_locale(default_language);
2218         QLocale::setDefault(default_locale);
2219
2220         // install translation file for Qt built-in dialogs
2221         QString const language_name = QString("qt_") + default_locale.name();
2222
2223         // language_name can be short (e.g. qt_zh) or long (e.g. qt_zh_CN).
2224         // Short-named translator can be loaded from a long name, but not the
2225         // opposite. Therefore, long name should be used without truncation.
2226         // c.f. http://doc.trolltech.com/4.1/qtranslator.html#load
2227         if (!d->qt_trans_.load(language_name,
2228                         QLibraryInfo::location(QLibraryInfo::TranslationsPath))) {
2229                 LYXERR(Debug::LOCALE, "Could not find Qt translations for locale "
2230                         << language_name);
2231         } else {
2232                 LYXERR(Debug::LOCALE, "Successfully installed Qt translations for locale "
2233                         << language_name);
2234         }
2235
2236         switch (default_locale.language()) {
2237         case QLocale::Arabic :
2238         case QLocale::Hebrew :
2239         case QLocale::Persian :
2240         case QLocale::Urdu :
2241                 setLayoutDirection(Qt::RightToLeft);
2242                 break;
2243         default:
2244                 setLayoutDirection(Qt::LeftToRight);
2245         }
2246 }
2247
2248
2249 void GuiApplication::execBatchCommands()
2250 {
2251         setGuiLanguage();
2252
2253         // Read menus
2254         if (!readUIFile(toqstr(lyxrc.ui_file)))
2255                 // Gives some error box here.
2256                 return;
2257
2258 #ifdef Q_WS_MACX
2259 #if QT_VERSION > 0x040600
2260         setAttribute(Qt::AA_MacDontSwapCtrlAndMeta,lyxrc.mac_dontswap_ctrl_meta);
2261 #endif
2262         // Create the global default menubar which is shown for the dialogs
2263         // and if no GuiView is visible.
2264         // This must be done after the session was recovered to know the "last files".
2265         d->global_menubar_ = new GlobalMenuBar;
2266         d->menus_.fillMenuBar(d->global_menubar_, 0, true);
2267 #endif
2268
2269         lyx::execBatchCommands();
2270 }
2271
2272
2273 QAbstractItemModel * GuiApplication::languageModel()
2274 {
2275         if (d->language_model_)
2276                 return d->language_model_;
2277
2278         QStandardItemModel * lang_model = new QStandardItemModel(this);
2279         lang_model->insertColumns(0, 3);
2280         int current_row;
2281         QIcon speller(getPixmap("images/", "dialog-show_spellchecker", "png"));
2282         QIcon saurus(getPixmap("images/", "thesaurus-entry", "png"));
2283         Languages::const_iterator it = lyx::languages.begin();
2284         Languages::const_iterator end = lyx::languages.end();
2285         for (; it != end; ++it) {
2286                 current_row = lang_model->rowCount();
2287                 lang_model->insertRows(current_row, 1);
2288                 QModelIndex pl_item = lang_model->index(current_row, 0);
2289                 QModelIndex sp_item = lang_model->index(current_row, 1);
2290                 QModelIndex th_item = lang_model->index(current_row, 2);
2291                 lang_model->setData(pl_item, qt_(it->second.display()), Qt::DisplayRole);
2292                 lang_model->setData(pl_item, toqstr(it->second.lang()), Qt::UserRole);
2293                 lang_model->setData(sp_item, qt_(it->second.display()), Qt::DisplayRole);
2294                 lang_model->setData(sp_item, toqstr(it->second.lang()), Qt::UserRole);
2295                 if (theSpellChecker() && theSpellChecker()->hasDictionary(&it->second))
2296                         lang_model->setData(sp_item, speller, Qt::DecorationRole);
2297                 lang_model->setData(th_item, qt_(it->second.display()), Qt::DisplayRole);
2298                 lang_model->setData(th_item, toqstr(it->second.lang()), Qt::UserRole);
2299                 if (thesaurus.thesaurusInstalled(from_ascii(it->second.code())))
2300                         lang_model->setData(th_item, saurus, Qt::DecorationRole);
2301         }
2302         d->language_model_ = new QSortFilterProxyModel(this);
2303         d->language_model_->setSourceModel(lang_model);
2304         d->language_model_->setSortLocaleAware(true);
2305         return d->language_model_;
2306 }
2307
2308
2309 void GuiApplication::restoreGuiSession()
2310 {
2311         if (!lyxrc.load_session)
2312                 return;
2313
2314         Session & session = theSession();
2315         LastOpenedSection::LastOpened const & lastopened =
2316                 session.lastOpened().getfiles();
2317
2318         validateCurrentView();
2319
2320         FileName active_file;
2321         // do not add to the lastfile list since these files are restored from
2322         // last session, and should be already there (regular files), or should
2323         // not be added at all (help files).
2324         for (size_t i = 0; i < lastopened.size(); ++i) {
2325                 FileName const & file_name = lastopened[i].file_name;
2326                 if (d->views_.empty() || (!lyxrc.open_buffers_in_tabs
2327                           && current_view_->documentBufferView() != 0)) {
2328                         boost::crc_32_type crc;
2329                         string const & fname = file_name.absFileName();
2330                         crc = for_each(fname.begin(), fname.end(), crc);
2331                         createView(crc.checksum());
2332                 }
2333                 current_view_->loadDocument(file_name, false);
2334
2335                 if (lastopened[i].active)
2336                         active_file = file_name;
2337         }
2338
2339         // Restore last active buffer
2340         Buffer * buffer = theBufferList().getBuffer(active_file);
2341         if (buffer && current_view_)
2342                 current_view_->setBuffer(buffer);
2343
2344         // clear this list to save a few bytes of RAM
2345         session.lastOpened().clear();
2346 }
2347
2348
2349 QString const GuiApplication::romanFontName()
2350 {
2351         QFont font;
2352         font.setKerning(false);
2353         font.setStyleHint(QFont::Serif);
2354         font.setFamily("serif");
2355
2356         return QFontInfo(font).family();
2357 }
2358
2359
2360 QString const GuiApplication::sansFontName()
2361 {
2362         QFont font;
2363         font.setKerning(false);
2364         font.setStyleHint(QFont::SansSerif);
2365         font.setFamily("sans");
2366
2367         return QFontInfo(font).family();
2368 }
2369
2370
2371 QString const GuiApplication::typewriterFontName()
2372 {
2373         QFont font;
2374         font.setKerning(false);
2375         font.setStyleHint(QFont::TypeWriter);
2376         font.setFamily("monospace");
2377
2378         return QFontInfo(font).family();
2379 }
2380
2381
2382 void GuiApplication::handleRegularEvents()
2383 {
2384         ForkedCallsController::handleCompletedProcesses();
2385 }
2386
2387
2388 bool GuiApplication::event(QEvent * e)
2389 {
2390         switch(e->type()) {
2391         case QEvent::FileOpen: {
2392                 // Open a file; this happens only on Mac OS X for now.
2393                 //
2394                 // We do this asynchronously because on startup the batch
2395                 // commands are not executed here yet and the gui is not ready
2396                 // therefore.
2397                 QFileOpenEvent * foe = static_cast<QFileOpenEvent *>(e);
2398                 FuncRequest const fr(LFUN_FILE_OPEN, qstring_to_ucs4(foe->file()));
2399                 processFuncRequestAsync(fr);
2400                 e->accept();
2401                 return true;
2402         }
2403         default:
2404                 return QApplication::event(e);
2405         }
2406 }
2407
2408
2409 bool GuiApplication::notify(QObject * receiver, QEvent * event)
2410 {
2411         try {
2412                 return QApplication::notify(receiver, event);
2413         }
2414         catch (ExceptionMessage const & e) {
2415                 switch(e.type_) {
2416                 case ErrorException:
2417                         emergencyCleanup();
2418                         setQuitOnLastWindowClosed(false);
2419                         closeAllViews();
2420                         Alert::error(e.title_, e.details_);
2421 #ifndef NDEBUG
2422                         // Properly crash in debug mode in order to get a useful backtrace.
2423                         abort();
2424 #endif
2425                         // In release mode, try to exit gracefully.
2426                         this->exit(1);
2427
2428                 case BufferException: {
2429                         if (!current_view_ || !current_view_->documentBufferView())
2430                                 return false;
2431                         Buffer * buf = &current_view_->documentBufferView()->buffer();
2432                         docstring details = e.details_ + '\n';
2433                         details += buf->emergencyWrite();
2434                         theBufferList().release(buf);
2435                         details += "\n" + _("The current document was closed.");
2436                         Alert::error(e.title_, details);
2437                         return false;
2438                 }
2439                 case WarningException:
2440                         Alert::warning(e.title_, e.details_);
2441                         return false;
2442                 }
2443         }
2444         catch (exception const & e) {
2445                 docstring s = _("LyX has caught an exception, it will now "
2446                         "attempt to save all unsaved documents and exit."
2447                         "\n\nException: ");
2448                 s += from_ascii(e.what());
2449                 Alert::error(_("Software exception Detected"), s);
2450                 lyx_exit(1);
2451         }
2452         catch (...) {
2453                 docstring s = _("LyX has caught some really weird exception, it will "
2454                         "now attempt to save all unsaved documents and exit.");
2455                 Alert::error(_("Software exception Detected"), s);
2456                 lyx_exit(1);
2457         }
2458
2459         return false;
2460 }
2461
2462
2463 bool GuiApplication::getRgbColor(ColorCode col, RGBColor & rgbcol)
2464 {
2465         QColor const & qcol = d->color_cache_.get(col);
2466         if (!qcol.isValid()) {
2467                 rgbcol.r = 0;
2468                 rgbcol.g = 0;
2469                 rgbcol.b = 0;
2470                 return false;
2471         }
2472         rgbcol.r = qcol.red();
2473         rgbcol.g = qcol.green();
2474         rgbcol.b = qcol.blue();
2475         return true;
2476 }
2477
2478
2479 bool Application::getRgbColorUncached(ColorCode col, RGBColor & rgbcol)
2480 {
2481         QColor const qcol(lcolor.getX11Name(col).c_str());
2482         if (!qcol.isValid()) {
2483                 rgbcol.r = 0;
2484                 rgbcol.g = 0;
2485                 rgbcol.b = 0;
2486                 return false;
2487         }
2488         rgbcol.r = qcol.red();
2489         rgbcol.g = qcol.green();
2490         rgbcol.b = qcol.blue();
2491         return true;
2492 }
2493
2494
2495 string const GuiApplication::hexName(ColorCode col)
2496 {
2497         return ltrim(fromqstr(d->color_cache_.get(col).name()), "#");
2498 }
2499
2500
2501 void GuiApplication::registerSocketCallback(int fd, SocketCallback func)
2502 {
2503         SocketNotifier * sn = new SocketNotifier(this, fd, func);
2504         d->socket_notifiers_[fd] = sn;
2505         connect(sn, SIGNAL(activated(int)), this, SLOT(socketDataReceived(int)));
2506 }
2507
2508
2509 void GuiApplication::socketDataReceived(int fd)
2510 {
2511         d->socket_notifiers_[fd]->func_();
2512 }
2513
2514
2515 void GuiApplication::unregisterSocketCallback(int fd)
2516 {
2517         d->socket_notifiers_.take(fd)->setEnabled(false);
2518 }
2519
2520
2521 void GuiApplication::commitData(QSessionManager & sm)
2522 {
2523         /// The implementation is required to avoid an application exit
2524         /// when session state save is triggered by session manager.
2525         /// The default implementation sends a close event to all
2526         /// visible top level widgets when session managment allows
2527         /// interaction.
2528         /// We are changing that to close all wiew one by one.
2529         /// FIXME: verify if the default implementation is enough now.
2530         #ifdef QT_NO_SESSIONMANAGER
2531                 #ifndef _MSC_VER
2532                         #warning Qt is compiled without session manager
2533                 #else
2534                         #pragma message("warning: Qt is compiled without session manager")
2535                 #endif
2536                 (void) sm;
2537         #else
2538                 if (sm.allowsInteraction() && !closeAllViews())
2539                         sm.cancel();
2540         #endif
2541 }
2542
2543
2544 void GuiApplication::unregisterView(GuiView * gv)
2545 {
2546         LAPPERR(d->views_[gv->id()] == gv);
2547         d->views_.remove(gv->id());
2548         if (current_view_ == gv)
2549                 current_view_ = 0;
2550 }
2551
2552
2553 bool GuiApplication::closeAllViews()
2554 {
2555         if (d->views_.empty())
2556                 return true;
2557
2558         // When a view/window was closed before without quitting LyX, there
2559         // are already entries in the lastOpened list.
2560         theSession().lastOpened().clear();
2561
2562         QList<GuiView *> const views = d->views_.values();
2563         foreach (GuiView * view, views) {
2564                 if (!view->closeScheduled())
2565                         return false;
2566         }
2567
2568         d->views_.clear();
2569         return true;
2570 }
2571
2572
2573 GuiView & GuiApplication::view(int id) const
2574 {
2575         LAPPERR(d->views_.contains(id));
2576         return *d->views_.value(id);
2577 }
2578
2579
2580 void GuiApplication::hideDialogs(string const & name, Inset * inset) const
2581 {
2582         QList<GuiView *> const views = d->views_.values();
2583         foreach (GuiView * view, views)
2584                 view->hideDialog(name, inset);
2585 }
2586
2587
2588 Buffer const * GuiApplication::updateInset(Inset const * inset) const
2589 {
2590         Buffer const * buffer_ = 0;
2591         QHash<int, GuiView *>::const_iterator end = d->views_.end();
2592         for (QHash<int, GuiView *>::iterator it = d->views_.begin(); it != end; ++it) {
2593                 if (Buffer const * ptr = (*it)->updateInset(inset))
2594                         buffer_ = ptr;
2595         }
2596         return buffer_;
2597 }
2598
2599
2600 bool GuiApplication::searchMenu(FuncRequest const & func,
2601         docstring_list & names) const
2602 {
2603         return d->menus_.searchMenu(func, names);
2604 }
2605
2606
2607 // Ensure that a file is read only once (prevents include loops)
2608 static QStringList uifiles;
2609 // store which ui files define Toolbars
2610 static QStringList toolbar_uifiles;
2611
2612
2613 GuiApplication::ReturnValues GuiApplication::readUIFile(FileName ui_path)
2614 {
2615         enum {
2616                 ui_menuset = 1,
2617                 ui_toolbars,
2618                 ui_toolbarset,
2619                 ui_include,
2620                 ui_format,
2621                 ui_last
2622         };
2623
2624         LexerKeyword uitags[] = {
2625                 { "format", ui_format },
2626                 { "include", ui_include },
2627                 { "menuset", ui_menuset },
2628                 { "toolbars", ui_toolbars },
2629                 { "toolbarset", ui_toolbarset }
2630         };
2631
2632         Lexer lex(uitags);
2633         lex.setFile(ui_path);
2634         if (!lex.isOK()) {
2635                 lyxerr << "Unable to set LyXLeX for ui file: " << ui_path
2636                                          << endl;
2637         }
2638
2639         if (lyxerr.debugging(Debug::PARSER))
2640                 lex.printTable(lyxerr);
2641
2642         bool error = false;
2643         // format before introduction of format tag
2644         unsigned int format = 0;
2645         while (lex.isOK()) {
2646                 int const status = lex.lex();
2647
2648                 // we have to do this check here, outside the switch,
2649                 // because otherwise we would start reading include files,
2650                 // e.g., if the first tag we hit was an include tag.
2651                 if (status == ui_format)
2652                         if (lex.next()) {
2653                                 format = lex.getInteger();
2654                                 continue;
2655                         }
2656
2657                 // this will trigger unless the first tag we hit is a format
2658                 // tag, with the right format.
2659                 if (format != LFUN_FORMAT)
2660                         return FormatMismatch;
2661
2662                 switch (status) {
2663                 case Lexer::LEX_FEOF:
2664                         continue;
2665
2666                 case ui_include: {
2667                         lex.next(true);
2668                         QString const file = toqstr(lex.getString());
2669                         bool const success = readUIFile(file, true);
2670                         if (!success) {
2671                                 LYXERR0("Failed to read inlcuded file: " << fromqstr(file));
2672                                 return ReadError;
2673                         }
2674                         break;
2675                 }
2676
2677                 case ui_menuset:
2678                         d->menus_.read(lex);
2679                         break;
2680
2681                 case ui_toolbarset:
2682                         d->toolbars_.readToolbars(lex);
2683                         break;
2684
2685                 case ui_toolbars:
2686                         d->toolbars_.readToolbarSettings(lex);
2687                         toolbar_uifiles.push_back(toqstr(ui_path.absFileName()));
2688                         break;
2689
2690                 default:
2691                         if (!rtrim(lex.getString()).empty())
2692                                 lex.printError("LyX::ReadUIFile: "
2693                                                "Unknown menu tag: `$$Token'");
2694                         else
2695                                 LYXERR0("Error with status: " << status);
2696                         error = true;
2697                         break;
2698                 }
2699
2700         }
2701         return (error ? ReadError : ReadOK);
2702 }
2703
2704
2705 bool GuiApplication::readUIFile(QString const & name, bool include)
2706 {
2707         LYXERR(Debug::INIT, "About to read " << name << "...");
2708
2709         FileName ui_path;
2710         if (include) {
2711                 ui_path = libFileSearch("ui", name, "inc");
2712                 if (ui_path.empty())
2713                         ui_path = libFileSearch("ui", changeExtension(name, "inc"));
2714         } else {
2715                 ui_path = libFileSearch("ui", name, "ui");
2716         }
2717
2718         if (ui_path.empty()) {
2719                 static const QString defaultUIFile = "default";
2720                 LYXERR(Debug::INIT, "Could not find " << name);
2721                 if (include) {
2722                         Alert::warning(_("Could not find UI definition file"),
2723                                 bformat(_("Error while reading the included file\n%1$s\n"
2724                                         "Please check your installation."), qstring_to_ucs4(name)));
2725                         return false;
2726                 }
2727                 if (name == defaultUIFile) {
2728                         LYXERR(Debug::INIT, "Could not find default UI file!!");
2729                         Alert::warning(_("Could not find default UI file"),
2730                                 _("LyX could not find the default UI file!\n"
2731                                   "Please check your installation."));
2732                         return false;
2733                 }
2734                 Alert::warning(_("Could not find UI definition file"),
2735                 bformat(_("Error while reading the configuration file\n%1$s\n"
2736                         "Falling back to default.\n"
2737                         "Please look under Tools>Preferences>User Interface and\n"
2738                         "check which User Interface file you are using."), qstring_to_ucs4(name)));
2739                 return readUIFile(defaultUIFile, false);
2740         }
2741
2742         QString const uifile = toqstr(ui_path.absFileName());
2743         if (uifiles.contains(uifile)) {
2744                 if (!include) {
2745                         // We are reading again the top uifile so reset the safeguard:
2746                         uifiles.clear();
2747                         d->menus_.reset();
2748                         d->toolbars_.reset();
2749                 } else {
2750                         LYXERR(Debug::INIT, "UI file '" << name << "' has been read already. "
2751                                 << "Is this an include loop?");
2752                         return false;
2753                 }
2754         }
2755         uifiles.push_back(uifile);
2756
2757         LYXERR(Debug::INIT, "Found " << name << " in " << ui_path);
2758
2759         ReturnValues retval = readUIFile(ui_path);
2760
2761         if (retval == FormatMismatch) {
2762                 LYXERR(Debug::FILES, "Converting ui file to format " << LFUN_FORMAT);
2763                 FileName const tempfile = FileName::tempName("convert_ui");
2764                 bool const success = prefs2prefs(ui_path, tempfile, true);
2765                 if (!success) {
2766                         LYXERR0("Unable to convert " << ui_path.absFileName() <<
2767                                 " to format " << LFUN_FORMAT << ".");
2768                 } else {
2769                         retval = readUIFile(tempfile);
2770                         tempfile.removeFile();
2771                 }
2772         }
2773
2774         if (retval != ReadOK) {
2775                 LYXERR0("Unable to read UI file: " << ui_path.absFileName());
2776                 return false;
2777         }
2778
2779         if (include)
2780                 return true;
2781
2782         QSettings settings;
2783         settings.beginGroup("ui_files");
2784         bool touched = false;
2785         for (int i = 0; i != uifiles.size(); ++i) {
2786                 QFileInfo fi(uifiles[i]);
2787                 QDateTime const date_value = fi.lastModified();
2788                 QString const name_key = QString::number(i);
2789                 // if an ui file which defines Toolbars has changed,
2790                 // we have to reset the settings
2791                 if (toolbar_uifiles.contains(uifiles[i])
2792                  && (!settings.contains(name_key)
2793                  || settings.value(name_key).toString() != uifiles[i]
2794                  || settings.value(name_key + "/date").toDateTime() != date_value)) {
2795                         touched = true;
2796                         settings.setValue(name_key, uifiles[i]);
2797                         settings.setValue(name_key + "/date", date_value);
2798                 }
2799         }
2800         settings.endGroup();
2801         if (touched)
2802                 settings.remove("views");
2803
2804         return true;
2805 }
2806
2807
2808 void GuiApplication::onLastWindowClosed()
2809 {
2810         if (d->global_menubar_)
2811                 d->global_menubar_->grabKeyboard();
2812 }
2813
2814
2815 void GuiApplication::startLongOperation() {
2816         d->key_checker_.start();
2817 }
2818
2819
2820 bool GuiApplication::longOperationCancelled() {
2821         return d->key_checker_.pressed();
2822 }
2823
2824
2825 void GuiApplication::stopLongOperation() {
2826         d->key_checker_.stop();
2827 }
2828
2829
2830 bool GuiApplication::longOperationStarted() {
2831         return d->key_checker_.started();
2832 }
2833
2834
2835 ////////////////////////////////////////////////////////////////////////
2836 //
2837 // X11 specific stuff goes here...
2838
2839 #ifdef Q_WS_X11
2840 bool GuiApplication::x11EventFilter(XEvent * xev)
2841 {
2842         if (!current_view_)
2843                 return false;
2844
2845         switch (xev->type) {
2846         case SelectionRequest: {
2847                 if (xev->xselectionrequest.selection != XA_PRIMARY)
2848                         break;
2849                 LYXERR(Debug::SELECTION, "X requested selection.");
2850                 BufferView * bv = current_view_->currentBufferView();
2851                 if (bv) {
2852                         docstring const sel = bv->requestSelection();
2853                         if (!sel.empty())
2854                                 d->selection_.put(sel);
2855                 }
2856                 break;
2857         }
2858         case SelectionClear: {
2859                 if (xev->xselectionclear.selection != XA_PRIMARY)
2860                         break;
2861                 LYXERR(Debug::SELECTION, "Lost selection.");
2862                 BufferView * bv = current_view_->currentBufferView();
2863                 if (bv)
2864                         bv->clearSelection();
2865                 break;
2866         }
2867         }
2868         return false;
2869 }
2870 #endif
2871
2872 } // namespace frontend
2873
2874
2875 void hideDialogs(std::string const & name, Inset * inset)
2876 {
2877         if (theApp())
2878                 frontend::guiApp->hideDialogs(name, inset);
2879 }
2880
2881
2882 ////////////////////////////////////////////////////////////////////
2883 //
2884 // Font stuff
2885 //
2886 ////////////////////////////////////////////////////////////////////
2887
2888 frontend::FontLoader & theFontLoader()
2889 {
2890         LAPPERR(frontend::guiApp);
2891         return frontend::guiApp->fontLoader();
2892 }
2893
2894
2895 frontend::FontMetrics const & theFontMetrics(Font const & f)
2896 {
2897         return theFontMetrics(f.fontInfo());
2898 }
2899
2900
2901 frontend::FontMetrics const & theFontMetrics(FontInfo const & f)
2902 {
2903         LAPPERR(frontend::guiApp);
2904         return frontend::guiApp->fontLoader().metrics(f);
2905 }
2906
2907
2908 ////////////////////////////////////////////////////////////////////
2909 //
2910 // Misc stuff
2911 //
2912 ////////////////////////////////////////////////////////////////////
2913
2914 frontend::Clipboard & theClipboard()
2915 {
2916         LAPPERR(frontend::guiApp);
2917         return frontend::guiApp->clipboard();
2918 }
2919
2920
2921 frontend::Selection & theSelection()
2922 {
2923         LAPPERR(frontend::guiApp);
2924         return frontend::guiApp->selection();
2925 }
2926
2927
2928 } // namespace lyx
2929
2930 #include "moc_GuiApplication.cpp"