From 9306cb60c32082c5403931de0823a9fd5daa196c Mon Sep 17 00:00:00 2001 From: Jason Katz-Brown Date: Sun, 25 Aug 2013 02:17:13 -0700 Subject: Initial git commit. --- quacker/letterbox.cpp | 1449 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1449 insertions(+) create mode 100644 quacker/letterbox.cpp (limited to 'quacker/letterbox.cpp') diff --git a/quacker/letterbox.cpp b/quacker/letterbox.cpp new file mode 100644 index 0000000..e07aa0a --- /dev/null +++ b/quacker/letterbox.cpp @@ -0,0 +1,1449 @@ +/* + * 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 += ""; + + html += ""; + + if (hasBackExtensionColumn) + html += ""; + + html += ""; + } + html += "
" + tableOfExtensions((*it).getFrontExtensionList()) + "" + htmlForSolution(*it) + "" + tableOfExtensions((*it).getBackExtensionList()) + "

\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"; +} + -- cgit v1.2.3