summaryrefslogtreecommitdiffstats
path: root/vcl/qt5/QtWidget.cxx
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:06:44 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:06:44 +0000
commited5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch)
tree7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /vcl/qt5/QtWidget.cxx
parentInitial commit. (diff)
downloadlibreoffice-upstream.tar.xz
libreoffice-upstream.zip
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vcl/qt5/QtWidget.cxx')
-rw-r--r--vcl/qt5/QtWidget.cxx967
1 files changed, 967 insertions, 0 deletions
diff --git a/vcl/qt5/QtWidget.cxx b/vcl/qt5/QtWidget.cxx
new file mode 100644
index 000000000..b2451123e
--- /dev/null
+++ b/vcl/qt5/QtWidget.cxx
@@ -0,0 +1,967 @@
+/* -*- 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 <QtWidget.hxx>
+#include <QtWidget.moc>
+
+#include <QtFrame.hxx>
+#include <QtGraphics.hxx>
+#include <QtInstance.hxx>
+#include <QtMainWindow.hxx>
+#include <QtSvpGraphics.hxx>
+#include <QtTransferable.hxx>
+#include <QtTools.hxx>
+
+#include <QtCore/QMimeData>
+#include <QtGui/QDrag>
+#include <QtGui/QFocusEvent>
+#include <QtGui/QGuiApplication>
+#include <QtGui/QImage>
+#include <QtGui/QKeyEvent>
+#include <QtGui/QMouseEvent>
+#include <QtGui/QPainter>
+#include <QtGui/QPaintEvent>
+#include <QtGui/QResizeEvent>
+#include <QtGui/QShowEvent>
+#include <QtGui/QTextCharFormat>
+#include <QtGui/QWheelEvent>
+#include <QtWidgets/QMainWindow>
+#include <QtWidgets/QToolTip>
+#include <QtWidgets/QWidget>
+
+#include <cairo.h>
+#include <vcl/commandevent.hxx>
+#include <vcl/event.hxx>
+#include <vcl/toolkit/floatwin.hxx>
+#include <window.h>
+#include <tools/diagnose_ex.h>
+
+#include <com/sun/star/accessibility/XAccessibleContext.hpp>
+#include <com/sun/star/accessibility/XAccessibleEditableText.hpp>
+
+#if CHECK_ANY_QT_USING_X11
+#define XK_MISCELLANY
+#include <X11/keysymdef.h>
+#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<const QtMimeData*>(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<QKeyEvent*>(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<FloatingWindow> 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<QInputMethodEvent::Attribute>& rAttrList = pEvent->attributes();
+ std::vector<ExtTextInputAttr> 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<QTextFormat>(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<int>(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<int>(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<int>(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<accessibility::XAccessibleEditableText> xText;
+ try
+ {
+ uno::Reference<accessibility::XAccessible> 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<int>(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<int>(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: */