]> git.lyx.org Git - lyx.git/blobdiff - src/frontends/qt/GuiApplication.cpp
Move LYX_(BEGIN|END)_MUTE_GCC_WARNING macros to their own header.
[lyx.git] / src / frontends / qt / GuiApplication.cpp
index b0ec64ee0919d53f506239bd7754f689b3f42409..26288a5f55dd1cf148b1153fa56493f947480c7f 100644 (file)
@@ -63,6 +63,7 @@
 #include "support/convert.h"
 #include "support/debug.h"
 #include "support/ExceptionMessage.h"
+#include "support/environment.h"
 #include "support/FileName.h"
 #include "support/filetools.h"
 #include "support/ForkedCalls.h"
@@ -70,6 +71,7 @@
 #include "support/lassert.h"
 #include "support/lstrings.h"
 #include "support/lyxalgo.h" // sorted
+#include "support/mute_warning.h"
 #include "support/textutils.h"
 #include "support/Messages.h"
 #include "support/os.h"
@@ -88,7 +90,6 @@
 #include <QByteArray>
 #include <QBitmap>
 #include <QDateTime>
-#include <QDesktopWidget>
 #include <QEvent>
 #include <QFileOpenEvent>
 #include <QFileInfo>
 #include <QObject>
 #include <QPainter>
 #include <QPixmap>
-#include <QRegExp>
+#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
+#include <QRandomGenerator>
+#endif
+#include <QScreen>
 #include <QSessionManager>
 #include <QSettings>
 #include <QSocketNotifier>
 #include <QSortFilterProxyModel>
 #include <QStandardItemModel>
+#include <QSvgRenderer>
 #include <QTimer>
 #include <QTranslator>
 #include <QThreadPool>
 #include <QWidget>
 
-#ifdef Q_WS_X11
-#include <X11/Xatom.h>
-#include <X11/Xlib.h>
-#include <QX11Info>
-#undef CursorShape
-#undef None
-#elif defined(QPA_XCB)
+#if defined(QPA_XCB)
 #include <xcb/xcb.h>
 #ifdef HAVE_QT5_X11_EXTRAS
 #include <QtX11Extras/QX11Info>
 #endif
 #endif
 
-#if (QT_VERSION < 0x050000) || (QT_VERSION >= 0x050400)
+#if (QT_VERSION >= 0x050400)
 #if defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)
-#if (QT_VERSION < 0x050000)
-#include <QWindowsMime>
+#if (QT_VERSION >= 0x060000)
+#if (QT_VERSION >= 0x060500)
+#include <QtGui/QWindowsMimeConverter>
+#define QWINDOWSMIME QWindowsMimeConverter
+#define QVARIANTTYPE QMetaType
+#else
+#include <QtGui/private/qguiapplication_p.h>
+#include <QtGui/private/qwindowsmime_p.h>
+#include <QtGui/qpa/qplatformintegration.h>
 #define QWINDOWSMIME QWindowsMime
+#define QVARIANTTYPE QMetaType
+using QWindowsMime = QNativeInterface::Private::QWindowsMime;
+using QWindowsApplication = QNativeInterface::Private::QWindowsApplication;
+#endif
 #else
 #include <QWinMime>
 #define QWINDOWSMIME QWinMime
+#define QVARIANTTYPE QVariant::Type
 #endif
 #ifdef Q_CC_GNU
 #include <wtypes.h>
 #endif
 #endif
 
-#ifdef Q_OS_MAC
+#if defined(Q_OS_MAC) && (QT_VERSION < 0x060000)
 #include <QMacPasteboardMime>
 #endif // Q_OS_MAC
 
