calc: on editing invalidation of view with different zoom is wrong
[LibreOffice.git] / vcl / qt5 / QtWidget.cxx
blob1c70a2490a1580ccfcb4cb31eb9a7903ffe094b4
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_NumberSign:
454 nCode = KEY_NUMBERSIGN;
455 break;
456 case Qt::Key_Colon:
457 nCode = KEY_COLON;
458 break;
459 case Qt::Key_Semicolon:
460 nCode = KEY_SEMICOLON;
461 break;
462 case Qt::Key_Copy:
463 nCode = KEY_COPY;
464 break;
465 case Qt::Key_Cut:
466 nCode = KEY_CUT;
467 break;
468 case Qt::Key_Open:
469 nCode = KEY_OPEN;
470 break;
471 case Qt::Key_Paste:
472 nCode = KEY_PASTE;
473 break;
477 return nCode;
480 void QtWidget::commitText(QtFrame& rFrame, const QString& aText)
482 SalExtTextInputEvent aInputEvent;
483 aInputEvent.mpTextAttr = nullptr;
484 aInputEvent.mnCursorFlags = 0;
485 aInputEvent.maText = toOUString(aText);
486 aInputEvent.mnCursorPos = aInputEvent.maText.getLength();
488 SolarMutexGuard aGuard;
489 vcl::DeletionListener aDel(&rFrame);
490 rFrame.CallCallback(SalEvent::ExtTextInput, &aInputEvent);
491 if (!aDel.isDeleted())
492 rFrame.CallCallback(SalEvent::EndExtTextInput, nullptr);
495 void QtWidget::deleteReplacementText(QtFrame& rFrame, int nReplacementStart, int nReplacementLength)
497 // get the surrounding text
498 SolarMutexGuard aGuard;
499 SalSurroundingTextRequestEvent aSurroundingTextEvt;
500 aSurroundingTextEvt.maText.clear();
501 aSurroundingTextEvt.mnStart = aSurroundingTextEvt.mnEnd = 0;
502 rFrame.CallCallback(SalEvent::SurroundingTextRequest, &aSurroundingTextEvt);
504 // Turn nReplacementStart, nReplacementLength into a UTF-16 selection
505 const Selection aSelection = SalFrame::CalcDeleteSurroundingSelection(
506 aSurroundingTextEvt.maText, aSurroundingTextEvt.mnStart, nReplacementStart,
507 nReplacementLength);
509 const Selection aInvalid(SAL_MAX_UINT32, SAL_MAX_UINT32);
510 if (aSelection == aInvalid)
512 SAL_WARN("vcl.qt", "Invalid selection when deleting IM replacement text");
513 return;
516 SalSurroundingTextSelectionChangeEvent aEvt;
517 aEvt.mnStart = aSelection.Min();
518 aEvt.mnEnd = aSelection.Max();
519 rFrame.CallCallback(SalEvent::DeleteSurroundingTextRequest, &aEvt);
522 bool QtWidget::handleKeyEvent(QtFrame& rFrame, const QWidget& rWidget, QKeyEvent* pEvent)
524 const bool bIsKeyPressed
525 = pEvent->type() == QEvent::KeyPress || pEvent->type() == QEvent::ShortcutOverride;
526 sal_uInt16 nCode = GetKeyCode(pEvent->key(), pEvent->modifiers());
527 if (bIsKeyPressed && nCode == 0 && pEvent->text().length() > 1
528 && rWidget.testAttribute(Qt::WA_InputMethodEnabled))
530 commitText(rFrame, pEvent->text());
531 pEvent->accept();
532 return true;
535 if (nCode == 0 && pEvent->text().isEmpty())
537 sal_uInt16 nModCode = GetKeyModCode(pEvent->modifiers());
538 SalKeyModEvent aModEvt;
539 aModEvt.mbDown = bIsKeyPressed;
540 aModEvt.mnModKeyCode = ModKeyFlags::NONE;
542 #if CHECK_ANY_QT_USING_X11
543 if (QGuiApplication::platformName() == "xcb")
545 // pressing just the ctrl key leads to a keysym of XK_Control but
546 // the event state does not contain ControlMask. In the release
547 // event it's the other way round: it does contain the Control mask.
548 // The modifier mode therefore has to be adapted manually.
549 ModKeyFlags nExtModMask = ModKeyFlags::NONE;
550 sal_uInt16 nModMask = 0;
551 switch (pEvent->nativeVirtualKey())
553 case XK_Control_L:
554 nExtModMask = ModKeyFlags::LeftMod1;
555 nModMask = KEY_MOD1;
556 break;
557 case XK_Control_R:
558 nExtModMask = ModKeyFlags::RightMod1;
559 nModMask = KEY_MOD1;
560 break;
561 case XK_Alt_L:
562 nExtModMask = ModKeyFlags::LeftMod2;
563 nModMask = KEY_MOD2;
564 break;
565 case XK_Alt_R:
566 nExtModMask = ModKeyFlags::RightMod2;
567 nModMask = KEY_MOD2;
568 break;
569 case XK_Shift_L:
570 nExtModMask = ModKeyFlags::LeftShift;
571 nModMask = KEY_SHIFT;
572 break;
573 case XK_Shift_R:
574 nExtModMask = ModKeyFlags::RightShift;
575 nModMask = KEY_SHIFT;
576 break;
577 // Map Meta/Super keys to MOD3 modifier on all Unix systems
578 // except macOS
579 case XK_Meta_L:
580 case XK_Super_L:
581 nExtModMask = ModKeyFlags::LeftMod3;
582 nModMask = KEY_MOD3;
583 break;
584 case XK_Meta_R:
585 case XK_Super_R:
586 nExtModMask = ModKeyFlags::RightMod3;
587 nModMask = KEY_MOD3;
588 break;
591 if (!bIsKeyPressed)
593 // sending the old mnModKeyCode mask on release is needed to
594 // implement the writing direction switch with Ctrl + L/R-Shift
595 aModEvt.mnModKeyCode = rFrame.m_nKeyModifiers;
596 nModCode &= ~nModMask;
597 rFrame.m_nKeyModifiers &= ~nExtModMask;
599 else
601 nModCode |= nModMask;
602 rFrame.m_nKeyModifiers |= nExtModMask;
603 aModEvt.mnModKeyCode = rFrame.m_nKeyModifiers;
606 #endif
607 aModEvt.mnCode = nModCode;
609 rFrame.CallCallback(SalEvent::KeyModChange, &aModEvt);
610 return false;
613 #if CHECK_ANY_QT_USING_X11
614 // prevent interference of writing direction switch (Ctrl + L/R-Shift) with "normal" shortcuts
615 rFrame.m_nKeyModifiers = ModKeyFlags::NONE;
616 #endif
618 SalKeyEvent aEvent;
619 aEvent.mnCharCode = (pEvent->text().isEmpty() ? 0 : pEvent->text().at(0).unicode());
620 aEvent.mnRepeat = 0;
621 aEvent.mnCode = nCode;
622 aEvent.mnCode |= GetKeyModCode(pEvent->modifiers());
624 QGuiApplication::inputMethod()->update(Qt::ImCursorRectangle);
626 bool bStopProcessingKey;
627 if (bIsKeyPressed)
628 bStopProcessingKey = rFrame.CallCallback(SalEvent::KeyInput, &aEvent);
629 else
630 bStopProcessingKey = rFrame.CallCallback(SalEvent::KeyUp, &aEvent);
631 if (bStopProcessingKey)
632 pEvent->accept();
633 return bStopProcessingKey;
636 bool QtWidget::handleEvent(QtFrame& rFrame, QWidget& rWidget, QEvent* pEvent)
638 if (pEvent->type() == QEvent::ShortcutOverride)
640 // ignore non-spontaneous QEvent::ShortcutOverride events,
641 // since such an extra event is sent e.g. with Orca screen reader enabled,
642 // so that two events of that kind (the "real one" and a non-spontaneous one)
643 // would otherwise be processed, resulting in duplicate input as 'handleKeyEvent'
644 // is called below (s. tdf#122053)
645 if (!pEvent->spontaneous())
647 return false;
650 // Accepted event disables shortcut activation,
651 // but enables keypress event.
652 // If event is not accepted and shortcut is successfully activated,
653 // KeyPress event is omitted.
655 // Instead of processing keyPressEvent, handle ShortcutOverride event,
656 // and if it's handled - disable the shortcut, it should have been activated.
657 // Don't process keyPressEvent generated after disabling shortcut since it was handled here.
658 // If event is not handled, don't accept it and let Qt activate related shortcut.
659 if (handleKeyEvent(rFrame, rWidget, static_cast<QKeyEvent*>(pEvent)))
660 return true;
662 else if (pEvent->type() == QEvent::ToolTip)
664 // Qt's POV on the active popup is wrong due to our fake popup, so check LO's state.
665 // Otherwise Qt will continue handling ToolTip events from the "parent" window.
666 const QtFrame* pPopupFrame = GetQtInstance()->activePopup();
667 if (!rFrame.m_aTooltipText.isEmpty() && (!pPopupFrame || pPopupFrame == &rFrame))
668 QToolTip::showText(QCursor::pos(), toQString(rFrame.m_aTooltipText), &rWidget,
669 rFrame.m_aTooltipArea);
670 else
672 QToolTip::hideText();
673 pEvent->ignore();
675 return true;
677 return false;
680 bool QtWidget::event(QEvent* pEvent)
682 return handleEvent(m_rFrame, *this, pEvent) || QWidget::event(pEvent);
685 void QtWidget::keyReleaseEvent(QKeyEvent* pEvent)
687 if (!handleKeyReleaseEvent(m_rFrame, *this, pEvent))
688 QWidget::keyReleaseEvent(pEvent);
691 void QtWidget::focusInEvent(QFocusEvent*) { m_rFrame.CallCallback(SalEvent::GetFocus, nullptr); }
693 void QtWidget::closePopup()
695 VclPtr<FloatingWindow> pFirstFloat = ImplGetSVData()->mpWinData->mpFirstFloat;
696 if (pFirstFloat && !(pFirstFloat->GetPopupModeFlags() & FloatWinPopupFlags::NoAppFocusClose))
698 SolarMutexGuard aGuard;
699 pFirstFloat->EndPopupMode(FloatWinPopupEndFlags::Cancel | FloatWinPopupEndFlags::CloseAll);
703 void QtWidget::focusOutEvent(QFocusEvent*)
705 #if CHECK_ANY_QT_USING_X11
706 m_rFrame.m_nKeyModifiers = ModKeyFlags::NONE;
707 #endif
708 endExtTextInput();
709 m_rFrame.CallCallback(SalEvent::LoseFocus, nullptr);
710 closePopup();
713 QtWidget::QtWidget(QtFrame& rFrame, Qt::WindowFlags f)
714 // if you try to set the QWidget parent via the QtFrame, instead of using the Q_NULLPTR, at
715 // least test Wayland popups; these horribly broke last time doing this (read commits)!
716 : QWidget(Q_NULLPTR, f)
717 , m_rFrame(rFrame)
718 , m_bNonEmptyIMPreeditSeen(false)
719 , m_bInInputMethodQueryCursorRectangle(false)
720 , m_nDeltaX(0)
721 , m_nDeltaY(0)
723 setAttribute(Qt::WA_TranslucentBackground);
724 setAttribute(Qt::WA_OpaquePaintEvent);
725 setAttribute(Qt::WA_NoSystemBackground);
726 setMouseTracking(true);
727 if (!rFrame.isPopup())
728 setFocusPolicy(Qt::StrongFocus);
729 else
730 setFocusPolicy(Qt::ClickFocus);
733 static ExtTextInputAttr lcl_MapUnderlineStyle(QTextCharFormat::UnderlineStyle us)
735 switch (us)
737 case QTextCharFormat::NoUnderline:
738 return ExtTextInputAttr::NONE;
739 case QTextCharFormat::DotLine:
740 return ExtTextInputAttr::DottedUnderline;
741 case QTextCharFormat::DashDotDotLine:
742 case QTextCharFormat::DashDotLine:
743 return ExtTextInputAttr::DashDotUnderline;
744 case QTextCharFormat::WaveUnderline:
745 return ExtTextInputAttr::GrayWaveline;
746 default:
747 return ExtTextInputAttr::Underline;
751 void QtWidget::inputMethodEvent(QInputMethodEvent* pEvent)
753 const bool bHasCommitText = !pEvent->commitString().isEmpty();
754 const int nReplacementLength = pEvent->replacementLength();
756 if (nReplacementLength > 0 || bHasCommitText)
758 if (nReplacementLength > 0)
759 deleteReplacementText(m_rFrame, pEvent->replacementStart(), nReplacementLength);
760 if (bHasCommitText)
761 commitText(m_rFrame, pEvent->commitString());
763 else
765 SalExtTextInputEvent aInputEvent;
766 aInputEvent.mpTextAttr = nullptr;
767 aInputEvent.mnCursorFlags = 0;
768 aInputEvent.maText = toOUString(pEvent->preeditString());
769 aInputEvent.mnCursorPos = 0;
771 const sal_Int32 nLength = aInputEvent.maText.getLength();
772 const QList<QInputMethodEvent::Attribute>& rAttrList = pEvent->attributes();
773 std::vector<ExtTextInputAttr> aTextAttrs(std::max(sal_Int32(1), nLength),
774 ExtTextInputAttr::NONE);
775 aInputEvent.mpTextAttr = aTextAttrs.data();
777 for (const QInputMethodEvent::Attribute& rAttr : rAttrList)
779 switch (rAttr.type)
781 case QInputMethodEvent::TextFormat:
783 QTextCharFormat aCharFormat
784 = qvariant_cast<QTextFormat>(rAttr.value).toCharFormat();
785 if (aCharFormat.isValid())
787 ExtTextInputAttr aETIP
788 = lcl_MapUnderlineStyle(aCharFormat.underlineStyle());
789 if (aCharFormat.hasProperty(QTextFormat::BackgroundBrush))
790 aETIP |= ExtTextInputAttr::Highlight;
791 if (aCharFormat.fontStrikeOut())
792 aETIP |= ExtTextInputAttr::RedText;
793 for (int j = rAttr.start; j < rAttr.start + rAttr.length; j++)
795 SAL_WARN_IF(j >= static_cast<int>(aTextAttrs.size()), "vcl.qt",
796 "QInputMethodEvent::Attribute out of range. Broken range: "
797 << rAttr.start << "," << rAttr.start + rAttr.length
798 << " Legal range: 0," << aTextAttrs.size());
799 if (j >= static_cast<int>(aTextAttrs.size()))
800 break;
801 aTextAttrs[j] = aETIP;
804 break;
806 case QInputMethodEvent::Cursor:
808 aInputEvent.mnCursorPos = rAttr.start;
809 if (rAttr.length == 0)
810 aInputEvent.mnCursorFlags |= EXTTEXTINPUT_CURSOR_INVISIBLE;
811 break;
813 default:
814 SAL_WARN("vcl.qt", "Unhandled QInputMethodEvent attribute: "
815 << static_cast<int>(rAttr.type));
816 break;
820 const bool bIsEmpty = aInputEvent.maText.isEmpty();
821 if (m_bNonEmptyIMPreeditSeen || !bIsEmpty)
823 SolarMutexGuard aGuard;
824 vcl::DeletionListener aDel(&m_rFrame);
825 m_rFrame.CallCallback(SalEvent::ExtTextInput, &aInputEvent);
826 if (!aDel.isDeleted() && bIsEmpty)
827 m_rFrame.CallCallback(SalEvent::EndExtTextInput, nullptr);
828 m_bNonEmptyIMPreeditSeen = !bIsEmpty;
832 pEvent->accept();
835 static bool lcl_retrieveSurrounding(sal_Int32& rPosition, sal_Int32& rAnchor, QString* pText,
836 QString* pSelection)
838 SolarMutexGuard aGuard;
839 vcl::Window* pFocusWin = Application::GetFocusWindow();
840 if (!pFocusWin)
841 return false;
843 uno::Reference<accessibility::XAccessibleEditableText> xText;
846 uno::Reference<accessibility::XAccessible> xAccessible(pFocusWin->GetAccessible());
847 if (xAccessible.is())
848 xText = FindFocusedEditableText(xAccessible->getAccessibleContext());
850 catch (const uno::Exception&)
852 TOOLS_WARN_EXCEPTION("vcl.qt", "Exception in getting input method surrounding text");
855 if (xText.is())
857 rPosition = xText->getCaretPosition();
858 if (rPosition != -1)
860 if (pText)
861 *pText = toQString(xText->getText());
863 sal_Int32 nSelStart = xText->getSelectionStart();
864 sal_Int32 nSelEnd = xText->getSelectionEnd();
865 if (nSelStart == nSelEnd)
867 rAnchor = rPosition;
869 else
871 if (rPosition == nSelStart)
872 rAnchor = nSelEnd;
873 else
874 rAnchor = nSelStart;
875 if (pSelection)
876 *pSelection = toQString(xText->getSelectedText());
878 return true;
882 return false;
885 QVariant QtWidget::inputMethodQuery(Qt::InputMethodQuery property) const
887 switch (property)
889 case Qt::ImSurroundingText:
891 QString aText;
892 sal_Int32 nCursorPos, nAnchor;
893 if (lcl_retrieveSurrounding(nCursorPos, nAnchor, &aText, nullptr))
894 return QVariant(aText);
895 return QVariant();
897 case Qt::ImCursorPosition:
899 sal_Int32 nCursorPos, nAnchor;
900 if (lcl_retrieveSurrounding(nCursorPos, nAnchor, nullptr, nullptr))
901 return QVariant(static_cast<int>(nCursorPos));
902 return QVariant();
904 case Qt::ImCursorRectangle:
906 if (!m_bInInputMethodQueryCursorRectangle)
908 m_bInInputMethodQueryCursorRectangle = true;
909 SalExtTextInputPosEvent aPosEvent;
910 m_rFrame.CallCallback(SalEvent::ExtTextInputPos, &aPosEvent);
911 const qreal fRatio = m_rFrame.devicePixelRatioF();
912 m_aImCursorRectangle.setRect(aPosEvent.mnX / fRatio, aPosEvent.mnY / fRatio,
913 aPosEvent.mnWidth / fRatio,
914 aPosEvent.mnHeight / fRatio);
915 m_bInInputMethodQueryCursorRectangle = false;
917 return QVariant(m_aImCursorRectangle);
919 case Qt::ImAnchorPosition:
921 sal_Int32 nCursorPos, nAnchor;
922 if (lcl_retrieveSurrounding(nCursorPos, nAnchor, nullptr, nullptr))
923 return QVariant(static_cast<int>(nAnchor));
924 return QVariant();
926 case Qt::ImCurrentSelection:
928 QString aSelection;
929 sal_Int32 nCursorPos, nAnchor;
930 if (lcl_retrieveSurrounding(nCursorPos, nAnchor, nullptr, &aSelection))
931 return QVariant(aSelection);
932 return QVariant();
934 default:
935 return QWidget::inputMethodQuery(property);
939 void QtWidget::endExtTextInput()
941 if (m_bNonEmptyIMPreeditSeen)
943 m_rFrame.CallCallback(SalEvent::EndExtTextInput, nullptr);
944 m_bNonEmptyIMPreeditSeen = false;
948 void QtWidget::changeEvent(QEvent* pEvent)
950 switch (pEvent->type())
952 case QEvent::FontChange:
953 [[fallthrough]];
954 case QEvent::PaletteChange:
955 [[fallthrough]];
956 case QEvent::StyleChange:
958 auto* pSalInst(GetQtInstance());
959 assert(pSalInst);
960 pSalInst->UpdateStyle(QEvent::FontChange == pEvent->type());
961 break;
963 default:
964 break;
966 QWidget::changeEvent(pEvent);
969 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */