forked from mozilla/gecko-dev
-
Notifications
You must be signed in to change notification settings - Fork 2
/
mozqwidget.cpp
646 lines (571 loc) · 19.7 KB
/
mozqwidget.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* vim: set ts=4 et sw=4 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include <QGraphicsSceneHoverEvent>
#include <QGraphicsSceneMouseEvent>
#if (QT_VERSION < QT_VERSION_CHECK(5, 0, 0))
#include <QInputContext>
#endif
#include <QtCore/QTimer>
// Solve conflict of qgl.h and GLDefs.h
#define GLdouble_defined 1
#include "mozqwidget.h"
#include "nsWindow.h"
#include "nsIObserverService.h"
#include "mozilla/Services.h"
#ifdef MOZ_ENABLE_QTMOBILITY
#include "mozqorientationsensorfilter.h"
#ifdef MOZ_X11
#include <QX11Info>
#include <X11/Xlib.h>
#include <X11/Xatom.h>
# undef KeyPress
# undef KeyRelease
# undef CursorShape
#endif //MOZ_X11
#endif //MOZ_ENABLE_QTMOBILITY
/*
Pure Qt is lacking a clear API to get the current state of the VKB (opened
or closed).
*/
static bool gKeyboardOpen = false;
/*
In case we could not open the keyboard, we will try again when the focus
event is sent. This can happen if the keyboard is asked for before the
window is focused. This global is used to track that case.
*/
static bool gFailedOpenKeyboard = false;
/*
For websites that focus editable elements during other operations for a very
short time, we add some decoupling to prevent the VKB from appearing and
reappearing for a very short time. This global is set when the keyboard should
be opened and if it is still set when a timer runs out, the VKB is really
shown.
*/
static bool gPendingVKBOpen = false;
/*
Contains the last preedit String, this is needed in order to generate KeyEvents
*/
static QString gLastPreeditString;
MozQWidget::MozQWidget(nsWindow* aReceiver, QGraphicsItem* aParent)
: mReceiver(aReceiver)
{
setParentItem(aParent);
#if (QT_VERSION >= QT_VERSION_CHECK(4, 6, 0))
setFlag(QGraphicsItem::ItemAcceptsInputMethod);
setAcceptTouchEvents(true);
#endif
setAcceptHoverEvents(true);
}
MozQWidget::~MozQWidget()
{
if (mReceiver)
mReceiver->QWidgetDestroyed();
}
void MozQWidget::paint(QPainter* aPainter, const QStyleOptionGraphicsItem* aOption, QWidget* aWidget /*= 0*/)
{
if (mReceiver)
mReceiver->DoPaint(aPainter, aOption, aWidget);
}
void MozQWidget::activate()
{
// ensure that the keyboard is hidden when we activate the window
hideVKB();
mReceiver->DispatchActivateEventOnTopLevelWindow();
}
void MozQWidget::deactivate()
{
// ensure that the keyboard is hidden when we deactivate the window
hideVKB();
mReceiver->DispatchDeactivateEventOnTopLevelWindow();
}
void MozQWidget::resizeEvent(QGraphicsSceneResizeEvent* aEvent)
{
mReceiver->OnResizeEvent(aEvent);
}
void MozQWidget::contextMenuEvent(QGraphicsSceneContextMenuEvent* aEvent)
{
mReceiver->contextMenuEvent(aEvent);
}
void MozQWidget::dragEnterEvent(QGraphicsSceneDragDropEvent* aEvent)
{
mReceiver->OnDragEnter(aEvent);
}
void MozQWidget::dragLeaveEvent(QGraphicsSceneDragDropEvent* aEvent)
{
mReceiver->OnDragLeaveEvent(aEvent);
}
void MozQWidget::dragMoveEvent(QGraphicsSceneDragDropEvent* aEvent)
{
mReceiver->OnDragMotionEvent(aEvent);
}
void MozQWidget::dropEvent(QGraphicsSceneDragDropEvent* aEvent)
{
mReceiver->OnDragDropEvent(aEvent);
}
void MozQWidget::focusInEvent(QFocusEvent* aEvent)
{
mReceiver->OnFocusInEvent(aEvent);
// The application requested the VKB during startup but did not manage
// to open it, because there was no focused window yet so we do it now by
// requesting the VKB without any timeout.
if (gFailedOpenKeyboard)
requestVKB(0, this);
}
#ifdef MOZ_ENABLE_QTMOBILITY
void MozQWidget::orientationChanged()
{
if (!scene() || !scene()->views().size()) {
return;
}
NS_ASSERTION(scene()->views().size() == 1, "Not exactly one view for our scene!");
QTransform& transform = MozQOrientationSensorFilter::GetRotationTransform();
QRect scrTrRect = transform.mapRect(scene()->views()[0]->rect());
setTransformOriginPoint(scene()->views()[0]->size().width() / 2, scene()->views()[0]->size().height() / 2);
scene()->views()[0]->setTransform(transform);
int orientation = MozQOrientationSensorFilter::GetWindowRotationAngle();
if (orientation == 0 || orientation == 180) {
setPos(0,0);
} else {
setPos(-(scrTrRect.size().width() - scrTrRect.size().height()) / 2,
(scrTrRect.size().width() - scrTrRect.size().height()) / 2);
}
resize(scrTrRect.size());
scene()->setSceneRect(QRectF(QPointF(0, 0), scrTrRect.size()));
#ifdef MOZ_X11
Display* display = QX11Info::display();
if (!display) {
return;
}
Atom orientationAngleAtom = XInternAtom(display, "_MEEGOTOUCH_ORIENTATION_ANGLE", False);
XChangeProperty(display, scene()->views()[0]->effectiveWinId(),
orientationAngleAtom, XA_CARDINAL, 32,
PropModeReplace, (unsigned char*)&orientation, 1);
#endif
}
#endif
void MozQWidget::focusOutEvent(QFocusEvent* aEvent)
{
mReceiver->OnFocusOutEvent(aEvent);
//OtherFocusReason most like means VKB was closed manual (done button)
if (aEvent->reason() == Qt::OtherFocusReason && gKeyboardOpen) {
hideVKB();
}
}
void MozQWidget::hoverEnterEvent(QGraphicsSceneHoverEvent* aEvent)
{
mReceiver->OnEnterNotifyEvent(aEvent);
}
void MozQWidget::hoverLeaveEvent(QGraphicsSceneHoverEvent* aEvent)
{
mReceiver->OnLeaveNotifyEvent(aEvent);
}
void MozQWidget::hoverMoveEvent(QGraphicsSceneHoverEvent* aEvent)
{
mReceiver->OnMotionNotifyEvent(aEvent->pos(), aEvent->modifiers());
}
void MozQWidget::keyPressEvent(QKeyEvent* aEvent)
{
#if (MOZ_PLATFORM_MAEMO == 6)
if (!gKeyboardOpen ||
//those might get sended as KeyEvents, even in 'NormalMode'
aEvent->key() == Qt::Key_Space ||
aEvent->key() == Qt::Key_Return ||
aEvent->key() == Qt::Key_Backspace) {
mReceiver->OnKeyPressEvent(aEvent);
}
#elif (MOZ_PLATFORM_MAEMO == 5)
// Below removed to prevent invertion of upper and lower case
// See bug 561234
// mReceiver->OnKeyPressEvent(aEvent);
#else
mReceiver->OnKeyPressEvent(aEvent);
#endif
}
void MozQWidget::keyReleaseEvent(QKeyEvent* aEvent)
{
#if (MOZ_PLATFORM_MAEMO == 6)
if (!gKeyboardOpen ||
//those might get sended as KeyEvents, even in 'NormalMode'
aEvent->key() == Qt::Key_Space ||
aEvent->key() == Qt::Key_Return ||
aEvent->key() == Qt::Key_Backspace) {
mReceiver->OnKeyReleaseEvent(aEvent);
}
return;
#elif (MOZ_PLATFORM_MAEMO == 5)
// Below line should be removed when bug 561234 is fixed
mReceiver->OnKeyPressEvent(aEvent);
#endif
mReceiver->OnKeyReleaseEvent(aEvent);
}
void MozQWidget::inputMethodEvent(QInputMethodEvent* aEvent)
{
QString currentPreeditString = aEvent->preeditString();
QString currentCommitString = aEvent->commitString();
//first check for some controllkeys send as text...
if (currentCommitString == " ") {
sendPressReleaseKeyEvent(Qt::Key_Space, currentCommitString.unicode());
} else if (currentCommitString == "\n") {
sendPressReleaseKeyEvent(Qt::Key_Return, currentCommitString.unicode());
} else if (currentCommitString.isEmpty()) {
//if its no controllkey than check if current Commit is empty
//if yes than we have some preedit text here
if (currentPreeditString.length() == 1 && gLastPreeditString.isEmpty()) {
//Preedit text can change its entire look'a'like
//check if length of new compared to the old is 1,
//means that its a new startup
sendPressReleaseKeyEvent(0, currentPreeditString.unicode());
} else if (currentPreeditString.startsWith(gLastPreeditString)) {
//Length was not 1 or not a new startup
//check if the current preedit starts with the last one,
//if so: Add new letters (note: this can be more then one new letter)
const QChar * text = currentPreeditString.unicode();
for (int i = gLastPreeditString.length(); i < currentPreeditString.length(); i++) {
sendPressReleaseKeyEvent(0, &text[i]);
}
} else {
//last possible case, we had a PreeditString which was now completely changed.
//first, check if just one letter was removed (normal Backspace case!)
//if so: just send the backspace
QString tempLastPre = gLastPreeditString;
tempLastPre.truncate(gLastPreeditString.length()-1);
if (currentPreeditString == tempLastPre) {
sendPressReleaseKeyEvent(Qt::Key_Backspace);
} else if (currentPreeditString != tempLastPre) {
//more than one character changed, so just renew everything
//delete all preedit
for (int i = 0; i < gLastPreeditString.length(); i++) {
sendPressReleaseKeyEvent(Qt::Key_Backspace);
}
//send new Preedit
const QChar * text = currentPreeditString.unicode();
for (int i = 0; i < currentPreeditString.length(); i++) {
sendPressReleaseKeyEvent(0, &text[i]);
}
}
}
} else if (gLastPreeditString != currentCommitString) {
//User commited something
if (currentCommitString.length() == 1 && gLastPreeditString.isEmpty()) {
//if commit string ist one and there is no Preedit String
//case i.e. when no error correction is enabled in the system (default meego.com)
sendPressReleaseKeyEvent(0, currentCommitString.unicode());
} else {
//There is a Preedit, first remove it
for (int i = 0; i < gLastPreeditString.length(); i++) {
sendPressReleaseKeyEvent(Qt::Key_Backspace);
}
//Now push commited String into
const QChar * text = currentCommitString.unicode();
for (int i = 0; i < currentCommitString.length(); i++) {
sendPressReleaseKeyEvent(0, &text[i]);
}
}
}
//save preedit for next round.
gLastPreeditString = currentPreeditString;
//pre edit is continues string of new chars pressed by the user.
//if pre edit is changing rapidly without commit string first then user choose some overed text
//if commitstring comes directly after, forget about it
QGraphicsWidget::inputMethodEvent(aEvent);
}
void MozQWidget::sendPressReleaseKeyEvent(int key,
const QChar* letter,
bool autorep,
ushort count)
{
Qt::KeyboardModifiers modifiers = Qt::NoModifier;
if (letter && letter->isUpper()) {
modifiers = Qt::ShiftModifier;
}
if (letter) {
// Handle as TextEvent
nsCompositionEvent start(true, NS_COMPOSITION_START, mReceiver);
mReceiver->DispatchEvent(&start);
nsTextEvent text(true, NS_TEXT_TEXT, mReceiver);
QString commitString = QString(*letter);
text.theText.Assign(commitString.utf16());
mReceiver->DispatchEvent(&text);
nsCompositionEvent end(true, NS_COMPOSITION_END, mReceiver);
mReceiver->DispatchEvent(&end);
return;
}
QKeyEvent press(QEvent::KeyPress, key, modifiers, QString(), autorep, count);
mReceiver->OnKeyPressEvent(&press);
QKeyEvent release(QEvent::KeyRelease, key, modifiers, QString(), autorep, count);
mReceiver->OnKeyReleaseEvent(&release);
}
void MozQWidget::mouseDoubleClickEvent(QGraphicsSceneMouseEvent* aEvent)
{
// Qt sends double click event, but not second press event.
mReceiver->OnButtonPressEvent(aEvent);
mReceiver->OnMouseDoubleClickEvent(aEvent);
}
void MozQWidget::mouseMoveEvent(QGraphicsSceneMouseEvent* aEvent)
{
mReceiver->OnMotionNotifyEvent(aEvent->pos(), aEvent->modifiers());
}
void MozQWidget::mousePressEvent(QGraphicsSceneMouseEvent* aEvent)
{
mReceiver->OnButtonPressEvent(aEvent);
}
void MozQWidget::mouseReleaseEvent(QGraphicsSceneMouseEvent* aEvent)
{
mReceiver->OnButtonReleaseEvent(aEvent);
}
bool MozQWidget::event ( QEvent * event )
{
// check receiver, since due to deleteLater() call it's possible, that
// events pass loop after receiver's destroy and while widget is still alive
if (!mReceiver)
return QGraphicsWidget::event(event);
#if (QT_VERSION >= QT_VERSION_CHECK(4, 6, 0))
switch (event->type())
{
case QEvent::TouchBegin:
case QEvent::TouchEnd:
case QEvent::TouchUpdate:
{
// Do not send this event to other handlers, this is needed
// to be able to receive the gesture events
bool handled = false;
mReceiver->OnTouchEvent(static_cast<QTouchEvent *>(event),handled);
return handled;
}
case (QEvent::Gesture):
{
bool handled = false;
mReceiver->OnGestureEvent(static_cast<QGestureEvent*>(event),handled);
return handled;
}
default:
break;
}
#endif
return QGraphicsWidget::event(event);
}
void MozQWidget::wheelEvent(QGraphicsSceneWheelEvent* aEvent)
{
mReceiver->OnScrollEvent(aEvent);
}
void MozQWidget::closeEvent(QCloseEvent* aEvent)
{
mReceiver->OnCloseEvent(aEvent);
}
void MozQWidget::hideEvent(QHideEvent* aEvent)
{
if (mReceiver) {
mReceiver->hideEvent(aEvent);
}
QGraphicsWidget::hideEvent(aEvent);
}
void MozQWidget::showEvent(QShowEvent* aEvent)
{
mReceiver->showEvent(aEvent);
QGraphicsWidget::showEvent(aEvent);
}
void MozQWidget::SetCursor(nsCursor aCursor)
{
Qt::CursorShape cursor = Qt::ArrowCursor;
switch(aCursor) {
case eCursor_standard:
cursor = Qt::ArrowCursor;
break;
case eCursor_wait:
cursor = Qt::WaitCursor;
break;
case eCursor_select:
cursor = Qt::IBeamCursor;
break;
case eCursor_hyperlink:
cursor = Qt::PointingHandCursor;
break;
case eCursor_ew_resize:
cursor = Qt::SplitHCursor;
break;
case eCursor_ns_resize:
cursor = Qt::SplitVCursor;
break;
case eCursor_nw_resize:
case eCursor_se_resize:
cursor = Qt::SizeBDiagCursor;
break;
case eCursor_ne_resize:
case eCursor_sw_resize:
cursor = Qt::SizeFDiagCursor;
break;
case eCursor_crosshair:
case eCursor_move:
cursor = Qt::SizeAllCursor;
break;
case eCursor_help:
cursor = Qt::WhatsThisCursor;
break;
case eCursor_copy:
case eCursor_alias:
break;
case eCursor_context_menu:
case eCursor_cell:
case eCursor_grab:
case eCursor_grabbing:
case eCursor_spinning:
case eCursor_zoom_in:
case eCursor_zoom_out:
default:
break;
}
setCursor(cursor);
}
void MozQWidget::SetCursor(const QPixmap& aCursor, int aHotX, int aHotY)
{
QCursor bitmapCursor(aCursor, aHotX, aHotY);
setCursor(bitmapCursor);
}
void MozQWidget::setModal(bool modal)
{
#if QT_VERSION >= 0x040600
setPanelModality(modal ? QGraphicsItem::SceneModal : QGraphicsItem::NonModal);
#else
LOG(("Modal QGraphicsWidgets not supported in Qt < 4.6\n"));
#endif
}
QVariant MozQWidget::inputMethodQuery(Qt::InputMethodQuery aQuery) const
{
// Additional MeeGo Touch queries, which do not depend on actually
// having a focused input field.
switch ((int) aQuery) {
case 10001: // VisualizationPriorityQuery.
// Tells if input method widget wants to have high priority
// for visualization. Input methods should honor this and stay
// out of widgets space.
// Return false, eg. the input method can overlap QGraphicsWKView.
return QVariant(false);
case 10003: // ImCorrectionEnabledQuery.
// Explicit correction enabling for text entries.
return QVariant(false);
case 10004: // ImModeQuery.
// Retrieval mode: normal (0), direct [minics hardware keyboard] (1) or proxy (2)
return QVariant::fromValue(0);
case 10006: // InputMethodToolbarQuery.
// Custom toolbar file name for text entry.
return QVariant();
}
// Standard Qt queries dependent on having a focused web text input.
switch (aQuery) {
case Qt::ImFont:
return QVariant(QFont());
case Qt::ImMaximumTextLength:
return QVariant(); // Means no limit.
}
// Additional MeeGo Touch queries dependent on having a focused web text input
switch ((int) aQuery) {
case 10002: // PreeditRectangleQuery.
// Retrieve bounding rectangle for current preedit text.
return QVariant(QRect());
}
return QVariant();
}
/**
Request the VKB and starts a timer with the given timeout in milliseconds.
If the request is not canceled when the timer runs out, the VKB is actually
shown.
*/
void MozQWidget::requestVKB(int aTimeout, QObject* aWidget)
{
if (!gPendingVKBOpen) {
gPendingVKBOpen = true;
if (aTimeout == 0 || !aWidget) {
showVKB();
} else {
QTimer::singleShot(aTimeout, aWidget, SLOT(showVKB()));
}
}
}
void MozQWidget::showVKB()
{
// skip showing of keyboard if not pending
if (!gPendingVKBOpen) {
return;
}
gPendingVKBOpen = false;
#if (QT_VERSION >= QT_VERSION_CHECK(4, 6, 0)) && (QT_VERSION < QT_VERSION_CHECK(5, 0, 0))
QWidget* focusWidget = qApp->focusWidget();
if (focusWidget) {
QInputContext *inputContext = qApp->inputContext();
if (!inputContext) {
NS_WARNING("Requesting SIP: but no input context");
return;
}
QEvent request(QEvent::RequestSoftwareInputPanel);
inputContext->filterEvent(&request);
focusWidget->setAttribute(Qt::WA_InputMethodEnabled, true);
inputContext->setFocusWidget(focusWidget);
gKeyboardOpen = true;
gFailedOpenKeyboard = false;
}
else
{
// No focused widget yet, so we have to open the VKB later on.
gFailedOpenKeyboard = true;
}
#else
LOG(("VKB not supported in Qt < 4.6\n"));
#endif
}
void MozQWidget::hideVKB()
{
if (gPendingVKBOpen) {
// do not really open
gPendingVKBOpen = false;
}
if (!gKeyboardOpen) {
return;
}
#if (QT_VERSION >= QT_VERSION_CHECK(4, 6, 0)) && (QT_VERSION < QT_VERSION_CHECK(5, 0, 0))
QInputContext *inputContext = qApp->inputContext();
if (!inputContext) {
NS_WARNING("Closing SIP: but no input context");
return;
}
QEvent request(QEvent::CloseSoftwareInputPanel);
inputContext->filterEvent(&request);
inputContext->reset();
gKeyboardOpen = false;
#else
LOG(("VKB not supported in Qt < 4.6\n"));
#endif
}
bool MozQWidget::isVKBOpen()
{
return gKeyboardOpen;
}
void
MozQWidget::NotifyVKB(const QRect& rect)
{
QRegion region(scene()->views()[0]->rect());
region -= rect;
QRectF bounds = mapRectFromScene(region.boundingRect());
nsCOMPtr<nsIObserverService> observerService = mozilla::services::GetObserverService();
if (observerService) {
QString rect = QString("{\"left\": %1, \"top\": %2, \"right\": %3, \"bottom\": %4}")
.arg(bounds.x()).arg(bounds.y()).arg(bounds.width()).arg(bounds.height());
observerService->NotifyObservers(nullptr, "softkb-change", rect.utf16());
}
}
void MozQWidget::SwitchToForeground()
{
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
if (!os)
return;
os->NotifyObservers(nullptr, "application-foreground", nullptr);
}
void MozQWidget::SwitchToBackground()
{
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
if (!os)
return;
os->NotifyObservers(nullptr, "application-background", nullptr);
}