/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if CHECK_ANY_QT_USING_X11 #define XK_MISCELLANY #include #endif using namespace com::sun::star; void QtWidget::paintEvent(QPaintEvent* pEvent) { QPainter p(this); if (!m_rFrame.m_bNullRegion) p.setClipRegion(m_rFrame.m_aRegion); QImage aImage; if (m_rFrame.m_bUseCairo) { cairo_surface_t* pSurface = m_rFrame.m_pSurface.get(); cairo_surface_flush(pSurface); aImage = QImage(cairo_image_surface_get_data(pSurface), cairo_image_surface_get_width(pSurface), cairo_image_surface_get_height(pSurface), Qt_DefaultFormat32); } else aImage = *m_rFrame.m_pQImage; const qreal fRatio = m_rFrame.devicePixelRatioF(); aImage.setDevicePixelRatio(fRatio); QRectF source(pEvent->rect().topLeft() * fRatio, pEvent->rect().size() * fRatio); p.drawImage(pEvent->rect(), aImage, source); } void QtWidget::resizeEvent(QResizeEvent* pEvent) { const qreal fRatio = m_rFrame.devicePixelRatioF(); const int nWidth = ceil(pEvent->size().width() * fRatio); const int nHeight = ceil(pEvent->size().height() * fRatio); m_rFrame.maGeometry.nWidth = nWidth; m_rFrame.maGeometry.nHeight = nHeight; if (m_rFrame.m_bUseCairo) { if (m_rFrame.m_pSurface) { const int nOldWidth = cairo_image_surface_get_width(m_rFrame.m_pSurface.get()); const int nOldHeight = cairo_image_surface_get_height(m_rFrame.m_pSurface.get()); if (nOldWidth != nWidth || nOldHeight != nHeight) { cairo_surface_t* pSurface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, nWidth, nHeight); cairo_surface_set_user_data(pSurface, SvpSalGraphics::getDamageKey(), &m_rFrame.m_aDamageHandler, nullptr); m_rFrame.m_pSvpGraphics->setSurface(pSurface, basegfx::B2IVector(nWidth, nHeight)); UniqueCairoSurface old_surface(m_rFrame.m_pSurface.release()); m_rFrame.m_pSurface.reset(pSurface); const int nMinWidth = qMin(nOldWidth, nWidth); const int nMinHeight = qMin(nOldHeight, nHeight); SalTwoRect rect(0, 0, nMinWidth, nMinHeight, 0, 0, nMinWidth, nMinHeight); m_rFrame.m_pSvpGraphics->copySource(rect, old_surface.get()); } } } else { if (m_rFrame.m_pQImage && m_rFrame.m_pQImage->size() != QSize(nWidth, nHeight)) { QImage* pImage = new QImage(m_rFrame.m_pQImage->copy(0, 0, nWidth, nHeight)); m_rFrame.m_pQtGraphics->ChangeQImage(pImage); m_rFrame.m_pQImage.reset(pImage); } } m_rFrame.CallCallback(SalEvent::Resize, nullptr); } void QtWidget::fakeResize() { QResizeEvent aEvent(size(), QSize()); resizeEvent(&aEvent); } void QtWidget::fillSalAbstractMouseEvent(const QtFrame& rFrame, const QInputEvent* pQEvent, const QPoint& rPos, Qt::MouseButtons eButtons, int nWidth, SalAbstractMouseEvent& aSalEvent) { const qreal fRatio = rFrame.devicePixelRatioF(); const Point aPos = toPoint(rPos * fRatio); aSalEvent.mnX = QGuiApplication::isLeftToRight() ? aPos.X() : round(nWidth * fRatio) - aPos.X(); aSalEvent.mnY = aPos.Y(); aSalEvent.mnTime = pQEvent->timestamp(); aSalEvent.mnCode = GetKeyModCode(pQEvent->modifiers()) | GetMouseModCode(eButtons); } #define FILL_SAME(rFrame, nWidth) \ fillSalAbstractMouseEvent(rFrame, pEvent, pEvent->pos(), pEvent->buttons(), nWidth, aEvent) void QtWidget::handleMouseButtonEvent(const QtFrame& rFrame, const QMouseEvent* pEvent, const ButtonKeyState eState) { SalMouseEvent aEvent; FILL_SAME(rFrame, rFrame.GetQWidget()->width()); switch (pEvent->button()) { case Qt::LeftButton: aEvent.mnButton = MOUSE_LEFT; break; case Qt::MiddleButton: aEvent.mnButton = MOUSE_MIDDLE; break; case Qt::RightButton: aEvent.mnButton = MOUSE_RIGHT; break; default: return; } SalEvent nEventType; if (eState == ButtonKeyState::Pressed) nEventType = SalEvent::MouseButtonDown; else nEventType = SalEvent::MouseButtonUp; rFrame.CallCallback(nEventType, &aEvent); } void QtWidget::mousePressEvent(QMouseEvent* pEvent) { handleMousePressEvent(m_rFrame, pEvent); if (m_rFrame.isPopup() && !geometry().translated(geometry().topLeft() * -1).contains(pEvent->pos())) closePopup(); } void QtWidget::mouseReleaseEvent(QMouseEvent* pEvent) { handleMouseReleaseEvent(m_rFrame, pEvent); } void QtWidget::mouseMoveEvent(QMouseEvent* pEvent) { SalMouseEvent aEvent; FILL_SAME(m_rFrame, width()); aEvent.mnButton = 0; m_rFrame.CallCallback(SalEvent::MouseMove, &aEvent); pEvent->accept(); } void QtWidget::handleMouseEnterLeaveEvents(const QtFrame& rFrame, QEvent* pQEvent) { const qreal fRatio = rFrame.devicePixelRatioF(); const QWidget* pWidget = rFrame.GetQWidget(); const Point aPos = toPoint(pWidget->mapFromGlobal(QCursor::pos()) * fRatio); SalMouseEvent aEvent; aEvent.mnX = QGuiApplication::isLeftToRight() ? aPos.X() : round(pWidget->width() * fRatio) - aPos.X(); aEvent.mnY = aPos.Y(); aEvent.mnTime = 0; aEvent.mnButton = 0; aEvent.mnCode = GetKeyModCode(QGuiApplication::keyboardModifiers()) | GetMouseModCode(QGuiApplication::mouseButtons()); SalEvent nEventType; if (pQEvent->type() == QEvent::Enter) nEventType = SalEvent::MouseMove; else nEventType = SalEvent::MouseLeave; rFrame.CallCallback(nEventType, &aEvent); pQEvent->accept(); } void QtWidget::leaveEvent(QEvent* pEvent) { handleMouseEnterLeaveEvents(m_rFrame, pEvent); } #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) void QtWidget::enterEvent(QEnterEvent* pEvent) #else void QtWidget::enterEvent(QEvent* pEvent) #endif { handleMouseEnterLeaveEvents(m_rFrame, pEvent); } void QtWidget::wheelEvent(QWheelEvent* pEvent) { SalWheelMouseEvent aEvent; #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) fillSalAbstractMouseEvent(m_rFrame, pEvent, pEvent->position().toPoint(), pEvent->buttons(), width(), aEvent); #else fillSalAbstractMouseEvent(m_rFrame, pEvent, pEvent->pos(), pEvent->buttons(), width(), aEvent); #endif // mouse wheel ticks are 120, which we map to 3 lines. // we have to accumulate for touch scroll to keep track of the absolute delta. int nDelta = pEvent->angleDelta().y(), lines; aEvent.mbHorz = nDelta == 0; if (aEvent.mbHorz) { nDelta = (QGuiApplication::isLeftToRight() ? 1 : -1) * pEvent->angleDelta().x(); if (!nDelta) return; m_nDeltaX += nDelta; lines = m_nDeltaX / 40; m_nDeltaX = m_nDeltaX % 40; } else { m_nDeltaY += nDelta; lines = m_nDeltaY / 40; m_nDeltaY = m_nDeltaY % 40; } aEvent.mnDelta = nDelta; aEvent.mnNotchDelta = nDelta < 0 ? -1 : 1; aEvent.mnScrollLines = std::abs(lines); m_rFrame.CallCallback(SalEvent::WheelMouse, &aEvent); pEvent->accept(); } void QtWidget::dragEnterEvent(QDragEnterEvent* event) { if (dynamic_cast(event->mimeData())) event->accept(); else event->acceptProposedAction(); } // also called when a drop is rejected void QtWidget::dragLeaveEvent(QDragLeaveEvent*) { m_rFrame.handleDragLeave(); } void QtWidget::dragMoveEvent(QDragMoveEvent* pEvent) { m_rFrame.handleDragMove(pEvent); } void QtWidget::dropEvent(QDropEvent* pEvent) { m_rFrame.handleDrop(pEvent); } void QtWidget::moveEvent(QMoveEvent* pEvent) { // already handled by QtMainWindow::moveEvent if (m_rFrame.m_pTopLevel) return; const Point aPos = toPoint(pEvent->pos() * m_rFrame.devicePixelRatioF()); m_rFrame.maGeometry.nX = aPos.X(); m_rFrame.maGeometry.nY = aPos.Y(); m_rFrame.CallCallback(SalEvent::Move, nullptr); } void QtWidget::showEvent(QShowEvent*) { QSize aSize(m_rFrame.GetQWidget()->size() * m_rFrame.devicePixelRatioF()); // forcing an immediate update somehow interferes with the hide + show // sequence from QtFrame::SetModal, if the frame was already set visible, // resulting in a hidden / unmapped window SalPaintEvent aPaintEvt(0, 0, aSize.width(), aSize.height()); if (m_rFrame.isPopup()) GetQtInstance()->setActivePopup(&m_rFrame); m_rFrame.CallCallback(SalEvent::Paint, &aPaintEvt); } void QtWidget::hideEvent(QHideEvent*) { if (m_rFrame.isPopup() && GetQtInstance()->activePopup() == &m_rFrame) GetQtInstance()->setActivePopup(nullptr); } void QtWidget::closeEvent(QCloseEvent* /*pEvent*/) { m_rFrame.CallCallback(SalEvent::Close, nullptr); } static sal_uInt16 GetKeyCode(int keyval, Qt::KeyboardModifiers modifiers) { sal_uInt16 nCode = 0; if (keyval >= Qt::Key_0 && keyval <= Qt::Key_9) nCode = KEY_0 + (keyval - Qt::Key_0); else if (keyval >= Qt::Key_A && keyval <= Qt::Key_Z) nCode = KEY_A + (keyval - Qt::Key_A); else if (keyval >= Qt::Key_F1 && keyval <= Qt::Key_F26) nCode = KEY_F1 + (keyval - Qt::Key_F1); else if (modifiers.testFlag(Qt::KeypadModifier) && (keyval == Qt::Key_Period || keyval == Qt::Key_Comma)) // Qt doesn't use a special keyval for decimal separator ("," or ".") // on numerical keypad, but sets Qt::KeypadModifier in addition nCode = KEY_DECIMAL; else { switch (keyval) { case Qt::Key_Down: nCode = KEY_DOWN; break; case Qt::Key_Up: nCode = KEY_UP; break; case Qt::Key_Left: nCode = KEY_LEFT; break; case Qt::Key_Right: nCode = KEY_RIGHT; break; case Qt::Key_Home: nCode = KEY_HOME; break; case Qt::Key_End: nCode = KEY_END; break; case Qt::Key_PageUp: nCode = KEY_PAGEUP; break; case Qt::Key_PageDown: nCode = KEY_PAGEDOWN; break; case Qt::Key_Return: case Qt::Key_Enter: nCode = KEY_RETURN; break; case Qt::Key_Escape: nCode = KEY_ESCAPE; break; case Qt::Key_Tab: // oddly enough, Qt doesn't send Shift-Tab event as 'Tab key pressed with Shift // modifier' but as 'Backtab key pressed' (while its modifier bits are still // set to Shift) -- so let's map both Key_Tab and Key_Backtab to VCL's KEY_TAB case Qt::Key_Backtab: nCode = KEY_TAB; break; case Qt::Key_Backspace: nCode = KEY_BACKSPACE; break; case Qt::Key_Space: nCode = KEY_SPACE; break; case Qt::Key_Insert: nCode = KEY_INSERT; break; case Qt::Key_Delete: nCode = KEY_DELETE; break; case Qt::Key_Plus: nCode = KEY_ADD; break; case Qt::Key_Minus: nCode = KEY_SUBTRACT; break; case Qt::Key_Asterisk: nCode = KEY_MULTIPLY; break; case Qt::Key_Slash: nCode = KEY_DIVIDE; break; case Qt::Key_Period: nCode = KEY_POINT; break; case Qt::Key_Comma: nCode = KEY_COMMA; break; case Qt::Key_Less: nCode = KEY_LESS; break; case Qt::Key_Greater: nCode = KEY_GREATER; break; case Qt::Key_Equal: nCode = KEY_EQUAL; break; case Qt::Key_Find: nCode = KEY_FIND; break; case Qt::Key_Menu: nCode = KEY_CONTEXTMENU; break; case Qt::Key_Help: nCode = KEY_HELP; break; case Qt::Key_Undo: nCode = KEY_UNDO; break; case Qt::Key_Redo: nCode = KEY_REPEAT; break; case Qt::Key_Cancel: nCode = KEY_F11; break; case Qt::Key_AsciiTilde: nCode = KEY_TILDE; break; case Qt::Key_QuoteLeft: nCode = KEY_QUOTELEFT; break; case Qt::Key_BracketLeft: nCode = KEY_BRACKETLEFT; break; case Qt::Key_BracketRight: nCode = KEY_BRACKETRIGHT; break; case Qt::Key_Semicolon: nCode = KEY_SEMICOLON; break; case Qt::Key_Copy: nCode = KEY_COPY; break; case Qt::Key_Cut: nCode = KEY_CUT; break; case Qt::Key_Open: nCode = KEY_OPEN; break; case Qt::Key_Paste: nCode = KEY_PASTE; break; } } return nCode; } void QtWidget::commitText(QtFrame& rFrame, const QString& aText) { SalExtTextInputEvent aInputEvent; aInputEvent.mpTextAttr = nullptr; aInputEvent.mnCursorFlags = 0; aInputEvent.maText = toOUString(aText); aInputEvent.mnCursorPos = aInputEvent.maText.getLength(); SolarMutexGuard aGuard; vcl::DeletionListener aDel(&rFrame); rFrame.CallCallback(SalEvent::ExtTextInput, &aInputEvent); if (!aDel.isDeleted()) rFrame.CallCallback(SalEvent::EndExtTextInput, nullptr); } void QtWidget::deleteReplacementText(QtFrame& rFrame, int nReplacementStart, int nReplacementLength) { // get the surrounding text SolarMutexGuard aGuard; SalSurroundingTextRequestEvent aSurroundingTextEvt; aSurroundingTextEvt.maText.clear(); aSurroundingTextEvt.mnStart = aSurroundingTextEvt.mnEnd = 0; rFrame.CallCallback(SalEvent::SurroundingTextRequest, &aSurroundingTextEvt); // Turn nReplacementStart, nReplacementLength into a UTF-16 selection const Selection aSelection = SalFrame::CalcDeleteSurroundingSelection( aSurroundingTextEvt.maText, aSurroundingTextEvt.mnStart, nReplacementStart, nReplacementLength); const Selection aInvalid(SAL_MAX_UINT32, SAL_MAX_UINT32); if (aSelection == aInvalid) { SAL_WARN("vcl.qt", "Invalid selection when deleting IM replacement text"); return; } SalSurroundingTextSelectionChangeEvent aEvt; aEvt.mnStart = aSelection.Min(); aEvt.mnEnd = aSelection.Max(); rFrame.CallCallback(SalEvent::DeleteSurroundingTextRequest, &aEvt); } bool QtWidget::handleKeyEvent(QtFrame& rFrame, const QWidget& rWidget, QKeyEvent* pEvent, const ButtonKeyState eState) { sal_uInt16 nCode = GetKeyCode(pEvent->key(), pEvent->modifiers()); if (eState == ButtonKeyState::Pressed && nCode == 0 && pEvent->text().length() > 1 && rWidget.testAttribute(Qt::WA_InputMethodEnabled)) { commitText(rFrame, pEvent->text()); pEvent->accept(); return true; } if (nCode == 0 && pEvent->text().isEmpty()) { sal_uInt16 nModCode = GetKeyModCode(pEvent->modifiers()); SalKeyModEvent aModEvt; aModEvt.mbDown = eState == ButtonKeyState::Pressed; aModEvt.mnModKeyCode = ModKeyFlags::NONE; #if CHECK_ANY_QT_USING_X11 if (QGuiApplication::platformName() == "xcb") { // pressing just the ctrl key leads to a keysym of XK_Control but // the event state does not contain ControlMask. In the release // event it's the other way round: it does contain the Control mask. // The modifier mode therefore has to be adapted manually. ModKeyFlags nExtModMask = ModKeyFlags::NONE; sal_uInt16 nModMask = 0; switch (pEvent->nativeVirtualKey()) { case XK_Control_L: nExtModMask = ModKeyFlags::LeftMod1; nModMask = KEY_MOD1; break; case XK_Control_R: nExtModMask = ModKeyFlags::RightMod1; nModMask = KEY_MOD1; break; case XK_Alt_L: nExtModMask = ModKeyFlags::LeftMod2; nModMask = KEY_MOD2; break; case XK_Alt_R: nExtModMask = ModKeyFlags::RightMod2; nModMask = KEY_MOD2; break; case XK_Shift_L: nExtModMask = ModKeyFlags::LeftShift; nModMask = KEY_SHIFT; break; case XK_Shift_R: nExtModMask = ModKeyFlags::RightShift; nModMask = KEY_SHIFT; break; // Map Meta/Super keys to MOD3 modifier on all Unix systems // except macOS case XK_Meta_L: case XK_Super_L: nExtModMask = ModKeyFlags::LeftMod3; nModMask = KEY_MOD3; break; case XK_Meta_R: case XK_Super_R: nExtModMask = ModKeyFlags::RightMod3; nModMask = KEY_MOD3; break; } if (eState == ButtonKeyState::Released) { // sending the old mnModKeyCode mask on release is needed to // implement the writing direction switch with Ctrl + L/R-Shift aModEvt.mnModKeyCode = rFrame.m_nKeyModifiers; nModCode &= ~nModMask; rFrame.m_nKeyModifiers &= ~nExtModMask; } else { nModCode |= nModMask; rFrame.m_nKeyModifiers |= nExtModMask; aModEvt.mnModKeyCode = rFrame.m_nKeyModifiers; } } #endif aModEvt.mnCode = nModCode; rFrame.CallCallback(SalEvent::KeyModChange, &aModEvt); return false; } #if CHECK_ANY_QT_USING_X11 // prevent interference of writing direction switch (Ctrl + L/R-Shift) with "normal" shortcuts rFrame.m_nKeyModifiers = ModKeyFlags::NONE; #endif SalKeyEvent aEvent; aEvent.mnCharCode = (pEvent->text().isEmpty() ? 0 : pEvent->text().at(0).unicode()); aEvent.mnRepeat = 0; aEvent.mnCode = nCode; aEvent.mnCode |= GetKeyModCode(pEvent->modifiers()); QGuiApplication::inputMethod()->update(Qt::ImCursorRectangle); bool bStopProcessingKey; if (eState == ButtonKeyState::Pressed) bStopProcessingKey = rFrame.CallCallback(SalEvent::KeyInput, &aEvent); else bStopProcessingKey = rFrame.CallCallback(SalEvent::KeyUp, &aEvent); if (bStopProcessingKey) pEvent->accept(); return bStopProcessingKey; } bool QtWidget::handleEvent(QtFrame& rFrame, QWidget& rWidget, QEvent* pEvent) { if (pEvent->type() == QEvent::ShortcutOverride) { // ignore non-spontaneous QEvent::ShortcutOverride events, // since such an extra event is sent e.g. with Orca screen reader enabled, // so that two events of that kind (the "real one" and a non-spontaneous one) // would otherwise be processed, resulting in duplicate input as 'handleKeyEvent' // is called below (s. tdf#122053) if (!pEvent->spontaneous()) { return false; } // Accepted event disables shortcut activation, // but enables keypress event. // If event is not accepted and shortcut is successfully activated, // KeyPress event is omitted. // // Instead of processing keyPressEvent, handle ShortcutOverride event, // and if it's handled - disable the shortcut, it should have been activated. // Don't process keyPressEvent generated after disabling shortcut since it was handled here. // If event is not handled, don't accept it and let Qt activate related shortcut. if (handleKeyEvent(rFrame, rWidget, static_cast(pEvent), ButtonKeyState::Pressed)) return true; } else if (pEvent->type() == QEvent::ToolTip) { // Qt's POV on the active popup is wrong due to our fake popup, so check LO's state. // Otherwise Qt will continue handling ToolTip events from the "parent" window. const QtFrame* pPopupFrame = GetQtInstance()->activePopup(); if (!rFrame.m_aTooltipText.isEmpty() && (!pPopupFrame || pPopupFrame == &rFrame)) QToolTip::showText(QCursor::pos(), toQString(rFrame.m_aTooltipText), &rWidget, rFrame.m_aTooltipArea); else { QToolTip::hideText(); pEvent->ignore(); } return true; } return false; } bool QtWidget::event(QEvent* pEvent) { return handleEvent(m_rFrame, *this, pEvent) || QWidget::event(pEvent); } void QtWidget::keyReleaseEvent(QKeyEvent* pEvent) { if (!handleKeyReleaseEvent(m_rFrame, *this, pEvent)) QWidget::keyReleaseEvent(pEvent); } void QtWidget::focusInEvent(QFocusEvent*) { m_rFrame.CallCallback(SalEvent::GetFocus, nullptr); } void QtWidget::closePopup() { VclPtr pFirstFloat = ImplGetSVData()->mpWinData->mpFirstFloat; if (pFirstFloat && !(pFirstFloat->GetPopupModeFlags() & FloatWinPopupFlags::NoAppFocusClose)) { SolarMutexGuard aGuard; pFirstFloat->EndPopupMode(FloatWinPopupEndFlags::Cancel | FloatWinPopupEndFlags::CloseAll); } } void QtWidget::focusOutEvent(QFocusEvent*) { #if CHECK_ANY_QT_USING_X11 m_rFrame.m_nKeyModifiers = ModKeyFlags::NONE; #endif endExtTextInput(); m_rFrame.CallCallback(SalEvent::LoseFocus, nullptr); closePopup(); } QtWidget::QtWidget(QtFrame& rFrame, Qt::WindowFlags f) // if you try to set the QWidget parent via the QtFrame, instead of using the Q_NULLPTR, at // least test Wayland popups; these horribly broke last time doing this (read commits)! : QWidget(Q_NULLPTR, f) , m_rFrame(rFrame) , m_bNonEmptyIMPreeditSeen(false) , m_bInInputMethodQueryCursorRectangle(false) , m_nDeltaX(0) , m_nDeltaY(0) { setAttribute(Qt::WA_TranslucentBackground); setAttribute(Qt::WA_OpaquePaintEvent); setAttribute(Qt::WA_NoSystemBackground); setMouseTracking(true); if (!rFrame.isPopup()) setFocusPolicy(Qt::StrongFocus); else setFocusPolicy(Qt::ClickFocus); } static ExtTextInputAttr lcl_MapUnderlineStyle(QTextCharFormat::UnderlineStyle us) { switch (us) { case QTextCharFormat::NoUnderline: return ExtTextInputAttr::NONE; case QTextCharFormat::DotLine: return ExtTextInputAttr::DottedUnderline; case QTextCharFormat::DashDotDotLine: case QTextCharFormat::DashDotLine: return ExtTextInputAttr::DashDotUnderline; case QTextCharFormat::WaveUnderline: return ExtTextInputAttr::GrayWaveline; default: return ExtTextInputAttr::Underline; } } void QtWidget::inputMethodEvent(QInputMethodEvent* pEvent) { const bool bHasCommitText = !pEvent->commitString().isEmpty(); const int nReplacementLength = pEvent->replacementLength(); if (nReplacementLength > 0 || bHasCommitText) { if (nReplacementLength > 0) deleteReplacementText(m_rFrame, pEvent->replacementStart(), nReplacementLength); if (bHasCommitText) commitText(m_rFrame, pEvent->commitString()); } else { SalExtTextInputEvent aInputEvent; aInputEvent.mpTextAttr = nullptr; aInputEvent.mnCursorFlags = 0; aInputEvent.maText = toOUString(pEvent->preeditString()); aInputEvent.mnCursorPos = 0; const sal_Int32 nLength = aInputEvent.maText.getLength(); const QList& rAttrList = pEvent->attributes(); std::vector aTextAttrs(std::max(sal_Int32(1), nLength), ExtTextInputAttr::NONE); aInputEvent.mpTextAttr = aTextAttrs.data(); for (const QInputMethodEvent::Attribute& rAttr : rAttrList) { switch (rAttr.type) { case QInputMethodEvent::TextFormat: { QTextCharFormat aCharFormat = qvariant_cast(rAttr.value).toCharFormat(); if (aCharFormat.isValid()) { ExtTextInputAttr aETIP = lcl_MapUnderlineStyle(aCharFormat.underlineStyle()); if (aCharFormat.hasProperty(QTextFormat::BackgroundBrush)) aETIP |= ExtTextInputAttr::Highlight; if (aCharFormat.fontStrikeOut()) aETIP |= ExtTextInputAttr::RedText; for (int j = rAttr.start; j < rAttr.start + rAttr.length; j++) { SAL_WARN_IF(j >= static_cast(aTextAttrs.size()), "vcl.qt", "QInputMethodEvent::Attribute out of range. Broken range: " << rAttr.start << "," << rAttr.start + rAttr.length << " Legal range: 0," << aTextAttrs.size()); if (j >= static_cast(aTextAttrs.size())) break; aTextAttrs[j] = aETIP; } } break; } case QInputMethodEvent::Cursor: { aInputEvent.mnCursorPos = rAttr.start; if (rAttr.length == 0) aInputEvent.mnCursorFlags |= EXTTEXTINPUT_CURSOR_INVISIBLE; break; } default: SAL_WARN("vcl.qt", "Unhandled QInputMethodEvent attribute: " << static_cast(rAttr.type)); break; } } const bool bIsEmpty = aInputEvent.maText.isEmpty(); if (m_bNonEmptyIMPreeditSeen || !bIsEmpty) { SolarMutexGuard aGuard; vcl::DeletionListener aDel(&m_rFrame); m_rFrame.CallCallback(SalEvent::ExtTextInput, &aInputEvent); if (!aDel.isDeleted() && bIsEmpty) m_rFrame.CallCallback(SalEvent::EndExtTextInput, nullptr); m_bNonEmptyIMPreeditSeen = !bIsEmpty; } } pEvent->accept(); } static bool lcl_retrieveSurrounding(sal_Int32& rPosition, sal_Int32& rAnchor, QString* pText, QString* pSelection) { SolarMutexGuard aGuard; vcl::Window* pFocusWin = Application::GetFocusWindow(); if (!pFocusWin) return false; uno::Reference xText; try { uno::Reference xAccessible(pFocusWin->GetAccessible()); if (xAccessible.is()) xText = FindFocusedEditableText(xAccessible->getAccessibleContext()); } catch (const uno::Exception&) { TOOLS_WARN_EXCEPTION("vcl.qt", "Exception in getting input method surrounding text"); } if (xText.is()) { rPosition = xText->getCaretPosition(); if (rPosition != -1) { if (pText) *pText = toQString(xText->getText()); sal_Int32 nSelStart = xText->getSelectionStart(); sal_Int32 nSelEnd = xText->getSelectionEnd(); if (nSelStart == nSelEnd) { rAnchor = rPosition; } else { if (rPosition == nSelStart) rAnchor = nSelEnd; else rAnchor = nSelStart; if (pSelection) *pSelection = toQString(xText->getSelectedText()); } return true; } } return false; } QVariant QtWidget::inputMethodQuery(Qt::InputMethodQuery property) const { switch (property) { case Qt::ImSurroundingText: { QString aText; sal_Int32 nCursorPos, nAnchor; if (lcl_retrieveSurrounding(nCursorPos, nAnchor, &aText, nullptr)) return QVariant(aText); return QVariant(); } case Qt::ImCursorPosition: { sal_Int32 nCursorPos, nAnchor; if (lcl_retrieveSurrounding(nCursorPos, nAnchor, nullptr, nullptr)) return QVariant(static_cast(nCursorPos)); return QVariant(); } case Qt::ImCursorRectangle: { if (!m_bInInputMethodQueryCursorRectangle) { m_bInInputMethodQueryCursorRectangle = true; SalExtTextInputPosEvent aPosEvent; m_rFrame.CallCallback(SalEvent::ExtTextInputPos, &aPosEvent); const qreal fRatio = m_rFrame.devicePixelRatioF(); m_aImCursorRectangle.setRect(aPosEvent.mnX / fRatio, aPosEvent.mnY / fRatio, aPosEvent.mnWidth / fRatio, aPosEvent.mnHeight / fRatio); m_bInInputMethodQueryCursorRectangle = false; } return QVariant(m_aImCursorRectangle); } case Qt::ImAnchorPosition: { sal_Int32 nCursorPos, nAnchor; if (lcl_retrieveSurrounding(nCursorPos, nAnchor, nullptr, nullptr)) return QVariant(static_cast(nAnchor)); return QVariant(); } case Qt::ImCurrentSelection: { QString aSelection; sal_Int32 nCursorPos, nAnchor; if (lcl_retrieveSurrounding(nCursorPos, nAnchor, nullptr, &aSelection)) return QVariant(aSelection); return QVariant(); } default: return QWidget::inputMethodQuery(property); } } void QtWidget::endExtTextInput() { if (m_bNonEmptyIMPreeditSeen) { m_rFrame.CallCallback(SalEvent::EndExtTextInput, nullptr); m_bNonEmptyIMPreeditSeen = false; } } void QtWidget::changeEvent(QEvent* pEvent) { switch (pEvent->type()) { case QEvent::FontChange: [[fallthrough]]; case QEvent::PaletteChange: [[fallthrough]]; case QEvent::StyleChange: { auto* pSalInst(GetQtInstance()); assert(pSalInst); pSalInst->UpdateStyle(QEvent::FontChange == pEvent->type()); break; } default: break; } QWidget::changeEvent(pEvent); } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */