Fix wrong position of conversion windows of the input method #11723, #13054

This commit is contained in:
Koji Yokota 2024-03-29 22:09:36 +09:00
parent 0ecfb01a79
commit 76dbb9dc40
8 changed files with 162 additions and 35 deletions

View 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()) if (isBufferDependent() && !isBufferAvailable())
return; return;
@ -131,7 +131,7 @@ void Dialog::showData(string const & data)
return; return;
} }
showView(); showView(reason);
} }
@ -169,25 +169,26 @@ void Dialog::prepareView()
} }
void Dialog::showView() void Dialog::showView(Qt::FocusReason reason)
{ {
prepareView(); prepareView();
QWidget * w = asQWidget(); QWidget * w = asQWidget();
if (!w->isVisible()) if (!w->isVisible()) {
w->show(); w->setFocus(reason);
w->show();
}
w->raise(); w->raise();
w->activateWindow(); w->activateWindow();
if (wantInitialFocus()) if (wantInitialFocus())
w->setFocus(); w->setFocus(reason);
else { else {
lyxview_.raise(); lyxview_.raise();
lyxview_.activateWindow(); lyxview_.activateWindow();
lyxview_.setFocus(); lyxview_.setFocus(reason);
} }
} }
void Dialog::hideView() void Dialog::hideView()
{ {
QWidget * w = asQWidget(); QWidget * w = asQWidget();

View File

@ -88,7 +88,18 @@ public:
//@{ //@{
/// \param data is a string encoding of the data to be displayed. /// \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. /// 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. /// \return inset at current cursor location.
@ -121,7 +132,14 @@ public:
void hideView(); void hideView();
/// Prepare dialog and display it. /// 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. /// Prepare dialog before view.
void prepareView(); void prepareView();

View File

@ -122,7 +122,7 @@ bool FindAndReplaceWidget::eventFilter(QObject * obj, QEvent * event)
FuncRequest func = theTopLevelKeymap().getBinding(seq); FuncRequest func = theTopLevelKeymap().getBinding(seq);
if (!getStatus(func).enabled()) { if (!getStatus(func).enabled()) {
LYXERR(Debug::FINDVERBOSE, "Focusing replace WA"); LYXERR(Debug::FINDVERBOSE, "Focusing replace WA");
replace_work_area_->setFocus(); replace_work_area_->setFocus(Qt::TabFocusReason);
LYXERR(Debug::FINDVERBOSE, "Selecting entire replace buffer"); LYXERR(Debug::FINDVERBOSE, "Selecting entire replace buffer");
dispatch(FuncRequest(LFUN_BUFFER_BEGIN)); dispatch(FuncRequest(LFUN_BUFFER_BEGIN));
dispatch(FuncRequest(LFUN_BUFFER_END_SELECT)); dispatch(FuncRequest(LFUN_BUFFER_END_SELECT));
@ -138,7 +138,7 @@ bool FindAndReplaceWidget::eventFilter(QObject * obj, QEvent * event)
FuncRequest func = theTopLevelKeymap().getBinding(seq); FuncRequest func = theTopLevelKeymap().getBinding(seq);
if (!getStatus(func).enabled()) { if (!getStatus(func).enabled()) {
LYXERR(Debug::FINDVERBOSE, "Focusing find WA"); LYXERR(Debug::FINDVERBOSE, "Focusing find WA");
find_work_area_->setFocus(); find_work_area_->setFocus(Qt::BacktabFocusReason);
LYXERR(Debug::FINDVERBOSE, "Selecting entire find buffer"); LYXERR(Debug::FINDVERBOSE, "Selecting entire find buffer");
dispatch(FuncRequest(LFUN_BUFFER_BEGIN)); dispatch(FuncRequest(LFUN_BUFFER_BEGIN));
dispatch(FuncRequest(LFUN_BUFFER_END_SELECT)); dispatch(FuncRequest(LFUN_BUFFER_END_SELECT));

View 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 bool GuiView::hasFocus() const
{ {
if (currentWorkArea()) if (currentWorkArea())
@ -1234,11 +1241,11 @@ void GuiView::focusInEvent(QFocusEvent * e)
// Make sure guiApp points to the correct view. // Make sure guiApp points to the correct view.
guiApp->setCurrentView(this); guiApp->setCurrentView(this);
if (currentWorkArea()) if (currentWorkArea())
currentWorkArea()->setFocus(); currentWorkArea()->setFocus(e->reason());
else if (currentMainWorkArea()) else if (currentMainWorkArea())
currentMainWorkArea()->setFocus(); currentMainWorkArea()->setFocus(e->reason());
else else
d.bg_widget_->setFocus(); d.bg_widget_->setFocus(e->reason());
} }
@ -1571,6 +1578,9 @@ void GuiView::on_currentWorkAreaChanged(GuiWorkArea * wa)
connectBufferView(wa->bufferView()); connectBufferView(wa->bufferView());
connectBuffer(wa->bufferView().buffer()); connectBuffer(wa->bufferView().buffer());
d.current_work_area_ = wa; 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 *)), QObject::connect(wa, SIGNAL(titleChanged(GuiWorkArea *)),
this, SLOT(updateWindowTitle(GuiWorkArea *))); this, SLOT(updateWindowTitle(GuiWorkArea *)));
QObject::connect(wa, SIGNAL(busy(bool)), QObject::connect(wa, SIGNAL(busy(bool)),
@ -1736,7 +1746,7 @@ bool GuiView::event(QEvent * e)
case QEvent::WindowActivate: { case QEvent::WindowActivate: {
GuiView * old_view = guiApp->currentView(); GuiView * old_view = guiApp->currentView();
if (this == old_view) { if (this == old_view) {
setFocus(); setFocus(Qt::ActiveWindowFocusReason);
return QMainWindow::event(e); return QMainWindow::event(e);
} }
if (old_view && old_view->currentBufferView()) { if (old_view && old_view->currentBufferView()) {
@ -1749,7 +1759,7 @@ bool GuiView::event(QEvent * e)
on_currentWorkAreaChanged(d.current_work_area_); on_currentWorkAreaChanged(d.current_work_area_);
else else
resetWindowTitle(); resetWindowTitle();
setFocus(); setFocus(Qt::ActiveWindowFocusReason);
return QMainWindow::event(e); return QMainWindow::event(e);
} }
@ -4836,6 +4846,7 @@ void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
case LFUN_DIALOG_HIDE: { case LFUN_DIALOG_HIDE: {
guiApp->hideDialogs(to_utf8(cmd.argument()), nullptr); guiApp->hideDialogs(to_utf8(cmd.argument()), nullptr);
setFocus(Qt::PopupFocusReason);
break; break;
} }
@ -5361,7 +5372,10 @@ void GuiView::doShowDialog(QString const & qname, QString const & qdata,
Dialog * dialog = findOrBuild(name, false); Dialog * dialog = findOrBuild(name, false);
if (dialog) { if (dialog) {
bool const visible = dialog->isVisibleView(); bool const visible = dialog->isVisibleView();
dialog->showData(sdata); if (name == "findreplaceadv")
dialog->showData(sdata, Qt::OtherFocusReason);
else
dialog->showData(sdata);
if (currentBufferView()) if (currentBufferView())
currentBufferView()->editInset(name, inset); currentBufferView()->editInset(name, inset);
// We only set the focus to the new dialog if it was not yet // We only set the focus to the new dialog if it was not yet

View File

@ -131,6 +131,7 @@ public:
/// ///
void setFocus(); void setFocus();
void setFocus(Qt::FocusReason reason);
bool hasFocus() const; bool hasFocus() const;
/// ///

View File

@ -223,8 +223,14 @@ void GuiWorkArea::init()
// Enables input methods for asian languages. // Enables input methods for asian languages.
// Must be set when creating custom text editing widgets. // Must be set when creating custom text editing widgets.
setAttribute(Qt::WA_InputMethodEnabled, true); 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) void GuiWorkArea::setFullScreen(bool full_screen)
{ {
d->buffer_view_->setFullScreen(full_screen); d->buffer_view_->setFullScreen(full_screen);
queryInputItemTransform();
if (full_screen && lyxrc.full_screen_scrollbar) if (full_screen && lyxrc.full_screen_scrollbar)
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
else else
@ -687,12 +696,22 @@ void GuiWorkArea::contextMenuEvent(QContextMenuEvent * e)
void GuiWorkArea::focusInEvent(QFocusEvent * 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) { if (d->lyx_view_->currentWorkArea() != this) {
d->lyx_view_->setCurrentWorkArea(this); d->lyx_view_->setCurrentWorkArea(this);
d->lyx_view_->currentWorkArea()->bufferView().buffer().updateBuffer(); 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(); startBlinkingCaret();
QAbstractScrollArea::focusInEvent(e); 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) 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; return;
// FIXME: shall we use real_current_font here? (see #10478) // lower margin of the preedit area to separate the candidate window
FontInfo const font = buffer_view_->cursor().getFont().fontInfo(); // report to IM the height of preedit rectangle larger than the actual by
FontMetrics const & fm = theFontMetrics(font); // 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; Point point;
Dimension dim; Dimension dim;
buffer_view_->caretPosAndDim(point, dim); buffer_view_->caretPosAndDim(point, dim);
int cur_x = point.x_ - dim.width(); int cur_x = point.x_ - dim.width();
int cur_y = point.y_ + dim.height(); 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 if (preedit_string_.empty()) {
// preedit_lower_margin so that the conversion suggestion window does not // Chinese input methods may exit here just obtaining im_cursor_rect
// hide the underline of the preedit text im_cursor_rect_ =
int preedit_lower_margin = 3; QRectF(cur_x, cur_y - dim.height(), 1, dim.height() + preedit_lower_margin);
// reset item transformation since it gets wrong after the item get im_->update(Qt::ImCursorRectangle);
// lost and regain focus. return;
im_->setInputItemTransform(im_item_trans_); }
// 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 // force fulldraw to remove previous paint remaining on screen
// FIXME: This is costly to do
buffer_view_->processUpdateFlags(Update::ForceDraw); buffer_view_->processUpdateFlags(Update::ForceDraw);
// get attributes of input method cursor. // get attributes of input method cursor.
@ -1783,7 +1859,7 @@ bool TabWorkArea::setCurrentWorkArea(GuiWorkArea * work_area)
else else
// Switch to the work area. // Switch to the work area.
setCurrentIndex(index); setCurrentIndex(index);
work_area->setFocus(); work_area->setFocus(Qt::OtherFocusReason);
return true; return true;
} }
@ -1809,6 +1885,13 @@ GuiWorkArea * TabWorkArea::addWorkArea(Buffer & buffer, GuiView & view)
updateTabTexts(); updateTabTexts();
// obtain new input item coordinates in the new and old work areas
wa->queryInputItemTransform();
if (currentWorkArea())
currentWorkArea()->queryInputItemTransform();
view.setBusy(false);
return wa; return wa;
} }
@ -1833,6 +1916,7 @@ bool TabWorkArea::removeWorkArea(GuiWorkArea * work_area)
else else
// Show tabbar only if there's more than one tab. // Show tabbar only if there's more than one tab.
showBar(count() > 1); showBar(count() > 1);
currentWorkArea()->queryInputItemTransform();
} else } else
lastWorkAreaRemoved(); lastWorkAreaRemoved();

View File

@ -67,6 +67,8 @@ public:
/// return true if the key is part of a shortcut /// return true if the key is part of a shortcut
bool queryKeySym(KeySymbol const & key, KeyModifier mod) const; bool queryKeySym(KeySymbol const & key, KeyModifier mod) const;
/// Ask relative position of input item coordinates against the main coordinates
void queryInputItemTransform();
bool inDialogMode() const; bool inDialogMode() const;
void setDialogMode(bool mode); void setDialogMode(bool mode);

View File

@ -100,6 +100,9 @@ struct GuiWorkArea::Private
/// Change the cursor when the mouse hovers over a clickable inset /// Change the cursor when the mouse hovers over a clickable inset
void updateCursorShape(); void updateCursorShape();
/// Restore coordinate transformation information
void resetInputItemTransform();
/// Paint preedit text provided by the platform input method
void paintPreeditText(GuiPainter & pain); void paintPreeditText(GuiPainter & pain);
/// Prepare screen for next painting /// Prepare screen for next painting
@ -136,6 +139,7 @@ struct GuiWorkArea::Private
/// ///
bool need_resize_ = false; bool need_resize_ = false;
/// provides access to the platform input method
QInputMethod * im_ = QGuiApplication::inputMethod(); QInputMethod * im_ = QGuiApplication::inputMethod();
/// the current preedit text of the input method /// the current preedit text of the input method
docstring preedit_string_; docstring preedit_string_;
@ -145,7 +149,10 @@ struct GuiWorkArea::Private
QList<QInputMethodEvent::Attribute> preedit_attr_; QList<QInputMethodEvent::Attribute> preedit_attr_;
QRectF im_cursor_rect_; QRectF im_cursor_rect_;
QRectF im_anchor_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 /// Ratio between physical pixels and device-independent pixels
/// We save the last used value to detect changes of the /// We save the last used value to detect changes of the