2 * \file GuiApplication.cpp
3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
8 * \author Abdelrazak Younes
10 * Full author contact details are available in file CREDITS.
15 #include "GuiApplication.h"
17 #include "ColorCache.h"
19 #include "GuiClipboard.h"
21 #include "GuiKeySymbol.h"
22 #include "GuiSelection.h"
25 #include "qt_helpers.h"
28 #include "frontends/alert.h"
29 #include "frontends/Application.h"
30 #include "frontends/FontLoader.h"
31 #include "frontends/FontMetrics.h"
34 #include "BufferList.h"
35 #include "BufferView.h"
38 #include "FuncRequest.h"
39 #include "FuncStatus.h"
43 #include "LyXAction.h"
49 #include "support/lassert.h"
50 #include "support/debug.h"
51 #include "support/ExceptionMessage.h"
52 #include "support/FileName.h"
53 #include "support/filetools.h"
54 #include "support/foreach.h"
55 #include "support/ForkedCalls.h"
56 #include "support/gettext.h"
57 #include "support/lstrings.h"
58 #include "support/lyxalgo.h" // sorted
59 #include "support/Messages.h"
60 #include "support/os.h"
61 #include "support/Package.h"
64 #include "support/linkback/LinkBackProxy.h"
74 #include <QFileOpenEvent>
78 #include <QImageReader>
80 #include <QLibraryInfo>
82 #include <QMacPasteboardMime>
87 #include <QPixmapCache>
89 #include <QSessionManager>
91 #include <QSocketNotifier>
92 #include <QSortFilterProxyModel>
93 #include <QStandardItemModel>
96 #include <QTranslator>
100 #include <X11/Xatom.h>
101 #include <X11/Xlib.h>
107 #include <QWindowsMime>
114 #include <boost/bind.hpp>
115 #include <boost/crc.hpp>
121 using namespace lyx::support;
124 static void initializeResources()
126 static bool initialized = false;
128 Q_INIT_RESOURCE(Resources);
136 frontend::Application * createApplication(int & argc, char * argv[])
139 // prune -geometry argument(s) by shifting
140 // the following ones 2 places down.
141 for (int i = 0 ; i < argc ; ++i) {
142 if (strcmp(argv[i], "-geometry") == 0) {
143 int const remove = (i+1) < argc ? 2 : 1;
145 for (int j = i; j < argc; ++j)
146 argv[j] = argv[j + remove];
151 return new frontend::GuiApplication(argc, argv);
157 /// Return the list of loadable formats.
158 vector<string> loadableImageFormats()
162 QList<QByteArray> qt_formats = QImageReader::supportedImageFormats();
164 LYXERR(Debug::GRAPHICS,
165 "\nThe image loader can load the following directly:\n");
167 if (qt_formats.empty())
168 LYXERR(Debug::GRAPHICS, "\nQt4 Problem: No Format available!");
170 for (QList<QByteArray>::const_iterator it = qt_formats.begin(); it != qt_formats.end(); ++it) {
172 LYXERR(Debug::GRAPHICS, (const char *) *it << ", ");
174 string ext = ascii_lowercase((const char *) *it);
185 ////////////////////////////////////////////////////////////////////////
187 // Icon loading support code
189 ////////////////////////////////////////////////////////////////////////
199 bool operator<(PngMap const & lhs, PngMap const & rhs)
201 return lhs.key < rhs.key;
207 CompareKey(QString const & name) : name_(name) {}
208 bool operator()(PngMap const & other) const { return other.key == name_; }
214 // this must be sorted alphabetically
215 // Upper case comes before lower case
216 PngMap sorted_png_map[] = {
217 { "Bumpeq", "bumpeq2" },
220 { "Delta", "delta2" },
221 { "Downarrow", "downarrow2" },
222 { "Gamma", "gamma2" },
223 { "Lambda", "lambda2" },
224 { "Leftarrow", "leftarrow2" },
225 { "Leftrightarrow", "leftrightarrow2" },
226 { "Longleftarrow", "longleftarrow2" },
227 { "Longleftrightarrow", "longleftrightarrow2" },
228 { "Longrightarrow", "longrightarrow2" },
229 { "Omega", "omega2" },
233 { "Rightarrow", "rightarrow2" },
234 { "Sigma", "sigma2" },
235 { "Subset", "subset2" },
236 { "Supset", "supset2" },
237 { "Theta", "theta2" },
238 { "Uparrow", "uparrow2" },
239 { "Updownarrow", "updownarrow2" },
240 { "Upsilon", "upsilon2" },
241 { "Vdash", "vdash3" },
244 { "nLeftarrow", "nleftarrow2" },
245 { "nLeftrightarrow", "nleftrightarrow2" },
246 { "nRightarrow", "nrightarrow2" },
247 { "nVDash", "nvdash3" },
248 { "nvDash", "nvdash2" },
249 { "textrm \\AA", "textrm_AA"},
250 { "textrm \\O", "textrm_O"},
251 { "vDash", "vdash2" }
254 size_t const nr_sorted_png_map = sizeof(sorted_png_map) / sizeof(PngMap);
257 QString findPng(QString const & name)
259 PngMap const * const begin = sorted_png_map;
260 PngMap const * const end = begin + nr_sorted_png_map;
261 LASSERT(sorted(begin, end), /**/);
263 PngMap const * const it = find_if(begin, end, CompareKey(name));
267 png_name = it->value;
270 png_name.replace('_', "underscore");
271 png_name.replace(' ', '_');
273 // This way we can have "math-delim { }" on the toolbar.
274 png_name.replace('(', "lparen");
275 png_name.replace(')', "rparen");
276 png_name.replace('[', "lbracket");
277 png_name.replace(']', "rbracket");
278 png_name.replace('{', "lbrace");
279 png_name.replace('}', "rbrace");
280 png_name.replace('|', "bars");
281 png_name.replace(',', "thinspace");
282 png_name.replace(':', "mediumspace");
283 png_name.replace(';', "thickspace");
284 png_name.replace('!', "negthinspace");
287 LYXERR(Debug::GUI, "findPng(" << name << ")\n"
288 << "Looking for math PNG called \"" << png_name << '"');
295 QString iconName(FuncRequest const & f, bool unknown)
297 initializeResources();
302 case LFUN_MATH_INSERT:
303 if (!f.argument().empty()) {
305 name1 = findPng(toqstr(f.argument()).mid(1));
308 case LFUN_MATH_DELIM:
309 case LFUN_MATH_BIGDELIM:
311 name1 = findPng(toqstr(f.argument()));
315 name1 = toqstr(f.argument());
317 case LFUN_COMMAND_ALTERNATIVES: {
318 // use the first of the alternative commands
320 docstring dummy = split(f.argument(), firstcom, ';');
321 name1 = toqstr(firstcom);
322 name1.replace(' ', '_');
326 name2 = toqstr(lyxaction.getActionName(f.action));
329 if (!f.argument().empty()) {
330 name1 = name2 + ' ' + toqstr(f.argument());
331 name1.replace(' ', '_');
332 name1.replace('\\', "backslash");
336 FileName fname = libFileSearch("images/" + path, name1, "png");
338 return toqstr(fname.absFilename());
340 fname = libFileSearch("images/" + path, name2, "png");
342 return toqstr(fname.absFilename());
344 path = ":/images/" + path;
347 LYXERR0("Directory " << path << " not found in resource!");
351 if (res.exists(name1))
355 if (res.exists(name2))
358 LYXERR(Debug::GUI, "Cannot find icon with filename "
359 << "\"" << name1 << "\""
361 << "\"" << name2 << "\""
363 << lyxaction.getActionName(f.action)
364 << '(' << to_utf8(f.argument()) << ")\"");
367 fname = libFileSearch(QString("images/"), "unknown", "png");
369 return toqstr(fname.absFilename());
370 return QString(":/images/unknown.png");
376 QPixmap getPixmap(QString const & path, QString const & name, QString const & ext)
379 FileName fname = libFileSearch(path, name, ext);
380 QString path1 = toqstr(fname.absFilename());
381 QString path2 = ":/" + path + name + "." + ext;
383 if (pixmap.load(path1)) {
386 else if (pixmap.load(path2)) {
390 LYXERR0("Cannot load pixmap \""
391 << path << name << '.' << ext
392 << "\", please verify resource system!");
397 QIcon getIcon(FuncRequest const & f, bool unknown)
399 QString icon = iconName(f, unknown);
403 //LYXERR(Debug::GUI, "Found icon: " << icon);
405 if (!pm.load(icon)) {
406 LYXERR0("Cannot load icon " << icon << " please verify resource system!");
414 ////////////////////////////////////////////////////////////////////////
416 // LyX server support code.
418 ////////////////////////////////////////////////////////////////////////
420 class SocketNotifier : public QSocketNotifier
423 /// connect a connection notification from the LyXServerSocket
424 SocketNotifier(QObject * parent, int fd, Application::SocketCallback func)
425 : QSocketNotifier(fd, QSocketNotifier::Read, parent), func_(func)
429 /// The callback function
430 Application::SocketCallback func_;
434 ////////////////////////////////////////////////////////////////////////
436 // Mac specific stuff goes here...
438 ////////////////////////////////////////////////////////////////////////
440 class MenuTranslator : public QTranslator
443 MenuTranslator(QObject * parent)
444 : QTranslator(parent)
447 QString translate(const char * /*context*/,
448 const char * sourceText,
449 const char * /*comment*/ = 0)
451 string const s = sourceText;
452 if (s == N_("About %1") || s == N_("Preferences")
453 || s == N_("Reconfigure") || s == N_("Quit %1"))
460 class GlobalMenuBar : public QMenuBar
464 GlobalMenuBar() : QMenuBar(0) {}
467 bool event(QEvent * e)
469 if (e->type() == QEvent::ShortcutOverride) {
470 // && activeWindow() == 0) {
471 QKeyEvent * ke = static_cast<QKeyEvent*>(e);
473 setKeySymbol(&sym, ke);
474 theLyXFunc().processKeySym(sym, q_key_state(ke->modifiers()));
483 // QMacPasteboardMimeGraphics can only be compiled on Mac.
485 class QMacPasteboardMimeGraphics : public QMacPasteboardMime
488 QMacPasteboardMimeGraphics()
489 : QMacPasteboardMime(MIME_QT_CONVERTOR|MIME_ALL)
492 QString convertorName() { return "Graphics"; }
494 QString flavorFor(QString const & mime)
496 LYXERR(Debug::ACTION, "flavorFor " << mime);
497 if (mime == pdfMimeType())
498 return QLatin1String("com.adobe.pdf");
502 QString mimeFor(QString flav)
504 LYXERR(Debug::ACTION, "mimeFor " << flav);
505 if (flav == QLatin1String("com.adobe.pdf"))
506 return pdfMimeType();
510 bool canConvert(QString const & mime, QString flav)
512 return mimeFor(flav) == mime;
515 QVariant convertToMime(QString const & /*mime*/, QList<QByteArray> data,
519 qWarning("QMacPasteboardMimeGraphics: Cannot handle multiple member data");
523 QList<QByteArray> convertFromMime(QString const & /*mime*/,
524 QVariant data, QString /*flav*/)
526 QList<QByteArray> ret;
527 ret.append(data.toByteArray());
533 ///////////////////////////////////////////////////////////////
535 // You can find more platform specific stuff at the end of this file...
537 ///////////////////////////////////////////////////////////////
539 ////////////////////////////////////////////////////////////////////////
540 // Windows specific stuff goes here...
543 // QWindowsMimeMetafile can only be compiled on Windows.
545 static FORMATETC cfFromMime(QString const & mimetype)
548 if (mimetype == emfMimeType()) {
549 formatetc.cfFormat = CF_ENHMETAFILE;
550 formatetc.tymed = TYMED_ENHMF;
551 } else if (mimetype == wmfMimeType()) {
552 formatetc.cfFormat = CF_METAFILEPICT;
553 formatetc.tymed = TYMED_MFPICT;
556 formatetc.dwAspect = DVASPECT_CONTENT;
557 formatetc.lindex = -1;
562 class QWindowsMimeMetafile : public QWindowsMime {
564 QWindowsMimeMetafile() {}
566 bool canConvertFromMime(FORMATETC const & formatetc,
567 QMimeData const * mimedata) const
572 bool canConvertToMime(QString const & mimetype,
573 IDataObject * pDataObj) const
575 if (mimetype != emfMimeType() && mimetype != wmfMimeType())
577 FORMATETC formatetc = cfFromMime(mimetype);
578 return pDataObj->QueryGetData(&formatetc) == S_OK;
581 bool convertFromMime(FORMATETC const & formatetc,
582 const QMimeData * mimedata, STGMEDIUM * pmedium) const
587 QVariant convertToMime(QString const & mimetype, IDataObject * pDataObj,
588 QVariant::Type preferredType) const
591 if (!canConvertToMime(mimetype, pDataObj))
594 FORMATETC formatetc = cfFromMime(mimetype);
596 if (pDataObj->GetData(&formatetc, &s) != S_OK)
600 if (s.tymed == TYMED_ENHMF) {
601 dataSize = GetEnhMetaFileBits(s.hEnhMetaFile, 0, 0);
602 data.resize(dataSize);
603 dataSize = GetEnhMetaFileBits(s.hEnhMetaFile, dataSize,
604 (LPBYTE)data.data());
605 } else if (s.tymed == TYMED_MFPICT) {
606 dataSize = GetMetaFileBitsEx((HMETAFILE)s.hMetaFilePict, 0, 0);
607 data.resize(dataSize);
608 dataSize = GetMetaFileBitsEx((HMETAFILE)s.hMetaFilePict, dataSize,
609 (LPBYTE)data.data());
612 ReleaseStgMedium(&s);
618 QVector<FORMATETC> formatsForMime(QString const & mimetype,
619 QMimeData const * mimedata) const
621 QVector<FORMATETC> formats;
622 if (mimetype == emfMimeType() || mimetype == wmfMimeType())
623 formats += cfFromMime(mimetype);
627 QString mimeForFormat(FORMATETC const & formatetc) const
629 switch (formatetc.cfFormat) {
631 return emfMimeType();
632 case CF_METAFILEPICT:
633 return wmfMimeType();
641 ////////////////////////////////////////////////////////////////////////
642 // GuiApplication::Private definition and implementation.
643 ////////////////////////////////////////////////////////////////////////
645 struct GuiApplication::Private
647 Private(): language_model_(0), global_menubar_(0)
650 /// WMF Mime handler for Windows clipboard.
651 wmf_mime_ = new QWindowsMimeMetafile();
656 QSortFilterProxyModel * language_model_;
658 GuiClipboard clipboard_;
660 GuiSelection selection_;
662 FontLoader font_loader_;
664 ColorCache color_cache_;
666 QTranslator qt_trans_;
668 QHash<int, SocketNotifier *> socket_notifiers_;
672 /// The global instance
675 /// this timer is used for any regular events one wants to
676 /// perform. at present it is used to check if forked processes
678 QTimer general_timer_;
680 /// delayed FuncRequests
681 std::queue<FuncRequest> func_request_queue_;
683 /// Multiple views container.
685 * Warning: This must not be a smart pointer as the destruction of the
686 * object is handled by Qt when the view is closed
687 * \sa Qt::WA_DeleteOnClose attribute.
689 QHash<int, GuiView *> views_;
691 /// Only used on mac.
692 GlobalMenuBar * global_menubar_;
695 /// Linkback mime handler for MacOSX.
696 QMacPasteboardMimeGraphics mac_pasteboard_mime_;
700 /// WMF Mime handler for Windows clipboard.
701 QWindowsMimeMetafile * wmf_mime_;
706 GuiApplication * guiApp;
708 GuiApplication::~GuiApplication()
711 closeAllLinkBackLinks();
717 GuiApplication::GuiApplication(int & argc, char ** argv)
718 : QApplication(argc, argv), current_view_(0),
719 d(new GuiApplication::Private)
721 QString app_name = "LyX";
722 QCoreApplication::setOrganizationName(app_name);
723 QCoreApplication::setOrganizationDomain("lyx.org");
724 QCoreApplication::setApplicationName(lyx_package);
726 // Install translator for GUI elements.
727 installTranslator(&d->qt_trans_);
729 // FIXME: quitOnLastWindowClosed is true by default. We should have a
730 // lyxrc setting for this in order to let the application stay resident.
731 // But then we need some kind of dock icon, at least on Windows.
733 if (lyxrc.quit_on_last_window_closed)
734 setQuitOnLastWindowClosed(false);
737 // FIXME: Do we need a lyxrc setting for this on Mac? This behaviour
738 // seems to be the default case for applications like LyX.
739 setQuitOnLastWindowClosed(false);
741 // This allows to translate the strings that appear in the LyX menu.
742 /// A translator suitable for the entries in the LyX menu.
743 /// Only needed with Qt/Mac.
744 installTranslator(new MenuTranslator(this));
748 // doubleClickInterval() is 400 ms on X11 which is just too long.
749 // On Windows and Mac OS X, the operating system's value is used.
750 // On Microsoft Windows, calling this function sets the double
751 // click interval for all applications. So we don't!
752 QApplication::setDoubleClickInterval(300);
755 connect(this, SIGNAL(lastWindowClosed()), this, SLOT(onLastWindowClosed()));
757 // needs to be done before reading lyxrc
759 lyxrc.dpi = (w.logicalDpiX() + w.logicalDpiY()) / 2;
763 // Set the cache to 5120 kilobytes which corresponds to screen size of
764 // 1280 by 1024 pixels with a color depth of 32 bits.
765 QPixmapCache::setCacheLimit(5120);
767 // Initialize RC Fonts
768 if (lyxrc.roman_font_name.empty())
769 lyxrc.roman_font_name = fromqstr(romanFontName());
771 if (lyxrc.sans_font_name.empty())
772 lyxrc.sans_font_name = fromqstr(sansFontName());
774 if (lyxrc.typewriter_font_name.empty())
775 lyxrc.typewriter_font_name = fromqstr(typewriterFontName());
777 d->general_timer_.setInterval(500);
778 connect(&d->general_timer_, SIGNAL(timeout()),
779 this, SLOT(handleRegularEvents()));
780 d->general_timer_.start();
784 GuiApplication * theGuiApp()
786 return dynamic_cast<GuiApplication *>(theApp());
790 void GuiApplication::clearSession()
797 docstring GuiApplication::iconName(FuncRequest const & f, bool unknown)
799 return qstring_to_ucs4(lyx::frontend::iconName(f, unknown));
803 LyXView * GuiApplication::currentWindow()
806 /* In LyX/Mac, when a dialog is open, the menus of the
807 application can still be accessed without giving focus to
808 the main window. In this case, we want to disable the menu
809 entries that are buffer or view-related.
811 if (current_view_ && activeWindow() != current_view_)
814 return current_view_;
818 bool GuiApplication::getStatus(FuncRequest const & cmd, FuncStatus & flag) const
824 case LFUN_WINDOW_CLOSE:
825 enable = d->views_.size() > 0;
828 case LFUN_BUFFER_NEW:
829 case LFUN_BUFFER_NEW_TEMPLATE:
832 case LFUN_SCREEN_FONT_UPDATE:
834 case LFUN_WINDOW_NEW:
844 flag.setEnabled(false);
850 bool GuiApplication::dispatch(FuncRequest const & cmd)
852 switch (cmd.action) {
854 case LFUN_WINDOW_NEW:
855 createView(toqstr(cmd.argument()));
858 case LFUN_WINDOW_CLOSE:
859 // update bookmark pit of the current buffer before window close
860 for (size_t i = 0; i < theSession().bookmarks().size(); ++i)
861 theLyXFunc().gotoBookmark(i+1, false, false);
862 // clear the last opened list, because
863 // maybe this will end the session
864 theSession().lastOpened().clear();
865 current_view_->close();
869 // quitting is triggered by the gui code
870 // (leaving the event loop).
872 current_view_->message(from_utf8(N_("Exiting.")));
877 case LFUN_SCREEN_FONT_UPDATE: {
878 // handle the screen font changes.
879 d->font_loader_.update();
880 // Backup current_view_
881 GuiView * view = current_view_;
882 // Set current_view_ to zero to forbid GuiWorkArea::redraw()
883 // to skip the refresh.
885 BufferList::iterator it = theBufferList().begin();
886 BufferList::iterator const end = theBufferList().end();
887 for (; it != end; ++it)
889 // Restore current_view_
890 current_view_ = view;
894 case LFUN_BUFFER_NEW:
895 if (d->views_.empty()
896 || (!lyxrc.open_buffers_in_tabs && current_view_->documentBufferView() != 0)) {
897 createView(QString(), false); // keep hidden
898 current_view_->newDocument(to_utf8(cmd.argument()), false);
899 current_view_->show();
900 setActiveWindow(current_view_);
902 current_view_->newDocument(to_utf8(cmd.argument()), false);
906 case LFUN_BUFFER_NEW_TEMPLATE:
907 if (d->views_.empty()
908 || (!lyxrc.open_buffers_in_tabs && current_view_->documentBufferView() != 0)) {
910 current_view_->newDocument(to_utf8(cmd.argument()), true);
911 if (!current_view_->documentBufferView())
912 current_view_->close();
914 current_view_->newDocument(to_utf8(cmd.argument()), true);
919 // FIXME: create a new method shared with LFUN_HELP_OPEN.
920 if (d->views_.empty()
921 || (!lyxrc.open_buffers_in_tabs && current_view_->documentBufferView() != 0)) {
922 string const fname = to_utf8(cmd.argument());
923 // We want the ui session to be saved per document and not per
924 // window number. The filename crc is a good enough identifier.
925 boost::crc_32_type crc;
926 crc = for_each(fname.begin(), fname.end(), crc);
927 createView(crc.checksum());
928 current_view_->openDocument(fname);
929 if (current_view_ && !current_view_->documentBufferView())
930 current_view_->close();
932 current_view_->openDocument(to_utf8(cmd.argument()));
935 case LFUN_HELP_OPEN: {
936 // FIXME: create a new method shared with LFUN_FILE_OPEN.
937 if (current_view_ == 0)
939 string const arg = to_utf8(cmd.argument());
941 current_view_->message(_("Missing argument"));
944 FileName fname = i18nLibFileSearch("doc", arg, "lyx");
946 fname = i18nLibFileSearch("examples", arg, "lyx");
949 lyxerr << "LyX: unable to find documentation file `"
950 << arg << "'. Bad installation?" << endl;
953 current_view_->message(bformat(_("Opening help file %1$s..."),
954 makeDisplayPath(fname.absFilename())));
955 Buffer * buf = current_view_->loadDocument(fname, false);
957 current_view_->setBuffer(buf);
959 buf->errors("Parse");
964 case LFUN_SET_COLOR: {
966 string const x11_name = split(to_utf8(cmd.argument()), lyx_name, ' ');
967 if (lyx_name.empty() || x11_name.empty()) {
968 current_view_->message(
969 _("Syntax: set-color <lyx_name> <x11_name>"));
973 string const graphicsbg = lcolor.getLyXName(Color_graphicsbg);
974 bool const graphicsbg_changed = lyx_name == graphicsbg
975 && x11_name != graphicsbg;
976 if (graphicsbg_changed) {
977 // FIXME: The graphics cache no longer has a changeDisplay method.
979 graphics::GCache::get().changeDisplay(true);
983 if (!lcolor.setColor(lyx_name, x11_name)) {
984 current_view_->message(
985 bformat(_("Set-color \"%1$s\" failed "
986 "- color is undefined or "
987 "may not be redefined"),
988 from_utf8(lyx_name)));
991 // Make sure we don't keep old colors in cache.
992 d->color_cache_.clear();
997 // Notify the caller that the action has not been dispatched.
1001 // The action has been dispatched.
1006 void GuiApplication::dispatchDelayed(FuncRequest const & func)
1008 d->func_request_queue_.push(func);
1009 QTimer::singleShot(0, this, SLOT(processFuncRequestQueue()));
1013 void GuiApplication::resetGui()
1015 // Set the language defined by the user.
1019 if (!readUIFile(toqstr(lyxrc.ui_file)))
1020 // Gives some error box here.
1023 if (d->global_menubar_)
1024 d->menus_.fillMenuBar(d->global_menubar_, 0, false);
1026 QHash<int, GuiView *>::iterator it;
1027 for (it = d->views_.begin(); it != d->views_.end(); ++it) {
1030 gv->setLayoutDirection(layoutDirection());
1034 dispatch(FuncRequest(LFUN_SCREEN_FONT_UPDATE));
1038 void GuiApplication::createView(int view_id)
1040 createView(QString(), true, view_id);
1044 void GuiApplication::createView(QString const & geometry_arg, bool autoShow,
1047 // release the keyboard which might have been grabed by the global
1048 // menubar on Mac to catch shortcuts even without any GuiView.
1049 if (d->global_menubar_)
1050 d->global_menubar_->releaseKeyboard();
1054 while (d->views_.find(id) != d->views_.end())
1057 LYXERR(Debug::GUI, "About to create new window with ID " << id);
1058 GuiView * view = new GuiView(id);
1060 d->views_[id] = view;
1064 setActiveWindow(view);
1067 if (!geometry_arg.isEmpty()) {
1071 QRegExp re( "[=]*(?:([0-9]+)[xX]([0-9]+)){0,1}[ ]*(?:([+-][0-9]*)([+-][0-9]*)){0,1}" );
1072 re.indexIn(geometry_arg);
1073 w = re.cap(1).toInt();
1074 h = re.cap(2).toInt();
1075 x = re.cap(3).toInt();
1076 y = re.cap(4).toInt();
1077 view->setGeometry(x, y, w, h);
1084 Clipboard & GuiApplication::clipboard()
1086 return d->clipboard_;
1090 Selection & GuiApplication::selection()
1092 return d->selection_;
1096 FontLoader & GuiApplication::fontLoader()
1098 return d->font_loader_;
1102 Toolbars const & GuiApplication::toolbars() const
1104 return d->toolbars_;
1108 Toolbars & GuiApplication::toolbars()
1110 return d->toolbars_;
1114 Menus const & GuiApplication::menus() const
1120 Menus & GuiApplication::menus()
1126 QList<int> GuiApplication::viewIds() const
1128 return d->views_.keys();
1132 ColorCache & GuiApplication::colorCache()
1134 return d->color_cache_;
1138 int GuiApplication::exec()
1140 // asynchronously handle batch commands. This event will be in
1141 // the event queue in front of other asynchronous events. Hence,
1142 // we can assume in the latter that the gui is setup already.
1143 QTimer::singleShot(0, this, SLOT(execBatchCommands()));
1145 return QApplication::exec();
1149 void GuiApplication::exit(int status)
1151 QApplication::exit(status);
1155 void GuiApplication::setGuiLanguage()
1157 // Set the language defined by the user.
1160 QString const default_language = toqstr(Messages::defaultLanguage());
1161 LYXERR(Debug::LOCALE, "Tring to set default locale to: " << default_language);
1162 QLocale const default_locale(default_language);
1163 QLocale::setDefault(default_locale);
1165 // install translation file for Qt built-in dialogs
1166 QString const language_name = QString("qt_") + default_locale.name();
1168 // language_name can be short (e.g. qt_zh) or long (e.g. qt_zh_CN).
1169 // Short-named translator can be loaded from a long name, but not the
1170 // opposite. Therefore, long name should be used without truncation.
1171 // c.f. http://doc.trolltech.com/4.1/qtranslator.html#load
1172 if (!d->qt_trans_.load(language_name,
1173 QLibraryInfo::location(QLibraryInfo::TranslationsPath))) {
1174 LYXERR(Debug::LOCALE, "Could not find Qt translations for locale "
1177 LYXERR(Debug::LOCALE, "Successfully installed Qt translations for locale "
1181 switch (default_locale.language()) {
1182 case QLocale::Arabic :
1183 case QLocale::Hebrew :
1184 case QLocale::Persian :
1185 case QLocale::Urdu :
1186 setLayoutDirection(Qt::RightToLeft);
1189 setLayoutDirection(Qt::LeftToRight);
1194 void GuiApplication::processFuncRequestQueue()
1196 while (!d->func_request_queue_.empty()) {
1197 lyx::dispatch(d->func_request_queue_.back());
1198 d->func_request_queue_.pop();
1203 void GuiApplication::execBatchCommands()
1208 if (!readUIFile(toqstr(lyxrc.ui_file)))
1209 // Gives some error box here.
1213 // Create the global default menubar which is shown for the dialogs
1214 // and if no GuiView is visible.
1215 // This must be done after the session was recovered to know the "last files".
1216 d->global_menubar_ = new GlobalMenuBar();
1217 d->menus_.fillMenuBar(d->global_menubar_, 0, true);
1220 lyx::execBatchCommands();
1224 QAbstractItemModel * GuiApplication::languageModel()
1226 if (d->language_model_)
1227 return d->language_model_;
1229 QStandardItemModel * lang_model = new QStandardItemModel(this);
1230 lang_model->insertColumns(0, 1);
1232 Languages::const_iterator it = languages.begin();
1233 Languages::const_iterator end = languages.end();
1234 for (; it != end; ++it) {
1235 current_row = lang_model->rowCount();
1236 lang_model->insertRows(current_row, 1);
1237 QModelIndex item = lang_model->index(current_row, 0);
1238 lang_model->setData(item, qt_(it->second.display()), Qt::DisplayRole);
1239 lang_model->setData(item, toqstr(it->second.lang()), Qt::UserRole);
1241 d->language_model_ = new QSortFilterProxyModel(this);
1242 d->language_model_->setSourceModel(lang_model);
1243 #if QT_VERSION >= 0x040300
1244 d->language_model_->setSortLocaleAware(true);
1246 return d->language_model_;
1250 void GuiApplication::restoreGuiSession()
1252 if (!lyxrc.load_session)
1255 Session & session = theSession();
1256 LastOpenedSection::LastOpened const & lastopened =
1257 session.lastOpened().getfiles();
1259 FileName active_file;
1260 // do not add to the lastfile list since these files are restored from
1261 // last session, and should be already there (regular files), or should
1262 // not be added at all (help files).
1263 for (size_t i = 0; i < lastopened.size(); ++i) {
1264 FileName const & file_name = lastopened[i].file_name;
1265 if (d->views_.empty() || (!lyxrc.open_buffers_in_tabs
1266 && current_view_->documentBufferView() != 0)) {
1267 boost::crc_32_type crc;
1268 string const & fname = file_name.absFilename();
1269 crc = for_each(fname.begin(), fname.end(), crc);
1270 createView(crc.checksum());
1272 current_view_->loadDocument(file_name, false);
1274 if (lastopened[i].active)
1275 active_file = file_name;
1278 // Restore last active buffer
1279 Buffer * buffer = theBufferList().getBuffer(active_file);
1281 current_view_->setBuffer(buffer);
1283 // clear this list to save a few bytes of RAM
1284 session.lastOpened().clear();
1288 QString const GuiApplication::romanFontName()
1291 font.setKerning(false);
1292 font.setStyleHint(QFont::Serif);
1293 font.setFamily("serif");
1295 return QFontInfo(font).family();
1299 QString const GuiApplication::sansFontName()
1302 font.setKerning(false);
1303 font.setStyleHint(QFont::SansSerif);
1304 font.setFamily("sans");
1306 return QFontInfo(font).family();
1310 QString const GuiApplication::typewriterFontName()
1313 font.setKerning(false);
1314 font.setStyleHint(QFont::TypeWriter);
1315 font.setFamily("monospace");
1317 return QFontInfo(font).family();
1321 void GuiApplication::handleRegularEvents()
1323 ForkedCallsController::handleCompletedProcesses();
1327 bool GuiApplication::event(QEvent * e)
1330 case QEvent::FileOpen: {
1331 // Open a file; this happens only on Mac OS X for now.
1333 // We do this asynchronously because on startup the batch
1334 // commands are not executed here yet and the gui is not ready
1336 QFileOpenEvent * foe = static_cast<QFileOpenEvent *>(e);
1337 dispatchDelayed(FuncRequest(LFUN_FILE_OPEN, qstring_to_ucs4(foe->file())));
1342 return QApplication::event(e);
1347 bool GuiApplication::notify(QObject * receiver, QEvent * event)
1350 return QApplication::notify(receiver, event);
1352 catch (ExceptionMessage const & e) {
1354 case ErrorException:
1356 setQuitOnLastWindowClosed(false);
1358 Alert::error(e.title_, e.details_);
1360 // Properly crash in debug mode in order to get a useful backtrace.
1363 // In release mode, try to exit gracefully.
1366 case BufferException: {
1367 if (!current_view_->documentBufferView())
1369 Buffer * buf = ¤t_view_->documentBufferView()->buffer();
1370 docstring details = e.details_ + '\n';
1371 details += buf->emergencyWrite();
1372 theBufferList().release(buf);
1373 details += "\n" + _("The current document was closed.");
1374 Alert::error(e.title_, details);
1377 case WarningException:
1378 Alert::warning(e.title_, e.details_);
1382 catch (exception const & e) {
1383 docstring s = _("LyX has caught an exception, it will now "
1384 "attempt to save all unsaved documents and exit."
1386 s += from_ascii(e.what());
1387 Alert::error(_("Software exception Detected"), s);
1391 docstring s = _("LyX has caught some really weird exception, it will "
1392 "now attempt to save all unsaved documents and exit.");
1393 Alert::error(_("Software exception Detected"), s);
1401 bool GuiApplication::getRgbColor(ColorCode col, RGBColor & rgbcol)
1403 QColor const & qcol = d->color_cache_.get(col);
1404 if (!qcol.isValid()) {
1410 rgbcol.r = qcol.red();
1411 rgbcol.g = qcol.green();
1412 rgbcol.b = qcol.blue();
1417 string const GuiApplication::hexName(ColorCode col)
1419 return ltrim(fromqstr(d->color_cache_.get(col).name()), "#");
1423 void GuiApplication::registerSocketCallback(int fd, SocketCallback func)
1425 SocketNotifier * sn = new SocketNotifier(this, fd, func);
1426 d->socket_notifiers_[fd] = sn;
1427 connect(sn, SIGNAL(activated(int)), this, SLOT(socketDataReceived(int)));
1431 void GuiApplication::socketDataReceived(int fd)
1433 d->socket_notifiers_[fd]->func_();
1437 void GuiApplication::unregisterSocketCallback(int fd)
1439 d->socket_notifiers_.take(fd)->setEnabled(false);
1443 void GuiApplication::commitData(QSessionManager & sm)
1445 /// The implementation is required to avoid an application exit
1446 /// when session state save is triggered by session manager.
1447 /// The default implementation sends a close event to all
1448 /// visible top level widgets when session managment allows
1450 /// We are changing that to close all wiew one by one.
1451 /// FIXME: verify if the default implementation is enough now.
1452 if (sm.allowsInteraction() && !closeAllViews())
1457 void GuiApplication::unregisterView(GuiView * gv)
1459 LASSERT(d->views_[gv->id()] == gv, /**/);
1460 d->views_.remove(gv->id());
1461 if (current_view_ == gv)
1466 bool GuiApplication::closeAllViews()
1468 if (d->views_.empty())
1471 // When a view/window was closed before without quitting LyX, there
1472 // are already entries in the lastOpened list.
1473 theSession().lastOpened().clear();
1475 QList<GuiView *> views = d->views_.values();
1476 foreach (GuiView * view, views) {
1486 GuiView & GuiApplication::view(int id) const
1488 LASSERT(d->views_.contains(id), /**/);
1489 return *d->views_.value(id);
1493 void GuiApplication::hideDialogs(string const & name, Inset * inset) const
1495 QList<GuiView *> views = d->views_.values();
1496 foreach (GuiView * view, views)
1497 view->hideDialog(name, inset);
1501 Buffer const * GuiApplication::updateInset(Inset const * inset) const
1503 Buffer const * buffer_ = 0;
1504 QHash<int, GuiView *>::iterator end = d->views_.end();
1505 for (QHash<int, GuiView *>::iterator it = d->views_.begin(); it != end; ++it) {
1506 if (Buffer const * ptr = (*it)->updateInset(inset))
1513 bool GuiApplication::searchMenu(FuncRequest const & func,
1514 docstring_list & names) const
1516 return d->menus_.searchMenu(func, names);
1520 bool GuiApplication::readUIFile(QString const & name, bool include)
1522 LYXERR(Debug::INIT, "About to read " << name << "...");
1526 ui_path = libFileSearch("ui", name, "inc");
1527 if (ui_path.empty())
1528 ui_path = libFileSearch("ui", changeExtension(name, "inc"));
1530 ui_path = libFileSearch("ui", name, "ui");
1533 if (ui_path.empty()) {
1534 static const QString defaultUIFile = "default";
1535 LYXERR(Debug::INIT, "Could not find " << name);
1537 Alert::warning(_("Could not find UI definition file"),
1538 bformat(_("Error while reading the included file\n%1$s\n"
1539 "Please check your installation."), qstring_to_ucs4(name)));
1542 if (name == defaultUIFile) {
1543 LYXERR(Debug::INIT, "Could not find default UI file!!");
1544 Alert::warning(_("Could not find default UI file"),
1545 _("LyX could not find the default UI file!\n"
1546 "Please check your installation."));
1549 Alert::warning(_("Could not find UI definition file"),
1550 bformat(_("Error while reading the configuration file\n%1$s\n"
1551 "Falling back to default.\n"
1552 "Please look under Tools>Preferences>User Interface and\n"
1553 "check which User Interface file you are using."), qstring_to_ucs4(name)));
1554 return readUIFile(defaultUIFile, false);
1557 // Ensure that a file is read only once (prevents include loops)
1558 static QStringList uifiles;
1559 QString const uifile = toqstr(ui_path.absFilename());
1560 if (uifiles.contains(uifile)) {
1562 // We are reading again the top uifile so reset the safeguard:
1565 d->toolbars_.reset();
1567 LYXERR(Debug::INIT, "UI file '" << name << "' has been read already. "
1568 << "Is this an include loop?");
1572 uifiles.push_back(uifile);
1574 LYXERR(Debug::INIT, "Found " << name << " in " << ui_path);
1584 LexerKeyword uitags[] = {
1585 { "include", ui_include },
1586 { "menuset", ui_menuset },
1587 { "toolbars", ui_toolbars },
1588 { "toolbarset", ui_toolbarset }
1592 lex.setFile(ui_path);
1594 lyxerr << "Unable to set LyXLeX for ui file: " << ui_path
1598 if (lyxerr.debugging(Debug::PARSER))
1599 lex.printTable(lyxerr);
1601 // store which ui files define Toolbars
1602 static QStringList toolbar_uifiles;
1604 while (lex.isOK()) {
1605 switch (lex.lex()) {
1608 QString const file = toqstr(lex.getString());
1609 if (!readUIFile(file, true))
1614 d->menus_.read(lex);
1618 d->toolbars_.readToolbars(lex);
1622 d->toolbars_.readToolbarSettings(lex);
1623 toolbar_uifiles.push_back(uifile);
1627 if (!rtrim(lex.getString()).empty())
1628 lex.printError("LyX::ReadUIFile: "
1629 "Unknown menu tag: `$$Token'");
1638 settings.beginGroup("ui_files");
1639 bool touched = false;
1640 for (int i = 0; i != uifiles.size(); ++i) {
1641 QFileInfo fi(uifiles[i]);
1642 QDateTime const date_value = fi.lastModified();
1643 QString const name_key = QString::number(i);
1644 // if an ui file which defines Toolbars has changed,
1645 // we have to reset the settings
1646 if (toolbar_uifiles.contains(uifiles[i])
1647 && (!settings.contains(name_key)
1648 || settings.value(name_key).toString() != uifiles[i]
1649 || settings.value(name_key + "/date").toDateTime() != date_value)) {
1651 settings.setValue(name_key, uifiles[i]);
1652 settings.setValue(name_key + "/date", date_value);
1655 settings.endGroup();
1657 settings.remove("views");
1663 void GuiApplication::onLastWindowClosed()
1665 if (d->global_menubar_)
1666 d->global_menubar_->grabKeyboard();
1670 ////////////////////////////////////////////////////////////////////////
1672 // X11 specific stuff goes here...
1675 bool GuiApplication::x11EventFilter(XEvent * xev)
1680 switch (xev->type) {
1681 case SelectionRequest: {
1682 if (xev->xselectionrequest.selection != XA_PRIMARY)
1684 LYXERR(Debug::SELECTION, "X requested selection.");
1685 BufferView * bv = current_view_->currentBufferView();
1687 docstring const sel = bv->requestSelection();
1689 d->selection_.put(sel);
1693 case SelectionClear: {
1694 if (xev->xselectionclear.selection != XA_PRIMARY)
1696 LYXERR(Debug::SELECTION, "Lost selection.");
1697 BufferView * bv = current_view_->currentBufferView();
1699 bv->clearSelection();
1707 } // namespace frontend
1710 void hideDialogs(std::string const & name, Inset * inset)
1713 theApp()->hideDialogs(name, inset);
1717 ////////////////////////////////////////////////////////////////////
1721 ////////////////////////////////////////////////////////////////////
1723 frontend::FontLoader & theFontLoader()
1725 LASSERT(frontend::guiApp, /**/);
1726 return frontend::guiApp->fontLoader();
1730 frontend::FontMetrics const & theFontMetrics(Font const & f)
1732 return theFontMetrics(f.fontInfo());
1736 frontend::FontMetrics const & theFontMetrics(FontInfo const & f)
1738 LASSERT(frontend::guiApp, /**/);
1739 return frontend::guiApp->fontLoader().metrics(f);
1743 ////////////////////////////////////////////////////////////////////
1747 ////////////////////////////////////////////////////////////////////
1749 frontend::Clipboard & theClipboard()
1751 LASSERT(frontend::guiApp, /**/);
1752 return frontend::guiApp->clipboard();
1756 frontend::Selection & theSelection()
1758 LASSERT(frontend::guiApp, /**/);
1759 return frontend::guiApp->selection();
1765 #include "moc_GuiApplication.cpp"