Various changes to preferences object, file loading, and error logging.
[jben.git] / panel_kanjidrill.cpp
blobcfc3d689f6e49b8370d77f38d13d466b12803a88
1 /*
2 Project: J-Ben
3 Author: Paul Goins
4 Website: http://www.vultaire.net/software/jben/
5 License: GNU General Public License (GPL) version 2
6 (http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt)
8 File: panel_kanjidrill.cpp
10 This program is free software; you can redistribute it and/or modify
11 it under the terms of the GNU General Public License as published by
12 the Free Software Foundation; either version 2 of the License, or
13 (at your option) any later version.
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 GNU General Public License for more details.
20 You should have received a copy of the GNU General Public License
21 along with this program. If not, see <http://www.gnu.org/licenses/>
24 #include "panel_kanjidrill.h"
25 #include "jben.h"
26 #include "frame_maingui.h"
27 #include "kdict.h"
28 #include "string_utils.h"
29 #include "encoding_convert.h"
30 #include "errorlog.h"
31 #include <cstdlib>
32 #include <algorithm>
33 #include <sstream>
34 using namespace std;
36 enum {
37 /* Controls to configure the test */
38 ID_pnlConfig = 1,
39 ID_spnKanjiCount,
40 ID_sbKanjiSelect,
41 ID_rdoRandom,
42 ID_rdoStartIndex,
43 ID_spnStartIndex,
44 ID_rdoKanjiReading,
45 ID_rdoKanjiWriting,
46 ID_btnStart,
47 /* Controls for use during the test */
48 ID_pnlTest,
49 ID_txtKanji,
50 ID_txtOnyomi,
51 ID_txtKunyomi,
52 ID_txtEnglish,
53 ID_btnCorrect,
54 ID_btnWrong,
55 ID_btnStop,
56 ID_lblTestProgress
59 enum {
60 PKD_TM_Reading = 1,
61 PKD_TM_Writing
64 BEGIN_EVENT_TABLE(PanelKanjiDrill, wxPanel)
65 EVT_SPINCTRL(ID_spnKanjiCount, PanelKanjiDrill::OnKanjiCountChange)
66 EVT_RADIOBUTTON(ID_rdoRandom, PanelKanjiDrill::OnRdoRandom)
67 EVT_RADIOBUTTON(ID_rdoStartIndex, PanelKanjiDrill::OnRdoStartIndex)
68 EVT_BUTTON(ID_btnStart, PanelKanjiDrill::OnStart)
70 EVT_BUTTON(ID_btnCorrect, PanelKanjiDrill::OnCorrect)
71 EVT_BUTTON(ID_btnWrong, PanelKanjiDrill::OnWrong)
72 EVT_BUTTON(ID_btnStop, PanelKanjiDrill::OnStop)
73 END_EVENT_TABLE()
75 PanelKanjiDrill::PanelKanjiDrill(wxWindow *owner) : RedrawablePanel(owner) {
76 /* Create test config controls */
77 pnlConfig = new wxPanel(this, ID_pnlConfig);
79 wxPanel *pnlKanjiSelect = new wxPanel(pnlConfig, wxID_ANY);
80 wxStaticText *lblKanjiCount = new wxStaticText(pnlKanjiSelect, wxID_ANY, _T("Number of kanji to test: "));
81 spnKanjiCount = new wxSpinCtrl(pnlKanjiSelect, ID_spnKanjiCount);
82 spnKanjiCount->SetValue(20);
83 rdoRandom = new wxRadioButton(pnlKanjiSelect, ID_rdoRandom, _T("Choose randomly from list"), wxDefaultPosition, wxDefaultSize, wxRB_GROUP);
84 rdoStartIndex = new wxRadioButton(pnlKanjiSelect, ID_rdoStartIndex, _T("Start at index: "));
85 spnStartIndex = new wxSpinCtrl(pnlKanjiSelect, ID_spnStartIndex);
86 spnStartIndex->Enable(false);
88 wxPanel *pnlTestSelect = new wxPanel(pnlConfig, wxID_ANY);
89 rdoKanjiReading = new wxRadioButton(pnlTestSelect, ID_rdoKanjiReading, _T("Reading Kanji"), wxDefaultPosition, wxDefaultSize, wxRB_GROUP);
90 rdoKanjiWriting = new wxRadioButton(pnlTestSelect, ID_rdoKanjiWriting, _T("Writing Kanji"));
92 btnStart = new wxButton(pnlConfig, ID_btnStart, _T("Start Drill"));
94 /* Create test controls */
95 pnlTest = new wxPanel(this, ID_pnlTest);
96 txtKanji = new CoveredTextBox(pnlTest, ID_txtKanji, _T("< Kanji >"));
97 wxFont fnt = txtKanji->GetFont();
98 fnt.SetPointSize(fnt.GetPointSize()+10);
99 txtKanji->SetFont(fnt);
100 txtOnyomi = new CoveredTextBox(pnlTest, ID_txtOnyomi, _T("< Onyomi >"));
101 txtKunyomi = new CoveredTextBox(pnlTest, ID_txtKunyomi, _T("< Kunyomi >"));
102 txtEnglish = new CoveredTextBox(pnlTest, ID_txtEnglish, _T("< English >"), _T("Some overly ridiculously long English string to see how resizing will work when uncovering a string which is too big for the current shape/size of the CoveredTextBox."));
103 btnCorrect = new wxButton(pnlTest, ID_btnCorrect, _T("Correct"));
104 btnWrong = new wxButton(pnlTest, ID_btnWrong, _T("Wrong"));
105 btnStop = new wxButton(pnlTest, ID_btnStop, _T("Stop Drill"));
106 lblTestProgress = new wxStaticText(pnlTest, ID_lblTestProgress, _T(""));
108 /* Create sizers */
109 /* Our custom radio box for selecting the kanji to test */
110 wxStaticBoxSizer *kanjiConfigSizer = new wxStaticBoxSizer(wxVERTICAL, pnlKanjiSelect, _T("Choose Kanji to Test"));
111 /* Row 1: Number of kanji to test */
112 wxBoxSizer *kanjiCountSizer = new wxBoxSizer(wxHORIZONTAL);
113 kanjiCountSizer->Add(lblKanjiCount, 0, wxALIGN_CENTER_VERTICAL);
114 kanjiCountSizer->Add(spnKanjiCount, 0, wxALIGN_CENTER_VERTICAL);
115 /* Row 2: Random kanji, or do range by start index */
116 wxBoxSizer *kanjiSelectSizer = new wxBoxSizer(wxVERTICAL);
117 kanjiSelectSizer->Add(rdoRandom, 1, wxALIGN_CENTER_VERTICAL);
118 wxBoxSizer *kanjiStartIndexSizer = new wxBoxSizer(wxHORIZONTAL);
119 kanjiStartIndexSizer->Add(rdoStartIndex, 0, wxALIGN_CENTER_VERTICAL);
120 kanjiStartIndexSizer->Add(spnStartIndex, 0, wxALIGN_CENTER_VERTICAL);
121 kanjiStartIndexSizer->AddStretchSpacer(1);
122 kanjiSelectSizer->Add(kanjiStartIndexSizer, 1, wxEXPAND | wxALIGN_CENTER_VERTICAL);
123 /* Finally: Add both rows to the static box sizer */
124 kanjiConfigSizer->Add(kanjiCountSizer, 0);
125 kanjiConfigSizer->Add(kanjiSelectSizer, 0, wxEXPAND);
126 pnlKanjiSelect->SetSizer(kanjiConfigSizer);
128 /* Our custom radio box for selecting the test mode */
129 wxStaticBoxSizer *testModeSizer = new wxStaticBoxSizer(wxVERTICAL, pnlTestSelect, _T("Choose Test Type"));
130 testModeSizer->Add(rdoKanjiReading, 1);
131 testModeSizer->Add(rdoKanjiWriting, 1);
132 pnlTestSelect->SetSizer(testModeSizer);
134 /* Create any other horizontal sizers */
135 wxBoxSizer *correctWrongSizer = new wxBoxSizer(wxHORIZONTAL);
136 correctWrongSizer->Add(btnCorrect, 0, wxRIGHT, 10);
137 correctWrongSizer->Add(btnWrong);
138 correctWrongSizer->AddStretchSpacer(1);
139 correctWrongSizer->Add(btnStop);
140 /* The config sizer */
141 wxBoxSizer *configSizer = new wxBoxSizer(wxVERTICAL);
142 configSizer->Add(pnlKanjiSelect, 0, wxEXPAND | wxBOTTOM, 10);
143 configSizer->Add(pnlTestSelect, 0, wxEXPAND | wxBOTTOM, 10);
144 configSizer->Add(btnStart, 0, wxALIGN_CENTER_HORIZONTAL);
145 pnlConfig->SetSizer(configSizer);
146 /* The test mode sizer */
147 wxBoxSizer *testSizer = new wxBoxSizer(wxVERTICAL);
148 testSizer->Add(txtKanji, 0, wxEXPAND | wxBOTTOM, 10);
149 testSizer->Add(txtOnyomi, 0, wxEXPAND | wxBOTTOM, 10);
150 testSizer->Add(txtKunyomi, 0, wxEXPAND | wxBOTTOM, 10);
151 testSizer->Add(txtEnglish, 0, wxEXPAND | wxBOTTOM, 10);
152 testSizer->Add(correctWrongSizer, 0, wxEXPAND);
153 testSizer->AddStretchSpacer(1);
154 testSizer->Add(lblTestProgress, 0, wxEXPAND);
155 pnlTest->SetSizer(testSizer);
156 /* The panel-wide sizer */
157 wxBoxSizer *panelSizer = new wxBoxSizer(wxVERTICAL);
158 panelSizer->Add(pnlConfig, 1, wxEXPAND | wxALL, 10);
159 panelSizer->Add(pnlTest, 1, wxEXPAND | wxALL, 10);
161 testing=false;
162 pnlTest->Show(false); /* Set the test panel as invisible at the beginning */
163 this->SetSizerAndFit(panelSizer); /* Now apply the sizer afterwards so layout occurs properly. */
166 void PanelKanjiDrill::OnStart(wxCommandEvent& ev) {
167 /* Placeholder code */
168 /* We need to do the following:
169 0. (Possibly) Verify that test mode is NOT enabled when this event is triggered
170 (In case the button can somehow be pushed when not visible.) (Done)
171 0.5. (Possibly) Validate the input of the user. Might not be necessary if all controls limit input properly.
172 (They do now.)
173 1. Reset any test variables
174 2. Create kanji test list based upon selected options
175 3. Load up the first kanji to be tested
176 4. Switch to the test GUI */
178 unsigned int i, startIndex;
179 if(!testing) {
180 /* Reset test variables */
181 currentKanjiIndex = -1;
182 extraPractice = false;
183 lastWasCorrect = false; /* used for Extra Practice mode only */
184 totalToTest = spnKanjiCount->GetValue();
185 testKanji.clear();
186 missedKanji.clear();
187 correct = totalTested = 0;
188 if(rdoKanjiReading->GetValue()) testMode = PKD_TM_Reading;
189 else if(rdoKanjiWriting->GetValue()) testMode = PKD_TM_Writing;
190 else {
191 wxMessageBox(_T("Unknown test mode selected!"), _T("Error!"), wxOK | wxICON_ERROR, this);
192 return;
195 /* Create kanji test list based upon selected options */
196 if(rdoStartIndex->GetValue()) {
197 /* Copy from study list at specified index */
198 startIndex = spnStartIndex->GetValue() - 1; /* Remember, the GUI uses a 1-base. */
199 for(i=startIndex; i < startIndex + totalToTest; i++)
200 testKanji.push_back((*jben->kanjiList)[i]);
201 } else {
202 /* Random character selection */
203 vector<wxChar> localVector = jben->kanjiList->GetVector();
204 wxChar c;
205 while(testKanji.size()<totalToTest) {
206 i = rand() % localVector.size();
207 c = localVector[i];
208 testKanji.push_back(c);
209 localVector.erase(localVector.begin() + i);
213 /* Debug: Show our list of kanji to test */
214 #ifdef DEBUG
215 printf("DEBUG: Kanji to test: ");
216 for(vector<wxChar>::iterator vi=testKanji.begin();vi!=testKanji.end();vi++)
217 printf("%lc", *vi);
218 printf("\n");
219 #endif
221 /* Load up the first kanji */
222 ShowNextKanji();
224 /* Switch the displayed panel */
225 jben->gui->GetMenuBar()->EnableTop(GUI_menuKanji, false);
226 pnlConfig->Show(false);
227 pnlTest->Show(true);
228 pnlTest->SetFocus();
229 this->GetSizer()->Layout();
230 testing=true;
234 void PanelKanjiDrill::Stop() {
235 double score=0.0;
236 if(totalTested>0) score = round(100000*
237 (double)correct/
238 (double)totalTested)/1000;
239 wxString kanjiMissed;
240 for(vector<wxChar>::iterator vi=missedKanji.begin(); vi!=missedKanji.end(); vi++)
241 kanjiMissed.append(*vi);
243 /* Display test results */
244 if(totalTested==totalToTest) {
245 wxMessageBox(
246 wxString::Format(_T("TEST COMPLETED\n\nFinal Score: %d/%d (%0.3f%%)\nKanji Missed: %s"),
247 correct, totalTested, score, kanjiMissed.c_str()),
248 _T("Test Results"), wxOK | wxICON_INFORMATION, this);
249 } else {
250 wxMessageBox(
251 wxString::Format(_T("TEST INCOMPLETE\n\nScore: %d/%d (%0.3f%%)\nTest Progress: %d/%d (%0.3f%%)\nKanji Missed: %s"),
252 correct, totalTested, score, totalTested, totalToTest,
253 round(100000*(double)totalTested/(double)totalToTest)/1000,
254 kanjiMissed.c_str()),
255 _T("Test Results"), wxOK | wxICON_INFORMATION, this);
258 /* Switch the displayed panel */
259 pnlTest->Show(false);
260 pnlConfig->Show(true);
261 pnlConfig->SetFocus();
262 this->GetSizer()->Layout();
263 jben->gui->GetMenuBar()->EnableTop(GUI_menuKanji, true);
264 testing=false;
267 void PanelKanjiDrill::OnStop(wxCommandEvent& ev) {
268 if(testing) {
269 /* If testing is not complete, prompt to make sure we want to quit. */
270 int result = wxYES;
271 if(testKanji.size()!=0) {
272 result = wxMessageBox(_T("A test is still in progress! Are you sure?"), _T("Test in progress!"), wxYES_NO | wxICON_EXCLAMATION, this);
274 if(result==wxYES) {
275 Stop();
280 bool PanelKanjiDrill::TestInProgress() {return testing;}
282 void PanelKanjiDrill::UpdateKanjiCountSpinner() {
283 int max = jben->kanjiList->Size();
284 if(max>0) {
285 spnKanjiCount->SetRange(1, max);
286 int i = spnKanjiCount->GetValue();
287 if(i<1) {i=1; spnKanjiCount->SetValue(1);}
288 if(i>max) {i=max; spnKanjiCount->SetValue(max);}
292 void PanelKanjiDrill::UpdateStartIndexSpinner() {
293 int max = jben->kanjiList->Size();
294 int i = spnKanjiCount->GetValue();
295 int indexMax = max - (i-1);
296 spnStartIndex->SetRange(1, indexMax);
297 i = spnStartIndex->GetValue();
298 if(i<1) spnStartIndex->SetValue(1);
299 if(i>indexMax) spnStartIndex->SetValue(indexMax);
302 void PanelKanjiDrill::OnKanjiCountChange(wxSpinEvent& ev) {
303 UpdateStartIndexSpinner();
306 void PanelKanjiDrill::Redraw() {
307 if(!testing) {
308 int max = jben->kanjiList->Size();
309 if(max>0) {
310 UpdateKanjiCountSpinner();
311 UpdateStartIndexSpinner();
312 this->Enable(true);
313 } else {
314 this->Enable(false);
319 void PanelKanjiDrill::OnRdoRandom(wxCommandEvent& ev) {
320 spnStartIndex->Enable(false);
323 void PanelKanjiDrill::OnRdoStartIndex(wxCommandEvent& ev) {
324 spnStartIndex->Enable(true);
327 void PanelKanjiDrill::ShowNextKanji() {
328 const KDict* kd = KDict::Get();
329 const KInfo* ki;
330 /* Remove the current kanji, and get the new one */
331 if(!extraPractice) {
332 if(currentKanjiIndex!=-1)
333 testKanji.erase(testKanji.begin()+currentKanjiIndex);
334 if(testKanji.size()>0) {
335 currentKanjiIndex = rand() % testKanji.size();
336 currentKanji = testKanji[currentKanjiIndex];
337 } else { /* If there's no more kanji to get... */
338 if(missedKanji.size()>0) {
339 extraPractice = true;
340 testKanji = missedKanji;
342 else Stop();
345 if(extraPractice) {
346 if(lastWasCorrect)
347 testKanji.erase(testKanji.begin()+currentKanjiIndex);
348 if(testKanji.size()>0) {
349 currentKanjiIndex = rand() % testKanji.size();
350 currentKanji = testKanji[currentKanjiIndex];
351 } else { /* If there's no more kanji to get... */
352 Stop();
356 /* Update CoveredTextBoxes with new info from KANJIDIC. */
357 ki = kd->GetEntry(currentKanji);
358 txtKanji->Cover();
359 txtOnyomi->Cover();
360 txtKunyomi->Cover();
361 txtEnglish->Cover();
362 txtKanji->SetHiddenStr(wxString(currentKanji));
363 txtOnyomi->SetHiddenStr(
364 utfconv_mw(ListToString<string>(ki->onyomi, "、")));
365 txtKunyomi->SetHiddenStr(
366 utfconv_mw(ListToString<string>(ki->kunyomi, "、")));
367 map<string, list<string> >::const_iterator
368 mslsi = ki->meaning.find("en");
369 if(mslsi != ki->meaning.end()) {
370 txtEnglish->SetHiddenStr(
371 utfconv_mw(ListToString<string>(mslsi->second, "、")));
372 } else {
373 ostringstream oss;
374 oss << "Unable to find english meaning in character "
375 << ki->literal << "!";
376 el.Push(EL_Warning, oss.str());
379 /* Update the test status label */
380 double score=0.0;
381 if(totalTested>0) score = round(100000*
382 (double)correct/
383 (double)totalTested)/1000;
384 if(!extraPractice) {
385 lblTestProgress->SetLabel(wxString::Format(
386 _T("Current score: %d/%d (%0.3f%%) Test Progress: %d/%d (%0.3f%% done)"),
387 correct, totalTested, score,
388 totalTested, totalToTest, round(100000*
389 double(totalTested)/
390 double(totalToTest))/1000));
391 } else {
392 lblTestProgress->SetLabel(wxString::Format(
393 _T("Extra practice: %d remaining Final score: %d/%d (%0.3f%%)"),
394 testKanji.size(), correct, totalTested, score));
397 /* Refresh layout */
398 this->GetSizer()->Layout();
400 /* Uncover the field(s) appropriate for the current test mode. */
401 switch(testMode) {
402 case PKD_TM_Reading:
403 txtKanji->Uncover();
404 break;
405 case PKD_TM_Writing:
406 txtOnyomi->Uncover();
407 txtKunyomi->Uncover();
408 txtEnglish->Uncover();
409 break;
413 void PanelKanjiDrill::OnCorrect(wxCommandEvent& ev) {
414 if(!extraPractice) {
415 totalTested++;
416 correct++;
417 } else lastWasCorrect = true;
418 ShowNextKanji();
421 void PanelKanjiDrill::OnWrong(wxCommandEvent& ev) {
422 if(!extraPractice) {
423 totalTested++;
424 missedKanji.push_back(currentKanji);
425 } else lastWasCorrect = false;
426 ShowNextKanji();