1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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 <Qt5Widget.hxx>
21 #include <Qt5Widget.moc>
23 #include <Qt5Frame.hxx>
24 #include <Qt5Graphics.hxx>
25 #include <Qt5Instance.hxx>
26 #include <Qt5SvpGraphics.hxx>
27 #include <Qt5Transferable.hxx>
28 #include <Qt5Tools.hxx>
30 #include <QtCore/QMimeData>
31 #include <QtGui/QDrag>
32 #include <QtGui/QFocusEvent>
33 #include <QtGui/QGuiApplication>
34 #include <QtGui/QImage>
35 #include <QtGui/QKeyEvent>
36 #include <QtGui/QMouseEvent>
37 #include <QtGui/QPainter>
38 #include <QtGui/QPaintEvent>
39 #include <QtGui/QResizeEvent>
40 #include <QtGui/QShowEvent>
41 #include <QtGui/QTextCharFormat>
42 #include <QtGui/QWheelEvent>
43 #include <QtWidgets/QMainWindow>
44 #include <QtWidgets/QWidget>
47 #include <vcl/commandevent.hxx>
48 #include <vcl/event.hxx>
50 #include <tools/diagnose_ex.h>
52 #include <com/sun/star/accessibility/XAccessibleContext.hpp>
53 #include <com/sun/star/accessibility/XAccessibleEditableText.hpp>
55 using namespace com::sun::star
;
57 void Qt5Widget::paintEvent(QPaintEvent
* pEvent
)
60 if (!m_rFrame
.m_bNullRegion
)
61 p
.setClipRegion(m_rFrame
.m_aRegion
);
64 if (m_rFrame
.m_bUseCairo
)
66 cairo_surface_t
* pSurface
= m_rFrame
.m_pSurface
.get();
67 cairo_surface_flush(pSurface
);
69 aImage
= QImage(cairo_image_surface_get_data(pSurface
),
70 cairo_image_surface_get_width(pSurface
),
71 cairo_image_surface_get_height(pSurface
), Qt5_DefaultFormat32
);
74 aImage
= *m_rFrame
.m_pQImage
;
76 const qreal fRatio
= m_rFrame
.devicePixelRatioF();
77 aImage
.setDevicePixelRatio(fRatio
);
78 QRectF
source(pEvent
->rect().topLeft() * fRatio
, pEvent
->rect().size() * fRatio
);
79 p
.drawImage(pEvent
->rect(), aImage
, source
);
82 void Qt5Widget::resizeEvent(QResizeEvent
* pEvent
)
84 const qreal fRatio
= m_rFrame
.devicePixelRatioF();
85 const int nWidth
= ceil(pEvent
->size().width() * fRatio
);
86 const int nHeight
= ceil(pEvent
->size().height() * fRatio
);
88 m_rFrame
.maGeometry
.nWidth
= nWidth
;
89 m_rFrame
.maGeometry
.nHeight
= nHeight
;
91 if (m_rFrame
.m_bUseCairo
)
93 if (m_rFrame
.m_pSvpGraphics
)
95 cairo_surface_t
* pSurface
96 = cairo_image_surface_create(CAIRO_FORMAT_ARGB32
, nWidth
, nHeight
);
97 cairo_surface_set_user_data(pSurface
, SvpSalGraphics::getDamageKey(),
98 &m_rFrame
.m_aDamageHandler
, nullptr);
99 m_rFrame
.m_pSvpGraphics
->setSurface(pSurface
, basegfx::B2IVector(nWidth
, nHeight
));
100 UniqueCairoSurface
old_surface(m_rFrame
.m_pSurface
.release());
101 m_rFrame
.m_pSurface
.reset(pSurface
);
103 int min_width
= qMin(cairo_image_surface_get_width(old_surface
.get()), nWidth
);
104 int min_height
= qMin(cairo_image_surface_get_height(old_surface
.get()), nHeight
);
106 SalTwoRect
rect(0, 0, min_width
, min_height
, 0, 0, min_width
, min_height
);
108 m_rFrame
.m_pSvpGraphics
->copySource(rect
, old_surface
.get());
113 QImage
* pImage
= nullptr;
115 if (m_rFrame
.m_pQImage
)
116 pImage
= new QImage(m_rFrame
.m_pQImage
->copy(0, 0, nWidth
, nHeight
));
119 pImage
= new QImage(nWidth
, nHeight
, Qt5_DefaultFormat32
);
120 pImage
->fill(Qt::transparent
);
123 m_rFrame
.m_pQt5Graphics
->ChangeQImage(pImage
);
124 m_rFrame
.m_pQImage
.reset(pImage
);
127 m_rFrame
.CallCallback(SalEvent::Resize
, nullptr);
130 void Qt5Widget::fillSalAbstractMouseEvent(const Qt5Frame
& rFrame
, const QInputEvent
* pQEvent
,
131 const QPoint
& rPos
, Qt::MouseButtons eButtons
, int nWidth
,
132 SalAbstractMouseEvent
& aSalEvent
)
134 const qreal fRatio
= rFrame
.devicePixelRatioF();
135 const Point aPos
= toPoint(rPos
* fRatio
);
137 aSalEvent
.mnX
= QGuiApplication::isLeftToRight() ? aPos
.X() : round(nWidth
* fRatio
) - aPos
.X();
138 aSalEvent
.mnY
= aPos
.Y();
139 aSalEvent
.mnTime
= pQEvent
->timestamp();
140 aSalEvent
.mnCode
= GetKeyModCode(pQEvent
->modifiers()) | GetMouseModCode(eButtons
);
143 #define FILL_SAME(rFrame, nWidth) \
144 fillSalAbstractMouseEvent(rFrame, pEvent, pEvent->pos(), pEvent->buttons(), nWidth, aEvent)
146 void Qt5Widget::handleMouseButtonEvent(const Qt5Frame
& rFrame
, const QMouseEvent
* pEvent
,
147 const ButtonKeyState eState
)
149 SalMouseEvent aEvent
;
150 FILL_SAME(rFrame
, rFrame
.GetQWidget()->width());
152 switch (pEvent
->button())
155 aEvent
.mnButton
= MOUSE_LEFT
;
157 case Qt::MiddleButton
:
158 aEvent
.mnButton
= MOUSE_MIDDLE
;
160 case Qt::RightButton
:
161 aEvent
.mnButton
= MOUSE_RIGHT
;
168 if (eState
== ButtonKeyState::Pressed
)
169 nEventType
= SalEvent::MouseButtonDown
;
171 nEventType
= SalEvent::MouseButtonUp
;
172 rFrame
.CallCallback(nEventType
, &aEvent
);
175 void Qt5Widget::mousePressEvent(QMouseEvent
* pEvent
) { handleMousePressEvent(m_rFrame
, pEvent
); }
177 void Qt5Widget::mouseReleaseEvent(QMouseEvent
* pEvent
)
179 handleMouseReleaseEvent(m_rFrame
, pEvent
);
182 void Qt5Widget::mouseMoveEvent(QMouseEvent
* pEvent
)
184 SalMouseEvent aEvent
;
185 FILL_SAME(m_rFrame
, width());
189 m_rFrame
.CallCallback(SalEvent::MouseMove
, &aEvent
);
193 void Qt5Widget::wheelEvent(QWheelEvent
* pEvent
)
195 SalWheelMouseEvent aEvent
;
196 #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
197 fillSalAbstractMouseEvent(m_rFrame
, pEvent
, pEvent
->position().toPoint(), pEvent
->buttons(),
200 fillSalAbstractMouseEvent(m_rFrame
, pEvent
, pEvent
->pos(), pEvent
->buttons(), width(), aEvent
);
203 // mouse wheel ticks are 120, which we map to 3 lines.
204 // we have to accumulate for touch scroll to keep track of the absolute delta.
206 int nDelta
= pEvent
->angleDelta().y(), lines
;
207 aEvent
.mbHorz
= nDelta
== 0;
210 nDelta
= (QGuiApplication::isLeftToRight() ? 1 : -1) * pEvent
->angleDelta().x();
215 lines
= m_nDeltaX
/ 40;
216 m_nDeltaX
= m_nDeltaX
% 40;
221 lines
= m_nDeltaY
/ 40;
222 m_nDeltaY
= m_nDeltaY
% 40;
225 aEvent
.mnDelta
= nDelta
;
226 aEvent
.mnNotchDelta
= nDelta
< 0 ? -1 : 1;
227 aEvent
.mnScrollLines
= std::abs(lines
);
229 m_rFrame
.CallCallback(SalEvent::WheelMouse
, &aEvent
);
233 void Qt5Widget::dragEnterEvent(QDragEnterEvent
* event
)
235 if (dynamic_cast<const Qt5MimeData
*>(event
->mimeData()))
238 event
->acceptProposedAction();
241 // also called when a drop is rejected
242 void Qt5Widget::dragLeaveEvent(QDragLeaveEvent
*) { m_rFrame
.handleDragLeave(); }
244 void Qt5Widget::dragMoveEvent(QDragMoveEvent
* pEvent
) { m_rFrame
.handleDragMove(pEvent
); }
246 void Qt5Widget::dropEvent(QDropEvent
* pEvent
) { m_rFrame
.handleDrop(pEvent
); }
248 void Qt5Widget::moveEvent(QMoveEvent
* pEvent
)
250 if (m_rFrame
.m_pTopLevel
)
253 const Point aPos
= toPoint(pEvent
->pos() * m_rFrame
.devicePixelRatioF());
254 m_rFrame
.maGeometry
.nX
= aPos
.X();
255 m_rFrame
.maGeometry
.nY
= aPos
.Y();
256 m_rFrame
.CallCallback(SalEvent::Move
, nullptr);
259 void Qt5Widget::showEvent(QShowEvent
*)
261 QSize
aSize(m_rFrame
.GetQWidget()->size() * m_rFrame
.devicePixelRatioF());
262 // forcing an immediate update somehow interferes with the hide + show
263 // sequence from Qt5Frame::SetModal, if the frame was already set visible,
264 // resulting in a hidden / unmapped window
265 SalPaintEvent
aPaintEvt(0, 0, aSize
.width(), aSize
.height());
266 m_rFrame
.CallCallback(SalEvent::Paint
, &aPaintEvt
);
269 void Qt5Widget::closeEvent(QCloseEvent
* /*pEvent*/)
271 m_rFrame
.CallCallback(SalEvent::Close
, nullptr);
274 static sal_uInt16
GetKeyCode(int keyval
, Qt::KeyboardModifiers modifiers
)
276 sal_uInt16 nCode
= 0;
277 if (keyval
>= Qt::Key_0
&& keyval
<= Qt::Key_9
)
278 nCode
= KEY_0
+ (keyval
- Qt::Key_0
);
279 else if (keyval
>= Qt::Key_A
&& keyval
<= Qt::Key_Z
)
280 nCode
= KEY_A
+ (keyval
- Qt::Key_A
);
281 else if (keyval
>= Qt::Key_F1
&& keyval
<= Qt::Key_F26
)
282 nCode
= KEY_F1
+ (keyval
- Qt::Key_F1
);
283 else if (modifiers
.testFlag(Qt::KeypadModifier
)
284 && (keyval
== Qt::Key_Period
|| keyval
== Qt::Key_Comma
))
285 // Qt doesn't use a special keyval for decimal separator ("," or ".")
286 // on numerical keypad, but sets Qt::KeypadModifier in addition
313 case Qt::Key_PageDown
:
314 nCode
= KEY_PAGEDOWN
;
324 // oddly enough, Qt doesn't send Shift-Tab event as 'Tab key pressed with Shift
325 // modifier' but as 'Backtab key pressed' (while its modifier bits are still
326 // set to Shift) -- so let's map both Key_Tab and Key_Backtab to VCL's KEY_TAB
327 case Qt::Key_Backtab
:
330 case Qt::Key_Backspace
:
331 nCode
= KEY_BACKSPACE
;
346 nCode
= KEY_SUBTRACT
;
348 case Qt::Key_Asterisk
:
349 nCode
= KEY_MULTIPLY
;
363 case Qt::Key_Greater
:
373 nCode
= KEY_CONTEXTMENU
;
387 case Qt::Key_AsciiTilde
:
390 case Qt::Key_QuoteLeft
:
391 nCode
= KEY_QUOTELEFT
;
393 case Qt::Key_BracketLeft
:
394 nCode
= KEY_BRACKETLEFT
;
396 case Qt::Key_BracketRight
:
397 nCode
= KEY_BRACKETRIGHT
;
399 case Qt::Key_Semicolon
:
400 nCode
= KEY_SEMICOLON
;
420 void Qt5Widget::commitText(Qt5Frame
& rFrame
, const QString
& aText
)
422 SalExtTextInputEvent aInputEvent
;
423 aInputEvent
.mpTextAttr
= nullptr;
424 aInputEvent
.mnCursorFlags
= 0;
425 aInputEvent
.maText
= toOUString(aText
);
426 aInputEvent
.mnCursorPos
= aInputEvent
.maText
.getLength();
428 SolarMutexGuard aGuard
;
429 vcl::DeletionListener
aDel(&rFrame
);
430 rFrame
.CallCallback(SalEvent::ExtTextInput
, &aInputEvent
);
431 if (!aDel
.isDeleted())
432 rFrame
.CallCallback(SalEvent::EndExtTextInput
, nullptr);
435 bool Qt5Widget::handleKeyEvent(Qt5Frame
& rFrame
, const QWidget
& rWidget
, QKeyEvent
* pEvent
,
436 const ButtonKeyState eState
)
438 sal_uInt16 nCode
= GetKeyCode(pEvent
->key(), pEvent
->modifiers());
439 if (eState
== ButtonKeyState::Pressed
&& nCode
== 0 && pEvent
->text().length() > 1
440 && rWidget
.testAttribute(Qt::WA_InputMethodEnabled
))
442 commitText(rFrame
, pEvent
->text());
448 aEvent
.mnCharCode
= (pEvent
->text().isEmpty() ? 0 : pEvent
->text().at(0).unicode());
450 aEvent
.mnCode
= nCode
;
451 aEvent
.mnCode
|= GetKeyModCode(pEvent
->modifiers());
453 QGuiApplication::inputMethod()->update(Qt::ImCursorRectangle
);
455 bool bStopProcessingKey
;
456 if (eState
== ButtonKeyState::Pressed
)
457 bStopProcessingKey
= rFrame
.CallCallback(SalEvent::KeyInput
, &aEvent
);
459 bStopProcessingKey
= rFrame
.CallCallback(SalEvent::KeyUp
, &aEvent
);
460 if (bStopProcessingKey
)
462 return bStopProcessingKey
;
465 bool Qt5Widget::handleEvent(Qt5Frame
& rFrame
, const QWidget
& rWidget
, QEvent
* pEvent
)
467 if (pEvent
->type() == QEvent::ShortcutOverride
)
469 // ignore non-spontaneous QEvent::ShortcutOverride events,
470 // since such an extra event is sent e.g. with Orca screen reader enabled,
471 // so that two events of that kind (the "real one" and a non-spontaneous one)
472 // would otherwise be processed, resulting in duplicate input as 'handleKeyEvent'
473 // is called below (s. tdf#122053)
474 if (!pEvent
->spontaneous())
479 // Accepted event disables shortcut activation,
480 // but enables keypress event.
481 // If event is not accepted and shortcut is successfully activated,
482 // KeyPress event is omitted.
484 // Instead of processing keyPressEvent, handle ShortcutOverride event,
485 // and if it's handled - disable the shortcut, it should have been activated.
486 // Don't process keyPressEvent generated after disabling shortcut since it was handled here.
487 // If event is not handled, don't accept it and let Qt activate related shortcut.
488 if (handleKeyEvent(rFrame
, rWidget
, static_cast<QKeyEvent
*>(pEvent
),
489 ButtonKeyState::Pressed
))
495 bool Qt5Widget::event(QEvent
* pEvent
)
497 return handleEvent(m_rFrame
, *this, pEvent
) || QWidget::event(pEvent
);
500 void Qt5Widget::keyReleaseEvent(QKeyEvent
* pEvent
)
502 if (!handleKeyReleaseEvent(m_rFrame
, *this, pEvent
))
503 QWidget::keyReleaseEvent(pEvent
);
506 void Qt5Widget::focusInEvent(QFocusEvent
*) { m_rFrame
.CallCallback(SalEvent::GetFocus
, nullptr); }
508 void Qt5Widget::focusOutEvent(QFocusEvent
*)
511 m_rFrame
.CallCallback(SalEvent::LoseFocus
, nullptr);
514 Qt5Widget::Qt5Widget(Qt5Frame
& rFrame
, Qt::WindowFlags f
)
515 : QWidget(Q_NULLPTR
, f
)
517 , m_bNonEmptyIMPreeditSeen(false)
522 setMouseTracking(true);
523 setFocusPolicy(Qt::StrongFocus
);
526 static ExtTextInputAttr
lcl_MapUndrelineStyle(QTextCharFormat::UnderlineStyle us
)
530 case QTextCharFormat::NoUnderline
:
531 return ExtTextInputAttr::NONE
;
532 case QTextCharFormat::DotLine
:
533 return ExtTextInputAttr::DottedUnderline
;
534 case QTextCharFormat::DashDotDotLine
:
535 case QTextCharFormat::DashDotLine
:
536 return ExtTextInputAttr::DashDotUnderline
;
537 case QTextCharFormat::WaveUnderline
:
538 return ExtTextInputAttr::GrayWaveline
;
540 return ExtTextInputAttr::Underline
;
544 void Qt5Widget::inputMethodEvent(QInputMethodEvent
* pEvent
)
546 if (!pEvent
->commitString().isEmpty())
547 commitText(m_rFrame
, pEvent
->commitString());
550 SalExtTextInputEvent aInputEvent
;
551 aInputEvent
.mpTextAttr
= nullptr;
552 aInputEvent
.mnCursorFlags
= 0;
553 aInputEvent
.maText
= toOUString(pEvent
->preeditString());
554 aInputEvent
.mnCursorPos
= 0;
556 const sal_Int32 nLength
= aInputEvent
.maText
.getLength();
557 const QList
<QInputMethodEvent::Attribute
>& rAttrList
= pEvent
->attributes();
558 std::vector
<ExtTextInputAttr
> aTextAttrs(std::max(sal_Int32(1), nLength
),
559 ExtTextInputAttr::NONE
);
560 aInputEvent
.mpTextAttr
= aTextAttrs
.data();
562 for (int i
= 0; i
< rAttrList
.size(); ++i
)
564 const QInputMethodEvent::Attribute
& rAttr
= rAttrList
.at(i
);
567 case QInputMethodEvent::TextFormat
:
569 QTextCharFormat aCharFormat
570 = qvariant_cast
<QTextFormat
>(rAttr
.value
).toCharFormat();
571 if (aCharFormat
.isValid())
573 ExtTextInputAttr aETIP
574 = lcl_MapUndrelineStyle(aCharFormat
.underlineStyle());
575 if (aCharFormat
.hasProperty(QTextFormat::BackgroundBrush
))
576 aETIP
|= ExtTextInputAttr::Highlight
;
577 if (aCharFormat
.fontStrikeOut())
578 aETIP
|= ExtTextInputAttr::RedText
;
579 for (int j
= rAttr
.start
; j
< rAttr
.start
+ rAttr
.length
; j
++)
580 aTextAttrs
[j
] = aETIP
;
584 case QInputMethodEvent::Cursor
:
586 aInputEvent
.mnCursorPos
= rAttr
.start
;
587 if (rAttr
.length
== 0)
588 aInputEvent
.mnCursorFlags
|= EXTTEXTINPUT_CURSOR_INVISIBLE
;
592 SAL_WARN("vcl.qt5", "Unhandled QInputMethodEvent attribute: "
593 << static_cast<int>(rAttr
.type
));
598 const bool bIsEmpty
= aInputEvent
.maText
.isEmpty();
599 if (m_bNonEmptyIMPreeditSeen
|| !bIsEmpty
)
601 SolarMutexGuard aGuard
;
602 vcl::DeletionListener
aDel(&m_rFrame
);
603 m_rFrame
.CallCallback(SalEvent::ExtTextInput
, &aInputEvent
);
604 if (!aDel
.isDeleted() && bIsEmpty
)
605 m_rFrame
.CallCallback(SalEvent::EndExtTextInput
, nullptr);
606 m_bNonEmptyIMPreeditSeen
= !bIsEmpty
;
613 static bool lcl_retrieveSurrounding(sal_Int32
& rPosition
, sal_Int32
& rAnchor
, QString
* pText
,
616 SolarMutexGuard aGuard
;
617 vcl::Window
* pFocusWin
= Application::GetFocusWindow();
621 uno::Reference
<accessibility::XAccessibleEditableText
> xText
;
624 uno::Reference
<accessibility::XAccessible
> xAccessible(pFocusWin
->GetAccessible());
625 if (xAccessible
.is())
626 xText
= FindFocusedEditableText(xAccessible
->getAccessibleContext());
628 catch (const uno::Exception
&)
630 TOOLS_WARN_EXCEPTION("vcl.qt5", "Exception in getting input method surrounding text");
635 rPosition
= xText
->getCaretPosition();
639 *pText
= toQString(xText
->getText());
641 sal_Int32 nSelStart
= xText
->getSelectionStart();
642 sal_Int32 nSelEnd
= xText
->getSelectionEnd();
643 if (nSelStart
== nSelEnd
)
649 if (rPosition
== nSelStart
)
654 *pSelection
= toQString(xText
->getSelectedText());
663 QVariant
Qt5Widget::inputMethodQuery(Qt::InputMethodQuery property
) const
667 case Qt::ImSurroundingText
:
670 sal_Int32 nCursorPos
, nAnchor
;
671 if (lcl_retrieveSurrounding(nCursorPos
, nAnchor
, &aText
, nullptr))
672 return QVariant(aText
);
675 case Qt::ImCursorPosition
:
677 sal_Int32 nCursorPos
, nAnchor
;
678 if (lcl_retrieveSurrounding(nCursorPos
, nAnchor
, nullptr, nullptr))
679 return QVariant(static_cast<int>(nCursorPos
));
682 case Qt::ImCursorRectangle
:
684 const qreal fRatio
= m_rFrame
.devicePixelRatioF();
685 SalExtTextInputPosEvent aPosEvent
;
686 m_rFrame
.CallCallback(SalEvent::ExtTextInputPos
, &aPosEvent
);
687 return QVariant(QRect(aPosEvent
.mnX
/ fRatio
, aPosEvent
.mnY
/ fRatio
,
688 aPosEvent
.mnWidth
/ fRatio
, aPosEvent
.mnHeight
/ fRatio
));
690 case Qt::ImAnchorPosition
:
692 sal_Int32 nCursorPos
, nAnchor
;
693 if (lcl_retrieveSurrounding(nCursorPos
, nAnchor
, nullptr, nullptr))
694 return QVariant(static_cast<int>(nAnchor
));
697 case Qt::ImCurrentSelection
:
700 sal_Int32 nCursorPos
, nAnchor
;
701 if (lcl_retrieveSurrounding(nCursorPos
, nAnchor
, nullptr, &aSelection
))
702 return QVariant(aSelection
);
706 return QWidget::inputMethodQuery(property
);
710 void Qt5Widget::endExtTextInput()
712 if (m_bNonEmptyIMPreeditSeen
)
714 m_rFrame
.CallCallback(SalEvent::EndExtTextInput
, nullptr);
715 m_bNonEmptyIMPreeditSeen
= false;
719 void Qt5Widget::changeEvent(QEvent
* pEvent
)
721 switch (pEvent
->type())
723 case QEvent::FontChange
:
725 case QEvent::PaletteChange
:
727 case QEvent::StyleChange
:
729 auto* pSalInst(static_cast<Qt5Instance
*>(GetSalData()->m_pInstance
));
731 pSalInst
->UpdateStyle(QEvent::FontChange
== pEvent
->type());
737 QWidget::changeEvent(pEvent
);
740 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */