1
/****************************************************************************
2
**
3
** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
4
** All rights reserved.
5
** Contact: Nokia Corporation (qt-info@nokia.com)
6
**
7
** This file is part of the QtGui module of the Qt Toolkit.
8
**
9
** $QT_BEGIN_LICENSE:LGPL$
10
** No Commercial Usage
11
** This file contains pre-release code and may not be distributed.
12
** You may use this file in accordance with the terms and conditions
13
** contained in the Technology Preview License Agreement accompanying
14
** this package.
15
**
16
** GNU Lesser General Public License Usage
17
** Alternatively, this file may be used under the terms of the GNU Lesser
18
** General Public License version 2.1 as published by the Free Software
19
** Foundation and appearing in the file LICENSE.LGPL included in the
20
** packaging of this file.  Please review the following information to
21
** ensure the GNU Lesser General Public License version 2.1 requirements
22
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23
**
24
** In addition, as a special exception, Nokia gives you certain additional
25
** rights.  These rights are described in the Nokia Qt LGPL Exception
26
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27
**
28
** If you have questions regarding the use of this file, please contact
29
** Nokia at qt-info@nokia.com.
30
**
31
**
32
**
33
**
34
**
35
**
36
**
37
**
38
** $QT_END_LICENSE$
39
**
40
****************************************************************************/
41
#include "qdebug.h"
42
#include "qhildoninputcontext_p.h"
43
#include "qpointer.h"
44
#include "qapplication.h"
45
#include "qclipboard.h"
46
#include "qplaintextedit.h"
47
#include "qlineedit.h"
48
#include "qtextedit.h"
49
#include "qtextbrowser.h"
50
#include "kernel/qevent_p.h"       //QKeyEventEx
51
#include "kernel/qapplication_p.h" //QApplicationPrivate::areXInputEventsUsed()
52
#include "qinputcontext.h"
53
#include "qtextcodec.h"
54
#include "qtextboundaryfinder.h"
55
#include <private/qkeymapper_p.h>
56
57
#include "qgraphicsview.h"
58
#ifndef QT_NO_GRAPHICSVIEW
59
#include "qgraphicsitem.h"
60
#include "qgraphicsproxywidget.h"
61
#include "qgraphicsscene.h"
62
#include "qgraphicswidget.h"
63
#endif
64
65
#ifdef Q_WS_MAEMO_5
66
67
#define GDK_ISO_ENTER  0xfe34
68
#define COMPOSE_KEY    Qt::Key_Multi_key   // "Ch" key
69
#define LEVEL_KEY      Qt::Key_AltGr       //"Fn" key
70
71
#define STATE_LEVEL_MASK    1 << 7
72
#define STATE_CONTROL_MASK  1 << 2
73
#define STATE_SHIFT_MASK    1 << 0
74
75
//Keyboard layout levels
76
#define NUMERIC_LEVEL 2
77
#define LOCKABLE_LEVEL 4
78
79
80
extern bool qt_sendSpontaneousEvent(QObject*, QEvent*); //qapplication_x11.cpp
81
82
#define HIM_DEBUG
83
84
static inline bool qHimDebugEnabled()
85
{
86
    static const bool debug = !qgetenv("QT_HIM_DEBUG").isEmpty();
87
    return debug;
88
}
89
90
#ifdef HIM_DEBUG
91
#  define qHimDebug  if (!qHimDebugEnabled()) {} else qDebug
92
#else
93
#  define qHimDebug  while (false) qDebug
94
#endif
95
96
static const char *debugNameForCommunicationId(HildonIMCommunication id)
97
{
98
#ifdef HIM_DEBUG
99
    static const char * const mapping[] = {
100
        "HILDON_IM_CONTEXT_HANDLE_ENTER",
101
        "HILDON_IM_CONTEXT_HANDLE_TAB",
102
        "HILDON_IM_CONTEXT_HANDLE_BACKSPACE",
103
        "HILDON_IM_CONTEXT_HANDLE_SPACE",
104
        "HILDON_IM_CONTEXT_CONFIRM_SENTENCE_START",
105
        "HILDON_IM_CONTEXT_FLUSH_PREEDIT",
106
        "HILDON_IM_CONTEXT_CANCEL_PREEDIT",
107
        "HILDON_IM_CONTEXT_BUFFERED_MODE",
108
        "HILDON_IM_CONTEXT_DIRECT_MODE",
109
        "HILDON_IM_CONTEXT_REDIRECT_MODE",
110
        "HILDON_IM_CONTEXT_SURROUNDING_MODE",
111
        "HILDON_IM_CONTEXT_PREEDIT_MODE",
112
        "HILDON_IM_CONTEXT_CLIPBOARD_COPY",
113
        "HILDON_IM_CONTEXT_CLIPBOARD_CUT",
114
        "HILDON_IM_CONTEXT_CLIPBOARD_PASTE",
115
        "HILDON_IM_CONTEXT_CLIPBOARD_SELECTION_QUERY",
116
        "HILDON_IM_CONTEXT_REQUEST_SURROUNDING",
117
        "HILDON_IM_CONTEXT_REQUEST_SURROUNDING_FULL",
118
        "HILDON_IM_CONTEXT_WIDGET_CHANGED",
119
        "HILDON_IM_CONTEXT_OPTION_CHANGED",
120
        "HILDON_IM_CONTEXT_CLEAR_STICKY",
121
        "HILDON_IM_CONTEXT_ENTER_ON_FOCUS",
122
        "HILDON_IM_CONTEXT_SPACE_AFTER_COMMIT",
123
        "HILDON_IM_CONTEXT_NO_SPACE_AFTER_COMMIT"
124
    };
125
    if (unsigned(id) < (sizeof(mapping) / sizeof(mapping[0]))) {
126
        return mapping[id];
127
    } else {
128
        static char name[] = "ID 00";
129
        name[3] = '0' + (id / 10);
130
        name[4] = '0' + (id % 10);
131
        return name;
132
    }
133
#endif
134
    return 0;
135
}
136
137
138
#define LOGMESSAGE1(x)       qHimDebug() << x;
139
#define LOGMESSAGE2(x, y)    qHimDebug() << x << "(" << y << ")";
140
#define LOGMESSAGE3(x, y, z) qHimDebug() << x << "(" << y << " " << z << ")";
141
142
143
QMap<QWidget *, QHIMProxyWidget *> QHIMProxyWidget::proxies;
144
145
QHIMProxyWidget::QHIMProxyWidget(QWidget *widget)
146
    : QWidget(0), w(widget)
147
{
148
    setAttribute(Qt::WA_InputMethodEnabled);
149
    setAttribute(Qt::WA_NativeWindow);
150
    createWinId();
151
    connect(w, SIGNAL(destroyed()), this, SLOT(widgetWasDestroyed()));
152
}
153
154
QHIMProxyWidget::~QHIMProxyWidget()
155
{
156
}
157
158
QWidget *QHIMProxyWidget::widget() const
159
{
160
    return w;
161
}
162
163
QHIMProxyWidget *QHIMProxyWidget::proxyFor(QWidget *w)
164
{
165
    QHIMProxyWidget *proxy = qobject_cast<QHIMProxyWidget *>(w);
166
167
    if (!proxy)
168
        proxy = proxies.value(w);
169
170
    if (!proxy) {
171
        proxy = new QHIMProxyWidget(w);
172
        proxies.insert(w, proxy);
173
    }
174
    //qWarning() << "Using HIM Proxy widget" << proxy << "for widget" << w << "isnative: " << proxy->testAttribute(Qt::WA_NativeWindow) << " / " << w->testAttribute(Qt::WA_NativeWindow);
175
176
    return proxy;
177
}
178
179
void QHIMProxyWidget::widgetWasDestroyed()
180
{
181
    proxies.remove(w);
182
    delete this;
183
}
184
185
186
/*! XkbLookupKeySym ( X11->display, event->nativeScanCode(), HILDON_IM_SHIFT_STICKY_MASK, &mods_rtrn, sym_rtrn)
187
 */
188
static QString translateKeycodeAndState(KeyCode key, uint state, quint32 &keysym){
189
    uint mods;
190
    KeySym *ks = reinterpret_cast<KeySym*>(&keysym);
191
    if ( XkbLookupKeySym ( X11->display, key, state, &mods, ks) )
192
        return QKeyMapperPrivate::maemo5TranslateKeySym(*ks);
193
    else
194
        return QString();
195
}
196
197
static Window findHildonIm()
198
{
199
    union
200
    {
201
        Window *win;
202
        unsigned char *val;
203
    } value;
204
205
    Window result = 0;
206
    ulong n = 0;
207
    ulong extra = 0;
208
    int format = 0;
209
    Atom realType;
210
211
    int status = XGetWindowProperty(X11->display, QX11Info::appRootWindow(),
212
                    ATOM(_HILDON_IM_WINDOW), 0L, 1L, 0,
213
                    XA_WINDOW, &realType, &format,
214
                    &n, &extra, (unsigned char **) &value.val);
215
216
    if (status == Success && realType == XA_WINDOW
217
          && format == HILDON_IM_WINDOW_ID_FORMAT && n == 1 && value.win != 0) {
218
        result = value.win[0];
219
        XFree(value.val);
220
    } else {
221
        qWarning("QHildonInputContext: Unable to get the Hildon IM window id");
222
    }
223
224
    return result;
225
}
226
227
228
229
/*! Send a key event to the IM, which makes it available to the plugins
230
 */
231
static void sendKeyEvent(QWidget *widget, QEvent::Type type, uint state, uint keyval, quint16 keycode)
232
{
233
    int gdkEventType;
234
    Window w = findHildonIm();
235
236
    if (!w)
237
        return;
238
239
    //Translate QEvent::Type in GDK_Event
240
    switch (type){
241
        case QEvent::KeyPress:
242
            gdkEventType = 8;
243
        break;
244
        case QEvent::KeyRelease:
245
            gdkEventType = 9;
246
        break;
247
        default:
248
            qWarning("QHildonInputContext: Event type not allowed");
249
            return;
250
    }
251
252
    XEvent ev;
253
    memset(&ev, 0, sizeof(XEvent));
254
255
    ev.xclient.type = ClientMessage;
256
    ev.xclient.window = w;
257
    ev.xclient.message_type = ATOM(_HILDON_IM_KEY_EVENT);
258
    ev.xclient.format = HILDON_IM_KEY_EVENT_FORMAT;
259
260
    HildonIMKeyEventMessage *msg = reinterpret_cast<HildonIMKeyEventMessage *>(&ev.xclient.data);
261
    msg->input_window = QHIMProxyWidget::proxyFor(widget)->winId();
262
263
    msg->type = gdkEventType;
264
    msg->state = state;
265
    msg->keyval = keyval;
266
    msg->hardware_keycode = keycode;
267
268
    XSendEvent(X11->display, w, false, 0, &ev);
269
    XSync( X11->display, false );
270
}
271
272
static quint32 dead_key_to_unicode_combining_character(int /*qtkeycode*/)
273
{
274
  quint32 combining = 0; //Unicode Hex value
275
276
#if 0
277
  //TODO Diablo - Not required in Fremantle
278
  switch (qtkeycode)
279
  {
280
    case Qt::Key_Dead_Grave:            combining = 0x0300; break;
281
    case Qt::Key_Dead_Acute:            combining = 0x0301; break;
282
    case Qt::Key_Dead_Circumflex:       combining = 0x0302; break;
283
    case Qt::Key_Dead_Tilde:            combining = 0x0303; break;
284
    case Qt::Key_Dead_Macron:           combining = 0x0304; break;
285
    case Qt::Key_Dead_Breve:            combining = 0x032e; break;
286
    case Qt::Key_Dead_Abovedot:         combining = 0x0307; break;
287
    case Qt::Key_Dead_Diaeresis:        combining = 0x0308; break;
288
    case Qt::Key_Dead_Abovering:        combining = 0x030a; break;
289
    case Qt::Key_Dead_Doubleacute:      combining = 0x030b; break;
290
    case Qt::Key_Dead_Caron:            combining = 0x030c; break;
291
    case Qt::Key_Dead_Cedilla:          combining = 0x0327; break;
292
    case Qt::Key_Dead_Ogonek:           combining = 0x0328; break;
293
    case Qt::Key_Dead_Iota:             combining = 0; break; /* Cannot be combined */
294
    case Qt::Key_Dead_Voiced_Sound:     combining = 0; break; /* Cannot be combined */
295
    case Qt::Key_Dead_Semivoiced_Sound: combining = 0; break; /* Cannot be combined */
296
    case Qt::Key_Dead_Belowdot:         combining = 0x0323; break;
297
    case Qt::Key_Dead_Hook:             combining = 0x0309; break;
298
    case Qt::Key_Dead_Horn:             combining = 0x031b; break;
299
    default: combining = 0; break; /* Unknown dead key */
300
  }
301
#endif
302
303
  return combining;
304
}
305
306
/*! Sends the key as a spontaneous event.
307
 */
308
static void sendKey(QWidget *keywidget, int qtCode)
309
{
310
    QPointer<QWidget> guard = keywidget;
311
312
    KeySym keysym = NoSymbol;
313
    int keycode;
314
315
    switch (qtCode){
316
        case Qt::Key_Enter:
317
            keycode = 36;
318
        break;
319
        case Qt::Key_Tab:
320
            keycode = 66;
321
        break;
322
        case Qt::Key_Backspace:
323
            keycode = 22;
324
        break;
325
        default:
326
        qWarning("keycode not allowed");
327
        return;
328
    }
329
330
    keysym = XKeycodeToKeysym(X11->display, keycode, 0);
331
332
    QKeyEventEx click(QEvent::KeyPress, qtCode, Qt::NoModifier , QString(), false, 1, keycode, keysym, 0);
333
    qt_sendSpontaneousEvent(keywidget, &click);
334
335
    // in case the widget was destroyed when the key went down
336
    if (guard.isNull()){
337
        return;
338
    }
339
340
    QKeyEventEx release(QEvent::KeyRelease, qtCode, Qt::NoModifier , QString(), false, 1, keycode, keysym, 0);
341
    qt_sendSpontaneousEvent(keywidget, &release);
342
}
343
344
/*!
345
 */
346
static void answerClipboardSelectionQuery(QWidget *widget)
347
{
348
    bool hasSelection = !widget->inputMethodQuery(Qt::ImCurrentSelection).toString().isEmpty();
349
350
    XEvent xev;
351
    Window w = findHildonIm();
352
353
    memset(&xev, 0, sizeof(xev));
354
    xev.xclient.type = ClientMessage;
355
    xev.xclient.window = w;
356
    xev.xclient.message_type = ATOM(_HILDON_IM_CLIPBOARD_SELECTION_REPLY);
357
    xev.xclient.format = HILDON_IM_CLIPBOARD_SELECTION_REPLY_FORMAT;
358
    xev.xclient.data.l[0] = hasSelection;
359
360
    XSendEvent(X11->display, w, false, 0, &xev);
361
}
362
363
364
KeySym getKeySymForLevel(int keycode, int level ){
365
    XkbDescPtr xkbDesc = XkbGetMap(X11->display, XkbAllClientInfoMask, XkbUseCoreKbd);
366
    if (!xkbDesc)
367
        return NoSymbol;
368
369
    KeySym keySym = XkbKeySymEntry(xkbDesc, keycode, level, 0);
370
371
    //Check for a not repated keysym
372
    KeySym keySymTest = XkbKeySymEntry(xkbDesc, keycode, 0, 1);
373
    if (keySym == keySymTest)
374
        return NoSymbol;
375
376
    return keySym;
377
}
378
379
QHildonInputContext::QHildonInputContext(QObject* parent)
380
    : QInputContext(parent),
