You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdeedu/kiten/learn.cpp

955 lines
22 KiB

/**
This file is part of Kiten, a KDE Japanese Reference Tool...
Copyright (C) 2001 Jason Katz-Brown <jason@katzbrown.com>
(C) 2005 Paul Temple <paul.temple@gmx.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
USA
**/
#include <kaction.h>
#include <kapplication.h>
#include <kdebug.h>
#include <kfiledialog.h>
#include <klistview.h>
#include <klocale.h>
#include <kmessagebox.h>
#include <kpushbutton.h>
#include <kstatusbar.h>
#include <kstringhandler.h>
#include <tqbuttongroup.h>
#include <tqheader.h>
#include <tqlayout.h>
#include <tqtabwidget.h>
#include <tqtimer.h>
#include <tqtextcodec.h>
#include <stdlib.h> // RAND_MAX
#include <cassert>
#include <set>
#include "kitenconfig.h"
#include "dict.h"
#include "kloader.h"
#include "ksaver.h"
#include "learn.h"
LearnItem::LearnItem(TQListView *parent, TQString label1, TQString label2, TQString label3, TQString label4, TQString label5, TQString label6, TQString label7, TQString label8)
: TQListViewItem(parent, label1, label2, label3, label4, label5, label6, label7, label8)
{
}
int LearnItem::compare(TQListViewItem *item, int col, bool ascending) const
{
// "Returns < 0 if this item is less than i [item] , 0 if they
// are equal and > 0 if this item is greater than i [item]."
return key(col, ascending).toInt() - item->key(col, ascending).toInt();
}
const int Learn::numberOfAnswers = 5;
Learn::Learn(Dict::Index *parentDict, TQWidget *parent, const char *name)
: KMainWindow(parent, name), initialized(false), isMod(false), prevItem(0), curItem(0)
{
index = parentDict;
TQWidget *dummy = new TQWidget(this);
setCentralWidget(dummy);
TQVBoxLayout *veryTop = new TQVBoxLayout(dummy, 0, KDialog::spacingHint());
Tabs = new TQTabWidget(dummy);
connect(Tabs, TQT_SIGNAL(currentChanged(TQWidget *)), TQT_SLOT(tabChanged(TQWidget *)));
veryTop->addWidget(Tabs);
listTop = new TQSplitter(Tabs);
listTop->setOrientation(Qt::Vertical);
quizTop = new TQWidget(Tabs);
Tabs->addTab(listTop, i18n("&List"));
Tabs->addTab(quizTop, i18n("&Quiz"));
View = new ResultView(true, listTop, "View");
View->setBasicMode(true);
connect(View, TQT_SIGNAL(linkClicked(const TQString &)), this, TQT_SIGNAL(linkClicked(const TQString &)));
List = new KListView(listTop);
List->addColumn(i18n("Kanji"));
List->addColumn(i18n("Meanings"));
List->addColumn(i18n("Readings"));
List->addColumn(i18n("Grade"));
List->addColumn(i18n("Your Score"));
List->setAllColumnsShowFocus(true);
List->setColumnWidthMode(0, TQListView::Maximum);
List->setColumnWidthMode(1, TQListView::Maximum);
List->setColumnWidthMode(2, TQListView::Maximum);
List->setColumnWidthMode(3, TQListView::Maximum);
List->setMultiSelection(true);
List->setDragEnabled(true);
List->setSorting(4);
List->setSelectionModeExt(KListView::Extended);
connect(List, TQT_SIGNAL(executed(TQListViewItem *)), TQT_SLOT(showKanji(TQListViewItem *)));
connect(List, TQT_SIGNAL(selectionChanged()), this, TQT_SLOT(itemSelectionChanged()));
TQStringList grades(i18n("Grade 1"));
grades.append(i18n("Grade 2"));
grades.append(i18n("Grade 3"));
grades.append(i18n("Grade 4"));
grades.append(i18n("Grade 5"));
grades.append(i18n("Grade 6"));
grades.append(i18n("Others in Jouyou"));
grades.append(i18n("Jinmeiyou"));
/*KAction *closeAction = */(void) KStdAction::close(TQT_TQOBJECT(this), TQT_SLOT(close()), actionCollection());
printAct = KStdAction::print(TQT_TQOBJECT(this), TQT_SLOT(print()), actionCollection());
forwardAct = KStdAction::forward(TQT_TQOBJECT(this), TQT_SLOT(next()), actionCollection());
forwardAct->plug(toolBar());
backAct = KStdAction::back(TQT_TQOBJECT(this), TQT_SLOT(prev()), actionCollection());
backAct->plug(toolBar());
cheatAct = new KAction(i18n("&Cheat"), CTRL + Key_C, TQT_TQOBJECT(this), TQT_SLOT(cheat()), actionCollection(), "cheat");
randomAct = new KAction(i18n("&Random"), "goto", CTRL + Key_R, TQT_TQOBJECT(this), TQT_SLOT(random()), actionCollection(), "random");
gradeAct = new KListAction(i18n("Grade"), 0, 0, 0, actionCollection(), "grade");
gradeAct->setItems(grades);
connect(gradeAct, TQT_SIGNAL(activated(const TQString&)), TQT_SLOT(updateGrade()));
removeAct = new KAction(i18n("&Delete"), "edit_remove", CTRL + Key_X, TQT_TQOBJECT(this), TQT_SLOT(del()), actionCollection(), "del");
addAct = new KAction(i18n("&Add"), "edit_add", CTRL + Key_A, TQT_TQOBJECT(this), TQT_SLOT(add()), actionCollection(), "add");
addAllAct = new KAction(i18n("Add A&ll"), 0, TQT_TQOBJECT(this), TQT_SLOT(addAll()), actionCollection(), "addall");
newAct = KStdAction::openNew(TQT_TQOBJECT(this), TQT_SLOT(openNew()), actionCollection());
openAct = KStdAction::open(TQT_TQOBJECT(this), TQT_SLOT(open()), actionCollection());
saveAct = KStdAction::save(TQT_TQOBJECT(this), TQT_SLOT(save()), actionCollection());
saveAsAct = KStdAction::saveAs(TQT_TQOBJECT(this), TQT_SLOT(saveAs()), actionCollection());
(void) KStdAction::preferences(TQT_TQOBJECT(this), TQT_SIGNAL(configureLearn()), actionCollection());
removeAct->setEnabled(false);
TQVBoxLayout *quizLayout = new TQVBoxLayout(quizTop, KDialog::marginHint(), KDialog::spacingHint());
quizLayout->addStretch();
TQHBoxLayout *hlayout = new TQHBoxLayout(quizLayout);
qKanji = new TQPushButton(quizTop);
connect(qKanji, TQT_SIGNAL(clicked()), this, TQT_SLOT(qKanjiClicked()));
hlayout->addStretch();
hlayout->addWidget(qKanji);
hlayout->addStretch();
quizLayout->addStretch();
answers = new TQButtonGroup(1,Qt::Horizontal, quizTop);
for (int i = 0; i < numberOfAnswers; ++i)
answers->insert(new KPushButton(answers), i);
quizLayout->addWidget(answers);
quizLayout->addStretch();
connect(answers, TQT_SIGNAL(clicked(int)), this, TQT_SLOT(answerClicked(int)));
createGUI("learnui.rc");
//closeAction->plug(toolBar());
resize(600, 400);
applyMainWindowSettings(Config::self()->config(), "LearnWindow");
statusBar()->message(i18n("Put on your thinking cap!"));
nogood = false;
// this is so learn doesn't take so long to show itself
TQTimer::singleShot(200, this, TQT_SLOT(finishCtor()));
}
void Learn::finishCtor()
{
Config* config = Config::self();
setCurrentGrade(config->grade());
/*
* this must be done now, because
* to start a quiz, we need a working randomMeaning()
* and that needs a loaded grade!
*/
updateGrade();
updateQuizConfiguration(); // first
TQString entry = config->lastFile();
//kdDebug() << "lastFile: " << entry << endl;
if (!entry.isEmpty())
{
filename = entry;
read(filename);
}
else
{
openNew();
}
initialized = true;
}
Learn::~Learn()
{
emit destroyed(this);
}
bool Learn::warnClose()
{
if (isMod)
{
int result = KMessageBox::warningYesNoCancel(this, i18n("There are unsaved changes to learning list. Save them?"), i18n("Unsaved Changes"), KStdGuiItem::save(), KStdGuiItem::discard(), "DiscardAsk", true);
switch (result)
{
case KMessageBox::Yes:
saveAct->activate();
// fallthrough
case KMessageBox::No:
return true;
case KMessageBox::Cancel:
return false;
}
}
return true;
}
bool Learn::closeWindow()
{
if (!warnClose())
{
return false;
}
else
{
close();
return true;
}
}
bool Learn::queryClose()
{
if (!warnClose())
return false; // cancel
saveScores(); // also sync()s;
saveMainWindowSettings(Config::self()->config(), "LearnWindow");
return true;
}
void Learn::random()
{
int rand = static_cast<int>(static_cast<float>(list.count()) / (static_cast<float>(RAND_MAX) / kapp->random()));
current = list.at(rand - 1);
update();
}
void Learn::next()
{
++current;
if (current == list.end())
current = list.begin();
update();
}
void Learn::prev()
{
if (Tabs->currentPageIndex() == 1)
{
if (!prevItem)
return;
curItem = prevItem;
statusBar()->clear();
qupdate();
nogood = true;
backAct->setEnabled(false);
return;
}
if (current == list.begin())
current = list.end();
--current;
update();
}
void Learn::update()
{
View->clear();
Dict::Entry curKanji = *current;
if (!curKanji.kanji())
{
statusBar()->message(i18n("Grade not loaded")); // oops
return;
}
View->addKanjiResult(curKanji);
// now show some compounds in which this kanji appears
TQString kanji = curKanji.kanji();
Dict::SearchResult compounds = index->search(TQRegExp(kanji), kanji, true);
View->addHeader(i18n("%1 in compounds").arg(kanji));
for (TQValueListIterator<Dict::Entry> it = compounds.list.begin(); it != compounds.list.end(); ++it)
{
kapp->processEvents();
View->addResult(*it, true);
}
View->flush();
}
void Learn::updateGrade()
{
int grade = getCurrentGrade();
TQString regexp("G%1 ");
regexp = regexp.arg(grade);
Dict::SearchResult result = index->searchKanji(TQRegExp(regexp), regexp, false);
list = result.list;
statusBar()->message(i18n("%1 entries in grade %2").arg(list.count()).arg(grade));
list.remove(list.begin());
current = list.begin();
update();
Config::self()->setGrade(grade);
}
void Learn::read(const KURL &url)
{
List->clear();
KLoader loader(url);
if (!loader.open())
{
KMessageBox::error(this, loader.error(), i18n("Error"));
return;
}
TQTextCodec &codec = *TQTextCodec::codecForName("eucJP");
TQTextStream &stream = loader.textStream();
stream.setCodec(&codec);
while (!stream.atEnd())
{
TQChar kanji;
stream >> kanji;
// ignore whitespace
if (!kanji.isSpace())
{
TQRegExp regexp ( TQString("^%1\\W").arg(kanji) );
Dict::SearchResult res = index->searchKanji(regexp, kanji, false);
Dict::Entry first = Dict::firstEntry(res);
if (first.extendedKanjiInfo())
add(first, true);
}
}
setClean();
}
void Learn::open()
{
if (!warnClose())
return;
KURL prevname = filename;
filename = KFileDialog::getOpenURL(TQString(), "*.kiten");
if (filename.isEmpty())
{
filename = prevname;
return;
}
read(filename);
//kdDebug() << "saving lastFile\n";
Config* config = Config::self();
config->setLastFile(filename.url());
// redo quiz, because we deleted the current quiz item
curItem = List->firstChild();
backAct->setEnabled(false);
prevItem = curItem;
qnew();
numChanged();
}
void Learn::openNew()
{
if (!warnClose())
return;
filename = "";
setCaption("");
List->clear();
setClean();
numChanged();
}
void Learn::saveAs()
{
KURL prevname = filename;
filename = KFileDialog::getSaveURL(TQString(), "*.kiten");
if (filename.isEmpty())
{
filename = prevname;
return;
}
save();
}
void Learn::save()
{
if (filename.isEmpty())
saveAs();
if (filename.isEmpty())
return;
write(filename);
Config* config = Config::self();
config->setLastFile(filename.url());
}
void Learn::write(const KURL &url)
{
KSaver saver(url);
if (!saver.open())
{
KMessageBox::error(this, saver.error(), i18n("Error"));
return;
}
TQTextCodec &codec = *TQTextCodec::codecForName("eucJP");
TQTextStream &stream = saver.textStream();
stream.setCodec(&codec);
for (TQListViewItemIterator it(List); it.current(); ++it)
stream << it.current()->text(0).at(0);
if (!saver.close())
{
KMessageBox::error(this, saver.error(), i18n("Error"));
return;
}
saveScores();
setClean();
statusBar()->message(i18n("%1 written").arg(url.prettyURL()));
}
void Learn::saveScores()
{
KConfig &config = *Config::self()->config();
config.setGroup("Learn Scores");
for (TQListViewItemIterator it(List); it.current(); ++it)
config.writeEntry(it.current()->text(0), it.current()->text(4).toInt());
config.sync();
Config::self()->writeConfig();
}
void Learn::add(Dict::Entry toAdd, bool noEmit)
{
// Remove peripheral readings: This is a study mode, not a reference mode
TQRegExp inNames (",\\s*[A-Za-z ]+:.*");
TQString readings = Dict::prettyKanjiReading(toAdd.readings()).replace(inNames, "");
TQString meanings = shortenString(Dict::prettyMeaning(toAdd.meanings()).replace(inNames, ""));
TQString kanji = toAdd.kanji();
// here's a dirty rotten cheat (well, not really)
// noEmit always means it's not added by the user, so this check isn't needed
if (!noEmit)
{
for (TQListViewItemIterator it(List); it.current(); ++it)
{
if (it.current()->text(0) == kanji)
{
statusBar()->message(i18n("%1 already on your list").arg(kanji));
return;
}
}
}
statusBar()->message(i18n("%1 added to your list").arg(kanji));
KConfig &config = *Config::self()->config();
int score = 0;
config.setGroup("Learn Scores");
score = config.readNumEntry(kanji, score);
unsigned int grade = toAdd.grade();
addItem(new LearnItem(List, kanji, meanings, readings, TQString::number(grade), TQString::number(score)), noEmit);
numChanged();
}
void Learn::add()
{
add(*current);
setDirty();
}
void Learn::addAll()
{
int grade = getCurrentGrade();
TQString regexp("G%1 ");
regexp = regexp.arg(grade);
Dict::SearchResult result = index->searchKanji(TQRegExp(regexp), regexp, false);
for (TQValueListIterator<Dict::Entry> i = result.list.begin(); i != result.list.end(); ++i)
{
// don't add headers
if ((*i).dictName() == "__NOTSET" && (*i).header() == "__NOTSET")
add(*i);
}
}
void Learn::addItem(TQListViewItem *item, bool noEmit)
{
// 2 is the magic jump
if (List->childCount() == 2)
{
curItem = item;
prevItem = curItem;
qnew(); // init first quiz
//kdDebug() << "initting first quiz in addItem\n";
}
if (!noEmit)
{
List->ensureItemVisible(item);
setDirty();
}
}
void Learn::showKanji(TQListViewItem *item)
{
assert(item);
TQString kanji(item->text(0));
int grade = item->text(3).toUInt();
if (getCurrentGrade() != grade)
{
setCurrentGrade(grade);
updateGrade();
}
// Why does this fail to find the kanji sometimes?
for (current = list.begin(); current != list.end() && (*current).kanji() != kanji; ++current);
update();
}
void Learn::del()
{
// quiz page
if (Tabs->currentPageIndex() == 1)
{
delete curItem;
curItem = prevItem; // needs to be something
qnew();
}
else // setup page
{
TQPtrList<TQListViewItem> selected = List->selectedItems();
assert(selected.count());
bool makenewq = false; // must make new quiz if we
// delete the current item
for (TQPtrListIterator<TQListViewItem> i(selected); *i; ++i)
{
if (curItem == i)
makenewq = true;
delete *i;
}
curItem = List->firstChild();
prevItem = curItem;
backAct->setEnabled(false);
if (makenewq)
{
qnew();
}
setDirty();
}
itemSelectionChanged();
numChanged();
}
// too easy...
void Learn::print()
{
View->clear();
View->addHeader(TQString("<h1>%1</h1>").arg(i18n("Learning List")), 1);
TQListViewItemIterator it(List);
for (; it.current(); ++it)
{
TQString kanji = it.current()->text(0);
Dict::SearchResult result = index->searchKanji(TQRegExp(kanji), kanji, false);
for (TQValueListIterator<Dict::Entry> i = result.list.begin(); i != result.list.end(); ++i)
{
if ((*i).dictName() == "__NOTSET" && (*i).header() == "__NOTSET")
{
View->addKanjiResult(*i);
break;
}
}
}
View->print();
}
void Learn::answerClicked(int i)
{
if (!curItem)
return;
int newscore = 0;
// KConfig &config = *Config::self()->config();
// config.setGroup("Learn");
bool donew = false;
if (seikai == i)
{
statusBar()->message(i18n("Good!"));
if (!nogood) // add two to their score
newscore = curItem->text(4).toInt() + 2;
else
{
qnew();
return;
}
donew = true;
}
else
{
statusBar()->message(i18n("Wrong"));
// take one off score
newscore = curItem->text(4).toInt() - 1;
if (!nogood)
nogood = true;
else
return;
}
//config.writeEntry(curItem->text(0) + "_4", newscore);
TQListViewItem *newItem = new LearnItem(List, curItem->text(0), curItem->text(1), curItem->text(2), curItem->text(3), TQString::number(newscore));
// readd, so it sorts
// 20 November 2004: why?? why not List->sort() ??
// haha I used to be naive
delete curItem;
curItem = newItem;
if (donew)
qnew();
}
TQString Learn::randomMeaning(TQStringList &oldMeanings)
{
TQString meaning;
do
{
float rand = kapp->random();
if ((rand > (RAND_MAX / 2)) || (List->childCount() < numberOfAnswers))
{
// get a meaning from dict
//kdDebug() << "from our dict\n";
rand = kapp->random();
float rand2 = RAND_MAX / rand;
rand = ((float)list.count() - 1) / rand2;
//rand -= 1;
//kdDebug() << "rand: " << rand << endl;
//kdDebug() << "list.count(): " << list.count() << endl;
switch (guessOn)
{
case 1:
meaning = shortenString(Dict::prettyMeaning((*list.at(static_cast<unsigned int>(rand))).meanings()));
break;
case 2:
meaning = Dict::prettyKanjiReading((*list.at(static_cast<unsigned int>(rand))).readings());
break;
case 0:
meaning = (*list.at(static_cast<unsigned int>(rand))).kanji();
}
}
else
{
// get a meaning from our list
//kdDebug() << "from our list\n";
rand = kapp->random();
float rand2 = RAND_MAX / rand;
rand = List->childCount() / rand2;
int max = (int) rand;
TQListViewItemIterator it(List);
it += max;
meaning = it.current()->text(guessOn);
}
//kdDebug() << "meaning: " << meaning << endl;
for (TQStringList::Iterator it = oldMeanings.begin(); it != oldMeanings.end(); ++it)
{
//kdDebug() << "oldMeaning: " << *it << endl;
}
//kdDebug() << "curMeaning: " << curItem->text(guessOn) << endl;
}
while (oldMeanings.contains(meaning) || meaning == curItem->text(guessOn));
oldMeanings.append(meaning);
meaning = shortenString(meaning);
return meaning;
}
void Learn::qupdate()
{
if (!curItem)
return;
qKanji->setText(curItem->text(quizOn));
TQFont newFont = font();
if (quizOn == 0)
newFont.setPixelSize(24);
qKanji->setFont(newFont);
float rand = kapp->random();
float rand2 = RAND_MAX / rand;
seikai = static_cast<int>(numberOfAnswers / rand2);
TQStringList oldMeanings;
for (int i = 0; i < numberOfAnswers; ++i)
answers->find(i)->setText(randomMeaning(oldMeanings));
answers->find(seikai)->setText(curItem->text(guessOn));
}
struct Learn::scoreCompare
{
bool operator()(const TQListViewItem* v1, const TQListViewItem* v2)
{
return v1->text(4).toInt() < v2->text(4).toInt();
}
};
void Learn::qnew() // new quiz kanji
{
//kdDebug() << "qnew\n";
nogood = false;
statusBar()->clear();
statusBar()->message(TQString("%1 %2 %3").arg(curItem->text(0)).arg(curItem->text(1)).arg(curItem->text(2)));
backAct->setEnabled(true);
unsigned int count = List->childCount();
if (count < 2)
return;
// the following lines calculate which kanji will be used next:
// use f(2) every third time, f(1) otherwise
// where f(1) = numberOfItems * rand[0..1]
// and f(2) = numberOfItems * rand[0..1] * rand[0..1]
// rand[0..1] = kapp->random() / RAND_MAX
float max = static_cast<float>(count) / (static_cast<float>(RAND_MAX) / kapp->random());
if (kapp->random() < (static_cast<float>(RAND_MAX) / 3.25))
max /= (static_cast<float>(RAND_MAX) / (kapp->random() + 1));
max = static_cast<int>(max);
if (max > count)
max = count;
std::multiset<const TQListViewItem*, scoreCompare> scores;
TQListViewItemIterator sIt(List);
for (; sIt.current(); ++sIt)
scores.insert(sIt.current());
std::multiset<const TQListViewItem*, scoreCompare>::iterator it = scores.begin();
std::multiset<const TQListViewItem*, scoreCompare>::iterator tmp = scores.begin();
int i;
for (i = 2; i <= max; ++it)
{i++; ++tmp;}
if (curItem->text(0) == (*it)->text(0)) // same, don't use
{
++it;
if (it == scores.end())
{
tmp--;
it = tmp;
}
}
if (it == scores.end())
{
return;
}
prevItem = curItem;
curItem = const_cast<TQListViewItem*>(*it);
qKanji->setFocus();
qupdate();
}
void Learn::cheat()
{
answers->find(seikai)->setFocus();
statusBar()->message(i18n("Better luck next time"));
nogood = true;
}
TQString Learn::shortenString(const TQString &thestring)
{
return KStringHandler::rsqueeze(thestring, 60).stripWhiteSpace();
}
void Learn::tabChanged(TQWidget *widget)
{
bool isQuiz = (widget == quizTop);
if (isQuiz)
backAct->setEnabled(prevItem != 0);
else
backAct->setEnabled(true);
forwardAct->setEnabled(!isQuiz);
gradeAct->setEnabled(!isQuiz);
saveAct->setEnabled(!isQuiz);
addAct->setEnabled(!isQuiz);
addAllAct->setEnabled(!isQuiz);
randomAct->setEnabled(!isQuiz);
openAct->setEnabled(!isQuiz);
newAct->setEnabled(!isQuiz);
saveAsAct->setEnabled(!isQuiz);
cheatAct->setEnabled(isQuiz);
// also handled below for !isQuiz case
removeAct->setEnabled(isQuiz);
if (isQuiz)
{
qKanji->setFocus();
}
else
{
// handle removeAct;
itemSelectionChanged();
}
statusBar()->clear();
}
void Learn::updateQuiz()
{
if (List->childCount() < 3)
Tabs->setTabEnabled(quizTop, false);
else
Tabs->setTabEnabled(quizTop, true);
}
void Learn::itemSelectionChanged()
{
removeAct->setEnabled(List->selectedItems().count() > 0);
}
int Learn::getCurrentGrade(void)
{
int grade = gradeAct->currentItem() + 1;
if (grade > 6) ++grade;
return grade;
}
void Learn::setCurrentGrade(int grade)
{
if (grade > 6) --grade;
gradeAct->setCurrentItem(grade - 1);
}
void Learn::updateQuizConfiguration()
{
Config* config = Config::self();
quizOn = config->quizOn();
guessOn = config->guessOn();
answers->setTitle(List->columnText(guessOn));
View->updateFont();
if (List->childCount() >= 2 && initialized)
qnew();
}
void Learn::setDirty()
{
isMod = true;
setCaption(filename.prettyURL(), true);
}
void Learn::setClean()
{
isMod = false;
if (!filename.prettyURL().isEmpty())
setCaption(filename.prettyURL(), false);
}
void Learn::qKanjiClicked()
{
if (!curItem)
return;
showKanji(curItem);
nogood = true;
}
void Learn::numChanged()
{
Tabs->setTabEnabled(quizTop, List->childCount() >= 2);
//quizTop->setEnabled(List->childCount() >= 2);
}
#include "learn.moc"