File indexing completed on 2023-05-30 10:39:36

0001 /***************************************************************************
0002  *   Copyright (C) 2002 by Gunnar Schmi Dt <kmouth@schmi-dt.de             *
0003  *             (C) 2015 by Jeremy Whiting <jpwhiting@kde.org>              *
0004  *                                                                         *
0005  *   This program is free software; you can redistribute it and/or modify  *
0006  *   it under the terms of the GNU General Public License as published by  *
0007  *   the Free Software Foundation; either version 2 of the License, or     *
0008  *   (at your option) any later version.                                   *
0009  *                                                                         *
0010  *   This program is distributed in the hope that it will be useful,       *
0011  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
0012  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
0013  *   GNU General Public License for more details.                          *
0014  *                                                                         *
0015  *   You should have received a copy of the GNU General Public License     *
0016  *   along with this program; if not, write to the                         *
0017  *   Free Software Foundation, Inc.,                                       *
0018  *   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.          *
0019  ***************************************************************************/
0020 
0021 // application specific includes
0022 #include "phraselist.h"
0023 
0024 // include files for Qt
0025 #include <QApplication>
0026 #include <QClipboard>
0027 #include <QFileDialog>
0028 #include <QHBoxLayout>
0029 #include <QKeyEvent>
0030 #include <QListView>
0031 #include <QPushButton>
0032 #include <QStandardItem>
0033 #include <QVBoxLayout>
0034 
0035 // include files for KDE
0036 #include <KComboBox>
0037 #include <KConfigGroup>
0038 #include <QIcon>
0039 #include <KLineEdit>
0040 #include <KLocalizedString>
0041 #include <KMessageBox>
0042 #include <KXMLGUIFactory>
0043 
0044 #include "kmouth.h"
0045 #include "texttospeechsystem.h"
0046 #include "phrasebook/phrasebook.h"
0047 #include "wordcompletion/wordcompletion.h"
0048 
0049 PhraseList::PhraseList(QWidget *parent, const QString &name) : QWidget(parent)
0050 {
0051     Q_UNUSED(name);
0052     isInSlot = false;
0053     // FIXME: Remove or change PaletteBase to Qt::OpaqueMode?
0054     // setBackgroundMode(PaletteBase);
0055     QVBoxLayout *layout = new QVBoxLayout(this);
0056 
0057     m_listView = new QListView(this);
0058     m_model = new QStandardItemModel(this);
0059     m_listView->setModel(m_model);
0060     m_listView->setFocusPolicy(Qt::NoFocus);
0061     m_listView->setSelectionMode(QAbstractItemView::ExtendedSelection);
0062     m_listView->setWhatsThis(i18n("This list contains the history of spoken sentences. You can select sentences and press the speak button for re-speaking."));
0063     layout->addWidget(m_listView);
0064 
0065     QHBoxLayout *rowLayout = new QHBoxLayout();
0066     layout->addLayout(rowLayout);
0067 
0068     completion = new WordCompletion();
0069 
0070     dictionaryCombo = new KComboBox(this);
0071     configureCompletionCombo(completion->wordLists());
0072     rowLayout->addWidget(dictionaryCombo);
0073 
0074     lineEdit = new KLineEdit(this);
0075     lineEdit->setFocusPolicy(Qt::StrongFocus);
0076     lineEdit->setFrame(true);
0077     lineEdit->setEchoMode(QLineEdit::Normal);
0078     lineEdit->setCompletionObject(completion);
0079     lineEdit->setAutoDeleteCompletionObject(true);
0080     lineEdit->setWhatsThis(i18n("Into this edit field you can type a phrase. Click on the speak button in order to speak the entered phrase."));
0081     rowLayout->addWidget(lineEdit);
0082     lineEdit->setFocus();
0083 
0084     QIcon icon = QIcon::fromTheme(QStringLiteral("text-speak"));
0085     speakButton = new QPushButton(icon, i18n("&Speak"), this);
0086     speakButton->setFocusPolicy(Qt::NoFocus);
0087     speakButton->setAutoDefault(false);
0088     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."));
0089     rowLayout->addWidget(speakButton);
0090 
0091     connect(dictionaryCombo, QOverload<const QString &>::of(&KComboBox::textActivated), completion, &WordCompletion::setWordList);
0092     connect(completion, &WordCompletion::wordListsChanged, this, &PhraseList::configureCompletionCombo);
0093 
0094     connect(m_listView->selectionModel(),  &QItemSelectionModel::selectionChanged, this, &PhraseList::selectionChanged);
0095     connect(m_listView,  &QWidget::customContextMenuRequested, this, &PhraseList::contextMenuRequested);
0096     connect(lineEdit, &KLineEdit::returnKeyPressed, this, &PhraseList::lineEntered);
0097     connect(lineEdit, &QLineEdit::textChanged, this, &PhraseList::textChanged);
0098     connect(speakButton, &QAbstractButton::clicked, this, &PhraseList::speak);
0099 }
0100 
0101 PhraseList::~PhraseList()
0102 {
0103     delete speakButton;
0104     delete m_model;
0105     delete lineEdit;
0106 }
0107 
0108 void PhraseList::print(QPrinter *pPrinter)
0109 {
0110     PhraseBook book;
0111     QStandardItem *rootItem = m_model->invisibleRootItem();
0112     int count = rootItem->rowCount();
0113     for (int i = 0; i < count; ++i) {
0114         QStandardItem *item = rootItem->child(i);
0115         book += PhraseBookEntry(Phrase(item->text()));
0116     }
0117 
0118     book.print(pPrinter);
0119 }
0120 
0121 QStringList PhraseList::getListSelection()
0122 {
0123     QStringList res = QStringList();
0124 
0125     QStandardItem *rootItem = m_model->invisibleRootItem();
0126     int count = rootItem->rowCount();
0127     QItemSelectionModel *selection = m_listView->selectionModel();
0128     for (int i = 0; i < count; ++i) {
0129         QStandardItem *item = rootItem->child(i);
0130         if (selection->isSelected(m_model->indexFromItem(item)))
0131             res += item->text();
0132     }
0133 
0134     return res;
0135 }
0136 
0137 bool PhraseList::existListSelection()
0138 {
0139     return m_listView->selectionModel()->hasSelection();
0140 }
0141 
0142 bool PhraseList::existEditSelection()
0143 {
0144     return lineEdit->hasSelectedText();
0145 }
0146 
0147 void PhraseList::enableMenuEntries()
0148 {
0149     bool deselected = false;
0150     bool selected = existListSelection();
0151     QStandardItem *rootItem = m_model->invisibleRootItem();
0152     int count = rootItem->rowCount();
0153     QItemSelectionModel *selection = m_listView->selectionModel();
0154     for (int i = 0; i < count; ++i) {
0155         QStandardItem *item = rootItem->child(i);
0156         if (!selection->isSelected(m_model->indexFromItem(item))) {
0157             deselected = true;
0158             break;
0159         }
0160     }
0161     KMouthApp *theApp = qobject_cast<KMouthApp*>(parentWidget());
0162     theApp->enableMenuEntries(selected, deselected);
0163 }
0164 
0165 void PhraseList::configureCompletion()
0166 {
0167     completion->configure();
0168 }
0169 
0170 void PhraseList::configureCompletionCombo(const QStringList &list)
0171 {
0172     QString current = completion->currentWordList();
0173     dictionaryCombo->clear();
0174     if (list.isEmpty())
0175         dictionaryCombo->hide();
0176     else if (list.count() == 1) {
0177         dictionaryCombo->addItems(list);
0178         dictionaryCombo->setCurrentIndex(0);
0179         dictionaryCombo->hide();
0180     } else {
0181         dictionaryCombo->addItems(list);
0182         dictionaryCombo->show();
0183 
0184         QStringList::ConstIterator it;
0185         int i = 0;
0186         for (it = list.begin(), i = 0; it != list.end(); ++it, ++i) {
0187             if (current == *it) {
0188                 dictionaryCombo->setCurrentIndex(i);
0189                 return;
0190             }
0191         }
0192     }
0193 }
0194 
0195 void PhraseList::saveCompletionOptions()
0196 {
0197     KConfigGroup cg(KSharedConfig::openConfig(), "General Options");
0198     cg.writeEntry("Show speak button", speakButton->isVisible() || !lineEdit->isVisible());
0199 
0200     KConfigGroup cg2(KSharedConfig::openConfig(), "Completion");
0201     cg2.writeEntry("Mode", static_cast<int>(lineEdit->completionMode()));
0202     cg2.writeEntry("List", completion->currentWordList());
0203 }
0204 
0205 void PhraseList::readCompletionOptions()
0206 {
0207     KConfigGroup cg(KSharedConfig::openConfig(), "General Options");
0208     if (!cg.readEntry("Show speak button", true))
0209         speakButton->hide();
0210 
0211     if (KSharedConfig::openConfig()->hasGroup("Completion")) {
0212         KConfigGroup cg2(KSharedConfig::openConfig(), "Completion");
0213         //int mode = cg2.readEntry("Mode", int(KGlobalSettings::completionMode()));
0214         //lineEdit->setCompletionMode(static_cast<KGlobalSettings::Completion>(mode));
0215 
0216         QString current = cg2.readEntry("List", QString());
0217         const QStringList list = completion->wordLists();
0218         QStringList::ConstIterator it;
0219         int i = 0;
0220         for (it = list.constBegin(), i = 0; it != list.constEnd(); ++it, ++i) {
0221             if (current == *it) {
0222                 dictionaryCombo->setCurrentIndex(i);
0223                 return;
0224             }
0225         }
0226     }
0227 }
0228 
0229 void PhraseList::saveWordCompletion()
0230 {
0231     completion->save();
0232 }
0233 
0234 
0235 void PhraseList::selectAllEntries()
0236 {
0237     m_listView->selectAll();
0238 }
0239 
0240 void PhraseList::deselectAllEntries()
0241 {
0242     m_listView->clearSelection();
0243 }
0244 
0245 void PhraseList::speak()
0246 {
0247     QString phrase = lineEdit->text();
0248     if (phrase.isNull() || phrase.isEmpty())
0249         speakListSelection();
0250     else {
0251         insertIntoPhraseList(phrase, true);
0252         speakPhrase(phrase);
0253     }
0254 }
0255 
0256 void PhraseList::cut()
0257 {
0258     if (lineEdit->hasSelectedText())
0259         lineEdit->cut();
0260     else
0261         cutListSelection();
0262 }
0263 
0264 void PhraseList::copy()
0265 {
0266     if (lineEdit->hasSelectedText())
0267         lineEdit->copy();
0268     else
0269         copyListSelection();
0270 }
0271 
0272 void PhraseList::paste()
0273 {
0274     lineEdit->paste();
0275 }
0276 
0277 void PhraseList::insert(const QString &s)
0278 {
0279     setEditLineText(s);
0280 }
0281 
0282 void PhraseList::speakListSelection()
0283 {
0284     speakPhrase(getListSelection().join(QLatin1String("\n")));
0285 }
0286 
0287 void PhraseList::removeListSelection()
0288 {
0289     if (m_listView->selectionModel()->hasSelection()) {
0290         QList<QModelIndex> selected = m_listView->selectionModel()->selectedRows();
0291         std::sort(selected.begin(), selected.end());
0292         // Iterate over the rows backwards so we don't modify the .row of any indexes in selected.
0293         for (int i = selected.size() - 1; i >= 0; --i) {
0294             QModelIndex index = selected.at(i);
0295             m_model->removeRows(index.row(), 1);
0296         }
0297     }
0298     enableMenuEntries();
0299 }
0300 
0301 void PhraseList::cutListSelection()
0302 {
0303     copyListSelection();
0304     removeListSelection();
0305 }
0306 
0307 void PhraseList::copyListSelection()
0308 {
0309     QApplication::clipboard()->setText(getListSelection().join(QLatin1String("\n")));
0310 }
0311 
0312 void PhraseList::lineEntered(const QString &phrase)
0313 {
0314     if (phrase.isNull() || phrase.isEmpty())
0315         speakListSelection();
0316     else {
0317         insertIntoPhraseList(phrase, true);
0318         speakPhrase(phrase);
0319     }
0320 }
0321 
0322 void PhraseList::speakPhrase(const QString &phrase)
0323 {
0324     QApplication::setOverrideCursor(Qt::WaitCursor);
0325     KMouthApp *theApp = qobject_cast<KMouthApp*>(parentWidget());
0326     QString language = completion->languageOfWordList(completion->currentWordList());
0327     theApp->getTTSSystem()->speak(phrase, language);
0328     QApplication::restoreOverrideCursor();
0329 }
0330 
0331 void PhraseList::insertIntoPhraseList(const QString &phrase, bool clearEditLine)
0332 {
0333     int lastLine = m_model->rowCount() - 1;
0334     if ((lastLine == -1) || (phrase != m_model->data(m_model->index(lastLine, 0)).toString())) {
0335         QStandardItem *item = new QStandardItem(phrase);
0336         m_model->appendRow(item);
0337         if (clearEditLine)
0338             completion->addSentence(phrase);
0339     }
0340 
0341     if (clearEditLine) {
0342         lineEdit->selectAll();
0343         line.clear();
0344     }
0345     enableMenuEntries();
0346 }
0347 
0348 void PhraseList::contextMenuRequested(const QPoint &pos)
0349 {
0350     QString name;
0351     if (existListSelection())
0352         name = QStringLiteral("phraselist_selection_popup");
0353     else
0354         name = QStringLiteral("phraselist_popup");
0355 
0356     KMouthApp *theApp = qobject_cast<KMouthApp*>(parentWidget());
0357     KXMLGUIFactory *factory = theApp->factory();
0358     QMenu *popup = (QMenu *)factory->container(name, theApp);
0359     if (popup != nullptr) {
0360         popup->exec(pos, nullptr);
0361     }
0362 }
0363 
0364 void PhraseList::textChanged(const QString &s)
0365 {
0366     if (!isInSlot) {
0367         isInSlot = true;
0368         line = s;
0369         m_listView->setCurrentIndex(m_model->index(m_model->rowCount() - 1, 0));
0370         m_listView->clearSelection();
0371         isInSlot = false;
0372     }
0373 }
0374 
0375 void PhraseList::selectionChanged()
0376 {
0377     if (!isInSlot) {
0378         isInSlot = true;
0379 
0380         QStringList sel = getListSelection();
0381 
0382         if (sel.empty())
0383             setEditLineText(line);
0384         else if (sel.count() == 1)
0385             setEditLineText(sel.first());
0386         else {
0387             setEditLineText(QLatin1String(""));
0388         }
0389         isInSlot = false;
0390     }
0391     enableMenuEntries();
0392 }
0393 
0394 void PhraseList::setEditLineText(const QString &s)
0395 {
0396     lineEdit->end(false);
0397     while (!(lineEdit->text().isNull() || lineEdit->text().isEmpty()))
0398         lineEdit->backspace();
0399     lineEdit->insert(s);
0400 }
0401 
0402 void PhraseList::keyPressEvent(QKeyEvent *e)
0403 {
0404     if (e->key() == Qt::Key_Up) {
0405         bool selected = m_listView->selectionModel()->hasSelection();
0406 
0407         if (!selected) {
0408             m_listView->setCurrentIndex(m_model->index(m_model->rowCount() - 1, 0));
0409             //listBox->ensureCurrentVisible ();
0410         } else {
0411             int curr = m_listView->currentIndex().row();
0412 
0413             if (curr == -1) {
0414                 isInSlot = true;
0415                 m_listView->clearSelection();
0416                 isInSlot = false;
0417                 curr = m_model->rowCount() - 1;
0418                 m_listView->setCurrentIndex(m_model->index(curr, 0));
0419                 //listBox->ensureCurrentVisible ();
0420             } else if (curr != 0) {
0421                 isInSlot = true;
0422                 m_listView->clearSelection();
0423                 isInSlot = false;
0424                 m_listView->setCurrentIndex(m_model->index(curr - 1, 0));
0425                 //listBox->ensureCurrentVisible ();
0426             }
0427         }
0428 
0429         e->accept();
0430     } else if (e->key() == Qt::Key_Down) {
0431         bool selected = m_listView->selectionModel()->hasSelection();
0432 
0433         if (selected) {
0434             int curr = m_listView->currentIndex().row();
0435 
0436             if (curr == (int)m_model->rowCount() - 1) {
0437                 m_listView->clearSelection();
0438             } else if (curr != -1) {
0439                 isInSlot = true;
0440                 m_listView->clearSelection();
0441                 isInSlot = false;
0442                 m_listView->setCurrentIndex(m_model->index(curr + 1, 0));
0443                 //listBox->ensureCurrentVisible ();
0444             }
0445         }
0446         e->accept();
0447     } else if (e->modifiers() & Qt::ControlModifier) {
0448         if (e->key() == Qt::Key_C) {
0449             copy();
0450             e->accept();
0451         } else if (e->key() == Qt::Key_X) {
0452             cut();
0453             e->accept();
0454         }
0455     } else
0456         e->ignore();
0457 }
0458 
0459 void PhraseList::save()
0460 {
0461     // We want to save a history of spoken sentences here. However, as
0462     // the class PhraseBook does already provide a method for saving
0463     // phrase books in both the phrase book format and plain text file
0464     // format we use that method here.
0465 
0466     PhraseBook book;
0467     QStandardItem *rootItem = m_model->invisibleRootItem();
0468     int count = m_model->rowCount();
0469     for (int i = 0; i < count; ++i) {
0470         QStandardItem *item = rootItem->child(i);
0471         book += PhraseBookEntry(Phrase(item->text()));
0472     }
0473 
0474     QUrl url;
0475     if (book.save(this, i18n("Save As"), url, false) == -1)
0476         KMessageBox::error(this, i18n("There was an error saving file\n%1", url.url()));
0477 }
0478 
0479 void PhraseList::open()
0480 {
0481     QUrl url = QFileDialog::getOpenFileUrl(this, i18n("Open File as History"), QUrl(),
0482                                            i18n("All Files (*);;Phrase Books (*.phrasebook);;Plain Text Files (*.txt)"));
0483 
0484     if (!url.isEmpty())
0485         open(url);
0486 }
0487 
0488 void PhraseList::open(const QUrl &url)
0489 {
0490     // We want to open a history of spoken sentences here. However, as
0491     // the class PhraseBook does already provide a method for opening
0492     // both phrase books and plain text files we use that method here.
0493 
0494     PhraseBook book;
0495     if (book.open(url)) {
0496         // convert PhraseBookEntryList -> QStringList
0497         QStringList list = book.toStringList();
0498         m_model->clear();
0499         QStringList::iterator it;
0500         for (it = list.begin(); it != list.end(); ++it)
0501             insertIntoPhraseList(*it, false);
0502     } else
0503         KMessageBox::error(this, i18n("There was an error loading file\n%1", url.toDisplayString()));
0504 }
0505