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