File indexing completed on 2024-05-05 03:40:58

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 #include "phrasebook.h"
0022 #include "phrasebookreader.h"
0023 
0024 #include <QBuffer>
0025 #include <QFile>
0026 #include <QFileDialog>
0027 #include <QFontDatabase>
0028 #include <QPainter>
0029 #include <QStack>
0030 #include <QUrl>
0031 
0032 #include <KActionMenu>
0033 #include <KDesktopFile>
0034 #include <KIO/StoredTransferJob>
0035 #include <KLocalizedString>
0036 #include <KMessageBox>
0037 
0038 #include <QtDebug>
0039 #include <kwidgetsaddons_version.h>
0040 
0041 Phrase::Phrase()
0042 {
0043     this->phrase.clear();
0044     this->shortcut.clear();
0045 }
0046 
0047 Phrase::Phrase(const QString &phrase)
0048 {
0049     this->phrase = phrase;
0050     this->shortcut.clear();
0051 }
0052 
0053 Phrase::Phrase(const QString &phrase, const QString &shortcut)
0054 {
0055     this->phrase = phrase;
0056     this->shortcut = shortcut;
0057 }
0058 
0059 QString Phrase::getPhrase() const
0060 {
0061     return phrase;
0062 }
0063 
0064 QString Phrase::getShortcut() const
0065 {
0066     return shortcut;
0067 }
0068 
0069 void Phrase::setPhrase(const QString &phrase)
0070 {
0071     this->phrase = phrase;
0072 }
0073 
0074 void Phrase::setShortcut(const QString &shortcut)
0075 {
0076     this->shortcut = shortcut;
0077 }
0078 
0079 // ***************************************************************************
0080 
0081 PhraseBookEntry::PhraseBookEntry()
0082 {
0083     level = 1;
0084     isPhraseValue = false;
0085 }
0086 
0087 PhraseBookEntry::PhraseBookEntry(const Phrase &phrase, int level, bool isPhrase)
0088 {
0089     this->phrase = phrase;
0090     this->level = level;
0091     isPhraseValue = isPhrase;
0092 }
0093 
0094 bool PhraseBookEntry::isPhrase() const
0095 {
0096     return isPhraseValue;
0097 }
0098 
0099 Phrase PhraseBookEntry::getPhrase() const
0100 {
0101     return phrase;
0102 }
0103 
0104 int PhraseBookEntry::getLevel() const
0105 {
0106     return level;
0107 }
0108 
0109 // ***************************************************************************
0110 
0111 void PhraseBook::print(QPrinter *pPrinter)
0112 {
0113     QPainter printpainter;
0114     printpainter.begin(pPrinter);
0115 
0116     QRect size = printpainter.viewport();
0117     int x = size.x();
0118     int y = size.y();
0119     int w = size.width();
0120     printpainter.setFont(QFont(QFontDatabase::systemFont(QFontDatabase::GeneralFont).family(), 12));
0121     QFontMetrics metrics = printpainter.fontMetrics();
0122 
0123     PhraseBookEntryList::iterator it;
0124     for (it = begin(); it != end(); ++it) {
0125         QRect rect = metrics.boundingRect(x + 16 * (*it).getLevel(),
0126                                           y,
0127                                           w - 16 * (*it).getLevel(),
0128                                           0,
0129                                           Qt::AlignJustify | Qt::TextWordWrap,
0130                                           (*it).getPhrase().getPhrase());
0131 
0132         if (y + rect.height() > size.height()) {
0133             pPrinter->newPage();
0134             y = 0;
0135         }
0136         printpainter.drawText(x + 16 * (*it).getLevel(),
0137                               y,
0138                               w - 16 * (*it).getLevel(),
0139                               rect.height(),
0140                               Qt::AlignJustify | Qt::TextWordWrap,
0141                               (*it).getPhrase().getPhrase());
0142         y += rect.height();
0143     }
0144 
0145     printpainter.end();
0146 }
0147 
0148 bool PhraseBook::decode(QIODevice *source)
0149 {
0150     PhraseBookReader reader;
0151 
0152     if (reader.read(source)) {
0153         PhraseBookEntryList::clear();
0154         *(PhraseBookEntryList *)this += reader.getPhraseList();
0155         return true;
0156     } else {
0157         qDebug() << "Unable to read xml file: " << reader.errorString();
0158         return false;
0159     }
0160 }
0161 
0162 QByteArray encodeString(const QString &str)
0163 {
0164     QByteArray res = "";
0165     for (int i = 0; i < (int)str.length(); i++) {
0166         QChar ch = str.at(i);
0167         ushort uc = ch.unicode();
0168         QByteArray number;
0169         number.setNum(uc);
0170         if ((uc > 127) || (uc < 32) || (ch == QLatin1Char('<')) || (ch == QLatin1Char('>')) || (ch == QLatin1Char('&')) || (ch == QLatin1Char(';')))
0171             res = res + "&#" + number + ';';
0172         else
0173             res = res + (char)uc;
0174     }
0175     return res;
0176 }
0177 
0178 QString PhraseBook::encode()
0179 {
0180     QString result;
0181     result = QStringLiteral("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
0182     result += QLatin1String("<!DOCTYPE phrasebook>\n");
0183     result += QLatin1String("<phrasebook>\n");
0184 
0185     PhraseBookEntryList::iterator it;
0186     int level = 0;
0187     for (it = begin(); it != end(); ++it) {
0188         int newLevel = (*it).getLevel();
0189         while (level < newLevel) {
0190             result += QLatin1String("<phrasebook>\n");
0191             level++;
0192         }
0193         while (level > newLevel) {
0194             result += QLatin1String("</phrasebook>\n");
0195             level--;
0196         }
0197 
0198         if ((*it).isPhrase()) {
0199             Phrase phrase = (*it).getPhrase();
0200             result += QStringLiteral("<phrase shortcut=\"") + QLatin1String(encodeString(phrase.getShortcut()));
0201             result += QStringLiteral("\">") + QLatin1String(encodeString(phrase.getPhrase())) + QStringLiteral("</phrase>\n");
0202         } else {
0203             Phrase phrase = (*it).getPhrase();
0204             result += QStringLiteral("<phrasebook name=\"") + QLatin1String(encodeString(phrase.getPhrase())) + QStringLiteral("\">\n");
0205             level++;
0206         }
0207     }
0208     while (level > 0) {
0209         result += QLatin1String("</phrasebook>\n");
0210         level--;
0211     }
0212     result += QLatin1String("</phrasebook>");
0213     return result;
0214 }
0215 
0216 QStringList PhraseBook::toStringList()
0217 {
0218     QStringList result;
0219 
0220     PhraseBook::iterator it;
0221     for (it = begin(); it != end(); ++it) {
0222         if ((*it).isPhrase())
0223             result += (*it).getPhrase().getPhrase();
0224     }
0225     return result;
0226 }
0227 
0228 bool PhraseBook::save(const QUrl &url)
0229 {
0230     return save(url, url.fileName().endsWith(QLatin1String(".phrasebook")));
0231 }
0232 
0233 void PhraseBook::save(QTextStream &stream, bool asPhrasebook)
0234 {
0235     if (asPhrasebook)
0236         stream << encode();
0237     else
0238         stream << toStringList().join(QLatin1String("\n"));
0239 }
0240 
0241 bool PhraseBook::save(const QUrl &url, bool asPhrasebook)
0242 {
0243     if (url.isLocalFile()) {
0244         QFile file(url.path());
0245         if (!file.open(QIODevice::WriteOnly))
0246             return false;
0247 
0248         QTextStream stream(&file);
0249         save(stream, asPhrasebook);
0250         file.close();
0251 
0252         if (file.error() != QFile::NoError)
0253             return false;
0254         else
0255             return true;
0256     } else {
0257         QByteArray data;
0258         QTextStream ts(&data);
0259         save(ts, asPhrasebook);
0260         ts.flush();
0261 
0262         KIO::StoredTransferJob *uploadJob = KIO::storedPut(data, url, -1);
0263         return uploadJob->exec();
0264     }
0265 }
0266 
0267 int PhraseBook::save(QWidget *parent, const QString &title, QUrl &url, bool phrasebookFirst)
0268 {
0269     // KFileDialog::getSaveUrl(...) is not useful here as we need
0270     // to know the requested file type.
0271 
0272     QString filters;
0273     if (phrasebookFirst)
0274         filters = i18n("Phrase Books (*.phrasebook);;Plain Text Files (*.txt);;All Files (*)");
0275     else
0276         filters = i18n("Plain Text Files (*.txt);;Phrase Books (*.phrasebook);;All Files (*)");
0277 
0278     QFileDialog fdlg(parent, title, QString(), filters);
0279     fdlg.setAcceptMode(QFileDialog::AcceptSave);
0280 
0281     if (fdlg.exec() != QDialog::Accepted || fdlg.selectedUrls().size() < 1) {
0282         return 0;
0283     }
0284 
0285     url = fdlg.selectedUrls().at(0);
0286 
0287     if (url.isEmpty() || !url.isValid()) {
0288         return -1;
0289     }
0290 
0291     if (QFile::exists(url.toLocalFile())) {
0292         if (KMessageBox::warningContinueCancel(nullptr,
0293                                                QStringLiteral("<qt>%1</qt>")
0294                                                    .arg(i18n("The file %1 already exists. "
0295                                                              "Do you want to overwrite it?",
0296                                                              url.url())),
0297                                                i18n("File Exists"),
0298                                                KGuiItem(i18n("&Overwrite")))
0299             == KMessageBox::Cancel) {
0300             return 0;
0301         }
0302     }
0303 
0304     bool result;
0305     if (fdlg.selectedNameFilter() == QLatin1String("*.phrasebook")) {
0306         if (url.fileName(QUrl::PrettyDecoded).contains(QLatin1Char('.')) == 0) {
0307             url = url.adjusted(QUrl::RemoveFilename);
0308             url.setPath(url.path() + url.fileName(QUrl::PrettyDecoded) + QStringLiteral(".phrasebook"));
0309         } else if (url.fileName(QUrl::PrettyDecoded).right(11).contains(QLatin1String(".phrasebook"), Qt::CaseInsensitive) == 0) {
0310             int filetype =
0311                 KMessageBox::questionTwoActionsCancel(nullptr,
0312                                                       QStringLiteral("<qt>%1</qt>")
0313                                                           .arg(i18n("Your chosen filename <i>%1</i> has a different extension than <i>.phrasebook</i>. "
0314                                                                     "Do you wish to add <i>.phrasebook</i> to the filename?",
0315                                                                     url.fileName())),
0316                                                       i18n("File Extension"),
0317                                                       KGuiItem(i18n("Add")),
0318                                                       KGuiItem(i18n("Do Not Add")));
0319             if (filetype == KMessageBox::Cancel) {
0320                 return 0;
0321             }
0322             if (filetype == KMessageBox::ButtonCode::PrimaryAction) {
0323                 url = url.adjusted(QUrl::RemoveFilename);
0324                 url.setPath(url.path() + url.fileName(QUrl::PrettyDecoded) + QStringLiteral(".phrasebook"));
0325             }
0326         }
0327         result = save(url, true);
0328     } else if (fdlg.selectedNameFilter() == QLatin1String("*.txt")) {
0329         if (url.fileName(QUrl::PrettyDecoded).right(11).contains(QLatin1String(".phrasebook"), Qt::CaseInsensitive) == 0) {
0330             result = save(url, false);
0331         } else {
0332             int filetype = KMessageBox::questionTwoActionsCancel(nullptr,
0333                                                                  QStringLiteral("<qt>%1</qt>")
0334                                                                      .arg(i18n("Your chosen filename <i>%1</i> has the extension <i>.phrasebook</i>. "
0335                                                                                "Do you wish to save in phrasebook format?",
0336                                                                                url.fileName())),
0337                                                                  i18n("File Extension"),
0338                                                                  KGuiItem(i18n("As Phrasebook")),
0339                                                                  KGuiItem(i18n("As Plain Text")));
0340             if (filetype == KMessageBox::Cancel) {
0341                 return 0;
0342             }
0343             if (filetype == KMessageBox::ButtonCode::PrimaryAction) {
0344                 result = save(url, true);
0345             } else {
0346                 result = save(url, false);
0347             }
0348         }
0349     } else // file format "All files" requested, so decide by extension
0350         result = save(url);
0351 
0352     if (result)
0353         return 1;
0354     else
0355         return -1;
0356 }
0357 
0358 bool PhraseBook::open(const QUrl &url)
0359 {
0360     KIO::StoredTransferJob *downloadJob = KIO::storedGet(url);
0361     if (downloadJob->exec()) {
0362         // First: try to load it as a normal phrase book
0363         QBuffer fileBuffer;
0364         fileBuffer.setData(downloadJob->data());
0365 
0366         fileBuffer.open(QIODevice::ReadOnly);
0367 
0368         bool error = !decode(&fileBuffer);
0369 
0370         // Second: if the file does not contain a phrase book, load it as
0371         // a plain text file
0372         if (error) {
0373             // Load each line of the plain text file as a new phrase
0374 
0375             QTextStream stream(&fileBuffer);
0376 
0377             while (!stream.atEnd()) {
0378                 QString s = stream.readLine();
0379                 if (!(s.isNull() || s.isEmpty()))
0380                     *this += PhraseBookEntry(Phrase(s, QLatin1String("")), 0, true);
0381             }
0382             error = false;
0383         }
0384 
0385         return !error;
0386     }
0387     return false;
0388 }
0389 
0390 StandardBookList PhraseBook::standardPhraseBooks()
0391 {
0392     // Get all the standard phrasebook filenames in bookPaths.
0393     QStringList bookPaths;
0394     const QStringList dirs = QStandardPaths::locateAll(QStandardPaths::AppDataLocation, QStringLiteral("books"), QStandardPaths::LocateDirectory);
0395     for (const QString &dir : dirs) {
0396         const QStringList locales = QDir(dir).entryList(QDir::Dirs | QDir::NoDotAndDotDot);
0397         for (const QString &locale : locales) {
0398             const QStringList fileNames = QDir(dir + QLatin1Char('/') + locale).entryList(QStringList() << QStringLiteral("*.phrasebook"));
0399             for (const QString &file : fileNames) {
0400                 bookPaths.append(dir + QLatin1Char('/') + locale + QLatin1Char('/') + file);
0401             }
0402         }
0403     }
0404     QStringList bookNames;
0405     QMap<QString, StandardBook> bookMap;
0406     QStringList::iterator it;
0407     // Iterate over all books creating a phrasebook for each, creating a StandardBook of each.
0408     for (it = bookPaths.begin(); it != bookPaths.end(); ++it) {
0409         PhraseBook pbook;
0410         // Open the phrasebook.
0411         if (pbook.open(QUrl::fromLocalFile(*it))) {
0412             StandardBook book;
0413             book.name = (*pbook.begin()).getPhrase().getPhrase();
0414 
0415             book.path = displayPath(*it);
0416             book.filename = *it;
0417 
0418             bookNames += book.path + QLatin1Char('/') + book.name;
0419             bookMap[book.path + QLatin1Char('/') + book.name] = book;
0420         }
0421     }
0422 
0423     bookNames.sort();
0424 
0425     StandardBookList result;
0426     for (it = bookNames.begin(); it != bookNames.end(); ++it)
0427         result += bookMap[*it];
0428 
0429     return result;
0430 }
0431 
0432 QString PhraseBook::displayPath(const QString &filename)
0433 {
0434     QFileInfo file(filename);
0435     QString path = file.path();
0436     QString dispPath;
0437     int position = path.indexOf(QLatin1String("/kmouth/books/")) + QStringLiteral("/kmouth/books/").length();
0438 
0439     while (path.length() > position) {
0440         file.setFile(path);
0441 
0442         KDesktopFile *dirDesc = new KDesktopFile(QStandardPaths::GenericDataLocation, path + QStringLiteral("/.directory"));
0443         QString name = dirDesc->readName();
0444         delete dirDesc;
0445 
0446         if (name.isNull() || name.isEmpty())
0447             dispPath += QLatin1Char('/') + file.fileName();
0448         else
0449             dispPath += QLatin1Char('/') + name;
0450 
0451         path = file.path();
0452     }
0453     return dispPath;
0454 }
0455 
0456 void PhraseBook::addToGUI(QMenu *popup, KToolBar *toolbar, KActionCollection *phrases, QObject *receiver, const char *slot) const
0457 {
0458     if ((popup != nullptr) || (toolbar != nullptr)) {
0459         QStack<QWidget *> stack;
0460         QWidget *parent = popup;
0461         int level = 0;
0462 
0463         QList<PhraseBookEntry>::ConstIterator it;
0464         for (it = begin(); it != end(); ++it) {
0465             int newLevel = (*it).getLevel();
0466             while (newLevel > level) {
0467                 KActionMenu *menu = phrases->add<KActionMenu>(QStringLiteral("phrasebook"));
0468                 menu->setPopupMode(QToolButton::InstantPopup);
0469                 if (parent == popup)
0470                     toolbar->addAction(menu);
0471                 if (parent != nullptr) {
0472                     parent->addAction(menu);
0473                     stack.push(parent);
0474                 }
0475                 parent = menu->menu();
0476                 level++;
0477             }
0478             while (newLevel < level && (parent != popup)) {
0479                 parent = stack.pop();
0480                 level--;
0481             }
0482             if ((*it).isPhrase()) {
0483                 Phrase phrase = (*it).getPhrase();
0484                 QAction *action = new PhraseAction(phrase.getPhrase(), phrase.getShortcut(), receiver, slot, phrases);
0485                 if (parent == popup)
0486                     toolbar->addAction(action);
0487                 if (parent != nullptr)
0488                     parent->addAction(action);
0489             } else {
0490                 Phrase phrase = (*it).getPhrase();
0491                 KActionMenu *menu = phrases->add<KActionMenu>(QStringLiteral("phrasebook"));
0492                 menu->setText(phrase.getPhrase());
0493                 menu->setPopupMode(QToolButton::InstantPopup);
0494                 if (parent == popup)
0495                     toolbar->addAction(menu);
0496                 parent->addAction(menu);
0497                 stack.push(parent);
0498                 parent = menu->menu();
0499                 level++;
0500             }
0501         }
0502     }
0503 }
0504 
0505 void PhraseBook::insert(const QString &name, const PhraseBook &book)
0506 {
0507     *this += PhraseBookEntry(Phrase(name), 0, false);
0508 
0509     QList<PhraseBookEntry>::ConstIterator it;
0510     for (it = book.begin(); it != book.end(); ++it) {
0511         *this += PhraseBookEntry((*it).getPhrase(), (*it).getLevel() + 1, (*it).isPhrase());
0512     }
0513 }