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