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: