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 }