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
);
63 if (m_rFrame
.m_bUseCairo
)
65 cairo_surface_t
* pSurface
= m_rFrame
.m_pSurface
.get();
66 cairo_surface_flush(pSurface
);
68 QImage
aImage(cairo_image_surface_get_data(pSurface
), size().width(), size().height(),
70 p
.drawImage(pEvent
->rect().topLeft(), aImage
, pEvent
->rect());
73 p
.drawImage(pEvent
->rect().topLeft(), *m_rFrame
.m_pQImage
, pEvent
->rect());
76 void Qt5Widget::resizeEvent(QResizeEvent
* pEvent
)
78 const int nWidth
= pEvent
->size().width();
79 const int nHeight
= pEvent
->size().height();
81 m_rFrame
.maGeometry
.nWidth
= nWidth
;
82 m_rFrame
.maGeometry
.nHeight
= nHeight
;
84 if (m_rFrame
.m_bUseCairo
)
86 if (m_rFrame
.m_pSvpGraphics
)
88 cairo_surface_t
* pSurface
89 = cairo_image_surface_create(CAIRO_FORMAT_ARGB32
, nWidth
, nHeight
);
90 cairo_surface_set_user_data(pSurface
, SvpSalGraphics::getDamageKey(),
91 &m_rFrame
.m_aDamageHandler
, nullptr);
92 m_rFrame
.m_pSvpGraphics
->setSurface(pSurface
, basegfx::B2IVector(nWidth
, nHeight
));
93 UniqueCairoSurface
old_surface(m_rFrame
.m_pSurface
.release());
94 m_rFrame
.m_pSurface
.reset(pSurface
);
96 int min_width
= qMin(pEvent
->oldSize().width(), nWidth
);
97 int min_height
= qMin(pEvent
->oldSize().height(), nHeight
);
99 SalTwoRect
rect(0, 0, min_width
, min_height
, 0, 0, min_width
, min_height
);
101 m_rFrame
.m_pSvpGraphics
->copySource(rect
, old_surface
.get());
106 QImage
* pImage
= nullptr;
108 if (m_rFrame
.m_pQImage
)
109 pImage
= new QImage(m_rFrame
.m_pQImage
->copy(0, 0, nWidth
, nHeight
));
112 pImage
= new QImage(nWidth
, nHeight
, Qt5_DefaultFormat32
);
113 pImage
->fill(Qt::transparent
);
116 m_rFrame
.m_pQt5Graphics
->ChangeQImage(pImage
);
117 m_rFrame
.m_pQImage
.reset(pImage
);
120 m_rFrame
.CallCallback(SalEvent::Resize
, nullptr);
123 void Qt5Widget::handleMouseButtonEvent(const Qt5Frame
& rFrame
, const QMouseEvent
* pEvent
,
124 const ButtonKeyState eState
)
126 SalMouseEvent aEvent
;
127 switch (pEvent
->button())
130 aEvent
.mnButton
= MOUSE_LEFT
;
133 aEvent
.mnButton
= MOUSE_MIDDLE
;
135 case Qt::RightButton
:
136 aEvent
.mnButton
= MOUSE_RIGHT
;
142 aEvent
.mnTime
= pEvent
->timestamp();
143 aEvent
.mnX
= static_cast<long>(QGuiApplication::isLeftToRight()
145 : rFrame
.GetQWidget()->width() - pEvent
->pos().x());
146 aEvent
.mnY
= static_cast<long>(pEvent
->pos().y());
147 aEvent
.mnCode
= GetKeyModCode(pEvent
->modifiers()) | GetMouseModCode(pEvent
->buttons());
150 if (eState
== ButtonKeyState::Pressed
)
151 nEventType
= SalEvent::MouseButtonDown
;
153 nEventType
= SalEvent::MouseButtonUp
;
154 rFrame
.CallCallback(nEventType
, &aEvent
);
157 void Qt5Widget::mousePressEvent(QMouseEvent
* pEvent
) { handleMousePressEvent(m_rFrame
, pEvent
); }
159 void Qt5Widget::mouseReleaseEvent(QMouseEvent
* pEvent
)
161 handleMouseReleaseEvent(m_rFrame
, pEvent
);
164 void Qt5Widget::mouseMoveEvent(QMouseEvent
* pEvent
)
166 QPoint point
= pEvent
->pos();
168 SalMouseEvent aEvent
;
169 aEvent
.mnTime
= pEvent
->timestamp();
170 aEvent
.mnX
= QGuiApplication::isLeftToRight() ? point
.x() : width() - point
.x();
171 aEvent
.mnY
= point
.y();
172 aEvent
.mnCode
= GetKeyModCode(pEvent
->modifiers()) | GetMouseModCode(pEvent
->buttons());
175 m_rFrame
.CallCallback(SalEvent::MouseMove
, &aEvent
);
179 void Qt5Widget::wheelEvent(QWheelEvent
* pEvent
)
181 SalWheelMouseEvent aEvent
;
183 aEvent
.mnTime
= pEvent
->timestamp();
184 aEvent
.mnX
= pEvent
->pos().x();
185 aEvent
.mnY
= pEvent
->pos().y();
186 aEvent
.mnCode
= GetKeyModCode(pEvent
->modifiers()) | GetMouseModCode(pEvent
->buttons());
188 // mouse wheel ticks are 120, which we map to 3 lines.
189 // we have to accumulate for touch scroll to keep track of the absolute delta.
191 int nDelta
= pEvent
->angleDelta().y(), lines
;
192 aEvent
.mbHorz
= nDelta
== 0;
195 nDelta
= (QGuiApplication::isLeftToRight() ? 1 : -1) * pEvent
->angleDelta().x();
200 lines
= m_nDeltaX
/ 40;
201 m_nDeltaX
= m_nDeltaX
% 40;
206 lines
= m_nDeltaY
/ 40;
207 m_nDeltaY
= m_nDeltaY
% 40;
210 aEvent
.mnDelta
= nDelta
;
211 aEvent
.mnNotchDelta
= nDelta
< 0 ? -1 : 1;
212 aEvent
.mnScrollLines
= std::abs(lines
);
214 m_rFrame
.CallCallback(SalEvent::WheelMouse
, &aEvent
);
218 void Qt5Widget::dragEnterEvent(QDragEnterEvent
* event
)
220 if (dynamic_cast<const Qt5MimeData
*>(event
->mimeData()))
223 event
->acceptProposedAction();
226 // also called when a drop is rejected
227 void Qt5Widget::dragLeaveEvent(QDragLeaveEvent
*) { m_rFrame
.handleDragLeave(); }
229 void Qt5Widget::dragMoveEvent(QDragMoveEvent
* pEvent
) { m_rFrame
.handleDragMove(pEvent
); }
231 void Qt5Widget::dropEvent(QDropEvent
* pEvent
) { m_rFrame
.handleDrop(pEvent
); }
233 void Qt5Widget::moveEvent(QMoveEvent
* pEvent
)
235 if (m_rFrame
.m_pTopLevel
)
238 m_rFrame
.maGeometry
.nX
= pEvent
->pos().x();
239 m_rFrame
.maGeometry
.nY
= pEvent
->pos().y();
240 m_rFrame
.CallCallback(SalEvent::Move
, nullptr);
243 void Qt5Widget::showEvent(QShowEvent
*)
245 QSize
aSize(m_rFrame
.GetQWidget()->size());
246 // forcing an immediate update somehow interferes with the hide + show
247 // sequence from Qt5Frame::SetModal, if the frame was already set visible,
248 // resulting in a hidden / unmapped window
249 SalPaintEvent
aPaintEvt(0, 0, aSize
.width(), aSize
.height());
250 m_rFrame
.CallCallback(SalEvent::Paint
, &aPaintEvt
);
253 void Qt5Widget::closeEvent(QCloseEvent
* /*pEvent*/)
255 m_rFrame
.CallCallback(SalEvent::Close
, nullptr);
258 static sal_uInt16
GetKeyCode(int keyval
, Qt::KeyboardModifiers modifiers
)
260 sal_uInt16 nCode
= 0;
261 if (keyval
>= Qt::Key_0
&& keyval
<= Qt::Key_9
)
262 nCode
= KEY_0
+ (keyval
- Qt::Key_0
);
263 else if (keyval
>= Qt::Key_A
&& keyval
<= Qt::Key_Z
)
264 nCode
= KEY_A
+ (keyval
- Qt::Key_A
);
265 else if (keyval
>= Qt::Key_F1
&& keyval
<= Qt::Key_F26
)
266 nCode
= KEY_F1
+ (keyval
- Qt::Key_F1
);
267 else if (modifiers
.testFlag(Qt::KeypadModifier
)
268 && (keyval
== Qt::Key_Period
|| keyval
== Qt::Key_Comma
))
269 // Qt doesn't use a special keyval for decimal separator ("," or ".")
270 // on numerical keypad, but sets Qt::KeypadModifier in addition
297 case Qt::Key_PageDown
:
298 nCode
= KEY_PAGEDOWN
;
308 // oddly enough, Qt doesn't send Shift-Tab event as 'Tab key pressed with Shift
309 // modifier' but as 'Backtab key pressed' (while its modifier bits are still
310 // set to Shift) -- so let's map both Key_Tab and Key_Backtab to VCL's KEY_TAB
311 case Qt::Key_Backtab
:
314 case Qt::Key_Backspace
:
315 nCode
= KEY_BACKSPACE
;
330 nCode
= KEY_SUBTRACT
;
332 case Qt::Key_Asterisk
:
333 nCode
= KEY_MULTIPLY
;
347 case Qt::Key_Greater
:
357 nCode
= KEY_CONTEXTMENU
;
371 case Qt::Key_AsciiTilde
:
374 case Qt::Key_QuoteLeft
:
375 nCode
= KEY_QUOTELEFT
;
377 case Qt::Key_BracketLeft
:
378 nCode
= KEY_BRACKETLEFT
;
380 case Qt::Key_BracketRight
:
381 nCode
= KEY_BRACKETRIGHT
;
383 case Qt::Key_Semicolon
:
384 nCode
= KEY_SEMICOLON
;
404 void Qt5Widget::commitText(Qt5Frame
& rFrame
, const QString
& aText
)
406 SalExtTextInputEvent aInputEvent
;
407 aInputEvent
.mpTextAttr
= nullptr;
408 aInputEvent
.mnCursorFlags
= 0;
409 aInputEvent
.maText
= toOUString(aText
);
410 aInputEvent
.mnCursorPos
= aInputEvent
.maText
.getLength();
412 SolarMutexGuard aGuard
;
413 vcl::DeletionListener
aDel(&rFrame
);
414 rFrame
.CallCallback(SalEvent::ExtTextInput
, &aInputEvent
);
415 if (!aDel
.isDeleted())
416 rFrame
.CallCallback(SalEvent::EndExtTextInput
, nullptr);
419 bool Qt5Widget::handleKeyEvent(Qt5Frame
& rFrame
, const QWidget
& rWidget
, QKeyEvent
* pEvent
,
420 const ButtonKeyState eState
)
422 sal_uInt16 nCode
= GetKeyCode(pEvent
->key(), pEvent
->modifiers());
423 if (eState
== ButtonKeyState::Pressed
&& nCode
== 0 && !pEvent
->text().isEmpty()
424 && rWidget
.testAttribute(Qt::WA_InputMethodEnabled
))
426 commitText(rFrame
, pEvent
->text());
432 aEvent
.mnCharCode
= (pEvent
->text().isEmpty() ? 0 : pEvent
->text().at(0).unicode());
434 aEvent
.mnCode
= nCode
;
435 aEvent
.mnCode
|= GetKeyModCode(pEvent
->modifiers());
437 QGuiApplication::inputMethod()->update(Qt::ImCursorRectangle
);
439 bool bStopProcessingKey
;
440 if (eState
== ButtonKeyState::Pressed
)
441 bStopProcessingKey
= rFrame
.CallCallback(SalEvent::KeyInput
, &aEvent
);
443 bStopProcessingKey
= rFrame
.CallCallback(SalEvent::KeyUp
, &aEvent
);
444 if (bStopProcessingKey
)
446 return bStopProcessingKey
;
449 bool Qt5Widget::handleEvent(Qt5Frame
& rFrame
, const QWidget
& rWidget
, QEvent
* pEvent
)
451 if (pEvent
->type() == QEvent::ShortcutOverride
)
453 // Accepted event disables shortcut activation,
454 // but enables keypress event.
455 // If event is not accepted and shortcut is successfully activated,
456 // KeyPress event is omitted.
458 // Instead of processing keyPressEvent, handle ShortcutOverride event,
459 // and if it's handled - disable the shortcut, it should have been activated.
460 // Don't process keyPressEvent generated after disabling shortcut since it was handled here.
461 // If event is not handled, don't accept it and let Qt activate related shortcut.
462 if (handleKeyEvent(rFrame
, rWidget
, static_cast<QKeyEvent
*>(pEvent
),
463 ButtonKeyState::Pressed
))
469 bool Qt5Widget::event(QEvent
* pEvent
)
471 return handleEvent(m_rFrame
, *this, pEvent
) || QWidget::event(pEvent
);
474 void Qt5Widget::keyReleaseEvent(QKeyEvent
* pEvent
)
476 if (!handleKeyReleaseEvent(m_rFrame
, *this, pEvent
))
477 QWidget::keyReleaseEvent(pEvent
);
480 void Qt5Widget::focusInEvent(QFocusEvent
*) { m_rFrame
.CallCallback(SalEvent::GetFocus
, nullptr); }
482 void Qt5Widget::focusOutEvent(QFocusEvent
*)
485 m_rFrame
.CallCallback(SalEvent::LoseFocus
, nullptr);
488 Qt5Widget::Qt5Widget(Qt5Frame
& rFrame
, Qt::WindowFlags f
)
489 : QWidget(Q_NULLPTR
, f
)
491 , m_bNonEmptyIMPreeditSeen(false)
496 setMouseTracking(true);
497 setFocusPolicy(Qt::StrongFocus
);
500 static ExtTextInputAttr
lcl_MapUndrelineStyle(QTextCharFormat::UnderlineStyle us
)
504 case QTextCharFormat::NoUnderline
:
505 return ExtTextInputAttr::NONE
;
506 case QTextCharFormat::DotLine
:
507 return ExtTextInputAttr::DottedUnderline
;
508 case QTextCharFormat::DashDotDotLine
:
509 case QTextCharFormat::DashDotLine
:
510 return ExtTextInputAttr::DashDotUnderline
;
511 case QTextCharFormat::WaveUnderline
:
512 return ExtTextInputAttr::GrayWaveline
;
514 return ExtTextInputAttr::Underline
;
518 void Qt5Widget::inputMethodEvent(QInputMethodEvent
* pEvent
)
520 if (!pEvent
->commitString().isEmpty())
521 commitText(m_rFrame
, pEvent
->commitString());
524 SalExtTextInputEvent aInputEvent
;
525 aInputEvent
.mpTextAttr
= nullptr;
526 aInputEvent
.mnCursorFlags
= 0;
527 aInputEvent
.maText
= toOUString(pEvent
->preeditString());
528 aInputEvent
.mnCursorPos
= 0;
530 const sal_Int32 nLength
= aInputEvent
.maText
.getLength();
531 const QList
<QInputMethodEvent::Attribute
>& rAttrList
= pEvent
->attributes();
532 std::vector
<ExtTextInputAttr
> aTextAttrs(std::max(sal_Int32(1), nLength
),
533 ExtTextInputAttr::NONE
);
534 aInputEvent
.mpTextAttr
= aTextAttrs
.data();
536 for (int i
= 0; i
< rAttrList
.size(); ++i
)
538 const QInputMethodEvent::Attribute
& rAttr
= rAttrList
.at(i
);
541 case QInputMethodEvent::TextFormat
:
543 QTextCharFormat aCharFormat
544 = qvariant_cast
<QTextFormat
>(rAttr
.value
).toCharFormat();
545 if (aCharFormat
.isValid())
547 ExtTextInputAttr aETIP
548 = lcl_MapUndrelineStyle(aCharFormat
.underlineStyle());
549 if (aCharFormat
.hasProperty(QTextFormat::BackgroundBrush
))
550 aETIP
|= ExtTextInputAttr::Highlight
;
551 if (aCharFormat
.fontStrikeOut())
552 aETIP
|= ExtTextInputAttr::RedText
;
553 for (int j
= rAttr
.start
; j
< rAttr
.start
+ rAttr
.length
; j
++)
554 aTextAttrs
[j
] = aETIP
;
558 case QInputMethodEvent::Cursor
:
560 aInputEvent
.mnCursorPos
= rAttr
.start
;
561 if (rAttr
.length
== 0)
562 aInputEvent
.mnCursorFlags
|= EXTTEXTINPUT_CURSOR_INVISIBLE
;
566 SAL_WARN("vcl.qt5", "Unhandled QInputMethodEvent attribute: "
567 << static_cast<int>(rAttr
.type
));
572 const bool bIsEmpty
= aInputEvent
.maText
.isEmpty();
573 if (m_bNonEmptyIMPreeditSeen
|| !bIsEmpty
)
575 SolarMutexGuard aGuard
;
576 vcl::DeletionListener
aDel(&m_rFrame
);
577 m_rFrame
.CallCallback(SalEvent::ExtTextInput
, &aInputEvent
);
578 if (!aDel
.isDeleted() && bIsEmpty
)
579 m_rFrame
.CallCallback(SalEvent::EndExtTextInput
, nullptr);
580 m_bNonEmptyIMPreeditSeen
= !bIsEmpty
;
587 static bool lcl_retrieveSurrounding(sal_Int32
& rPosition
, sal_Int32
& rAnchor
, QString
* pText
,
590 SolarMutexGuard aGuard
;
591 vcl::Window
* pFocusWin
= Application::GetFocusWindow();
595 uno::Reference
<accessibility::XAccessibleEditableText
> xText
;
598 uno::Reference
<accessibility::XAccessible
> xAccessible(pFocusWin
->GetAccessible());
599 if (xAccessible
.is())
600 xText
= FindFocusedEditableText(xAccessible
->getAccessibleContext());
602 catch (const uno::Exception
&)
604 TOOLS_WARN_EXCEPTION("vcl.qt5", "Exception in getting input method surrounding text");
610 rPosition
= xText
->getCaretPosition();
615 *pText
= toQString(xText
->getText());
617 sal_Int32 nSelStart
= xText
->getSelectionStart();
618 sal_Int32 nSelEnd
= xText
->getSelectionEnd();
619 if (nSelStart
== nSelEnd
)
627 if (rPosition
== nSelStart
)
632 *pSelection
= toQString(xText
->getSelectedText());
641 QVariant
Qt5Widget::inputMethodQuery(Qt::InputMethodQuery property
) const
645 case Qt::ImSurroundingText
:
648 sal_Int32 nCursorPos
, nAnchor
;
649 if (lcl_retrieveSurrounding(nCursorPos
, nAnchor
, &aText
, nullptr))
650 return QVariant(aText
);
653 case Qt::ImCursorPosition
:
655 sal_Int32 nCursorPos
, nAnchor
;
656 if (lcl_retrieveSurrounding(nCursorPos
, nAnchor
, nullptr, nullptr))
657 return QVariant(static_cast<int>(nCursorPos
));
660 case Qt::ImCursorRectangle
:
662 SalExtTextInputPosEvent aPosEvent
;
663 m_rFrame
.CallCallback(SalEvent::ExtTextInputPos
, &aPosEvent
);
665 QRect(aPosEvent
.mnX
, aPosEvent
.mnY
, aPosEvent
.mnWidth
, aPosEvent
.mnHeight
));
667 case Qt::ImAnchorPosition
:
669 sal_Int32 nCursorPos
, nAnchor
;
670 if (lcl_retrieveSurrounding(nCursorPos
, nAnchor
, nullptr, nullptr))
671 return QVariant(static_cast<int>(nAnchor
));
674 case Qt::ImCurrentSelection
:
677 sal_Int32 nCursorPos
, nAnchor
;
678 if (lcl_retrieveSurrounding(nCursorPos
, nAnchor
, nullptr, &aSelection
))
679 return QVariant(aSelection
);
683 return QWidget::inputMethodQuery(property
);
689 void Qt5Widget::endExtTextInput()
691 if (m_bNonEmptyIMPreeditSeen
)
693 m_rFrame
.CallCallback(SalEvent::EndExtTextInput
, nullptr);
694 m_bNonEmptyIMPreeditSeen
= false;
698 void Qt5Widget::changeEvent(QEvent
* pEvent
)
700 switch (pEvent
->type())
702 case QEvent::FontChange
:
704 case QEvent::PaletteChange
:
706 case QEvent::StyleChange
:
708 auto* pSalInst(static_cast<Qt5Instance
*>(GetSalData()->m_pInstance
));
710 pSalInst
->UpdateStyle(QEvent::FontChange
== pEvent
->type());
716 QWidget::changeEvent(pEvent
);
719 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */