~ubuntu-branches/ubuntu/vivid/kdepim/vivid

« back to all changes in this revision

Viewing changes to messagecomposer/autocorrection/composerautocorrection.cpp

  • Committer: Package Import Robot
  • Author(s): Scott Kitterman, Jonathan Riddell, Rohan Garg, Scott Kitterman
  • Date: 2012-11-21 13:12:36 UTC
  • mfrom: (0.2.33)
  • Revision ID: package-import@ubuntu.com-20121121131236-32ijw9a2txrar80k
Tags: 4:4.9.80-0ubuntu1
[ Jonathan Riddell ]
* New upstream beta release

[ Rohan Garg ]
* Add nepomuk-core-dev to build-deps

[ Scott Kitterman ]
* Add new package, libpimcommon4
  - Add libpimcommon4.install
  - Add to debian/control, including kdepim-dbg and kdepim-dev depends
  - Add to kdepim-dev.install
* Remove usr/bin/backupmail and related files from kmail.install as they are
  not provided by upstream anymore
* Add usr/bin/pimsettingexporter and related files to kmail.install
* Add libnepomukwidgets-dev to build-depends

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
  Copyright (c) 2012 Montel Laurent <montel@kde.org>
 
3
  code based on calligra autocorrection.
 
4
  
 
5
  This program is free software; you can redistribute it and/or modify it
 
6
  under the terms of the GNU General Public License, version 2, as
 
7
  published by the Free Software Foundation.
 
8
  
 
9
  This program is distributed in the hope that it will be useful, but
 
10
  WITHOUT ANY WARRANTY; without even the implied warranty of
 
11
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 
12
  General Public License for more details.
 
13
  
 
14
  You should have received a copy of the GNU General Public License along
 
15
  with this program; if not, write to the Free Software Foundation, Inc.,
 
16
  51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 
17
*/
 
18
 
 
19
#include "composerautocorrection.h"
 
20
#include "messagecomposersettings.h"
 
21
#include "import/importkmailautocorrection.h"
 
22
#include <KLocale>
 
23
#include <KGlobal>
 
24
#include <KColorScheme>
 
25
#include <KCalendarSystem>
 
26
#include <KStandardDirs>
 
27
#include <QTextBlock>
 
28
#include <QDomDocument>
 
29
#include <QFile>
 
30
 
 
31
using namespace MessageComposer;
 
32
 
 
33
ComposerAutoCorrection::ComposerAutoCorrection()
 
34
  : mSingleSpaces(true),
 
35
    mUppercaseFirstCharOfSentence(false),
 
36
    mFixTwoUppercaseChars(false),
 
37
    mAutoFractions(true),
 
38
    mCapitalizeWeekDays(false),
 
39
    mReplaceDoubleQuotes(false),
 
40
    mReplaceSingleQuotes(false),
 
41
    mEnabled(false),
 
42
    mSuperScriptAppendix(false)
 
43
{
 
44
  // default double quote open 0x201c
 
45
  // default double quote close 0x201d
 
46
  // default single quote open 0x2018
 
47
  // default single quote close 0x2019
 
48
  mTypographicSingleQuotes.begin = QChar(0x2018);
 
49
  mTypographicSingleQuotes.end = QChar(0x2019);
 
50
  mTypographicDoubleQuotes.begin = QChar(0x201c);
 
51
  mTypographicDoubleQuotes.end = QChar(0x201d);
 
52
 
 
53
  readConfig();
 
54
 
 
55
  KLocale *locale = KGlobal::locale();
 
56
  for (int i = 1; i <=7; i++)
 
57
    mCacheNameOfDays.append(locale->calendar()->weekDayName(i).toLower());
 
58
 
 
59
}
 
60
 
 
61
ComposerAutoCorrection::~ComposerAutoCorrection()
 
62
{
 
63
}
 
64
 
 
65
 
 
66
void ComposerAutoCorrection::selectWord(QTextCursor &cursor, int cursorPosition)
 
67
{
 
68
  cursor.setPosition(cursorPosition);
 
69
  QTextBlock block = cursor.block();
 
70
  cursor.setPosition(block.position());
 
71
  cursorPosition -= block.position();
 
72
  QString string = block.text();
 
73
  int pos = 0;
 
74
  bool space = false;
 
75
  QString::Iterator iter = string.begin();
 
76
  while (iter != string.end()) {
 
77
    if (iter->isSpace()) {
 
78
      if (space)
 
79
          ;// double spaces belong to the previous word
 
80
      else if (pos < cursorPosition)
 
81
        cursor.setPosition(pos + block.position() + 1); // +1 because we don't want to set it on the space itself
 
82
      else
 
83
       space = true;
 
84
    } else if (space) {
 
85
      break;
 
86
    }
 
87
    pos++;
 
88
    iter++;
 
89
  }
 
90
  cursor.setPosition(pos + block.position(), QTextCursor::KeepAnchor);
 
91
}
 
92
 
 
93
 
 
94
void ComposerAutoCorrection::autocorrect(bool htmlMode, QTextDocument& document, int position)
 
95
{
 
96
  if (!mEnabled)
 
97
    return;
 
98
  mCursor =  QTextCursor(&document);
 
99
  selectWord(mCursor,position);
 
100
  mWord = mCursor.selectedText();
 
101
  if (mWord.isEmpty())
 
102
    return;
 
103
 
 
104
  bool done = false;
 
105
  if (htmlMode) {
 
106
     done = autoFormatURLs();
 
107
     if(!done) {
 
108
         done = autoBoldUnderline();
 
109
     }
 
110
     if(!done) {
 
111
         superscriptAppendix();
 
112
     }
 
113
  }
 
114
  if (!done)
 
115
    done = singleSpaces();
 
116
  if (!done)
 
117
    done = autoFractions();
 
118
  if (!done)
 
119
    advancedAutocorrect();
 
120
  if (!done)
 
121
    uppercaseFirstCharOfSentence();
 
122
  if (!done)
 
123
    fixTwoUppercaseChars();
 
124
  if (!done)
 
125
    capitalizeWeekDays();
 
126
  if (!done)
 
127
    replaceTypographicQuotes();
 
128
 
 
129
  if (mCursor.selectedText() != mWord)
 
130
     mCursor.insertText(mWord);
 
131
}
 
132
 
 
133
void ComposerAutoCorrection::readConfig()
 
134
{
 
135
  mAutoBoldUnderline = MessageComposer::MessageComposerSettings::self()->autoBoldUnderline();
 
136
  mAutoFormatUrl = MessageComposer::MessageComposerSettings::self()->autoFormatUrl();
 
137
  mUppercaseFirstCharOfSentence = MessageComposer::MessageComposerSettings::self()->uppercaseFirstCharOfSentence();
 
138
  mFixTwoUppercaseChars = MessageComposer::MessageComposerSettings::self()->fixTwoUppercaseChars();
 
139
  mSingleSpaces = MessageComposer::MessageComposerSettings::self()->singleSpaces();
 
140
  mAutoFractions = MessageComposer::MessageComposerSettings::self()->autoFractions();
 
141
  mCapitalizeWeekDays = MessageComposer::MessageComposerSettings::self()->capitalizeWeekDays();
 
142
  mAdvancedAutocorrect = MessageComposer::MessageComposerSettings::self()->advancedAutocorrect();
 
143
  mReplaceDoubleQuotes = MessageComposer::MessageComposerSettings::self()->replaceDoubleQuotes();
 
144
  mReplaceSingleQuotes = MessageComposer::MessageComposerSettings::self()->replaceSingleQuotes();
 
145
  mEnabled = MessageComposer::MessageComposerSettings::self()->enabled();
 
146
  mSuperScriptAppendix = MessageComposer::MessageComposerSettings::self()->superScript();
 
147
  readAutoCorrectionXmlFile();
 
148
}
 
149
 
 
150
void ComposerAutoCorrection::writeConfig()
 
151
{
 
152
  MessageComposer::MessageComposerSettings::self()->setAutoBoldUnderline(mAutoBoldUnderline);
 
153
  MessageComposer::MessageComposerSettings::self()->setAutoFormatUrl(mAutoFormatUrl);
 
154
  MessageComposer::MessageComposerSettings::self()->setUppercaseFirstCharOfSentence(mUppercaseFirstCharOfSentence);
 
155
  MessageComposer::MessageComposerSettings::self()->setFixTwoUppercaseChars(mFixTwoUppercaseChars);
 
156
  MessageComposer::MessageComposerSettings::self()->setSingleSpaces(mSingleSpaces);
 
157
  MessageComposer::MessageComposerSettings::self()->setAutoFractions(mAutoFractions);
 
158
  MessageComposer::MessageComposerSettings::self()->setCapitalizeWeekDays(mCapitalizeWeekDays);
 
159
  MessageComposer::MessageComposerSettings::self()->setAdvancedAutocorrect(mAdvancedAutocorrect);
 
160
  MessageComposer::MessageComposerSettings::self()->setReplaceDoubleQuotes(mReplaceDoubleQuotes);
 
161
  MessageComposer::MessageComposerSettings::self()->setReplaceSingleQuotes(mReplaceSingleQuotes);
 
162
  MessageComposer::MessageComposerSettings::self()->setEnabled(mEnabled);
 
163
  MessageComposer::MessageComposerSettings::self()->setSuperScript(mSuperScriptAppendix);
 
164
  MessageComposer::MessageComposerSettings::self()->requestSync();
 
165
  writeAutoCorrectionXmlFile();
 
166
}
 
167
 
 
168
void ComposerAutoCorrection::addAutoCorrect(const QString &currentWord, const QString &replaceWord)
 
169
{
 
170
  mAutocorrectEntries.insert(currentWord, replaceWord);
 
171
  writeAutoCorrectionXmlFile();
 
172
}
 
173
 
 
174
 
 
175
void ComposerAutoCorrection::setUpperCaseExceptions(const QSet<QString>& exceptions)
 
176
{
 
177
  mUpperCaseExceptions = exceptions;
 
178
}
 
179
 
 
180
void ComposerAutoCorrection::setTwoUpperLetterExceptions(const QSet<QString>& exceptions)
 
181
{
 
182
  mTwoUpperLetterExceptions = exceptions;
 
183
}
 
184
 
 
185
void ComposerAutoCorrection::setAutocorrectEntries(const QHash<QString, QString>& entries)
 
186
{
 
187
  mAutocorrectEntries = entries;
 
188
}
 
189
 
 
190
 
 
191
ComposerAutoCorrection::TypographicQuotes ComposerAutoCorrection::typographicDefaultSingleQuotes()
 
192
{
 
193
  ComposerAutoCorrection::TypographicQuotes quote;
 
194
  quote.begin = QChar(0x2018);
 
195
  quote.end = QChar(0x2019);
 
196
  return quote;
 
197
}
 
198
 
 
199
ComposerAutoCorrection::TypographicQuotes ComposerAutoCorrection::typographicDefaultDoubleQuotes()
 