@@ -161,7 +172,7 @@ namespace lyx {
 
 frontend::Application * createApplication(int & argc, char * argv[])
 {
-#if !defined(Q_WS_X11) && !defined(QPA_XCB)
+#if !defined(QPA_XCB)
        // prune -geometry argument(s) by shifting
        // the following ones 2 places down.
        for (int i = 0 ; i < argc ; ++i) {
@@ -180,6 +191,38 @@ frontend::Application * createApplication(int & argc, char * argv[])
        AllowSetForegroundWindow(ASFW_ANY);
 #endif
 
+
+#if defined(Q_OS_MAC)
+       int const cursor_time_on = NSTextInsertionPointBlinkPeriodOn();
+       int const cursor_time_off = NSTextInsertionPointBlinkPeriodOff();
+       if (cursor_time_on > 0 && cursor_time_off > 0) {
+               QApplication::setCursorFlashTime(cursor_time_on + cursor_time_off);
+       } else if (cursor_time_on <= 0 && cursor_time_off > 0) {
+               // Off is set and On is undefined of zero
+               QApplication::setCursorFlashTime(0);
+       } else if (cursor_time_off <= 0 && cursor_time_on > 0) {
+               // On is set and Off is undefined of zero
+               QApplication::setCursorFlashTime(0);
+       }
+#endif
+
+// Setup high DPI handling. This is a bit complicated, but will be default in Qt6.
+// macOS does it by itself.
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) && !defined(Q_OS_MAC)
+#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)
+    // Attribute Qt::AA_EnableHighDpiScaling must be set before QCoreApplication is created
+    if (getEnv("QT_ENABLE_HIGHDPI_SCALING").empty()
+               && getEnv("QT_AUTO_SCREEN_SCALE_FACTOR").empty())
+        QApplication::setAttribute(Qt::AA_EnableHighDpiScaling, true);
+#endif
+
+#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
+    // HighDPI scale factor policy must be set before QGuiApplication is created
+    if (getEnv("QT_SCALE_FACTOR_ROUNDING_POLICY").empty())
+        QApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
+#endif
+#endif
+
        frontend::GuiApplication * guiApp = new frontend::GuiApplication(argc, argv);
        // I'd rather do that in the constructor, but I do not think that
        // the palette is accessible there.
@@ -477,6 +520,47 @@ QString themeIconName(QString const & action)
 }
 
 
+namespace {
+
+/* Get aliases for icon name. This allows to avoid duplication of
+ * icons when new versions of functions are introduced for the
+ * toolbar. A good example is the introduction of layout-toggle in
+ * #9864.
+ * The file is parsed by Lexer. Each line is of the form
+ *     <original substring> <replacement substring>
+ *
+ * The return value is another icon file name that can be queried.
+ */
+// FIXME: consider using regular expressions.
+QString getAlias(QString name) {
+       static bool has_aliases = false;
+       static vector <pair<QString,QString>> aliases;
+       // Initialize aliases list (once).
+       if (!has_aliases) {
+               FileName alfile = libFileSearch("images", "icon.aliases");
+               if (alfile.exists()) {
+                       Lexer lex;
+                       lex.setFile(alfile);
+                       while(lex.isOK()) {
+                               string from, to;
+                               lex >> from >> to;
+                               if (!from.empty())
+                                       aliases.push_back({toqstr(from), toqstr(to)});
+                       }
+               }
+               has_aliases = true;
+       }
+       // check for the following aliases
+       for (auto const & alias : aliases) {
+               if (name.contains(alias.first))
+                       name.replace(alias.first, alias.second);
+       }
+       return name;
+}
+
+} // namespace
+
+
 IconInfo iconInfo(FuncRequest const & f, bool unknown, bool rtl)
 {
        IconInfo res;
@@ -491,6 +575,9 @@ IconInfo iconInfo(FuncRequest const & f, bool unknown, bool rtl)
                name.replace(';', '_');
                name.replace('\\', "backslash");
                names << name;
+               QString alias = getAlias(name);
+               if (alias != name)
+                       names << alias;
 
                // then special default icon for some lfuns
                switch (f.action()) {
@@ -522,16 +609,19 @@ IconInfo iconInfo(FuncRequest const & f, bool unknown, bool rtl)
 
        // next thing to try is function name alone
        names << lfunname;
+       QString alias = getAlias(lfunname);
+       if (alias != lfunname)
+               names << alias;
 
        // and finally maybe the unknown icon
        if (unknown)
                names << "unknown";
 
-       search_mode const mode = theGuiApp()->imageSearchMode();
+       search_mode const mode = theGuiApp() ? theGuiApp()->imageSearchMode() : support::must_exist;
        // The folders where icons are searched for
        QStringList imagedirs;
        imagedirs << "images/ipa/" << "images/";
-       // This is used to search for rtl version of icons which have the +rrtl suffix.
+       // This is used to search for rtl version of icons which have the +rtl suffix.
        QStringList suffixes;
        if (rtl)
                suffixes << "+rtl";
@@ -607,7 +697,8 @@ QPixmap getPixmap(QString const & path, QString const & name, QString const & ex
        QPixmap pixmap = QPixmap();
 
        if (pixmap.load(fpath)) {
-               if (fpath.contains("math") || fpath.contains("ipa"))
+               if (fpath.contains("math") || fpath.contains("ipa")
+                   || fpath.contains("bullets"))
                        return prepareForDarkMode(pixmap);
                return pixmap;
        }
@@ -623,7 +714,6 @@ QPixmap getPixmap(QString const & path, QString const & name, QString const & ex
 
 QIcon getIcon(FuncRequest const & f, bool unknown, bool rtl)
 {
-#if (QT_VERSION >= 0x040600)
        if (lyxrc.use_system_theme_icons) {
                // use the icons from system theme that are available
                QString action = toqstr(lyxaction.getActionName(f.action()));
@@ -636,7 +726,6 @@ QIcon getIcon(FuncRequest const & f, bool unknown, bool rtl)
                                return thmicn;
                }
        }
-#endif
 
        IconInfo icondata = iconInfo(f, unknown, rtl);
        if (icondata.filepath.isEmpty())
@@ -688,11 +777,7 @@ public:
 
        QString translate(const char * /* context */,
                const char *sourceText,
-#if QT_VERSION >= 0x050000
                const char * /* disambiguation */ = nullptr, int /* n */ = -1) const override
-#else
-               const char * /*comment*/ = 0) const override
-#endif
        {
                // Here we declare the strings that need to be translated from Qt own GUI
                // This is needed to include these strings to po files
@@ -723,7 +808,7 @@ public:
 //
 ////////////////////////////////////////////////////////////////////////
 
-#ifdef Q_OS_MAC
+#if defined(Q_OS_MAC) && (QT_VERSION < 0x060000)
 // QMacPasteboardMimeGraphics can only be compiled on Mac.
 
 class QMacPasteboardMimeGraphics : public QMacPasteboardMime
@@ -783,7 +868,7 @@ public:
 ////////////////////////////////////////////////////////////////////////
 // Windows specific stuff goes here...
 
-#if (QT_VERSION < 0x050000) || (QT_VERSION >= 0x050400)
+#if (QT_VERSION >= 0x050400)
 #if defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)
 // QWindowsMimeMetafile can only be compiled on Windows.
 
@@ -830,7 +915,7 @@ public:
        }
 
        QVariant convertToMime(QString const & mimetype, IDataObject * pDataObj,
-               QVariant::Type /*preferredType*/) const override
+               QVARIANTTYPE /*preferredType*/) const override
        {
                QByteArray data;
                if (!canConvertToMime(mimetype, pDataObj))
@@ -944,17 +1029,31 @@ public:
 struct GuiApplication::Private
 {
        Private(): language_model_(nullptr), meta_fake_bit(NoModifier),
-               global_menubar_(nullptr)
+               global_menubar_(nullptr), last_state_(Qt::ApplicationInactive)
        {
-       #if (QT_VERSION < 0x050000) || (QT_VERSION >= 0x050400)
+       #if (QT_VERSION >= 0x050400)
        #if defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)
                /// WMF Mime handler for Windows clipboard.
                wmf_mime_ = new QWindowsMimeMetafile;
+       #if (QT_VERSION >= 0x060000 && QT_VERSION < 0x060500)
+               win_app_ = dynamic_cast<QWindowsApplication *>
+                       (QGuiApplicationPrivate::platformIntegration());
+               win_app_->registerMime(wmf_mime_);
+       #endif
        #endif
        #endif
                initKeySequences(&theTopLevelKeymap());
        }
 
+       #if (QT_VERSION >= 0x060000 && QT_VERSION < 0x060500)
+       #if defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)
+       ~Private()
+       {
+               win_app_->unregisterMime(wmf_mime_);
+       }
+       #endif
+       #endif
+
        void initKeySequences(KeyMap * kb)
        {
                keyseq = KeySequence(kb, kb);
@@ -1011,16 +1110,21 @@ struct GuiApplication::Private
 
        /// Only used on mac.
        QMenuBar * global_menubar_;
+       /// Holds previous application state on Mac
+       Qt::ApplicationState last_state_;
 
-#ifdef Q_OS_MAC
+#if defined(Q_OS_MAC) && (QT_VERSION < 0x060000)
        /// Linkback mime handler for MacOSX.
        QMacPasteboardMimeGraphics mac_pasteboard_mime_;
 #endif
 
-#if (QT_VERSION < 0x050000) || (QT_VERSION >= 0x050400)
+#if (QT_VERSION >= 0x050400)
 #if defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)
        /// WMF Mime handler for Windows clipboard.
        QWindowsMimeMetafile * wmf_mime_;
+#if (QT_VERSION >= 0x060000 && QT_VERSION < 0x060500)
+       QWindowsApplication * win_app_;
+#endif
 #endif
 #endif
 
@@ -1048,7 +1152,7 @@ GuiApplication::GuiApplication(int & argc, char ** argv)
        QCoreApplication::setOrganizationName(app_name);
        QCoreApplication::setOrganizationDomain("lyx.org");
        QCoreApplication::setApplicationName(lyx_package);
-#if QT_VERSION >= 0x050000
+#if QT_VERSION < 0x060000
        QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
 #endif
 
@@ -1056,7 +1160,11 @@ GuiApplication::GuiApplication(int & argc, char ** argv)
        setDesktopFileName(lyx_package);
 #endif
 
+#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
+       QRandomGenerator(QDateTime::currentDateTime().toSecsSinceEpoch());
+#else
        qsrand(QDateTime::currentDateTime().toTime_t());
+#endif
 
        // Install LyX translator for missing Qt translations
        installTranslator(&d->gui_trans_);
@@ -1083,9 +1191,11 @@ GuiApplication::GuiApplication(int & argc, char ** argv)
        setupApplescript();
        appleCleanupEditMenu();
        appleCleanupViewMenu();
+       connect(this, SIGNAL(applicationStateChanged(Qt::ApplicationState)),
+                       this, SLOT(onApplicationStateChanged(Qt::ApplicationState)));
 #endif
 
-#if defined(Q_WS_X11) || defined(QPA_XCB)
+#if defined(QPA_XCB)
        // doubleClickInterval() is 400 ms on X11 which is just too long.
        // On Windows and Mac OS X, the operating system's value is used.
        // On Microsoft Windows, calling this function sets the double
@@ -1111,11 +1221,8 @@ GuiApplication::GuiApplication(int & argc, char ** argv)
        if (lyxrc.typewriter_font_name.empty())
                lyxrc.typewriter_font_name = fromqstr(typewriterFontName());
 
-#if (QT_VERSION >= 0x050000)
-       // Qt4 does this in event(), see below.
        // Track change of keyboard
        connect(inputMethod(), SIGNAL(localeChanged()), this, SLOT(onLocaleChanged()));
-#endif
 
        d->general_timer_.setInterval(500);
        connect(&d->general_timer_, SIGNAL(timeout()),
@@ -1138,36 +1245,9 @@ GuiApplication * theGuiApp()
 }
 
 
-#if QT_VERSION < 0x050000
-// Emulate platformName() for Qt4
-
-// FIXME: when ditching this method, remove all tests
-//     platformName() == "qt4x11"
-// in the code
-QString GuiApplication::platformName() const
-{
-# if defined(Q_WS_X11)
-       // Note that this one does not really exist
-       return "qt4x11";
-# elif defined(Q_OS_MAC)
-       return "cocoa";
-# elif defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)
-       return "windows";
-# else
-       LYXERR0("Unknown platform!");
-       return "unknown";
-# endif
-}
-#endif
-
-
 double GuiApplication::pixelRatio() const
 {
-#if QT_VERSION >= 0x050000
        return qt_scale_factor * devicePixelRatio();
-#else
-       return 1.0;
-#endif
 }
 
 
@@ -1189,6 +1269,11 @@ docstring Application::mathIcon(docstring const & c)
        return qstring_to_ucs4(findImg(toqstr(c)));
 }
 
+void Application::applyPrefs()
+{
+       if (lyxrc.ui_style != "default")
+               lyx::frontend::GuiApplication::setStyle(toqstr(lyxrc.ui_style));
+}
 
 FuncStatus GuiApplication::getStatus(FuncRequest const & cmd) const
 {
@@ -1355,10 +1440,17 @@ bool GuiApplication::getStatus(FuncRequest const & cmd, FuncStatus & flag) const
        case LFUN_REPEAT:
        case LFUN_PREFERENCES_SAVE:
        case LFUN_BUFFER_SAVE_AS_DEFAULT:
-       case LFUN_DEBUG_LEVEL_SET:
                // these are handled in our dispatch()
                break;
 
+       case LFUN_DEBUG_LEVEL_SET: {
+               string bad = Debug::badValue(to_utf8(cmd.argument()));
+               enable = bad.empty();
+               if (!bad.empty())
+                       flag.message(bformat(_("Bad debug value `%1$s'."), from_utf8(bad)));
+               break;
+       }
+
        case LFUN_WINDOW_CLOSE:
                enable = !d->views_.empty();
                break;
@@ -1366,6 +1458,7 @@ bool GuiApplication::getStatus(FuncRequest const & cmd, FuncStatus & flag) const
        case LFUN_BUFFER_NEW:
        case LFUN_BUFFER_NEW_TEMPLATE:
        case LFUN_FILE_OPEN:
+       case LFUN_LYXFILES_OPEN:
        case LFUN_HELP_OPEN:
        case LFUN_SCREEN_FONT_UPDATE:
        case LFUN_SET_COLOR:
@@ -1478,13 +1571,16 @@ DispatchResult const & GuiApplication::dispatch(FuncRequest const & cmd)
 
        dr.screenUpdate(Update::FitCursor);
        {
-               // This handles undo groups automagically
+               // All the code is kept inside the undo group because
+               // updateBuffer can create undo actions (see #11292)
                UndoGroupHelper ugh(buffer);
                dispatch(cmd, dr);
-               // redraw the screen at the end (first of the two drawing steps).
-               // This is done unless explicitly requested otherwise.
-               // This code is kept inside the undo group because updateBuffer
-               // can create undo actions (see #11292)
+               if (dr.screenUpdate() & Update::ForceAll) {
+                       for (Buffer const * b : theBufferList())
+                               b->changed(true);
+                       dr.screenUpdate(dr.screenUpdate() & ~Update::ForceAll);
+               }
+
                updateCurrentView(cmd, dr);
        }
 
@@ -1500,7 +1596,7 @@ void GuiApplication::updateCurrentView(FuncRequest const & cmd, DispatchResult &
 
        BufferView * bv = current_view_->currentBufferView();
        if (bv) {
-               if (dr.needBufferUpdate()) {
+               if (dr.needBufferUpdate() || bv->buffer().needUpdate()) {
                        bv->cursor().clearBufferUpdate();
                        bv->buffer().updateBuffer();
                }
@@ -1597,8 +1693,10 @@ void GuiApplication::gotoBookmark(unsigned int idx, bool openFile,
 void GuiApplication::reconfigure(string const & option)
 {
        // emit message signal.
-       if (current_view_)
+       if (current_view_) {
                current_view_->message(_("Running configure..."));
+               current_view_->setCursor(Qt::WaitCursor);
+       }
 
        // Run configure in user lyx directory
        string const lock_file = package().getConfigureLockName();
@@ -1612,6 +1710,9 @@ void GuiApplication::reconfigure(string const & option)
        LaTeXPackages::getAvailable();
        fileUnlock(fd, lock_file.c_str());
 
+       if (current_view_)
+               current_view_->unsetCursor();
+
        if (ret)
                Alert::information(_("System reconfiguration failed"),
                           _("The system reconfiguration has failed.\n"
@@ -1723,7 +1824,7 @@ void GuiApplication::dispatch(FuncRequest const & cmd, DispatchResult & dr)
                        createView(QString(), false); // keep hidden
                        current_view_->newDocument(to_utf8(cmd.argument()));
                        current_view_->show();
-                       setActiveWindow(current_view_);
+                       current_view_->activateWindow();
                } else {
                        current_view_->newDocument(to_utf8(cmd.argument()));
                }
@@ -1750,7 +1851,7 @@ void GuiApplication::dispatch(FuncRequest const & cmd, DispatchResult & dr)
                //   current_view_ is not null.
                validateCurrentView();
                // FIXME: create a new method shared with LFUN_HELP_OPEN.
-               string const fname = to_utf8(cmd.argument());
+               string const fname = trim(to_utf8(cmd.argument()), "\"");
                bool const is_open = FileName::isAbsolute(fname)
                        && theBufferList().getBuffer(FileName(fname));
                if (!current_view_
@@ -1760,7 +1861,7 @@ void GuiApplication::dispatch(FuncRequest const & cmd, DispatchResult & dr)
                        // We want the ui session to be saved per document and not per
                        // window number. The filename crc is a good enough identifier.
                        createView(support::checksum(fname));
-                       current_view_->openDocument(fname);
+                       current_view_->openDocuments(fname, cmd.origin());
                        if (!current_view_->documentBufferView())
                                current_view_->close();
                        else if (cmd.origin() == FuncRequest::LYXSERVER) {
@@ -1769,7 +1870,7 @@ void GuiApplication::dispatch(FuncRequest const & cmd, DispatchResult & dr)
                                current_view_->showNormal();
                        }
                } else {
-                       current_view_->openDocument(fname);
+                       current_view_->openDocuments(fname, cmd.origin());
                        if (cmd.origin() == FuncRequest::LYXSERVER) {
                                current_view_->raise();
                                current_view_->activateWindow();
@@ -1805,6 +1906,19 @@ void GuiApplication::dispatch(FuncRequest const & cmd, DispatchResult & dr)
                break;
        }
 
+       case LFUN_LYXFILES_OPEN: {
+               string arg = to_utf8(cmd.argument());
+               if (arg.empty())
+                       // set default
+                       arg = "templates";
+               if (arg != "templates" && arg != "examples") {
+                       current_view_->message(_("Wrong argument. Must be 'examples' or 'templates'."));
+                       break;
+               }
+               lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "lyxfiles " + arg));
+               break;
+       }
+
        case LFUN_SET_COLOR: {
                string const lyx_name = cmd.getArg(0);
                string x11_name = cmd.getArg(1);
@@ -2112,9 +2226,8 @@ void GuiApplication::dispatch(FuncRequest const & cmd, DispatchResult & dr)
                DocumentClassConstPtr olddc = defaults.params().documentClassPtr();
                int const unknown_tokens = defaults.readHeader(lex);
                DocumentClassConstPtr newdc = defaults.params().documentClassPtr();
-               ErrorList el;
                InsetText & theinset = static_cast<InsetText &>(defaults.inset());
-               cap::switchBetweenClasses(olddc, newdc, theinset, el);
+               cap::switchBetweenClasses(olddc, newdc, theinset);
 
                if (unknown_tokens != 0) {
                        lyxerr << "Warning in LFUN_BUFFER_SAVE_AS_DEFAULT!\n"
@@ -2150,17 +2263,15 @@ void GuiApplication::dispatch(FuncRequest const & cmd, DispatchResult & dr)
 
        case LFUN_DIALOG_SHOW: {
                string const name = cmd.getArg(0);
-
-               if ( name == "aboutlyx"
-                       || name == "prefs"
-                       || name == "texinfo"
-                       || name == "progress"
-                       || name == "compare")
+               // Workaround: on Mac OS the application
+               // is not terminated when closing the last view.
+               // With the following dialogs which should still
+               // be usable, create a new one to be able
+               // to dispatch LFUN_DIALOG_SHOW to this view.
+               if (name == "aboutlyx" || name == "compare"
+                   || name == "lyxfiles" || name == "prefs"
+                   || name == "progress" || name == "texinfo")
                {
-                       // work around: on Mac OS the application
-                       // is not terminated when closing the last view.
-                       // Create a new one to be able to dispatch the
-                       // LFUN_DIALOG_SHOW to this view.
                        if (current_view_ == nullptr)
                                createView();
                }
@@ -2174,6 +2285,11 @@ void GuiApplication::dispatch(FuncRequest const & cmd, DispatchResult & dr)
                break;
        }
 
+       if (current_view_ && current_view_->isFullScreen()) {
+               if (current_view_->menuBar()->isVisible() && lyxrc.full_screen_menubar)
+                       current_view_->menuBar()->hide();
+       }
+
        if (cmd.origin() == FuncRequest::LYXSERVER)
                updateCurrentView(cmd, dr);
 }
@@ -2196,11 +2312,7 @@ docstring GuiApplication::viewStatusMessage()
 
 string GuiApplication::inputLanguageCode() const
 {
-#if (QT_VERSION < 0x050000)
-       QLocale loc = keyboardInputLocale();
-#else
        QLocale loc = inputMethod()->locale();
-#endif
        //LYXERR0("input lang = " << fromqstr(loc.name()));
        return loc.name() == "C" ? "en_US" : fromqstr(loc.name());
 }
@@ -2214,6 +2326,12 @@ void GuiApplication::onLocaleChanged()
 }
 
 
+void GuiApplication::onPaletteChanged()
+{
+       colorCache().setPalette(palette());
+}
+
+
 void GuiApplication::handleKeyFunc(FuncCode action)
 {
        char_type c = 0;
@@ -2256,7 +2374,8 @@ bool GuiApplication::queryKeySym(KeySymbol const & keysym,
                // seq has been reset at this point
                func = seq.addkey(keysym, NoModifier);
 
-       LYXERR(Debug::KEY, " Key (queried) [action=" << func.action() << "]["
+       LYXERR(Debug::KEY, " Key (queried) [action="
+              << lyxaction.getActionName(func.action()) << "]["
               << seq.print(KeySequence::Portable) << ']');
        return func.action() != LFUN_UNKNOWN_ACTION;
 }
@@ -2283,7 +2402,8 @@ void GuiApplication::processKeySym(KeySymbol const & keysym, KeyModifier state)
        d->cancel_meta_seq.reset();
 
        FuncRequest func = d->cancel_meta_seq.addkey(keysym, state);
-       LYXERR(Debug::KEY, "action first set to [" << func.action() << ']');
+       LYXERR(Debug::KEY, "action first set to ["
+              << lyxaction.getActionName(func.action()) << ']');
 
        // When not cancel or meta-fake, do the normal lookup.
        // Note how the meta_fake Mod1 bit is OR-ed in and reset afterwards.
@@ -2291,7 +2411,8 @@ void GuiApplication::processKeySym(KeySymbol const & keysym, KeyModifier state)
        if ((func.action() != LFUN_CANCEL) && (func.action() != LFUN_META_PREFIX)) {
                // remove Caps Lock and Mod2 as a modifiers
                func = d->keyseq.addkey(keysym, (state | d->meta_fake_bit));
-               LYXERR(Debug::KEY, "action now set to [" << func.action() << ']');
+               LYXERR(Debug::KEY, "action now set to ["
+                      << lyxaction.getActionName(func.action()) << ']');
        }
 
        // Don't remove this unless you know what you are doing.
@@ -2301,8 +2422,9 @@ void GuiApplication::processKeySym(KeySymbol const & keysym, KeyModifier state)
        if (func.action() == LFUN_NOACTION)
                func = FuncRequest(LFUN_COMMAND_PREFIX);
 
-       LYXERR(Debug::KEY, " Key [action=" << func.action() << "]["
-               << d->keyseq.print(KeySequence::Portable) << ']');
+       LYXERR(Debug::KEY, " Key [action="
+              << lyxaction.getActionName(func.action()) << "]["
+              << d->keyseq.print(KeySequence::Portable) << ']');
 
        // already here we know if it any point in going further
        // why not return already here if action == -1 and
@@ -2319,7 +2441,7 @@ void GuiApplication::processKeySym(KeySymbol const & keysym, KeyModifier state)
                // If addkey looked up a command and did not find further commands then
                // seq has been reset at this point
                func = d->keyseq.addkey(keysym, NoModifier);
-               LYXERR(Debug::KEY, "Action now " << func.action());
+               LYXERR(Debug::KEY, "Action now " << lyxaction.getActionName(func.action()));
        }
 
        if (func.action() == LFUN_UNKNOWN_ACTION) {
@@ -2488,7 +2610,7 @@ void GuiApplication::createView(QString const & geometry_arg, bool autoShow,
 
        if (autoShow) {
                view->show();
-               setActiveWindow(view);
+               view->activateWindow();
        }
 
        if (!geometry_arg.isEmpty()) {
@@ -2496,14 +2618,15 @@ void GuiApplication::createView(QString const & geometry_arg, bool autoShow,
                int x, y;
                int w, h;
                QChar sx, sy;
-               QRegExp re( "[=]*(?:([0-9]+)[xX]([0-9]+)){0,1}[ ]*(?:([+-][0-9]*)){0,1}(?:([+-][0-9]*)){0,1}" );
-               re.indexIn(geometry_arg);
-               w = re.cap(1).toInt();
-               h = re.cap(2).toInt();
-               x = re.cap(3).toInt();
-               y = re.cap(4).toInt();
-               sx = re.cap(3).isEmpty() ? '+' : re.cap(3).at(0);
-               sy = re.cap(4).isEmpty() ? '+' : re.cap(4).at(0);
+               QRegularExpression re( "[=]*(?:([0-9]+)[xX]([0-9]+)){0,1}[ ]*(?:([+-][0-9]*)){0,1}(?:([+-][0-9]*)){0,1}" );
+               QRegularExpressionMatch match = re.match(geometry_arg);
+               w = match.captured(1).toInt();
+               h = match.captured(2).toInt();
+               x = match.captured(3).toInt();
+               y = match.captured(4).toInt();
+               sx = match.captured(3).isEmpty() ? '+' : match.captured(3).at(0);
+               sy = match.captured(4).isEmpty() ? '+' : match.captured(4).at(0);
+
                // Set initial geometry such that we can get the frame size.
                view->setGeometry(x, y, w, h);
                int framewidth = view->geometry().x() - view->x();
@@ -2511,7 +2634,7 @@ void GuiApplication::createView(QString const & geometry_arg, bool autoShow,
                // Negative displacements must be interpreted as distances
                // from the right or bottom screen borders.
                if (sx == '-' || sy == '-') {
-                       QRect rec = QApplication::desktop()->screenGeometry();
+                       QRect rec = QGuiApplication::primaryScreen()->geometry();
                        if (sx == '-')
                                x += rec.width() - w - framewidth;
                        if (sy == '-')
@@ -2541,6 +2664,30 @@ bool GuiApplication::unhide(Buffer * buf)
 }
 
 
+QPixmap GuiApplication::getScaledPixmap(QString imagedir, QString name) const
+{
+       qreal dpr = 1.0;
+       // Consider device/pixel ratio (HiDPI)
+       if (currentView())
+               dpr = currentView()->devicePixelRatio();
+       // We render SVG directly for HiDPI scalability
+       QPixmap pm = getPixmap(imagedir, name, "svgz,png");
+       FileName fname = imageLibFileSearch(imagedir, name, "svgz,png");
+       QString fpath = toqstr(fname.absFileName());
+       if (!fpath.isEmpty() && !fpath.endsWith(".png")) {
+               QSvgRenderer svgRenderer(fpath);
+               if (svgRenderer.isValid()) {
+                       pm = QPixmap(pm.size() * dpr);
+                       pm.fill(Qt::transparent);
+                       QPainter painter(&pm);
+                       svgRenderer.render(&painter);
+                       pm.setDevicePixelRatio(dpr);
+               }
+       }
+       return pm;
+}
+
+
 Clipboard & GuiApplication::clipboard()
 {
        return d->clipboard_;
@@ -2583,6 +2730,18 @@ Menus & GuiApplication::menus()
 }
 
 
+bool GuiApplication::needsBackingStore() const
+{
+       /* Qt on macOS and Wayland does not respect the
+        * Qt::WA_OpaquePaintEvent attribute and resets the widget backing
+        * store at each update. Therefore, we use our own backing store
+        * in these two cases. It is also possible to force the use of the
+        * backing store for cases like x11 with transparent WM themes.
+        */
+       return platformName() == "cocoa" || platformName().contains("wayland");
+}
+
+
 QList<int> GuiApplication::viewIds() const
 {
        return d->views_.keys();
@@ -2623,7 +2782,11 @@ void GuiApplication::setGuiLanguage()
        // opposite. Therefore, long name should be used without truncation.
        // c.f. http://doc.trolltech.com/4.1/qtranslator.html#load
        if (!d->qt_trans_.load(language_name,
+#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
+                       QLibraryInfo::path(QLibraryInfo::TranslationsPath))) {
+#else
                        QLibraryInfo::location(QLibraryInfo::TranslationsPath))) {
+#endif
                LYXERR(Debug::LOCALE, "Could not find Qt translations for locale "
                        << language_name);
        } else {
@@ -2654,12 +2817,10 @@ void GuiApplication::execBatchCommands()
                return;
 
 #ifdef Q_OS_MAC
-#if QT_VERSION > 0x040600
        setAttribute(Qt::AA_MacDontSwapCtrlAndMeta,lyxrc.mac_dontswap_ctrl_meta);
-#endif
-#if QT_VERSION > 0x050100
+#  if QT_VERSION < 0x060000
        setAttribute(Qt::AA_UseHighDpiPixmaps,true);
-#endif
+#  endif
        // Create the global default menubar which is shown for the dialogs
        // and if no GuiView is visible.
        // This must be done after the session was recovered to know the "last files".
@@ -2678,8 +2839,10 @@ QAbstractItemModel * GuiApplication::languageModel()
 
        QStandardItemModel * lang_model = new QStandardItemModel(this);
        lang_model->insertColumns(0, 3);
-       QIcon speller(getPixmap("images/", "dialog-show_spellchecker", "svgz,png"));
-       QIcon saurus(getPixmap("images/", "thesaurus-entry", "svgz,png"));
+       QIcon speller(guiApp ? guiApp->getScaledPixmap("images/", "dialog-show_spellchecker")
+                         : getPixmap("images/", "dialog-show_spellchecker", "svgz,png"));
+       QIcon saurus(guiApp ? guiApp->getScaledPixmap("images/", "thesaurus-entry")
+                         : getPixmap("images/", "thesaurus-entry", "svgz,png"));
        Languages::const_iterator it = lyx::languages.begin();
        Languages::const_iterator end = lyx::languages.end();
        for (; it != end; ++it) {
@@ -2782,11 +2945,7 @@ namespace {
 
 QFont const GuiApplication::typewriterSystemFont()
 {
-#if QT_VERSION >= 0x050200
        QFont font = QFontDatabase::systemFont(QFontDatabase::FixedFont);
-#else
-       QFont font("monospace");
-#endif
        if (!isFixedPitch(font)) {
                // try to enforce a real monospaced font
                font.setStyleHint(QFont::Monospace);
@@ -2825,15 +2984,11 @@ bool GuiApplication::event(QEvent * e)
                e->accept();
                return true;
        }
-#if (QT_VERSION < 0x050000)
-       // Qt5 uses a signal for that, see above.
-       case QEvent::KeyboardLayoutChange:
-               //LYXERR0("keyboard change");
-               if (currentView() && currentView()->currentBufferView())
-                       currentView()->currentBufferView()->cursor().setLanguageFromInput();
-               e->accept();
-               return true;
-#endif
+       case QEvent::ApplicationPaletteChange: {
+               // runtime switch from/to dark mode
+               onPaletteChanged();
+               return QApplication::event(e);
+       }
        default:
                return QApplication::event(e);
        }
@@ -2894,6 +3049,11 @@ bool GuiApplication::notify(QObject * receiver, QEvent * event)
        return false;
 }
 
+bool GuiApplication::isInDarkMode()
+{
+       return colorCache().isDarkMode();
+}
+
 
 bool GuiApplication::getRgbColor(ColorCode col, RGBColor & rgbcol)
 {
@@ -3279,6 +3439,34 @@ void GuiApplication::onLastWindowClosed()
 }
 
 
+void GuiApplication::onApplicationStateChanged(Qt::ApplicationState state)
+{
+       std::string name = "unknown";
+       switch (state) {
+       case Qt::ApplicationSuspended:
+               name = "ApplicationSuspended";
+               break;
+       case Qt::ApplicationHidden:
+               name = "ApplicationHidden";
+               break;
+       case Qt::ApplicationInactive:
+               name = "ApplicationInactive";
+               break;
+       case Qt::ApplicationActive:
+               name = "ApplicationActive";
+               /// The Dock icon click produces 2 sequential QEvent::ApplicationStateChangeEvent events.
+               /// cmd+tab only one QEvent::ApplicationStateChangeEvent event
+               if (d->views_.empty() && d->last_state_ == state) {
+                       LYXERR(Debug::GUI, "Open new window...");
+                       createView();
+               }
+               break;
+       }
+       LYXERR(Debug::GUI, "onApplicationStateChanged..." << name);
+       d->last_state_ = state;
+}
+
+
 void GuiApplication::startLongOperation() {
        d->key_checker_.start();
 }
@@ -3303,58 +3491,9 @@ bool GuiApplication::longOperationStarted() {
 //
 // X11 specific stuff goes here...
 
-#ifdef Q_WS_X11
-bool GuiApplication::x11EventFilter(XEvent * xev)
-{
-       if (!current_view_)
-               return false;
-
-       switch (xev->type) {
-       case SelectionRequest: {
-               if (xev->xselectionrequest.selection != XA_PRIMARY)
-                       break;
-               LYXERR(Debug::SELECTION, "X requested selection.");
-               BufferView * bv = current_view_->currentBufferView();
-               if (bv) {
-                       docstring const sel = bv->requestSelection();
-                       if (!sel.empty()) {
-                               d->selection_.put(sel);
-                               // Refresh the selection request timestamp.
-                               // We have to do this by ourselves as Qt seems
-                               // not doing that, maybe because of our
-                               // "persistent selection" implementation
-                               // (see comments in GuiSelection.cpp).
-                               XSelectionEvent nev;
-                               nev.type = SelectionNotify;
-                               nev.display = xev->xselectionrequest.display;
-                               nev.requestor = xev->xselectionrequest.requestor;
-                               nev.selection = xev->xselectionrequest.selection;
-                               nev.target = xev->xselectionrequest.target;
-                               nev.property = 0L; // None
-                               nev.time = CurrentTime;
-                               XSendEvent(QX11Info::display(),
-                                       nev.requestor, False, 0,
-                                       reinterpret_cast<XEvent *>(&nev));
-                               return true;
-                       }
-               }
-               break;
-       }
-       case SelectionClear: {
-               if (xev->xselectionclear.selection != XA_PRIMARY)
-                       break;
-               LYXERR(Debug::SELECTION, "Lost selection.");
-               BufferView * bv = current_view_->currentBufferView();
-               if (bv)
-                       bv->clearSelection();
-               break;
-       }
-       }
-       return false;
-}
-#elif defined(QPA_XCB)
+#if defined(QPA_XCB)
 bool GuiApplication::nativeEventFilter(const QByteArray & eventType,
-                                      void * message, long *)
+                                      void * message, QINTPTR *)
 {
        if (!current_view_ || eventType != "xcb_generic_event_t")
                return false;