381
      timerId(-1), mask(0), options(0),
382
      triggerMode(HILDON_IM_TRIGGER_NONE),
383
      commitMode(HILDON_IM_COMMIT_REDIRECT),
384
      lastCommitMode(HILDON_IM_COMMIT_REDIRECT),
385
      inputMode(HILDON_GTK_INPUT_MODE_FULL),
386
      textCursorPosOnPress(0), autoUpper(false),
387
      lastInternalChange(false), spaceAfterCommit(false)
388
{
389
}
390
391
QHildonInputContext::~QHildonInputContext()
392
{
393
    sendHildonCommand(HILDON_IM_HIDE);
394
}
395
396
QString QHildonInputContext::identifierName()
397
{
398
    return QLatin1String("hildon");
399
}
400
401
QString QHildonInputContext::language()
402
{
403
    //TODO GConf /apps/osso/inputmethod/hildon-im-languages
404
    return QString();
405
}
406
407
/*! \internal
408
 *  Resolves the focus for a widget inside a QGraphicsView.
409
 *  Returns the Widget really holding the focus in this case.
410
 */
411
QWidget *resolveFocusWidget(QWidget *w)
412
{
413
#ifndef QT_NO_GRAPHICSVIEW
414
    while (QGraphicsView *view = qobject_cast<QGraphicsView *>(w)) {
415
416
        QGraphicsScene *scene = view->scene();
417
        if (scene) {
418
            QGraphicsItem *item = scene->focusItem();
419
            QGraphicsProxyWidget *proxy = qgraphicsitem_cast<QGraphicsProxyWidget *>(item);
420
            if (proxy && proxy->widget() && proxy->widget()->focusWidget() ) {
421
                w = proxy->widget()->focusWidget();
422
            } else {
423
                break;
424
            }
425
        } else {
426
            break;
427
        }
428
    }
429
#endif
430
431
    return w;
432
}
433
434
QWidget *QHildonInputContext::focusWidget() const
435
{
436
    return realFocus ? realFocus : lastFocus;
437
}
438
439
/*!\internal
440
reset the UI state
441
 */
442
void QHildonInputContext::reset()
443
{
444
    qHimDebug() << "HIM: reset()";
445
446
    if (realFocus)
447
        sendHildonCommand(HILDON_IM_CLEAR, realFocus);
448
449
    cancelPreedit();
450
451
    //Reset internals
452
    mask = 0;
453
    lastInternalChange = false;
454
}
455
456
bool QHildonInputContext::isComposing() const
457
{
458
    return false;
459
}
460
461
void QHildonInputContext::setFocusWidget(QWidget *w)
462
{
463
    // As soon as the virtual keyboard is mapped by the X11 server,
464
    // it is also activated, which essentially steals our focus.
465
    // The same happens for the symbol picker.
466
    // This is a bug in the HIM, that we try to work around here.
467
    lastFocus = realFocus;
468
469
    // Another work around for the GraphicsView.
470
    // In case of a Widget inside a GraphicsViewProxyWidget we need to remember
471
    // that it had the focus
472
    realFocus = resolveFocusWidget(w);
473
474
    if (realFocus) {
475
        Qt::InputMethodHints hints = realFocus->inputMethodHints();
476
477
        // restrictions
478
        if ((hints & Qt::ImhExclusiveInputMask) == Qt::ImhDialableCharactersOnly) {
479
            inputMode = HILDON_GTK_INPUT_MODE_TELE;
480
        } else if (((hints & Qt::ImhExclusiveInputMask) == (Qt::ImhDigitsOnly | Qt::ImhUppercaseOnly)) ||
481
                   ((hints & Qt::ImhExclusiveInputMask) == (Qt::ImhDigitsOnly | Qt::ImhLowercaseOnly))) {
482
            inputMode = HILDON_GTK_INPUT_MODE_HEXA;
483
        } else if ((hints & Qt::ImhExclusiveInputMask) == Qt::ImhDigitsOnly) {
484
            inputMode = HILDON_GTK_INPUT_MODE_NUMERIC;
485
        } else if ((hints & Qt::ImhExclusiveInputMask) == Qt::ImhFormattedNumbersOnly) {
486
            inputMode = HILDON_GTK_INPUT_MODE_NUMERIC | HILDON_GTK_INPUT_MODE_SPECIAL;
487
        } else {
488
            inputMode = HILDON_GTK_INPUT_MODE_FULL;
489
        }
490
491
        // behavior flags
492
        if (hints & Qt::ImhHiddenText) {
493
            inputMode |= HILDON_GTK_INPUT_MODE_INVISIBLE;
494
        } else {
495
            // no auto upper case or predictive text for passwords
496
            if (!(hints & Qt::ImhNoAutoUppercase))
497
                inputMode |= HILDON_GTK_INPUT_MODE_AUTOCAP;
498
            if (!(hints & Qt::ImhNoPredictiveText))
499
                inputMode |= HILDON_GTK_INPUT_MODE_DICTIONARY;
500
        }
501
502
        // multi-line support
503
        // TODO: this really needs to fixed in Qt
504
        if (qobject_cast<QTextEdit *>(realFocus) || qobject_cast<QPlainTextEdit *>(realFocus))
505
            inputMode |= HILDON_GTK_INPUT_MODE_MULTILINE;
506
507
        qHimDebug("Mapped hint: 0x%x to mode: 0x%x", int(hints), int(inputMode));
508
    } else {
509
        inputMode = 0;
510
    }
511
512
    QInputContext::setFocusWidget(w);
513
514
    qHimDebug() << "HIM: setFocusWidget: " << w << " (real: " << realFocus << " / last: " << lastFocus << ")";
515
}
516
517
bool QHildonInputContext::filterEvent(const QEvent *event)
518
{
519
     QWidget *w = realFocus;
520
     if (!w)
521
         return false;
522
523
    switch (event->type()){
524
    case QEvent::RequestSoftwareInputPanel:{
525
        //On the device, these events are sent at the same time of the TabletRelease ones
526
        triggerMode = HILDON_IM_TRIGGER_FINGER;
527
528
        // workaround for a very weird interaction between QLineEdit (which
529
        // changes its internal editing:yes/no state on focus out) and the
530
        // Hildon fullscreen keyboard, which steals the focus from the
531
        // application (NOT when it shows up, but as only soon as the user
532
        // clicks on any button within the keyboard)
533
        if (QLineEdit *le = qobject_cast<QLineEdit *>(w)) {
534
            if (le->echoMode() == QLineEdit::PasswordEchoOnEdit)
535
                le->clear();
536
        }
537
538
        sendHildonCommand(HILDON_IM_SETNSHOW, realFocus);
539
        return true;
540
    }
541
    case QEvent::KeyPress:
542
    case QEvent::KeyRelease:
543
        triggerMode = HILDON_IM_TRIGGER_KEYBOARD;
544
        return filterKeyPress(w, static_cast<const QKeyEvent *>(event));
545
546
    default:
547
        break;
548
    }
549
    return QInputContext::filterEvent(event);
550
}
551
552
//TODO
553
void QHildonInputContext::update()
554
{
555
    qHimDebug() << "HIM: update(): lastInternalChange =" << lastInternalChange;
556
557
    if (lastInternalChange) {
558
        //Autocase update
559
        checkSentenceStart();
560
        lastInternalChange = false;
561
    }
562
}
563
564
/*! \internal
565
Filters spontaneous keyevents then elaborates them and updates the Hildon Main UI
566
 *  via XMessages. In some cases it creates and posts a new keyevent
567
 *  as no spontaneous event.
568
 */