200
{
 
201
  ComposerAutoCorrection::TypographicQuotes quote;
 
202
  quote.begin = QChar(0x201c);
 
203
  quote.end = QChar(0x201d);
 
204
  return quote;
 
205
}
 
206
 
 
207
QSet<QString> ComposerAutoCorrection::upperCaseExceptions() const
 
208
{
 
209
  return mUpperCaseExceptions;
 
210
}
 
211
 
 
212
QSet<QString> ComposerAutoCorrection::twoUpperLetterExceptions() const
 
213
{
 
214
  return mTwoUpperLetterExceptions;
 
215
}
 
216
 
 
217
QHash<QString, QString> ComposerAutoCorrection::autocorrectEntries() const
 
218
{
 
219
  return mAutocorrectEntries;
 
220
}
 
221
 
 
222
void ComposerAutoCorrection::superscriptAppendix()
 
223
{
 
224
    if (!mSuperScriptAppendix) return;
 
225
 
 
226
    QString trimmed = mWord.trimmed();
 
227
    int startPos = -1;
 
228
    int endPos = -1;
 
229
 
 
230
    QHash<QString, QString>::const_iterator i = mSuperScriptEntries.constBegin();
 
231
    while (i != mSuperScriptEntries.constEnd()) {
 
232
        if (i.key() == trimmed) {
 
233
            startPos = mCursor.selectionStart() + 1;
 
234
            endPos = startPos - 1 + trimmed.length();
 
235
            break;
 
236
        }
 
237
        else if (i.key() == QLatin1String("othernb")) {
 
238
            int pos = trimmed.indexOf(i.value());
 
239
            if (pos > 0) {
 
240
                QString number = trimmed.left(pos);
 
241
                QString::ConstIterator constIter = number.constBegin();
 
242
                bool found = true;
 
243
                // don't apply superscript to 1th, 2th and 3th
 
244
                if (number.length() == 1 &&
 
245
                        (*constIter == QLatin1Char('1') || *constIter == QLatin1Char('2') || *constIter == QLatin1Char('3')))
 
246
                    found = false;
 
247
                if (found) {
 
248
                    while (constIter != number.constEnd()) {
 
249
                        if (!constIter->isNumber()) {
 
250
                            found = false;
 
251
                            break;
 
252
                        }
 
253
                        ++constIter;
 
254
                    }
 
255
                }
 
256
                if (found && number.length() + i.value().length() == trimmed.length()) {
 
257
                    startPos = mCursor.selectionStart() + pos;
 
258
                    endPos = startPos - pos + trimmed.length();
 
259
                    break;
 
260
                }
 
261
            }
 
262
        }
 
263
        ++i;
 
264
    }
 
265
 
 
266
    if (startPos != -1 && endPos != -1) {
 
267
        QTextCursor cursor(mCursor);
 
268
        cursor.setPosition(startPos);
 
269
        cursor.setPosition(endPos, QTextCursor::KeepAnchor);
 
270
 
 
271
        QTextCharFormat format;
 
272
        format.setVerticalAlignment(QTextCharFormat::AlignSuperScript);
 
273
        cursor.mergeCharFormat(format);
 
274
    }
 
275
}
 
276
 
 
277
bool ComposerAutoCorrection::autoBoldUnderline()
 
278
{
 
279
    if (!mAutoBoldUnderline)
 
280
        return false;
 
281
 
 
282
    QString trimmed = mWord.trimmed();
 
283
 
 
284
    if (trimmed.length() < 3)
 
285
        return false;
 
286
 
 
287
    bool underline = (trimmed.at(0) == QLatin1Char('_') && trimmed.at(trimmed.length() - 1) == QLatin1Char('_'));
 
288
    bool bold = (trimmed.at(0) == QLatin1Char('*') && trimmed.at(trimmed.length() - 1) == QLatin1Char('*'));
 
289
 
 
290
    if (underline || bold) {
 
291
        int startPos = mCursor.selectionStart();
 
292
        QString replacement = trimmed.mid(1, trimmed.length() - 2);
 
293
        bool foundLetterNumber = false;
 
294
 
 
295
        QString::ConstIterator constIter = replacement.constBegin();
 
296
        while (constIter != replacement.constEnd()) {
 
297
            if (constIter->isLetterOrNumber()) {
 
298
                foundLetterNumber = true;
 
299
                break;
 
300
            }
 
301
            constIter++;
 
302
        }
 
303
 
 
304
        // if no letter/number found, don't apply autocorrection like in OOo 2.x
 
305
        if (!foundLetterNumber)
 
306
            return false;
 
307
 
 
308
        mCursor.setPosition(startPos);
 
309
        mCursor.setPosition(startPos + trimmed.length(), QTextCursor::KeepAnchor);
 
310
        mCursor.insertText(replacement);
 
311
        mCursor.setPosition(startPos);
 
312
        mCursor.setPosition(startPos + replacement.length(), QTextCursor::KeepAnchor);
 
313
 
 
314
        QTextCharFormat format;
 
315
        format.setFontUnderline(underline ? true : mCursor.charFormat().fontUnderline());
 
316
        format.setFontWeight(bold ? QFont::Bold : mCursor.charFormat().fontWeight());
 
317
        mCursor.mergeCharFormat(format);
 
318
 
 
319
        // to avoid the selection being replaced by mWord
 
320
        mWord = mCursor.selectedText();
 
321
 
 
322
        // don't do this again if the text is already underlined and bold
 
323
        if(mCursor.charFormat().fontUnderline()
 
324
            && mCursor.charFormat().fontWeight() == QFont::Bold) {
 
325
            return true;
 
326
        } else {
 
327
            return autoBoldUnderline();
 
328
        }
 
329
    }
 
330
    else
 
331
        return false;
 
332
 
 
333
    return true;
 
334
}
 
