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