/*
* Quackle -- Crossword game artificial intelligence and analysis tool
* Copyright (C) 2005-2006 Jason Katz-Brown and John O'Laughlin.
*
* 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
#include
#include
#include "letterbox.h"
#include "customqsettings.h"
#include "quackersettings.h"
#include "letterboxsettings.h"
#include "lister.h"
Letterbox *Letterbox::m_self = 0;
Letterbox *Letterbox::self()
{
return m_self;
}
Letterbox::Letterbox(QWidget *parent, QAction *preferencesAction, ListerDialog *listerDialog)
: QMainWindow(parent), m_initializationChuu(false), m_modified(false), m_mistakeMade(false), m_listerDialog(listerDialog), m_pauseMs(0), m_keystrokes(0), m_numberIterator(0), m_preferencesAction(preferencesAction)
{
m_self = this;
createWidgets();
createMenu();
loadSettings();
setCaption(tr("No List"));
QTimer::singleShot(0, this, SLOT(finishInitialization()));
}
Letterbox::~Letterbox()
{
saveSettings();
}
void Letterbox::closeEvent(QCloseEvent *closeEvent)
{
closeEvent->setAccepted(tryToClose());
}
bool Letterbox::tryToClose()
{
pause(true);
if (m_modified)
{
switch (askToSave())
{
case 0:
qApp->processEvents();
writeFile();
// fall through
case 1:
return true;
case 2:
return false;
}
}
return true;
}
void Letterbox::finishInitialization()
{
// ensure no dangling iterators
m_clueResultsIterator = m_clueResults.begin();
m_answersIterator = m_answers.begin();
m_queryIterator = m_list.begin();
m_timer = new QTimer(this);
connect(m_timer, SIGNAL(timeout()), this, SLOT(timeout()));
m_clueResults.clear();
loadFile();
if (m_filename.isEmpty())
{
statusBar()->showMessage(tr("[Paused]") + QString(" ") + tr("Enjoy your letterboxings."));
loadExampleList();
}
else
{
statusBar()->showMessage(tr("[Paused]") + QString(" ") + tr("Enjoy your letterboxings on %1.").arg(m_filename.right(m_filename.length() - m_filename.lastIndexOf("/") - 1)));
}
}
void Letterbox::open()
{
pause(true);
if (m_modified)
{
switch (askToSave())
{
case 0:
writeFile();
case 1:
break;
case 2:
return;
}
}
QString defaultFilter = defaultStudyListFileFilter();
QString filename = QFileDialog::getOpenFileName(this, tr("Choose Letterbox file to open"), getInitialDirectory(), studyListFileFilters(), &defaultFilter);
if (!filename.isEmpty())
{
setInitialDirectory(filename);
m_filename = filename;
loadFile();
saveSettings();
}
}
void Letterbox::openParticularFile(const QString &filename)
{
if (!filename.isEmpty())
{
if (m_modified)
{
switch (askToSave())
{
case 0:
writeFile();
case 1:
break;
case 2:
return;
}
}
m_filename = filename;
loadFile();
}
}
void Letterbox::setCaption(const QString &text)
{
if (!text.isNull())
m_ourCaption = text;
setWindowTitle(QString("%1[*] - Quackle Letterbox").arg(m_ourCaption));
}
void Letterbox::setModified(bool modified)
{
m_modified = modified;
setWindowModified(m_modified);
}
bool Letterbox::dictCheck()
{
if (!QuackleIO::DictFactory::querier()->isLoaded())
{
QMessageBox::critical(this, tr("No Dictionary Loaded - Quackle Letterbox"), tr("Please open a dictionary with Settings->Set Dictionary."));
return false;
}
return true;
}
int Letterbox::askToSave()
{
return QMessageBox::warning(this, tr("Unsaved Results - Quackle Letterbox"), tr("There are unsaved results in the current Letterbox list. Save them?"), tr("&Save"), tr("&Discard"), tr("&Cancel"), 0, 2);
}
void Letterbox::generateList()
{
pause(true);
if (m_listerDialog)
{
m_listerDialog->show();
m_listerDialog->raise();
}
}
void Letterbox::loadFile()
{
if (m_filename.isEmpty())
return;
QString filename(m_filename.right(m_filename.length() - m_filename.lastIndexOf("/") - 1));
statusBar()->showMessage(tr("Loading %1...").arg(filename));
qApp->processEvents();
m_list.clear();
m_answers.clear();
m_clueResults.clear();
QFile file(m_filename);
if (!file.exists())
{
QMessageBox::critical(this, tr("Error Loading Letterbox List - Quackle Letterbox"), tr("Filename %1 does not exist").arg(m_filename));
return;
}
if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
{
QMessageBox::critical(this, tr("Error Loading Letterbox List - Quackle Letterbox"), tr("%1 cannot be opened.").arg(filename));
return;
}
int startAt = 0;
QTextStream stream(&file);
QString line;
m_initializationChuu = true;
bool firstLine = true;
while (!stream.atEnd())
{
line = stream.readLine().trimmed().toUpper();
if (firstLine)
{
if (line.left(10) == "\" RESUME: ")
startAt = line.right(line.length() - 10).toInt();
firstLine = false;
}
line.remove('#');
QString letters = line;
QString comment;
int quoteMarkIndex = line.indexOf("\"");
if (quoteMarkIndex >= 0)
{
letters = line.left(quoteMarkIndex).trimmed();
comment = line.right(line.length() - quoteMarkIndex - 1).trimmed();
}
if (letters.isEmpty())
continue;
m_list += letters;
m_clueResults.append(parseComment(comment));
m_clueResults.last().clue = clueFor(letters);
}
file.close();
if (!dictCheck())
return;
jumpTo(startAt);
statusBar()->showMessage(tr("Loaded list `%1' of length %2.").arg(filename).arg(m_clueResults.count()));
setCaption(filename);
m_initializationChuu = false;
setModified(false);
prepareQuiz();
pause(true);
}
void Letterbox::loadExampleList()
{
// TODO; load an interesting list, like sixes in playability order
}
void Letterbox::writeFile()
{
outputResults();
setModified(false);
statusBar()->showMessage(tr("Saved results to file."));
}
void Letterbox::jumpTo()
{
pause(true);
bool ok;
int index = QInputDialog::getInteger(this, tr("Jump to word - Quackle Letterbox"), tr("Index to which to jump:"), m_numberIterator + 1, 1, m_clueResults.count(), 1, &ok);
if (ok)
{
jumpTo(index);
}
prepareQuiz();
pause(true);
}
void Letterbox::jumpTo(int index)
{
if (m_numberIterator != index)
setModified(true);
m_numberIterator = index;
if (index >= m_clueResults.size())
{
m_answersIterator = m_answers.end();
m_clueResultsIterator = m_clueResults.end();
m_queryIterator = m_list.end();
return;
}
while (m_answers.count() <= m_numberIterator)
{
m_answers.append(answersFor(m_list.at(m_answers.count())));
m_clueResults[m_answers.count() - 1].setWordList(m_answers.at(m_answers.count() - 1));
}
m_clueResultsIterator = m_clueResults.begin();
m_answersIterator = m_answers.begin();
for (int i = 0; i < index; ++i)
{
++m_clueResultsIterator;
++m_answersIterator;
}
(*m_clueResultsIterator).resetStats();
m_queryIterator = m_list.begin();
for (int i = 0; i < index + 1; ++i)
{
++m_queryIterator;
}
}
ClueResult Letterbox::parseComment(const QString &comment)
{
if (comment.isEmpty())
return ClueResult();
QStringList items = comment.split(" ", QString::SkipEmptyParts);
ClueResult ret;
for (QStringList::iterator it = items.begin(); it != items.end(); ++it)
{
WordResult word;
word.word = *it;
++it;
word.time = (*it).toInt();
++it;
word.keystrokes = (*it).toInt();
word.missed = (word.time == 0);
ret.words.append(word);
}
return ret;
}
void Letterbox::increment()
{
jumpTo(m_numberIterator + 1);
if (m_numberIterator >= m_clueResults.size())
{
updateViews();
listFinished();
m_timer->stop();
return;
}
prepareQuiz();
}
void Letterbox::pause(bool paused)
{
timerControl(paused);
if (m_pauseAction->isChecked() != paused)
m_pauseAction->setChecked(paused);
if (!paused)
m_lineEdit->setFocus();
if (paused)
{
m_pauseTime.start();
statusBar()->showMessage(tr("Paused on #%1 of %2 total.").arg(m_numberIterator + 1).arg(m_clueResults.count()));
}
else
{
m_pauseMs += m_pauseTime.elapsed();
statusBar()->showMessage(tr("Resuming..."));
}
}
void Letterbox::timerControl(bool paused)
{
if (paused)
{
m_timer->stop();
}
else if (isInQuiz())
{
m_timer->setSingleShot(true);
m_timer->start(timerLength());
}
}
void Letterbox::markLastAsMissed()
{
ClueResultList::iterator it(m_clueResultsIterator);
(*(--it)).resetStats();
if (!m_pauseAction->isChecked())
{
// reset clock
timerControl(true);
timerControl(false);
}
statusBar()->showMessage(tr("%1 marked as missed.").arg((*it).clue.clueString));
}
void Letterbox::skip()
{
for (WordResultList::iterator it = (*m_clueResultsIterator).words.begin(); it != (*m_clueResultsIterator).words.end(); ++it)
{
(*it).missed = false;
(*it).keystrokes = (*it).word.length();
(*it).time = timerLength();
}
m_mistakeMade = false;
increment();
}
void Letterbox::prepareQuiz()
{
updateViews();
m_submittedAnswers.clear();
m_lineEdit->clear();
m_mistakeMade = false;
if (m_numberIterator == m_clueResults.count())
{
statusBar()->clearMessage();
return;
}
m_lineEdit->setFocus();
m_pauseAction->setChecked(false);
statusBar()->showMessage(tr("Word #%1 of %2 total.").arg(m_numberIterator + 1).arg(m_clueResults.count()));
timerControl(true);
timerControl(false);
m_time.start();
m_keystrokes = 0;
m_pauseMs = 0;
}
int Letterbox::timerLength()
{
if (!isInQuiz())
return 0;
return LetterboxSettings::self()->msecWaitBase + LetterboxSettings::self()->msecWaitExtraPerSolution * (*m_answersIterator).count();
}
void Letterbox::listFinished()
{
writeFile();
statusBar()->showMessage(tr("List is finished, results saved."));
m_timer->stop();
}
bool Letterbox::isInQuiz() const
{
return m_clueResults.size() > 0 && m_numberIterator < m_clueResults.size();
}
void Letterbox::outputResults()
{
QFile file(m_filename);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
{
QMessageBox::critical(this, tr("Error Writing File - Quackle Letterbox"), tr("Could not open %1 for writing.").arg(m_filename));
return;
}
QTextStream stream(&file);
if (m_numberIterator < m_clueResults.count())
stream << "\" Resume: " << m_numberIterator << "\n";
ClueResultList::iterator end = m_clueResults.end();
QStringList::iterator listIt = m_list.begin();
for (ClueResultList::iterator it = m_clueResults.begin(); it != end; ++it)
{
stream << *listIt;
if ((*it).words.count() > 0)
{
stream << " \"";
for (WordResultList::iterator word = (*it).words.begin(); word != (*it).words.end(); ++word)
stream << " " << (*word).word << " " << (*word).time << " " << (*word).keystrokes;
}
stream << "\n";
++listIt;
}
file.close();
if (LetterboxSettings::self()->newMissesFile)
{
QString missesFilename = m_filename + QString("-misses");
QFile missesFile(missesFilename);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
{
QMessageBox::critical(this, tr("Error writing misses file"), tr("Could not open %1 for writing.").arg(missesFilename));
return;
}
QTextStream stream(&missesFile);
for (ClueResultList::iterator it = m_clueResults.begin(); it != m_clueResults.end(); ++it)
{
for (WordResultList::iterator word = (*it).words.begin(); word != (*it).words.end(); ++word)
{
if ((*word).time == 0)
{
stream << (*it).clue.clueString << "\n";
break;
}
}
}
missesFile.close();
}
}
int Letterbox::chewScore(const QString & /*word*/, const QString & /*steal*/)
{
int ret = 0;
/*
int wordpairs[26][26];
for (int i = 0; i < 26; i++)
for (int j = 0; j < 26; j++)
wordpairs[i][j] = 0;
if ((word.length() < 2) || (steal.length() < word.length()))
return 0;
for (unsigned int i = 0; i < word.length() - 1; i++)
{
int a = word.at(i).latin1() - 'A';
int b = word.at(i + 1).latin1() - 'A';
if ((a >= 0) && (a < 26) && (b >= 0) && (b < 26))
wordpairs[a][b]++;
}
for (unsigned int i = 0; i < steal.length() - 1; i++)
{
int a = steal.at(i).latin1() - 'A';
int b = steal.at(i + 1).latin1() - 'A';
if ((a >= 0) && (a < 26) && (b >= 0) && (b < 26))
{
if (wordpairs[a][b] == 0)
ret++;
else
wordpairs[a][b]--;
}
}
*/
return ret;
}
void Letterbox::updateDuringQuery()
{
update();
}
Clue Letterbox::mathClue(const QString &clue)
{
Dict::WordList clueWords = QuackleIO::DictFactory::querier()->query(clue);
QString word = (*clueWords.begin()).word;
int bestChew = 0;
QString ret;
for (int i = 0; i < word.length(); i++)
{
QString query = word.left(i) + word.right(word.length() - i - 1);
Dict::WordList words = QuackleIO::DictFactory::querier()->query(query);
for (Dict::WordList::iterator it = words.begin(); it != words.end(); ++it)
{
int chew = chewScore((*it).word, word);
if (chew > bestChew)
{
bestChew = chew;
ret = (*it).word + " + " + word.at(i);
}
}
}
if (bestChew > 2)
return Clue(ret);
return Clue(arrangeLettersForUser(word));
}
QString Letterbox::alphagram(const QString &word)
{
return QuackleIO::Util::alphagram(word);
}
QString Letterbox::arrangeLettersForUser(const QString &word)
{
return QuackleIO::Util::arrangeLettersForUser(word);
}
Clue Letterbox::clueFor(const QString &word)
{
if (word.isNull())
return Clue(QString::null);
if (LetterboxSettings::self()->mathMode)
return mathClue(word);
return Clue(arrangeLettersForUser(word));
}
Dict::WordList Letterbox::answersFor(const QString &word)
{
Dict::WordList results;
if (word.isNull())
return results;
// no updates during initialization of lists, but with extensions
results = QuackleIO::DictFactory::querier()->query(word, (m_initializationChuu? Dict::Querier::None : Dict::Querier::CallUpdate) | Dict::Querier::WithExtensions);
return results;
}
void Letterbox::mistakeDetector(const QString &text)
{
m_keystrokes++;
QString upperText(text.toUpper());
if (upperText.length() == 0)
return;
for (Dict::WordList::iterator it = (*m_answersIterator).begin(); it != (*m_answersIterator).end(); ++it)
{
if (LetterboxSettings::self()->spaceComplete)
{
if (upperText[0] == ' ')
{
if (!m_submittedAnswers.contains((*it).word))
{
processAnswer((*it).word);
return;
}
}
}
else
{
if ((*it).word.startsWith(upperText) && !m_submittedAnswers.contains((*it).word))
{
if (m_mistakeMade)
statusBar()->clearMessage();
if (LetterboxSettings::self()->autoCompleteLength > 0)
if (upperText.length() == LetterboxSettings::self()->autoCompleteLength)
processAnswer((*it).word);
return;
}
}
}
m_mistakeMade = true;
statusBar()->showMessage(tr("MISTAKE DETECTED!"), /* show for 2 seconds */ 2000);
}
void Letterbox::lineEditReturnPressed()
{
processAnswer(m_lineEdit->text());
}
void Letterbox::processAnswer(const QString &answer)
{
if (!isInQuiz())
return;
QString upperAnswer(answer.toUpper());
if (m_submittedAnswers.contains(upperAnswer) > 0)
{
statusBar()->showMessage(tr("You already submitted %1.").arg(upperAnswer));
m_lineEdit->clear();
return;
}
for (WordResultList::iterator it = (*m_clueResultsIterator).words.begin(); it != (*m_clueResultsIterator).words.end(); ++it)
{
if ((*it).word == upperAnswer)
{
(*it).missed = false;
(*it).keystrokes = m_keystrokes;
(*it).time = m_time.elapsed() - m_pauseMs;
}
}
for (Dict::WordList::iterator it = (*m_answersIterator).begin(); it != (*m_answersIterator).end(); ++it)
{
if ((*it).word == upperAnswer)
{
m_submittedAnswers.append(upperAnswer);
m_lineEdit->clear();
m_mistakeMade = false;
if (m_submittedAnswers.count() >= (*m_answersIterator).count())
{
increment();
return;
}
else
{
statusBar()->showMessage(tr("%1 is correct.").arg(upperAnswer));
m_solutionsView->addSubmission(upperAnswer);
break;
}
}
}
m_lineEdit->clear();
m_keystrokes = 0;
m_pauseMs = 0;
}
void Letterbox::timeout()
{
increment();
}
void Letterbox::updateViews()
{
m_solutionsView->setWords(m_answers.begin(), m_answersIterator);
m_upcomingView->setWords(m_clueResultsIterator, m_clueResults.end());
}
void Letterbox::createMenu()
{
QMenu *file = menuBar()->addMenu(tr("&File"));
QMenu *go = menuBar()->addMenu(tr("&Go"));
QMenu *settings = menuBar()->addMenu(tr("&Settings"));
QMenu *help = menuBar()->addMenu(tr("&Help"));
QAction *generateAction = new QAction(tr("&Generate new list..."), this);
generateAction->setShortcut(tr("Ctrl+L"));
file->addAction(generateAction);
connect(generateAction, SIGNAL(activated()), this, SLOT(generateList()));
QAction *openAction = new QAction(tr("&Open..."), this);
openAction->setShortcut(tr("Ctrl+O"));
file->addAction(openAction);
connect(openAction, SIGNAL(activated()), this, SLOT(open()));
QAction *saveAction = new QAction(tr("&Save"), this);
saveAction->setShortcut(tr("Ctrl+S"));
file->addAction(saveAction);
connect(saveAction, SIGNAL(activated()), this, SLOT(writeFile()));
file->addSeparator();
QAction *printAction = new QAction(tr("&Print"), this);
file->addAction(printAction);
connect(printAction, SIGNAL(activated()), this, SLOT(print()));
QAction *printStudyAction = new QAction(tr("&Print Study Sheet"), this);
file->addAction(printStudyAction);
connect(printStudyAction, SIGNAL(activated()), this, SLOT(printStudy()));
file->addSeparator();
QAction *quitAction = new QAction(tr("&Close"), this);
quitAction->setShortcut(tr("Ctrl+W"));
file->addAction(quitAction);
connect(quitAction, SIGNAL(activated()), this, SLOT(close()));
QAction *jumpAction = new QAction(tr("&Jump to..."), this);
jumpAction->setShortcut(tr("Ctrl+J"));
go->addAction(jumpAction);
connect(jumpAction, SIGNAL(activated()), this, SLOT(jumpTo()));
QAction *markAsMissedAction = new QAction(tr("Mark last as &missed"), this);
markAsMissedAction->setShortcut(tr("Ctrl+M"));
go->addAction(markAsMissedAction);
connect(markAsMissedAction, SIGNAL(activated()), this, SLOT(markLastAsMissed()));
QAction *skipAction = new QAction(tr("S&kip"), this);
skipAction->setShortcut(tr("Ctrl+K"));
go->addAction(skipAction);
connect(skipAction, SIGNAL(activated()), this, SLOT(skip()));
go->addSeparator();
m_pauseAction = new QAction(tr("&Pause"), this);
m_pauseAction->setShortcut(tr("Ctrl+P"));
m_pauseAction->setCheckable(true);
go->addAction(m_pauseAction);
connect(m_pauseAction, SIGNAL(toggled(bool)), this, SLOT(pause(bool)));
QAction *focusAction = new QAction(tr("&Focus entry widget"), this);
focusAction->setShortcut(tr("Ctrl+F"));
go->addAction(focusAction);
connect(focusAction, SIGNAL(activated()), m_lineEdit, SLOT(setFocus()));
settings->addAction(m_preferencesAction);
settings->addSeparator();
QAction *aboutAction = new QAction(tr("&About Letterbox"), this);
help->addAction(aboutAction);
connect(aboutAction, SIGNAL(activated()), this, SLOT(about()));
}
void Letterbox::createWidgets()
{
QWidget *centralWidget = new QWidget(this);
QVBoxLayout *centralLayout = new QVBoxLayout(centralWidget);
setCentralWidget(centralWidget);
WordView *solutionsView = new WordView(centralWidget);
WordView *upcomingView = new WordView(centralWidget);
m_lineEdit = new QLineEdit(centralWidget);
m_lineEdit->setAlignment(Qt::AlignHCenter);
m_lineEdit->setValidator(new InputValidator(m_lineEdit));
connect(m_lineEdit, SIGNAL(returnPressed()), this, SLOT(lineEditReturnPressed()));
connect(m_lineEdit, SIGNAL(textChanged(const QString &)), this, SLOT(mistakeDetector(const QString &)));
centralLayout->addWidget(solutionsView);
centralLayout->addWidget(m_lineEdit);
centralLayout->addWidget(upcomingView);
centralLayout->setStretchFactor(solutionsView, 3);
m_solutionsView = solutionsView;
m_upcomingView = upcomingView;
}
void Letterbox::print()
{
pause(true);
QString filename = QFileDialog::getSaveFileName(this, tr("Choose HTML file to which to save pretty word list"), m_filename + ".html", "*.html");
if (filename.isEmpty())
return;
QFile file(filename);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
{
QMessageBox::critical(this, tr("Error Writing File - Quackle Letterbox"), tr("Could not open %1 for writing.").arg(filename));
return;
}
HTMLRepresentation printer;
bool wasModified = m_modified;
int previousNumber = m_numberIterator;
statusBar()->showMessage(tr("Generating HTML..."));
jumpTo(m_clueResults.size() - 1);
printer.setWords(m_answers.begin(), m_answers.end());
jumpTo(previousNumber);
setModified(wasModified);
QTextStream stream(&file);
stream << printer.html() << "\n";
file.close();
statusBar()->showMessage(tr("%1 written.").arg(filename));
}
void Letterbox::printStudy()
{
pause(true);
QString filename = QFileDialog::getSaveFileName(this, tr("Choose file to which to save study sheet"), m_filename + "-study");
if (filename.isEmpty())
return;
QFile file(filename);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
{
QMessageBox::critical(this, tr("Error Writing File - Quackle Letterbox"), tr("Could not open %1 for writing.").arg(filename));
return;
}
bool wasModified = m_modified;
int previousNumber = m_numberIterator;
statusBar()->showMessage(tr("Generating study sheet..."));
jumpTo(m_clueResults.size() - 1);
QTextStream stream(&file);
stream << generateStudySheet(m_answers.begin(), m_answers.end()) << "\n";
file.close();
jumpTo(previousNumber);
setModified(wasModified);
statusBar()->showMessage(tr("%1 written.").arg(filename));
}
QString Letterbox::generateStudySheet(Dict::WordListList::ConstIterator start, Dict::WordListList::ConstIterator end)
{
QString ret;
int prevLengthOfExtensions = LetterboxSettings::self()->lengthOfExtensions;
LetterboxSettings::self()->lengthOfExtensions = 1;
for (Dict::WordListList::ConstIterator it = start; it != end; ++it)
{
int length = (*it).front().word.length();
QString pad = " ";
for (int i = 0; i < length; ++i)
pad += " ";
ret += arrangeLettersForUser((*it).front().word) + " ";
bool first = true;
for (Dict::WordList::ConstIterator wit = (*it).begin(); wit != (*it).end(); ++wit)
{
if (first)
{
first = false;
}
else
{
ret += pad;
}
ret += HTMLRepresentation::prettyExtensionList((*wit).getFrontExtensionList(), false);
ret += " ";
ret += (*wit).word;
ret += " ";
ret += HTMLRepresentation::prettyExtensionList((*wit).getBackExtensionList(), false);
ret += "\n";
}
}
LetterboxSettings::self()->lengthOfExtensions = prevLengthOfExtensions;
return ret;
}
void Letterbox::about()
{
QMessageBox::about(this, tr("About Quackle Letterbox"), "Letterbox is a lexical study tool, that is now part of Quackle.
Copyright 2005-2007 by
- John O'Laughlin <olaughlin@gmail.com>
- Jason Katz-Brown <jasonkatzbrown@gmail.com>
");
}
void Letterbox::saveSettings()
{
LetterboxSettings::self()->writeSettings();
CustomQSettings settings;
settings.setValue("quackle/letterbox/most-recent-list", m_filename);
settings.setValue("quackle/letterbox/window-size", size());
}
void Letterbox::loadSettings()
{
LetterboxSettings::self()->readSettings();
CustomQSettings settings;
m_filename = settings.value("quackle/letterbox/most-recent-list", QString("")).toString();
resize(settings.value("quackle/letterbox/window-size", QSize(800, 600)).toSize());
}
//////////
WordResult::WordResult()
{
resetStats();
}
WordResult::WordResult(QString w)
{
word = w;
resetStats();
}
void WordResult::resetStats()
{
missed = true;
keystrokes = 0;
time = 0;
}
//////////
ClueResult::ClueResult()
{
}
ClueResult::ClueResult(const QString &newClue)
: clue(newClue)
{
}
void ClueResult::setWordList(Dict::WordList answers)
{
for (Dict::WordList::iterator it = answers.begin(); it != answers.end(); ++it)
{
bool isAnswerInOurWords = false;
for (WordResultList::iterator wrIt = words.begin(); wrIt != words.end(); ++wrIt)
{
if ((*it).word == (*wrIt).word)
{
isAnswerInOurWords = true;
break;
}
}
if (!isAnswerInOurWords)
words.append(WordResult((*it).word));
}
}
void ClueResult::resetStats()
{
for (WordResultList::iterator wrIt = words.begin(); wrIt != words.end(); ++wrIt)
(*wrIt).resetStats();
}
Clue::Clue()
{
}
Clue::Clue(const QString &newClueString)
: clueString(newClueString)
{
}
//////////
InputValidator::InputValidator(QObject *parent) : QValidator(parent)
{
}
InputValidator::~InputValidator()
{
}
QValidator::State InputValidator::validate(QString & /* input */, int &) const
{
return Acceptable;
}
/////////
WordView::WordView(QWidget *parent)
: QTextEdit(parent)
{
m_shownSolutions = 16;
m_shownClues = 30;
m_tablePadding = 0;
m_tableSpacing = 0;
setReadOnly(true);
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
// TODO(jasonkb) background/foreground colors
//setPaper(QBrush(QColor(LetterboxSettings::self()->backgroundColor)));
//setColor(LetterboxSettings::self()->foregroundColor);
}
WordView::~WordView()
{
}
void WordView::htmlUpdated(ContentType type)
{
setText(html());
switch (type)
{
case Content_Upcoming:
scrollToAnchor("top");
break;
case Content_Solutions:
QTextCursor cursor = textCursor();
cursor.movePosition(QTextCursor::End);
setTextCursor(cursor);
ensureCursorVisible();
break;
}
}
////////
HTMLRepresentation::HTMLRepresentation()
: m_shownSolutions(INT_MAX), m_shownClues(INT_MAX), m_tablePadding(4), m_tableSpacing(0)
{
}
HTMLRepresentation::~HTMLRepresentation()
{
}
void HTMLRepresentation::htmlUpdated(ContentType /* type */ )
{
}
void HTMLRepresentation::setHTML(const QString &text, ContentType type)
{
m_html = text;
htmlUpdated(type);
}
QString HTMLRepresentation::html()
{
return m_html;
}
void HTMLRepresentation::setWords(ClueResultList::ConstIterator start, ClueResultList::ConstIterator end, bool revers)
{
QString html("");
int shown = 0;
html += "";
if (revers)
{
ClueResultList::ConstIterator it = end;
while (shown < m_shownClues)
{
if (it == start)
break;
--it;
if (shown == 0)
html += "";
else
html += "";
html += htmlForPlainWord((*it).clue.clueString) + "
";
html += "";
shown++;
}
}
else
{
for (ClueResultList::ConstIterator it = start; (it != end) && (shown < 30); ++it)
{
if (shown == 0)
html += "";
else
html += "";
html += htmlForPlainWord((*it).clue.clueString) + "
";
if (shown == 0)
html += "";
shown++;
}
}
html += "";
setHTML(html, Content_Upcoming);
}
void HTMLRepresentation::setWords(Dict::WordListList::ConstIterator start, Dict::WordListList::ConstIterator end, bool revers)
{
Dict::WordListList::ConstIterator newStart = end;
for (int i = 0; i < m_shownSolutions; i++)
if (newStart != start)
newStart--;
start = newStart;
QString html("");
if (revers)
{
Dict::WordListList::ConstIterator it = end;
while (true)
{
if (it == start)
break;
--it;
html += htmlForWordList(*it);
}
}
else
{
for (Dict::WordListList::ConstIterator it = start; it != end; ++it)
html += htmlForWordList(*it);
}
setHTML(html, Content_Solutions);
}
QString HTMLRepresentation::htmlForWordList(const Dict::WordList &wordList)
{
if (wordList.isEmpty())
return QString::null;
QString html = QString("").arg(m_tableSpacing).arg(m_tablePadding);
bool hasFrontExtensionColumn = false;
bool hasBackExtensionColumn = false;
Dict::WordList::ConstIterator end = wordList.end();
for (Dict::WordList::ConstIterator it = wordList.begin(); it != end; ++it)
{
if (!(tableOfExtensions((*it).getFrontExtensionList()).isEmpty()))
hasFrontExtensionColumn = true;
if (!(tableOfExtensions((*it).getBackExtensionList()).isEmpty()))
hasBackExtensionColumn = true;
}
for (Dict::WordList::ConstIterator it = wordList.begin(); it != end; ++it)
{
html += "";
if (hasFrontExtensionColumn)
html += "" + tableOfExtensions((*it).getFrontExtensionList()) + " | ";
html += "" + htmlForSolution(*it) + " | ";
if (hasBackExtensionColumn)
html += "" + tableOfExtensions((*it).getBackExtensionList()) + " | ";
html += "
";
}
html += "
\n";
return html;
}
QString HTMLRepresentation::htmlForPlainWord(const QString &word)
{
QString fontArgs("color=\"%1\"");
QString html("foregroundColor) + ">%1");
return html.arg(word);
}
QString HTMLRepresentation::htmlForSolution(const Dict::Word &word)
{
QString fontArgs("size=\"+5\" color=\"%1\"");
QString html("sowpodsColor : LetterboxSettings::self()->foregroundColor) + ">%1");
return html.arg(word.word + (word.british? "#" : ""));
}
QString HTMLRepresentation::tableOfExtensions(const Dict::ExtensionList &list)
{
QString html;
bool first = true;
for (int i = 1; i <= LetterboxSettings::self()->lengthOfExtensions; ++i)
{
Dict::ExtensionList extensions(Dict::Word::extensionsByLength(i, list));
if (extensions.count() > 0)
{
QString extensionHtml = HTMLRepresentation::prettyExtensionList(extensions);
if (!extensionHtml.isEmpty())
{
if (!first)
html += "
";
else
first = false;
if (i == 1)
{
html += "";
}
else
{
html += "";
}
html += htmlForPlainWord(extensionHtml);
html += "";
}
}
}
return html;
}
void HTMLRepresentation::addSubmission(const QString &submission)
{
QString newHtml(html());
QString item(QString("%1").arg(submission));
if (newHtml.endsWith("
"))
newHtml.insert(newHtml.length() - 4, QString("
") + item);
else
newHtml += QString("%1
%2
").arg(qApp->tr("Correct Answers")).arg(item);
setHTML(newHtml, Content_Solutions);
}
QString HTMLRepresentation::prettyExtensionList(const Dict::ExtensionList &list, bool make_html)
{
QString ret;
int extensionChars = 0;
QString space = make_html? " " : " ";
for (Dict::ExtensionList::ConstIterator it = list.begin(); it != list.end(); ++it)
{
QString fontArgs("color=\"%1\"");
QString color = (*it).british? LetterboxSettings::self()->sowpodsColor : LetterboxSettings::self()->foregroundColor;
QString html;
if (make_html)
html += "%1";
else
html += "%1";
QString wordHtml;
if (LetterboxSettings::self()->numExtensionChars > 0)
{
if (extensionChars >= LetterboxSettings::self()->numExtensionChars)
{
int numExtensions = 0;
numExtensions = list.size();
ret += QString("...%1(%2)").arg(space).arg(numExtensions);
return ret;
}
}
wordHtml += (*it).word;
extensionChars += (*it).word.length();
// this is super clumsy, working around the fact that it doesn't show leading nbsps
bool displayableTokensRemain = false;
for (Dict::ExtensionList::ConstIterator lookAhead = it; lookAhead != list.end(); ++lookAhead)
{
if (lookAhead == it)
continue;
displayableTokensRemain = true;
}
if (displayableTokensRemain)
wordHtml += space;
ret += html.arg(wordHtml);
if (!displayableTokensRemain)
return ret;
}
return ret;
}
QString Letterbox::getInitialDirectory() const
{
return m_initialDirectory.isEmpty()? QDir::homePath() : m_initialDirectory;
}
void Letterbox::setInitialDirectory(const QString &filename)
{
QFileInfo file(filename);
m_initialDirectory = file.path();
}
QString Letterbox::studyListFileFilters() const
{
return tr("Letterbox files (%1);;All files (*)").arg("*.letterbox");
}
QString Letterbox::defaultStudyListFileFilter() const
{
return "*.letterbox";
}