1 /***************************************************************************
2 * Copyright (C) 2002 by Gunnar Schmi Dt <kmouth@schmi-dt.de *
3 * (C) 2015 by Jeremy Whiting <jpwhiting@kde.org> *
4 * *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) any later version. *
9 * *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
19 ***************************************************************************/
20
21 // application specific includes
22 #include "phraselist.h"
23
24 // include files for Qt
25 #include <QApplication>
26 #include <QClipboard>
27 #include <QFileDialog>
28 #include <QHBoxLayout>
29 #include <QKeyEvent>
30 #include <QListView>
31 #include <QPushButton>
32 #include <QStandardItem>
33 #include <QVBoxLayout>
34
35 // include files for KDE
36 #include <KComboBox>
37 #include <KConfigGroup>
38 #include <QIcon>
39 #include <KLineEdit>
40 #include <KLocalizedString>
41 #include <KMessageBox>
42 #include <KXMLGUIFactory>
43
44 #include "kmouth.h"
45 #include "texttospeechsystem.h"
46 #include "phrasebook/phrasebook.h"
47 #include "wordcompletion/wordcompletion.h"
48
PhraseList(QWidget * parent,const QString & name)49 PhraseList::PhraseList(QWidget *parent, const QString &name) : QWidget(parent)
50 {
51 Q_UNUSED(name);
52 isInSlot = false;
53 // FIXME: Remove or change PaletteBase to Qt::OpaqueMode?
54 // setBackgroundMode(PaletteBase);
55 QVBoxLayout *layout = new QVBoxLayout(this);
56
57 m_listView = new QListView(this);
58 m_model = new QStandardItemModel(this);
59 m_listView->setModel(m_model);
60 m_listView->setFocusPolicy(Qt::NoFocus);
61 m_listView->setSelectionMode(QAbstractItemView::ExtendedSelection);
62 m_listView->setWhatsThis(i18n("This list contains the history of spoken sentences. You can select sentences and press the speak button for re-speaking."));
63 layout->addWidget(m_listView);
64
65 QHBoxLayout *rowLayout = new QHBoxLayout();
66 layout->addLayout(rowLayout);
67
68 completion = new WordCompletion();
69
70 dictionaryCombo = new KComboBox(this);
71 configureCompletionCombo(completion->wordLists());
72 rowLayout->addWidget(dictionaryCombo);
73
74 lineEdit = new KLineEdit(this);
75 lineEdit->setFocusPolicy(Qt::StrongFocus);
76 lineEdit->setFrame(true);
77 lineEdit->setEchoMode(QLineEdit::Normal);
78 lineEdit->setCompletionObject(completion);
79 lineEdit->setAutoDeleteCompletionObject(true);
80 lineEdit->setWhatsThis(i18n("Into this edit field you can type a phrase. Click on the speak button in order to speak the entered phrase."));
81 rowLayout->addWidget(lineEdit);
82 lineEdit->setFocus();
83
84 QIcon icon = QIcon::fromTheme(QStringLiteral("text-speak"));
85 speakButton = new QPushButton(icon, i18n("&Speak"), this);
86 speakButton->setFocusPolicy(Qt::NoFocus);
87 speakButton->setAutoDefault(false);
88 speakButton->setWhatsThis(i18n("Speaks the currently active sentence(s). If there is some text in the edit field it is spoken. Otherwise the selected sentences in the history (if any) are spoken."));
89 rowLayout->addWidget(speakButton);
90
91 connect(dictionaryCombo, QOverload<const QString &>::of(&KComboBox::textActivated), completion, &WordCompletion::setWordList);
92 connect(completion, &WordCompletion::wordListsChanged, this, &PhraseList::configureCompletionCombo);
93
94 connect(m_listView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &PhraseList::selectionChanged);
95 connect(m_listView, &QWidget::customContextMenuRequested, this, &PhraseList::contextMenuRequested);
96 connect(lineEdit, &KLineEdit::returnKeyPressed, this, &PhraseList::lineEntered);
97 connect(lineEdit, &QLineEdit::textChanged, this, &PhraseList::textChanged);
98 connect(speakButton, &QAbstractButton::clicked, this, &PhraseList::speak);
99 }
100
~PhraseList()101 PhraseList::~PhraseList()
102 {
103 delete speakButton;
104 delete m_model;
105 delete lineEdit;
106 }
107
print(QPrinter * pPrinter)108 void PhraseList::print(QPrinter *pPrinter)
109 {
110 PhraseBook book;
111 QStandardItem *rootItem = m_model->invisibleRootItem();
112 int count = rootItem->rowCount();
113 for (int i = 0; i < count; ++i) {
114 QStandardItem *item = rootItem->child(i);
115 book += PhraseBookEntry(Phrase(item->text()));
116 }
117
118 book.print(pPrinter);
119 }
120
getListSelection()121 QStringList PhraseList::getListSelection()
122 {
123 QStringList res = QStringList();
124
125 QStandardItem *rootItem = m_model->invisibleRootItem();
126 int count = rootItem->rowCount();
127 QItemSelectionModel *selection = m_listView->selectionModel();
128 for (int i = 0; i < count; ++i) {
129 QStandardItem *item = rootItem->child(i);
130 if (selection->isSelected(m_model->indexFromItem(item)))
131 res += item->text();
132 }
133
134 return res;
135 }
136
existListSelection()137 bool PhraseList::existListSelection()
138 {
139 return m_listView->selectionModel()->hasSelection();
140 }
141
existEditSelection()142 bool PhraseList::existEditSelection()
143 {
144 return lineEdit->hasSelectedText();
145 }
146
enableMenuEntries()147 void PhraseList::enableMenuEntries()
148 {
149 bool deselected = false;
150 bool selected = existListSelection();
151 QStandardItem *rootItem = m_model->invisibleRootItem();
152 int count = rootItem->rowCount();
153 QItemSelectionModel *selection = m_listView->selectionModel();
154 for (int i = 0; i < count; ++i) {
155 QStandardItem *item = rootItem->child(i);
156 if (!selection->isSelected(m_model->indexFromItem(item))) {
157 deselected = true;
158 break;
159 }
160 }
161 KMouthApp *theApp = (KMouthApp *) parentWidget();
162 theApp->enableMenuEntries(selected, deselected);
163 }
164
configureCompletion()165 void PhraseList::configureCompletion()
166 {
167 completion->configure();
168 }
169
configureCompletionCombo(const QStringList & list)170 void PhraseList::configureCompletionCombo(const QStringList &list)
171 {
172 QString current = completion->currentWordList();
173 dictionaryCombo->clear();
174 if (list.isEmpty())
175 dictionaryCombo->hide();
176 else if (list.count() == 1) {
177 dictionaryCombo->addItems(list);
178 dictionaryCombo->setCurrentIndex(0);
179 dictionaryCombo->hide();
180 } else {
181 dictionaryCombo->addItems(list);
182 dictionaryCombo->show();
183
184 QStringList::ConstIterator it;
185 int i = 0;
186 for (it = list.begin(), i = 0; it != list.end(); ++it, ++i) {
187 if (current == *it) {
188 dictionaryCombo->setCurrentIndex(i);
189 return;
190 }
191 }
192 }
193 }
194
saveCompletionOptions()195 void PhraseList::saveCompletionOptions()
196 {
197 KConfigGroup cg(KSharedConfig::openConfig(), "General Options");
198 cg.writeEntry("Show speak button", speakButton->isVisible() || !lineEdit->isVisible());
199
200 KConfigGroup cg2(KSharedConfig::openConfig(), "Completion");
201 cg2.writeEntry("Mode", static_cast<int>(lineEdit->completionMode()));
202 cg2.writeEntry("List", completion->currentWordList());
203 }
204
readCompletionOptions()205 void PhraseList::readCompletionOptions()
206 {
207 KConfigGroup cg(KSharedConfig::openConfig(), "General Options");
208 if (!cg.readEntry("Show speak button", true))
209 speakButton->hide();
210
211 if (KSharedConfig::openConfig()->hasGroup("Completion")) {
212 KConfigGroup cg2(KSharedConfig::openConfig(), "Completion");
213 //int mode = cg2.readEntry("Mode", int(KGlobalSettings::completionMode()));
214 //lineEdit->setCompletionMode(static_cast<KGlobalSettings::Completion>(mode));
215
216 QString current = cg2.readEntry("List", QString());
217 const QStringList list = completion->wordLists();
218 QStringList::ConstIterator it;
219 int i = 0;
220 for (it = list.constBegin(), i = 0; it != list.constEnd(); ++it, ++i) {
221 if (current == *it) {
222 dictionaryCombo->setCurrentIndex(i);
223 return;
224 }
225 }
226 }
227 }
228
saveWordCompletion()229 void PhraseList::saveWordCompletion()
230 {
231 completion->save();
232 }
233
234
selectAllEntries()235 void PhraseList::selectAllEntries()
236 {
237 m_listView->selectAll();
238 }
239
deselectAllEntries()240 void PhraseList::deselectAllEntries()
241 {
242 m_listView->clearSelection();
243 }
244
speak()245 void PhraseList::speak()
246 {
247 QString phrase = lineEdit->text();
248 if (phrase.isNull() || phrase.isEmpty())
249 speakListSelection();
250 else {
251 insertIntoPhraseList(phrase, true);
252 speakPhrase(phrase);
253 }
254 }
255
cut()256 void PhraseList::cut()
257 {
258 if (lineEdit->hasSelectedText())
259 lineEdit->cut();
260 else
261 cutListSelection();
262 }
263
copy()264 void PhraseList::copy()
265 {
266 if (lineEdit->hasSelectedText())
267 lineEdit->copy();
268 else
269 copyListSelection();
270 }
271
paste()272 void PhraseList::paste()
273 {
274 lineEdit->paste();
275 }
276
insert(const QString & s)277 void PhraseList::insert(const QString &s)
278 {
279 setEditLineText(s);
280 }
281
speakListSelection()282 void PhraseList::speakListSelection()
283 {
284 speakPhrase(getListSelection().join(QLatin1String("\n")));
285 }
286
removeListSelection()287 void PhraseList::removeListSelection()
288 {
289 if (m_listView->selectionModel()->hasSelection()) {
290 QList<QModelIndex> selected = m_listView->selectionModel()->selectedRows();
291 std::sort(selected.begin(), selected.end());
292 // Iterate over the rows backwards so we don't modify the .row of any indexes in selected.
293 for (int i = selected.size() - 1; i >= 0; --i) {
294 QModelIndex index = selected.at(i);
295 m_model->removeRows(index.row(), 1);
296 }
297 }
298 enableMenuEntries();
299 }
300
cutListSelection()301 void PhraseList::cutListSelection()
302 {
303 copyListSelection();
304 removeListSelection();
305 }
306
copyListSelection()307 void PhraseList::copyListSelection()
308 {
309 QApplication::clipboard()->setText(getListSelection().join(QLatin1String("\n")));
310 }
311
lineEntered(const QString & phrase)312 void PhraseList::lineEntered(const QString &phrase)
313 {
314 if (phrase.isNull() || phrase.isEmpty())
315 speakListSelection();
316 else {
317 insertIntoPhraseList(phrase, true);
318 speakPhrase(phrase);
319 }
320 }
321
speakPhrase(const QString & phrase)322 void PhraseList::speakPhrase(const QString &phrase)
323 {
324 QApplication::setOverrideCursor(Qt::WaitCursor);
325 KMouthApp *theApp = (KMouthApp *) parentWidget();
326 QString language = completion->languageOfWordList(completion->currentWordList());
327 theApp->getTTSSystem()->speak(phrase, language);
328 QApplication::restoreOverrideCursor();
329 }
330
insertIntoPhraseList(const QString & phrase,bool clearEditLine)331 void PhraseList::insertIntoPhraseList(const QString &phrase, bool clearEditLine)
332 {
333 int lastLine = m_model->rowCount() - 1;
334 if ((lastLine == -1) || (phrase != m_model->data(m_model->index(lastLine, 0)).toString())) {
335 QStandardItem *item = new QStandardItem(phrase);
336 m_model->appendRow(item);
337 if (clearEditLine)
338 completion->addSentence(phrase);
339 }
340
341 if (clearEditLine) {
342 lineEdit->selectAll();
343 line.clear();
344 }
345 enableMenuEntries();
346 }
347
contextMenuRequested(const QPoint & pos)348 void PhraseList::contextMenuRequested(const QPoint &pos)
349 {
350 QString name;
351 if (existListSelection())
352 name = QStringLiteral("phraselist_selection_popup");
353 else
354 name = QStringLiteral("phraselist_popup");
355
356 KMouthApp *theApp = (KMouthApp *) parentWidget();
357 KXMLGUIFactory *factory = theApp->factory();
358 QMenu *popup = (QMenu *)factory->container(name, theApp);
359 if (popup != nullptr) {
360 popup->exec(pos, nullptr);
361 }
362 }
363
textChanged(const QString & s)364 void PhraseList::textChanged(const QString &s)
365 {
366 if (!isInSlot) {
367 isInSlot = true;
368 line = s;
369 m_listView->setCurrentIndex(m_model->index(m_model->rowCount() - 1, 0));
370 m_listView->clearSelection();
371 isInSlot = false;
372 }
373 }
374
selectionChanged()375 void PhraseList::selectionChanged()
376 {
377 if (!isInSlot) {
378 isInSlot = true;
379
380 QStringList sel = getListSelection();
381
382 if (sel.empty())
383 setEditLineText(line);
384 else if (sel.count() == 1)
385 setEditLineText(sel.first());
386 else {
387 setEditLineText(QLatin1String(""));
388 }
389 isInSlot = false;
390 }
391 enableMenuEntries();
392 }
393
setEditLineText(const QString & s)394 void PhraseList::setEditLineText(const QString &s)
395 {
396 lineEdit->end(false);
397 while (!(lineEdit->text().isNull() || lineEdit->text().isEmpty()))
398 lineEdit->backspace();
399 lineEdit->insert(s);
400 }
401
keyPressEvent(QKeyEvent * e)402 void PhraseList::keyPressEvent(QKeyEvent *e)
403 {
404 if (e->key() == Qt::Key_Up) {
405 bool selected = m_listView->selectionModel()->hasSelection();
406
407 if (!selected) {
408 m_listView->setCurrentIndex(m_model->index(m_model->rowCount() - 1, 0));
409 //listBox->ensureCurrentVisible ();
410 } else {
411 int curr = m_listView->currentIndex().row();
412
413 if (curr == -1) {
414 isInSlot = true;
415 m_listView->clearSelection();
416 isInSlot = false;
417 curr = m_model->rowCount() - 1;
418 m_listView->setCurrentIndex(m_model->index(curr, 0));
419 //listBox->ensureCurrentVisible ();
420 } else if (curr != 0) {
421 isInSlot = true;
422 m_listView->clearSelection();
423 isInSlot = false;
424 m_listView->setCurrentIndex(m_model->index(curr - 1, 0));
425 //listBox->ensureCurrentVisible ();
426 }
427 }
428
429 e->accept();
430 } else if (e->key() == Qt::Key_Down) {
431 bool selected = m_listView->selectionModel()->hasSelection();
432
433 if (selected) {
434 int curr = m_listView->currentIndex().row();
435
436 if (curr == (int)m_model->rowCount() - 1) {
437 m_listView->clearSelection();
438 } else if (curr != -1) {
439 isInSlot = true;
440 m_listView->clearSelection();
441 isInSlot = false;
442 m_listView->setCurrentIndex(m_model->index(curr + 1, 0));
443 //listBox->ensureCurrentVisible ();
444 }
445 }
446 e->accept();
447 } else if (e->modifiers() & Qt::ControlModifier) {
448 if (e->key() == Qt::Key_C) {
449 copy();
450 e->accept();
451 } else if (e->key() == Qt::Key_X) {
452 cut();
453 e->accept();
454 }
455 } else
456 e->ignore();
457 }
458
save()459 void PhraseList::save()
460 {
461 // We want to save a history of spoken sentences here. However, as
462 // the class PhraseBook does already provide a method for saving
463 // phrase books in both the phrase book format and plain text file
464 // format we use that method here.
465
466 PhraseBook book;
467 QStandardItem *rootItem = m_model->invisibleRootItem();
468 int count = m_model->rowCount();
469 for (int i = 0; i < count; ++i) {
470 QStandardItem *item = rootItem->child(i);
471 book += PhraseBookEntry(Phrase(item->text()));
472 }
473
474 QUrl url;
475 if (book.save(this, i18n("Save As"), url, false) == -1)
476 KMessageBox::sorry(this, i18n("There was an error saving file\n%1", url.url()));
477 }
478
open()479 void PhraseList::open()
480 {
481 QUrl url = QFileDialog::getOpenFileUrl(this, i18n("Open File as History"), QUrl(),
482 i18n("All Files (*);;Phrase Books (*.phrasebook);;Plain Text Files (*.txt)"));
483
484 if (!url.isEmpty())
485 open(url);
486 }
487
open(const QUrl & url)488 void PhraseList::open(const QUrl &url)
489 {
490 // We want to open a history of spoken sentences here. However, as
491 // the class PhraseBook does already provide a method for opening
492 // both phrase books and plain text files we use that method here.
493
494 PhraseBook book;
495 if (book.open(url)) {
496 // convert PhraseBookEntryList -> QStringList
497 QStringList list = book.toStringList();
498 m_model->clear();
499 QStringList::iterator it;
500 for (it = list.begin(); it != list.end(); ++it)
501 insertIntoPhraseList(*it, false);
502 } else
503 KMessageBox::sorry(this, i18n("There was an error loading file\n%1", url.toDisplayString()));
504 }
505
506