569
bool QHildonInputContext::filterKeyPress(QWidget *keywidget, const QKeyEvent *event){
570
571
    //Avoid to filter events generated by this function.
572
    // Also ignore non-extended events, since we can't handle those below.
573
    if (!event->spontaneous() || !event->hasExtendedInfo())
574
        return false;
575
576
    const quint32 state = event->nativeModifiers();
577
    const quint32 keycode = event->nativeScanCode();
578
    quint32 keysym= event->nativeVirtualKey();
579
    const int qtkeycode = event->key();
580
581
    //qHimDebug("HIM: filterKeyPress Mask: %x state: %x options: %x keycode: %d keysym: %x QtKey: %x",
582
    //          mask, state, options, keycode, keysym, qtkeycode);
583
584
    //Drop auto repeated keys for COMPOSE_KEY
585
    if (qtkeycode == COMPOSE_KEY && event->isAutoRepeat()){
586
        return true;
587
    }
588
589
    //TODO MOVE
590
    static QWidget* lastKeywidget = 0;
591
    static int lastQtkeycode = 0;
592
    static qint32 combiningChar = 0; //Unicode rappresentation of the dead key.
593
594
    QString commitString; //String to commit to the Key Widget
595
596
    //Reset static vars when the widget change.
597
    if (keywidget != lastKeywidget){
598
        mask = 0;
599
        lastKeywidget = keywidget;
600
        lastQtkeycode = 0;
601
        combiningChar = 0;
602
    }
603
604
    if (!qtkeycode)
605
        return true;
606
607
    //1. A dead key will not be immediately commited, but combined with the next key
608
    if (qtkeycode >= Qt::Key_Dead_Grave && qtkeycode <= Qt::Key_Dead_Horn)
609
        mask |= HILDON_IM_DEAD_KEY_MASK;
610
    else
611
        mask &= ~HILDON_IM_DEAD_KEY_MASK;
612
613
    if (mask & HILDON_IM_DEAD_KEY_MASK && combiningChar == 0)
614
    {
615
        combiningChar = dead_key_to_unicode_combining_character(qtkeycode);//### WORKS? IMPROVE?
616
        return true;
617
    }
618
619
    /*2. Pressing any key while the compose key is pressed will keep that
620
     *   character from being directly submitted to the application. This
621
     *   allows the IM process to override the interpretation of the key
622
     */
623
    if (qtkeycode == COMPOSE_KEY)
624
    {
625
        if (event->type() == QEvent::KeyPress)
626
            mask |= HILDON_IM_COMPOSE_MASK;
627
        else
628
            mask &= ~HILDON_IM_COMPOSE_MASK;
629
    }
630
631
    // 3 Sticky and locking keys initialization
632
    if (event->type() == QEvent::KeyRelease)
633
    {
634
        if (qtkeycode == Qt::Key_Shift )
635
        {
636
            setMaskState(&mask,
637
                         HILDON_IM_SHIFT_LOCK_MASK,
638
                         HILDON_IM_SHIFT_STICKY_MASK,
639
                         lastQtkeycode == Qt::Key_Shift);
640
        }else if (qtkeycode == LEVEL_KEY){
641
            setMaskState(&mask,
642
                         HILDON_IM_LEVEL_LOCK_MASK,
643
                         HILDON_IM_LEVEL_STICKY_MASK,
644
                         lastQtkeycode == LEVEL_KEY);
645
        }
646
    }
647
648
    //Update lastQtkeycode.
649
    lastQtkeycode=qtkeycode;
650
651
    if (qtkeycode == Qt::Key_Tab){
652
        commitString = QLatin1String("\t");
653
    }
654
655
    /* 5. When the level key is in sticky or locked state, translate the
656
     *    keyboard state as if that level key was being held down.
657
     */
658
    if ((mask & (HILDON_IM_LEVEL_STICKY_MASK | HILDON_IM_LEVEL_LOCK_MASK)) ||
659
        (state & STATE_LEVEL_MASK)) {
660
        commitString = translateKeycodeAndState(keycode, STATE_LEVEL_MASK, keysym);
661
    }
662
663
    /* If the input mode is strictly numeric and the digits are level
664
     *  shifted on the layout, it's not necessary for the level key to
665
     *  be pressed at all.
666
     */
667
    else if ((options & HILDON_IM_AUTOLEVEL_NUMERIC) &&
668
             ((inputMode & HILDON_GTK_INPUT_MODE_FULL) == HILDON_GTK_INPUT_MODE_NUMERIC)) {
669
        KeySym ks = getKeySymForLevel(keycode, NUMERIC_LEVEL);
670
        QString string = QKeyMapperPrivate::maemo5TranslateKeySym(ks);
671
672
        if (!string.isEmpty()) {
673
            keysym = ks;
674
            commitString = string;
675
        }
676
    }
677
    /* The input is forced to a predetermined level
678
     */
679
    else if (options & HILDON_IM_LOCK_LEVEL)
680
    {
681
        KeySym ks = getKeySymForLevel(keycode, LOCKABLE_LEVEL);
682
        QString string = QKeyMapperPrivate::maemo5TranslateKeySym(ks);
683
684
        if (!string.isEmpty()){
685
            keysym = ks;
686
            commitString = string;
687
        }
688
    }
689
    /* Hardware keyboard autocapitalization  */
690
    if (autoUpper && inputMode & HILDON_GTK_INPUT_MODE_AUTOCAP)
691
    {
692
        qHimDebug() << "AutoCAP";
693
        QChar currentChar;
694
        KeySym lower = NoSymbol;
695
        KeySym upper = NoSymbol;
696
697
        if (commitString.isEmpty()){
698
            QString ks = QKeyMapperPrivate::maemo5TranslateKeySym(keysym);
699
            if (!ks.isEmpty())
700
                currentChar = ks.at(0);
701
        }else{
702
            currentChar = commitString.at(0);
703
        }
704
705
        XConvertCase(keysym, &lower, &upper);
706
707
        if (currentChar.isPrint()){
708
            if (state & STATE_SHIFT_MASK){
709
                currentChar = currentChar.toLower();
710
                keysym = lower;
711
            } else {
712
                currentChar = currentChar.toUpper();
713
                keysym = upper;
714
            }
715
            commitString = QString(currentChar); //sent to the widget
716
        }
717
    }
718
719
    //6. Shift lock or holding the shift down forces uppercase, ignoring autocap
720
    if (mask & HILDON_IM_SHIFT_LOCK_MASK || state & STATE_SHIFT_MASK)
721
    {
722
        KeySym lower = NoSymbol;
723
        KeySym upper = NoSymbol;
724
        XConvertCase(keysym, &lower, &upper);
725
        QString tempStr = QKeyMapperPrivate::maemo5TranslateKeySym(upper);
726
        if (!tempStr.isEmpty())
727
            commitString = tempStr.at(0);
728
    }else if (mask & HILDON_IM_SHIFT_STICKY_MASK){
729
        KeySym lower = NoSymbol;
730
        KeySym upper = NoSymbol;
731
        QString tempStr = QKeyMapperPrivate::maemo5TranslateKeySym(keysym);
732
        QChar currentChar;
733
        if (!tempStr.isEmpty()){
734
          currentChar = tempStr.at(0);
735
736
            /* Simulate shift key being held down in sticky state for non-printables  */
737
            if ( currentChar.isPrint() ){
738
                /*  For printable characters sticky shift negates the case,
739
                 *  including any autocapitalization changes
740
                 */
741
                if ( currentChar.isUpper() ){
742
                    currentChar = currentChar.toLower();
743
                    lower = lower;
744
                }else{
745
                    currentChar = currentChar.toUpper();
746
                    upper = upper;
747
                }
748
                commitString = QString(currentChar); //sent to the widget
749
            }
750
        }
751
    }
752
753
    //F. word completion manipulation (for fremantle)
754
    if (event->type() == QEvent::KeyPress &&
755
        lastCommitMode == HILDON_IM_COMMIT_PREEDIT &&
756
        !preEditBuffer.isNull())
757
    {
758
        switch (qtkeycode){
759
            case Qt::Key_Right:{
760
                commitPreeditBuffer();
761
                return true;
762
            }
763
            case Qt::Key_Backspace:
764
            case Qt::Key_Up:
765
            case Qt::Key_Down:
766
            case Qt::Key_Left:{
767
                cancelPreedit();
768
                return true;
769
           }
770
771
           case Qt::Key_Return:
772
           case Qt::Key_Enter: {
773
                cancelPreedit();
774
                break;
775
           }
776
           default: {
777
               if (keysym == GDK_ISO_ENTER)
778
                   cancelPreedit();
779
               break;
780
           }
781
        }
782
    }
783
784
    //7. Sticky and lock state reset
785
    if (event->type() == QEvent::KeyRelease)
786
    {
787
        if (qtkeycode != Qt::Key_Shift )
788
        {
789
            /* If not locked, pressing any character resets shift state */
790
            if ((mask & HILDON_IM_SHIFT_LOCK_MASK) == 0)
791
            {
792
                mask &= ~HILDON_IM_SHIFT_STICKY_MASK;
793
            }
794
        }
795
        if (qtkeycode != LEVEL_KEY)
796
        {
797
            /* If not locked, pressing any character resets level state */
798
            if ((mask & HILDON_IM_LEVEL_LOCK_MASK) == 0)
799
            {
800
                mask &= ~HILDON_IM_LEVEL_STICKY_MASK;
801
            }
802
        }
803
    }
804
805
    if (event->type() == QEvent::KeyRelease || state & STATE_CONTROL_MASK)
806
    {
807
        //QString debug = QLatin1String("Sending state=0x%1 keysym=0x%2 keycode=%3");
808
        //LOGMESSAGE2(" - ", debug.arg(state,0,16).arg(keysym,0,16).arg(keycode));
809
810
        sendKeyEvent(keywidget, event->type(), state, keysym, keycode);
811
        return false;
812
    }
813
814
815
    /* 8. Pressing a dead key twice, or if followed by a space, inputs
816
     *    the dead key's character representation
817
     */
818
    if ((mask & HILDON_IM_DEAD_KEY_MASK || qtkeycode == Qt::Key_Space) && combiningChar)
819
    {
820
        qint32 last;
821
        last = dead_key_to_unicode_combining_character (qtkeycode);
822
        if ((last == combiningChar) || qtkeycode == Qt::Key_Space)
823
        {
824
            commitString = QString(combiningChar);
825
        }else{
826
            commitString = QString::fromUtf8(XKeysymToString(keysym));
827
        }
828
        combiningChar = 0;
829
    }else{
830
        /* Regular keypress */
831
        if (mask & HILDON_IM_COMPOSE_MASK)
832
        {
833
            sendKeyEvent(keywidget, event->type(),state, keysym, keycode);
834
            return true;
835
        }else{
836
            if ( commitString.isEmpty() && qtkeycode != Qt::Key_Backspace){
837
                //LOGMESSAGE3(" - ", "text sent to IM", event->text())
838
                commitString = QString(event->text());
839
            }
840
        }
841
    }
842
843
    /* Control keys should not produce commitString, if they do it's a bug
844
       on keymap side and we have to workaround this here */
845
    if (qtkeycode == Qt::Key_Return || qtkeycode == Qt::Key_Enter ||
846
        keysym == GDK_ISO_ENTER || qtkeycode == Qt::Key_Backspace) {
847
        commitString = QString();
848
        lastInternalChange = true;
849
    } else if (qtkeycode == Qt::Key_Shift || qtkeycode == Qt::Key_AltGr ||
850
               qtkeycode == Qt::Key_Control) {
851
        commitString = QString();
852
    }
853
854
    if (!commitString.isEmpty()){
855
        //entering a new character cleans the preedit buffer
856
        cancelPreedit();
857
858
        /* Pressing a dead key followed by a regular key combines to form
859
         * an accented character
860
         */
861
        if (combiningChar){ //FIXME
862
            commitString.append(combiningChar);//This will be sent to the widget
863
            const char *charStr = qPrintable(commitString);
864
            keysym = XStringToKeysym(charStr); //This will be sent to the IM
865
        }
866
867
        //Create the new event with the elaborate information,
868
        //then it adds the event to the events queue
869
        {
870
            QEvent::Type type = event->type();
871
            Qt::KeyboardModifiers modifiers= event->modifiers();
872
            //WARNING the qt keycode has not been updated!!
873
            QKeyEventEx *ke= new QKeyEventEx(type, keycode, modifiers, commitString, false, commitString.size(), keycode, keysym, state);
874
            QCoreApplication::postEvent(keywidget,ke);
875
        }
876
877
        //Send the new keysym
878
        sendKeyEvent(keywidget, event->type(), state, keysym, keycode);
879
#if 0
880
        /* Non-printable characters invalidate any previous dead keys */
881
        if (qtkeycode != Qt::Key_Shift)
882
            combiningChar=0;
883
#endif
884
        lastInternalChange = true;
885
        return true;
886
    } else {
887
        //Send the new keysym
888
        sendKeyEvent(keywidget, event->type(), state, keysym, keycode);
889
        return false;
890
    }
891
}
892
893
void QHildonInputContext::setCommitMode(HildonIMCommitMode mode, bool clearPreEdit)
894
{
895
    if (commitMode != mode) {
896
        if (clearPreEdit)
897
            preEditBuffer.clear();
898
        lastCommitMode = commitMode;
899
    }
900
    commitMode = mode;
901
}
902
903
904
905
/*! \internal
906
Filters the XClientMessages sent by QApplication_x11
907
 */
