]> git.lyx.org Git - features.git/blobdiff - src/frontends/qt/GuiApplication.cpp
Fix broken Apple speller interface
[features.git] / src / frontends / qt / GuiApplication.cpp
index 45e2d2ba9510a86c4fb70d5639f38d58e45c5ab0..ff33065fe34fe313fd051ef1d21227d614f02ec2 100644 (file)
@@ -89,7 +89,9 @@
 #include <QByteArray>
 #include <QBitmap>
 #include <QDateTime>
+#if QT_VERSION < 0x060000
 #include <QDesktopWidget>
+#endif
 #include <QEvent>
 #include <QFileOpenEvent>
 #include <QFileInfo>
 #include <QObject>
 #include <QPainter>
 #include <QPixmap>
+#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
+#include <QRandomGenerator>
+#endif
 #include <QRegExp>
 #include <QSessionManager>
 #include <QSettings>
 #if (QT_VERSION < 0x050000)
 #include <QWindowsMime>
 #define QWINDOWSMIME QWindowsMime
+#define QVARIANTTYPE QVariant::Type
+#elif (QT_VERSION >= 0x060000)
+#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;
 #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
 
@@ -182,6 +197,20 @@ frontend::Application * createApplication(int & argc, char * argv[])
 #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)
@@ -496,6 +525,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;
@@ -509,9 +579,10 @@ IconInfo iconInfo(FuncRequest const & f, bool unknown, bool rtl)
                name.replace(' ', '_');
                name.replace(';', '_');
                name.replace('\\', "backslash");
-               // avoid duplication for these
-               name.replace("dialog-toggle", "dialog-show");
                names << name;
+               QString alias = getAlias(name);
+               if (alias != name)
+                       names << alias;
 
                // then special default icon for some lfuns
                switch (f.action()) {
@@ -543,16 +614,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";
@@ -745,7 +819,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
@@ -852,7 +926,7 @@ public:
        }
 
        QVariant convertToMime(QString const & mimetype, IDataObject * pDataObj,
-               QVariant::Type /*preferredType*/) const override
+               QVARIANTTYPE /*preferredType*/) const override
        {
                QByteArray data;
                if (!canConvertToMime(mimetype, pDataObj))
@@ -975,11 +1049,25 @@ struct GuiApplication::Private
        #if defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)
                /// WMF Mime handler for Windows clipboard.
                wmf_mime_ = new QWindowsMimeMetafile;
+       #if (QT_VERSION >= 0x060000)
+               win_app_ = dynamic_cast<QWindowsApplication *>
+                       (QGuiApplicationPrivate::platformIntegration());
+               win_app_->registerMime(wmf_mime_);
+       #endif
        #endif
        #endif
                initKeySequences(&theTopLevelKeymap());
        }
 
+       #if (QT_VERSION >= 0x060000)
+       #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);
@@ -1041,7 +1129,7 @@ struct GuiApplication::Private
        Qt::ApplicationState last_state_;
 #endif
 
-#ifdef Q_OS_MAC
+#if defined(Q_OS_MAC) && (QT_VERSION < 0x060000)
        /// Linkback mime handler for MacOSX.
        QMacPasteboardMimeGraphics mac_pasteboard_mime_;
 #endif
@@ -1050,6 +1138,9 @@ struct GuiApplication::Private
 #if defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN)
        /// WMF Mime handler for Windows clipboard.
        QWindowsMimeMetafile * wmf_mime_;
+#if (QT_VERSION >= 0x060000)
+       QWindowsApplication * win_app_;
+#endif
 #endif
 #endif
 
@@ -1077,7 +1168,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 >= 0x050100 && QT_VERSION < 0x060000
        QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
 #endif
 
@@ -1085,8 +1176,11 @@ GuiApplication::GuiApplication(int & argc, char ** argv)
        setDesktopFileName(lyx_package);
 #endif
 
-       // FIXME Deprecated. Should use QRandomGenerator since 5.10
+#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_);
@@ -1131,7 +1225,7 @@ GuiApplication::GuiApplication(int & argc, char ** argv)
 
        // needs to be done before reading lyxrc
        QWidget w;
-       lyxrc.dpi = (w.physicalDpiX() + w.physicalDpiY()) / 2;
+       lyxrc.dpi = (w.logicalDpiX() + w.logicalDpiY()) / 2;
 
        guiApp = this;
 
@@ -1389,10 +1483,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;
@@ -1538,7 +1639,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();
                }
@@ -1635,8 +1736,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();
@@ -1650,6 +1753,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"
@@ -2275,6 +2381,12 @@ void GuiApplication::onLocaleChanged()
 }
 
 
+void GuiApplication::onPaletteChanged()
+{
+       colorCache().setPalette(palette());
+}
+
+
 void GuiApplication::handleKeyFunc(FuncCode action)
 {
        char_type c = 0;
@@ -2317,7 +2429,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;
 }
@@ -2344,7 +2457,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.
@@ -2352,7 +2466,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.
@@ -2362,8 +2477,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
@@ -2380,7 +2496,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) {
@@ -2557,6 +2673,7 @@ void GuiApplication::createView(QString const & geometry_arg, bool autoShow,
                int x, y;
                int w, h;
                QChar sx, sy;
+#if QT_VERSION < 0x060000
                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();
@@ -2565,6 +2682,16 @@ void GuiApplication::createView(QString const & geometry_arg, bool autoShow,
                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);
+#else
+               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);
+#endif
                // Set initial geometry such that we can get the frame size.
                view->setGeometry(x, y, w, h);
                int framewidth = view->geometry().x() - view->x();
@@ -2572,7 +2699,11 @@ 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 == '-') {
+#if QT_VERSION < 0x060000
                        QRect rec = QApplication::desktop()->screenGeometry();
+#else
+                       QRect rec = QGuiApplication::primaryScreen()->geometry();
+#endif
                        if (sx == '-')
                                x += rec.width() - w - framewidth;
                        if (sy == '-')
@@ -2644,6 +2775,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();
@@ -2684,7 +2827,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 {
@@ -2718,7 +2865,7 @@ void GuiApplication::execBatchCommands()
 #if QT_VERSION > 0x040600
        setAttribute(Qt::AA_MacDontSwapCtrlAndMeta,lyxrc.mac_dontswap_ctrl_meta);
 #endif
-#if QT_VERSION > 0x050100
+#if QT_VERSION >= 0x050000 && QT_VERSION < 0x060000
        setAttribute(Qt::AA_UseHighDpiPixmaps,true);
 #endif
        // Create the global default menubar which is shown for the dialogs
@@ -2895,6 +3042,11 @@ bool GuiApplication::event(QEvent * e)
                e->accept();
                return true;
 #endif
+       case QEvent::ApplicationPaletteChange: {
+               // runtime switch from/to dark mode
+               onPaletteChanged();
+               return QApplication::event(e);
+       }
        default:
                return QApplication::event(e);
        }