335
 
 
336
bool ComposerAutoCorrection::autoFormatURLs()
 
337
{
 
338
    if (!mAutoFormatUrl)
 
339
        return false;
 
340
 
 
341
    QString link = autoDetectURL(mWord);
 
342
    if (link.isNull())
 
343
        return false;
 
344
 
 
345
    QString trimmed = mWord.trimmed();
 
346
    int startPos = mCursor.selectionStart();
 
347
    mCursor.setPosition(startPos);
 
348
    mCursor.setPosition(startPos + trimmed.length(), QTextCursor::KeepAnchor);
 
349
 
 
350
    QTextCharFormat format;
 
351
    format.setAnchorHref(link);
 
352
    format.setFontItalic(true);
 
353
    format.setAnchor(true);
 
354
    format.setUnderlineStyle(QTextCharFormat::SingleUnderline);
 
355
    format.setUnderlineColor(KColorScheme(QPalette::Active, KColorScheme::View).foreground(KColorScheme::LinkText).color());
 
356
    format.setForeground(KColorScheme(QPalette::Active, KColorScheme::View).foreground(KColorScheme::LinkText).color());
 
357
    mCursor.mergeCharFormat(format);
 
358
 
 
359
    mWord = mCursor.selectedText();
 
360
    return true;
 
361
}
 
362
 
 
363
QString ComposerAutoCorrection::autoDetectURL(const QString &_word) const
 
364
{
 
365
    QString word = _word;
 
366
 
 
367
    /* this method is ported from lib/kotext/KoAutoFormat.cpp KoAutoFormat::doAutoDetectUrl
 
368
     * from Calligra 1.x branch */
 
369
    // kDebug() <<"link:" << word;
 
370
 
 
371
    char link_type = 0;
 
372
    int pos = word.indexOf(QLatin1String("http://"));
 
373
    int tmp_pos = word.indexOf(QLatin1String("https://"));
 
374
 
 
375
    if (tmp_pos < pos && tmp_pos != -1)
 
376
          pos = tmp_pos;
 
377
    tmp_pos = word.indexOf(QLatin1String("mailto:/"));
 
378
    if ((tmp_pos < pos || pos == -1) && tmp_pos != -1)
 
379
          pos = tmp_pos;
 
380
    tmp_pos = word.indexOf(QLatin1String("ftp://"));
 
381
    if ((tmp_pos < pos || pos == -1) && tmp_pos != -1)
 
382
          pos = tmp_pos;
 
383
    tmp_pos = word.indexOf(QLatin1String("ftp."));
 
384
    if ((tmp_pos < pos || pos == -1) && tmp_pos != -1) {
 
385
          pos = tmp_pos;
 
386
          link_type = 3;
 
387
    }
 
388
    tmp_pos = word.indexOf(QLatin1String("file:/"));
 
389
    if ((tmp_pos < pos || pos == -1) && tmp_pos != -1)
 
390
          pos = tmp_pos;
 
391
    tmp_pos = word.indexOf(QLatin1String("news:"));
 
392
    if ((tmp_pos < pos || pos == -1) && tmp_pos != -1)
 
393
          pos = tmp_pos;
 
394
    tmp_pos = word.indexOf(QLatin1String("www."));
 
395
    if ((tmp_pos < pos || pos == -1) && tmp_pos != -1 && word.indexOf(QLatin1Char('.'), tmp_pos+4) != -1 ) {
 
396
          pos = tmp_pos;
 
397
          link_type = 2;
 
398
    }
 
399
    tmp_pos = word.indexOf(QLatin1Char('@'));
 
400
    if (pos == -1 && tmp_pos != -1) {
 
401
        pos = tmp_pos-1;
 
402
        QChar c;
 
403
 
 
404
        while (pos >= 0) {
 
405
            c = word.at(pos);
 
406
            if (c.isPunct() && c != QLatin1Char('.') && c != QLatin1Char('_')) break;
 
407
            else --pos;
 
408
        }
 
409
        if (pos == tmp_pos - 1) // not a valid address
 
410
            pos = -1;
 
411
        else
 
412
            ++pos;
 
413
        link_type = 1;
 
414
    }
 
415
 
 
416
    if (pos != -1) {
 
417
        // A URL inside e.g. quotes (like "http://www.calligra.org" with the quotes) shouldn't include the quote in the URL.
 
418
        while (!word.at(word.length()-1).isLetter() &&  !word.at(word.length()-1).isDigit() && word.at(word.length()-1) != QLatin1Char('/'))
 
419
            word.truncate(word.length() - 1);
 
420
        word.remove(0, pos);
 
421
        QString newWord = word;
 
422
 
 
423
        if (link_type == 1)
 
424
            newWord = QLatin1String("mailto:") + word;
 
425
        else if (link_type == 2)
 
426
            newWord = QLatin1String("http://") + word;
 
427
        else if (link_type == 3)
 
428
            newWord = QLatin1String("ftp://") + word;
 
429
 
 
430
        kDebug() <<"newWord:" << newWord;
 
431
        return newWord;
 
432
    }
 
433
 
 
434
    return QString();
 
435
}
 
436
 
 
437
void ComposerAutoCorrection::fixTwoUppercaseChars()
 
438
{
 
439
  if (!mFixTwoUppercaseChars)
 
440
    return;
 
441
  if (mWord.length() <= 2)
 
442
    return;
 
443
 
 
444
  if (mTwoUpperLetterExceptions.contains(mWord.trimmed()))
 
445
    return;
 
446
 
 
447
  QChar firstChar = mWord.at(0);
 
448
  QChar secondChar = mWord.at(1);
 
449
 
 
450
  if (secondChar.isUpper()) {
 
451
    QChar thirdChar = mWord.at(2);
 
452
 
 
453
    if (firstChar.isUpper() && thirdChar.isLower())
 
454
      mWord.replace(1, 1, secondChar.toLower());
 
455
  }
 
456
}
 
457
 
 
458
 
 
459
bool ComposerAutoCorrection::singleSpaces()
 
460
{
 
461
  if (!mSingleSpaces)
 
462
      return false;
 
463
  if (!mCursor.atBlockStart() && mWord.length() == 1 && mWord.at(0) == QLatin1Char(' ')) {
 
464
    // then when the prev char is also a space, don't insert one.
 
465
    QTextBlock block = mCursor.block();
 
466
    QString text = block.text();
 
467
    if (text.at(mCursor.position() -1 - block.position()) == QLatin1Char(' ')) {
 
468
      mWord.clear();
 
469
      return true;
 
470
    }
 
471
  }
 
472
  return false;
 
473
}
 
474
 
 
475
void ComposerAutoCorrection::capitalizeWeekDays()
 
476
{
 
477
  if (!mCapitalizeWeekDays)
 
478
    return;
 
479
 
 
480
  const QString trimmed = mWord.trimmed();
 
481
  Q_FOREACH (const QString & name, mCacheNameOfDays) {
 
482
    if (trimmed == name) {
 
483
       int pos = mWord.indexOf(name);
 
484
       mWord.replace(pos, 1, name.at(0).toUpper());
 
485
       return;
 
486
    }
 
487
  }
 
488
}
 
489
 
 
490
void ComposerAutoCorrection::uppercaseFirstCharOfSentence()
 
491
{
 
492
    if (!mUppercaseFirstCharOfSentence)
 
493
        return;
 
494
 
 
495
    int startPos = mCursor.selectionStart();
 
496
    QTextBlock block = mCursor.block();
 
497
 
 
498
    mCursor.setPosition(block.position());
 
499
    mCursor.setPosition(startPos, QTextCursor::KeepAnchor);
 
500
 
 
501
    int position = mCursor.selectionEnd();
 
502
 
 
503
    QString text = mCursor.selectedText();
 
504
 
 
505
    if (text.isEmpty()) // start of a paragraph
 
506
        mWord.replace(0, 1, mWord.at(0).toUpper());
 
507
    else {
 
508
        QString::ConstIterator constIter = text.constEnd();
 
509
        constIter--;
 
510
 
 
511
        while (constIter != text.constBegin()) {
 
512
            while (constIter != text.begin() && constIter->isSpace()) {
 
513
                constIter--;
 
514
                position--;
 
515
            }
 
516
 
 
517
            if (constIter != text.constBegin() && (*constIter == QLatin1Char('.') || *constIter == QLatin1Char('!') || *constIter == QLatin1Char('?'))) {
 
518
                constIter--;
 
519
                while (constIter != text.constBegin() && !(constIter->isLetter())) {
 
520
                    position--;
 
521
                    constIter--;
 
522
                }
 
523
                bool replace = true;
 
524
                selectWord(mCursor, --position);
 
525
                QString prevWord = mCursor.selectedText();
 
526
 
 
527
                // search for exception
 
528
                if (mUpperCaseExceptions.contains(prevWord.trimmed()))
 
529
                    replace = false;
 
530
 
 
531
                if (replace)
 
532
                    mWord.replace(0, 1, mWord.at(0).toUpper());
 
533
                break;
 
534
            }
 
535
            else
 
536
                break;
 
537
        }
 
538
    }
 
539
 
 
540
    mCursor.setPosition(startPos);
 
541
    mCursor.setPosition(startPos + mWord.length(), QTextCursor::KeepAnchor);
 
542
}
 
543
 
 
544
bool ComposerAutoCorrection::autoFractions()
 
545
{
 
546
    if (!mAutoFractions)
 
547
        return false;
 
548
 
 
549
    QString trimmed = mWord.trimmed();
 
550
    if (trimmed.length() > 3) {
 
551
        QChar x = trimmed.at(3);
 
552
        if (!(x.unicode() == '.' || x.unicode() == ',' || x.unicode() == '?' || x.unicode() == '!'
 
553
                || x.unicode() == ':' || x.unicode() == ';'))
 
554
            return false;
 
555
    } else if (trimmed.length() < 3) {
 
556
        return false;
 
557
    }
 
558
 
 
559
    if (trimmed.startsWith(QLatin1String("1/2")))
 
560
        mWord.replace(0, 3, QString::fromUtf8("½"));
 
561
    else if (trimmed.startsWith(QLatin1String("1/4")))
 
562
        mWord.replace(0, 3, QString::fromUtf8("¼"));
 
563
    else if (trimmed.startsWith(QLatin1String("3/4")))
 
564
        mWord.replace(0, 3, QString::fromUtf8("¾"));
 
565
    else
 
566
        return false;
 
567
 
 
568
    return true;
 
569
}
 
570
 
 
571
void ComposerAutoCorrection::advancedAutocorrect()
 
572
{
 
573
  if (!mAdvancedAutocorrect)
 
574
    return;
 
575
 
 
576
  int startPos = mCursor.selectionStart();
 
577
  int length = mWord.length();
 
578
 
 
579
  QString trimmedWord = mWord.toLower().trimmed();
 
580
  QString actualWord = trimmedWord;
 
581
 
 
582
  if (actualWord.isEmpty())
 
583
    return;
 
584
 
 
585
  // If the last char is punctuation, drop it for now
 
586
  bool hasPunctuation = false;
 
587
  QChar lastChar = actualWord.at(actualWord.length() - 1);
 
588
  if (lastChar.unicode() == '.' || lastChar.unicode() == ',' || lastChar.unicode() == '?' ||
 
589
        lastChar.unicode() == '!' || lastChar.unicode() == ':' || lastChar.unicode() == ';') {
 
590
      hasPunctuation = true;
 
591
      actualWord.chop(1);
 
592
  }
 
593
 
 
594
  if (mAutocorrectEntries.contains(actualWord)) {
 
595
    int pos = mWord.toLower().indexOf(trimmedWord);
 
596
    QString replacement = mAutocorrectEntries.value(actualWord);
 
597
    // Keep capitalized words capitalized.
 
598
    // (Necessary to make sure the first letters match???)
 
599
    if (actualWord.at(0) == replacement.at(0).toLower()) {
 
600
      if (mWord.at(0).isUpper()) {
 
601
        replacement[0] = replacement[0].toUpper();
 
602
      }
 
603
    }
 
604
 
 
605
    // If a punctuation mark was on the end originally, add it back on
 
606
    if (hasPunctuation) {
 
607
       replacement.append(lastChar);
 
608
    }
 
609
 
 
610
    mWord.replace(pos, pos + trimmedWord.length(), replacement);
 
611
 
 
612
    // We do replacement here, since the length of new word might be different from length of
 
613
    // the old world. Length difference might affect other type of autocorrection
 
614
    mCursor.setPosition(startPos);
 
615
    mCursor.setPosition(startPos + length, QTextCursor::KeepAnchor);
 
616
    mCursor.insertText(mWord);
 
617
    mCursor.setPosition(startPos); // also restore the selection
 
618
    mCursor.setPosition(startPos + mWord.length(), QTextCursor::KeepAnchor);
 
619
  }
 
620
}
 
621
 
 
622
void ComposerAutoCorrection::replaceTypographicQuotes()
 
623
{
 
624
    /* this method is ported from lib/kotext/KoAutoFormat.cpp KoAutoFormat::doTypographicQuotes
 
625
     * from Calligra 1.x branch */
 
626
 
 
627
    if (!(mReplaceDoubleQuotes && mWord.contains(QLatin1Char('"'))) &&
 
628
            !(mReplaceSingleQuotes && mWord.contains(QLatin1Char('\'')))) return;
 
629
 
 
630
    // Need to determine if we want a starting or ending quote.
 
631
    // we use a starting quote in three cases:
 
632
    //  1. if the previous character is a space
 
633
    //  2. if the previous character is some kind of opening punctuation (e.g., "(", "[", or "{")
 
634
    //     a. and the character before that is not an opening quote (so that we get quotations of single characters
 
635
    //        right)
 
636
    //  3. if the previous character is an opening quote (so that we get nested quotations right)
 
637
    //     a. and the character before that is not an opening quote (so that we get quotations of single characters
 
638
    //         right)
 
639
    //     b. and the previous quote of a different kind (so that we get empty quotations right)
 
640
 
 
641
    bool ending = true;
 
642
    QString::Iterator iter = mWord.end();
 
643
    iter--;
 
644
 
 
645
    while (iter != mWord.begin()) {
 
646
        if (*iter == QLatin1Char('"') || *iter == QLatin1Char('\'')) {
 
647
            bool doubleQuotes = *iter == QLatin1Char('"');
 
648
 
 
649
            if ((iter - 1) != mWord.begin()) {
 
650
                QChar::Category c1 = (*(iter - 1)).category();
 
651
 
 
652
                // case 1 and 2
 
653
                if (c1 == QChar::Separator_Space || c1 == QChar::Separator_Line || c1 == QChar::Separator_Paragraph ||
 
654
                        c1 == QChar::Punctuation_Open || c1 == QChar::Other_Control)
 
655
                    ending = false;
 
656
 
 
657
                // case 3
 
658
                if (c1 == QChar::Punctuation_InitialQuote) {
 
659
                    QChar openingQuote;
 
660
 
 
661
                    if (doubleQuotes)
 
662
                        openingQuote = mTypographicDoubleQuotes.begin;
 
663
                    else
 
664
                        openingQuote = mTypographicSingleQuotes.begin;
 
665
 
 
666
                    // case 3b
 
667
                    if (*(iter - 1) != openingQuote)
 
668
                        ending = false;
 
669
                }
 
670
            }
 
671
 
 
672
            // case 2a and 3a
 
673
            if ((iter - 2) != mWord.constBegin() && !ending)
 
674
            {
 
675
                 QChar::Category c2 = (*(iter - 2)).category();
 
676
                 ending = (c2 == QChar::Punctuation_InitialQuote);
 
677
            }
 
678
 
 
679
            if (doubleQuotes && mReplaceDoubleQuotes) {
 
680
                if (!ending)
 
681
                    *iter = mTypographicDoubleQuotes.begin;
 
682
                else
 
683
                    *iter = mTypographicDoubleQuotes.end;
 
684
            }
 
685
            else if (mReplaceSingleQuotes) {
 
686
                if (!ending)
 
687
                    *iter = mTypographicSingleQuotes.begin;
 
688
                else
 
689
                    *iter = mTypographicSingleQuotes.end;
 
690
            }
 
691
        }
 
692
        iter--;
 
693
    }
 
694
 
 
695
    // first character
 
696
    if (*iter == QLatin1Char('"') && mReplaceDoubleQuotes)
 
697
        *iter = mTypographicDoubleQuotes.begin;
 
698
    else if (*iter == QLatin1Char('\'') && mReplaceSingleQuotes)
 
699
        *iter = mTypographicSingleQuotes.begin;
 
700
}
 
