]> git.lyx.org Git - lyx.git/commitdiff
Fix wrong position of conversion windows of the input method #11723, #13054
authorKoji Yokota <yokota@lyx.org>
Fri, 29 Mar 2024 13:09:36 +0000 (22:09 +0900)
committerKoji Yokota <yokota@lyx.org>
Fri, 12 Apr 2024 12:22:31 +0000 (21:22 +0900)
src/frontends/qt/Dialog.cpp
src/frontends/qt/Dialog.h
src/frontends/qt/FindAndReplace.cpp
src/frontends/qt/GuiView.cpp
src/frontends/qt/GuiView.h
src/frontends/qt/GuiWorkArea.cpp
src/frontends/qt/GuiWorkArea.h
src/frontends/qt/GuiWorkArea_Private.h

index 783f5eb22c86c6cf8e41d1a1bc53b95a4523d44f..35c40d041bea030da45a6454068ec67a20530ed3 100644 (file)
@@ -120,7 +120,7 @@ Buffer const & Dialog::documentBuffer() const
 }
 
 
-void Dialog::showData(string const & data)
+void Dialog::showData(string const & data, Qt::FocusReason reason)
 {
        if (isBufferDependent() && !isBufferAvailable())
                return;
@@ -131,7 +131,7 @@ void Dialog::showData(string const & data)
                return;
        }
 
-       showView();
+       showView(reason);
 }
 
 
@@ -169,25 +169,26 @@ void Dialog::prepareView()
 }
 
 
-void Dialog::showView()
+void Dialog::showView(Qt::FocusReason reason)
 {
        prepareView();
 
        QWidget * w = asQWidget();
-       if (!w->isVisible())
-               w->show();
+    if (!w->isVisible()) {
+        w->setFocus(reason);
+        w->show();
+    }
        w->raise();
        w->activateWindow();
        if (wantInitialFocus())
-               w->setFocus();
+               w->setFocus(reason);
        else {
                lyxview_.raise();
                lyxview_.activateWindow();
-               lyxview_.setFocus();
+               lyxview_.setFocus(reason);
        }
 }
 
-
 void Dialog::hideView()
 {
        QWidget * w = asQWidget();
index 6968aea803d299230b2f4fa3c93fb7fe33676fdc..928513c3fe04c3c4190c0b686889d111b2e13bd2 100644 (file)
@@ -88,7 +88,18 @@ public:
        //@{
        /// \param data is a string encoding of the data to be displayed.
        /// It is passed to the Controller to be translated into a usable form.
-       virtual void showData(std::string const & data);
+    virtual void showData(std::string const & data)
+    {
+        return showData(data, Qt::PopupFocusReason);
+    }
+
+    /// \param data is a string encoding of the data to be displayed.
+    /// It is passed to the Controller to be translated into a usable form.
+    /// \param reason provides the focus reason of the dialog.
+    /// E.g. dialog "findreplaceadv" requires special treatment after
+    /// obtaining focus in terms of the input method item transformation,
+    /// so should be marked as reason = Qt::OtherFocusReason.
+    virtual void showData(std::string const & data, Qt::FocusReason reason);
        //@}
 
        /// \return inset at current cursor location.
@@ -121,7 +132,14 @@ public:
        void hideView();
 
        /// Prepare dialog and display it.
-       void showView();
+    void showView() { return showView(Qt::PopupFocusReason); }
+
+    /// Prepare dialog and display it.
+    /// \param reason provides the focus reason of the dialog.
+    /// E.g. dialog "findreplaceadv" requires special treatment after
+    /// obtaining focus in terms of the input method item transformation,
+    /// so should be marked as reason = Qt::OtherFocusReason.
+    void showView(Qt::FocusReason reason);
 
        /// Prepare dialog before view.
        void prepareView();
index b53c520974bf8c6e7b88f10b42ee44ac1e20a253..d61636af62ca123447fe1015c3d74849a7d868df 100644 (file)
@@ -122,7 +122,7 @@ bool FindAndReplaceWidget::eventFilter(QObject * obj, QEvent * event)
                        FuncRequest func = theTopLevelKeymap().getBinding(seq);
                        if (!getStatus(func).enabled()) {
                                LYXERR(Debug::FINDVERBOSE, "Focusing replace WA");
-                               replace_work_area_->setFocus();
+                               replace_work_area_->setFocus(Qt::TabFocusReason);
                                LYXERR(Debug::FINDVERBOSE, "Selecting entire replace buffer");
                                dispatch(FuncRequest(LFUN_BUFFER_BEGIN));
                                dispatch(FuncRequest(LFUN_BUFFER_END_SELECT));
@@ -138,7 +138,7 @@ bool FindAndReplaceWidget::eventFilter(QObject * obj, QEvent * event)
                        FuncRequest func = theTopLevelKeymap().getBinding(seq);
                        if (!getStatus(func).enabled()) {
                                LYXERR(Debug::FINDVERBOSE, "Focusing find WA");
-                               find_work_area_->setFocus();
+                               find_work_area_->setFocus(Qt::BacktabFocusReason);
                                LYXERR(Debug::FINDVERBOSE, "Selecting entire find buffer");
                                dispatch(FuncRequest(LFUN_BUFFER_BEGIN));
                                dispatch(FuncRequest(LFUN_BUFFER_END_SELECT));
index 6f15d736266572070ccb50190efd567c43935fdb..c44fbb3ed14bb877cfb51c69d7d9c73100f160ac 100644 (file)
@@ -1217,6 +1217,13 @@ void GuiView::setFocus()
 }
 
 
+void GuiView::setFocus(Qt::FocusReason reason)
+{
+    LYXERR(Debug::DEBUG, "GuiView::setFocus()" << this << " reason = " << reason);
+    QMainWindow::setFocus(reason);
+}
+
+
 bool GuiView::hasFocus() const
 {
        if (currentWorkArea())
@@ -1234,11 +1241,11 @@ void GuiView::focusInEvent(QFocusEvent * e)
        // Make sure guiApp points to the correct view.
        guiApp->setCurrentView(this);
        if (currentWorkArea())
-               currentWorkArea()->setFocus();
+               currentWorkArea()->setFocus(e->reason());
        else if (currentMainWorkArea())
-               currentMainWorkArea()->setFocus();
+               currentMainWorkArea()->setFocus(e->reason());
        else
-               d.bg_widget_->setFocus();
+               d.bg_widget_->setFocus(e->reason());
 }
 
 
@@ -1571,6 +1578,9 @@ void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa)
        connectBufferView(wa->bufferView());
        connectBuffer(wa->bufferView().buffer());
        d.current_work_area_ = wa;
+    // The below specifies that the input method item transformation will
+    // not reset
+    wa->setFocus(Qt::OtherFocusReason);
        QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
                         this, SLOT(updateWindowTitle(GuiWorkArea *)));
        QObject::connect(wa, SIGNAL(busy(bool)),
@@ -1736,7 +1746,7 @@ bool GuiView::event(QEvent * e)
        case QEvent::WindowActivate: {
                GuiView * old_view = guiApp->currentView();
                if (this == old_view) {
-                       setFocus();
+                       setFocus(Qt::ActiveWindowFocusReason);
                        return QMainWindow::event(e);
                }
                if (old_view && old_view->currentBufferView()) {
@@ -1749,7 +1759,7 @@ bool GuiView::event(QEvent * e)
                        on_currentWorkAreaChanged(d.current_work_area_);
                else
                        resetWindowTitle();
-               setFocus();
+               setFocus(Qt::ActiveWindowFocusReason);
                return QMainWindow::event(e);
        }
 
@@ -4836,6 +4846,7 @@ void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
 
                case LFUN_DIALOG_HIDE: {
                        guiApp->hideDialogs(to_utf8(cmd.argument()), nullptr);
+            setFocus(Qt::PopupFocusReason);
                        break;
                }
 
@@ -5361,7 +5372,10 @@ void GuiView::doShowDialog(QString const & qname, QString const & qdata,
                Dialog * dialog = findOrBuild(name, false);
                if (dialog) {
                        bool const visible = dialog->isVisibleView();
-                       dialog->showData(sdata);
+                       if (name == "findreplaceadv")
+                               dialog->showData(sdata, Qt::OtherFocusReason);
+                       else
+                               dialog->showData(sdata);
                        if (currentBufferView())
                                currentBufferView()->editInset(name, inset);
                        // We only set the focus to the new dialog if it was not yet
index ac6d293aaf9f90c7e24c7519e5204802d7b4e10e..18a5672239db36e3d0cab268383cebca2437e9e6 100644 (file)
@@ -131,6 +131,7 @@ public:
 
        ///
        void setFocus();
+       void setFocus(Qt::FocusReason reason);
        bool hasFocus() const;
 
        ///
index de8241c21faa8c859fd5723426c758d9adb9d756..d860d29b60adb50d3cb72773fbf0ca2405daa7ed 100644 (file)
@@ -220,8 +220,14 @@ void GuiWorkArea::init()
        // Enables input methods for asian languages.
        // Must be set when creating custom text editing widgets.
        setAttribute(Qt::WA_InputMethodEnabled, true);
-       // obtain transformation information to reset it when LyX gets refocus
-       d->im_item_trans_ = d->im_->inputItemTransform();
+
+       // Initialize d->im_cursor_rect_
+       Point point;
+       Dimension dim;
+       d->buffer_view_->caretPosAndDim(point, dim);
+       int cur_x = point.x_ - dim.width();
+       int cur_y = point.y_ + dim.height();
+       d->im_cursor_rect_ = QRectF(cur_x, (cur_y - dim.height()) , 1, dim.height() );
 }
 
 
@@ -280,6 +286,9 @@ void GuiWorkArea::close()
 void GuiWorkArea::setFullScreen(bool full_screen)
 {
        d->buffer_view_->setFullScreen(full_screen);
+
+       queryInputItemTransform();
+
        if (full_screen && lyxrc.full_screen_scrollbar)
                setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
        else
@@ -684,12 +693,22 @@ void GuiWorkArea::contextMenuEvent(QContextMenuEvent * e)
 
 void GuiWorkArea::focusInEvent(QFocusEvent * e)
 {
-       LYXERR(Debug::DEBUG, "GuiWorkArea::focusInEvent(): " << this << endl);
+       LYXERR(Debug::DEBUG, "GuiWorkArea::focusInEvent(): " << this << " reason() = " << e->reason() << endl);
        if (d->lyx_view_->currentWorkArea() != this) {
                d->lyx_view_->setCurrentWorkArea(this);
                d->lyx_view_->currentWorkArea()->bufferView().buffer().updateBuffer();
        }
 
+       // needs to reset IM item coordinates when focus in from dialogs or other apps
+       if ((e->reason() == Qt::PopupFocusReason || e->reason() == Qt::ActiveWindowFocusReason) &&
+               !(this->inDialogMode())) {
+               // Switched from most of dialogs or other apps, and not on a dialog (e.g. findreplaceadv)
+               d->item_trans_needs_reset_ = true;
+       } else {
+               // Switched from advanced search dialog or else (e.g. mouse event)
+               d->item_trans_needs_reset_ = false;
+       }
+
        startBlinkingCaret();
        QAbstractScrollArea::focusInEvent(e);
 }
@@ -1119,28 +1138,85 @@ void GuiWorkArea::resizeEvent(QResizeEvent * ev)
 }
 
 
+void GuiWorkArea::queryInputItemTransform()
+{
+       LYXERR(
+                  Debug::DEBUG,
+                  "item_trans_ is aquired: dx() = " << d->item_trans_.dx() <<
+                  " -> " << d->im_->inputItemTransform().dx() <<
+                  ", dy() = " << d->item_trans_.dy() <<
+                  " -> " << d->im_->inputItemTransform().dy()
+                  );
+
+       d->item_trans_ = d->im_->inputItemTransform();
+}
+
+
+void GuiWorkArea::Private::resetInputItemTransform()
+{
+       if (item_trans_needs_reset_) {
+               LYXERR(
+                          Debug::DEBUG,
+                          "(" << this <<
+                          ") item_trans_ is reset: dx() = " << im_->inputItemTransform().dx() <<
+                          " -> " << item_trans_.dx() <<
+                          ", dy() = " << im_->inputItemTransform().dy() <<
+                          " -> " << item_trans_.dy()
+                          );
+               im_->setInputItemTransform(item_trans_);
+               item_trans_needs_reset_ = false;
+       }
+}
+
+
+//#define DEBUG_PREEDIT
+
 void GuiWorkArea::Private::paintPreeditText(GuiPainter & pain)
 {
-       if (preedit_string_.empty())
+#ifdef DEBUG_PREEDIT
+       // check the language that current input method uses
+       QLocale::Language lang = im_->locale().language();
+       if (lang != im_lang_) {
+               LYXERR0("QLocale = " << QLocale::languageToString(lang));
+               im_lang_ = lang;
+       }
+#endif
+               
+       // Chinese IM may want cursor position even when preedit string is empty
+       // such a case is handled below
+       if (preedit_string_.empty() && im_->locale().language() != QLocale::Chinese)
                return;
 
-       // FIXME: shall we use real_current_font here? (see #10478)
-       FontInfo const font = buffer_view_->cursor().getFont().fontInfo();
-       FontMetrics const & fm = theFontMetrics(font);
+       // lower margin of the preedit area to separate the candidate window
+       // report to IM the height of preedit rectangle larger than the actual by
+       // preedit_lower_margin so that the conversion suggestion window does not
+       // hide the underline of the preedit text
+       int preedit_lower_margin = 1;
+
        Point point;
        Dimension dim;
        buffer_view_->caretPosAndDim(point, dim);
        int cur_x = point.x_ - dim.width();
        int cur_y = point.y_ + dim.height();
-       // lower margin of the preedit area to separate the candidate window
-       // report to IM the height of preedit rectangle larger than the actual by
-       // preedit_lower_margin so that the conversion suggestion window does not
-       // hide the underline of the preedit text
-       int preedit_lower_margin = 3;
-       // reset item transformation since it gets wrong after the item get
-       // lost and regain focus.
-       im_->setInputItemTransform(im_item_trans_);
+
+       if (preedit_string_.empty()) {
+               // Chinese input methods may exit here just obtaining im_cursor_rect
+               im_cursor_rect_ =
+                       QRectF(cur_x, cur_y - dim.height(), 1, dim.height() + preedit_lower_margin);
+               im_->update(Qt::ImCursorRectangle);
+               return;
+       }
+
+       // reset item transformation since it can go wrong after the item gets
+       // lost and regains focus or after a new tab (dis)appears etc.
+       resetInputItemTransform();
+
+       // FIXME: shall we use real_current_font here? (see #10478)
+       FontInfo const font = buffer_view_->cursor().getFont().fontInfo();
+       FontMetrics const & fm = theFontMetrics(font);
+
        // force fulldraw to remove previous paint remaining on screen
+    // FIXME: This is costly to do
        buffer_view_->processUpdateFlags(Update::ForceDraw);
 
        // get attributes of input method cursor.
@@ -1782,7 +1858,7 @@ bool TabWorkArea::setCurrentWorkArea(GuiWorkArea * work_area)
        else
                // Switch to the work area.
                setCurrentIndex(index);
-       work_area->setFocus();
+       work_area->setFocus(Qt::OtherFocusReason);
 
        return true;
 }
@@ -1809,6 +1885,11 @@ GuiWorkArea * TabWorkArea::addWorkArea(Buffer & buffer, GuiView & view)
 
        updateTabTexts();
 
+       // obtain new input item coordinates in the new and old work areas
+       wa->queryInputItemTransform();
+       if (currentWorkArea())
+               currentWorkArea()->queryInputItemTransform();
+
        view.setBusy(false);
 
        return wa;
@@ -1835,6 +1916,7 @@ bool TabWorkArea::removeWorkArea(GuiWorkArea * work_area)
                else
                        // Show tabbar only if there's more than one tab.
                        showBar(count() > 1);
+               currentWorkArea()->queryInputItemTransform();
        } else
                lastWorkAreaRemoved();
 
index 6f24ff313c579b6aa4d81003772d670e6cacbe4e..148b79b73aaff257c908884c890166e4aa6befa8 100644 (file)
@@ -67,6 +67,8 @@ public:
 
        /// return true if the key is part of a shortcut
        bool queryKeySym(KeySymbol const & key, KeyModifier mod) const;
+       /// Ask relative position of input item coordinates against the main coordinates
+       void queryInputItemTransform();
 
        bool inDialogMode() const;
        void setDialogMode(bool mode);
index 9da4cfaead3bbfc4f5dac04477245ad23e15f945..42dcef836c89a8871517053ec122d9b635b8d904 100644 (file)
@@ -100,6 +100,9 @@ struct GuiWorkArea::Private
        /// Change the cursor when the mouse hovers over a clickable inset
        void updateCursorShape();
 
+       /// Restore coordinate transformation information
+       void resetInputItemTransform();
+       /// Paint preedit text provided by the platform input method
        void paintPreeditText(GuiPainter & pain);
 
        /// Prepare screen for next painting
@@ -136,6 +139,7 @@ struct GuiWorkArea::Private
        ///
        bool need_resize_ = false;
 
+       /// provides access to the platform input method
        QInputMethod * im_ = QGuiApplication::inputMethod();
        /// the current preedit text of the input method
        docstring preedit_string_;
@@ -145,7 +149,10 @@ struct GuiWorkArea::Private
        QList<QInputMethodEvent::Attribute> preedit_attr_;
        QRectF im_cursor_rect_;
        QRectF im_anchor_rect_;
-       QTransform im_item_trans_;
+       QTransform item_trans_;
+       bool item_trans_needs_reset_ = false;
+       /// for debug
+       QLocale::Language im_lang_;
 
        /// Ratio between physical pixels and device-independent pixels
        /// We save the last used value to detect changes of the