#include <QContextMenuEvent>
#include <QDrag>
#include <QHelpEvent>
+#include <QInputMethod>
#ifdef Q_OS_MAC
#include <QProxyStyle>
#endif
double GuiWorkArea::pixelRatio() const
{
- return qt_scale_factor * devicePixelRatio();
+#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0) && QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
+ return devicePixelRatioF();
+#else
+ return devicePixelRatio();
+#endif
}
});
d->resetScreen();
- // A mouse event will happen before the first paint event,
- // so make sure that the buffer view has an up to date metrics.
- d->buffer_view_->resize(viewport()->width(), viewport()->height());
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
setAcceptDrops(true);
// Enables input methods for asian languages.
// Must be set when creating custom text editing widgets.
setAttribute(Qt::WA_InputMethodEnabled, true);
+
+ // 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() );
}
void GuiWorkArea::setFullScreen(bool full_screen)
{
d->buffer_view_->setFullScreen(full_screen);
+
+ queryInputItemTransform();
+
if (full_screen && lyxrc.full_screen_scrollbar)
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
else
void GuiWorkArea::scheduleRedraw(bool update_metrics)
{
- if (!isVisible())
+ if (!isVisible() || view().busy())
// No need to redraw in this case.
return;
}
-void GuiWorkArea::Private::resizeBufferView()
+void GuiWorkArea::resizeBufferView()
{
// WARNING: Please don't put any code that will trigger a repaint here!
// We are already inside a paint event.
- p->stopBlinkingCaret();
+ stopBlinkingCaret();
// Warn our container (GuiView).
- p->busy(true);
+ busy(true);
- bool const caret_in_view = buffer_view_->caretInView();
- buffer_view_->resize(p->viewport()->width(), p->viewport()->height());
+ bool const caret_in_view = d->buffer_view_->caretInView();
+ d->buffer_view_->resize(viewport()->width(), viewport()->height());
if (caret_in_view)
- buffer_view_->showCursor();
- resetCaret();
+ d->buffer_view_->showCursor();
+ d->resetCaret();
// Update scrollbars which might have changed due different
// BufferView dimension. This is especially important when the
// BufferView goes from zero-size to the real-size for the first time,
// as the scrollbar parameters are then set for the first time.
- updateScrollbar();
+ d->updateScrollbar();
- need_resize_ = false;
- p->busy(false);
+ d->need_resize_ = false;
+ busy(false);
// Eventually, restart the caret after the resize event.
// We might be resizing even if the focus is on another widget so we only
// restart the caret if we have the focus.
- if (p->hasFocus())
- QTimer::singleShot(50, p, SLOT(startBlinkingCaret()));
+ if (hasFocus())
+ QTimer::singleShot(50, this, SLOT(startBlinkingCaret()));
}
++pos.rx();
}
}
+ if (e->reason() == QContextMenuEvent::Keyboard)
+ // Subtract the top margin, see #12811
+ pos.setY(pos.y() - d->buffer_view_->topMargin());
+
name = d->buffer_view_->contextMenu(pos.x(), pos.y());
}
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);
}
// Find the row at which we set the cursor.
RowList::const_iterator rit = pm.rows().begin();
RowList::const_iterator rlast = pm.rows().end();
- int yy = pm.position() - pm.ascent();
+ int yy = pm.top();
for (--rlast; rit != rlast; ++rit) {
int h = rit->height();
if ((up && yy + h > 0)
}
+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_;
+ int cur_x = point.x_ - dim.width();
int cur_y = point.y_ + dim.height();
+ 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.
// cursor_pos : cursor position in preedit string.
size_t cursor_pos = 0;
rLength = 0;
}
- int const right_margin = buffer_view_->rightMargin();
+ int const text_width = p->viewport()->width() - buffer_view_->rightMargin()
+ - buffer_view_->leftMargin();
Painter::preedit_style ps;
// Most often there would be only one line:
preedit_lines_ = 1;
ps = Painter::preedit_default;
// if we reached the right extremity of the screen, go to next line.
- if (cur_x + fm.width(typed_char) > p->viewport()->width() - right_margin) {
- cur_x = right_margin;
+ if (cur_x + fm.width(typed_char) > p->viewport()->width() - buffer_view_->rightMargin()) {
+ cur_x = buffer_view_->leftMargin();
cur_y += dim.height() + 1;
++preedit_lines_;
}
// FIXME: should be put out of the loop.
if (pos >= rStart
&& pos < rStart + rLength
- && !(cursor_pos < rLength && rLength == preedit_length))
+ && !(cursor_pos < rLength && rLength == preedit_length)) {
ps = Painter::preedit_selecting;
+ }
if (pos == cursor_pos
&& (cursor_pos < rLength && rLength == preedit_length))
// draw one character and update cur_x.
cur_x += pain.preeditText(cur_x, cur_y, typed_char, font, ps);
}
+
+ // the selection candidate window follows the position of Qt::ImCursorRectangle
+ // so we set it here in preparation for InputMethodQuery.
+ if(rLength > 0) {
+ // candidate selection mode
+ div_t mod_cur, mod_anc;
+ // width of preedit string in pixels
+ int preedit_width = fm.width(preedit_string_);
+ // calculate for modular so that its remainder and quotient become horizontal
+ // and vertical adjustment factor respectively
+ // FIXME: divider should be (text_width - font width of the end-of-line character)
+ // however, since it's very rare that preedit becomes so long that it goes over
+ // more than two lines, current error of position is more or less negligible
+ mod_cur = div(text_width + cur_x - (preedit_width - fm.width(preedit_string_.substr(0, rStart))),
+ text_width);
+ mod_anc = div(text_width + cur_x - (preedit_width - fm.width(preedit_string_.substr(0, rStart + rLength))),
+ text_width);
+ // Obtain cursor and anchor rectangles to bind starting and ending points of selection.
+ // Since the remainder of div moves from positive to negative as the line becomes longer
+ // while its quotient repeats zero twice, we need to take it into account by conditioning.
+ if (mod_cur.rem >= 0)
+ im_cursor_rect_ = QRectF(mod_cur.rem,
+ (cur_y - dim.height()) + (dim.height() + 1) * (mod_cur.quot - 1), 1,
+ dim.height() + preedit_lower_margin);
+ else
+ im_cursor_rect_ = QRectF(text_width + mod_cur.rem,
+ (cur_y - dim.height()) + (dim.height() + 1) * (mod_cur.quot - 2), 1,
+ dim.height() + preedit_lower_margin);
+ if (mod_anc.rem >= 0)
+ im_anchor_rect_ = QRectF(mod_anc.rem,
+ point.y_ + (dim.height() + 1) * (mod_anc.quot - 1), 1,
+ dim.height() + preedit_lower_margin );
+ else
+ im_anchor_rect_ = QRectF(text_width + mod_anc.rem,
+ point.y_ + (dim.height() + 1) * (mod_anc.quot - 2), 1,
+ dim.height() + preedit_lower_margin );
+ } else {
+ im_cursor_rect_ = QRectF(point.x_, point.y_, 1, dim.height() + preedit_lower_margin);
+ im_anchor_rect_ = im_cursor_rect_;
+ }
+ // Urge platform input method to make inputMethodQuery to check the values
+ // set above
+ im_->update(Qt::ImQueryInput);
}
void GuiWorkArea::Private::resetScreen()
{
if (use_backingstore_) {
- int const pr = p->pixelRatio();
- screen_ = QImage(pr * p->viewport()->width(),
- pr * p->viewport()->height(),
+ double const pr = p->pixelRatio();
+ screen_ = QImage(int(pr * p->viewport()->width()),
+ int(pr * p->viewport()->height()),
QImage::Format_ARGB32_Premultiplied);
screen_.setDevicePixelRatio(pr);
}
if (d->need_resize_ || pixelRatio() != d->last_pixel_ratio_) {
d->resetScreen();
- d->resizeBufferView();
+ resizeBufferView();
}
d->last_pixel_ratio_ = pixelRatio();
QVariant GuiWorkArea::inputMethodQuery(Qt::InputMethodQuery query) const
{
+ LYXERR(Debug::INFO, "incoming InputMethodQuery Value: 0x" << std::hex << query);
switch (query) {
- // this is the CJK-specific composition window position and
- // the context menu position when the menu key is pressed.
+ // this is the CJK-specific composition window position and
+ // the context menu position when the menu key is pressed.
case Qt::ImCursorRectangle: {
- CaretGeometry const & cg = bufferView().caretGeometry();
- return QRect(cg.left - 10 * (d->preedit_lines_ != 1),
- cg.top + cg.height() * d->preedit_lines_,
- cg.width(), cg.height());
+ return QVariant(d->im_cursor_rect_);
+ break;
+ }
+#if QT_VERSION >= QT_VERSION_CHECK(5, 7, 0)
+ case Qt::ImAnchorRectangle: {
+ return QVariant(d->im_anchor_rect_);
+ break;
}
+#endif
default:
return QWidget::inputMethodQuery(query);
}
QObject::connect(this, SIGNAL(currentChanged(int)),
this, SLOT(on_currentTabChanged(int)));
+ // Fix for #11835
+ QObject::connect(this, SIGNAL(tabBarClicked(int)),
+ this, SLOT(on_currentTabChanged(int)));
closeBufferButton = new QToolButton(this);
closeBufferButton->setPalette(pal);
else
// Switch to the work area.
setCurrentIndex(index);
- work_area->setFocus();
+ work_area->setFocus(Qt::OtherFocusReason);
return true;
}
GuiWorkArea * TabWorkArea::addWorkArea(Buffer & buffer, GuiView & view)
{
+ view.setBusy(true);
GuiWorkArea * wa = new GuiWorkArea(buffer, view);
GuiWorkAreaContainer * wac = new GuiWorkAreaContainer(wa);
wa->setUpdatesEnabled(false);
updateTabTexts();
+ // obtain new input item coordinates in the new and old work areas
+ wa->queryInputItemTransform();
+ if (currentWorkArea())
+ currentWorkArea()->queryInputItemTransform();
+
+ view.setBusy(false);
+
return wa;
}
else
// Show tabbar only if there's more than one tab.
showBar(count() > 1);
+ currentWorkArea()->queryInputItemTransform();
} else
lastWorkAreaRemoved();
// show tab popup
QMenu popup;
popup.addAction(QIcon(getPixmap("images/", "hidetab", "svgz,png")),
- qt_("Hide tab"), this, SLOT(hideCurrentTab()));
+ qt_("&Hide Tab"), this, SLOT(hideCurrentTab()));
// we want to show the 'close' option only if this is not a child buffer.
Buffer const & buf = wa->bufferView().buffer();
if (!buf.parent())
popup.addAction(QIcon(getPixmap("images/", "closetab", "svgz,png")),
- qt_("Close tab"), this, SLOT(closeCurrentBuffer()));
+ qt_("&Close Tab"), this, SLOT(closeCurrentBuffer()));
popup.exec(tabBar()->mapToGlobal(pos));
clicked_tab_ = -1;