908
bool QHildonInputContext::x11FilterEvent(QWidget *keywidget, XEvent *event)
909
{
910
    if (QHIMProxyWidget *proxy = qobject_cast<QHIMProxyWidget *>(keywidget))
911
        keywidget = proxy->widget();
912
913
    if (event->xclient.message_type == ATOM(_HILDON_IM_INSERT_UTF8) &&
914
        event->xclient.format == HILDON_IM_INSERT_UTF8_FORMAT) {
915
        qHimDebug() << "HIM: x11FilterEvent( HILDON_IM_INSERT_UTF8_FORMAT )";
916
917
        HildonIMInsertUtf8Message *msg = reinterpret_cast<HildonIMInsertUtf8Message *>(&event->xclient.data);
918
        insertUtf8(msg->msg_flag, QString::fromUtf8(msg->utf8_str));
919
        return true;
920
    } else if (event->xclient.message_type == ATOM(_HILDON_IM_COM)) {
921
        HildonIMComMessage *msg = (HildonIMComMessage *)&event->xclient.data;
922
        options = msg->options;
923
924
        qHimDebug() << "HIM: x11FilterEvent( _HILDON_IM_COM /" << debugNameForCommunicationId(msg->type) << ")";
925
926
        switch (msg->type) {
927
        //Handle Keys msgs
928
        case HILDON_IM_CONTEXT_HANDLE_ENTER:
929
            sendKey(keywidget, Qt::Key_Enter);
930
            return true;
931
        case HILDON_IM_CONTEXT_HANDLE_TAB:
932
            sendKey(keywidget, Qt::Key_Tab);
933
            return true;
934
        case HILDON_IM_CONTEXT_HANDLE_BACKSPACE:
935
            sendKey(keywidget, Qt::Key_Backspace);
936
            return true;
937
        case HILDON_IM_CONTEXT_HANDLE_SPACE:
938
            insertUtf8(HILDON_IM_MSG_CONTINUE, QChar(Qt::Key_Space));
939
            commitPreeditBuffer();
940
            return true;
941
942
        //Handle Clipboard msgs
943
        case HILDON_IM_CONTEXT_CLIPBOARD_SELECTION_QUERY:
944
            answerClipboardSelectionQuery(keywidget);
945
            return true;
946
        case HILDON_IM_CONTEXT_CLIPBOARD_PASTE:
947
            if (QClipboard *clipboard = QApplication::clipboard()) {
948
                QInputMethodEvent e;
949
                e.setCommitString(clipboard->text());
950
                QApplication::sendEvent(keywidget, &e);
951
            }
952
            return true;
953
        case HILDON_IM_CONTEXT_CLIPBOARD_COPY:
954
            if (QClipboard *clipboard = QApplication::clipboard())
955
                clipboard->setText(keywidget->inputMethodQuery(Qt::ImCurrentSelection).toString());
956
            return true;
957
        case HILDON_IM_CONTEXT_CLIPBOARD_CUT:
958
            if (QClipboard *clipboard = QApplication::clipboard()) {
959
                clipboard->setText(keywidget->inputMethodQuery(Qt::ImCurrentSelection).toString());
960
                QInputMethodEvent ev;
961
                QApplication::sendEvent(keywidget, &ev);
962
            }
963
            return true;
964
965
        //Handle commit mode msgs
966
        case HILDON_IM_CONTEXT_DIRECT_MODE:
967
            setCommitMode(HILDON_IM_COMMIT_DIRECT);
968
            return true;
969
        case HILDON_IM_CONTEXT_BUFFERED_MODE:
970
            setCommitMode(HILDON_IM_COMMIT_BUFFERED);
971
            return true;
972
        case HILDON_IM_CONTEXT_REDIRECT_MODE:
973
            setCommitMode(HILDON_IM_COMMIT_REDIRECT);
974
            clearSelection();
975
            return true;
976
        case HILDON_IM_CONTEXT_SURROUNDING_MODE:
977
            setCommitMode(HILDON_IM_COMMIT_SURROUNDING);
978
            return true;
979
        case HILDON_IM_CONTEXT_PREEDIT_MODE:
980
            setCommitMode(HILDON_IM_COMMIT_PREEDIT);
981
            return true;
982
983
        //Handle context
984
        case HILDON_IM_CONTEXT_CONFIRM_SENTENCE_START:
985
            checkSentenceStart();
986
            return true;
987
        case HILDON_IM_CONTEXT_FLUSH_PREEDIT:
988
            commitPreeditBuffer();
989
            return true;
990
        case HILDON_IM_CONTEXT_REQUEST_SURROUNDING:
991
            sendSurrounding(false);
992
            return true;
993
        case HILDON_IM_CONTEXT_CLEAR_STICKY:
994
            mask &= ~(HILDON_IM_SHIFT_STICKY_MASK |
995
                      HILDON_IM_SHIFT_LOCK_MASK |
996
                      HILDON_IM_LEVEL_STICKY_MASK |
997
                      HILDON_IM_LEVEL_LOCK_MASK);
998
            return true;
999
        case HILDON_IM_CONTEXT_CANCEL_PREEDIT:
1000
            cancelPreedit();
1001
            return true;
1002
        case HILDON_IM_CONTEXT_REQUEST_SURROUNDING_FULL:
1003
            sendSurrounding(true);
1004
            return true;
1005
        case HILDON_IM_CONTEXT_SPACE_AFTER_COMMIT:
1006
        case HILDON_IM_CONTEXT_NO_SPACE_AFTER_COMMIT:
1007
            spaceAfterCommit = (msg->type == HILDON_IM_CONTEXT_SPACE_AFTER_COMMIT);
1008
            return true;
1009
1010
        case HILDON_IM_CONTEXT_WIDGET_CHANGED:
1011
        case HILDON_IM_CONTEXT_ENTER_ON_FOCUS:
1012
            // ignore
1013
            return true;
1014
1015
        default:
1016
            qWarning() << "HIM: x11FilterEvent( _HILDON_IM_COM /" << debugNameForCommunicationId(msg->type) << ") was not handled.";
1017
            break;
1018
        }
1019
    } else if (event->xclient.message_type == ATOM(_HILDON_IM_SURROUNDING_CONTENT) &&
1020
               event->xclient.format == HILDON_IM_SURROUNDING_CONTENT_FORMAT) {
1021
        qWarning() << "HIM: x11FilterEvent( _HILDON_IM_SURROUNDING_CONTENT ) is not supported";
1022
    } else if (event->xclient.message_type == ATOM(_HILDON_IM_SURROUNDING) &&
1023
               event->xclient.format == HILDON_IM_SURROUNDING_FORMAT) {
1024
        qHimDebug() << "HIM: x11FilterEvent( _HILDON_IM_SURROUNDING )";
1025
1026
        HildonIMSurroundingMessage *msg = reinterpret_cast<HildonIMSurroundingMessage*>(&event->xclient.data);
1027
        setClientCursorLocation(msg->offset_is_relative, msg->cursor_offset );
1028
        return true;
1029
    }
1030
    return false;
1031
}
1032
1033
/*! \internal
1034
Ask the client widget to insert the specified text at the cursor
1035
 *  position, by triggering the commit signal on the context
1036
 */
1037
void QHildonInputContext::insertUtf8(int flag, const QString& text)
1038
{
1039
    qHimDebug() << "HIM: insertUtf8(" << flag << ", " << text << ")";
1040
1041
    QWidget *w = focusWidget();
1042
    if (!w)
1043
        return;
1044
1045
    QString cleanText = text;
1046
    if (mask & HILDON_IM_SHIFT_LOCK_MASK)
1047
        cleanText = cleanText.toUpper();
1048
1049
    lastInternalChange = true;
1050
1051
    //TODO HILDON_IM_AUTOCORRECT is used by the hadwriting plugin
1052
    //Writing CiAo in the plugin add Ciao in the widget.
1053
    if (options & HILDON_IM_AUTOCORRECT){
1054
        qWarning() << "HILDON_IM_AUTOCORRECT Not Implemented Yet";
1055
    }
1056
1057
    //Delete suroundings when we are using the preeditbuffer.
1058
    // Eg: For the HandWriting plugin
1059
    if (!preEditBuffer.isNull()) {
1060
        //Updates preEditBuffer
1061
        if (flag != HILDON_IM_MSG_START) {
1062
            preEditBuffer.append(cleanText);
1063
            cleanText = preEditBuffer;
1064
        }
1065
     }
1066
1067
    if (commitMode == HILDON_IM_COMMIT_PREEDIT) {
1068
        if (preEditBuffer.isNull())
1069
            preEditBuffer = cleanText;
1070
1071
        //Creating attribute list
1072
        QList<QInputMethodEvent::Attribute> attributes;
1073
        QTextCharFormat textCharFormat;
1074
        textCharFormat.setFontUnderline(true);
1075
        textCharFormat.setBackground(w->palette().highlight());
1076
        textCharFormat.setForeground(w->palette().base());
1077
        attributes << QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, 0, cleanText.length(), textCharFormat);
1078
        attributes << QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, 0, 1, QVariant());
1079
1080
        QInputMethodEvent e(cleanText, attributes);
1081
        QApplication::sendEvent(w, &e);
1082
1083
        //Reset commit mode
1084
        if (flag == HILDON_IM_MSG_END)
1085
            setCommitMode(lastCommitMode, false);
1086
    } else { // commitMode != HILDON_IM_COMMIT_PREEDIT
1087
        QInputMethodEvent e;
1088
        e.setCommitString(cleanText);
1089
        QApplication::sendEvent(w, &e);
1090
    }
1091
}
1092
1093
void QHildonInputContext::clearSelection()
1094
{
1095
    qHimDebug() << "HIM: clearSelection()";
1096
1097
    QWidget *w = focusWidget();
1098
    if (!w)
1099
        return;
1100
1101
    int textCursorPos = w->inputMethodQuery(Qt::ImCursorPosition).toInt();
1102
    QString selection = w->inputMethodQuery(Qt::ImCurrentSelection).toString();
1103
1104
    if (selection.isEmpty())
1105
        return;
1106
1107
    //Remove the selection
1108
    QInputMethodEvent e;
1109
    e.setCommitString(selection);
1110
    QApplication::sendEvent(w, &e);
1111
1112
    //Move the cursor backward if the text has been selected from right to left
1113
    if (textCursorPos < textCursorPosOnPress){
1114
        QInputMethodEvent e;
1115
        e.setCommitString(QString(), -selection.length(),0);
1116
        QApplication::sendEvent(w, &e);
1117
    }
1118
}
1119
1120
void QHildonInputContext::cancelPreedit()
1121
{
1122
    qHimDebug() << "HIM: cancelPreedit()";
1123
1124
    QWidget *w = focusWidget();
1125
    if (!w)
1126
        return;
1127
1128
    if (preEditBuffer.isEmpty())
1129
        return;
1130
    preEditBuffer.clear();
1131
1132
    QInputMethodEvent e;
1133
    QApplication::sendEvent(w, &e);
1134
}
1135
1136
void QHildonInputContext::sendHildonCommand(HildonIMCommand cmd, QWidget *widget)
1137
{
1138
    qHimDebug() << "HIM: sendHildonCommand(" << cmd << "," << widget << ")";
1139
1140
    Window w = findHildonIm();
1141
    if (!w)
1142
        return;
1143
1144
    XEvent ev;
1145
    memset(&ev, 0, sizeof(XEvent));
1146
1147
    ev.xclient.type = ClientMessage;
1148
    ev.xclient.window = w;
1149
    ev.xclient.message_type = ATOM(_HILDON_IM_ACTIVATE);
1150
    ev.xclient.format = HILDON_IM_ACTIVATE_FORMAT;
1151
1152
    HildonIMActivateMessage *msg = reinterpret_cast<HildonIMActivateMessage *>(&ev.xclient.data);
1153
1154
    if (widget) {
1155
        msg->input_window = QHIMProxyWidget::proxyFor(widget)->winId();
1156
        msg->app_window = widget->window()->winId();
1157
    } else if (cmd != HILDON_IM_HIDE) {
1158
        qWarning() << "Invalid Hildon Command:" << cmd;
1159
        return;
1160
    }
1161
1162
    if (cmd == HILDON_IM_HIDE && timerId != -1)
1163
        killTimer(timerId);
1164
1165
    if (cmd == HILDON_IM_SETCLIENT || cmd == HILDON_IM_SETNSHOW)
1166
        sendInputMode();
1167
1168
    msg->cmd = cmd;
1169
    msg->trigger = triggerMode;
1170
1171
    XSendEvent(X11->display, w, false, 0, &ev);
1172
    XSync(X11->display, False);
1173
}
1174
1175
1176
/*!
1177
\internal
1178
 */
1179
void QHildonInputContext::sendX11Event(XEvent *event)
1180
{
1181
    if (Window w = findHildonIm()) {
1182
        event->xclient.type = ClientMessage;
1183
        event->xclient.window = w;
1184
1185
        XSendEvent(X11->display, w, false, 0, event);
1186
        XSync(X11->display, False);
1187
    }
1188
}
1189
1190
//CONTEXT
1191
/*! \internal
1192
Updates the IM with the autocap state at the active cursor position
1193
 */
1194
void QHildonInputContext::checkSentenceStart()
1195
{
1196
    qHimDebug() << "HIM: checkSentenceStart()";
1197
1198
    QWidget *w = focusWidget();
1199
    if (!w)
1200
        return;
1201
1202
    if ((inputMode & (HILDON_GTK_INPUT_MODE_ALPHA | HILDON_GTK_INPUT_MODE_AUTOCAP)) !=
1203
            (HILDON_GTK_INPUT_MODE_ALPHA | HILDON_GTK_INPUT_MODE_AUTOCAP)) {
1204
        // If autocap is off, but the mode contains alpha, send autocap message.
1205
        // The important part is that when entering a numerical entry the autocap
1206
        // is not defined, and the plugin sets the mode appropriate for the language */
1207
        if (inputMode & HILDON_GTK_INPUT_MODE_ALPHA) {
1208
            autoUpper = false;
1209
            sendHildonCommand(HILDON_IM_LOW, w);
1210
        }
1211
        return;
1212
    } else if (inputMode & HILDON_GTK_INPUT_MODE_INVISIBLE) {
1213
        // no autocap for passwords
1214
        autoUpper = false;
1215
        sendHildonCommand(HILDON_IM_LOW, w);
1216
    }
1217
1218
    int cpos = w->inputMethodQuery(Qt::ImCursorPosition).toInt();
1219
    QString analyze;
1220
    const int analyzeCount = 10;
1221
1222
    // Improve performance: only analyze 10 chars before the cursor
1223
    if (cpos) {
1224
        analyze = w->inputMethodQuery(Qt::ImSurroundingText).toString()
1225
                                                            .mid(qMax(cpos - analyzeCount, 0), qMin(cpos, analyzeCount));
1226
    }
1227
1228
    int spaces = 0;
1229
1230
    while (spaces < analyze.length()) {
1231
        if (analyze.at(analyze.length() - spaces - 1).isSpace())
1232
            spaces++;
1233
        else
1234
            break;
1235
    }
1236
1237
    // not very nice, but QTextBoundaryFinder doesn't really work here
1238
    static const QString punctuation = QLatin1String(".!?\xa1\xbf"); // spanish inverted ! and ?
1239
1240
    if (!cpos || analyze.length() == spaces) {
1241
        autoUpper = true;
1242
        sendHildonCommand(HILDON_IM_UPP, w);
1243
    } else if (spaces && punctuation.contains(analyze.at(analyze.length() - spaces - 1))) {
1244
        autoUpper = options & HILDON_IM_AUTOCASE;
1245
        sendHildonCommand(HILDON_IM_UPP, w);
1246
    } else {
1247
        autoUpper = false;
1248
        sendHildonCommand(HILDON_IM_LOW, w);
1249
    }
1250
}
1251
1252
void QHildonInputContext::commitPreeditBuffer()
1253
{
1254
    qHimDebug() << "HIM: commitPreeditBuffer()";
1255
1256
    QWidget *w = focusWidget();
1257
    if (!w)
1258
        return;
1259
1260
    QInputMethodEvent e;
1261
1262
    if (spaceAfterCommit)
1263
        e.setCommitString(preEditBuffer + QLatin1Char(' '));
1264
    else
1265
        e.setCommitString(preEditBuffer);
1266
1267
    QApplication::sendEvent(w, &e);
1268
    preEditBuffer.clear();
1269
}
1270
1271
void QHildonInputContext::sendSurrounding(bool sendAllContents)
1272
{
1273
    QWidget *w = focusWidget();
1274
    if (!w)
1275
        return;
1276
1277
    QString surrounding;
1278
    int cpos;
1279
    if (sendAllContents) {
1280
         // Qt::ImSurrounding only returns the current block
1281
1282
        if (QTextEdit *te = qobject_cast<QTextEdit*>(w)) {
1283
            surrounding = te->toPlainText();
1284
            cpos = te->textCursor().position();
1285
        } else if (QPlainTextEdit *pte = qobject_cast<QPlainTextEdit*>(w)) {
1286
            surrounding = pte->toPlainText();
1287
            cpos = pte->textCursor().position();
1288
        } else {
1289
            surrounding = w->inputMethodQuery(Qt::ImSurroundingText).toString();
1290
            cpos = w->inputMethodQuery(Qt::ImCursorPosition).toInt();
1291
        }
1292
    } else {
1293
        surrounding = w->inputMethodQuery(Qt::ImSurroundingText).toString();
1294
        cpos = w->inputMethodQuery(Qt::ImCursorPosition).toInt();
1295
    }
1296
1297
    if (surrounding.isEmpty())
1298
        cpos = 0;
1299
1300
    XEvent xev;
1301
    HildonIMSurroundingContentMessage *surroundingContentMsg = reinterpret_cast<HildonIMSurroundingContentMessage*>(&xev.xclient.data);
1302
1303
    // Split surrounding context into parts that are small enough to send in a X11 message
1304
    QByteArray ba = surrounding.toUtf8();
1305
    bool firstPart = true;
1306
    int offset = 0;
1307
1308
    while (firstPart || (offset < ba.size())) {
1309
        //this call will take care of adding the trailing '\0' for surrounding string
1310
        memset(&xev, 0, sizeof(XEvent));
1311
        xev.xclient.message_type = ATOM(_HILDON_IM_SURROUNDING_CONTENT);
1312
        xev.xclient.format = HILDON_IM_SURROUNDING_CONTENT_FORMAT;
1313
1314
        int len = qMin(ba.size() - offset, int(HILDON_IM_CLIENT_MESSAGE_BUFFER_SIZE) - 1);
1315
        ::memcpy(surroundingContentMsg->surrounding, ba.constData() + offset, len);
1316
        offset += len;
1317
1318
        if (firstPart)
1319
            surroundingContentMsg->msg_flag = HILDON_IM_MSG_START;
1320
        else if (offset == ba.size())
1321
            surroundingContentMsg->msg_flag = HILDON_IM_MSG_END;
1322
        else
1323
            surroundingContentMsg->msg_flag = HILDON_IM_MSG_CONTINUE;
1324
1325
        sendX11Event(&xev);
1326
        firstPart = false;
1327
    }
1328
1329
    // Send the cursor offset in the surrounding
1330
    memset(&xev, 0, sizeof(XEvent));
1331
    xev.xclient.message_type = ATOM(_HILDON_IM_SURROUNDING);
1332
    xev.xclient.format = HILDON_IM_SURROUNDING_FORMAT;
1333
1334
    HildonIMSurroundingMessage *surroundingMsg = reinterpret_cast<HildonIMSurroundingMessage *>(&xev.xclient.data);
1335
    surroundingMsg->commit_mode = commitMode;
1336
    surroundingMsg->cursor_offset = cpos;
1337
    sendX11Event(&xev);
1338
}
1339
1340
1341
/*! \internal
1342
Notify IM of any input mode changes
1343
 */
1344
void QHildonInputContext::inputModeChanged()
1345
{
1346
    qHimDebug() << "HIM: inputModeChanged()";
1347
1348
#if 0
1349
  //TODO
1350
  if ((input_mode & HILDON_GTK_INPUT_MODE_ALPHA) == 0  &&
1351
      (input_mode & HILDON_GTK_INPUT_MODE_HEXA)  == 0  &&
1352
      ( (input_mode & HILDON_GTK_INPUT_MODE_NUMERIC) != 0 ||
1353
        (input_mode & HILDON_GTK_INPUT_MODE_TELE)    != 0))
1354
  {
1355
    self->mask = HILDON_IM_LEVEL_LOCK_MASK | HILDON_IM_LEVEL_STICKY_MASK;
1356
  }
1357
  else
1358
  {
1359
    self->mask &= ~HILDON_IM_LEVEL_LOCK_MASK;
1360
    self->mask &= ~HILDON_IM_LEVEL_STICKY_MASK;
1361
  }
1362
#endif
1363
  /* Notify IM of any input mode changes in cases where the UI is
1364
     already visible. */
1365
  sendInputMode();
1366
}
1367
1368
void QHildonInputContext::sendInputMode()
1369
{
1370
    qHimDebug() << "HIM: sendInputMode";
1371
1372
    Window w = findHildonIm();
1373
    if (!w)
1374
        return;
1375
1376
    XEvent ev;
1377
    memset(&ev, 0, sizeof(XEvent));
1378
1379
    ev.xclient.type = ClientMessage;
1380
    ev.xclient.window = w;
1381
    ev.xclient.message_type = ATOM(_HILDON_IM_INPUT_MODE);
1382
    ev.xclient.format = HILDON_IM_INPUT_MODE_FORMAT;
1383
1384
    HildonIMInputModeMessage *msg = reinterpret_cast<HildonIMInputModeMessage *>(&ev.xclient.data);
1385
    msg->input_mode = static_cast<HildonGtkInputMode>(inputMode);
1386
    msg->default_input_mode = static_cast<HildonGtkInputMode>(HILDON_GTK_INPUT_MODE_FULL);
1387
1388
    XSendEvent(X11->display, w, false, 0, &ev);
1389
    XSync(X11->display, False);
1390
}
1391
1392
/*! \internal
1393
In redirect mode we use a proxy widget (fullscreen vkb). When the cursor position
1394
 *  changes there, the HIM update the cursor position in the client (Qt application)
1395
 */
1396
void QHildonInputContext::setClientCursorLocation(bool offsetIsRelative, int cursorOffset)
1397
{
1398
    qHimDebug() << "HIM: setClientCursorLocation(" << offsetIsRelative << ", " << cursorOffset<< ")";
1399
1400
    QWidget *w = focusWidget();
1401
    if (!w)
1402
        return;
1403
1404
    if (offsetIsRelative)
1405
        cursorOffset += w->inputMethodQuery(Qt::ImCursorPosition).toInt();
1406
1407
1408
    QList<QInputMethodEvent::Attribute> attributes;
1409
1410
    attributes << QInputMethodEvent::Attribute(QInputMethodEvent::Selection,
1411
                                               cursorOffset, 0, QVariant());
1412
1413
    QInputMethodEvent e(QString(), attributes);
1414
    QApplication::sendEvent(w, &e);
1415
}
1416
1417
void QHildonInputContext::setMaskState(int *mask,
1418
                                              HildonIMInternalModifierMask lock_mask,
1419
                                              HildonIMInternalModifierMask sticky_mask,
1420
                                              bool was_press_and_release)
1421
{
1422
    //LOGMESSAGE3("setMaskState", lock_mask, sticky_mask)
1423
    //LOGMESSAGE3(" - ", "mask=", *mask)
1424
1425
   /* Locking Fn is disabled in TELE and NUMERIC */
1426
    if (!(inputMode & HILDON_GTK_INPUT_MODE_ALPHA) &&
1427
        !(inputMode & HILDON_GTK_INPUT_MODE_HEXA)  &&
1428
        ((inputMode & HILDON_GTK_INPUT_MODE_TELE) ||
1429
         (inputMode & HILDON_GTK_INPUT_MODE_NUMERIC))
1430
       ) {
1431
        if (*mask & lock_mask){
1432
            /* already locked, remove lock and set it to sticky */
1433
            *mask &= ~(lock_mask | sticky_mask);
1434
            *mask |= sticky_mask;
1435
        }else if (*mask & sticky_mask){
1436
            /* the key is already sticky, it's fine */
1437
        }else if (was_press_and_release){
1438
            /* Pressing the key for the first time stickies the key for one character,
1439
             * but only if no characters were entered while holding the key down */
1440
            *mask |= sticky_mask;
1441
        }
1442
        return;
1443
    }
1444
1445
    if (*mask & lock_mask)
1446
    {
1447
        /* Pressing the key while already locked clears the state */
1448
        if (lock_mask & HILDON_IM_SHIFT_LOCK_MASK)
1449
            sendHildonCommand(HILDON_IM_SHIFT_UNLOCKED, realFocus);
1450
        else if (lock_mask & HILDON_IM_LEVEL_LOCK_MASK)
1451
            sendHildonCommand(HILDON_IM_MOD_UNLOCKED, realFocus);
1452
1453
        *mask &= ~(lock_mask | sticky_mask);
1454
    } else if (*mask & sticky_mask) {
1455
        /* When the key is already sticky, a second press locks the key */
1456
        *mask |= lock_mask;
1457
1458
        if (lock_mask & HILDON_IM_SHIFT_LOCK_MASK)
1459
            sendHildonCommand(HILDON_IM_SHIFT_LOCKED, realFocus);
1460
        else if (lock_mask & HILDON_IM_LEVEL_LOCK_MASK)
1461
            sendHildonCommand(HILDON_IM_MOD_LOCKED, realFocus);
1462
    }else if (was_press_and_release){
1463
        /* Pressing the key for the first time stickies the key for one character,
1464
         * but only if no characters were entered while holding the key down */
1465
        *mask |= sticky_mask;
1466
    }
1467
1468
}
1469
1470
#endif