701
 
 
702
 
 
703
void ComposerAutoCorrection::readAutoCorrectionXmlFile( bool forceGlobal )
 
704
{
 
705
    KLocale *locale = KGlobal::locale();
 
706
    QString kdelang = locale->languageList().first();
 
707
    kdelang.remove(QRegExp(QLatin1String("@.*")));
 
708
 
 
709
    //qDebug()<<"void ComposerAutoCorrection::readAutoCorrectionXmlFile() "<<mAutoCorrectLang;
 
710
 
 
711
    mUpperCaseExceptions.clear();
 
712
    mAutocorrectEntries.clear();
 
713
    mTwoUpperLetterExceptions.clear();
 
714
    mSuperScriptEntries.clear();
 
715
 
 
716
 
 
717
    QString LocalFile;
 
718
    //Look at local file:
 
719
    if(!forceGlobal) {
 
720
        if (!mAutoCorrectLang.isEmpty()) {
 
721
            LocalFile = KGlobal::dirs()->findResource("data", QLatin1String("autocorrect/custom-") + mAutoCorrectLang + QLatin1String(".xml"));
 
722
        } else {
 
723
            if(!kdelang.isEmpty())
 
724
                LocalFile = KGlobal::dirs()->findResource("data", QLatin1String("autocorrect/custom-") + kdelang + QLatin1String(".xml"));
 
725
            if (LocalFile.isEmpty() && kdelang.contains(QLatin1String("_"))) {
 
726
                kdelang.remove( QRegExp( QLatin1String("_.*") ) );
 
727
                LocalFile = KGlobal::dirs()->findResource("data", QLatin1String("autocorrect/custom-") + kdelang + QLatin1String(".xml"));
 
728
            }
 
729
        }
 
730
    }
 
731
    QString fname;
 
732
    //Load Global directly
 
733
    if (!mAutoCorrectLang.isEmpty()) {
 
734
        if(mAutoCorrectLang == QLatin1String("en_US")) {
 
735
          fname = KGlobal::dirs()->findResource("data", QLatin1String("autocorrect/autocorrect.xml"));
 
736
        } else {
 
737
          fname = KGlobal::dirs()->findResource("data", QLatin1String("autocorrect/") + mAutoCorrectLang + QLatin1String(".xml"));
 
738
        }
 
739
    } else {
 
740
        if (fname.isEmpty() && !kdelang.isEmpty())
 
741
            fname = KGlobal::dirs()->findResource("data", QLatin1String("autocorrect/") + kdelang + QLatin1String(".xml"));
 
742
        if (fname.isEmpty() && kdelang.contains(QLatin1String("_"))) {
 
743
            kdelang.remove( QRegExp( QLatin1String("_.*") ) );
 
744
            fname = KGlobal::dirs()->findResource("data", QLatin1String("autocorrect/") + kdelang + QLatin1String(".xml"));
 
745
        }
 
746
    }
 
747
    if (fname.isEmpty())
 
748
        fname = KGlobal::dirs()->findResource("data", QLatin1String("autocorrect/autocorrect.xml"));
 
749
 
 
750
 
 
751
    if (mAutoCorrectLang.isEmpty())
 
752
        mAutoCorrectLang = kdelang;
 
753
    qDebug()<<" fname :"<<fname;
 
754
    qDebug()<<" LocalFile:"<<LocalFile;
 
755
 
 
756
    if(LocalFile.isEmpty()) {
 
757
        if(fname.isEmpty()) {
 
758
            mTypographicSingleQuotes = typographicDefaultSingleQuotes();
 
759
            mTypographicDoubleQuotes = typographicDefaultDoubleQuotes();
 
760
        } else {
 
761
            ImportKMailAutocorrection import;
 
762
            if (import.import(fname,ImportAbstractAutocorrection::All)) {
 
763
                mUpperCaseExceptions = import.upperCaseExceptions();
 
764
                mTwoUpperLetterExceptions = import.twoUpperLetterExceptions();
 
765
                mAutocorrectEntries = import.autocorrectEntries();
 
766
                mTypographicSingleQuotes = import.typographicSingleQuotes();
 
767
                mTypographicDoubleQuotes = import.typographicDoubleQuotes();
 
768
                mSuperScriptEntries = import.superScriptEntries();
 
769
            }
 
770
        }
 
771
    } else {
 
772
        ImportKMailAutocorrection import;
 
773
        if (import.import(LocalFile,ImportAbstractAutocorrection::All)) {
 
774
            mUpperCaseExceptions = import.upperCaseExceptions();
 
775
            mTwoUpperLetterExceptions = import.twoUpperLetterExceptions();
 
776
            mAutocorrectEntries = import.autocorrectEntries();
 
777
            mTypographicSingleQuotes = import.typographicSingleQuotes();
 
778
            mTypographicDoubleQuotes = import.typographicDoubleQuotes();
 
779
            //Don't import it in local
 
780
            //mSuperScriptEntries = import.superScriptEntries();
 
781
        }
 
782
        if (!fname.isEmpty() && import.import(fname,ImportAbstractAutocorrection::SuperScript)) {
 
783
            mSuperScriptEntries = import.superScriptEntries();
 
784
        }
 
785
        qDebug()<<" mSuperScriptEntries"<<mSuperScriptEntries;
 
786
    }
 
787
}
 
