1 /*
2     This file is part of Kiten, a KDE Japanese Reference Tool
3     SPDX-FileCopyrightText: 2011 Daniel E. Moctezuma <democtezuma@gmail.com>
4 
5     SPDX-License-Identifier: LGPL-2.0-or-later
6 */
7 
8 #include "kanjibrowserview.h"
9 
10 #include "DictKanjidic/dictfilekanjidic.h"
11 #include "DictKanjidic/entrykanjidic.h"
12 #include "dictquery.h"
13 #include "entrylist.h"
14 #include "kanjibrowser.h"
15 #include "kanjibrowserconfig.h"
16 
17 #include <QAction>
18 #include <QClipboard>
19 #include <QDebug>
20 #include <KActionCollection>
21 #include <KLocalizedString>
22 #include <KMessageBox>
23 
KanjiBrowserView(QWidget * parent)24 KanjiBrowserView::KanjiBrowserView( QWidget *parent )
25 : QWidget( parent )
26 , _currentKanji( 0 )
27 {
28   setupUi( this );
29   loadSettings();
30 }
31 
~KanjiBrowserView()32 KanjiBrowserView::~KanjiBrowserView()
33 {
34 
35 }
36 
changeGrade(const int grade)37 void KanjiBrowserView::changeGrade( const int grade )
38 {
39   _currentGradeList.clear();
40 
41   // Indexes of items in the ComboBox:
42   //   All Jouyou Kanji Grades: 0
43   //   Grade 1: 1
44   //   Grade 2: 2
45   //   .
46   //   .
47   //   .
48   //   Not in Jouyou list: ComboBox->count() - 1
49 
50   if( grade == AllJouyouGrades )
51   {
52     // Add the all the grades found in our list.
53     foreach( const int grd, _gradeList )
54     {
55       _currentGradeList << grd;
56     }
57   }
58   // Here the user selected "Not in Jouyou list".
59   else if( grade == ( _grades->count() - 1 ) )
60   {
61     // Only show the kanji with grade 0 (not in Jouyou).
62     _currentGradeList << 0;
63   }
64   // It seems KANJIDIC doesn't have a G7 (grade 7) kanji.
65   // If the user selects G8 or above, we need to add 1 to the grade
66   // because the index (from the ComboBox) and the grade will be different.
67   else if( grade >= Grade7 )
68   {
69     _currentGradeList << ( grade + 1 );
70   }
71   // Show the kanji with the selected grade.
72   else
73   {
74     _currentGradeList << grade;
75   }
76 
77   // Reload our QListWidget widget.
78   reloadKanjiList();
79 }
80 
changeStrokeCount(const int strokes)81 void KanjiBrowserView::changeStrokeCount( const int strokes )
82 {
83   _currentStrokesList.clear();
84 
85   // Indexes of items in the ComboBox:
86   //   No stroke limit: 0
87   //   1 stroke: 1
88   //   2 strokes: 2
89   //   .
90   //   .
91   //   .
92 
93   // We don't need to filter any kanji by stroke number.
94   if( strokes == NoStrokeLimit )
95   {
96     // Add all the strokes found to our the list.
97     foreach( const int stroke, _strokesList )
98     {
99       _currentStrokesList << stroke;
100     }
101   }
102   // Show the kanji with the selected number of strokes.
103   else
104   {
105     _currentStrokesList << strokes;
106   }
107 
108   // Reload our QListWidget widget.
109   reloadKanjiList();
110 }
111 
changeToInfoPage()112 void KanjiBrowserView::changeToInfoPage()
113 {
114   _stackedWidget->setCurrentIndex( Info );
115 }
116 
changeToListPage()117 void KanjiBrowserView::changeToListPage()
118 {
119   _stackedWidget->setCurrentIndex( List );
120 }
121 
convertToCSS(const QFont & font)122 QString KanjiBrowserView::convertToCSS( const QFont &font )
123 {
124   QString weight;
125   switch( font.weight() )
126   {
127     case QFont::Light:
128       weight = QStringLiteral("lighter");
129       break;
130     case QFont::Normal:
131       weight = QStringLiteral("normal");
132       break;
133     case QFont::Bold:
134       weight = QStringLiteral("bold");
135       break;
136   }
137 
138   QString style;
139   switch( font.style() )
140   {
141     case QFont::StyleNormal:
142       style = QStringLiteral("normal");
143       break;
144     case QFont::StyleItalic:
145       style = QStringLiteral("italic");
146       break;
147     case QFont::StyleOblique:
148       style = QStringLiteral("oblique");
149       break;
150   }
151 
152   return QString( "font-family:\"%1\";"
153                   "font-size:%2px;"
154                   "font-weight:%3;"
155                   "font-style:%4;" ).arg( font.family() )
156                                     .arg( font.pointSizeF() )
157                                     .arg( weight )
158                                     .arg( style );
159 }
160 
loadSettings()161 void KanjiBrowserView::loadSettings()
162 {
163   _kanjiList->setFont( KanjiBrowserConfigSkeleton::self()->kanjiListFont() );
164   _kanjiSize = KanjiBrowserConfigSkeleton::self()->kanjiSize();
165   _kanaFont  = KanjiBrowserConfigSkeleton::self()->kanaFont();
166   _labelFont = KanjiBrowserConfigSkeleton::self()->labelFont();
167 
168   // Reload the Kanji Information page with the new font changes.
169   if( _currentKanji != nullptr )
170   {
171     showKanjiInformation( _currentKanji );
172   }
173 }
174 
reloadKanjiList()175 void KanjiBrowserView::reloadKanjiList()
176 {
177   // Grade and strokes lists have the information of
178   // which kanji we are going to filter.
179   // We just iterate on them to actually do the filtering.
180   QStringList list;
181   foreach( const int strokes, _currentStrokesList )
182   {
183     foreach( const int grade, _currentGradeList )
184     {
185       list.append( _kanji.keys( qMakePair( grade, strokes ) ) );
186     }
187   }
188 
189   _kanjiList->clear();
190   _kanjiList->addItems( list );
191 
192   // Update our status bar with the number of kanji filtered.
193   statusBarChanged( i18np( "%1 kanji found", "%1 kanji found", _kanjiList->count() ) );
194 }
195 
searchKanji(QListWidgetItem * item)196 void KanjiBrowserView::searchKanji( QListWidgetItem *item )
197 {
198   if(   _currentKanji != nullptr
199       && item->text() == _currentKanji->getWord() )
200   {
201     return;
202   }
203 
204   _goToKanjiInfo->setText( i18n( "About %1", item->text() ) );
205   _copyToClipboard->setText( i18n( "Copy %1 to clipboard", item->text() ) );
206   _copyToClipboard->setVisible( true );
207 
208   Entry *result = _parent->_dictFileKanjidic->doSearch( DictQuery( item->text() ) )->first();
209   EntryKanjidic *kanji = static_cast<EntryKanjidic*>( result );
210   _currentKanji = kanji;
211 
212   showKanjiInformation( kanji );
213 }
214 
setupView(KanjiBrowser * parent,const QHash<QString,QPair<int,int>> & kanji,QList<int> & kanjiGrades,QList<int> & strokeCount)215 void KanjiBrowserView::setupView(   KanjiBrowser *parent
216                                   , const QHash< QString, QPair<int, int> > &kanji
217                                   , QList<int> &kanjiGrades
218                                   , QList<int> &strokeCount )
219 {
220   if( kanji.isEmpty() || kanjiGrades.isEmpty() || strokeCount.isEmpty() )
221   {
222     qDebug() << "One or more of our lists are empty (kanji, grades, strokes).";
223     qDebug() << "Could not load the view properly.";
224     KMessageBox::error( this, i18n( "Could not load the necessary kanji information." ) );
225     return;
226   }
227 
228   _parent = parent;
229   _kanji = kanji;
230   _gradeList = kanjiGrades;
231   _strokesList = strokeCount;
232 
233   QAction *goToKanjiList = _parent->actionCollection()->addAction( QStringLiteral("kanji_list") );
234   goToKanjiList->setText( i18n( "Kanji &List" ) );
235 
236   _goToKanjiInfo = _parent->actionCollection()->addAction( QStringLiteral("kanji_info") );
237   _goToKanjiInfo->setText( i18n( "Kanji &Information" ) );
238 
239   _copyToClipboard = _parent->actionCollection()->addAction( QStringLiteral("copy_kanji_to_clipboard") );
240   _copyToClipboard->setVisible(false);
241 
242   _grades->addItem( i18n( "All Jouyou Kanji grades" ) );
243   foreach( const int &grade, kanjiGrades )
244   {
245     // Grades 9 and above are considered Jinmeiyou.
246     if( grade >= Jinmeiyou )
247     {
248       _grades->addItem( i18n( "Grade %1 (Jinmeiyou)", grade ) );
249     }
250     else
251     {
252       _grades->addItem( i18n( "Grade %1", grade ) );
253     }
254   }
255   _grades->addItem( i18n( "Not in Jouyou list" ) );
256 
257   _strokes->addItem( i18n( "No stroke limit" ) );
258   foreach( const int &stroke, strokeCount )
259   {
260     _strokes->addItem( i18np( "%1 stroke", "%1 strokes", stroke ) );
261   }
262 
263   connect(_grades, static_cast<void (KComboBox::*)(int)>(&KComboBox::currentIndexChanged), this, &KanjiBrowserView::changeGrade);
264   connect(_strokes, static_cast<void (KComboBox::*)(int)>(&KComboBox::currentIndexChanged), this, &KanjiBrowserView::changeStrokeCount);
265   connect(_kanjiList, &QListWidget::itemClicked, this, &KanjiBrowserView::searchKanji);
266   connect(_kanjiList, &QListWidget::itemClicked, _goToKanjiInfo, &QAction::triggered);
267   connect(goToKanjiList, &QAction::triggered, this, &KanjiBrowserView::changeToListPage);
268   connect(_goToKanjiInfo, &QAction::triggered, this, &KanjiBrowserView::changeToInfoPage);
269   connect(_copyToClipboard, &QAction::triggered, this, &KanjiBrowserView::toClipboard);
270 
271   // Set the current grade (Grade 1).
272   _grades->setCurrentIndex( 1 );
273   // Set the current number of strokes (No stroke limit).
274   // NOTE: we change from '1 stroke' to 'No stroke limit'
275   // to let the ComboBox notice the change and do the filter.
276   _strokes->setCurrentIndex( 1 );
277   _strokes->setCurrentIndex( NoStrokeLimit );
278 
279   qDebug() << "Initial setup succeeded!";
280 }
281 
showKanjiInformation(const EntryKanjidic * kanji)282 void KanjiBrowserView::showKanjiInformation( const EntryKanjidic *kanji )
283 {
284   // This font is shipped with Kiten and should not be changed as it shows
285   // the stroke order of a kanji.
286   QFont kanjiFont( QStringLiteral("KanjiStrokeOrders") );
287   kanjiFont.setPointSizeF( _kanjiSize.toReal() );
288 
289   QString text;
290   text.append( "<html><body><style>" );
291   text.append( QStringLiteral( ".kanji { %1 }" ).arg( convertToCSS( kanjiFont ) ) );
292   text.append( QStringLiteral( ".label { %1 }" ).arg( convertToCSS( _labelFont ) ) );
293   text.append( QStringLiteral( ".kana  { %1 }" ).arg( convertToCSS( _kanaFont ) ) );
294   text.append( "</style>" );
295 
296   // Put the kanji.
297   text.append( QStringLiteral( "<table><tr><td><p class=\"kanji\">%1</p></td>" )
298                         .arg( kanji->getWord() ) );
299 
300   // Now the kanji grades and number of strokes.
301   text.append( "<td>" );
302   if( ! kanji->getKanjiGrade().isEmpty() )
303   {
304     text.append( QStringLiteral( "<p class=\"label\">%1 %2</p></br>" )
305                           .arg( i18n( "Grade:" ) )
306                           .arg( kanji->getKanjiGrade() ) );
307   }
308   text.append( QStringLiteral( "<p class=\"label\">%1 %2</p></td></tr></table>" )
309                         .arg( i18n( "Strokes:" ) )
310                         .arg( kanji->getStrokesCount() ) );
311 
312   // Onyomi readings.
313   if( ! kanji->getOnyomiReadingsList().isEmpty() )
314   {
315     text.append( QString( "<p class=\"label\">%1"
316                           "<span class=\"kana\">%2</span></p></br>" )
317                           .arg( i18n( "Onyomi: " ) )
318                           .arg( kanji->getOnyomiReadings() ) );
319   }
320 
321   // Kunyomi readings.
322   if( ! kanji->getKunyomiReadingsList().isEmpty() )
323   {
324     text.append( QString( "<p class=\"label\">%1"
325                           "<span class=\"kana\">%2</span></p></br>" )
326                           .arg( i18n( "Kunyomi: " ) )
327                           .arg( kanji->getKunyomiReadings() ) );
328   }
329 
330   // Special readings used in names.
331   if( ! kanji->getInNamesReadingsList().isEmpty() )
332   {
333     text.append( QString( "<p class=\"label\">%1"
334                           "<span class=\"kana\">%2</span></p></br>" )
335                           .arg( i18n( "In names: " ) )
336                           .arg( kanji->getInNamesReadings() ) );
337   }
338 
339   // Reading used as radical.
340   if( ! kanji->getAsRadicalReadingsList().isEmpty() )
341   {
342     text.append( QString( "<p class=\"label\">%1"
343                           "<span class=\"kana\">%2</span></p></br>" )
344                           .arg( i18n( "As radical: " ) )
345                           .arg( kanji->getAsRadicalReadings() ) );
346   }
347 
348   // Meanings
349   text.append( "<p class=\"label\">" );
350   if( kanji->getMeaningsList().count() == 1 )
351   {
352     text.append( i18n( "Meaning: ") );
353   }
354   else
355   {
356     text.append( i18n( "Meanings: " ) );
357   }
358   text.append( QStringLiteral( "<span class=\"kana\">%1</span></p>" )
359                         .arg( kanji->getMeanings() ) );
360 
361   // Close remaining tags and set the HTML text.
362   text.append( "</body></html>" );
363   _kanjiInformation->setHtml( text );
364 }
365 
toClipboard()366 void KanjiBrowserView::toClipboard()
367 {
368   QClipboard *cb = QApplication::clipboard();
369   cb->setText( _currentKanji->getWord(), QClipboard::Clipboard );
370   cb->setText( _currentKanji->getWord(), QClipboard::Selection );
371 }
372 
373