diff --git a/src/frontends/qt/Dialog.cpp b/src/frontends/qt/Dialog.cpp index 783f5eb22c..35c40d041b 100644 --- a/src/frontends/qt/Dialog.cpp +++ b/src/frontends/qt/Dialog.cpp @@ -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(); diff --git a/src/frontends/qt/Dialog.h b/src/frontends/qt/Dialog.h index 6968aea803..928513c3fe 100644 --- a/src/frontends/qt/Dialog.h +++ b/src/frontends/qt/Dialog.h @@ -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(); diff --git a/src/frontends/qt/FindAndReplace.cpp b/src/frontends/qt/FindAndReplace.cpp index b53c520974..d61636af62 100644 --- a/src/frontends/qt/FindAndReplace.cpp +++ b/src/frontends/qt/FindAndReplace.cpp @@ -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)); diff --git a/src/frontends/qt/GuiView.cpp b/src/frontends/qt/GuiView.cpp index 6f15d73626..c44fbb3ed1 100644 --- a/src/frontends/qt/GuiView.cpp +++ b/src/frontends/qt/GuiView.cpp @@ -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 diff --git a/src/frontends/qt/GuiView.h b/src/frontends/qt/GuiView.h index ac6d293aaf..18a5672239 100644 --- a/src/frontends/qt/GuiView.h +++ b/src/frontends/qt/GuiView.h @@ -131,6 +131,7 @@ public: /// void setFocus(); + void setFocus(Qt::FocusReason reason); bool hasFocus() const; /// diff --git a/src/frontends/qt/GuiWorkArea.cpp b/src/frontends/qt/GuiWorkArea.cpp index a4b874097c..4941d047cf 100644 --- a/src/frontends/qt/GuiWorkArea.cpp +++ b/src/frontends/qt/GuiWorkArea.cpp @@ -223,8 +223,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() ); } @@ -283,6 +289,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 @@ -687,12 +696,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); } @@ -1122,28 +1141,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. @@ -1783,7 +1859,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,13 @@ 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; } @@ -1833,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(); diff --git a/src/frontends/qt/GuiWorkArea.h b/src/frontends/qt/GuiWorkArea.h index 6f24ff313c..148b79b73a 100644 --- a/src/frontends/qt/GuiWorkArea.h +++ b/src/frontends/qt/GuiWorkArea.h @@ -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); diff --git a/src/frontends/qt/GuiWorkArea_Private.h b/src/frontends/qt/GuiWorkArea_Private.h index 9da4cfaead..42dcef836c 100644 --- a/src/frontends/qt/GuiWorkArea_Private.h +++ b/src/frontends/qt/GuiWorkArea_Private.h @@ -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 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