bump product version to 7.6.3.2-android
[LibreOffice.git] / vcl / qt5 / QtWidget.cxx
blob83ada7866f2bf440afaa9e581ab148f5a3dffa5b
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <QtWidget.hxx>
21 #include <QtWidget.moc>
23 #include <QtFrame.hxx>
24 #include <QtGraphics.hxx>
25 #include <QtInstance.hxx>
26 #include <QtMainWindow.hxx>
27 #include <QtSvpGraphics.hxx>
28 #include <QtTransferable.hxx>
29 #include <QtTools.hxx>
31 #include <QtCore/QMimeData>
32 #include <QtGui/QDrag>
33 #include <QtGui/QFocusEvent>
34 #include <QtGui/QGuiApplication>
35 #include <QtGui/QImage>
36 #include <QtGui/QKeyEvent>
37 #include <QtGui/QMouseEvent>
38 #include <QtGui/QPainter>
39 #include <QtGui/QPaintEvent>
40 #include <QtGui/QResizeEvent>
41 #include <QtGui/QShowEvent>
42 #include <QtGui/QTextCharFormat>
43 #include <QtGui/QWheelEvent>
44 #include <QtWidgets/QMainWindow>
45 #include <QtWidgets/QToolTip>
46 #include <QtWidgets/QWidget>
48 #include <cairo.h>
49 #include <vcl/commandevent.hxx>
50 #include <vcl/event.hxx>
51 #include <vcl/toolkit/floatwin.hxx>
52 #include <window.h>
53 #include <comphelper/diagnose_ex.hxx>
55 #include <com/sun/star/accessibility/XAccessibleContext.hpp>
56 #include <com/sun/star/accessibility/XAccessibleEditableText.hpp>
58 #if CHECK_ANY_QT_USING_X11
59 #define XK_MISCELLANY
60 #include <X11/keysymdef.h>
61 #endif
63 using namespace com::sun::star;
65 void QtWidget::paintEvent(QPaintEvent* pEvent)
67 QPainter p(this);
68 if (!m_rFrame.m_bNullRegion)
69 p.setClipRegion(m_rFrame.m_aRegion);
71 QImage aImage;
72 if (m_rFrame.m_bUseCairo)
74 cairo_surface_t* pSurface = m_rFrame.m_pSurface.get();
75 cairo_surface_flush(pSurface);
77 aImage = QImage(cairo_image_surface_get_data(pSurface),
78 cairo_image_surface_get_width(pSurface),
79 cairo_image_surface_get_height(pSurface), Qt_DefaultFormat32);
81 else
82 aImage = *m_rFrame.m_pQImage;
84 const qreal fRatio = m_rFrame.devicePixelRatioF();
85 aImage.setDevicePixelRatio(fRatio);
86 QRectF source(pEvent->rect().topLeft() * fRatio, pEvent->rect().size() * fRatio);
87 p.drawImage(pEvent->rect(), aImage, source);
90 void QtWidget::resizeEvent(QResizeEvent* pEvent)
92 const qreal fRatio = m_rFrame.devicePixelRatioF();
93 const int nWidth = ceil(pEvent->size().width() * fRatio);
94 const int nHeight = ceil(pEvent->size().height() * fRatio);
96 m_rFrame.maGeometry.setSize({ nWidth, nHeight });
98 if (m_rFrame.m_bUseCairo)
100 if (m_rFrame.m_pSurface)
102 const int nOldWidth = cairo_image_surface_get_width(m_rFrame.m_pSurface.get());
103 const int nOldHeight = cairo_image_surface_get_height(m_rFrame.m_pSurface.get());
104 if (nOldWidth != nWidth || nOldHeight != nHeight)
106 cairo_surface_t* pSurface
107 = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, nWidth, nHeight);
108 cairo_surface_set_user_data(pSurface, SvpSalGraphics::getDamageKey(),
109 &m_rFrame.m_aDamageHandler, nullptr);
110 m_rFrame.m_pSvpGraphics->setSurface(pSurface, basegfx::B2IVector(nWidth, nHeight));
111 UniqueCairoSurface old_surface(m_rFrame.m_pSurface.release());
112 m_rFrame.m_pSurface.reset(pSurface);
114 const int nMinWidth = qMin(nOldWidth, nWidth);
115 const int nMinHeight = qMin(nOldHeight, nHeight);
116 SalTwoRect rect(0, 0, nMinWidth, nMinHeight, 0, 0, nMinWidth, nMinHeight);
117 m_rFrame.m_pSvpGraphics->copySource(rect, old_surface.get());
121 else
123 if (m_rFrame.m_pQImage && m_rFrame.m_pQImage->size() != QSize(nWidth, nHeight))
125 QImage* pImage = new QImage(m_rFrame.m_pQImage->copy(0, 0, nWidth, nHeight));
126 m_rFrame.m_pQtGraphics->ChangeQImage(pImage);
127 m_rFrame.m_pQImage.reset(pImage);
131 m_rFrame.CallCallback(SalEvent::Resize, nullptr);
134 void QtWidget::fakeResize()
136 QResizeEvent aEvent(size(), QSize());
137 resizeEvent(&aEvent);
140 void QtWidget::fillSalAbstractMouseEvent(const QtFrame& rFrame, const QInputEvent* pQEvent,
141 const QPoint& rPos, Qt::MouseButtons eButtons, int nWidth,
142 SalAbstractMouseEvent& aSalEvent)
144 const qreal fRatio = rFrame.devicePixelRatioF();
145 const Point aPos = toPoint(rPos * fRatio);
147 aSalEvent.mnX = QGuiApplication::isLeftToRight() ? aPos.X() : round(nWidth * fRatio) - aPos.X();
148 aSalEvent.mnY = aPos.Y();
149 aSalEvent.mnTime = pQEvent->timestamp();
150 aSalEvent.mnCode = GetKeyModCode(pQEvent->modifiers()) | GetMouseModCode(eButtons);
153 #define FILL_SAME(rFrame, nWidth) \
154 fillSalAbstractMouseEvent(rFrame, pEvent, pEvent->pos(), pEvent->buttons(), nWidth, aEvent)
156 void QtWidget::handleMouseButtonEvent(const QtFrame& rFrame, const QMouseEvent* pEvent)
158 SalMouseEvent aEvent;
159 FILL_SAME(rFrame, rFrame.GetQWidget()->width());
161 switch (pEvent->button())
163 case Qt::LeftButton:
164 aEvent.mnButton = MOUSE_LEFT;
165 break;
166 case Qt::MiddleButton:
167 aEvent.mnButton = MOUSE_MIDDLE;
168 break;
169 case Qt::RightButton:
170 aEvent.mnButton = MOUSE_RIGHT;
171 break;
172 default:
173 return;
176 SalEvent nEventType;
177 if (pEvent->type() == QEvent::MouseButtonPress || pEvent->type() == QEvent::MouseButtonDblClick)
178 nEventType = SalEvent::MouseButtonDown;
179 else
180 nEventType = SalEvent::MouseButtonUp;
181 rFrame.CallCallback(nEventType, &aEvent);
184 void QtWidget::mousePressEvent(QMouseEvent* pEvent)
186 handleMouseButtonEvent(m_rFrame, pEvent);
187 if (m_rFrame.isPopup()
188 && !geometry().translated(geometry().topLeft() * -1).contains(pEvent->pos()))
189 closePopup();
192 void QtWidget::mouseReleaseEvent(QMouseEvent* pEvent) { handleMouseButtonEvent(m_rFrame, pEvent); }
194 void QtWidget::mouseMoveEvent(QMouseEvent* pEvent)
196 SalMouseEvent aEvent;
197 FILL_SAME(m_rFrame, width());
199 aEvent.mnButton = 0;
201 m_rFrame.CallCallback(SalEvent::MouseMove, &aEvent);
202 pEvent->accept();
205 void QtWidget::handleMouseEnterLeaveEvents(const QtFrame& rFrame, QEvent* pQEvent)
207 const qreal fRatio = rFrame.devicePixelRatioF();
208 const QWidget* pWidget = rFrame.GetQWidget();
209 const Point aPos = toPoint(pWidget->mapFromGlobal(QCursor::pos()) * fRatio);
211 SalMouseEvent aEvent;
212 aEvent.mnX
213 = QGuiApplication::isLeftToRight() ? aPos.X() : round(pWidget->width() * fRatio) - aPos.X();
214 aEvent.mnY = aPos.Y();
215 aEvent.mnTime = 0;
216 aEvent.mnButton = 0;
217 aEvent.mnCode = GetKeyModCode(QGuiApplication::keyboardModifiers())
218 | GetMouseModCode(QGuiApplication::mouseButtons());
220 SalEvent nEventType;
221 if (pQEvent->type() == QEvent::Enter)
222 nEventType = SalEvent::MouseMove;
223 else
224 nEventType = SalEvent::MouseLeave;
225 rFrame.CallCallback(nEventType, &aEvent);
226 pQEvent->accept();
229 void QtWidget::leaveEvent(QEvent* pEvent) { handleMouseEnterLeaveEvents(m_rFrame, pEvent); }
231 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
232 void QtWidget::enterEvent(QEnterEvent* pEvent)
233 #else
234 void QtWidget::enterEvent(QEvent* pEvent)
235 #endif
237 handleMouseEnterLeaveEvents(m_rFrame, pEvent);
240 void QtWidget::wheelEvent(QWheelEvent* pEvent)
242 SalWheelMouseEvent aEvent;
243 #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
244 fillSalAbstractMouseEvent(m_rFrame, pEvent, pEvent->position().toPoint(), pEvent->buttons(),
245 width(), aEvent);
246 #else
247 fillSalAbstractMouseEvent(m_rFrame, pEvent, pEvent->pos(), pEvent->buttons(), width(), aEvent);
248 #endif
250 // mouse wheel ticks are 120, which we map to 3 lines.
251 // we have to accumulate for touch scroll to keep track of the absolute delta.
253 int nDelta = pEvent->angleDelta().y(), lines;
254 aEvent.mbHorz = nDelta == 0;
255 if (aEvent.mbHorz)
257 nDelta = (QGuiApplication::isLeftToRight() ? 1 : -1) * pEvent->angleDelta().x();
258 if (!nDelta)
259 return;
261 m_nDeltaX += nDelta;
262 lines = m_nDeltaX / 40;
263 m_nDeltaX = m_nDeltaX % 40;
265 else
267 m_nDeltaY += nDelta;
268 lines = m_nDeltaY / 40;
269 m_nDeltaY = m_nDeltaY % 40;
272 aEvent.mnDelta = nDelta;
273 aEvent.mnNotchDelta = nDelta < 0 ? -1 : 1;
274 aEvent.mnScrollLines = std::abs(lines);
276 m_rFrame.CallCallback(SalEvent::WheelMouse, &aEvent);
277 pEvent->accept();
280 void QtWidget::dragEnterEvent(QDragEnterEvent* event)
282 if (dynamic_cast<const QtMimeData*>(event->mimeData()))
283 event->accept();
284 else
285 event->acceptProposedAction();
288 // also called when a drop is rejected
289 void QtWidget::dragLeaveEvent(QDragLeaveEvent*) { m_rFrame.handleDragLeave(); }
291 void QtWidget::dragMoveEvent(QDragMoveEvent* pEvent) { m_rFrame.handleDragMove(pEvent); }
293 void QtWidget::dropEvent(QDropEvent* pEvent) { m_rFrame.handleDrop(pEvent); }
295 void QtWidget::moveEvent(QMoveEvent* pEvent)
297 // already handled by QtMainWindow::moveEvent
298 if (m_rFrame.m_pTopLevel)
299 return;
301 m_rFrame.maGeometry.setPos(toPoint(pEvent->pos() * m_rFrame.devicePixelRatioF()));
302 m_rFrame.CallCallback(SalEvent::Move, nullptr);
305 void QtWidget::showEvent(QShowEvent*)
307 QSize aSize(m_rFrame.GetQWidget()->size() * m_rFrame.devicePixelRatioF());
308 // forcing an immediate update somehow interferes with the hide + show
309 // sequence from QtFrame::SetModal, if the frame was already set visible,
310 // resulting in a hidden / unmapped window
311 SalPaintEvent aPaintEvt(0, 0, aSize.width(), aSize.height());
312 if (m_rFrame.isPopup())
313 GetQtInstance()->setActivePopup(&m_rFrame);
314 m_rFrame.CallCallback(SalEvent::Paint, &aPaintEvt);
317 void QtWidget::hideEvent(QHideEvent*)
319 if (m_rFrame.isPopup() && GetQtInstance()->activePopup() == &m_rFrame)
320 GetQtInstance()->setActivePopup(nullptr);
323 void QtWidget::closeEvent(QCloseEvent* /*pEvent*/)
325 m_rFrame.CallCallback(SalEvent::Close, nullptr);
328 static sal_uInt16 GetKeyCode(int keyval, Qt::KeyboardModifiers modifiers)
330 sal_uInt16 nCode = 0;
331 if (keyval >= Qt::Key_0 && keyval <= Qt::Key_9)
332 nCode = KEY_0 + (keyval - Qt::Key_0);
333 else if (keyval >= Qt::Key_A && keyval <= Qt::Key_Z)
334 nCode = KEY_A + (keyval - Qt::Key_A);
335 else if (keyval >= Qt::Key_F1 && keyval <= Qt::Key_F26)
336 nCode = KEY_F1 + (keyval - Qt::Key_F1);
337 else if (modifiers.testFlag(Qt::KeypadModifier)
338 && (keyval == Qt::Key_Period || keyval == Qt::Key_Comma))
339 // Qt doesn't use a special keyval for decimal separator ("," or ".")
340 // on numerical keypad, but sets Qt::KeypadModifier in addition
341 nCode = KEY_DECIMAL;
342 else
344 switch (keyval)
346 case Qt::Key_Down:
347 nCode = KEY_DOWN;
348 break;
349 case Qt::Key_Up:
350 nCode = KEY_UP;
351 break;
352 case Qt::Key_Left:
353 nCode = KEY_LEFT;
354 break;
355 case Qt::Key_Right:
356 nCode = KEY_RIGHT;
357 break;
358 case Qt::Key_Home:
359 nCode = KEY_HOME;
360 break;
361 case Qt::Key_End:
362 nCode = KEY_END;
363 break;
364 case Qt::Key_PageUp:
365 nCode = KEY_PAGEUP;
366 break;
367 case Qt::Key_PageDown:
368 nCode = KEY_PAGEDOWN;
369 break;
370 case Qt::Key_Return:
371 case Qt::Key_Enter:
372 nCode = KEY_RETURN;
373 break;
374 case Qt::Key_Escape:
375 nCode = KEY_ESCAPE;
376 break;
377 case Qt::Key_Tab:
378 // oddly enough, Qt doesn't send Shift-Tab event as 'Tab key pressed with Shift
379 // modifier' but as 'Backtab key pressed' (while its modifier bits are still
380 // set to Shift) -- so let's map both Key_Tab and Key_Backtab to VCL's KEY_TAB
381 case Qt::Key_Backtab:
382 nCode = KEY_TAB;
383 break;
384 case Qt::Key_Backspace:
385 nCode = KEY_BACKSPACE;
386 break;
387 case Qt::Key_Space:
388 nCode = KEY_SPACE;
389 break;
390 case Qt::Key_Insert:
391 nCode = KEY_INSERT;
392 break;
393 case Qt::Key_Delete:
394 nCode = KEY_DELETE;
395 break;
396 case Qt::Key_Plus:
397 nCode = KEY_ADD;
398 break;
399 case Qt::Key_Minus:
400 nCode = KEY_SUBTRACT;
401 break;
402 case Qt::Key_Asterisk:
403 nCode = KEY_MULTIPLY;
404 break;
405 case Qt::Key_Slash:
406 nCode = KEY_DIVIDE;
407 break;
408 case Qt::Key_Period:
409 nCode = KEY_POINT;
410 break;
411 case Qt::Key_Comma:
412 nCode = KEY_COMMA;
413 break;
414 case Qt::Key_Less:
415 nCode = KEY_LESS;
416 break;
417 case Qt::Key_Greater:
418 nCode = KEY_GREATER;
419 break;
420 case Qt::Key_Equal:
421 nCode = KEY_EQUAL;
422 break;
423 case Qt::Key_Find:
424 nCode = KEY_FIND;
425 break;
426 case Qt::Key_Menu:
427 nCode = KEY_CONTEXTMENU;
428 break;
429 case Qt::Key_Help:
430 nCode = KEY_HELP;
431 break;
432 case Qt::Key_Undo:
433 nCode = KEY_UNDO;
434 break;
435 case Qt::Key_Redo:
436 nCode = KEY_REPEAT;
437 break;
438 case Qt::Key_Cancel:
439 nCode = KEY_F11;
440 break;
441 case Qt::Key_AsciiTilde:
442 nCode = KEY_TILDE;
443 break;
444 case Qt::Key_QuoteLeft:
445 nCode = KEY_QUOTELEFT;
446 break;
447 case Qt::Key_BracketLeft:
448 nCode = KEY_BRACKETLEFT;
449 break;
450 case Qt::Key_BracketRight:
451 nCode = KEY_BRACKETRIGHT;
452 break;
453 case Qt::Key_Colon:
454 nCode = KEY_COLON;
455 break;
456 case Qt::Key_Semicolon:
457 nCode = KEY_SEMICOLON;
458 break;
459 case Qt::Key_Copy:
460 nCode = KEY_COPY;
461 break;
462 case Qt::Key_Cut:
463 nCode = KEY_CUT;
464 break;
465 case Qt::Key_Open:
466 nCode = KEY_OPEN;
467 break;
468 case Qt::Key_Paste:
469 nCode = KEY_PASTE;
470 break;
474 return nCode;
477 void QtWidget::commitText(QtFrame& rFrame, const QString& aText)
479 SalExtTextInputEvent aInputEvent;
480 aInputEvent.mpTextAttr = nullptr;
481 aInputEvent.mnCursorFlags = 0;
482 aInputEvent.maText = toOUString(aText);
483 aInputEvent.mnCursorPos = aInputEvent.maText.getLength();
485 SolarMutexGuard aGuard;
486 vcl::DeletionListener aDel(&rFrame);
487 rFrame.CallCallback(SalEvent::ExtTextInput, &aInputEvent);
488 if (!aDel.isDeleted())
489 rFrame.CallCallback(SalEvent::EndExtTextInput, nullptr);
492 void QtWidget::deleteReplacementText(QtFrame& rFrame, int nReplacementStart, int nReplacementLength)
494 // get the surrounding text
495 SolarMutexGuard aGuard;
496 SalSurroundingTextRequestEvent aSurroundingTextEvt;
497 aSurroundingTextEvt.maText.clear();
498 aSurroundingTextEvt.mnStart = aSurroundingTextEvt.mnEnd = 0;
499 rFrame.CallCallback(SalEvent::SurroundingTextRequest, &aSurroundingTextEvt);
501 // Turn nReplacementStart, nReplacementLength into a UTF-16 selection
502 const Selection aSelection = SalFrame::CalcDeleteSurroundingSelection(
503 aSurroundingTextEvt.maText, aSurroundingTextEvt.mnStart, nReplacementStart,
504 nReplacementLength);
506 const Selection aInvalid(SAL_MAX_UINT32, SAL_MAX_UINT32);
507 if (aSelection == aInvalid)
509 SAL_WARN("vcl.qt", "Invalid selection when deleting IM replacement text");
510 return;
513 SalSurroundingTextSelectionChangeEvent aEvt;
514 aEvt.mnStart = aSelection.Min();
515 aEvt.mnEnd = aSelection.Max();
516 rFrame.CallCallback(SalEvent::DeleteSurroundingTextRequest, &aEvt);
519 bool QtWidget::handleKeyEvent(QtFrame& rFrame, const QWidget& rWidget, QKeyEvent* pEvent)
521 const bool bIsKeyPressed
522 = pEvent->type() == QEvent::KeyPress || pEvent->type() == QEvent::ShortcutOverride;
523 sal_uInt16 nCode = GetKeyCode(pEvent->key(), pEvent->modifiers());
524 if (bIsKeyPressed && nCode == 0 && pEvent->text().length() > 1
525 && rWidget.testAttribute(Qt::WA_InputMethodEnabled))
527 commitText(rFrame, pEvent->text());
528 pEvent->accept();
529 return true;
532 if (nCode == 0 && pEvent->text().isEmpty())
534 sal_uInt16 nModCode = GetKeyModCode(pEvent->modifiers());
535 SalKeyModEvent aModEvt;
536 aModEvt.mbDown = bIsKeyPressed;
537 aModEvt.mnModKeyCode = ModKeyFlags::NONE;
539 #if CHECK_ANY_QT_USING_X11
540 if (QGuiApplication::platformName() == "xcb")
542 // pressing just the ctrl key leads to a keysym of XK_Control but
543 // the event state does not contain ControlMask. In the release
544 // event it's the other way round: it does contain the Control mask.
545 // The modifier mode therefore has to be adapted manually.
546 ModKeyFlags nExtModMask = ModKeyFlags::NONE;
547 sal_uInt16 nModMask = 0;
548 switch (pEvent->nativeVirtualKey())
550 case XK_Control_L:
551 nExtModMask = ModKeyFlags::LeftMod1;
552 nModMask = KEY_MOD1;
553 break;
554 case XK_Control_R:
555 nExtModMask = ModKeyFlags::RightMod1;
556 nModMask = KEY_MOD1;
557 break;
558 case XK_Alt_L:
559 nExtModMask = ModKeyFlags::LeftMod2;
560 nModMask = KEY_MOD2;
561 break;
562 case XK_Alt_R:
563 nExtModMask = ModKeyFlags::RightMod2;
564 nModMask = KEY_MOD2;
565 break;
566 case XK_Shift_L:
567 nExtModMask = ModKeyFlags::LeftShift;
568 nModMask = KEY_SHIFT;
569 break;
570 case XK_Shift_R:
571 nExtModMask = ModKeyFlags::RightShift;
572 nModMask = KEY_SHIFT;
573 break;
574 // Map Meta/Super keys to MOD3 modifier on all Unix systems
575 // except macOS
576 case XK_Meta_L:
577 case XK_Super_L:
578 nExtModMask = ModKeyFlags::LeftMod3;
579 nModMask = KEY_MOD3;
580 break;
581 case XK_Meta_R:
582 case XK_Super_R:
583 nExtModMask = ModKeyFlags::RightMod3;
584 nModMask = KEY_MOD3;
585 break;
588 if (!bIsKeyPressed)
590 // sending the old mnModKeyCode mask on release is needed to
591 // implement the writing direction switch with Ctrl + L/R-Shift
592 aModEvt.mnModKeyCode = rFrame.m_nKeyModifiers;
593 nModCode &= ~nModMask;
594 rFrame.m_nKeyModifiers &= ~nExtModMask;
596 else
598 nModCode |= nModMask;
599 rFrame.m_nKeyModifiers |= nExtModMask;
600 aModEvt.mnModKeyCode = rFrame.m_nKeyModifiers;
603 #endif
604 aModEvt.mnCode = nModCode;
606 rFrame.CallCallback(SalEvent::KeyModChange, &aModEvt);
607 return false;
610 #if CHECK_ANY_QT_USING_X11
611 // prevent interference of writing direction switch (Ctrl + L/R-Shift) with "normal" shortcuts
612 rFrame.m_nKeyModifiers = ModKeyFlags::NONE;
613 #endif
615 SalKeyEvent aEvent;
616 aEvent.mnCharCode = (pEvent->text().isEmpty() ? 0 : pEvent->text().at(0).unicode());
617 aEvent.mnRepeat = 0;
618 aEvent.mnCode = nCode;
619 aEvent.mnCode |= GetKeyModCode(pEvent->modifiers());
621 QGuiApplication::inputMethod()->update(Qt::ImCursorRectangle);
623 bool bStopProcessingKey;
624 if (bIsKeyPressed)
625 bStopProcessingKey = rFrame.CallCallback(SalEvent::KeyInput, &aEvent);
626 else
627 bStopProcessingKey = rFrame.CallCallback(SalEvent::KeyUp, &aEvent);
628 if (bStopProcessingKey)
629 pEvent->accept();
630 return bStopProcessingKey;
633 bool QtWidget::handleEvent(QtFrame& rFrame, QWidget& rWidget, QEvent* pEvent)
635 if (pEvent->type() == QEvent::ShortcutOverride)
637 // ignore non-spontaneous QEvent::ShortcutOverride events,
638 // since such an extra event is sent e.g. with Orca screen reader enabled,
639 // so that two events of that kind (the "real one" and a non-spontaneous one)
640 // would otherwise be processed, resulting in duplicate input as 'handleKeyEvent'
641 // is called below (s. tdf#122053)
642 if (!pEvent->spontaneous())
644 return false;
647 // Accepted event disables shortcut activation,
648 // but enables keypress event.
649 // If event is not accepted and shortcut is successfully activated,
650 // KeyPress event is omitted.
652 // Instead of processing keyPressEvent, handle ShortcutOverride event,
653 // and if it's handled - disable the shortcut, it should have been activated.
654 // Don't process keyPressEvent generated after disabling shortcut since it was handled here.
655 // If event is not handled, don't accept it and let Qt activate related shortcut.
656 if (handleKeyEvent(rFrame, rWidget, static_cast<QKeyEvent*>(pEvent)))
657 return true;
659 else if (pEvent->type() == QEvent::ToolTip)
661 // Qt's POV on the active popup is wrong due to our fake popup, so check LO's state.
662 // Otherwise Qt will continue handling ToolTip events from the "parent" window.
663 const QtFrame* pPopupFrame = GetQtInstance()->activePopup();
664 if (!rFrame.m_aTooltipText.isEmpty() && (!pPopupFrame || pPopupFrame == &rFrame))
665 QToolTip::showText(QCursor::pos(), toQString(rFrame.m_aTooltipText), &rWidget,
666 rFrame.m_aTooltipArea);
667 else
669 QToolTip::hideText();
670 pEvent->ignore();
672 return true;
674 return false;
677 bool QtWidget::event(QEvent* pEvent)
679 return handleEvent(m_rFrame, *this, pEvent) || QWidget::event(pEvent);
682 void QtWidget::keyReleaseEvent(QKeyEvent* pEvent)
684 if (!handleKeyReleaseEvent(m_rFrame, *this, pEvent))
685 QWidget::keyReleaseEvent(pEvent);
688 void QtWidget::focusInEvent(QFocusEvent*) { m_rFrame.CallCallback(SalEvent::GetFocus, nullptr); }
690 void QtWidget::closePopup()
692 VclPtr<FloatingWindow> pFirstFloat = ImplGetSVData()->mpWinData->mpFirstFloat;
693 if (pFirstFloat && !(pFirstFloat->GetPopupModeFlags() & FloatWinPopupFlags::NoAppFocusClose))
695 SolarMutexGuard aGuard;
696 pFirstFloat->EndPopupMode(FloatWinPopupEndFlags::Cancel | FloatWinPopupEndFlags::CloseAll);
700 void QtWidget::focusOutEvent(QFocusEvent*)
702 #if CHECK_ANY_QT_USING_X11
703 m_rFrame.m_nKeyModifiers = ModKeyFlags::NONE;
704 #endif
705 endExtTextInput();
706 m_rFrame.CallCallback(SalEvent::LoseFocus, nullptr);
707 closePopup();
710 QtWidget::QtWidget(QtFrame& rFrame, Qt::WindowFlags f)
711 // if you try to set the QWidget parent via the QtFrame, instead of using the Q_NULLPTR, at
712 // least test Wayland popups; these horribly broke last time doing this (read commits)!
713 : QWidget(Q_NULLPTR, f)
714 , m_rFrame(rFrame)
715 , m_bNonEmptyIMPreeditSeen(false)
716 , m_bInInputMethodQueryCursorRectangle(false)
717 , m_nDeltaX(0)
718 , m_nDeltaY(0)
720 setAttribute(Qt::WA_TranslucentBackground);
721 setAttribute(Qt::WA_OpaquePaintEvent);
722 setAttribute(Qt::WA_NoSystemBackground);
723 setMouseTracking(true);
724 if (!rFrame.isPopup())
725 setFocusPolicy(Qt::StrongFocus);
726 else
727 setFocusPolicy(Qt::ClickFocus);
730 static ExtTextInputAttr lcl_MapUnderlineStyle(QTextCharFormat::UnderlineStyle us)
732 switch (us)
734 case QTextCharFormat::NoUnderline:
735 return ExtTextInputAttr::NONE;
736 case QTextCharFormat::DotLine:
737 return ExtTextInputAttr::DottedUnderline;
738 case QTextCharFormat::DashDotDotLine:
739 case QTextCharFormat::DashDotLine:
740 return ExtTextInputAttr::DashDotUnderline;
741 case QTextCharFormat::WaveUnderline:
742 return ExtTextInputAttr::GrayWaveline;
743 default:
744 return ExtTextInputAttr::Underline;
748 void QtWidget::inputMethodEvent(QInputMethodEvent* pEvent)
750 const bool bHasCommitText = !pEvent->commitString().isEmpty();
751 const int nReplacementLength = pEvent->replacementLength();
753 if (nReplacementLength > 0 || bHasCommitText)
755 if (nReplacementLength > 0)
756 deleteReplacementText(m_rFrame, pEvent->replacementStart(), nReplacementLength);
757 if (bHasCommitText)
758 commitText(m_rFrame, pEvent->commitString());
760 else
762 SalExtTextInputEvent aInputEvent;
763 aInputEvent.mpTextAttr = nullptr;
764 aInputEvent.mnCursorFlags = 0;
765 aInputEvent.maText = toOUString(pEvent->preeditString());
766 aInputEvent.mnCursorPos = 0;
768 const sal_Int32 nLength = aInputEvent.maText.getLength();
769 const QList<QInputMethodEvent::Attribute>& rAttrList = pEvent->attributes();
770 std::vector<ExtTextInputAttr> aTextAttrs(std::max(sal_Int32(1), nLength),
771 ExtTextInputAttr::NONE);
772 aInputEvent.mpTextAttr = aTextAttrs.data();
774 for (const QInputMethodEvent::Attribute& rAttr : rAttrList)
776 switch (rAttr.type)
778 case QInputMethodEvent::TextFormat:
780 QTextCharFormat aCharFormat
781 = qvariant_cast<QTextFormat>(rAttr.value).toCharFormat();
782 if (aCharFormat.isValid())
784 ExtTextInputAttr aETIP
785 = lcl_MapUnderlineStyle(aCharFormat.underlineStyle());
786 if (aCharFormat.hasProperty(QTextFormat::BackgroundBrush))
787 aETIP |= ExtTextInputAttr::Highlight;
788 if (aCharFormat.fontStrikeOut())
789 aETIP |= ExtTextInputAttr::RedText;
790 for (int j = rAttr.start; j < rAttr.start + rAttr.length; j++)
792 SAL_WARN_IF(j >= static_cast<int>(aTextAttrs.size()), "vcl.qt",
793 "QInputMethodEvent::Attribute out of range. Broken range: "
794 << rAttr.start << "," << rAttr.start + rAttr.length
795 << " Legal range: 0," << aTextAttrs.size());
796 if (j >= static_cast<int>(aTextAttrs.size()))
797 break;
798 aTextAttrs[j] = aETIP;
801 break;
803 case QInputMethodEvent::Cursor:
805 aInputEvent.mnCursorPos = rAttr.start;
806 if (rAttr.length == 0)
807 aInputEvent.mnCursorFlags |= EXTTEXTINPUT_CURSOR_INVISIBLE;
808 break;
810 default:
811 SAL_WARN("vcl.qt", "Unhandled QInputMethodEvent attribute: "
812 << static_cast<int>(rAttr.type));
813 break;
817 const bool bIsEmpty = aInputEvent.maText.isEmpty();
818 if (m_bNonEmptyIMPreeditSeen || !bIsEmpty)
820 SolarMutexGuard aGuard;
821 vcl::DeletionListener aDel(&m_rFrame);
822 m_rFrame.CallCallback(SalEvent::ExtTextInput, &aInputEvent);
823 if (!aDel.isDeleted() && bIsEmpty)
824 m_rFrame.CallCallback(SalEvent::EndExtTextInput, nullptr);
825 m_bNonEmptyIMPreeditSeen = !bIsEmpty;
829 pEvent->accept();
832 static bool lcl_retrieveSurrounding(sal_Int32& rPosition, sal_Int32& rAnchor, QString* pText,
833 QString* pSelection)
835 SolarMutexGuard aGuard;
836 vcl::Window* pFocusWin = Application::GetFocusWindow();
837 if (!pFocusWin)
838 return false;
840 uno::Reference<accessibility::XAccessibleEditableText> xText;
843 uno::Reference<accessibility::XAccessible> xAccessible(pFocusWin->GetAccessible());
844 if (xAccessible.is())
845 xText = FindFocusedEditableText(xAccessible->getAccessibleContext());
847 catch (const uno::Exception&)
849 TOOLS_WARN_EXCEPTION("vcl.qt", "Exception in getting input method surrounding text");
852 if (xText.is())
854 rPosition = xText->getCaretPosition();
855 if (rPosition != -1)
857 if (pText)
858 *pText = toQString(xText->getText());
860 sal_Int32 nSelStart = xText->getSelectionStart();
861 sal_Int32 nSelEnd = xText->getSelectionEnd();
862 if (nSelStart == nSelEnd)
864 rAnchor = rPosition;
866 else
868 if (rPosition == nSelStart)
869 rAnchor = nSelEnd;
870 else
871 rAnchor = nSelStart;
872 if (pSelection)
873 *pSelection = toQString(xText->getSelectedText());
875 return true;
879 return false;
882 QVariant QtWidget::inputMethodQuery(Qt::InputMethodQuery property) const
884 switch (property)
886 case Qt::ImSurroundingText:
888 QString aText;
889 sal_Int32 nCursorPos, nAnchor;
890 if (lcl_retrieveSurrounding(nCursorPos, nAnchor, &aText, nullptr))
891 return QVariant(aText);
892 return QVariant();
894 case Qt::ImCursorPosition:
896 sal_Int32 nCursorPos, nAnchor;
897 if (lcl_retrieveSurrounding(nCursorPos, nAnchor, nullptr, nullptr))
898 return QVariant(static_cast<int>(nCursorPos));
899 return QVariant();
901 case Qt::ImCursorRectangle:
903 if (!m_bInInputMethodQueryCursorRectangle)
905 m_bInInputMethodQueryCursorRectangle = true;
906 SalExtTextInputPosEvent aPosEvent;
907 m_rFrame.CallCallback(SalEvent::ExtTextInputPos, &aPosEvent);
908 const qreal fRatio = m_rFrame.devicePixelRatioF();
909 m_aImCursorRectangle.setRect(aPosEvent.mnX / fRatio, aPosEvent.mnY / fRatio,
910 aPosEvent.mnWidth / fRatio,
911 aPosEvent.mnHeight / fRatio);
912 m_bInInputMethodQueryCursorRectangle = false;
914 return QVariant(m_aImCursorRectangle);
916 case Qt::ImAnchorPosition:
918 sal_Int32 nCursorPos, nAnchor;
919 if (lcl_retrieveSurrounding(nCursorPos, nAnchor, nullptr, nullptr))
920 return QVariant(static_cast<int>(nAnchor));
921 return QVariant();
923 case Qt::ImCurrentSelection:
925 QString aSelection;
926 sal_Int32 nCursorPos, nAnchor;
927 if (lcl_retrieveSurrounding(nCursorPos, nAnchor, nullptr, &aSelection))
928 return QVariant(aSelection);
929 return QVariant();
931 default:
932 return QWidget::inputMethodQuery(property);
936 void QtWidget::endExtTextInput()
938 if (m_bNonEmptyIMPreeditSeen)
940 m_rFrame.CallCallback(SalEvent::EndExtTextInput, nullptr);
941 m_bNonEmptyIMPreeditSeen = false;
945 void QtWidget::changeEvent(QEvent* pEvent)
947 switch (pEvent->type())
949 case QEvent::FontChange:
950 [[fallthrough]];
951 case QEvent::PaletteChange:
952 [[fallthrough]];
953 case QEvent::StyleChange:
955 auto* pSalInst(GetQtInstance());
956 assert(pSalInst);
957 pSalInst->UpdateStyle(QEvent::FontChange == pEvent->type());
958 break;
960 default:
961 break;
963 QWidget::changeEvent(pEvent);
966 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */