~michael-sheldon/ubuntu-keyboard/fix-oxide-dismiss-test

« back to all changes in this revision

Viewing changes to src/view/abstracttexteditor.cpp

  • Committer: Thomas Moenicke
  • Date: 2013-07-19 12:05:07 UTC
  • Revision ID: thomas.moenicke@canonical.com-20130719120507-lzw5oq50xm567x0j
initial commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * This file is part of Maliit Plugins
 
3
 *
 
4
 * Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). All rights reserved.
 
5
 *
 
6
 * Contact: Mohammad Anwari <Mohammad.Anwari@nokia.com>
 
7
 *
 
8
 * Redistribution and use in source and binary forms, with or without modification,
 
9
 * are permitted provided that the following conditions are met:
 
10
 *
 
11
 * Redistributions of source code must retain the above copyright notice, this list
 
12
 * of conditions and the following disclaimer.
 
13
 * Redistributions in binary form must reproduce the above copyright notice, this list
 
14
 * of conditions and the following disclaimer in the documentation and/or other materials
 
15
 * provided with the distribution.
 
16
 * Neither the name of Nokia Corporation nor the names of its contributors may be
 
17
 * used to endorse or promote products derived from this software without specific
 
18
 * prior written permission.
 
19
 *
 
20
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
 
21
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 
22
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 
23
 * THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 
24
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 
25
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 
26
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 
27
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 
28
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
29
 *
 
30
 */
 
31
 
 
32
#include "abstracttexteditor.h"
 
33
#include "models/wordribbon.h"
 
34
 
 
35
#include "logic/chineselanguagefeatures.h"
 
36
#include "logic/languagefeatures.h"
 
37
 
 
38
namespace MaliitKeyboard {
 
39
 
 
40
//! \class EditorOptions
 
41
//! \brief Plain struct implementing editor options.
 
42
 
 
43
//! \fn EditorOptions::EditorOptions()
 
44
//! \brief Constructor.
 
45
//!
 
46
//! Sets backspace_auto_repeat_delay to 500 miliseconds and backspace_auto_repeat_interval to 300 miliseconds.
 
47
 
 
48
//! \var EditorOptions::backspace_auto_repeat_delay
 
49
//! \brief Delay before first automatically repeated key in miliseconds.
 
50
 
 
51
//! \var EditorOptions::backspace_auto_repeat_interval
 
52
//! \brief Interval between automatically repeated key in miliseconds.
 
53
 
 
54
//! \class AbstractTextEditor
 
55
//! \brief Class implementing preedit edition.
 
56
//!
 
57
//! It owns a text model (which can be gotten by text() method) and a
 
58
//! word engine (word_engine()). The class has to be subclassed and
 
59
//! subclass has to provide sendPreeditString(), sendCommitString(),
 
60
//! sendKeyEvent(), invokeAction() and destructor implementations.
 
61
 
 
62
// The function declaration has to be in one line, because \fn is a
 
63
// single line parameter.
 
64
//! \fn void AbstractTextEditor::sendPreeditString(const QString &preedit, Model::Text::PreeditFace face, const Replacement &replacement)
 
65
//! \brief Sends preedit to application.
 
66
//! \param preedit Preedit to send.
 
67
//! \param face Face of the preedit.
 
68
//! \param replacement Struct describing replacement of the text.
 
69
//!
 
70
//! Implementations of this pure virtual method have to convert \a
 
71
//! face into specific attributes used by backend they
 
72
//! support. Preedit text has to be sent as-is. Replacement members
 
73
//! should be understood as follows: if either start or length are
 
74
//! lesser than zero or both of them are zeros then those parameters
 
75
//! should be ignored. Otherwise they mark the beginning and length of
 
76
//! a surrounding text's substring that is going to be replaced by
 
77
//! given \a preedit. Cursor position member of replacement should be
 
78
//! ignored if its value is lesser than zero. Otherwise it describes a
 
79
//! position of cursor relatively to the beginning of preedit.
 
80
 
 
81
//! \fn void AbstractTextEditor::sendCommitString(const QString &commit)
 
82
//! \brief Commits a string to application.
 
83
//! \param commit String to be commited in place of preedit.
 
84
//!
 
85
//! Implementations of this method should discard current preedit and
 
86
//! commit given \a commit in its place.
 
87
 
 
88
//! \fn void AbstractTextEditor::sendKeyEvent(const QKeyEvent &ev)
 
89
//! \brief Sends a key event to application.
 
90
//! \param ev Key event to send.
 
91
//!
 
92
//! The implementation should translate passed \a ev to values their
 
93
//! backend understands and pass this to application.
 
94
 
 
95
//! \fn void AbstractTextEditor::invokeAction(const QString &action, const QKeySequence &sequence)
 
96
//! \brief Invokes an action in the application
 
97
//! \param action Action to invoke
 
98
//! \param sequence Key sequence to emit when action cannot be called directly
 
99
//!
 
100
//! Application first tries to invoke a signal/slot \a action and when not available
 
101
//! it will emit the key sequence \a sequence. One would call this method for
 
102
//! example with "copy", "CTRL+C" arguments.
 
103
 
 
104
//! \property AbstractTextEditor::preeditEnabled
 
105
//! \brief Describes whether preedit is enabled.
 
106
//!
 
107
//! When it is \c false then everything typed by virtual keyboard is
 
108
//! immediately commited.
 
109
 
 
110
//! \property AbstractTextEditor::autoCorrectEnabled
 
111
//! \brief Describes whether auto correction on space is enabled.
 
112
//!
 
113
//! When it is \c true then pressing space will commit corrected word
 
114
//! if it was misspelled. Otherwise it will just commit what was in
 
115
//! preedit.
 
116
 
 
117
//! \fn void AbstractTextEditor::autoCapsActivated()
 
118
//! \brief Emitted when auto capitalization mode is enabled for
 
119
//! following input.
 
120
 
 
121
//! \fn void AbstractTextEditor::autoCorrectEnabledChanged(bool enabled)
 
122
//! \brief Emitted when auto correction setting changes.
 
123
//! \param enabled New setting.
 
124
 
 
125
//! \fn void AbstractTextEditor::preeditEnabledChanged(bool enabled)
 
126
//! \brief Emitted when preedit setting changes.
 
127
//! \param enabled New setting.
 
128
 
 
129
//! \fn void AbstractTextEditor::wordCandidatesChanged(const WordCandidateList &word_candidates)
 
130
//! \brief Emitted when new word candidates are generated.
 
131
//! \param word_candidates New word candidates.
 
132
//!
 
133
//! Note that the list might be empty as well to indicate that there
 
134
//! should be no word candidates.
 
135
 
 
136
//! \fn void AbstractTextEditor::keyboardClosed()
 
137
//! \brief Emitted when keyboard close is requested.
 
138
 
 
139
//! \class AbstractTextEditor::Replacement
 
140
//! \brief Plain struct containing beginning and length of replacement and
 
141
//! desired cursor position.
 
142
//!
 
143
//! Mostly used by sendPreeditString() implementations.
 
144
 
 
145
//! \fn AbstractTextEditor::Replacement::Replacement()
 
146
//! \brief Constructor.
 
147
//!
 
148
//! Constructs an instance with no replacement and no cursor position
 
149
//! change.
 
150
 
 
151
//! \fn AbstractTextEditor::Replacement::Replacement(int position)
 
152
//! \brief Constructor.
 
153
//! \param position New cursor position.
 
154
//!
 
155
//! Constructs an instance with no replacement and new cursor position.
 
156
 
 
157
//! \fn AbstractTextEditor::Replacement::Replacement(int r_start, int r_length, int position)
 
158
//! \brief Constructor.
 
159
//! \param r_start Replacement start.
 
160
//! \param r_length Replacement length.
 
161
//! \param position New cursor position.
 
162
//!
 
163
//! Constructs an instance with a replacement and new cursor position.
 
164
 
 
165
//! \var AbstractTextEditor::Replacement::start
 
166
//! \brief Beginning of replacement.
 
167
 
 
168
//! \var AbstractTextEditor::Replacement::length
 
169
//! \brief Length of replacement.
 
170
 
 
171
//! \var AbstractTextEditor::Replacement::cursor_position
 
172
//! \brief New cursor position relative to the beginning of preedit.
 
173
 
 
174
namespace {
 
175
 
 
176
//! \brief Checks whether given \a c is a word separator.
 
177
//! \param c Char to test.
 
178
//!
 
179
//! Other way to do checks would be using isLetterOrNumber() + some
 
180
//! other methods. But UTF is so crazy that I am not sure whether
 
181
//! other strange categories are parts of the word or not. It is
 
182
//! easier to specify punctuations and whitespaces.
 
183
inline bool isSeparator(const QChar &c)
 
184
{
 
185
    return (c.isPunct() or c.isSpace());
 
186
}
 
187
 
 
188
//! \brief Extracts a word boundaries at cursor position.
 
189
//! \param surrounding_text Text from which extraction will happen.
 
190
//! \param cursor_position Position of cursor within \a surrounding_text.
 
191
//! \param replacement Place where replacement data will be stored.
 
192
//!
 
193
//! \return whether surrounding text was valid (not empty).
 
194
//!
 
195
//! If cursor is placed right after the word, boundaries of this word
 
196
//! are extracted.  Otherwise if cursor is placed right before the
 
197
//! word, then no word boundaries are stored - instead invalid
 
198
//! replacement is stored. It might happen that cursor position is
 
199
//! outside the string, so \a replacement will have fixed position.
 
200
bool extractWordBoundariesAtCursor(const QString& surrounding_text,
 
201
                                   int cursor_position,
 
202
                                   AbstractTextEditor::Replacement *replacement)
 
203
{
 
204
    const int text_length(surrounding_text.length());
 
205
 
 
206
    if (text_length == 0) {
 
207
        return false;
 
208
    }
 
209
 
 
210
    // just in case - if cursor is far after last char in surrounding
 
211
    // text we place it right after last char.
 
212
    cursor_position = qBound(0, cursor_position, text_length);
 
213
 
 
214
    // cursor might be placed in after last char (that is to say - its
 
215
    // index might be the one of string terminator) - for simplifying
 
216
    // the algorithm below we fake it as cursor is put on delimiter:
 
217
    // "abc" - surrounding text
 
218
    //     | - cursor placement
 
219
    // "abc " - fake surrounding text
 
220
    const QString fake_surrounding_text(surrounding_text + " ");
 
221
    const QChar *const fake_data(fake_surrounding_text.constData());
 
222
    // begin is index of first char in a word
 
223
    int begin(-1);
 
224
    // end is index of a char after last char in a word.
 
225
    // -2, because -2 - (-1) = -1 and we would like to
 
226
    // have -1 as invalid length.
 
227
    int end(-2);
 
228
 
 
229
    for (int iter(cursor_position); iter >= 0; --iter) {
 
230
        const QChar &c(fake_data[iter]);
 
231
 
 
232
        if (isSeparator(c)) {
 
233
            if (iter != cursor_position) {
 
234
                break;
 
235
            }
 
236
        } else {
 
237
            begin = iter;
 
238
        }
 
239
    }
 
240
 
 
241
    if (begin >= 0) {
 
242
        // take note that fake_data's last QChar is always a space.
 
243
        for (int iter(cursor_position); iter <= text_length; ++iter) {
 
244
            const QChar &c(fake_data[iter]);
 
245
 
 
246
            end = iter;
 
247
            if (isSeparator(c)) {
 
248
                break;
 
249
            }
 
250
        }
 
251
    }
 
252
 
 
253
    if (replacement) {
 
254
        replacement->start = begin;
 
255
        replacement->length = end - begin;
 
256
        replacement->cursor_position = cursor_position;
 
257
    }
 
258
 
 
259
    return true;
 
260
}
 
261
 
 
262
} // unnamed namespace
 
263
 
 
264
EditorOptions::EditorOptions()
 
265
    : backspace_auto_repeat_delay(500)
 
266
    , backspace_auto_repeat_interval(300)
 
267
{}
 
268
 
 
269
class AbstractTextEditorPrivate
 
270
{
 
271
public:
 
272
    QTimer auto_repeat_backspace_timer;
 
273
    bool backspace_sent;
 
274
    EditorOptions options;
 
275
    QScopedPointer<Model::Text> text;
 
276
    QScopedPointer<Logic::AbstractWordEngine> word_engine;
 
277
    QScopedPointer<Logic::AbstractLanguageFeatures> language_features;
 
278
    bool preedit_enabled;
 
279
    bool auto_correct_enabled;
 
280
    bool auto_caps_enabled;
 
281
    int ignore_next_cursor_position;
 
282
    QString ignore_next_surrounding_text;
 
283
 
 
284
    explicit AbstractTextEditorPrivate(const EditorOptions &new_options,
 
285
                                       Model::Text *new_text,
 
286
                                       Logic::AbstractWordEngine *new_word_engine,
 
287
                                       Logic::AbstractLanguageFeatures *new_language_features);
 
288
    bool valid() const;
 
289
};
 
290
 
 
291
AbstractTextEditorPrivate::AbstractTextEditorPrivate(const EditorOptions &new_options,
 
292
                                                     Model::Text *new_text,
 
293
                                                     Logic::AbstractWordEngine *new_word_engine,
 
294
                                                     Logic::AbstractLanguageFeatures *new_language_features)
 
295
    : auto_repeat_backspace_timer()
 
296
    , backspace_sent(false)
 
297
    , options(new_options)
 
298
    , text(new_text)
 
299
    , word_engine(new_word_engine)
 
300
    , language_features(new_language_features)
 
301
    , preedit_enabled(false)
 
302
    , auto_correct_enabled(false)
 
303
    , auto_caps_enabled(false)
 
304
    , ignore_next_cursor_position(-1)
 
305
    , ignore_next_surrounding_text()
 
306
{
 
307
    auto_repeat_backspace_timer.setSingleShot(true);
 
308
    (void) valid();
 
309
}
 
310
 
 
311
bool AbstractTextEditorPrivate::valid() const
 
312
{
 
313
    const bool is_invalid(text.isNull() || word_engine.isNull() || language_features.isNull());
 
314
 
 
315
    if (is_invalid) {
 
316
        qCritical() << __PRETTY_FUNCTION__
 
317
                    << "Invalid text model, or no word engine given! The text editor will not function properly.";
 
318
    }
 
319
 
 
320
    return (not is_invalid);
 
321
}
 
322
 
 
323
//! \brief Constructor.
 
324
//! \param options Editor options.
 
325
//! \param text Text model.
 
326
//! \param word_engine Word engine.
 
327
//! \param language_features Language features.
 
328
//! \param parent Parent of this instance or \c NULL if none is needed.
 
329
//!
 
330
//! Takes ownership of \a text, \a word_engine and \a language_features.
 
331
AbstractTextEditor::AbstractTextEditor(const EditorOptions &options,
 
332
                                       Model::Text *text,
 
333
                                       Logic::AbstractWordEngine *word_engine,
 
334
                                       Logic::AbstractLanguageFeatures *language_features,
 
335
                                       QObject *parent)
 
336
    : QObject(parent)
 
337
    , d_ptr(new AbstractTextEditorPrivate(options, text, word_engine, language_features))
 
338
{
 
339
    connect(&d_ptr->auto_repeat_backspace_timer, SIGNAL(timeout()),
 
340
            this,                                SLOT(autoRepeatBackspace()));
 
341
 
 
342
    connect(word_engine, SIGNAL(enabledChanged(bool)),
 
343
            this,        SLOT(setPreeditEnabled(bool)));
 
344
 
 
345
    connect(word_engine, SIGNAL(candidatesChanged(WordCandidateList)),
 
346
            this,        SIGNAL(wordCandidatesChanged(WordCandidateList)));
 
347
 
 
348
    setPreeditEnabled(word_engine->isEnabled());
 
349
}
 
350
 
 
351
//! \brief Destructor.
 
352
AbstractTextEditor::~AbstractTextEditor()
 
353
{}
 
354
 
 
355
//! \brief Gets editor's text model.
 
356
Model::Text * AbstractTextEditor::text() const
 
357
{
 
358
    Q_D(const AbstractTextEditor);
 
359
    return d->text.data();
 
360
}
 
361
 
 
362
//! \brief Gets editor's word engine.
 
363
Logic::AbstractWordEngine * AbstractTextEditor::wordEngine() const
 
364
{
 
365
    Q_D(const AbstractTextEditor);
 
366
    return d->word_engine.data();
 
367
}
 
368
 
 
369
//! \brief Reacts to key press.
 
370
//! \param key Pressed key.
 
371
//!
 
372
//! For now it only checks whether backspace was pressed. In such case
 
373
//! preedit is commited and primary candidate is cleared.
 
374
void AbstractTextEditor::onKeyPressed(const Key &key)
 
375
{
 
376
    Q_D(AbstractTextEditor);
 
377
 
 
378
    if (not d->valid()) {
 
379
        return;
 
380
    }
 
381
 
 
382
    if (key.action() == Key::ActionBackspace) {
 
383
        if (d->auto_correct_enabled && not d->text->primaryCandidate().isEmpty()) {
 
384
            d->text->setPrimaryCandidate(QString());
 
385
            d->backspace_sent = true;
 
386
        } else {
 
387
            d->backspace_sent = false;
 
388
        }
 
389
 
 
390
        commitPreedit();
 
391
        d->auto_repeat_backspace_timer.start(d->options.backspace_auto_repeat_delay);
 
392
    }
 
393
}
 
394
 
 
395
//! \brief Reacts to key release.
 
396
//! \param key Released key.
 
397
//!
 
398
//! If common key is pressed then it is appended to preedit.  If
 
399
//! backspace was pressed then preedit is commited and a character
 
400
//! before cursor is removed. If space is pressed then primary
 
401
//! candidate is applied if enabled. In other cases standard behaviour
 
402
//! applies.
 
403
void AbstractTextEditor::onKeyReleased(const Key &key)
 
404
{
 
405
    Q_D(AbstractTextEditor);
 
406
 
 
407
    if (not d->valid()) {
 
408
        return;
 
409
    }
 
410
 
 
411
    const QString &text(key.label().text());
 
412
    Qt::Key event_key = Qt::Key_unknown;
 
413
 
 
414
    switch(key.action()) {
 
415
    case Key::ActionInsert:
 
416
        d->text->appendToPreedit(text);
 
417
 
 
418
        // computeCandidates can change preedit face, so needs to happen
 
419
        // before sending preedit:
 
420
        if (d->preedit_enabled) {
 
421
            d->word_engine->computeCandidates(d->text.data());
 
422
        }
 
423
 
 
424
        sendPreeditString(d->text->preedit(), d->text->preeditFace(),
 
425
                          Replacement(d->text->cursorPosition()));
 
426
 
 
427
        if (not d->preedit_enabled) {
 
428
            commitPreedit();
 
429
        }
 
430
 
 
431
        break;
 
432
 
 
433
    case Key::ActionBackspace: {
 
434
        commitPreedit();
 
435
 
 
436
        if (not d->backspace_sent) {
 
437
            event_key = Qt::Key_Backspace;
 
438
        }
 
439
 
 
440
        d->auto_repeat_backspace_timer.stop();
 
441
     } break;
 
442
 
 
443
    case Key::ActionSpace: {
 
444
        const bool auto_caps_activated = d->language_features->activateAutoCaps(d->text->preedit());
 
445
        const bool replace_preedit = d->auto_correct_enabled && not d->text->primaryCandidate().isEmpty();
 
446
 
 
447
        if (replace_preedit) {
 
448
            const QString &appendix = d->language_features->appendixForReplacedPreedit(d->text->preedit());
 
449
            d->text->setPreedit(d->text->primaryCandidate());
 
450
            d->text->appendToPreedit(appendix);
 
451
        } else {
 
452
            d->text->appendToPreedit(" ");
 
453
        }
 
454
        commitPreedit();
 
455
 
 
456
        if (auto_caps_activated && d->auto_caps_enabled) {
 
457
            Q_EMIT autoCapsActivated();
 
458
        }
 
459
    } break;
 
460
 
 
461
    case Key::ActionReturn:
 
462
        event_key = Qt::Key_Return;
 
463
        break;
 
464
 
 
465
    case Key::ActionClose:
 
466
        Q_EMIT keyboardClosed();
 
467
        break;
 
468
 
 
469
    case Key::ActionLeft:
 
470
        event_key = Qt::Key_Left;
 
471
        break;
 
472
 
 
473
    case Key::ActionUp:
 
474
        event_key = Qt::Key_Up;
 
475
        break;
 
476
 
 
477
    case Key::ActionRight:
 
478
        event_key = Qt::Key_Right;
 
479
        break;
 
480
 
 
481
    case Key::ActionDown:
 
482
        event_key = Qt::Key_Down;
 
483
        break;
 
484
 
 
485
    case Key::ActionCommand:
 
486
        invokeAction(text, QKeySequence::fromString(key.commandSequence()));
 
487
 
 
488
    case Key::ActionLeftLayout:
 
489
        Q_EMIT leftLayoutSelected();
 
490
        break;
 
491
 
 
492
    case Key::ActionRightLayout:
 
493
        Q_EMIT rightLayoutSelected();
 
494
        break;
 
495
 
 
496
    default:
 
497
        break;
 
498
    }
 
499
 
 
500
    if (event_key != Qt::Key_unknown) {
 
501
        commitPreedit();
 
502
        QKeyEvent ev(QEvent::KeyPress, event_key, Qt::NoModifier);
 
503
        sendKeyEvent(ev);
 
504
    }
 
505
}
 
506
 
 
507
//! \brief Reacts to sliding into a key.
 
508
//! \param key Slid in key.
 
509
//!
 
510
//! For now it only set backspace repeat timer if we slide into
 
511
//! backspace.
 
512
void AbstractTextEditor::onKeyEntered(const Key &key)
 
513
{
 
514
    Q_D(AbstractTextEditor);
 
515
 
 
516
    if (key.action() == Key::ActionBackspace) {
 
517
        d->backspace_sent = false;
 
518
        d->auto_repeat_backspace_timer.start(d->options.backspace_auto_repeat_delay);
 
519
    }
 
520
}
 
521
 
 
522
//! \brief Reacts to sliding out of a key.
 
523
//! \param key Slid out key.
 
524
//!
 
525
//! For now it only stops backspace repeat timer if we slide out
 
526
//! from backspace.
 
527
void AbstractTextEditor::onKeyExited(const Key &key)
 
528
{
 
529
    Q_D(AbstractTextEditor);
 
530
 
 
531
    if (key.action() == Key::ActionBackspace) {
 
532
        d->auto_repeat_backspace_timer.stop();
 
533
    }
 
534
}
 
535
 
 
536
//! \brief Replaces current preedit with given replacement
 
537
//! \param replacement New preedit.
 
538
void AbstractTextEditor::replacePreedit(const QString &replacement)
 
539
{
 
540
    Q_D(AbstractTextEditor);
 
541
 
 
542
    if (not d->valid()) {
 
543
        return;
 
544
    }
 
545
 
 
546
    d->text->setPreedit(replacement);
 
547
    // computeCandidates can change preedit face, so needs to happen
 
548
    // before sending preedit:
 
549
    d->word_engine->computeCandidates(d->text.data());
 
550
    sendPreeditString(d->text->preedit(), d->text->preeditFace());
 
551
}
 
552
 
 
553
//! \brief Replaces current preedit with given replacement and then
 
554
//! commits it.
 
555
//! \param replacement New preedit string to commit.
 
556
void AbstractTextEditor::replaceAndCommitPreedit(const QString &replacement)
 
557
{
 
558
    Q_D(AbstractTextEditor);
 
559
 
 
560
    if (not d->valid()) {
 
561
        return;
 
562
    }
 
563
 
 
564
    const bool auto_caps_activated = d->language_features->activateAutoCaps(d->text->preedit());
 
565
    const QString &appendix(d->language_features->appendixForReplacedPreedit(d->text->preedit()));
 
566
    d->text->setPreedit(replacement);
 
567
    d->text->appendToPreedit(appendix);
 
568
    commitPreedit();
 
569
 
 
570
    if (auto_caps_activated && d->auto_caps_enabled) {
 
571
        Q_EMIT autoCapsActivated();
 
572
    }
 
573
}
 
574
 
 
575
//! \brief Clears preedit.
 
576
void AbstractTextEditor::clearPreedit()
 
577
{
 
578
    replacePreedit("");
 
579
}
 
580
 
 
581
//! \brief Returns whether preedit functionality is enabled.
 
582
//! \sa preeditEnabled
 
583
bool AbstractTextEditor::isPreeditEnabled() const
 
584
{
 
585
    Q_D(const AbstractTextEditor);
 
586
    return d->preedit_enabled;
 
587
}
 
588
 
 
589
//! \brief Sets whether enable preedit functionality.
 
590
//! \param enabled \c true to enable preedit functionality.
 
591
//! \sa preeditEnabled
 
592
void AbstractTextEditor::setPreeditEnabled(bool enabled)
 
593
{
 
594
    Q_D(AbstractTextEditor);
 
595
 
 
596
    if (d->preedit_enabled != enabled) {
 
597
        d->preedit_enabled = enabled;
 
598
        Q_EMIT preeditEnabledChanged(d->preedit_enabled);
 
599
    }
 
600
}
 
601
 
 
602
//! \brief Returns whether auto-correct functionality is enabled.
 
603
//! \sa autoCorrectEnabled
 
604
bool AbstractTextEditor::isAutoCorrectEnabled() const
 
605
{
 
606
    Q_D(const AbstractTextEditor);
 
607
    return d->auto_correct_enabled;
 
608
}
 
609
 
 
610
//! \brief Sets whether enable the auto-correct functionality.
 
611
//! \param enabled \c true to enable auto-correct functionality.
 
612
//! \sa autoCorrectEnabled
 
613
void AbstractTextEditor::setAutoCorrectEnabled(bool enabled)
 
614
{
 
615
    Q_D(AbstractTextEditor);
 
616
 
 
617
    if (d->auto_correct_enabled != enabled) {
 
618
        d->auto_correct_enabled = enabled;
 
619
        Q_EMIT autoCorrectEnabledChanged(d->auto_correct_enabled);
 
620
    }
 
621
}
 
622
 
 
623
bool AbstractTextEditor::isAutoCapsEnabled() const
 
624
{
 
625
    Q_D(const AbstractTextEditor);
 
626
    return d->auto_caps_enabled;
 
627
}
 
628
 
 
629
void AbstractTextEditor::setAutoCapsEnabled(bool enabled)
 
630
{
 
631
    Q_D(AbstractTextEditor);
 
632
 
 
633
    if (d->auto_caps_enabled != enabled) {
 
634
        d->auto_caps_enabled = enabled;
 
635
        Q_EMIT autoCapsEnabledChanged(d->auto_caps_enabled);
 
636
    }
 
637
}
 
638
 
 
639
//! \brief Commits current preedit.
 
640
void AbstractTextEditor::commitPreedit()
 
641
{
 
642
    Q_D(AbstractTextEditor);
 
643
 
 
644
    if (not d->valid() || d->text->preedit().isEmpty()) {
 
645
        return;
 
646
    }
 
647
 
 
648
    sendCommitString(d->text->preedit());
 
649
    d->text->commitPreedit();
 
650
    d->word_engine->clearCandidates();
 
651
}
 
652
 
 
653
// TODO: this implementation does not take into account following features:
 
654
// 1) preedit string
 
655
//      if there is preedit then first call to autoRepeatBackspace should clean it completely
 
656
//      and following calls should remove remaining text character by character
 
657
// 2) multitouch
 
658
//      it is not completely clean how to handle multitouch for backspace,
 
659
//      but we can follow the strategy from meego-keyboard - release pressed
 
660
//      key when user press another one at the same time. Then we do not need to
 
661
//      change anything in this method
 
662
//! \brief Sends backspace and sets backspace repeat timer.
 
663
void AbstractTextEditor::autoRepeatBackspace()
 
664
{
 
665
    Q_D(AbstractTextEditor);
 
666
 
 
667
    QKeyEvent ev(QEvent::KeyPress, Qt::Key_Backspace, Qt::NoModifier);
 
668
    sendKeyEvent(ev);
 
669
    d->backspace_sent = true;
 
670
    d->auto_repeat_backspace_timer.start(d->options.backspace_auto_repeat_interval);
 
671
}
 
672
 
 
673
//! \brief Emits wordCandidatesChanged() signal with current preedit
 
674
//! as a candidate.
 
675
void AbstractTextEditor::showUserCandidate()
 
676
{
 
677
    Q_D(AbstractTextEditor);
 
678
 
 
679
    if (d->text->preedit().isEmpty()) {
 
680
        return;
 
681
    }
 
682
 
 
683
    WordCandidateList candidates;
 
684
    WordCandidate candidate(WordCandidate::SourceUser, d->text->preedit());
 
685
 
 
686
    candidates << candidate;
 
687
 
 
688
    Q_EMIT wordCandidatesChanged(candidates);
 
689
}
 
690
 
 
691
//! \brief Adds \a word to user dictionary.
 
692
//! \param word Word to be added.
 
693
void AbstractTextEditor::addToUserDictionary(const QString &word)
 
694
{
 
695
    Q_D(AbstractTextEditor);
 
696
 
 
697
    d->word_engine->addToUserDictionary(word);
 
698
    d->text->setPrimaryCandidate(word);
 
699
 
 
700
    Q_EMIT wordCandidatesChanged(WordCandidateList());
 
701
}
 
702
 
 
703
//! \brief Sends preedit string to application with no replacement.
 
704
//! \param preedit Preedit to send.
 
705
//! \param face Face of the preedit.
 
706
void AbstractTextEditor::sendPreeditString(const QString &preedit,
 
707
                                           Model::Text::PreeditFace face)
 
708
{
 
709
    sendPreeditString(preedit, face, Replacement());
 
710
}
 
711
 
 
712
//! \brief Reacts to cursor position change in application's text
 
713
//! field.
 
714
//! \param cursor_position new cursor position
 
715
//! \param surrounding_text surrounding text of a preedit
 
716
//!
 
717
//! Extract words with the cursor inside and replaces it with a preedit.
 
718
//! This is called preedit activation.
 
719
void AbstractTextEditor::onCursorPositionChanged(int cursor_position,
 
720
                                                 const QString &surrounding_text)
 
721
{
 
722
    Q_D(AbstractTextEditor);
 
723
    Replacement r;
 
724
 
 
725
    if (not extractWordBoundariesAtCursor(surrounding_text, cursor_position, &r)) {
 
726
        return;
 
727
    }
 
728
 
 
729
    if (r.start < 0 or r.length < 0) {
 
730
        if (d->ignore_next_surrounding_text == surrounding_text and
 
731
            d->ignore_next_cursor_position == cursor_position) {
 
732
            d->ignore_next_surrounding_text.clear();
 
733
            d->ignore_next_cursor_position = -1;
 
734
        } else {
 
735
            d->text->setPreedit("");
 
736
            d->text->setCursorPosition(0);
 
737
        }
 
738
    } else {
 
739
        const int cursor_pos_relative_word_begin(r.start - r.cursor_position);
 
740
        const int word_begin_relative_cursor_pos(r.cursor_position - r.start);
 
741
        const QString word(surrounding_text.mid(r.start, r.length));
 
742
        Replacement word_r(cursor_pos_relative_word_begin, r.length,
 
743
                           word_begin_relative_cursor_pos);
 
744
 
 
745
        d->text->setPreedit(word, word_begin_relative_cursor_pos);
 
746
        // computeCandidates can change preedit face, so needs to happen
 
747
        // before sending preedit:
 
748
        d->word_engine->computeCandidates(d->text.data());
 
749
        sendPreeditString(d->text->preedit(), d->text->preeditFace(), word_r);
 
750
        // Qt is going to send us an event with cursor position places
 
751
        // at the beginning of replaced word and surrounding text
 
752
        // without the replaced word. We want to ignore it.
 
753
        d->ignore_next_cursor_position = r.start;
 
754
        d->ignore_next_surrounding_text = QString(surrounding_text).remove(r.start, r.length);
 
755
    }
 
756
}
 
757
 
 
758
//! \brief sets language features
 
759
//! \param language id as string (as found in settings file)
 
760
//!
 
761
void AbstractTextEditor::onLanguageChanged(const QString& languageId)
 
762
{
 
763
    Q_D(AbstractTextEditor);
 
764
 
 
765
    if (languageId == "zh_cn_pinyin")
 
766
        d->language_features.reset(new Logic::ChineseLanguageFeatures);
 
767
    else
 
768
        d->language_features.reset(new Logic::LanguageFeatures);
 
769
}
 
770
 
 
771
} // namespace MaliitKeyboard