File indexing completed on 2024-05-26 07:29:10

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