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_ && !current_view_->hasFocus())
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) {
1029 gv->setLayoutDirection(layoutDirection());
1033 dispatch(FuncRequest(LFUN_SCREEN_FONT_UPDATE));
1037 void GuiApplication::createView(int view_id)
1039 createView(QString(), true, view_id);
1043 void GuiApplication::createView(QString const & geometry_arg, bool autoShow,
1046 // release the keyboard which might have been grabed by the global
1047 // menubar on Mac to catch shortcuts even without any GuiView.
1048 if (d->global_menubar_)
1049 d->global_menubar_->releaseKeyboard();
1053 while (d->views_.find(id) != d->views_.end())
1056 LYXERR(Debug::GUI, "About to create new window with ID " << id);
1057 GuiView * view = new GuiView(id);
1059 d->views_[id] = view;
1063 setActiveWindow(view);
1066 if (!geometry_arg.isEmpty()) {
1070 QRegExp re( "[=]*(?:([0-9]+)[xX]([0-9]+)){0,1}[ ]*(?:([+-][0-9]*)([+-][0-9]*)){0,1}" );
1071 re.indexIn(geometry_arg);
1072 w = re.cap(1).toInt();
1073 h = re.cap(2).toInt();
1074 x = re.cap(3).toInt();
1075 y = re.cap(4).toInt();
1076 view->setGeometry(x, y, w, h);
1083 Clipboard & GuiApplication::clipboard()
1085 return d->clipboard_;
1089 Selection & GuiApplication::selection()
1091 return d->selection_;
1095 FontLoader & GuiApplication::fontLoader()
1097 return d->font_loader_;
1101 Toolbars const & GuiApplication::toolbars() const
1103 return d->toolbars_;
1107 Toolbars & GuiApplication::toolbars()
1109 return d->toolbars_;
1113 Menus const & GuiApplication::menus() const
1119 Menus & GuiApplication::menus()
1125 QList<int> GuiApplication::viewIds() const
1127 return d->views_.keys();
1131 ColorCache & GuiApplication::colorCache()
1133 return d->color_cache_;
1137 int GuiApplication::exec()
1139 // asynchronously handle batch commands. This event will be in
1140 // the event queue in front of other asynchronous events. Hence,
1141 // we can assume in the latter that the gui is setup already.
1142 QTimer::singleShot(0, this, SLOT(execBatchCommands()));
1144 return QApplication::exec();
1148 void GuiApplication::exit(int status)
1150 QApplication::exit(status);
1154 void GuiApplication::setGuiLanguage()
1156 // Set the language defined by the user.
1159 QString const default_language = toqstr(Messages::defaultLanguage());
1160 LYXERR(Debug::LOCALE, "Tring to set default locale to: " << default_language);
1161 QLocale const default_locale(default_language);
1162 QLocale::setDefault(default_locale);
1164 // install translation file for Qt built-in dialogs
1165 QString const language_name = QString("qt_") + default_locale.name();
1167 // language_name can be short (e.g. qt_zh) or long (e.g. qt_zh_CN).
1168 // Short-named translator can be loaded from a long name, but not the
1169 // opposite. Therefore, long name should be used without truncation.
1170 // c.f. http://doc.trolltech.com/4.1/qtranslator.html#load
1171 if (!d->qt_trans_.load(language_name,
1172 QLibraryInfo::location(QLibraryInfo::TranslationsPath))) {
1173 LYXERR(Debug::LOCALE, "Could not find Qt translations for locale "
1176 LYXERR(Debug::LOCALE, "Successfully installed Qt translations for locale "
1180 switch (default_locale.language()) {
1181 case QLocale::Arabic :
1182 case QLocale::Hebrew :
1183 case QLocale::Persian :
1184 case QLocale::Urdu :
1185 setLayoutDirection(Qt::RightToLeft);
1188 setLayoutDirection(Qt::LeftToRight);
1193 void GuiApplication::processFuncRequestQueue()
1195 while (!d->func_request_queue_.empty()) {
1196 lyx::dispatch(d->func_request_queue_.back());
1197 d->func_request_queue_.pop();
1202 void GuiApplication::execBatchCommands()
1207 if (!readUIFile(toqstr(lyxrc.ui_file)))
1208 // Gives some error box here.
1212 // Create the global default menubar which is shown for the dialogs
1213 // and if no GuiView is visible.
1214 // This must be done after the session was recovered to know the "last files".
1215 d->global_menubar_ = new GlobalMenuBar();
1216 d->menus_.fillMenuBar(d->global_menubar_, 0, true);
1219 lyx::execBatchCommands();
1223 QAbstractItemModel * GuiApplication::languageModel()
1225 if (d->language_model_)
1226 return d->language_model_;
1228 QStandardItemModel * lang_model = new QStandardItemModel(this);
1229 lang_model->insertColumns(0, 1);
1231 Languages::const_iterator it = languages.begin();
1232 Languages::const_iterator end = languages.end();
1233 for (; it != end; ++it) {
1234 current_row = lang_model->rowCount();
1235 lang_model->insertRows(current_row, 1);
1236 QModelIndex item = lang_model->index(current_row, 0);
1237 lang_model->setData(item, qt_(it->second.display()), Qt::DisplayRole);
1238 lang_model->setData(item, toqstr(it->second.lang()), Qt::UserRole);
1240 d->language_model_ = new QSortFilterProxyModel(this);
1241 d->language_model_->setSourceModel(lang_model);
1242 #if QT_VERSION >= 0x040300
1243 d->language_model_->setSortLocaleAware(true);
1245 return d->language_model_;
1249 void GuiApplication::restoreGuiSession()
1251 if (!lyxrc.load_session)
1254 Session & session = theSession();
1255 LastOpenedSection::LastOpened const & lastopened =
1256 session.lastOpened().getfiles();
1258 FileName active_file;
1259 // do not add to the lastfile list since these files are restored from
1260 // last session, and should be already there (regular files), or should
1261 // not be added at all (help files).
1262 for (size_t i = 0; i < lastopened.size(); ++i) {
1263 FileName const & file_name = lastopened[i].file_name;
1264 if (d->views_.empty() || (!lyxrc.open_buffers_in_tabs
1265 && current_view_->documentBufferView() != 0)) {
1266 boost::crc_32_type crc;
1267 string const & fname = file_name.absFilename();
1268 crc = for_each(fname.begin(), fname.end(), crc);
1269 createView(crc.checksum());
1271 current_view_->loadDocument(file_name, false);
1273 if (lastopened[i].active)
1274 active_file = file_name;
1277 // Restore last active buffer
1278 Buffer * buffer = theBufferList().getBuffer(active_file);
1280 current_view_->setBuffer(buffer);
1282 // clear this list to save a few bytes of RAM
1283 session.lastOpened().clear();
1287 QString const GuiApplication::romanFontName()
1290 font.setKerning(false);
1291 font.setStyleHint(QFont::Serif);
1292 font.setFamily("serif");
1294 return QFontInfo(font).family();
1298 QString const GuiApplication::sansFontName()
1301 font.setKerning(false);
1302 font.setStyleHint(QFont::SansSerif);
1303 font.setFamily("sans");
1305 return QFontInfo(font).family();
1309 QString const GuiApplication::typewriterFontName()
1312 font.setKerning(false);
1313 font.setStyleHint(QFont::TypeWriter);
1314 font.setFamily("monospace");
1316 return QFontInfo(font).family();
1320 void GuiApplication::handleRegularEvents()
1322 ForkedCallsController::handleCompletedProcesses();
1326 bool GuiApplication::event(QEvent * e)
1329 case QEvent::FileOpen: {
1330 // Open a file; this happens only on Mac OS X for now.
1332 // We do this asynchronously because on startup the batch
1333 // commands are not executed here yet and the gui is not ready
1335 QFileOpenEvent * foe = static_cast<QFileOpenEvent *>(e);
1336 dispatchDelayed(FuncRequest(LFUN_FILE_OPEN, qstring_to_ucs4(foe->file())));
1341 return QApplication::event(e);
1346 bool GuiApplication::notify(QObject * receiver, QEvent * event)
1349 return QApplication::notify(receiver, event);
1351 catch (ExceptionMessage const & e) {
1353 case ErrorException:
1355 setQuitOnLastWindowClosed(false);
1357 Alert::error(e.title_, e.details_);
1359 // Properly crash in debug mode in order to get a useful backtrace.
1362 // In release mode, try to exit gracefully.
1365 case BufferException: {
1366 if (!current_view_->documentBufferView())
1368 Buffer * buf = ¤t_view_->documentBufferView()->buffer();
1369 docstring details = e.details_ + '\n';
1370 details += buf->emergencyWrite();
1371 theBufferList().release(buf);
1372 details += "\n" + _("The current document was closed.");
1373 Alert::error(e.title_, details);
1376 case WarningException:
1377 Alert::warning(e.title_, e.details_);
1381 catch (exception const & e) {
1382 docstring s = _("LyX has caught an exception, it will now "
1383 "attempt to save all unsaved documents and exit."
1385 s += from_ascii(e.what());
1386 Alert::error(_("Software exception Detected"), s);
1390 docstring s = _("LyX has caught some really weird exception, it will "
1391 "now attempt to save all unsaved documents and exit.");
1392 Alert::error(_("Software exception Detected"), s);
1400 bool GuiApplication::getRgbColor(ColorCode col, RGBColor & rgbcol)
1402 QColor const & qcol = d->color_cache_.get(col);
1403 if (!qcol.isValid()) {
1409 rgbcol.r = qcol.red();
1410 rgbcol.g = qcol.green();
1411 rgbcol.b = qcol.blue();
1416 string const GuiApplication::hexName(ColorCode col)
1418 return ltrim(fromqstr(d->color_cache_.get(col).name()), "#");
1422 void GuiApplication::registerSocketCallback(int fd, SocketCallback func)
1424 SocketNotifier * sn = new SocketNotifier(this, fd, func);
1425 d->socket_notifiers_[fd] = sn;
1426 connect(sn, SIGNAL(activated(int)), this, SLOT(socketDataReceived(int)));
1430 void GuiApplication::socketDataReceived(int fd)
1432 d->socket_notifiers_[fd]->func_();
1436 void GuiApplication::unregisterSocketCallback(int fd)
1438 d->socket_notifiers_.take(fd)->setEnabled(false);
1442 void GuiApplication::commitData(QSessionManager & sm)
1444 /// The implementation is required to avoid an application exit
1445 /// when session state save is triggered by session manager.
1446 /// The default implementation sends a close event to all
1447 /// visible top level widgets when session managment allows
1449 /// We are changing that to close all wiew one by one.
1450 /// FIXME: verify if the default implementation is enough now.
1451 if (sm.allowsInteraction() && !closeAllViews())
1456 void GuiApplication::unregisterView(GuiView * gv)
1458 LASSERT(d->views_[gv->id()] == gv, /**/);
1459 d->views_.remove(gv->id());
1460 if (current_view_ == gv)
1465 bool GuiApplication::closeAllViews()
1467 if (d->views_.empty())
1470 // When a view/window was closed before without quitting LyX, there
1471 // are already entries in the lastOpened list.
1472 theSession().lastOpened().clear();
1474 QList<GuiView *> views = d->views_.values();
1475 foreach (GuiView * view, views) {
1485 GuiView & GuiApplication::view(int id) const
1487 LASSERT(d->views_.contains(id), /**/);
1488 return *d->views_.value(id);
1492 void GuiApplication::hideDialogs(string const & name, Inset * inset) const
1494 QList<GuiView *> views = d->views_.values();
1495 foreach (GuiView * view, views)
1496 view->hideDialog(name, inset);
1500 Buffer const * GuiApplication::updateInset(Inset const * inset) const
1502 Buffer const * buffer_ = 0;
1503 QHash<int, GuiView *>::iterator end = d->views_.end();
1504 for (QHash<int, GuiView *>::iterator it = d->views_.begin(); it != end; ++it) {
1505 if (Buffer const * ptr = (*it)->updateInset(inset))
1512 bool GuiApplication::searchMenu(FuncRequest const & func,
1513 docstring_list & names) const
1515 return d->menus_.searchMenu(func, names);
1519 bool GuiApplication::readUIFile(QString const & name, bool include)
1521 LYXERR(Debug::INIT, "About to read " << name << "...");
1525 ui_path = libFileSearch("ui", name, "inc");
1526 if (ui_path.empty())
1527 ui_path = libFileSearch("ui", changeExtension(name, "inc"));
1529 ui_path = libFileSearch("ui", name, "ui");
1532 if (ui_path.empty()) {
1533 static const QString defaultUIFile = "default";
1534 LYXERR(Debug::INIT, "Could not find " << name);
1536 Alert::warning(_("Could not find UI definition file"),
1537 bformat(_("Error while reading the included file\n%1$s\n"
1538 "Please check your installation."), qstring_to_ucs4(name)));
1541 if (name == defaultUIFile) {
1542 LYXERR(Debug::INIT, "Could not find default UI file!!");
1543 Alert::warning(_("Could not find default UI file"),
1544 _("LyX could not find the default UI file!\n"
1545 "Please check your installation."));
1548 Alert::warning(_("Could not find UI definition file"),
1549 bformat(_("Error while reading the configuration file\n%1$s\n"
1550 "Falling back to default.\n"
1551 "Please look under Tools>Preferences>User Interface and\n"
1552 "check which User Interface file you are using."), qstring_to_ucs4(name)));
1553 return readUIFile(defaultUIFile, false);
1556 // Ensure that a file is read only once (prevents include loops)
1557 static QStringList uifiles;
1558 QString const uifile = toqstr(ui_path.absFilename());
1559 if (uifiles.contains(uifile)) {
1561 // We are reading again the top uifile so reset the safeguard:
1564 d->toolbars_.reset();
1566 LYXERR(Debug::INIT, "UI file '" << name << "' has been read already. "
1567 << "Is this an include loop?");
1571 uifiles.push_back(uifile);
1573 LYXERR(Debug::INIT, "Found " << name << " in " << ui_path);
1583 LexerKeyword uitags[] = {
1584 { "include", ui_include },
1585 { "menuset", ui_menuset },
1586 { "toolbars", ui_toolbars },
1587 { "toolbarset", ui_toolbarset }
1591 lex.setFile(ui_path);
1593 lyxerr << "Unable to set LyXLeX for ui file: " << ui_path
1597 if (lyxerr.debugging(Debug::PARSER))
1598 lex.printTable(lyxerr);
1600 // store which ui files define Toolbars
1601 static QStringList toolbar_uifiles;
1603 while (lex.isOK()) {
1604 switch (lex.lex()) {
1607 QString const file = toqstr(lex.getString());
1608 if (!readUIFile(file, true))
1613 d->menus_.read(lex);
1617 d->toolbars_.readToolbars(lex);
1621 d->toolbars_.readToolbarSettings(lex);
1622 toolbar_uifiles.push_back(uifile);
1626 if (!rtrim(lex.getString()).empty())
1627 lex.printError("LyX::ReadUIFile: "
1628 "Unknown menu tag: `$$Token'");
1637 settings.beginGroup("ui_files");
1638 bool touched = false;
1639 for (int i = 0; i != uifiles.size(); ++i) {
1640 QFileInfo fi(uifiles[i]);
1641 QDateTime const date_value = fi.lastModified();
1642 QString const name_key = QString::number(i);
1643 // if an ui file which defines Toolbars has changed,
1644 // we have to reset the settings
1645 if (toolbar_uifiles.contains(uifiles[i])
1646 && (!settings.contains(name_key)
1647 || settings.value(name_key).toString() != uifiles[i]
1648 || settings.value(name_key + "/date").toDateTime() != date_value)) {
1650 settings.setValue(name_key, uifiles[i]);
1651 settings.setValue(name_key + "/date", date_value);
1654 settings.endGroup();
1656 settings.remove("views");
1662 void GuiApplication::onLastWindowClosed()
1664 if (d->global_menubar_)
1665 d->global_menubar_->grabKeyboard();
1669 ////////////////////////////////////////////////////////////////////////
1671 // X11 specific stuff goes here...
1674 bool GuiApplication::x11EventFilter(XEvent * xev)
1679 switch (xev->type) {
1680 case SelectionRequest: {
1681 if (xev->xselectionrequest.selection != XA_PRIMARY)
1683 LYXERR(Debug::SELECTION, "X requested selection.");
1684 BufferView * bv = current_view_->currentBufferView();
1686 docstring const sel = bv->requestSelection();
1688 d->selection_.put(sel);
1692 case SelectionClear: {
1693 if (xev->xselectionclear.selection != XA_PRIMARY)
1695 LYXERR(Debug::SELECTION, "Lost selection.");
1696 BufferView * bv = current_view_->currentBufferView();
1698 bv->clearSelection();
1706 } // namespace frontend
1709 void hideDialogs(std::string const & name, Inset * inset)
1712 theApp()->hideDialogs(name, inset);
1716 ////////////////////////////////////////////////////////////////////
1720 ////////////////////////////////////////////////////////////////////
1722 frontend::FontLoader & theFontLoader()
1724 LASSERT(frontend::guiApp, /**/);
1725 return frontend::guiApp->fontLoader();
1729 frontend::FontMetrics const & theFontMetrics(Font const & f)
1731 return theFontMetrics(f.fontInfo());
1735 frontend::FontMetrics const & theFontMetrics(FontInfo const & f)
1737 LASSERT(frontend::guiApp, /**/);
1738 return frontend::guiApp->fontLoader().metrics(f);
1742 ////////////////////////////////////////////////////////////////////
1746 ////////////////////////////////////////////////////////////////////
1748 frontend::Clipboard & theClipboard()
1750 LASSERT(frontend::guiApp, /**/);
1751 return frontend::guiApp->clipboard();
1755 frontend::Selection & theSelection()
1757 LASSERT(frontend::guiApp, /**/);
1758 return frontend::guiApp->selection();
1764 #include "moc_GuiApplication.cpp"