Mark some Calc slots as inactive in readonly mode
[LibreOffice.git] / vcl / qt5 / QtWidget.cxx
blob37c5e7fff912d489305f3490328002f8b80aed8f
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
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/qt/QtUtils.hxx>
52 #include <vcl/toolkit/floatwin.hxx>
53 #include <window.h>
54 #include <comphelper/diagnose_ex.hxx>
56 #include <com/sun/star/accessibility/XAccessibleContext.hpp>
57 #include <com/sun/star/accessibility/XAccessibleEditableText.hpp>
59 #if CHECK_ANY_QT_USING_X11
60 #define XK_MISCELLANY
61 #include <X11/keysymdef.h>
62 #endif
64 using namespace com::sun::star;
66 void QtWidget::paintEvent(QPaintEvent* pEvent) { m_rFrame.handlePaintEvent(pEvent, this); }
68 void QtWidget::resizeEvent(QResizeEvent* pEvent) { m_rFrame.handleResizeEvent(pEvent); }
70 void QtWidget::fakeResize()
72 QResizeEvent aEvent(size(), QSize());
73 resizeEvent(&aEvent);
76 void QtWidget::fillSalAbstractMouseEvent(const QtFrame& rFrame, const QInputEvent* pQEvent,
77 const QPoint& rPos, Qt::MouseButtons eButtons, int nWidth,
78 SalAbstractMouseEvent& aSalEvent)
80 const qreal fRatio = rFrame.devicePixelRatioF();
81 const Point aPos = toPoint(rPos * fRatio);
83 aSalEvent.mnX = QGuiApplication::isLeftToRight() ? aPos.X() : round(nWidth * fRatio) - aPos.X();
84 aSalEvent.mnY = aPos.Y();
85 aSalEvent.mnTime = pQEvent->timestamp();
86 aSalEvent.mnCode = GetKeyModCode(pQEvent->modifiers()) | GetMouseModCode(eButtons);
89 #define FILL_SAME(rFrame, nWidth) \
90 fillSalAbstractMouseEvent(rFrame, pEvent, pEvent->pos(), pEvent->buttons(), nWidth, aEvent)
92 void QtWidget::handleMouseButtonEvent(const QtFrame& rFrame, const QMouseEvent* pEvent)
94 SalMouseEvent aEvent;
95 FILL_SAME(rFrame, rFrame.GetQWidget()->width());
97 switch (pEvent->button())
99 case Qt::LeftButton:
100 aEvent.mnButton = MOUSE_LEFT;
101 break;
102 case Qt::MiddleButton:
103 aEvent.mnButton = MOUSE_MIDDLE;
104 break;
105 case Qt::RightButton:
106 aEvent.mnButton = MOUSE_RIGHT;
107 break;
108 default:
109 return;
112 SalEvent nEventType;
113 if (pEvent->type() == QEvent::MouseButtonPress || pEvent->type() == QEvent::MouseButtonDblClick)
114 nEventType = SalEvent::MouseButtonDown;
115 else
116 nEventType = SalEvent::MouseButtonUp;
117 rFrame.CallCallback(nEventType, &aEvent);
120 void QtWidget::mousePressEvent(QMouseEvent* pEvent)
122 handleMouseButtonEvent(m_rFrame, pEvent);
123 if (m_rFrame.isPopup()
124 && !geometry().translated(geometry().topLeft() * -1).contains(pEvent->pos()))
125 closePopup();
128 void QtWidget::mouseReleaseEvent(QMouseEvent* pEvent) { handleMouseButtonEvent(m_rFrame, pEvent); }
130 void QtWidget::mouseMoveEvent(QMouseEvent* pEvent)
132 SalMouseEvent aEvent;
133 FILL_SAME(m_rFrame, width());
135 aEvent.mnButton = 0;
137 m_rFrame.CallCallback(SalEvent::MouseMove, &aEvent);
138 pEvent->accept();
141 void QtWidget::handleMouseEnterLeaveEvents(const QtFrame& rFrame, QEvent* pQEvent)
143 const qreal fRatio = rFrame.devicePixelRatioF();
144 const QWidget* pWidget = rFrame.GetQWidget();
145 const Point aPos = toPoint(pWidget->mapFromGlobal(QCursor::pos()) * fRatio);
147 SalMouseEvent aEvent;
148 aEvent.mnX
149 = QGuiApplication::isLeftToRight() ? aPos.X() : round(pWidget->width() * fRatio) - aPos.X();
150 aEvent.mnY = aPos.Y();
151 aEvent.mnTime = 0;
152 aEvent.mnButton = 0;
153 aEvent.mnCode = GetKeyModCode(QGuiApplication::keyboardModifiers())
154 | GetMouseModCode(QGuiApplication::mouseButtons());
156 SalEvent nEventType;
157 if (pQEvent->type() == QEvent::Enter)
158 nEventType = SalEvent::MouseMove;
159 else
160 nEventType = SalEvent::MouseLeave;
161 rFrame.CallCallback(nEventType, &aEvent);
162 pQEvent->accept();
165 void QtWidget::leaveEvent(QEvent* pEvent) { handleMouseEnterLeaveEvents(m_rFrame, pEvent); }
167 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
168 void QtWidget::enterEvent(QEnterEvent* pEvent)
169 #else
170 void QtWidget::enterEvent(QEvent* pEvent)
171 #endif
173 handleMouseEnterLeaveEvents(m_rFrame, pEvent);
176 void QtWidget::wheelEvent(QWheelEvent* pEvent)
178 SalWheelMouseEvent aEvent;
179 fillSalAbstractMouseEvent(m_rFrame, pEvent, pEvent->position().toPoint(), pEvent->buttons(),
180 width(), aEvent);
182 // mouse wheel ticks are 120, which we map to 3 lines.
183 // we have to accumulate for touch scroll to keep track of the absolute delta.
185 int nDelta = pEvent->angleDelta().y(), lines;
186 aEvent.mbHorz = nDelta == 0;
187 if (aEvent.mbHorz)
189 nDelta = (QGuiApplication::isLeftToRight() ? 1 : -1) * pEvent->angleDelta().x();
190 if (!nDelta)
191 return;
193 m_nDeltaX += nDelta;
194 lines = m_nDeltaX / 40;
195 m_nDeltaX = m_nDeltaX % 40;
197 else
199 m_nDeltaY += nDelta;
200 lines = m_nDeltaY / 40;
201 m_nDeltaY = m_nDeltaY % 40;
204 aEvent.mnDelta = nDelta;
205 aEvent.mnNotchDelta = nDelta < 0 ? -1 : 1;
206 aEvent.mnScrollLines = std::abs(lines);
208 m_rFrame.CallCallback(SalEvent::WheelMouse, &aEvent);
209 pEvent->accept();
212 void QtWidget::dragEnterEvent(QDragEnterEvent* event)
214 if (qobject_cast<const QtMimeData*>(event->mimeData()))
215 event->accept();
216 else
217 event->acceptProposedAction();
220 // also called when a drop is rejected
221 void QtWidget::dragLeaveEvent(QDragLeaveEvent*) { m_rFrame.handleDragLeave(); }
223 void QtWidget::dragMoveEvent(QDragMoveEvent* pEvent) { m_rFrame.handleDragMove(pEvent); }
225 void QtWidget::dropEvent(QDropEvent* pEvent) { m_rFrame.handleDrop(pEvent); }
227 void QtWidget::moveEvent(QMoveEvent* pEvent)
229 // already handled by QtMainWindow::moveEvent
230 if (m_rFrame.m_pTopLevel)
231 return;
233 m_rFrame.handleMoveEvent(pEvent);
236 void QtWidget::showEvent(QShowEvent*)
238 QSize aSize(m_rFrame.GetQWidget()->size() * m_rFrame.devicePixelRatioF());
239 // forcing an immediate update somehow interferes with the hide + show
240 // sequence from QtFrame::SetModal, if the frame was already set visible,
241 // resulting in a hidden / unmapped window
242 SalPaintEvent aPaintEvt(0, 0, aSize.width(), aSize.height());
243 if (m_rFrame.isPopup())
244 GetQtInstance().setActivePopup(&m_rFrame);
245 m_rFrame.CallCallback(SalEvent::Paint, &aPaintEvt);
248 void QtWidget::hideEvent(QHideEvent* pEvent)
250 if (m_rFrame.isPopup())
252 if (GetQtInstance().activePopup() == &m_rFrame)
253 GetQtInstance().setActivePopup(nullptr);
255 // destroy the QWindow as the popup would otherwise still show up
256 // as a top-level child of the app on the a11y level
257 // (Qt explicitly filters out widgets of type Qt::Popup, but
258 // Qt::ToolTip is currently used for popups to work around another
259 // issue, s. the QtFrame ctor)
260 if (!pEvent->spontaneous())
261 destroy();
265 void QtWidget::closeEvent(QCloseEvent* /*pEvent*/)
267 m_rFrame.CallCallback(SalEvent::Close, nullptr);
270 static sal_uInt16 GetKeyCode(int keyval, Qt::KeyboardModifiers modifiers)
272 sal_uInt16 nCode = 0;
273 if (keyval >= Qt::Key_0 && keyval <= Qt::Key_9)
274 nCode = KEY_0 + (keyval - Qt::Key_0);
275 else if (keyval >= Qt::Key_A && keyval <= Qt::Key_Z)
276 nCode = KEY_A + (keyval - Qt::Key_A);
277 else if (keyval >= Qt::Key_F1 && keyval <= Qt::Key_F26)
278 nCode = KEY_F1 + (keyval - Qt::Key_F1);
279 else if (modifiers.testFlag(Qt::KeypadModifier)
280 && (keyval == Qt::Key_Period || keyval == Qt::Key_Comma))
281 // Qt doesn't use a special keyval for decimal separator ("," or ".")
282 // on numerical keypad, but sets Qt::KeypadModifier in addition
283 nCode = KEY_DECIMAL;
284 else
286 switch (keyval)
288 case Qt::Key_Down:
289 nCode = KEY_DOWN;
290 break;
291 case Qt::Key_Up:
292 nCode = KEY_UP;
293 break;
294 case Qt::Key_Left:
295 nCode = KEY_LEFT;
296 break;
297 case Qt::Key_Right:
298 nCode = KEY_RIGHT;
299 break;
300 case Qt::Key_Home:
301 nCode = KEY_HOME;
302 break;
303 case Qt::Key_End:
304 nCode = KEY_END;
305 break;
306 case Qt::Key_PageUp:
307 nCode = KEY_PAGEUP;
308 break;
309 case Qt::Key_PageDown:
310 nCode = KEY_PAGEDOWN;
311 break;
312 case Qt::Key_Return:
313 case Qt::Key_Enter:
314 nCode = KEY_RETURN;
315 break;
316 case Qt::Key_Escape:
317 nCode = KEY_ESCAPE;
318 break;
319 case Qt::Key_Tab:
320 // oddly enough, Qt doesn't send Shift-Tab event as 'Tab key pressed with Shift
321 // modifier' but as 'Backtab key pressed' (while its modifier bits are still
322 // set to Shift) -- so let's map both Key_Tab and Key_Backtab to VCL's KEY_TAB
323 case Qt::Key_Backtab:
324 nCode = KEY_TAB;
325 break;
326 case Qt::Key_Backspace:
327 nCode = KEY_BACKSPACE;
328 break;
329 case Qt::Key_Space:
330 nCode = KEY_SPACE;
331 break;
332 case Qt::Key_Insert:
333 nCode = KEY_INSERT;
334 break;
335 case Qt::Key_Delete:
336 nCode = KEY_DELETE;
337 break;
338 case Qt::Key_Plus:
339 nCode = KEY_ADD;
340 break;
341 case Qt::Key_Minus:
342 nCode = KEY_SUBTRACT;
343 break;
344 case Qt::Key_Asterisk:
345 nCode = KEY_MULTIPLY;
346 break;
347 case Qt::Key_Slash:
348 nCode = KEY_DIVIDE;
349 break;
350 case Qt::Key_Period:
351 nCode = KEY_POINT;
352 break;
353 case Qt::Key_Comma:
354 nCode = KEY_COMMA;
355 break;
356 case Qt::Key_Less:
357 nCode = KEY_LESS;
358 break;
359 case Qt::Key_Greater:
360 nCode = KEY_GREATER;
361 break;
362 case Qt::Key_Equal:
363 nCode = KEY_EQUAL;
364 break;
365 case Qt::Key_Find:
366 nCode = KEY_FIND;
367 break;
368 case Qt::Key_Menu:
369 nCode = KEY_CONTEXTMENU;
370 break;
371 case Qt::Key_Help:
372 nCode = KEY_HELP;
373 break;
374 case Qt::Key_Undo:
375 nCode = KEY_UNDO;
376 break;
377 case Qt::Key_Redo:
378 nCode = KEY_REPEAT;
379 break;
380 case Qt::Key_Cancel:
381 nCode = KEY_F11;
382 break;
383 case Qt::Key_AsciiTilde:
384 nCode = KEY_TILDE;
385 break;
386 case Qt::Key_QuoteLeft:
387 nCode = KEY_QUOTELEFT;
388 break;
389 case Qt::Key_BracketLeft:
390 nCode = KEY_BRACKETLEFT;
391 break;
392 case Qt::Key_BracketRight:
393 nCode = KEY_BRACKETRIGHT;
394 break;
395 case Qt::Key_NumberSign:
396 nCode = KEY_NUMBERSIGN;
397 break;
398 case Qt::Key_Forward:
399 nCode = KEY_XF86FORWARD;
400 break;
401 case Qt::Key_Back:
402 nCode = KEY_XF86BACK;
403 break;
404 case Qt::Key_Colon:
405 nCode = KEY_COLON;
406 break;
407 case Qt::Key_Semicolon:
408 nCode = KEY_SEMICOLON;
409 break;
410 case Qt::Key_Copy:
411 nCode = KEY_COPY;
412 break;
413 case Qt::Key_Cut:
414 nCode = KEY_CUT;
415 break;
416 case Qt::Key_Open:
417 nCode = KEY_OPEN;
418 break;
419 case Qt::Key_Paste:
420 nCode = KEY_PASTE;
421 break;
425 return nCode;
428 void QtWidget::commitText(QtFrame& rFrame, const QString& aText)
430 SalExtTextInputEvent aInputEvent;
431 aInputEvent.mpTextAttr = nullptr;
432 aInputEvent.mnCursorFlags = 0;
433 aInputEvent.maText = toOUString(aText);
434 aInputEvent.mnCursorPos = aInputEvent.maText.getLength();
436 SolarMutexGuard aGuard;
437 vcl::DeletionListener aDel(&rFrame);
438 rFrame.CallCallback(SalEvent::ExtTextInput, &aInputEvent);
439 if (!aDel.isDeleted())
440 rFrame.CallCallback(SalEvent::EndExtTextInput, nullptr);
443 void QtWidget::deleteReplacementText(QtFrame& rFrame, int nReplacementStart, int nReplacementLength)
445 // get the surrounding text
446 SolarMutexGuard aGuard;
447 SalSurroundingTextRequestEvent aSurroundingTextEvt;
448 aSurroundingTextEvt.maText.clear();
449 aSurroundingTextEvt.mnStart = aSurroundingTextEvt.mnEnd = 0;
450 rFrame.CallCallback(SalEvent::SurroundingTextRequest, &aSurroundingTextEvt);
452 // Turn nReplacementStart, nReplacementLength into a UTF-16 selection
453 const Selection aSelection = SalFrame::CalcDeleteSurroundingSelection(
454 aSurroundingTextEvt.maText, aSurroundingTextEvt.mnStart, nReplacementStart,
455 nReplacementLength);
457 const Selection aInvalid(SAL_MAX_UINT32, SAL_MAX_UINT32);
458 if (aSelection == aInvalid)
460 SAL_WARN("vcl.qt", "Invalid selection when deleting IM replacement text");
461 return;
464 SalSurroundingTextSelectionChangeEvent aEvt;
465 aEvt.mnStart = aSelection.Min();
466 aEvt.mnEnd = aSelection.Max();
467 rFrame.CallCallback(SalEvent::DeleteSurroundingTextRequest, &aEvt);
470 bool QtWidget::handleGestureEvent(QtFrame& rFrame, QGestureEvent* pGestureEvent)
472 if (QGesture* pGesture = pGestureEvent->gesture(Qt::PinchGesture))
474 if (!pGesture->hasHotSpot())
476 pGestureEvent->ignore();
477 return false;
480 GestureEventZoomType eType = GestureEventZoomType::Begin;
481 switch (pGesture->state())
483 case Qt::GestureStarted:
484 eType = GestureEventZoomType::Begin;
485 break;
486 case Qt::GestureUpdated:
487 eType = GestureEventZoomType::Update;
488 break;
489 case Qt::GestureFinished:
490 eType = GestureEventZoomType::End;
491 break;
492 case Qt::NoGesture:
493 case Qt::GestureCanceled:
494 default:
495 SAL_WARN("vcl.qt", "Unhandled pinch gesture state: " << pGesture->state());
496 pGestureEvent->ignore();
497 return false;
500 QPinchGesture* pPinchGesture = static_cast<QPinchGesture*>(pGesture);
501 const QPointF aHotspot = pGesture->hotSpot();
502 SalGestureZoomEvent aEvent;
503 aEvent.meEventType = eType;
504 aEvent.mnX = aHotspot.x();
505 aEvent.mnY = aHotspot.y();
506 aEvent.mfScaleDelta = 1 + pPinchGesture->totalScaleFactor();
507 rFrame.CallCallback(SalEvent::GestureZoom, &aEvent);
508 pGestureEvent->accept();
509 return true;
512 pGestureEvent->ignore();
513 return false;
516 bool QtWidget::handleKeyEvent(QtFrame& rFrame, const QWidget& rWidget, QKeyEvent* pEvent)
518 const bool bIsKeyPressed
519 = pEvent->type() == QEvent::KeyPress || pEvent->type() == QEvent::ShortcutOverride;
520 sal_uInt16 nCode = GetKeyCode(pEvent->key(), pEvent->modifiers());
521 if (bIsKeyPressed && nCode == 0 && pEvent->text().length() > 1
522 && rWidget.testAttribute(Qt::WA_InputMethodEnabled))
524 commitText(rFrame, pEvent->text());
525 pEvent->accept();
526 return true;
529 QGuiApplication::inputMethod()->update(Qt::ImCursorRectangle);
531 if (nCode == 0 && pEvent->text().isEmpty())
533 sal_uInt16 nModCode = GetKeyModCode(pEvent->modifiers());
534 SalKeyModEvent aModEvt;
535 aModEvt.mbDown = bIsKeyPressed;
536 aModEvt.mnModKeyCode = ModKeyFlags::NONE;
538 #if CHECK_ANY_QT_USING_X11
539 if (QGuiApplication::platformName() == "xcb")
541 // pressing just the ctrl key leads to a keysym of XK_Control but
542 // the event state does not contain ControlMask. In the release
543 // event it's the other way round: it does contain the Control mask.
544 // The modifier mode therefore has to be adapted manually.
545 ModKeyFlags nExtModMask = ModKeyFlags::NONE;
546 sal_uInt16 nModMask = 0;
547 switch (pEvent->nativeVirtualKey())
549 case XK_Control_L:
550 nExtModMask = ModKeyFlags::LeftMod1;
551 nModMask = KEY_MOD1;
552 break;
553 case XK_Control_R:
554 nExtModMask = ModKeyFlags::RightMod1;
555 nModMask = KEY_MOD1;
556 break;
557 case XK_Alt_L:
558 nExtModMask = ModKeyFlags::LeftMod2;
559 nModMask = KEY_MOD2;
560 break;
561 case XK_Alt_R:
562 nExtModMask = ModKeyFlags::RightMod2;
563 nModMask = KEY_MOD2;
564 break;
565 case XK_Shift_L:
566 nExtModMask = ModKeyFlags::LeftShift;
567 nModMask = KEY_SHIFT;
568 break;
569 case XK_Shift_R:
570 nExtModMask = ModKeyFlags::RightShift;
571 nModMask = KEY_SHIFT;
572 break;
573 // Map Meta/Super keys to MOD3 modifier on all Unix systems
574 // except macOS
575 case XK_Meta_L:
576 case XK_Super_L:
577 nExtModMask = ModKeyFlags::LeftMod3;
578 nModMask = KEY_MOD3;
579 break;
580 case XK_Meta_R:
581 case XK_Super_R:
582 nExtModMask = ModKeyFlags::RightMod3;
583 nModMask = KEY_MOD3;
584 break;
587 if (!bIsKeyPressed)
589 // sending the old mnModKeyCode mask on release is needed to
590 // implement the writing direction switch with Ctrl + L/R-Shift
591 aModEvt.mnModKeyCode = rFrame.m_nKeyModifiers;
592 nModCode &= ~nModMask;
593 rFrame.m_nKeyModifiers &= ~nExtModMask;
595 else
597 nModCode |= nModMask;
598 rFrame.m_nKeyModifiers |= nExtModMask;
599 aModEvt.mnModKeyCode = rFrame.m_nKeyModifiers;
602 #endif
603 aModEvt.mnCode = nModCode;
605 rFrame.CallCallback(SalEvent::KeyModChange, &aModEvt);
606 return false;
609 #if CHECK_ANY_QT_USING_X11
610 // prevent interference of writing direction switch (Ctrl + L/R-Shift) with "normal" shortcuts
611 rFrame.m_nKeyModifiers = ModKeyFlags::NONE;
612 #endif
614 SalKeyEvent aEvent;
615 aEvent.mnCharCode = (pEvent->text().isEmpty() ? 0 : pEvent->text().at(0).unicode());
616 aEvent.mnRepeat = 0;
617 aEvent.mnCode = nCode;
618 aEvent.mnCode |= GetKeyModCode(pEvent->modifiers());
620 bool bStopProcessingKey;
621 if (bIsKeyPressed)
622 bStopProcessingKey = rFrame.CallCallback(SalEvent::KeyInput, &aEvent);
623 else
624 bStopProcessingKey = rFrame.CallCallback(SalEvent::KeyUp, &aEvent);
625 if (bStopProcessingKey)
626 pEvent->accept();
627 return bStopProcessingKey;
630 bool QtWidget::handleEvent(QtFrame& rFrame, QWidget& rWidget, QEvent* pEvent)
632 if (pEvent->type() == QEvent::Gesture)
634 QGestureEvent* pGestureEvent = static_cast<QGestureEvent*>(pEvent);
635 return handleGestureEvent(rFrame, pGestureEvent);
637 else if (pEvent->type() == QEvent::ShortcutOverride)
639 // ignore non-spontaneous QEvent::ShortcutOverride events,
640 // since such an extra event is sent e.g. with Orca screen reader enabled,
641 // so that two events of that kind (the "real one" and a non-spontaneous one)
642 // would otherwise be processed, resulting in duplicate input as 'handleKeyEvent'
643 // is called below (s. tdf#122053)
644 if (!pEvent->spontaneous())
646 // accept event so shortcut action (from menu) isn't triggered in addition
647 // to the processing for the spontaneous event further below
648 pEvent->accept();
649 return false;
652 // Accepted event disables shortcut activation,
653 // but enables keypress event.
654 // If event is not accepted and shortcut is successfully activated,
655 // KeyPress event is omitted.
657 // Instead of processing keyPressEvent, handle ShortcutOverride event,
658 // and if it's handled - disable the shortcut, it should have been activated.
659 // Don't process keyPressEvent generated after disabling shortcut since it was handled here.
660 // If event is not handled, don't accept it and let Qt activate related shortcut.
661 if (handleKeyEvent(rFrame, rWidget, static_cast<QKeyEvent*>(pEvent)))
662 return true;
664 else if (pEvent->type() == QEvent::ToolTip)
666 // Qt's POV on the active popup is wrong due to our fake popup, so check LO's state.
667 // Otherwise Qt will continue handling ToolTip events from the "parent" window.
668 const QtFrame* pPopupFrame = GetQtInstance().activePopup();
669 if (!rFrame.m_aTooltipText.isEmpty() && (!pPopupFrame || pPopupFrame == &rFrame))
671 // tdf#162297 Use a dummy style to ensure the tooltip is wrapped
672 QString sTooltipText("<font font-weight=normal>");
673 sTooltipText += toQString(rFrame.m_aTooltipText);
674 sTooltipText += "</font>";
675 QToolTip::showText(QCursor::pos(), sTooltipText, &rWidget, rFrame.m_aTooltipArea);
677 else
679 QToolTip::hideText();
680 pEvent->ignore();
682 return true;
684 return false;
687 bool QtWidget::event(QEvent* pEvent)
689 return handleEvent(m_rFrame, *this, pEvent) || QWidget::event(pEvent);
692 void QtWidget::keyReleaseEvent(QKeyEvent* pEvent)
694 if (!handleKeyReleaseEvent(m_rFrame, *this, pEvent))
695 QWidget::keyReleaseEvent(pEvent);
698 void QtWidget::focusInEvent(QFocusEvent*) { m_rFrame.CallCallback(SalEvent::GetFocus, nullptr); }
700 void QtWidget::closePopup()
702 VclPtr<FloatingWindow> pFirstFloat = ImplGetSVData()->mpWinData->mpFirstFloat;
703 if (pFirstFloat && !(pFirstFloat->GetPopupModeFlags() & FloatWinPopupFlags::NoAppFocusClose))
705 SolarMutexGuard aGuard;
706 pFirstFloat->EndPopupMode(FloatWinPopupEndFlags::Cancel | FloatWinPopupEndFlags::CloseAll);
710 void QtWidget::focusOutEvent(QFocusEvent*)
712 #if CHECK_ANY_QT_USING_X11
713 m_rFrame.m_nKeyModifiers = ModKeyFlags::NONE;
714 #endif
715 endExtTextInput();
716 m_rFrame.CallCallback(SalEvent::LoseFocus, nullptr);
717 closePopup();
720 QtWidget::QtWidget(QtFrame& rFrame, Qt::WindowFlags f)
721 // if you try to set the QWidget parent via the QtFrame, instead of using nullptr, at
722 // least test Wayland popups; these horribly broke last time doing this (read commits)!
723 : QWidget(nullptr, f)
724 , m_rFrame(rFrame)
725 , m_bNonEmptyIMPreeditSeen(false)
726 , m_bInInputMethodQueryCursorRectangle(false)
727 , m_nDeltaX(0)
728 , m_nDeltaY(0)
730 setAttribute(Qt::WA_TranslucentBackground);
731 setAttribute(Qt::WA_OpaquePaintEvent);
732 setAttribute(Qt::WA_NoSystemBackground);
733 setMouseTracking(true);
734 if (!rFrame.isPopup())
735 setFocusPolicy(Qt::StrongFocus);
736 else
737 setFocusPolicy(Qt::ClickFocus);
739 grabGesture(Qt::PinchGesture);
742 static ExtTextInputAttr lcl_MapUnderlineStyle(QTextCharFormat::UnderlineStyle us)
744 switch (us)
746 case QTextCharFormat::NoUnderline:
747 return ExtTextInputAttr::NONE;
748 case QTextCharFormat::DotLine:
749 return ExtTextInputAttr::DottedUnderline;
750 case QTextCharFormat::DashDotDotLine:
751 case QTextCharFormat::DashDotLine:
752 return ExtTextInputAttr::DashDotUnderline;
753 case QTextCharFormat::WaveUnderline:
754 return ExtTextInputAttr::GrayWaveline;
755 default:
756 return ExtTextInputAttr::Underline;
760 void QtWidget::inputMethodEvent(QInputMethodEvent* pEvent)
762 const bool bHasCommitText = !pEvent->commitString().isEmpty();
763 const int nReplacementLength = pEvent->replacementLength();
765 if (nReplacementLength > 0 || bHasCommitText)
767 if (nReplacementLength > 0)
768 deleteReplacementText(m_rFrame, pEvent->replacementStart(), nReplacementLength);
769 if (bHasCommitText)
770 commitText(m_rFrame, pEvent->commitString());
772 else
774 SalExtTextInputEvent aInputEvent;
775 aInputEvent.mpTextAttr = nullptr;
776 aInputEvent.mnCursorFlags = 0;
777 aInputEvent.maText = toOUString(pEvent->preeditString());
778 aInputEvent.mnCursorPos = 0;
780 const sal_Int32 nLength = aInputEvent.maText.getLength();
781 const QList<QInputMethodEvent::Attribute>& rAttrList = pEvent->attributes();
782 std::vector<ExtTextInputAttr> aTextAttrs(std::max(sal_Int32(1), nLength),
783 ExtTextInputAttr::NONE);
784 aInputEvent.mpTextAttr = aTextAttrs.data();
786 for (const QInputMethodEvent::Attribute& rAttr : rAttrList)
788 switch (rAttr.type)
790 case QInputMethodEvent::TextFormat:
792 QTextCharFormat aCharFormat
793 = qvariant_cast<QTextFormat>(rAttr.value).toCharFormat();
794 if (aCharFormat.isValid())
796 ExtTextInputAttr aETIP
797 = lcl_MapUnderlineStyle(aCharFormat.underlineStyle());
798 if (aCharFormat.hasProperty(QTextFormat::BackgroundBrush))
799 aETIP |= ExtTextInputAttr::Highlight;
800 if (aCharFormat.fontStrikeOut())
801 aETIP |= ExtTextInputAttr::RedText;
802 for (int j = rAttr.start; j < rAttr.start + rAttr.length; j++)
804 SAL_WARN_IF(j >= static_cast<int>(aTextAttrs.size()), "vcl.qt",
805 "QInputMethodEvent::Attribute out of range. Broken range: "
806 << rAttr.start << "," << rAttr.start + rAttr.length
807 << " Legal range: 0," << aTextAttrs.size());
808 if (j >= static_cast<int>(aTextAttrs.size()))
809 break;
810 aTextAttrs[j] = aETIP;
813 break;
815 case QInputMethodEvent::Cursor:
817 aInputEvent.mnCursorPos = rAttr.start;
818 if (rAttr.length == 0)
819 aInputEvent.mnCursorFlags |= EXTTEXTINPUT_CURSOR_INVISIBLE;
820 break;
822 default:
823 SAL_WARN("vcl.qt", "Unhandled QInputMethodEvent attribute: "
824 << static_cast<int>(rAttr.type));
825 break;
829 const bool bIsEmpty = aInputEvent.maText.isEmpty();
830 if (m_bNonEmptyIMPreeditSeen || !bIsEmpty)
832 SolarMutexGuard aGuard;
833 vcl::DeletionListener aDel(&m_rFrame);
834 m_rFrame.CallCallback(SalEvent::ExtTextInput, &aInputEvent);
835 if (!aDel.isDeleted() && bIsEmpty)
836 m_rFrame.CallCallback(SalEvent::EndExtTextInput, nullptr);
837 m_bNonEmptyIMPreeditSeen = !bIsEmpty;
841 pEvent->accept();
844 static bool lcl_retrieveSurrounding(sal_Int32& rPosition, sal_Int32& rAnchor, QString* pText,
845 QString* pSelection)
847 SolarMutexGuard aGuard;
848 vcl::Window* pFocusWin = Application::GetFocusWindow();
849 if (!pFocusWin)
850 return false;
852 uno::Reference<accessibility::XAccessibleEditableText> xText;
855 uno::Reference<accessibility::XAccessible> xAccessible(pFocusWin->GetAccessible());
856 if (xAccessible.is())
857 xText = FindFocusedEditableText(xAccessible->getAccessibleContext());
859 catch (const uno::Exception&)
861 TOOLS_WARN_EXCEPTION("vcl.qt", "Exception in getting input method surrounding text");
864 if (xText.is())
866 rPosition = xText->getCaretPosition();
867 if (rPosition != -1)
869 if (pText)
870 *pText = toQString(xText->getText());
872 sal_Int32 nSelStart = xText->getSelectionStart();
873 sal_Int32 nSelEnd = xText->getSelectionEnd();
874 if (nSelStart == nSelEnd)
876 rAnchor = rPosition;
878 else
880 if (rPosition == nSelStart)
881 rAnchor = nSelEnd;
882 else
883 rAnchor = nSelStart;
884 if (pSelection)
885 *pSelection = toQString(xText->getSelectedText());
887 return true;
891 return false;
894 QVariant QtWidget::inputMethodQuery(Qt::InputMethodQuery property) const
896 switch (property)
898 case Qt::ImSurroundingText:
900 QString aText;
901 sal_Int32 nCursorPos, nAnchor;
902 if (lcl_retrieveSurrounding(nCursorPos, nAnchor, &aText, nullptr))
903 return QVariant(aText);
904 return QVariant();
906 case Qt::ImCursorPosition:
908 sal_Int32 nCursorPos, nAnchor;
909 if (lcl_retrieveSurrounding(nCursorPos, nAnchor, nullptr, nullptr))
910 return QVariant(static_cast<int>(nCursorPos));
911 return QVariant();
913 case Qt::ImCursorRectangle:
915 if (!m_bInInputMethodQueryCursorRectangle)
917 m_bInInputMethodQueryCursorRectangle = true;
918 SalExtTextInputPosEvent aPosEvent;
919 m_rFrame.CallCallback(SalEvent::ExtTextInputPos, &aPosEvent);
920 const qreal fRatio = m_rFrame.devicePixelRatioF();
921 m_aImCursorRectangle.setRect(aPosEvent.mnX / fRatio, aPosEvent.mnY / fRatio,
922 aPosEvent.mnWidth / fRatio,
923 aPosEvent.mnHeight / fRatio);
924 m_bInInputMethodQueryCursorRectangle = false;
926 return QVariant(m_aImCursorRectangle);
928 case Qt::ImAnchorPosition:
930 sal_Int32 nCursorPos, nAnchor;
931 if (lcl_retrieveSurrounding(nCursorPos, nAnchor, nullptr, nullptr))
932 return QVariant(static_cast<int>(nAnchor));
933 return QVariant();
935 case Qt::ImCurrentSelection:
937 QString aSelection;
938 sal_Int32 nCursorPos, nAnchor;
939 if (lcl_retrieveSurrounding(nCursorPos, nAnchor, nullptr, &aSelection))
940 return QVariant(aSelection);
941 return QVariant();
943 default:
944 return QWidget::inputMethodQuery(property);
948 void QtWidget::endExtTextInput()
950 if (m_bNonEmptyIMPreeditSeen)
952 m_rFrame.CallCallback(SalEvent::EndExtTextInput, nullptr);
953 m_bNonEmptyIMPreeditSeen = false;
957 void QtWidget::changeEvent(QEvent* pEvent)
959 switch (pEvent->type())
961 case QEvent::FontChange:
962 [[fallthrough]];
963 case QEvent::PaletteChange:
964 [[fallthrough]];
965 case QEvent::StyleChange:
967 GetQtInstance().UpdateStyle(QEvent::FontChange == pEvent->type());
968 break;
970 default:
971 break;
973 QWidget::changeEvent(pEvent);
976 /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */