File indexing completed on 2024-04-14 03:40:37

0001 /*
0002     This file is part of Kiten, a KDE Japanese Reference Tool
0003     SPDX-FileCopyrightText: 2011 Daniel E. Moctezuma <democtezuma@gmail.com>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "kanjibrowserview.h"
0009 
0010 #include "DictKanjidic/dictfilekanjidic.h"
0011 #include "DictKanjidic/entrykanjidic.h"
0012 #include "dictquery.h"
0013 #include "entrylist.h"
0014 #include "kanjibrowser.h"
0015 #include "kanjibrowserconfig.h"
0016 #include "searchdialog.h"
0017 
0018 #include <KActionCollection>
0019 #include <KLocalizedString>
0020 #include <KMessageBox>
0021 #include <QAction>
0022 #include <QClipboard>
0023 
0024 KanjiBrowserView::KanjiBrowserView(QWidget *parent)
0025     : QWidget(parent)
0026     , _currentKanji(nullptr)
0027 {
0028     setupUi(this);
0029     loadSettings();
0030 }
0031 
0032 void KanjiBrowserView::changeGrade(const int grade)
0033 {
0034     _currentGradeList.clear();
0035 
0036     // Indexes of items in the ComboBox:
0037     //   All Jouyou Kanji Grades: 0
0038     //   Grade 1: 1
0039     //   Grade 2: 2
0040     //   .
0041     //   .
0042     //   .
0043     //   Not in Jouyou list: ComboBox->count() - 1
0044 
0045     if (grade == AllJouyouGrades) {
0046         // Add the all the grades found in our list.
0047         for (const int grd : _gradeList) {
0048             _currentGradeList << grd;
0049         }
0050     }
0051     // Here the user selected "Not in Jouyou list".
0052     else if (grade == (_grades->count() - 1)) {
0053         // Only show the kanji with grade 0 (not in Jouyou).
0054         _currentGradeList << 0;
0055     }
0056     // It seems KANJIDIC doesn't have a G7 (grade 7) kanji.
0057     // If the user selects G8 or above, we need to add 1 to the grade
0058     // because the index (from the ComboBox) and the grade will be different.
0059     else if (grade >= Grade7) {
0060         _currentGradeList << (grade + 1);
0061     }
0062     // Show the kanji with the selected grade.
0063     else {
0064         _currentGradeList << grade;
0065     }
0066 
0067     // Reload our QListWidget widget.
0068     reloadKanjiList();
0069 }
0070 
0071 void KanjiBrowserView::changeStrokeCount(const int strokes)
0072 {
0073     _currentStrokesList.clear();
0074 
0075     // Indexes of items in the ComboBox:
0076     //   No stroke limit: 0
0077     //   1 stroke: 1
0078     //   2 strokes: 2
0079     //   .
0080     //   .
0081     //   .
0082 
0083     // We don't need to filter any kanji by stroke number.
0084     if (strokes == NoStrokeLimit) {
0085         // Add all the strokes found to our the list.
0086         for (const int stroke : _strokesList) {
0087             _currentStrokesList << stroke;
0088         }
0089     }
0090     // Show the kanji with the selected number of strokes.
0091     else {
0092         _currentStrokesList << strokes;
0093     }
0094 
0095     // Reload our QListWidget widget.
0096     reloadKanjiList();
0097 }
0098 
0099 void KanjiBrowserView::changeToInfoPage()
0100 {
0101     _stackedWidget->setCurrentIndex(Info);
0102 }
0103 
0104 void KanjiBrowserView::changeToListPage()
0105 {
0106     _stackedWidget->setCurrentIndex(List);
0107 }
0108 
0109 QString KanjiBrowserView::convertToCSS(const QFont &font)
0110 {
0111     QString weight;
0112     // TODO: these have numeric values in Qt we could plug into the CSS here
0113     switch (font.weight()) {
0114     case QFont::Thin:
0115     case QFont::ExtraLight:
0116     case QFont::Light:
0117         weight = QStringLiteral("lighter");
0118         break;
0119     case QFont::Medium:
0120     case QFont::Normal:
0121         weight = QStringLiteral("normal");
0122         break;
0123     case QFont::DemiBold:
0124     case QFont::ExtraBold:
0125     case QFont::Black:
0126     case QFont::Bold:
0127         weight = QStringLiteral("bold");
0128         break;
0129     }
0130 
0131     QString style;
0132     switch (font.style()) {
0133     case QFont::StyleNormal:
0134         style = QStringLiteral("normal");
0135         break;
0136     case QFont::StyleItalic:
0137         style = QStringLiteral("italic");
0138         break;
0139     case QFont::StyleOblique:
0140         style = QStringLiteral("oblique");
0141         break;
0142     }
0143 
0144     return QStringLiteral(
0145                "font-family:\"%1\";"
0146                "font-size:%2px;"
0147                "font-weight:%3;"
0148                "font-style:%4;")
0149         .arg(font.family())
0150         .arg(font.pointSizeF())
0151         .arg(weight)
0152         .arg(style);
0153 }
0154 
0155 void KanjiBrowserView::loadSettings()
0156 {
0157     _kanjiList->setFont(KanjiBrowserConfigSkeleton::self()->kanjiListFont());
0158     _kanjiSize = KanjiBrowserConfigSkeleton::self()->kanjiSize();
0159     _kanaFont = KanjiBrowserConfigSkeleton::self()->kanaFont();
0160     _labelFont = KanjiBrowserConfigSkeleton::self()->labelFont();
0161 
0162     // Reload the Kanji Information page with the new font changes.
0163     if (_currentKanji != nullptr) {
0164         showKanjiInformation(_currentKanji);
0165     }
0166 }
0167 
0168 void KanjiBrowserView::reloadKanjiList()
0169 {
0170     // Grade and strokes lists have the information of
0171     // which kanji we are going to filter.
0172     // We just iterate on them to actually do the filtering.
0173     QStringList list;
0174     for (const int strokes : _currentStrokesList) {
0175         for (const int grade : _currentGradeList) {
0176             list.append(_kanji.keys(qMakePair(grade, strokes)));
0177         }
0178     }
0179 
0180     _kanjiList->clear();
0181     _kanjiList->addItems(list);
0182 
0183     // Update our status bar with the number of kanji filtered.
0184     statusBarChanged(i18np("%1 kanji found", "%1 kanji found", _kanjiList->count()));
0185 }
0186 
0187 void KanjiBrowserView::searchKanji(const QString &term)
0188 {
0189     if (_currentKanji != nullptr && term == _currentKanji->getWord()) {
0190         return;
0191     }
0192 
0193     if (auto result = _parent->_dictFileKanjidic->doSearch(DictQuery(term)); result != nullptr && !result->isEmpty()) {
0194         auto kanji = dynamic_cast<EntryKanjidic *>(result->first());
0195         _currentKanji = kanji;
0196 
0197         _goToKanjiInfo->setText(i18n("About %1", _currentKanji->getWord()));
0198         _copyToClipboard->setText(i18n("Copy %1 to clipboard", _currentKanji->getWord()));
0199         _copyToClipboard->setVisible(true);
0200 
0201         showKanjiInformation(kanji);
0202         _goToKanjiInfo->triggered();
0203     }
0204 }
0205 
0206 void KanjiBrowserView::setupView(KanjiBrowser *parent, const QHash<QString, QPair<int, int>> &kanji, QList<int> &kanjiGrades, QList<int> &strokeCount)
0207 {
0208     if (kanji.isEmpty() || kanjiGrades.isEmpty() || strokeCount.isEmpty()) {
0209         qDebug() << "One or more of our lists are empty (kanji, grades, strokes).";
0210         qDebug() << "Could not load the view properly.";
0211         KMessageBox::error(this, i18n("Could not load the necessary kanji information."));
0212         return;
0213     }
0214 
0215     _parent = parent;
0216     _kanji = kanji;
0217     _gradeList = kanjiGrades;
0218     _strokesList = strokeCount;
0219 
0220     QAction *goToKanjiList = _parent->actionCollection()->addAction(QStringLiteral("kanji_list"));
0221     goToKanjiList->setText(i18n("Kanji &List"));
0222 
0223     _goToKanjiInfo = _parent->actionCollection()->addAction(QStringLiteral("kanji_info"));
0224     _goToKanjiInfo->setText(i18n("Kanji &Information"));
0225 
0226     _searchKanjiAction = _parent->actionCollection()->addAction(QStringLiteral("search"));
0227     _searchKanjiAction->setIcon(QIcon::fromTheme(QStringLiteral("search")));
0228     _searchKanjiAction->setText(i18n("&Search…"));
0229 
0230     _copyToClipboard = _parent->actionCollection()->addAction(QStringLiteral("copy_kanji_to_clipboard"));
0231     _copyToClipboard->setVisible(false);
0232 
0233     _grades->addItem(i18n("All Jouyou Kanji grades"));
0234     for (const int &grade : kanjiGrades) {
0235         // Grades 9 and above are considered Jinmeiyou.
0236         if (grade >= Jinmeiyou) {
0237             _grades->addItem(i18n("Grade %1 (Jinmeiyou)", grade));
0238         } else {
0239             _grades->addItem(i18n("Grade %1", grade));
0240         }
0241     }
0242     _grades->addItem(i18n("Not in Jouyou list"));
0243 
0244     _strokes->addItem(i18n("No stroke limit"));
0245     for (const int &stroke : strokeCount) {
0246         _strokes->addItem(i18np("%1 stroke", "%1 strokes", stroke));
0247     }
0248 
0249     connect(_grades, static_cast<void (KComboBox::*)(int)>(&KComboBox::currentIndexChanged), this, &KanjiBrowserView::changeGrade);
0250     connect(_strokes, static_cast<void (KComboBox::*)(int)>(&KComboBox::currentIndexChanged), this, &KanjiBrowserView::changeStrokeCount);
0251     connect(_kanjiList, &QListWidget::itemClicked, this, [this](QListWidgetItem *item) {
0252         searchKanji(item->text());
0253     });
0254     connect(_kanjiList, &QListWidget::itemClicked, _goToKanjiInfo, [this] {
0255         _goToKanjiInfo->triggered();
0256     });
0257     connect(goToKanjiList, &QAction::triggered, this, &KanjiBrowserView::changeToListPage);
0258     connect(_goToKanjiInfo, &QAction::triggered, this, &KanjiBrowserView::changeToInfoPage);
0259     connect(_copyToClipboard, &QAction::triggered, this, &KanjiBrowserView::toClipboard);
0260     connect(_searchKanjiAction, &QAction::triggered, this, &KanjiBrowserView::openSearchDialog);
0261 
0262     // Set the current grade (Grade 1).
0263     _grades->setCurrentIndex(1);
0264     // Set the current number of strokes (No stroke limit).
0265     // NOTE: we change from '1 stroke' to 'No stroke limit'
0266     // to let the ComboBox notice the change and do the filter.
0267     _strokes->setCurrentIndex(1);
0268     _strokes->setCurrentIndex(NoStrokeLimit);
0269 
0270     qDebug() << "Initial setup succeeded!";
0271 }
0272 
0273 void KanjiBrowserView::showKanjiInformation(const EntryKanjidic *kanji)
0274 {
0275     // This font is shipped with Kiten and should not be changed as it shows
0276     // the stroke order of a kanji.
0277     QFont kanjiFont(QStringLiteral("KanjiStrokeOrders"));
0278     kanjiFont.setPointSizeF(_kanjiSize.toReal());
0279 
0280     QString text;
0281     text.append(QStringLiteral("<html><body><style>"));
0282     text.append(QStringLiteral(".kanji { %1 }").arg(convertToCSS(kanjiFont)));
0283     text.append(QStringLiteral(".label { %1 }").arg(convertToCSS(_labelFont)));
0284     text.append(QStringLiteral(".kana  { %1 }").arg(convertToCSS(_kanaFont)));
0285     text.append(QStringLiteral("</style>"));
0286 
0287     // Put the kanji.
0288     text.append(QStringLiteral("<table><tr><td><p class=\"kanji\">%1</p></td>").arg(kanji->getWord()));
0289 
0290     // Now the kanji grades and number of strokes.
0291     text.append(QStringLiteral("<td>"));
0292     if (!kanji->getKanjiGrade().isEmpty()) {
0293         text.append(QStringLiteral("<p class=\"label\">%1 %2</p></br>").arg(i18n("Grade:")).arg(kanji->getKanjiGrade()));
0294     }
0295     text.append(QStringLiteral("<p class=\"label\">%1 %2</p></td></tr></table>").arg(i18n("Strokes:")).arg(kanji->getStrokesCount()));
0296 
0297     // Onyomi readings.
0298     if (!kanji->getOnyomiReadingsList().isEmpty()) {
0299         text.append(QStringLiteral("<p class=\"label\">%1"
0300                                    "<span class=\"kana\">%2</span></p></br>")
0301                         .arg(i18n("Onyomi: "))
0302                         .arg(kanji->getOnyomiReadings()));
0303     }
0304 
0305     // Kunyomi readings.
0306     if (!kanji->getKunyomiReadingsList().isEmpty()) {
0307         text.append(QStringLiteral("<p class=\"label\">%1"
0308                                    "<span class=\"kana\">%2</span></p></br>")
0309                         .arg(i18n("Kunyomi: "))
0310                         .arg(kanji->getKunyomiReadings()));
0311     }
0312 
0313     // Special readings used in names.
0314     if (!kanji->getInNamesReadingsList().isEmpty()) {
0315         text.append(QStringLiteral("<p class=\"label\">%1"
0316                                    "<span class=\"kana\">%2</span></p></br>")
0317                         .arg(i18n("In names: "))
0318                         .arg(kanji->getInNamesReadings()));
0319     }
0320 
0321     // Reading used as radical.
0322     if (!kanji->getAsRadicalReadingsList().isEmpty()) {
0323         text.append(QStringLiteral("<p class=\"label\">%1"
0324                                    "<span class=\"kana\">%2</span></p></br>")
0325                         .arg(i18n("As radical: "))
0326                         .arg(kanji->getAsRadicalReadings()));
0327     }
0328 
0329     // Meanings
0330     text.append(QStringLiteral("<p class=\"label\">"));
0331     if (kanji->getMeaningsList().count() == 1) {
0332         text.append(i18n("Meaning: "));
0333     } else {
0334         text.append(i18n("Meanings: "));
0335     }
0336     text.append(QStringLiteral("<span class=\"kana\">%1</span></p>").arg(kanji->getMeanings()));
0337 
0338     // Close remaining tags and set the HTML text.
0339     text.append(QStringLiteral("</body></html>"));
0340     _kanjiInformation->setHtml(text);
0341 }
0342 
0343 void KanjiBrowserView::toClipboard()
0344 {
0345     QClipboard *cb = QApplication::clipboard();
0346     cb->setText(_currentKanji->getWord(), QClipboard::Clipboard);
0347     cb->setText(_currentKanji->getWord(), QClipboard::Selection);
0348 }
0349 
0350 void KanjiBrowserView::openSearchDialog()
0351 {
0352     auto dialog = new SearchDialog(this);
0353     connect(dialog, &SearchDialog::search, this, [this, dialog](const QString &query) {
0354         searchKanji(query);
0355         dialog->close();
0356     });
0357     dialog->show();
0358 }
0359 
0360 #include "moc_kanjibrowserview.cpp"