File indexing completed on 2024-04-21 03:41:54

0001 // SPDX-FileCopyrightText: 2001-2009 Anne-Marie Mahfouf <annma@kde.org>
0002 // SPDX-FileCopyCopyright: 2014 Rahul Chowdhury <rahul.chowdhury@kdemail.net>
0003 // SPDX-License-Identifier: GPL-2.0-or-later
0004 
0005 #include "khangman.h"
0006 
0007 #include <QApplication>
0008 #include <QStandardPaths>
0009 #include <QQmlContext>
0010 #include <QQmlEngine>
0011 #include <QShortcut>
0012 #include <QDesktopServices>
0013 
0014 #include <KLocalizedString>
0015 #include <KMessageBox>
0016 #include <KRandom>
0017 #include <knewstuff_version.h>
0018 #include <KAuthorized>
0019 
0020 #include <KEduVocDocument>
0021 #include <keduvocexpression.h>
0022 #include <sharedkvtmlfiles.h>
0023 
0024 #include "langutils.h"
0025 #include "prefs.h"
0026 
0027 namespace
0028 {
0029 /**
0030  * Strip the accents off given string
0031  * @params original string to strip accents off of
0032  * @returns string without accents
0033  */
0034 QString stripAccents(const QString &original)
0035 {
0036     QString noAccents;
0037     QString decomposed = original.normalized(QString::NormalizationForm_D);
0038     for (int i = 0; i < decomposed.length(); ++i) {
0039         if ( decomposed[i].category() != QChar::Mark_NonSpacing ) {
0040             noAccents.append(decomposed[i]);
0041         }
0042     }
0043     return noAccents;
0044 }
0045 
0046 }
0047 
0048 KHangMan::KHangMan()
0049         : QObject (),
0050           m_currentCategory(0),
0051           m_currentLanguage(0),
0052           m_winCount(0),
0053           m_lossCount(0),
0054           m_randomInt(0),
0055           m_scoreMultiplyingFactor(1),
0056           m_netScore(0)
0057 {
0058     setObjectName(QStringLiteral("KHangMan"));
0059 
0060     scanLanguages();
0061 
0062     //find the themes
0063     m_themeFactory.addTheme(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("khangman/themes/standardthemes.xml")));
0064     
0065     QShortcut *quitShortcut = new QShortcut(QKeySequence::Quit, this);
0066     connect(quitShortcut, &QShortcut::activated, qApp, &QCoreApplication::quit);
0067 
0068     loadLevels();
0069     // kvtml files have been found
0070 
0071     if (m_themeFactory.getQty() == 0) { // themes not present
0072         Q_EMIT errorOccured(i18n("No theme files found."));
0073         exit(EXIT_FAILURE);
0074     }
0075 }
0076 
0077 void KHangMan::showHandbook() const
0078 {
0079     if (KAuthorized::authorizeAction(QStringLiteral("help_contents"))) {
0080         QDesktopServices::openUrl(QUrl(QStringLiteral("help:/")));
0081     }
0082 }
0083 
0084 KConfigGroup KHangMan::config(const QString &group)
0085 {
0086     return KConfigGroup(KSharedConfig::openConfig(qApp->applicationName() + QStringLiteral("rc")), group);
0087 }
0088 
0089 // ----------------------------------------------------------------
0090 //                               Slots
0091 void KHangMan::setCurrentCategory(int index)
0092 {
0093     if (m_titleLevels.keys().count() <= index) {
0094         return;
0095     }
0096     const auto key = m_titleLevels.keys()[index];
0097 
0098     Prefs::setCurrentLevel(index);
0099     Prefs::setLevelFile(m_titleLevels[key]);
0100     Prefs::self()->save();
0101     m_currentCategory = index;
0102     Q_EMIT currentCategoryChanged();
0103 }
0104 
0105 void KHangMan::setCurrentLanguage(int index)
0106 {
0107     if (index >= 0 && index < m_languages.size()) {
0108         Prefs::setSelectedLanguage(m_languages[index]);
0109         m_currentLanguage = index;
0110         Prefs::self()->save();
0111         loadLevels();
0112         Q_EMIT currentLanguageChanged();
0113     }
0114 }
0115 
0116 void KHangMan::setCurrentTheme(int index)
0117 {
0118     const KHMTheme *theme = m_themeFactory.getTheme(index);
0119     Prefs::setTheme(theme->name());
0120     Prefs::self()->save();
0121     Q_EMIT currentThemeChanged();
0122 }
0123 
0124 void KHangMan::readFile()
0125 {
0126     // Check if the data files are installed in the correct dir.
0127     QFile myFile;
0128     myFile.setFileName(Prefs::levelFile());
0129 
0130     if (!myFile.exists()) {
0131         Q_EMIT errorOccured(i18n("File $KDEDIR/share/apps/kvtml/%1/%2 not found.\n"
0132             "Please check your installation.",
0133             Prefs::selectedLanguage(),
0134             Prefs::levelFile()));
0135     }
0136 
0137     // Detects if file is a kvtml file so that it's a hint enable file
0138     slotSetWordsSequence();
0139 }
0140 
0141 void KHangMan::nextWord()
0142 {
0143     // If there are no words, there's nothing we can do, trying will crash
0144     if (m_randomList.isEmpty()) {
0145         Q_EMIT errorOccured(i18n("No word list loaded"));
0146         return;
0147     }
0148 
0149     // Pick words from m_randomList sequentially since it is already shuffled.
0150     int whichWord = m_randomInt++ % m_randomList.count();
0151     m_originalWord = m_randomList[whichWord].first;
0152     m_originalWord = m_originalWord.toUpper();
0153     m_hint = m_randomList[whichWord].second;
0154 
0155     Q_EMIT currentHintChanged();
0156 
0157     m_currentWord.clear();
0158 
0159     // Find dashes, spaces, middot and apostrophe
0160     const QString search = QStringLiteral("- ยท'");
0161     for (const QChar &c : m_originalWord) {
0162         if (search.contains(c)) {
0163             m_currentWord.append(c);
0164         } else {
0165             m_currentWord.append(QLatin1Char('_'));
0166         }
0167     }
0168 
0169     Q_EMIT currentWordChanged();
0170 }
0171 
0172 void KHangMan::slotSetWordsSequence()
0173 {
0174     //Current level file
0175     KEduVocDocument doc(this);
0176 
0177     ///@todo open returns KEduVocDocument::ErrorCode
0178     doc.open(QUrl::fromLocalFile(Prefs::levelFile()), KEduVocDocument::FileIgnoreLock);
0179 
0180     //how many words in the file
0181     QList<KEduVocExpression *> words = doc.lesson()->entries(KEduVocLesson::Recursive);
0182 
0183     //get the words+hints
0184     m_randomList.clear();
0185     for (KEduVocExpression* entry : words) {
0186         QString hint = entry->translation(0)->comment();
0187         if (hint.isEmpty() && doc.identifierCount() > 0) {
0188             // if there is no comment or it's empty, use the first translation if there is one
0189             hint = entry->translation(1)->text();
0190         }
0191         QString text = entry->translation(0)->text();
0192         if (!text.isEmpty()) {
0193             m_randomList.append(qMakePair(text, hint));
0194         }
0195     }
0196     //shuffle the list
0197     KRandom::shuffle(m_randomList);
0198 }
0199 
0200 void KHangMan::replaceLetters(const QString& charString)
0201 {
0202     QChar ch = charString.at(0);
0203     bool oneLetter = Prefs::oneLetter();
0204 
0205     for (int i = 0; i < m_originalWord.size(); ++i) {
0206         if (m_originalWord.at(i) == ch) {
0207             m_currentWord[i] = ch;
0208 
0209             if (oneLetter)
0210                 break;
0211         }
0212     }
0213     Q_EMIT currentWordChanged();
0214 }
0215 
0216 bool KHangMan::isResolved() const
0217 {
0218     return m_currentWord == m_originalWord;
0219 }
0220 
0221 void KHangMan::revealCurrentWord()
0222 {
0223     m_currentWord = m_originalWord;
0224     Q_EMIT currentWordChanged();
0225 }
0226 
0227 void KHangMan::calculateNetScore()
0228 {
0229     m_netScore = ( m_winCount - m_lossCount ) * m_scoreMultiplyingFactor;
0230     Q_EMIT netScoreChanged();
0231 }
0232 
0233 bool KHangMan::containsChar(const QString &sChar) const
0234 {
0235     return m_originalWord.contains(sChar) || stripAccents(m_originalWord).contains(sChar);
0236 }
0237 
0238 int KHangMan::resolveTime()
0239 {
0240     return Prefs::resolveTime();
0241 }
0242 
0243 void KHangMan::setResolveTime(int resolveTime)
0244 {
0245     Prefs::setResolveTime(resolveTime);
0246     Q_EMIT resolveTimeChanged();
0247 }
0248 
0249 bool KHangMan::soundEnabled() const
0250 {
0251     return Prefs::sound();
0252 }
0253 
0254 void KHangMan::setSoundEnabled(bool sound)
0255 {
0256     Prefs::setSound(sound);
0257     Q_EMIT soundEnabledChanged();
0258 }
0259 
0260 QStringList KHangMan::languages() const
0261 {
0262     return m_languageNames;
0263 }
0264 
0265 int KHangMan::winCount() const
0266 {
0267     return m_winCount;
0268 }
0269 
0270 void KHangMan::setWinCount(int count)
0271 {
0272     m_winCount = count;
0273     calculateNetScore();
0274     Q_EMIT winCountChanged();
0275 }
0276 
0277 int KHangMan::lossCount() const
0278 {
0279     return m_lossCount;
0280 }
0281 
0282 void KHangMan::setLossCount(int count)
0283 {
0284     m_lossCount = count;
0285     calculateNetScore();
0286     Q_EMIT lossCountChanged();
0287 }
0288 
0289 int KHangMan::scoreMultiplyingFactor() const
0290 {
0291     return m_scoreMultiplyingFactor;
0292 }
0293 
0294 void KHangMan::setScoreMultiplyingFactor( int factor )
0295 {
0296     m_scoreMultiplyingFactor = factor;
0297     calculateNetScore();
0298     Q_EMIT scoreMultiplyingFactorChanged();
0299 }
0300 
0301 int KHangMan::netScore() const
0302 {
0303     return m_netScore;
0304 }
0305 
0306 int KHangMan::currentLanguage() const
0307 {
0308     return m_currentLanguage;
0309 }
0310 
0311 QStringList KHangMan::themes() const
0312 {
0313     return m_themeFactory.themeList();
0314 }
0315 
0316 int KHangMan::currentTheme() const
0317 {
0318     QStringList themes = m_themeFactory.getNames();
0319     return themes.indexOf(Prefs::theme());
0320 }
0321 
0322 QUrl KHangMan::backgroundUrl() const
0323 {
0324     const KHMTheme *theme = m_themeFactory.getTheme(currentTheme());
0325     if (theme) {
0326         return QUrl::fromLocalFile(QStandardPaths::locate(QStandardPaths::AppLocalDataLocation, QStringLiteral("themes/") + theme->svgFileName()));
0327     }
0328     return {};
0329 }
0330 
0331 QColor KHangMan::currentThemeLetterColor() const
0332 {
0333     const KHMTheme *theme = m_themeFactory.getTheme(currentTheme());
0334     if (theme) {
0335         return theme->letterColor();
0336     }
0337 
0338     // Default to white letters
0339     return QColor("white");
0340 }
0341 
0342 QStringList KHangMan::categories() const
0343 {
0344     return m_titleLevels.keys();
0345 }
0346 
0347 int KHangMan::currentCategory() const
0348 {
0349     return m_currentCategory;
0350 }
0351 
0352 QStringList KHangMan::currentWord() const
0353 {
0354     QStringList currentWordLetters;
0355 
0356     for (const QChar& currentWordLetter : std::as_const(m_currentWord))
0357     {
0358         currentWordLetters.append(currentWordLetter);
0359     }
0360 
0361     return currentWordLetters;
0362 }
0363 
0364 QString KHangMan::getCurrentHint() const
0365 {
0366     return m_hint;
0367 }
0368 
0369 QStringList KHangMan::alphabet() const
0370 {
0371     QStringList letterList;
0372     for (char c = 'A'; c <= 'Z'; ++c) {
0373         letterList.append(QChar(c));
0374     }
0375 
0376     letterList.append(m_specialCharacters);
0377 
0378     return letterList;
0379 }
0380 
0381 // ----------------------------------------------------------------
0382 
0383 void KHangMan::scanLanguages()
0384 {
0385     m_languageNames.clear();
0386 
0387     //the program scans in khangman/data/ to see what languages data is found
0388     m_languages = SharedKvtmlFiles::languages();
0389     if (m_languages.isEmpty()) {
0390         qApp->closeAllWindows();
0391     }
0392     //find duplicated entries in KDEDIR and KDEHOME
0393 
0394     // Write the present languages in config so they cannot be downloaded.
0395     // FIXME: use pre-seeding here
0396     KConfigGroup cg( KSharedConfig::openConfig() ,"KNewStuff2");
0397     for (int i=0;  i<m_languages.count(); ++i) {
0398         //QString tmp = cg.readEntry(m_languages[i]);
0399        // if (!tmp)
0400         cg.writeEntry(m_languages[i], QDate::currentDate().toString(Qt::ISODate));
0401     }
0402     cg.config()->sync();
0403 
0404     for (int i = 0; i < m_languages.size(); ++i) {
0405         QLocale locale(m_languages[i]);
0406         QString languageName = locale.nativeLanguageName();
0407         if (languageName.isEmpty()) {
0408             languageName = i18nc("@item:inlistbox no language for that locale","None");
0409         }
0410         m_languageNames.append(languageName);
0411 
0412         if (m_languages[i] == Prefs::selectedLanguage())
0413             m_currentLanguage = i;
0414     }
0415 
0416     Q_EMIT languagesChanged();
0417 }
0418 
0419 void KHangMan::loadLevels()
0420 {
0421     //build the Level combobox menu dynamically depending of the data for each language
0422     m_titleLevels.clear();
0423     QStringList levelFilenames = SharedKvtmlFiles::fileNames(Prefs::selectedLanguage());
0424 
0425     if (levelFilenames.isEmpty()) {
0426         Prefs::setSelectedLanguage(QStringLiteral("en"));
0427         Prefs::self()->save();
0428         levelFilenames = SharedKvtmlFiles::fileNames(Prefs::selectedLanguage());
0429         if (levelFilenames.isEmpty()) {
0430             Q_EMIT errorOccured(i18n("No kvtml files found."));
0431             exit(EXIT_FAILURE);
0432         }
0433     }
0434 
0435     const QStringList titles = SharedKvtmlFiles::titles(Prefs::selectedLanguage());
0436     Q_ASSERT(levelFilenames.count() == titles.count());
0437     for(int i = 0; i < levelFilenames.count(); ++i) {
0438         m_titleLevels.insert(titles.at(i), levelFilenames.at(i));
0439     }
0440     Q_EMIT categoriesChanged();
0441 
0442     int level = Prefs::currentLevel();
0443     if (level >= m_titleLevels.count() || !levelFilenames.contains(Prefs::levelFile())) {
0444         level = 0;
0445     }
0446     setCurrentCategory(level);
0447 
0448     loadLanguageSpecialCharacters();
0449 }
0450 
0451 void KHangMan::slotDownloadNewStuff(KNSCore::Entry *entry)
0452 {
0453     SharedKvtmlFiles::sortDownloadedFiles();
0454     //look for languages dirs installed
0455     scanLanguages();
0456     //refresh Languages menu
0457     setCurrentLanguage(m_languages.indexOf(Prefs::selectedLanguage()));
0458 }
0459 
0460 void KHangMan::loadLanguageSpecialCharacters()
0461 {
0462     QString lang = Prefs::selectedLanguage();
0463     if (lang.isEmpty())
0464         return;
0465 
0466     m_specialCharacters.clear();
0467     if (!LangUtils::hasSpecialChars(lang)) {
0468         return;
0469     }
0470 
0471     QString langFileName=QStringLiteral("khangman/%1.txt").arg(lang);
0472     QFile langFile;
0473     langFile.setFileName(QStandardPaths::locate(QStandardPaths::GenericDataLocation, langFileName));
0474 
0475     // Let's look in local KDEHOME dir then KNS installs each .txt
0476     // in kvtml/<lang> as it installs everything at the same place
0477     if (!langFile.exists()) {
0478         langFileName = QStringLiteral("apps/kvtml/%1/%1.txt").arg(lang);
0479         langFile.setFileName(QStandardPaths::locate(QStandardPaths::GenericDataLocation, langFileName));
0480         if (!langFile.exists()) {
0481             return;
0482         }
0483     }
0484 
0485     // We open the file and store info into the stream...
0486     QFile openFileStream(langFile.fileName());
0487     openFileStream.open(QIODevice::ReadOnly);
0488     QTextStream readFileStr(&openFileStream);
0489     readFileStr.setEncoding(QStringConverter::Utf8);
0490 
0491     // m_specialCharacters contains all the words from the file
0492     // FIXME: Better name
0493     m_specialCharacters = readFileStr.readAll().split(QLatin1Char('\n'), Qt::SkipEmptyParts);
0494     openFileStream.close();
0495 }
0496 
0497 #include "moc_khangman.cpp"
0498 
0499 // kate: space-indent on; tab-width 4; indent-width 4; mixed-indent off; replace-tabs on;
0500 // vim: set et sw=4 ts=4 cino=l1,cs,U1: