1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
4 *
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/.
8 *
9 * This file incorporates work covered by the following license notice:
10 *
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 .
18 */
19
20 #include <Qt5Widget.hxx>
21 #include <Qt5Widget.moc>
22
23 #include <Qt5Frame.hxx>
24 #include <Qt5Graphics.hxx>
25 #include <Qt5Instance.hxx>
26 #include <Qt5SvpGraphics.hxx>
27 #include <Qt5Transferable.hxx>
28 #include <Qt5Tools.hxx>
29
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>
45
46 #include <cairo.h>
47 #include <vcl/commandevent.hxx>
48 #include <vcl/event.hxx>
49 #include <window.h>
50 #include <tools/diagnose_ex.h>
51
52 #include <com/sun/star/accessibility/XAccessibleContext.hpp>
53 #include <com/sun/star/accessibility/XAccessibleEditableText.hpp>
54
55 #if QT5_USING_X11
56 #define XK_MISCELLANY
57 #include <X11/keysymdef.h>
58 #endif
59
60 using namespace com::sun::star;
61
paintEvent(QPaintEvent * pEvent)62 void Qt5Widget::paintEvent(QPaintEvent* pEvent)
63 {
64 QPainter p(this);
65 if (!m_rFrame.m_bNullRegion)
66 p.setClipRegion(m_rFrame.m_aRegion);
67
68 QImage aImage;
69 if (m_rFrame.m_bUseCairo)
70 {
71 cairo_surface_t* pSurface = m_rFrame.m_pSurface.get();
72 cairo_surface_flush(pSurface);
73
74 aImage = QImage(cairo_image_surface_get_data(pSurface),
75 cairo_image_surface_get_width(pSurface),
76 cairo_image_surface_get_height(pSurface), Qt5_DefaultFormat32);
77 }
78 else
79 aImage = *m_rFrame.m_pQImage;
80
81 const qreal fRatio = m_rFrame.devicePixelRatioF();
82 aImage.setDevicePixelRatio(fRatio);
83 QRectF source(pEvent->rect().topLeft() * fRatio, pEvent->rect().size() * fRatio);
84 p.drawImage(pEvent->rect(), aImage, source);
85 }
86
resizeEvent(QResizeEvent * pEvent)87 void Qt5Widget::resizeEvent(QResizeEvent* pEvent)
88 {
89 const qreal fRatio = m_rFrame.devicePixelRatioF();
90 const int nWidth = ceil(pEvent->size().width() * fRatio);
91 const int nHeight = ceil(pEvent->size().height() * fRatio);
92
93 m_rFrame.maGeometry.nWidth = nWidth;
94 m_rFrame.maGeometry.nHeight = nHeight;
95
96 if (m_rFrame.m_bUseCairo)
97 {
98 if (m_rFrame.m_pSvpGraphics)
99 {
100 cairo_surface_t* pSurface
101 = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, nWidth, nHeight);
102 cairo_surface_set_user_data(pSurface, SvpSalGraphics::getDamageKey(),
103 &m_rFrame.m_aDamageHandler, nullptr);
104 m_rFrame.m_pSvpGraphics->setSurface(pSurface, basegfx::B2IVector(nWidth, nHeight));
105 UniqueCairoSurface old_surface(m_rFrame.m_pSurface.release());
106 m_rFrame.m_pSurface.reset(pSurface);
107
108 int min_width = qMin(cairo_image_surface_get_width(old_surface.get()), nWidth);
109 int min_height = qMin(cairo_image_surface_get_height(old_surface.get()), nHeight);
110
111 SalTwoRect rect(0, 0, min_width, min_height, 0, 0, min_width, min_height);
112
113 m_rFrame.m_pSvpGraphics->copySource(rect, old_surface.get());
114 }
115 }
116 else
117 {
118 QImage* pImage = nullptr;
119
120 if (m_rFrame.m_pQImage)
121 pImage = new QImage(m_rFrame.m_pQImage->copy(0, 0, nWidth, nHeight));
122 else
123 {
124 pImage = new QImage(nWidth, nHeight, Qt5_DefaultFormat32);
125 pImage->fill(Qt::transparent);
126 }
127
128 m_rFrame.m_pQt5Graphics->ChangeQImage(pImage);
129 m_rFrame.m_pQImage.reset(pImage);
130 }
131
132 m_rFrame.CallCallback(SalEvent::Resize, nullptr);
133 }
134
fillSalAbstractMouseEvent(const Qt5Frame & rFrame,const QInputEvent * pQEvent,const QPoint & rPos,Qt::MouseButtons eButtons,int nWidth,SalAbstractMouseEvent & aSalEvent)135 void Qt5Widget::fillSalAbstractMouseEvent(const Qt5Frame& rFrame, const QInputEvent* pQEvent,
136 const QPoint& rPos, Qt::MouseButtons eButtons, int nWidth,
137 SalAbstractMouseEvent& aSalEvent)
138 {
139 const qreal fRatio = rFrame.devicePixelRatioF();
140 const Point aPos = toPoint(rPos * fRatio);
141
142 aSalEvent.mnX = QGuiApplication::isLeftToRight() ? aPos.X() : round(nWidth * fRatio) - aPos.X();
143 aSalEvent.mnY = aPos.Y();
144 aSalEvent.mnTime = pQEvent->timestamp();
145 aSalEvent.mnCode = GetKeyModCode(pQEvent->modifiers()) | GetMouseModCode(eButtons);
146 }
147
148 #define FILL_SAME(rFrame, nWidth) \
149 fillSalAbstractMouseEvent(rFrame, pEvent, pEvent->pos(), pEvent->buttons(), nWidth, aEvent)
150
handleMouseButtonEvent(const Qt5Frame & rFrame,const QMouseEvent * pEvent,const ButtonKeyState eState)151 void Qt5Widget::handleMouseButtonEvent(const Qt5Frame& rFrame, const QMouseEvent* pEvent,
152 const ButtonKeyState eState)
153 {
154 SalMouseEvent aEvent;
155 FILL_SAME(rFrame, rFrame.GetQWidget()->width());
156
157 switch (pEvent->button())
158 {
159 case Qt::LeftButton:
160 aEvent.mnButton = MOUSE_LEFT;
161 break;
162 case Qt::MiddleButton:
163 aEvent.mnButton = MOUSE_MIDDLE;
164 break;
165 case Qt::RightButton:
166 aEvent.mnButton = MOUSE_RIGHT;
167 break;
168 default:
169 return;
170 }
171
172 SalEvent nEventType;
173 if (eState == ButtonKeyState::Pressed)
174 nEventType = SalEvent::MouseButtonDown;
175 else
176 nEventType = SalEvent::MouseButtonUp;
177 rFrame.CallCallback(nEventType, &aEvent);
178 }
179
mousePressEvent(QMouseEvent * pEvent)180 void Qt5Widget::mousePressEvent(QMouseEvent* pEvent) { handleMousePressEvent(m_rFrame, pEvent); }
181
mouseReleaseEvent(QMouseEvent * pEvent)182 void Qt5Widget::mouseReleaseEvent(QMouseEvent* pEvent)
183 {
184 handleMouseReleaseEvent(m_rFrame, pEvent);
185 }
186
mouseMoveEvent(QMouseEvent * pEvent)187 void Qt5Widget::mouseMoveEvent(QMouseEvent* pEvent)
188 {
189 SalMouseEvent aEvent;
190 FILL_SAME(m_rFrame, width());
191
192 aEvent.mnButton = 0;
193
194 m_rFrame.CallCallback(SalEvent::MouseMove, &aEvent);
195 pEvent->accept();
196 }
197
wheelEvent(QWheelEvent * pEvent)198 void Qt5Widget::wheelEvent(QWheelEvent* pEvent)
199 {
200 SalWheelMouseEvent aEvent;
201 #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
202 fillSalAbstractMouseEvent(m_rFrame, pEvent, pEvent->position().toPoint(), pEvent->buttons(),
203 width(), aEvent);
204 #else
205 fillSalAbstractMouseEvent(m_rFrame, pEvent, pEvent->pos(), pEvent->buttons(), width(), aEvent);
206 #endif
207
208 // mouse wheel ticks are 120, which we map to 3 lines.
209 // we have to accumulate for touch scroll to keep track of the absolute delta.
210
211 int nDelta = pEvent->angleDelta().y(), lines;
212 aEvent.mbHorz = nDelta == 0;
213 if (aEvent.mbHorz)
214 {
215 nDelta = (QGuiApplication::isLeftToRight() ? 1 : -1) * pEvent->angleDelta().x();
216 if (!nDelta)
217 return;
218
219 m_nDeltaX += nDelta;
220 lines = m_nDeltaX / 40;
221 m_nDeltaX = m_nDeltaX % 40;
222 }
223 else
224 {
225 m_nDeltaY += nDelta;
226 lines = m_nDeltaY / 40;
227 m_nDeltaY = m_nDeltaY % 40;
228 }
229
230 aEvent.mnDelta = nDelta;
231 aEvent.mnNotchDelta = nDelta < 0 ? -1 : 1;
232 aEvent.mnScrollLines = std::abs(lines);
233
234 m_rFrame.CallCallback(SalEvent::WheelMouse, &aEvent);
235 pEvent->accept();
236 }
237
dragEnterEvent(QDragEnterEvent * event)238 void Qt5Widget::dragEnterEvent(QDragEnterEvent* event)
239 {
240 if (dynamic_cast<const Qt5MimeData*>(event->mimeData()))
241 event->accept();
242 else
243 event->acceptProposedAction();
244 }
245
246 // also called when a drop is rejected
dragLeaveEvent(QDragLeaveEvent *)247 void Qt5Widget::dragLeaveEvent(QDragLeaveEvent*) { m_rFrame.handleDragLeave(); }
248
dragMoveEvent(QDragMoveEvent * pEvent)249 void Qt5Widget::dragMoveEvent(QDragMoveEvent* pEvent) { m_rFrame.handleDragMove(pEvent); }
250
dropEvent(QDropEvent * pEvent)251 void Qt5Widget::dropEvent(QDropEvent* pEvent) { m_rFrame.handleDrop(pEvent); }
252
moveEvent(QMoveEvent * pEvent)253 void Qt5Widget::moveEvent(QMoveEvent* pEvent)
254 {
255 if (m_rFrame.m_pTopLevel)
256 return;
257
258 const Point aPos = toPoint(pEvent->pos() * m_rFrame.devicePixelRatioF());
259 m_rFrame.maGeometry.nX = aPos.X();
260 m_rFrame.maGeometry.nY = aPos.Y();
261 m_rFrame.CallCallback(SalEvent::Move, nullptr);
262 }
263
showEvent(QShowEvent *)264 void Qt5Widget::showEvent(QShowEvent*)
265 {
266 QSize aSize(m_rFrame.GetQWidget()->size() * m_rFrame.devicePixelRatioF());
267 // forcing an immediate update somehow interferes with the hide + show
268 // sequence from Qt5Frame::SetModal, if the frame was already set visible,
269 // resulting in a hidden / unmapped window
270 SalPaintEvent aPaintEvt(0, 0, aSize.width(), aSize.height());
271 m_rFrame.CallCallback(SalEvent::Paint, &aPaintEvt);
272 }
273
closeEvent(QCloseEvent *)274 void Qt5Widget::closeEvent(QCloseEvent* /*pEvent*/)
275 {
276 m_rFrame.CallCallback(SalEvent::Close, nullptr);
277 }
278
GetKeyCode(int keyval,Qt::KeyboardModifiers modifiers)279 static sal_uInt16 GetKeyCode(int keyval, Qt::KeyboardModifiers modifiers)
280 {
281 sal_uInt16 nCode = 0;
282 if (keyval >= Qt::Key_0 && keyval <= Qt::Key_9)
283 nCode = KEY_0 + (keyval - Qt::Key_0);
284 else if (keyval >= Qt::Key_A && keyval <= Qt::Key_Z)
285 nCode = KEY_A + (keyval - Qt::Key_A);
286 else if (keyval >= Qt::Key_F1 && keyval <= Qt::Key_F26)
287 nCode = KEY_F1 + (keyval - Qt::Key_F1);
288 else if (modifiers.testFlag(Qt::KeypadModifier)
289 && (keyval == Qt::Key_Period || keyval == Qt::Key_Comma))
290 // Qt doesn't use a special keyval for decimal separator ("," or ".")
291 // on numerical keypad, but sets Qt::KeypadModifier in addition
292 nCode = KEY_DECIMAL;
293 else
294 {
295 switch (keyval)
296 {
297 case Qt::Key_Down:
298 nCode = KEY_DOWN;
299 break;
300 case Qt::Key_Up:
301 nCode = KEY_UP;
302 break;
303 case Qt::Key_Left:
304 nCode = KEY_LEFT;
305 break;
306 case Qt::Key_Right:
307 nCode = KEY_RIGHT;
308 break;
309 case Qt::Key_Home:
310 nCode = KEY_HOME;
311 break;
312 case Qt::Key_End:
313 nCode = KEY_END;
314 break;
315 case Qt::Key_PageUp:
316 nCode = KEY_PAGEUP;
317 break;
318 case Qt::Key_PageDown:
319 nCode = KEY_PAGEDOWN;
320 break;
321 case Qt::Key_Return:
322 case Qt::Key_Enter:
323 nCode = KEY_RETURN;
324 break;
325 case Qt::Key_Escape:
326 nCode = KEY_ESCAPE;
327 break;
328 case Qt::Key_Tab:
329 // oddly enough, Qt doesn't send Shift-Tab event as 'Tab key pressed with Shift
330 // modifier' but as 'Backtab key pressed' (while its modifier bits are still
331 // set to Shift) -- so let's map both Key_Tab and Key_Backtab to VCL's KEY_TAB
332 case Qt::Key_Backtab:
333 nCode = KEY_TAB;
334 break;
335 case Qt::Key_Backspace:
336 nCode = KEY_BACKSPACE;
337 break;
338 case Qt::Key_Space:
339 nCode = KEY_SPACE;
340 break;
341 case Qt::Key_Insert:
342 nCode = KEY_INSERT;
343 break;
344 case Qt::Key_Delete:
345 nCode = KEY_DELETE;
346 break;
347 case Qt::Key_Plus:
348 nCode = KEY_ADD;
349 break;
350 case Qt::Key_Minus:
351 nCode = KEY_SUBTRACT;
352 break;
353 case Qt::Key_Asterisk:
354 nCode = KEY_MULTIPLY;
355 break;
356 case Qt::Key_Slash:
357 nCode = KEY_DIVIDE;
358 break;
359 case Qt::Key_Period:
360 nCode = KEY_POINT;
361 break;
362 case Qt::Key_Comma:
363 nCode = KEY_COMMA;
364 break;
365 case Qt::Key_Less:
366 nCode = KEY_LESS;
367 break;
368 case Qt::Key_Greater:
369 nCode = KEY_GREATER;
370 break;
371 case Qt::Key_Equal:
372 nCode = KEY_EQUAL;
373 break;
374 case Qt::Key_Find:
375 nCode = KEY_FIND;
376 break;
377 case Qt::Key_Menu:
378 nCode = KEY_CONTEXTMENU;
379 break;
380 case Qt::Key_Help:
381 nCode = KEY_HELP;
382 break;
383 case Qt::Key_Undo:
384 nCode = KEY_UNDO;
385 break;
386 case Qt::Key_Redo:
387 nCode = KEY_REPEAT;
388 break;
389 case Qt::Key_Cancel:
390 nCode = KEY_F11;
391 break;
392 case Qt::Key_AsciiTilde:
393 nCode = KEY_TILDE;
394 break;
395 case Qt::Key_QuoteLeft:
396 nCode = KEY_QUOTELEFT;
397 break;
398 case Qt::Key_BracketLeft:
399 nCode = KEY_BRACKETLEFT;
400 break;
401 case Qt::Key_BracketRight:
402 nCode = KEY_BRACKETRIGHT;
403 break;
404 case Qt::Key_Semicolon:
405 nCode = KEY_SEMICOLON;
406 break;
407 case Qt::Key_Copy:
408 nCode = KEY_COPY;
409 break;
410 case Qt::Key_Cut:
411 nCode = KEY_CUT;
412 break;
413 case Qt::Key_Open:
414 nCode = KEY_OPEN;
415 break;
416 case Qt::Key_Paste:
417 nCode = KEY_PASTE;
418 break;
419 }
420 }
421
422 return nCode;
423 }
424
commitText(Qt5Frame & rFrame,const QString & aText)425 void Qt5Widget::commitText(Qt5Frame& rFrame, const QString& aText)
426 {
427 SalExtTextInputEvent aInputEvent;
428 aInputEvent.mpTextAttr = nullptr;
429 aInputEvent.mnCursorFlags = 0;
430 aInputEvent.maText = toOUString(aText);
431 aInputEvent.mnCursorPos = aInputEvent.maText.getLength();
432
433 SolarMutexGuard aGuard;
434 vcl::DeletionListener aDel(&rFrame);
435 rFrame.CallCallback(SalEvent::ExtTextInput, &aInputEvent);
436 if (!aDel.isDeleted())
437 rFrame.CallCallback(SalEvent::EndExtTextInput, nullptr);
438 }
439
handleKeyEvent(Qt5Frame & rFrame,const QWidget & rWidget,QKeyEvent * pEvent,const ButtonKeyState eState)440 bool Qt5Widget::handleKeyEvent(Qt5Frame& rFrame, const QWidget& rWidget, QKeyEvent* pEvent,
441 const ButtonKeyState eState)
442 {
443 sal_uInt16 nCode = GetKeyCode(pEvent->key(), pEvent->modifiers());
444 if (eState == ButtonKeyState::Pressed && nCode == 0 && pEvent->text().length() > 1
445 && rWidget.testAttribute(Qt::WA_InputMethodEnabled))
446 {
447 commitText(rFrame, pEvent->text());
448 pEvent->accept();
449 return true;
450 }
451
452 if (nCode == 0 && pEvent->text().isEmpty())
453 {
454 sal_uInt16 nModCode = GetKeyModCode(pEvent->modifiers());
455 SalKeyModEvent aModEvt;
456 aModEvt.mbDown = eState == ButtonKeyState::Pressed;
457 aModEvt.mnModKeyCode = ModKeyFlags::NONE;
458
459 #if QT5_USING_X11
460 if (QGuiApplication::platformName() == "xcb")
461 {
462 // pressing just the ctrl key leads to a keysym of XK_Control but
463 // the event state does not contain ControlMask. In the release
464 // event it's the other way round: it does contain the Control mask.
465 // The modifier mode therefore has to be adapted manually.
466 ModKeyFlags nExtModMask = ModKeyFlags::NONE;
467 sal_uInt16 nModMask = 0;
468 switch (pEvent->nativeVirtualKey())
469 {
470 case XK_Control_L:
471 nExtModMask = ModKeyFlags::LeftMod1;
472 nModMask = KEY_MOD1;
473 break;
474 case XK_Control_R:
475 nExtModMask = ModKeyFlags::RightMod1;
476 nModMask = KEY_MOD1;
477 break;
478 case XK_Alt_L:
479 nExtModMask = ModKeyFlags::LeftMod2;
480 nModMask = KEY_MOD2;
481 break;
482 case XK_Alt_R:
483 nExtModMask = ModKeyFlags::RightMod2;
484 nModMask = KEY_MOD2;
485 break;
486 case XK_Shift_L:
487 nExtModMask = ModKeyFlags::LeftShift;
488 nModMask = KEY_SHIFT;
489 break;
490 case XK_Shift_R:
491 nExtModMask = ModKeyFlags::RightShift;
492 nModMask = KEY_SHIFT;
493 break;
494 // Map Meta/Super keys to MOD3 modifier on all Unix systems
495 // except macOS
496 case XK_Meta_L:
497 case XK_Super_L:
498 nExtModMask = ModKeyFlags::LeftMod3;
499 nModMask = KEY_MOD3;
500 break;
501 case XK_Meta_R:
502 case XK_Super_R:
503 nExtModMask = ModKeyFlags::RightMod3;
504 nModMask = KEY_MOD3;
505 break;
506 }
507
508 if (eState == ButtonKeyState::Released)
509 {
510 // sending the old mnModKeyCode mask on release is needed to
511 // implement the writing direction switch with Ctrl + L/R-Shift
512 aModEvt.mnModKeyCode = rFrame.m_nKeyModifiers;
513 nModCode &= ~nModMask;
514 rFrame.m_nKeyModifiers &= ~nExtModMask;
515 }
516 else
517 {
518 nModCode |= nModMask;
519 rFrame.m_nKeyModifiers |= nExtModMask;
520 aModEvt.mnModKeyCode = rFrame.m_nKeyModifiers;
521 }
522 }
523 #endif
524 aModEvt.mnCode = nModCode;
525
526 rFrame.CallCallback(SalEvent::KeyModChange, &aModEvt);
527 return false;
528 }
529
530 // prevent interference of writing direction switch (Ctrl + L/R-Shift) with "normal" shortcuts
531 rFrame.m_nKeyModifiers = ModKeyFlags::NONE;
532
533 SalKeyEvent aEvent;
534 aEvent.mnCharCode = (pEvent->text().isEmpty() ? 0 : pEvent->text().at(0).unicode());
535 aEvent.mnRepeat = 0;
536 aEvent.mnCode = nCode;
537 aEvent.mnCode |= GetKeyModCode(pEvent->modifiers());
538
539 QGuiApplication::inputMethod()->update(Qt::ImCursorRectangle);
540
541 bool bStopProcessingKey;
542 if (eState == ButtonKeyState::Pressed)
543 bStopProcessingKey = rFrame.CallCallback(SalEvent::KeyInput, &aEvent);
544 else
545 bStopProcessingKey = rFrame.CallCallback(SalEvent::KeyUp, &aEvent);
546 if (bStopProcessingKey)
547 pEvent->accept();
548 return bStopProcessingKey;
549 }
550
handleEvent(Qt5Frame & rFrame,const QWidget & rWidget,QEvent * pEvent)551 bool Qt5Widget::handleEvent(Qt5Frame& rFrame, const QWidget& rWidget, QEvent* pEvent)
552 {
553 if (pEvent->type() == QEvent::ShortcutOverride)
554 {
555 // ignore non-spontaneous QEvent::ShortcutOverride events,
556 // since such an extra event is sent e.g. with Orca screen reader enabled,
557 // so that two events of that kind (the "real one" and a non-spontaneous one)
558 // would otherwise be processed, resulting in duplicate input as 'handleKeyEvent'
559 // is called below (s. tdf#122053)
560 if (!pEvent->spontaneous())
561 {
562 return false;
563 }
564
565 // Accepted event disables shortcut activation,
566 // but enables keypress event.
567 // If event is not accepted and shortcut is successfully activated,
568 // KeyPress event is omitted.
569 //
570 // Instead of processing keyPressEvent, handle ShortcutOverride event,
571 // and if it's handled - disable the shortcut, it should have been activated.
572 // Don't process keyPressEvent generated after disabling shortcut since it was handled here.
573 // If event is not handled, don't accept it and let Qt activate related shortcut.
574 if (handleKeyEvent(rFrame, rWidget, static_cast<QKeyEvent*>(pEvent),
575 ButtonKeyState::Pressed))
576 return true;
577 }
578 return false;
579 }
580
event(QEvent * pEvent)581 bool Qt5Widget::event(QEvent* pEvent)
582 {
583 return handleEvent(m_rFrame, *this, pEvent) || QWidget::event(pEvent);
584 }
585
keyReleaseEvent(QKeyEvent * pEvent)586 void Qt5Widget::keyReleaseEvent(QKeyEvent* pEvent)
587 {
588 if (!handleKeyReleaseEvent(m_rFrame, *this, pEvent))
589 QWidget::keyReleaseEvent(pEvent);
590 }
591
focusInEvent(QFocusEvent *)592 void Qt5Widget::focusInEvent(QFocusEvent*) { m_rFrame.CallCallback(SalEvent::GetFocus, nullptr); }
593
focusOutEvent(QFocusEvent *)594 void Qt5Widget::focusOutEvent(QFocusEvent*)
595 {
596 m_rFrame.m_nKeyModifiers = ModKeyFlags::NONE;
597 endExtTextInput();
598 m_rFrame.CallCallback(SalEvent::LoseFocus, nullptr);
599 }
600
Qt5Widget(Qt5Frame & rFrame,Qt::WindowFlags f)601 Qt5Widget::Qt5Widget(Qt5Frame& rFrame, Qt::WindowFlags f)
602 : QWidget(Q_NULLPTR, f)
603 , m_rFrame(rFrame)
604 , m_bNonEmptyIMPreeditSeen(false)
605 , m_nDeltaX(0)
606 , m_nDeltaY(0)
607 {
608 create();
609 setMouseTracking(true);
610 setFocusPolicy(Qt::StrongFocus);
611 }
612
lcl_MapUndrelineStyle(QTextCharFormat::UnderlineStyle us)613 static ExtTextInputAttr lcl_MapUndrelineStyle(QTextCharFormat::UnderlineStyle us)
614 {
615 switch (us)
616 {
617 case QTextCharFormat::NoUnderline:
618 return ExtTextInputAttr::NONE;
619 case QTextCharFormat::DotLine:
620 return ExtTextInputAttr::DottedUnderline;
621 case QTextCharFormat::DashDotDotLine:
622 case QTextCharFormat::DashDotLine:
623 return ExtTextInputAttr::DashDotUnderline;
624 case QTextCharFormat::WaveUnderline:
625 return ExtTextInputAttr::GrayWaveline;
626 default:
627 return ExtTextInputAttr::Underline;
628 }
629 }
630
inputMethodEvent(QInputMethodEvent * pEvent)631 void Qt5Widget::inputMethodEvent(QInputMethodEvent* pEvent)
632 {
633 if (!pEvent->commitString().isEmpty())
634 commitText(m_rFrame, pEvent->commitString());
635 else
636 {
637 SalExtTextInputEvent aInputEvent;
638 aInputEvent.mpTextAttr = nullptr;
639 aInputEvent.mnCursorFlags = 0;
640 aInputEvent.maText = toOUString(pEvent->preeditString());
641 aInputEvent.mnCursorPos = 0;
642
643 const sal_Int32 nLength = aInputEvent.maText.getLength();
644 const QList<QInputMethodEvent::Attribute>& rAttrList = pEvent->attributes();
645 std::vector<ExtTextInputAttr> aTextAttrs(std::max(sal_Int32(1), nLength),
646 ExtTextInputAttr::NONE);
647 aInputEvent.mpTextAttr = aTextAttrs.data();
648
649 for (int i = 0; i < rAttrList.size(); ++i)
650 {
651 const QInputMethodEvent::Attribute& rAttr = rAttrList.at(i);
652 switch (rAttr.type)
653 {
654 case QInputMethodEvent::TextFormat:
655 {
656 QTextCharFormat aCharFormat
657 = qvariant_cast<QTextFormat>(rAttr.value).toCharFormat();
658 if (aCharFormat.isValid())
659 {
660 ExtTextInputAttr aETIP
661 = lcl_MapUndrelineStyle(aCharFormat.underlineStyle());
662 if (aCharFormat.hasProperty(QTextFormat::BackgroundBrush))
663 aETIP |= ExtTextInputAttr::Highlight;
664 if (aCharFormat.fontStrikeOut())
665 aETIP |= ExtTextInputAttr::RedText;
666 for (int j = rAttr.start; j < rAttr.start + rAttr.length; j++)
667 {
668 SAL_WARN_IF(j >= static_cast<int>(aTextAttrs.size()), "vcl.qt5",
669 "QInputMethodEvent::Attribute out of range. Broken range: "
670 << rAttr.start << "," << rAttr.start + rAttr.length
671 << " Legal range: 0," << aTextAttrs.size());
672 if (j >= static_cast<int>(aTextAttrs.size()))
673 break;
674 aTextAttrs[j] = aETIP;
675 }
676 }
677 break;
678 }
679 case QInputMethodEvent::Cursor:
680 {
681 aInputEvent.mnCursorPos = rAttr.start;
682 if (rAttr.length == 0)
683 aInputEvent.mnCursorFlags |= EXTTEXTINPUT_CURSOR_INVISIBLE;
684 break;
685 }
686 default:
687 SAL_WARN("vcl.qt5", "Unhandled QInputMethodEvent attribute: "
688 << static_cast<int>(rAttr.type));
689 break;
690 }
691 }
692
693 const bool bIsEmpty = aInputEvent.maText.isEmpty();
694 if (m_bNonEmptyIMPreeditSeen || !bIsEmpty)
695 {
696 SolarMutexGuard aGuard;
697 vcl::DeletionListener aDel(&m_rFrame);
698 m_rFrame.CallCallback(SalEvent::ExtTextInput, &aInputEvent);
699 if (!aDel.isDeleted() && bIsEmpty)
700 m_rFrame.CallCallback(SalEvent::EndExtTextInput, nullptr);
701 m_bNonEmptyIMPreeditSeen = !bIsEmpty;
702 }
703 }
704
705 pEvent->accept();
706 }
707
lcl_retrieveSurrounding(sal_Int32 & rPosition,sal_Int32 & rAnchor,QString * pText,QString * pSelection)708 static bool lcl_retrieveSurrounding(sal_Int32& rPosition, sal_Int32& rAnchor, QString* pText,
709 QString* pSelection)
710 {
711 SolarMutexGuard aGuard;
712 vcl::Window* pFocusWin = Application::GetFocusWindow();
713 if (!pFocusWin)
714 return false;
715
716 uno::Reference<accessibility::XAccessibleEditableText> xText;
717 try
718 {
719 uno::Reference<accessibility::XAccessible> xAccessible(pFocusWin->GetAccessible());
720 if (xAccessible.is())
721 xText = FindFocusedEditableText(xAccessible->getAccessibleContext());
722 }
723 catch (const uno::Exception&)
724 {
725 TOOLS_WARN_EXCEPTION("vcl.qt5", "Exception in getting input method surrounding text");
726 }
727
728 if (xText.is())
729 {
730 rPosition = xText->getCaretPosition();
731 if (rPosition != -1)
732 {
733 if (pText)
734 *pText = toQString(xText->getText());
735
736 sal_Int32 nSelStart = xText->getSelectionStart();
737 sal_Int32 nSelEnd = xText->getSelectionEnd();
738 if (nSelStart == nSelEnd)
739 {
740 rAnchor = rPosition;
741 }
742 else
743 {
744 if (rPosition == nSelStart)
745 rAnchor = nSelEnd;
746 else
747 rAnchor = nSelStart;
748 if (pSelection)
749 *pSelection = toQString(xText->getSelectedText());
750 }
751 return true;
752 }
753 }
754
755 return false;
756 }
757
inputMethodQuery(Qt::InputMethodQuery property) const758 QVariant Qt5Widget::inputMethodQuery(Qt::InputMethodQuery property) const
759 {
760 switch (property)
761 {
762 case Qt::ImSurroundingText:
763 {
764 QString aText;
765 sal_Int32 nCursorPos, nAnchor;
766 if (lcl_retrieveSurrounding(nCursorPos, nAnchor, &aText, nullptr))
767 return QVariant(aText);
768 return QVariant();
769 }
770 case Qt::ImCursorPosition:
771 {
772 sal_Int32 nCursorPos, nAnchor;
773 if (lcl_retrieveSurrounding(nCursorPos, nAnchor, nullptr, nullptr))
774 return QVariant(static_cast<int>(nCursorPos));
775 return QVariant();
776 }
777 case Qt::ImCursorRectangle:
778 {
779 const qreal fRatio = m_rFrame.devicePixelRatioF();
780 SalExtTextInputPosEvent aPosEvent;
781 m_rFrame.CallCallback(SalEvent::ExtTextInputPos, &aPosEvent);
782 return QVariant(QRect(aPosEvent.mnX / fRatio, aPosEvent.mnY / fRatio,
783 aPosEvent.mnWidth / fRatio, aPosEvent.mnHeight / fRatio));
784 }
785 case Qt::ImAnchorPosition:
786 {
787 sal_Int32 nCursorPos, nAnchor;
788 if (lcl_retrieveSurrounding(nCursorPos, nAnchor, nullptr, nullptr))
789 return QVariant(static_cast<int>(nAnchor));
790 return QVariant();
791 }
792 case Qt::ImCurrentSelection:
793 {
794 QString aSelection;
795 sal_Int32 nCursorPos, nAnchor;
796 if (lcl_retrieveSurrounding(nCursorPos, nAnchor, nullptr, &aSelection))
797 return QVariant(aSelection);
798 return QVariant();
799 }
800 default:
801 return QWidget::inputMethodQuery(property);
802 }
803 }
804
endExtTextInput()805 void Qt5Widget::endExtTextInput()
806 {
807 if (m_bNonEmptyIMPreeditSeen)
808 {
809 m_rFrame.CallCallback(SalEvent::EndExtTextInput, nullptr);
810 m_bNonEmptyIMPreeditSeen = false;
811 }
812 }
813
changeEvent(QEvent * pEvent)814 void Qt5Widget::changeEvent(QEvent* pEvent)
815 {
816 switch (pEvent->type())
817 {
818 case QEvent::FontChange:
819 [[fallthrough]];
820 case QEvent::PaletteChange:
821 [[fallthrough]];
822 case QEvent::StyleChange:
823 {
824 auto* pSalInst(static_cast<Qt5Instance*>(GetSalData()->m_pInstance));
825 assert(pSalInst);
826 pSalInst->UpdateStyle(QEvent::FontChange == pEvent->type());
827 break;
828 }
829 default:
830 break;
831 }
832 QWidget::changeEvent(pEvent);
833 }
834
835 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
836