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/foreach.h"
54 #include "support/ForkedCalls.h"
55 #include "support/gettext.h"
56 #include "support/lstrings.h"
57 #include "support/lyxalgo.h" // sorted
58 #include "support/Messages.h"
59 #include "support/os.h"
60 #include "support/Package.h"
63 #include "support/linkback/LinkBackProxy.h"
73 #include <QFileOpenEvent>
77 #include <QImageReader>
79 #include <QLibraryInfo>
81 #include <QMacPasteboardMime>
86 #include <QPixmapCache>
88 #include <QSessionManager>
90 #include <QSocketNotifier>
91 #include <QSortFilterProxyModel>
92 #include <QStandardItemModel>
95 #include <QTranslator>
99 #include <X11/Xatom.h>
100 #include <X11/Xlib.h>
106 #include <QWindowsMime>
113 #include <boost/bind.hpp>
114 #include <boost/crc.hpp>
120 using namespace lyx::support
;
123 static void initializeResources()
125 static bool initialized
= false;
127 Q_INIT_RESOURCE(Resources
);
135 frontend::Application
* createApplication(int & argc
, char * argv
[])
138 // prune -geometry argument(s) by shifting
139 // the following ones 2 places down.
140 for (int i
= 0 ; i
< argc
; ++i
) {
141 if (strcmp(argv
[i
], "-geometry") == 0) {
142 int const remove
= (i
+1) < argc
? 2 : 1;
144 for (int j
= i
; j
< argc
; ++j
)
145 argv
[j
] = argv
[j
+ remove
];
150 return new frontend::GuiApplication(argc
, argv
);
156 /// Return the list of loadable formats.
157 vector
<string
> loadableImageFormats()
161 QList
<QByteArray
> qt_formats
= QImageReader::supportedImageFormats();
163 LYXERR(Debug::GRAPHICS
,
164 "\nThe image loader can load the following directly:\n");
166 if (qt_formats
.empty())
167 LYXERR(Debug::GRAPHICS
, "\nQt4 Problem: No Format available!");
169 for (QList
<QByteArray
>::const_iterator it
= qt_formats
.begin(); it
!= qt_formats
.end(); ++it
) {
171 LYXERR(Debug::GRAPHICS
, (const char *) *it
<< ", ");
173 string ext
= ascii_lowercase((const char *) *it
);
184 ////////////////////////////////////////////////////////////////////////
186 // Icon loading support code
188 ////////////////////////////////////////////////////////////////////////
198 bool operator<(PngMap
const & lhs
, PngMap
const & rhs
)
200 return lhs
.key
< rhs
.key
;
206 CompareKey(QString
const & name
) : name_(name
) {}
207 bool operator()(PngMap
const & other
) const { return other
.key
== name_
; }
213 // this must be sorted alphabetically
214 // Upper case comes before lower case
215 PngMap sorted_png_map
[] = {
216 { "Bumpeq", "bumpeq2" },
219 { "Delta", "delta2" },
220 { "Downarrow", "downarrow2" },
221 { "Gamma", "gamma2" },
222 { "Lambda", "lambda2" },
223 { "Leftarrow", "leftarrow2" },
224 { "Leftrightarrow", "leftrightarrow2" },
225 { "Longleftarrow", "longleftarrow2" },
226 { "Longleftrightarrow", "longleftrightarrow2" },
227 { "Longrightarrow", "longrightarrow2" },
228 { "Omega", "omega2" },
232 { "Rightarrow", "rightarrow2" },
233 { "Sigma", "sigma2" },
234 { "Subset", "subset2" },
235 { "Supset", "supset2" },
236 { "Theta", "theta2" },
237 { "Uparrow", "uparrow2" },
238 { "Updownarrow", "updownarrow2" },
239 { "Upsilon", "upsilon2" },
240 { "Vdash", "vdash3" },
243 { "nLeftarrow", "nleftarrow2" },
244 { "nLeftrightarrow", "nleftrightarrow2" },
245 { "nRightarrow", "nrightarrow2" },
246 { "nVDash", "nvdash3" },
247 { "nvDash", "nvdash2" },
248 { "textrm \\AA", "textrm_AA"},
249 { "textrm \\O", "textrm_O"},
250 { "vDash", "vdash2" }
253 size_t const nr_sorted_png_map
= sizeof(sorted_png_map
) / sizeof(PngMap
);
256 QString
findPng(QString
const & name
)
258 PngMap
const * const begin
= sorted_png_map
;
259 PngMap
const * const end
= begin
+ nr_sorted_png_map
;
260 LASSERT(sorted(begin
, end
), /**/);
262 PngMap
const * const it
= find_if(begin
, end
, CompareKey(name
));
266 png_name
= it
->value
;
269 png_name
.replace('_', "underscore");
270 png_name
.replace(' ', '_');
272 // This way we can have "math-delim { }" on the toolbar.
273 png_name
.replace('(', "lparen");
274 png_name
.replace(')', "rparen");
275 png_name
.replace('[', "lbracket");
276 png_name
.replace(']', "rbracket");
277 png_name
.replace('{', "lbrace");
278 png_name
.replace('}', "rbrace");
279 png_name
.replace('|', "bars");
280 png_name
.replace(',', "thinspace");
281 png_name
.replace(':', "mediumspace");
282 png_name
.replace(';', "thickspace");
283 png_name
.replace('!', "negthinspace");
286 LYXERR(Debug::GUI
, "findPng(" << name
<< ")\n"
287 << "Looking for math PNG called \"" << png_name
<< '"');
294 QString
iconName(FuncRequest
const & f
, bool unknown
)
296 initializeResources();
301 case LFUN_MATH_INSERT
:
302 if (!f
.argument().empty()) {
304 name1
= findPng(toqstr(f
.argument()).mid(1));
307 case LFUN_MATH_DELIM
:
308 case LFUN_MATH_BIGDELIM
:
310 name1
= findPng(toqstr(f
.argument()));
314 name1
= toqstr(f
.argument());
316 case LFUN_COMMAND_ALTERNATIVES
: {
317 // use the first of the alternative commands
319 docstring dummy
= split(f
.argument(), firstcom
, ';');
320 name1
= toqstr(firstcom
);
321 name1
.replace(' ', '_');
325 name2
= toqstr(lyxaction
.getActionName(f
.action
));
328 if (!f
.argument().empty()) {
329 name1
= name2
+ ' ' + toqstr(f
.argument());
330 name1
.replace(' ', '_');
331 name1
.replace('\\', "backslash");
335 FileName fname
= libFileSearch("images/" + path
, name1
, "png");
337 return toqstr(fname
.absFilename());
339 fname
= libFileSearch("images/" + path
, name2
, "png");
341 return toqstr(fname
.absFilename());
343 path
= ":/images/" + path
;
346 LYXERR0("Directory " << path
<< " not found in resource!");
350 if (res
.exists(name1
))
354 if (res
.exists(name2
))
357 LYXERR(Debug::GUI
, "Cannot find icon with filename "
358 << "\"" << name1
<< "\""
360 << "\"" << name2
<< "\""
362 << lyxaction
.getActionName(f
.action
)
363 << '(' << to_utf8(f
.argument()) << ")\"");
366 fname
= libFileSearch(QString("images/"), "unknown", "png");
368 return toqstr(fname
.absFilename());
369 return QString(":/images/unknown.png");
375 QPixmap
getPixmap(QString
const & path
, QString
const & name
, QString
const & ext
)
378 FileName fname
= libFileSearch(path
, name
, ext
);
379 QString path1
= toqstr(fname
.absFilename());
380 QString path2
= ":/" + path
+ name
+ "." + ext
;
382 if (pixmap
.load(path1
)) {
385 else if (pixmap
.load(path2
)) {
389 LYXERR0("Cannot load pixmap \""
390 << path
<< name
<< '.' << ext
391 << "\", please verify resource system!");
396 QIcon
getIcon(FuncRequest
const & f
, bool unknown
)
398 QString icon
= iconName(f
, unknown
);
402 //LYXERR(Debug::GUI, "Found icon: " << icon);
404 if (!pm
.load(icon
)) {
405 LYXERR0("Cannot load icon " << icon
<< " please verify resource system!");
413 ////////////////////////////////////////////////////////////////////////
415 // LyX server support code.
417 ////////////////////////////////////////////////////////////////////////
419 class SocketNotifier
: public QSocketNotifier
422 /// connect a connection notification from the LyXServerSocket
423 SocketNotifier(QObject
* parent
, int fd
, Application::SocketCallback func
)
424 : QSocketNotifier(fd
, QSocketNotifier::Read
, parent
), func_(func
)
428 /// The callback function
429 Application::SocketCallback func_
;
433 ////////////////////////////////////////////////////////////////////////
435 // Mac specific stuff goes here...
437 ////////////////////////////////////////////////////////////////////////
439 class MenuTranslator
: public QTranslator
442 MenuTranslator(QObject
* parent
)
443 : QTranslator(parent
)
446 QString
translate(const char * /*context*/,
447 const char * sourceText
,
448 const char * /*comment*/ = 0)
450 string
const s
= sourceText
;
451 if (s
== N_("About %1") || s
== N_("Preferences")
452 || s
== N_("Reconfigure") || s
== N_("Quit %1"))
459 class GlobalMenuBar
: public QMenuBar
463 GlobalMenuBar() : QMenuBar(0) {}
466 bool event(QEvent
* e
)
468 if (e
->type() == QEvent::ShortcutOverride
) {
469 // && activeWindow() == 0) {
470 QKeyEvent
* ke
= static_cast<QKeyEvent
*>(e
);
472 setKeySymbol(&sym
, ke
);
473 theLyXFunc().processKeySym(sym
, q_key_state(ke
->modifiers()));
482 // QMacPasteboardMimeGraphics can only be compiled on Mac.
484 class QMacPasteboardMimeGraphics
: public QMacPasteboardMime
487 QMacPasteboardMimeGraphics()
488 : QMacPasteboardMime(MIME_QT_CONVERTOR
|MIME_ALL
)
491 QString
convertorName() { return "Graphics"; }
493 QString
flavorFor(QString
const & mime
)
495 LYXERR(Debug::ACTION
, "flavorFor " << mime
);
496 if (mime
== pdfMimeType())
497 return QLatin1String("com.adobe.pdf");
501 QString
mimeFor(QString flav
)
503 LYXERR(Debug::ACTION
, "mimeFor " << flav
);
504 if (flav
== QLatin1String("com.adobe.pdf"))
505 return pdfMimeType();
509 bool canConvert(QString
const & mime
, QString flav
)
511 return mimeFor(flav
) == mime
;
514 QVariant
convertToMime(QString
const & /*mime*/, QList
<QByteArray
> data
,
518 qWarning("QMacPasteboardMimeGraphics: Cannot handle multiple member data");
522 QList
<QByteArray
> convertFromMime(QString
const & /*mime*/,
523 QVariant data
, QString
/*flav*/)
525 QList
<QByteArray
> ret
;
526 ret
.append(data
.toByteArray());
532 ///////////////////////////////////////////////////////////////
534 // You can find more platform specific stuff at the end of this file...
536 ///////////////////////////////////////////////////////////////
538 ////////////////////////////////////////////////////////////////////////
539 // Windows specific stuff goes here...
542 // QWindowsMimeMetafile can only be compiled on Windows.
544 static FORMATETC
cfFromMime(QString
const & mimetype
)
547 if (mimetype
== emfMimeType()) {
548 formatetc
.cfFormat
= CF_ENHMETAFILE
;
549 formatetc
.tymed
= TYMED_ENHMF
;
550 } else if (mimetype
== wmfMimeType()) {
551 formatetc
.cfFormat
= CF_METAFILEPICT
;
552 formatetc
.tymed
= TYMED_MFPICT
;
555 formatetc
.dwAspect
= DVASPECT_CONTENT
;
556 formatetc
.lindex
= -1;
561 class QWindowsMimeMetafile
: public QWindowsMime
{
563 QWindowsMimeMetafile() {}
565 bool canConvertFromMime(FORMATETC
const & formatetc
,
566 QMimeData
const * mimedata
) const
571 bool canConvertToMime(QString
const & mimetype
,
572 IDataObject
* pDataObj
) const
574 if (mimetype
!= emfMimeType() && mimetype
!= wmfMimeType())
576 FORMATETC formatetc
= cfFromMime(mimetype
);
577 return pDataObj
->QueryGetData(&formatetc
) == S_OK
;
580 bool convertFromMime(FORMATETC
const & formatetc
,
581 const QMimeData
* mimedata
, STGMEDIUM
* pmedium
) const
586 QVariant
convertToMime(QString
const & mimetype
, IDataObject
* pDataObj
,
587 QVariant::Type preferredType
) const
590 if (!canConvertToMime(mimetype
, pDataObj
))
593 FORMATETC formatetc
= cfFromMime(mimetype
);
595 if (pDataObj
->GetData(&formatetc
, &s
) != S_OK
)
599 if (s
.tymed
== TYMED_ENHMF
) {
600 dataSize
= GetEnhMetaFileBits(s
.hEnhMetaFile
, 0, 0);
601 data
.resize(dataSize
);
602 dataSize
= GetEnhMetaFileBits(s
.hEnhMetaFile
, dataSize
,
603 (LPBYTE
)data
.data());
604 } else if (s
.tymed
== TYMED_MFPICT
) {
605 dataSize
= GetMetaFileBitsEx((HMETAFILE
)s
.hMetaFilePict
, 0, 0);
606 data
.resize(dataSize
);
607 dataSize
= GetMetaFileBitsEx((HMETAFILE
)s
.hMetaFilePict
, dataSize
,
608 (LPBYTE
)data
.data());
611 ReleaseStgMedium(&s
);
617 QVector
<FORMATETC
> formatsForMime(QString
const & mimetype
,
618 QMimeData
const * mimedata
) const
620 QVector
<FORMATETC
> formats
;
621 if (mimetype
== emfMimeType() || mimetype
== wmfMimeType())
622 formats
+= cfFromMime(mimetype
);
626 QString
mimeForFormat(FORMATETC
const & formatetc
) const
628 switch (formatetc
.cfFormat
) {
630 return emfMimeType();
631 case CF_METAFILEPICT
:
632 return wmfMimeType();
640 ////////////////////////////////////////////////////////////////////////
641 // GuiApplication::Private definition and implementation.
642 ////////////////////////////////////////////////////////////////////////
644 struct GuiApplication::Private
646 Private(): language_model_(0), global_menubar_(0)
649 /// WMF Mime handler for Windows clipboard.
650 wmf_mime_
= new QWindowsMimeMetafile();
655 QSortFilterProxyModel
* language_model_
;
657 GuiClipboard clipboard_
;
659 GuiSelection selection_
;
661 FontLoader font_loader_
;
663 ColorCache color_cache_
;
665 QTranslator qt_trans_
;
667 QHash
<int, SocketNotifier
*> socket_notifiers_
;
671 /// The global instance
674 /// this timer is used for any regular events one wants to
675 /// perform. at present it is used to check if forked processes
677 QTimer general_timer_
;
679 /// delayed FuncRequests
680 std::queue
<FuncRequest
> func_request_queue_
;
682 /// Multiple views container.
684 * Warning: This must not be a smart pointer as the destruction of the
685 * object is handled by Qt when the view is closed
686 * \sa Qt::WA_DeleteOnClose attribute.
688 QHash
<int, GuiView
*> views_
;
690 /// Only used on mac.
691 GlobalMenuBar
* global_menubar_
;
694 /// Linkback mime handler for MacOSX.
695 QMacPasteboardMimeGraphics mac_pasteboard_mime_
;
699 /// WMF Mime handler for Windows clipboard.
700 QWindowsMimeMetafile
* wmf_mime_
;
705 GuiApplication
* guiApp
;
707 GuiApplication::~GuiApplication()
710 closeAllLinkBackLinks();
716 GuiApplication::GuiApplication(int & argc
, char ** argv
)
717 : QApplication(argc
, argv
), current_view_(0),
718 d(new GuiApplication::Private
)
720 QString app_name
= "LyX";
721 QCoreApplication::setOrganizationName(app_name
);
722 QCoreApplication::setOrganizationDomain("lyx.org");
723 QCoreApplication::setApplicationName(lyx_package
);
725 // Install translator for GUI elements.
726 installTranslator(&d
->qt_trans_
);
728 // FIXME: quitOnLastWindowClosed is true by default. We should have a
729 // lyxrc setting for this in order to let the application stay resident.
730 // But then we need some kind of dock icon, at least on Windows.
732 if (lyxrc.quit_on_last_window_closed)
733 setQuitOnLastWindowClosed(false);
736 // FIXME: Do we need a lyxrc setting for this on Mac? This behaviour
737 // seems to be the default case for applications like LyX.
738 setQuitOnLastWindowClosed(false);
740 // This allows to translate the strings that appear in the LyX menu.
741 /// A translator suitable for the entries in the LyX menu.
742 /// Only needed with Qt/Mac.
743 installTranslator(new MenuTranslator(this));
747 // doubleClickInterval() is 400 ms on X11 which is just too long.
748 // On Windows and Mac OS X, the operating system's value is used.
749 // On Microsoft Windows, calling this function sets the double
750 // click interval for all applications. So we don't!
751 QApplication::setDoubleClickInterval(300);
754 connect(this, SIGNAL(lastWindowClosed()), this, SLOT(onLastWindowClosed()));
756 // needs to be done before reading lyxrc
758 lyxrc
.dpi
= (w
.logicalDpiX() + w
.logicalDpiY()) / 2;
762 // Set the cache to 5120 kilobytes which corresponds to screen size of
763 // 1280 by 1024 pixels with a color depth of 32 bits.
764 QPixmapCache::setCacheLimit(5120);
766 // Initialize RC Fonts
767 if (lyxrc
.roman_font_name
.empty())
768 lyxrc
.roman_font_name
= fromqstr(romanFontName());
770 if (lyxrc
.sans_font_name
.empty())
771 lyxrc
.sans_font_name
= fromqstr(sansFontName());
773 if (lyxrc
.typewriter_font_name
.empty())
774 lyxrc
.typewriter_font_name
= fromqstr(typewriterFontName());
776 d
->general_timer_
.setInterval(500);
777 connect(&d
->general_timer_
, SIGNAL(timeout()),
778 this, SLOT(handleRegularEvents()));
779 d
->general_timer_
.start();
783 GuiApplication
* theGuiApp()
785 return dynamic_cast<GuiApplication
*>(theApp());
789 void GuiApplication::clearSession()
796 docstring
GuiApplication::iconName(FuncRequest
const & f
, bool unknown
)
798 return qstring_to_ucs4(lyx::frontend::iconName(f
, unknown
));
803 bool GuiApplication::getStatus(FuncRequest
const & cmd
, FuncStatus
& flag
) const
809 case LFUN_WINDOW_CLOSE
:
810 enable
= d
->views_
.size() > 0;
813 case LFUN_BUFFER_NEW
:
814 case LFUN_BUFFER_NEW_TEMPLATE
:
816 case LFUN_SCREEN_FONT_UPDATE
:
818 case LFUN_WINDOW_NEW
:
828 flag
.setEnabled(false);
834 bool GuiApplication::dispatch(FuncRequest
const & cmd
)
836 switch (cmd
.action
) {
838 case LFUN_WINDOW_NEW
:
839 createView(toqstr(cmd
.argument()));
842 case LFUN_WINDOW_CLOSE
:
843 // update bookmark pit of the current buffer before window close
844 for (size_t i
= 0; i
< theSession().bookmarks().size(); ++i
)
845 theLyXFunc().gotoBookmark(i
+1, false, false);
846 current_view_
->close();
850 // quitting is triggered by the gui code
851 // (leaving the event loop).
853 current_view_
->message(from_utf8(N_("Exiting.")));
858 case LFUN_SCREEN_FONT_UPDATE
: {
859 // handle the screen font changes.
860 d
->font_loader_
.update();
861 // Backup current_view_
862 GuiView
* view
= current_view_
;
863 // Set current_view_ to zero to forbid GuiWorkArea::redraw()
864 // to skip the refresh.
866 BufferList::iterator it
= theBufferList().begin();
867 BufferList::iterator
const end
= theBufferList().end();
868 for (; it
!= end
; ++it
)
870 // Restore current_view_
871 current_view_
= view
;
875 case LFUN_BUFFER_NEW
:
876 if (d
->views_
.empty()
877 || (!lyxrc
.open_buffers_in_tabs
&& current_view_
->buffer() != 0)) {
878 createView(QString(), false); // keep hidden
879 current_view_
->newDocument(to_utf8(cmd
.argument()), false);
880 current_view_
->show();
881 setActiveWindow(current_view_
);
883 current_view_
->newDocument(to_utf8(cmd
.argument()), false);
887 case LFUN_BUFFER_NEW_TEMPLATE
:
888 if (d
->views_
.empty()
889 || (!lyxrc
.open_buffers_in_tabs
&& current_view_
->buffer() != 0)) {
891 current_view_
->newDocument(to_utf8(cmd
.argument()), true);
892 if (!current_view_
->buffer())
893 current_view_
->close();
895 current_view_
->newDocument(to_utf8(cmd
.argument()), true);
900 if (d
->views_
.empty()
901 || (!lyxrc
.open_buffers_in_tabs
&& current_view_
->buffer() != 0)) {
902 string
const fname
= to_utf8(cmd
.argument());
903 // We want the ui session to be saved per document and not per
904 // window number. The filename crc is a good enough identifier.
905 boost::crc_32_type crc
;
906 crc
= for_each(fname
.begin(), fname
.end(), crc
);
907 createView(crc
.checksum());
908 current_view_
->openDocument(fname
);
909 if (current_view_
&& !current_view_
->buffer())
910 current_view_
->close();
912 current_view_
->openDocument(to_utf8(cmd
.argument()));
915 case LFUN_SET_COLOR
: {
917 string
const x11_name
= split(to_utf8(cmd
.argument()), lyx_name
, ' ');
918 if (lyx_name
.empty() || x11_name
.empty()) {
919 current_view_
->message(
920 _("Syntax: set-color <lyx_name> <x11_name>"));
924 string
const graphicsbg
= lcolor
.getLyXName(Color_graphicsbg
);
925 bool const graphicsbg_changed
= lyx_name
== graphicsbg
926 && x11_name
!= graphicsbg
;
927 if (graphicsbg_changed
) {
928 // FIXME: The graphics cache no longer has a changeDisplay method.
930 graphics::GCache::get().changeDisplay(true);
934 if (!lcolor
.setColor(lyx_name
, x11_name
)) {
935 current_view_
->message(
936 bformat(_("Set-color \"%1$s\" failed "
937 "- color is undefined or "
938 "may not be redefined"),
939 from_utf8(lyx_name
)));
942 // Make sure we don't keep old colors in cache.
943 d
->color_cache_
.clear();
948 // Notify the caller that the action has not been dispatched.
952 // The action has been dispatched.
957 void GuiApplication::dispatchDelayed(FuncRequest
const & func
)
959 d
->func_request_queue_
.push(func
);
960 QTimer::singleShot(0, this, SLOT(processFuncRequestQueue()));
964 void GuiApplication::resetGui()
966 // Set the language defined by the user.
970 if (!readUIFile(toqstr(lyxrc
.ui_file
)))
971 // Gives some error box here.
974 if (d
->global_menubar_
)
975 d
->menus_
.fillMenuBar(d
->global_menubar_
, 0, false);
977 QHash
<int, GuiView
*>::iterator it
;
978 for (it
= d
->views_
.begin(); it
!= d
->views_
.end(); ++it
) {
980 gv
->setLayoutDirection(layoutDirection());
984 dispatch(FuncRequest(LFUN_SCREEN_FONT_UPDATE
));
988 void GuiApplication::createView(int view_id
)
990 createView(QString(), true, view_id
);
994 void GuiApplication::createView(QString
const & geometry_arg
, bool autoShow
,
997 // release the keyboard which might have been grabed by the global
998 // menubar on Mac to catch shortcuts even without any GuiView.
999 if (d
->global_menubar_
)
1000 d
->global_menubar_
->releaseKeyboard();
1005 while (d
->views_
.find(id
) != d
->views_
.end())
1008 LYXERR(Debug::GUI
, "About to create new window with ID " << id
);
1009 GuiView
* view
= new GuiView(id
);
1011 d
->views_
[id
] = view
;
1015 setActiveWindow(view
);
1018 if (!geometry_arg
.isEmpty()) {
1022 QRegExp
re( "[=]*(?:([0-9]+)[xX]([0-9]+)){0,1}[ ]*(?:([+-][0-9]*)([+-][0-9]*)){0,1}" );
1023 re
.indexIn(geometry_arg
);
1024 w
= re
.cap(1).toInt();
1025 h
= re
.cap(2).toInt();
1026 x
= re
.cap(3).toInt();
1027 y
= re
.cap(4).toInt();
1028 view
->setGeometry(x
, y
, w
, h
);
1035 Clipboard
& GuiApplication::clipboard()
1037 return d
->clipboard_
;
1041 Selection
& GuiApplication::selection()
1043 return d
->selection_
;
1047 FontLoader
& GuiApplication::fontLoader()
1049 return d
->font_loader_
;
1053 Toolbars
const & GuiApplication::toolbars() const
1055 return d
->toolbars_
;
1059 Toolbars
& GuiApplication::toolbars()
1061 return d
->toolbars_
;
1065 Menus
const & GuiApplication::menus() const
1071 Menus
& GuiApplication::menus()
1077 QList
<int> GuiApplication::viewIds() const
1079 return d
->views_
.keys();
1083 ColorCache
& GuiApplication::colorCache()
1085 return d
->color_cache_
;
1089 int GuiApplication::exec()
1091 // asynchronously handle batch commands. This event will be in
1092 // the event queue in front of other asynchronous events. Hence,
1093 // we can assume in the latter that the gui is setup already.
1094 QTimer::singleShot(0, this, SLOT(execBatchCommands()));
1096 return QApplication::exec();
1100 void GuiApplication::exit(int status
)
1102 QApplication::exit(status
);
1106 void GuiApplication::setGuiLanguage()
1108 // Set the language defined by the user.
1111 QString
const default_language
= toqstr(Messages::defaultLanguage());
1112 LYXERR(Debug::LOCALE
, "Tring to set default locale to: " << default_language
);
1113 QLocale
const default_locale(default_language
);
1114 QLocale::setDefault(default_locale
);
1116 // install translation file for Qt built-in dialogs
1117 QString
const language_name
= QString("qt_") + default_locale
.name();
1119 // language_name can be short (e.g. qt_zh) or long (e.g. qt_zh_CN).
1120 // Short-named translator can be loaded from a long name, but not the
1121 // opposite. Therefore, long name should be used without truncation.
1122 // c.f. http://doc.trolltech.com/4.1/qtranslator.html#load
1123 if (!d
->qt_trans_
.load(language_name
,
1124 QLibraryInfo::location(QLibraryInfo::TranslationsPath
))) {
1125 LYXERR(Debug::LOCALE
, "Could not find Qt translations for locale "
1128 LYXERR(Debug::LOCALE
, "Successfully installed Qt translations for locale "
1132 switch (default_locale
.language()) {
1133 case QLocale::Arabic
:
1134 case QLocale::Hebrew
:
1135 case QLocale::Persian
:
1136 case QLocale::Urdu
:
1137 setLayoutDirection(Qt::RightToLeft
);
1140 setLayoutDirection(Qt::LeftToRight
);
1145 void GuiApplication::processFuncRequestQueue()
1147 while (!d
->func_request_queue_
.empty()) {
1148 lyx::dispatch(d
->func_request_queue_
.back());
1149 d
->func_request_queue_
.pop();
1154 void GuiApplication::execBatchCommands()
1159 if (!readUIFile(toqstr(lyxrc
.ui_file
)))
1160 // Gives some error box here.
1164 // Create the global default menubar which is shown for the dialogs
1165 // and if no GuiView is visible.
1166 // This must be done after the session was recovered to know the "last files".
1167 d
->global_menubar_
= new GlobalMenuBar();
1168 d
->menus_
.fillMenuBar(d
->global_menubar_
, 0, true);
1171 lyx::execBatchCommands();
1175 QAbstractItemModel
* GuiApplication::languageModel()
1177 if (d
->language_model_
)
1178 return d
->language_model_
;
1180 QStandardItemModel
* lang_model
= new QStandardItemModel(this);
1181 lang_model
->insertColumns(0, 1);
1183 Languages::const_iterator it
= languages
.begin();
1184 Languages::const_iterator end
= languages
.end();
1185 for (; it
!= end
; ++it
) {
1186 current_row
= lang_model
->rowCount();
1187 lang_model
->insertRows(current_row
, 1);
1188 QModelIndex item
= lang_model
->index(current_row
, 0);
1189 lang_model
->setData(item
, qt_(it
->second
.display()), Qt::DisplayRole
);
1190 lang_model
->setData(item
, toqstr(it
->second
.lang()), Qt::UserRole
);
1192 d
->language_model_
= new QSortFilterProxyModel(this);
1193 d
->language_model_
->setSourceModel(lang_model
);
1194 #if QT_VERSION >= 0x040300
1195 d
->language_model_
->setSortLocaleAware(true);
1197 return d
->language_model_
;
1201 void GuiApplication::restoreGuiSession()
1203 if (!lyxrc
.load_session
)
1206 Session
& session
= theSession();
1207 LastOpenedSection::LastOpened
const & lastopened
=
1208 session
.lastOpened().getfiles();
1210 FileName active_file
;
1211 // do not add to the lastfile list since these files are restored from
1212 // last session, and should be already there (regular files), or should
1213 // not be added at all (help files).
1214 // Note that we open them in reverse order. This is because we close
1215 // buffers also in reverse order (aesthetically motivated).
1216 for (size_t i
= lastopened
.size(); i
> 0; --i
) {
1217 current_view_
->loadDocument(lastopened
[i
- 1].file_name
, false);
1218 if (lastopened
[i
- 1].active
)
1219 active_file
= lastopened
[i
- 1].file_name
;
1222 // Restore last active buffer
1223 Buffer
* buffer
= theBufferList().getBuffer(active_file
);
1225 current_view_
->setBuffer(buffer
);
1227 // clear this list to save a few bytes of RAM
1228 session
.lastOpened().clear();
1232 QString
const GuiApplication::romanFontName()
1235 font
.setKerning(false);
1236 font
.setStyleHint(QFont::Serif
);
1237 font
.setFamily("serif");
1239 return QFontInfo(font
).family();
1243 QString
const GuiApplication::sansFontName()
1246 font
.setKerning(false);
1247 font
.setStyleHint(QFont::SansSerif
);
1248 font
.setFamily("sans");
1250 return QFontInfo(font
).family();
1254 QString
const GuiApplication::typewriterFontName()
1257 font
.setKerning(false);
1258 font
.setStyleHint(QFont::TypeWriter
);
1259 font
.setFamily("monospace");
1261 return QFontInfo(font
).family();
1265 void GuiApplication::handleRegularEvents()
1267 ForkedCallsController::handleCompletedProcesses();
1271 bool GuiApplication::event(QEvent
* e
)
1274 case QEvent::FileOpen
: {
1275 // Open a file; this happens only on Mac OS X for now.
1277 // We do this asynchronously because on startup the batch
1278 // commands are not executed here yet and the gui is not ready
1280 QFileOpenEvent
* foe
= static_cast<QFileOpenEvent
*>(e
);
1281 dispatchDelayed(FuncRequest(LFUN_FILE_OPEN
, qstring_to_ucs4(foe
->file())));
1286 return QApplication::event(e
);
1291 bool GuiApplication::notify(QObject
* receiver
, QEvent
* event
)
1294 return QApplication::notify(receiver
, event
);
1296 catch (ExceptionMessage
const & e
) {
1298 case ErrorException
:
1300 setQuitOnLastWindowClosed(false);
1302 Alert::error(e
.title_
, e
.details_
);
1304 // Properly crash in debug mode in order to get a useful backtrace.
1307 // In release mode, try to exit gracefully.
1310 case BufferException
: {
1311 Buffer
* buf
= current_view_
->buffer();
1312 docstring details
= e
.details_
+ '\n';
1313 details
+= theBufferList().emergencyWrite(buf
);
1314 theBufferList().release(buf
);
1315 details
+= "\n" + _("The current document was closed.");
1316 Alert::error(e
.title_
, details
);
1319 case WarningException
:
1320 Alert::warning(e
.title_
, e
.details_
);
1324 catch (exception
const & e
) {
1325 docstring s
= _("LyX has caught an exception, it will now "
1326 "attempt to save all unsaved documents and exit."
1328 s
+= from_ascii(e
.what());
1329 Alert::error(_("Software exception Detected"), s
);
1333 docstring s
= _("LyX has caught some really weird exception, it will "
1334 "now attempt to save all unsaved documents and exit.");
1335 Alert::error(_("Software exception Detected"), s
);
1343 bool GuiApplication::getRgbColor(ColorCode col
, RGBColor
& rgbcol
)
1345 QColor
const & qcol
= d
->color_cache_
.get(col
);
1346 if (!qcol
.isValid()) {
1352 rgbcol
.r
= qcol
.red();
1353 rgbcol
.g
= qcol
.green();
1354 rgbcol
.b
= qcol
.blue();
1359 string
const GuiApplication::hexName(ColorCode col
)
1361 return ltrim(fromqstr(d
->color_cache_
.get(col
).name()), "#");
1365 void GuiApplication::registerSocketCallback(int fd
, SocketCallback func
)
1367 SocketNotifier
* sn
= new SocketNotifier(this, fd
, func
);
1368 d
->socket_notifiers_
[fd
] = sn
;
1369 connect(sn
, SIGNAL(activated(int)), this, SLOT(socketDataReceived(int)));
1373 void GuiApplication::socketDataReceived(int fd
)
1375 d
->socket_notifiers_
[fd
]->func_();
1379 void GuiApplication::unregisterSocketCallback(int fd
)
1381 d
->socket_notifiers_
.take(fd
)->setEnabled(false);
1385 void GuiApplication::commitData(QSessionManager
& sm
)
1387 /// The implementation is required to avoid an application exit
1388 /// when session state save is triggered by session manager.
1389 /// The default implementation sends a close event to all
1390 /// visible top level widgets when session managment allows
1392 /// We are changing that to close all wiew one by one.
1393 /// FIXME: verify if the default implementation is enough now.
1394 if (sm
.allowsInteraction() && !closeAllViews())
1399 void GuiApplication::unregisterView(GuiView
* gv
)
1401 LASSERT(d
->views_
[gv
->id()] == gv
, /**/);
1402 d
->views_
.remove(gv
->id());
1403 if (current_view_
== gv
) {
1405 theLyXFunc().setLyXView(0);
1410 bool GuiApplication::closeAllViews()
1412 if (d
->views_
.empty())
1415 // When a view/window was closed before without quitting LyX, there
1416 // are already entries in the lastOpened list.
1417 theSession().lastOpened().clear();
1419 QList
<GuiView
*> views
= d
->views_
.values();
1420 foreach (GuiView
* view
, views
) {
1430 GuiView
& GuiApplication::view(int id
) const
1432 LASSERT(d
->views_
.contains(id
), /**/);
1433 return *d
->views_
.value(id
);
1437 void GuiApplication::hideDialogs(string
const & name
, Inset
* inset
) const
1439 QList
<GuiView
*> views
= d
->views_
.values();
1440 foreach (GuiView
* view
, views
)
1441 view
->hideDialog(name
, inset
);
1445 Buffer
const * GuiApplication::updateInset(Inset
const * inset
) const
1447 Buffer
const * buffer_
= 0;
1448 QHash
<int, GuiView
*>::iterator end
= d
->views_
.end();
1449 for (QHash
<int, GuiView
*>::iterator it
= d
->views_
.begin(); it
!= end
; ++it
) {
1450 if (Buffer
const * ptr
= (*it
)->updateInset(inset
))
1457 bool GuiApplication::searchMenu(FuncRequest
const & func
,
1458 docstring_list
& names
) const
1460 return d
->menus_
.searchMenu(func
, names
);
1464 bool GuiApplication::readUIFile(QString
const & name
, bool include
)
1474 LexerKeyword uitags
[] = {
1475 { "include", ui_include
},
1476 { "menuset", ui_menuset
},
1477 { "toolbars", ui_toolbars
},
1478 { "toolbarset", ui_toolbarset
}
1481 LYXERR(Debug::INIT
, "About to read " << name
<< "...");
1485 ui_path
= libFileSearch("ui", name
, "inc");
1486 if (ui_path
.empty())
1487 ui_path
= libFileSearch("ui", changeExtension(name
, "inc"));
1489 ui_path
= libFileSearch("ui", name
, "ui");
1492 if (ui_path
.empty()) {
1493 LYXERR(Debug::INIT
, "Could not find " << name
);
1494 Alert::warning(_("Could not find UI definition file"),
1495 bformat(_("Error while reading the configuration file\n%1$s.\n"
1496 "Please check your installation."), qstring_to_ucs4(name
)));
1501 // Ensure that a file is read only once (prevents include loops)
1502 static QStringList uifiles
;
1503 QString
const uifile
= toqstr(ui_path
.absFilename());
1504 if (uifiles
.contains(uifile
)) {
1506 // We are reading again the top uifile so reset the safeguard:
1509 d
->toolbars_
.reset();
1511 LYXERR(Debug::INIT
, "UI file '" << name
<< "' has been read already. "
1512 << "Is this an include loop?");
1516 uifiles
.push_back(uifile
);
1518 LYXERR(Debug::INIT
, "Found " << name
<< " in " << ui_path
);
1521 lex
.setFile(ui_path
);
1523 lyxerr
<< "Unable to set LyXLeX for ui file: " << ui_path
1527 if (lyxerr
.debugging(Debug::PARSER
))
1528 lex
.printTable(lyxerr
);
1530 while (lex
.isOK()) {
1531 switch (lex
.lex()) {
1534 QString
const file
= toqstr(lex
.getString());
1535 if (!readUIFile(file
, true))
1540 d
->menus_
.read(lex
);
1544 d
->toolbars_
.readToolbars(lex
);
1548 d
->toolbars_
.readToolbarSettings(lex
);
1552 if (!rtrim(lex
.getString()).empty())
1553 lex
.printError("LyX::ReadUIFile: "
1554 "Unknown menu tag: `$$Token'");
1563 settings
.beginGroup("ui_files");
1564 bool touched
= false;
1565 for (int i
= 0; i
!= uifiles
.size(); ++i
) {
1566 QFileInfo
fi(uifiles
[i
]);
1567 QDateTime
const date_value
= fi
.lastModified();
1568 QString
const name_key
= QString::number(i
);
1569 if (!settings
.contains(name_key
)
1570 || settings
.value(name_key
).toString() != uifiles
[i
]
1571 || settings
.value(name_key
+ "/date").toDateTime() != date_value
) {
1573 settings
.setValue(name_key
, uifiles
[i
]);
1574 settings
.setValue(name_key
+ "/date", date_value
);
1577 settings
.endGroup();
1579 settings
.remove("views");
1585 void GuiApplication::onLastWindowClosed()
1587 if (d
->global_menubar_
)
1588 d
->global_menubar_
->grabKeyboard();
1592 ////////////////////////////////////////////////////////////////////////
1594 // X11 specific stuff goes here...
1597 bool GuiApplication::x11EventFilter(XEvent
* xev
)
1602 switch (xev
->type
) {
1603 case SelectionRequest
: {
1604 if (xev
->xselectionrequest
.selection
!= XA_PRIMARY
)
1606 LYXERR(Debug::SELECTION
, "X requested selection.");
1607 BufferView
* bv
= current_view_
->view();
1609 docstring
const sel
= bv
->requestSelection();
1611 d
->selection_
.put(sel
);
1615 case SelectionClear
: {
1616 if (xev
->xselectionclear
.selection
!= XA_PRIMARY
)
1618 LYXERR(Debug::SELECTION
, "Lost selection.");
1619 BufferView
* bv
= current_view_
->view();
1621 bv
->clearSelection();
1629 } // namespace frontend
1632 void hideDialogs(std::string
const & name
, Inset
* inset
)
1635 theApp()->hideDialogs(name
, inset
);
1639 ////////////////////////////////////////////////////////////////////
1643 ////////////////////////////////////////////////////////////////////
1645 frontend::FontLoader
& theFontLoader()
1647 LASSERT(frontend::guiApp
, /**/);
1648 return frontend::guiApp
->fontLoader();
1652 frontend::FontMetrics
const & theFontMetrics(Font
const & f
)
1654 return theFontMetrics(f
.fontInfo());
1658 frontend::FontMetrics
const & theFontMetrics(FontInfo
const & f
)
1660 LASSERT(frontend::guiApp
, /**/);
1661 return frontend::guiApp
->fontLoader().metrics(f
);
1665 ////////////////////////////////////////////////////////////////////
1669 ////////////////////////////////////////////////////////////////////
1671 frontend::Clipboard
& theClipboard()
1673 LASSERT(frontend::guiApp
, /**/);
1674 return frontend::guiApp
->clipboard();
1678 frontend::Selection
& theSelection()
1680 LASSERT(frontend::guiApp
, /**/);
1681 return frontend::guiApp
->selection();
1687 #include "moc_GuiApplication.cpp"