788
 
 
789
void ComposerAutoCorrection::writeAutoCorrectionXmlFile()
 
790
{
 
791
    const QString fname = KGlobal::dirs()->locateLocal("data", QLatin1String("autocorrect/custom-") + (mAutoCorrectLang == QLatin1String("en_US") ? QLatin1String("autocorrect") : mAutoCorrectLang) + QLatin1String(".xml"));
 
792
    QFile file(fname);
 
793
    if( !file.open( QIODevice::WriteOnly | QIODevice::Text ) ) {
 
794
        kDebug()<<"We can't save in file :"<<fname;
 
795
        return;
 
796
    }
 
797
    QDomDocument root(QLatin1String("autocorrection"));
 
798
 
 
799
    QDomElement word = root.createElement(QLatin1String( "Word" ));
 
800
    root.appendChild(word);
 
801
    QDomElement items = root.createElement(QLatin1String( "items" ));
 
802
 
 
803
    QHashIterator<QString, QString> i(mAutocorrectEntries);
 
804
    while (i.hasNext()) {
 
805
        i.next();
 
806
        QDomElement item = root.createElement(QLatin1String( "item" ));
 
807
        item.setAttribute(QLatin1String("find"),i.key());
 
808
        item.setAttribute(QLatin1String("replace"),i.value());
 
809
        items.appendChild(item);
 
810
    }
 
811
    word.appendChild(items);
 
812
 
 
813
 
 
814
    QDomElement upperCaseExceptions = root.createElement(QLatin1String( "UpperCaseExceptions" ));
 
815
    QSet<QString>::const_iterator upper = mUpperCaseExceptions.constBegin();
 
816
    while (upper != mUpperCaseExceptions.constEnd()) {
 
817
        QDomElement item = root.createElement(QLatin1String( "word" ));
 
818
        item.setAttribute(QLatin1String("exception"),*upper);
 
819
        upperCaseExceptions.appendChild(item);
 
820
        ++upper;
 
821
    }
 
822
    word.appendChild(upperCaseExceptions);
 
823
 
 
824
    QDomElement twoUpperLetterExceptions = root.createElement(QLatin1String( "TwoUpperLetterExceptions" ));
 
825
    QSet<QString>::const_iterator twoUpper = mTwoUpperLetterExceptions.constBegin();
 
826
    while (twoUpper != mTwoUpperLetterExceptions.constEnd()) {
 
827
        QDomElement item = root.createElement(QLatin1String( "word" ));
 
828
        item.setAttribute(QLatin1String("exception"),*twoUpper);
 
829
        twoUpperLetterExceptions.appendChild(item);
 
830
        ++twoUpper;
 
831
    }
 
832
    word.appendChild(twoUpperLetterExceptions);
 
833
 
 
834
    //Don't save it as  discussed with Calligra dev
 
835
    /*
 
836
    QDomElement supperscript = root.createElement(QLatin1String( "SuperScript" ));
 
837
    QHashIterator<QString, QString> j(mSuperScriptEntries);
 
838
    while (j.hasNext()) {
 
839
        j.next();
 
840
        QDomElement item = root.createElement(QLatin1String( "superscript" ));
 
841
        item.setAttribute(QLatin1String("find"), j.key());
 
842
        item.setAttribute(QLatin1String("super"), j.value());
 
843
        supperscript.appendChild(item);
 
844
    }
 
845
    word.appendChild(supperscript);
 
846
*/
 
847
 
 
848
    QDomElement doubleQuote = root.createElement(QLatin1String( "DoubleQuote" ));
 
849
    QDomElement item = root.createElement(QLatin1String( "doublequote" ));
 
850
    item.setAttribute(QLatin1String("begin"),mTypographicDoubleQuotes.begin);
 
851
    item.setAttribute(QLatin1String("end"),mTypographicDoubleQuotes.end);
 
852
    doubleQuote.appendChild(item);
 
853
    word.appendChild(doubleQuote);
 
854
 
 
855
    QDomElement singleQuote = root.createElement(QLatin1String( "SimpleQuote" ));
 
856
    item = root.createElement(QLatin1String( "simplequote" ));
 
857
    item.setAttribute(QLatin1String("begin"),mTypographicSingleQuotes.begin);
 
858
    item.setAttribute(QLatin1String("end"),mTypographicSingleQuotes.end);
 
859
    singleQuote.appendChild(item);
 
860
    word.appendChild(singleQuote);
 
861
 
 
862
 
 
863
    QTextStream ts( &file );
 
864
    ts << root.toString();
 
865
    file.close();
 
866
}
 
867
 
 
868
 
 
869
QString ComposerAutoCorrection::language() const
 
870
{
 
871
  return mAutoCorrectLang;
 
872
}
 
873
 
 
874
void ComposerAutoCorrection::setLanguage(const QString &lang, bool forceGlobal)
 
875
{
 
876
  if(mAutoCorrectLang != lang || forceGlobal) {
 
877
    mAutoCorrectLang = lang;
 
878
    //Re-read xml file
 
879
    readAutoCorrectionXmlFile(forceGlobal);
 
880
  }
 
881
}
 
882