1
/* This file is part of the KDE project
2
* Copyright (C) 2005, 2007 Thomas Zander <zander@kde.org>
3
* Copyright (C) 2012 Shreya Pandit <shreya@shreyapandit.com>
4
* This library is free software; you can redistribute it and/or
5
* modify it under the terms of the GNU Library General Public
6
* License as published by the Free Software Foundation; either
7
* version 2 of the License, or (at your option) any later version.
9
* This library is distributed in the hope that it will be useful,
10
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12
* Library General Public License for more details.
14
* You should have received a copy of the GNU Library General Public License
15
* along with this library; see the file COPYING.LIB. If not, write to
16
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17
* Boston, MA 02110-1301, USA.
20
#include "KWStatistics.h"
23
#include "KWDocument.h"
24
#include "frames/KWFrame.h"
25
#include "frames/KWFrameSet.h"
26
#include "frames/KWTextFrameSet.h"
27
#include <ui_KWStatisticsDocker.h>
28
#include "dockers/StatisticsPreferencesPopup.h"
29
#include <KoCanvasResourceManager.h>
30
#include <KoSelection.h>
33
#include <QTextLayout>
34
#include <QTextDocument>
38
KWStatistics::KWStatistics(KoCanvasResourceManager *provider, KWDocument *document, KoSelection *selection, QWidget *parent)
40
m_resourceManager(provider),
41
m_selection(selection),
46
m_charsWithoutSpace(0),
54
m_showInDocker = true;
55
m_timer = new QTimer(this);
57
widgetDocker.setupUi(this);
58
m_menu = new StatisticsPreferencesPopup(widgetDocker.preferences);
59
widgetDocker.preferences->setMenu(m_menu);
60
widgetDocker.preferences->setPopupMode(QToolButton::InstantPopup);
62
connect(m_timer, SIGNAL(timeout()), this, SLOT(updateData()));
63
connect(widgetDocker.preferences, SIGNAL(clicked()), widgetDocker.preferences, SLOT(showMenu()));
64
connect(m_menu, SIGNAL(wordsDisplayChange(int)), this, SLOT(wordsDisplayChanged(int)));
65
connect(m_menu, SIGNAL(sentencesDisplayChange(int)), this, SLOT(sentencesDisplayChanged(int)));
66
connect(m_menu, SIGNAL(linesDisplayChange(int)), this, SLOT(linesDisplayChanged(int)));
67
connect(m_menu, SIGNAL(syllablesDisplayChange(int)), this, SLOT(syllablesDisplayChanged(int)));
68
connect(m_menu, SIGNAL(charspaceDisplayChange(int)), this, SLOT(charspaceDisplayChanged(int)));
69
connect(m_menu, SIGNAL(charnospaceDisplayChange(int)), this, SLOT(charnospaceDisplayChanged(int)));
70
connect(m_menu, SIGNAL(eastDisplayChange(int)), this, SLOT(eastDisplayChanged(int)));
71
connect(m_menu, SIGNAL(fleschDisplayChange(int)), this, SLOT(fleschDisplayChanged(int)));
73
KConfigGroup cfgGroup = KGlobal::config()->group("Statistics");
74
bool visible = cfgGroup.readEntry("WordsVisible", true);
75
widgetDocker.Words->setVisible(visible);
76
widgetDocker.count_words->setVisible(visible);
78
visible = cfgGroup.readEntry("SentencesVisible", true);
79
widgetDocker.Sentences->setVisible(visible);
80
widgetDocker.count_sentences->setVisible(visible);
82
visible = cfgGroup.readEntry("SyllablesVisible", true);
83
widgetDocker.Syllables->setVisible(visible);
84
widgetDocker.count_syllables->setVisible(visible);
86
visible = cfgGroup.readEntry("LinesVisible", true);
87
widgetDocker.Lines->setVisible(visible);
88
widgetDocker.count_lines->setVisible(visible);
90
visible = cfgGroup.readEntry("EastAsianCharactersVisible", true);
91
widgetDocker.Cjkchars->setVisible(visible);
92
widgetDocker.count_cjkchars->setVisible(visible);
94
visible = cfgGroup.readEntry("FleschVisible", true);
95
widgetDocker.Flesch->setVisible(visible);
96
widgetDocker.count_flesch->setVisible(visible);
98
visible = cfgGroup.readEntry("CharspacesVisible", true);
99
widgetDocker.spaces->setVisible(visible);
100
widgetDocker.count_spaces->setVisible(visible);
102
visible = cfgGroup.readEntry("CharnospacesVisible", true);
103
widgetDocker.nospaces->setVisible(visible);
104
widgetDocker.count_nospaces->setVisible(visible);
107
KWStatistics::~KWStatistics()
111
void KWStatistics::updateData()
116
m_charsWithSpace = 0;
117
m_charsWithoutSpace = 0;
125
// parts of words for better counting of syllables:
126
// (only use reg exp if necessary -> speed up)
128
QStringList subs_syl;
129
subs_syl << "cial" << "tia" << "cius" << "cious" << "giu" << "ion" << "iou";
130
QStringList subs_syl_regexp;
131
subs_syl_regexp << "sia$" << "ely$";
134
add_syl << "ia" << "riet" << "dien" << "iu" << "io" << "ii";
135
QStringList add_syl_regexp;
136
add_syl_regexp << "[aeiouym]bl$" << "[aeiou]{3}" << "^mc" << "ism$"
137
<< "[^l]lien" << "^coa[dglx]." << "[^gq]ua[^auieo]" << "dnt$";
139
foreach (KWFrameSet *fs, m_document->frameSets()) {
140
KWTextFrameSet *tfs = dynamic_cast<KWTextFrameSet*>(fs);
141
if (tfs == 0) continue;
143
QTextDocument *doc = tfs->document();
144
QTextBlock block = doc->begin();
145
while (block.isValid()) {
147
m_charsWithSpace += block.text().length();
148
m_charsWithoutSpace += block.text().length() - block.text().count(QRegExp("\\s"));
150
m_lines += block.layout()->lineCount();
151
m_cjkChars += countCJKChars(block.text());
153
QString s = block.text();
155
// Syllable and Word count
156
// Algorithm mostly taken from Greg Fast's Lingua::EN::Syllable module for Perl.
157
// This guesses correct for 70-90% of English words, but the overall value
158
// is quite good, as some words get a number that's too high and others get
159
// one that's too low.
160
// IMPORTANT: please test any changes against demos/statistics.kwd
162
const QStringList wordlist = s.split(re, QString::SkipEmptyParts);
163
m_words += wordlist.count();
164
re.setCaseSensitivity(Qt::CaseInsensitive);
165
for (QStringList::ConstIterator it1 = wordlist.begin(); it1 != wordlist.end(); ++it1) {
166
QString word1 = *it1;
168
re.setPattern("[!?.,:_\"-]"); // clean word from punctuation
170
if (word.length() <= 3) { // extension to the original algorithm
176
re.setPattern("[^aeiouy]+");
177
QStringList syls = word.split(re, QString::SkipEmptyParts);
178
int word_syllables = 0;
179
for (QStringList::Iterator it = subs_syl.begin(); it != subs_syl.end(); ++it) {
180
if (word.indexOf(*it, 0, Qt::CaseInsensitive) != -1)
183
for (QStringList::Iterator it = subs_syl_regexp.begin(); it != subs_syl_regexp.end(); ++it) {
185
if (word.indexOf(re) != -1)
188
for (QStringList::Iterator it = add_syl.begin(); it != add_syl.end(); ++it) {
189
if (word.indexOf(*it, 0, Qt::CaseInsensitive) != -1)
192
for (QStringList::Iterator it = add_syl_regexp.begin(); it != add_syl_regexp.end(); ++it) {
194
if (word.indexOf(re) != -1)
197
word_syllables += syls.count();
198
if (word_syllables == 0)
200
m_syllables += word_syllables;
202
re.setCaseSensitivity(Qt::CaseSensitive);
204
block = block.next();
207
// Clean up for better result, destroys the original text but we only want to count
211
QChar lastchar = s.at(s.length() - 1);
212
if (! s.isEmpty() && lastchar != QChar('.') && lastchar != QChar('?') && lastchar != QChar('!')) { // e.g. for headlines
215
re.setPattern("[.?!]+"); // count "..." as only one "."
217
re.setPattern("\\d\\.\\d"); // don't count floating point numbers as sentences
218
s.replace(re, "0,0");
219
re.setPattern("[A-Z]\\.+"); // don't count "U.S.A." as three sentences
221
for (int i = 0 ; i < s.length(); ++i) {
223
if (ch == QChar('.') || ch == QChar('?') || ch == QChar('!'))
231
void KWStatistics::updateDataUi()
233
// calculate Flesch reading ease score:
234
float flesch_score = 0;
235
if (m_words > 0 && m_sentences > 0)
236
flesch_score = 206.835 - (1.015 * (m_words / m_sentences)) - (84.6 * m_syllables / m_words);
237
QString flesch = KGlobal::locale()->formatNumber(flesch_score);
239
newText[0] = KGlobal::locale()->formatNumber(m_words, 0);
240
widgetDocker.count_words->setText(newText[0]);
242
newText[1] = KGlobal::locale()->formatNumber(m_sentences, 0);
243
widgetDocker.count_sentences->setText(newText[1]);
245
newText[2] = KGlobal::locale()->formatNumber(m_syllables, 0);
246
widgetDocker.count_syllables->setText(newText[2]);
248
newText[3] = KGlobal::locale()->formatNumber(m_lines, 0);
249
widgetDocker.count_lines->setText(newText[3]);
251
newText[4] = KGlobal::locale()->formatNumber(m_charsWithSpace, 0);
252
widgetDocker.count_spaces->setText(newText[4]);
254
newText[5] = KGlobal::locale()->formatNumber(m_charsWithoutSpace, 0);
255
widgetDocker.count_nospaces->setText(newText[5]);
257
newText[6] = KGlobal::locale()->formatNumber(m_cjkChars, 0);
258
widgetDocker.count_cjkchars->setText(newText[6]);
261
widgetDocker.count_flesch->setText(newText[7]);
265
void KWStatistics::selectionChanged()
267
if (m_selection->count() != 1)
270
KoShape *shape = m_selection->firstSelectedShape();
272
KWFrame *frame = dynamic_cast<KWFrame*>(shape->applicationData());
273
if (!frame) return; // you can have embedded shapes selected, in that case it surely is no text frameset.
274
KWTextFrameSet *fs = dynamic_cast<KWTextFrameSet*>(frame->frameSet());
277
disconnect(m_textDocument, SIGNAL(contentsChanged()), m_timer, SLOT(start()));
278
m_textDocument = fs->document();
283
int KWStatistics::countCJKChars(const QString &text)
287
QString::const_iterator it;
288
for (it = text.constBegin(); it != text.constEnd(); ++it) {
291
* CJK punctuations: 0x3000 - 0x303F (but I believe we shouldn't include this in the statistics)
292
* Hiragana: 0x3040 - 0x309F
293
* Katakana: 0x30A0 - 0x30FF
294
* CJK Unified Ideographs: 4E00 - 9FFF (Chinese Traditional & Simplified, Kanji and Hanja
295
* Hangul: 0xAC00 - 0xD7AF
297
if ((qChar >= 0x3040 && qChar <= 0x309F)
298
|| (qChar >= 0x30A0 && qChar <= 0x30FF)
299
|| (qChar >= 0x4E00 && qChar <= 0x9FFF)
300
|| (qChar >= 0xAC00 && qChar <= 0xD7AF)) {
308
void KWStatistics::wordsDisplayChanged(int state)
309
{ KConfigGroup cfgGroup = KGlobal::config()->group("Statistics");
312
widgetDocker.Words->show();
313
widgetDocker.count_words->show();
314
cfgGroup.writeEntry("WordsVisible",true);
318
widgetDocker.Words->hide();
319
widgetDocker.count_words->hide();
320
cfgGroup.writeEntry("WordsVisible",false);
328
void KWStatistics::sentencesDisplayChanged(int state)
330
KConfigGroup cfgGroup = KGlobal::config()->group("Statistics");
333
widgetDocker.Sentences->show();
334
widgetDocker.count_sentences->show();
335
cfgGroup.writeEntry("SentencesVisible",true);
339
widgetDocker.Sentences->hide();
340
widgetDocker.count_sentences->hide();
341
cfgGroup.writeEntry("SentencesVisible",false);
349
void KWStatistics::linesDisplayChanged(int state)
351
KConfigGroup cfgGroup = KGlobal::config()->group("Statistics");
354
widgetDocker.Lines->show();
355
widgetDocker.count_lines->show();
356
cfgGroup.writeEntry("LinesVisible",true);
360
widgetDocker.Lines->hide();
361
widgetDocker.count_lines->hide();
362
cfgGroup.writeEntry("LinesVisible",false);
369
void KWStatistics::syllablesDisplayChanged(int state)
371
KConfigGroup cfgGroup = KGlobal::config()->group("Statistics");
374
widgetDocker.Syllables->show();
375
widgetDocker.count_syllables->show();
376
cfgGroup.writeEntry("SyllablesVisible",true);
380
widgetDocker.Syllables->hide();
381
widgetDocker.count_syllables->hide();
382
cfgGroup.writeEntry("SyllablesVisible",false);
389
void KWStatistics::charspaceDisplayChanged(int state)
391
KConfigGroup cfgGroup = KGlobal::config()->group("Statistics");
394
widgetDocker.spaces->show();
395
widgetDocker.count_spaces->show();
396
cfgGroup.writeEntry("CharspacesVisible",true);
400
widgetDocker.spaces->hide();
401
widgetDocker.count_spaces->hide();
402
cfgGroup.writeEntry("CharspacesVisible",false);
410
void KWStatistics::charnospaceDisplayChanged(int state)
412
KConfigGroup cfgGroup = KGlobal::config()->group("Statistics");
415
widgetDocker.nospaces->show();
416
widgetDocker.count_nospaces->show();
417
cfgGroup.writeEntry("CharnospacesVisible",true);
421
widgetDocker.nospaces->hide();
422
widgetDocker.count_nospaces->hide();
423
cfgGroup.writeEntry("CharnospacesVisible",false);
430
void KWStatistics::eastDisplayChanged(int state)
432
KConfigGroup cfgGroup = KGlobal::config()->group("Statistics");
435
widgetDocker.Cjkchars->show();
436
widgetDocker.count_cjkchars->show();
437
cfgGroup.writeEntry("EastAsianCharactersVisible",true);
441
widgetDocker.Cjkchars->hide();
442
widgetDocker.count_cjkchars->hide();
443
cfgGroup.writeEntry("EastAsianCharactersVisible",false);
450
void KWStatistics::fleschDisplayChanged(int state)
452
KConfigGroup cfgGroup = KGlobal::config()->group("Statistics");
455
widgetDocker.Flesch->show();
456
widgetDocker.count_flesch->show();
457
cfgGroup.writeEntry("FleschVisible",true);
461
widgetDocker.Flesch->hide();
462
widgetDocker.count_flesch->hide();
463
cfgGroup.writeEntry("FleschVisible",false);