/* * Quackle -- Crossword game artificial intelligence and analysis tool * Copyright (C) 2005-2019 Jason Katz-Brown, John O'Laughlin, and John Fultz. * * 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 3 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, see . */ #include using namespace std; #include #include #include "lister.h" #include "customqsettings.h" ListerDialog::ListerDialog(QWidget *parent, const QString &settingsGroup, const QString &appName, int flags) : QDialog(parent), m_settingsGroup(settingsGroup), m_appName(appName), m_flags(flags) { setWindowTitle(windowTitleWithAppName(tr("Lister"))); QVBoxLayout *vbox = new QVBoxLayout(this); QHBoxLayout *mainHorizontalLayout = new QHBoxLayout; vbox->addLayout(mainHorizontalLayout); QVBoxLayout *leftSideLayout = new QVBoxLayout; mainHorizontalLayout->addLayout(leftSideLayout); QVBoxLayout *rightSideLayout = new QVBoxLayout; mainHorizontalLayout->addLayout(rightSideLayout); mainHorizontalLayout->setStretchFactor(leftSideLayout, 3); // left side QHBoxLayout *queryLayout = new QHBoxLayout; leftSideLayout->addLayout(queryLayout); m_queryEdit = new QLineEdit; connect(m_queryEdit, SIGNAL(returnPressed()), this, SLOT(queryGo())); queryLayout->addWidget(m_queryEdit); m_queryButton = new QPushButton(tr("A-Z?* Query")); queryLayout->addWidget(m_queryButton); connect(m_queryButton, SIGNAL(clicked()), this, SLOT(queryGo())); QHBoxLayout *smallHLayout = new QHBoxLayout; leftSideLayout->addLayout(smallHLayout); m_numResultsLabel = new QLabel; smallHLayout->addWidget(m_numResultsLabel); smallHLayout->addStretch(); m_buildChecker = new QCheckBox(tr("Buil&d")); smallHLayout->addWidget(m_buildChecker); m_sowpodsChecker = new QCheckBox(tr("Remove &British")); connect(m_sowpodsChecker, SIGNAL(toggled(bool)), this, SLOT(setRemoveSowpods(bool))); if (!(m_flags & IgnoreBritishness)) smallHLayout->addWidget(m_sowpodsChecker); m_listBox = new QListWidget; leftSideLayout->addWidget(m_listBox); m_numResultsLabel->setBuddy(m_listBox); // right side QLabel *filtersLabel = new QLabel(tr("&Filters:")); rightSideLayout->addWidget(filtersLabel); m_filtersBox = new QListWidget; rightSideLayout->addWidget(m_filtersBox); connect(m_filtersBox, SIGNAL(itemClicked(QListWidgetItem *)), this, SLOT(showFilter(QListWidgetItem *))); filtersLabel->setBuddy(m_filtersBox); QStringList filters; if (m_flags & ProbabilityInsteadOfPlayability) filters << "Probability"; else filters << "Playability"; filters << "Regex"; filters << "Num. Anagrams"; if (!(m_flags & IgnoreBritishness)) filters << "Keep British"; m_filtersBox->addItems(filters); m_filtersLayout = new QVBoxLayout; rightSideLayout->addLayout(m_filtersLayout); m_currentFilter = 0; m_applyButton = new QPushButton(tr("&Apply")); rightSideLayout->addWidget(m_applyButton); // bottom QHBoxLayout *filenameLayout = new QHBoxLayout; vbox->addLayout(filenameLayout); QPushButton *filenameButton = new QPushButton(tr("Pic&k filename...")); filenameLayout->addWidget(filenameButton); connect(filenameButton, SIGNAL(clicked()), this, SLOT(chooseFilename())); m_filenameEdit = new QLineEdit; filenameLayout->addWidget(m_filenameEdit); QHBoxLayout *buttonBox = new QHBoxLayout; vbox->addLayout(buttonBox); QHBoxLayout *buttonBox2 = new QHBoxLayout; vbox->addLayout(buttonBox2); m_writeButton = new QPushButton(tr("&Write alphagram file")); m_writeNormalButton = new QPushButton(tr("&Write normal file")); m_studyThisButton = new QPushButton(tr("Write and &Study")); QPushButton *openButton = new QPushButton(tr("&Open File...")); QPushButton *clearButton = new QPushButton(tr("C&lear")); m_closeButton = new QPushButton(tr("&Close")); buttonBox->addWidget(m_writeButton); buttonBox->addWidget(m_writeNormalButton); if (!(m_flags & NothingToReturn)) buttonBox->addWidget(m_studyThisButton); buttonBox->addWidget(openButton); buttonBox2->addWidget(clearButton); buttonBox2->addStretch(); buttonBox2->addWidget(m_closeButton); filenameChanged(QString::null); // disable write button initially connect(m_filenameEdit, SIGNAL(textChanged(const QString &)), this, SLOT(filenameChanged(const QString &))); connect(m_writeButton, SIGNAL(clicked()), this, SLOT(writeButtonClicked())); connect(m_writeNormalButton, SIGNAL(clicked()), this, SLOT(writeNormalButtonClicked())); connect(m_studyThisButton, SIGNAL(clicked()), this, SLOT(studyButtonClicked())); connect(m_closeButton, SIGNAL(clicked()), this, SLOT(accept())); connect(clearButton, SIGNAL(clicked()), this, SLOT(clear())); connect(openButton, SIGNAL(clicked()), this, SLOT(openFile())); showFilter(filters.first()); //m_filtersBox->setCurrentItem(0); clear(); } ListerDialog::~ListerDialog() { saveSettings(); } void ListerDialog::resetFocus() { // Qt has some quirks so we're thorough m_closeButton->clearFocus(); m_queryButton->setFocus(); m_queryEdit->setFocus(); } void ListerDialog::setQuery(const QString &query) { m_queryEdit->setText(query); m_queryEdit->selectAll(); resetFocus(); } void ListerDialog::queryGo() { if (m_queryEdit->text().length() >= 40) { QMessageBox::warning(this, tr("Overlong Query - Quackle"), QString("%1").arg(tr("Queries, arbitrarily, cannot exceed 40 letters."))); return; } m_wordList = QuackleIO::DictFactory::querier()->query(m_queryEdit->text(), m_buildChecker->isChecked()? Dict::Querier::NoRequireAllLetters : Dict::Querier::None); setRemoveSowpods(m_sowpodsChecker->isChecked()); populateListBox(); // per Robin's request m_queryEdit->deselect(); } void ListerDialog::chooseFilename() { QString filename = QFileDialog::getSaveFileName(this, tr("Choose file to save list to"), m_filenameEdit->text()); if (!filename.isEmpty()) m_filenameEdit->setText(filename); } void ListerDialog::filenameChanged(const QString &filename) { m_studyThisButton->setEnabled(!filename.isEmpty()); m_writeButton->setEnabled(!filename.isEmpty()); m_writeNormalButton->setEnabled(!filename.isEmpty()); m_filename = filename; } void ListerDialog::clear() { m_wordList.clear(); populateListBox(); m_queryEdit->clear(); // do not remove this fatuous line; otherwise Close button gets // mysterious focus? m_queryButton->setFocus(); m_queryEdit->setFocus(); } void ListerDialog::openFile() { QString filename = QFileDialog::getOpenFileName(this, tr("Choose list to open")); if (filename.isEmpty()) return; m_wordList.clear(); QFile file(filename); if (file.open(QIODevice::ReadOnly | QIODevice::Text)) { QTextStream stream(&file); stream.setCodec(QTextCodec::codecForName("UTF-8")); QString line; while (!stream.atEnd()) { line = stream.readLine(); int quoteMarkIndex = line.indexOf("\""); if (quoteMarkIndex >= 0) line = line.left(quoteMarkIndex).trimmed(); QStringList words(line.split(" ")); for (const auto& it : words) { bool found = false; Dict::WordList results(QuackleIO::DictFactory::querier()->query(it)); for (const auto& resultsIt : results) { if (resultsIt.word == it) { m_wordList.append(resultsIt); found = true; break; } } if (found) continue; m_wordList += results; } } file.close(); } setWordList(m_wordList); } void ListerDialog::showFilter(const QString &filterName) { Filter *newFilter = 0; if (filterName == "Playability" || filterName == "Probability") newFilter = new PlayabilityFilter(this); else if (filterName == "Regex") newFilter = new RegexFilter(this); else if (filterName == "Num. Anagrams") newFilter = new NumAnagramsFilter(this); else if (filterName == "Keep British") newFilter = new KeepBritishFilter(this); if (m_currentFilter) saveSettings(); delete m_currentFilter; m_currentFilter = newFilter; if (!m_currentFilter) { m_applyButton->setEnabled(false); return; } m_filtersLayout->addWidget(m_currentFilter); loadSettings(); m_currentFilter->show(); m_applyButton->setEnabled(true); connect(m_applyButton, SIGNAL(clicked()), m_currentFilter, SLOT(apply())); } void ListerDialog::showFilter(QListWidgetItem *item) { showFilter(item->text()); } void ListerDialog::accept() { saveSettings(); resetFocus(); reject(); } QString ListerDialog::run(QWidget *parent, const QString &settingsGroup, const QString &appName, int flags) { ListerDialog dialog(parent, settingsGroup, appName, flags); bool accepted = (dialog.exec() == QDialog::Accepted); if (accepted) return dialog.filename(); return QString::null; } void ListerDialog::saveSettings() { CustomQSettings settings; settings.beginGroup(m_settingsGroup); if (m_currentFilter) m_currentFilter->saveSettings(&settings); settings.setValue("sowpods", m_sowpodsChecker->isChecked()); settings.setValue("build", m_buildChecker->isChecked()); } void ListerDialog::loadSettings() { CustomQSettings settings; settings.beginGroup(m_settingsGroup); if (m_currentFilter) m_currentFilter->loadSettings(&settings); m_sowpodsChecker->setChecked(settings.value("sowpods", false).toBool()); m_buildChecker->setChecked(settings.value("build", false).toBool()); } void ListerDialog::setRemoveSowpods(bool removeSowpods) { if (!removeSowpods) return; Dict::WordList filteredWords; Dict::WordList::Iterator end = m_wordList.end(); for (Dict::WordList::Iterator it = m_wordList.begin(); it != end; ++it) { if (!(*it).british) filteredWords.append((*it)); } m_wordList = filteredWords; populateListBox(); } void ListerDialog::writeButtonClicked() { // alphagrams writeList(true); } void ListerDialog::writeNormalButtonClicked() { // not alphagrams writeList(false); } void ListerDialog::studyButtonClicked() { // alphagrams writeList(true); saveSettings(); accept(); } void ListerDialog::populateListBox() { QStringList words; for (const auto& it : m_wordList) words.append(it.word + (it.british? "#" : "")); m_listBox->clear(); m_listBox->addItems(words); m_numResultsLabel->setText(tr("%1 &results").arg(words.count())); } QMap ListerDialog::anagramMap() { QMap anagramSets; for (const auto& it : m_wordList) { QString alpha = QuackleIO::DictFactory::querier()->alphagram(it.word); anagramSets[alpha].append(it); } return anagramSets; } QString ListerDialog::writeList(bool alphagrams) { if (m_filename.isEmpty()) return QString::null; QFile file(m_filename); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { QMessageBox::critical(this, windowTitleWithAppName(tr("Error writing file")), tr("Could not open %1 for writing.").arg(m_filename)); return QString::null; } QTextStream stream(&file); stream.setCodec(QTextCodec::codecForName("UTF-8")); QMap map(anagramMap()); for (const auto& it : m_wordList) { if (alphagrams) { QString alphagram(QuackleIO::DictFactory::querier()->alphagram(it.word)); if (map.contains(alphagram)) { stream << alphagram << "\n"; map.remove(alphagram); } } else stream << it.word << "\n"; } file.close(); return m_filename; } Dict::WordList &ListerDialog::wordList() { return m_wordList; } void ListerDialog::setWordList(Dict::WordList list) { m_wordList = list; setRemoveSowpods(m_sowpodsChecker->isChecked()); populateListBox(); } QSpinBox *ListerDialog::makeSpinBox(int minimum, int maximum, int singleStep) { QSpinBox *ret = new QSpinBox; ret->setMinimum(minimum); ret->setMaximum(maximum); ret->setSingleStep(singleStep); return ret; } QString ListerDialog::windowTitleWithAppName(const QString &windowTitle) { if (m_appName.isEmpty()) return windowTitle; else return windowTitle + QString(" - %1").arg(m_appName); } /////////////// Filter::Filter(ListerDialog *dialog) : QFrame(dialog), m_dialog(dialog) { m_vbox = new QVBoxLayout(this); setFrameStyle(QFrame::Panel | QFrame::Raised); setLineWidth(2); } void Filter::apply() { } void Filter::saveSettings(QSettings *) { } void Filter::loadSettings(QSettings *) { } /////////////// PlayabilityFilter::PlayabilityFilter(ListerDialog *dialog) : Filter(dialog) { QHBoxLayout *minRankLayout = new QHBoxLayout; m_vbox->addLayout(minRankLayout); QLabel *minRankLabel = new QLabel(tr("&Minimum rank:")); minRankLayout->addWidget(minRankLabel); m_minRankSpinner = ListerDialog::makeSpinBox(0, 99999, 100); m_minRankSpinner->setSpecialValueText(tr("none")); minRankLabel->setBuddy(m_minRankSpinner); minRankLayout->addWidget(m_minRankSpinner); QHBoxLayout *maxRankLayout = new QHBoxLayout; m_vbox->addLayout(maxRankLayout); QLabel *maxRankLabel = new QLabel(tr("Ma&ximum rank:")); maxRankLayout->addWidget(maxRankLabel); m_maxRankSpinner = ListerDialog::makeSpinBox(0, 99999, 100); m_maxRankSpinner->setSpecialValueText(tr("none")); maxRankLabel->setBuddy(m_maxRankSpinner); maxRankLayout->addWidget(m_maxRankSpinner); } void PlayabilityFilter::apply() { int minimumRank = m_minRankSpinner->value(); int maximumRank = m_maxRankSpinner->value(); if (maximumRank == 0) maximumRank = INT_MAX; const bool useProb = m_dialog->flags() & ListerDialog::ProbabilityInsteadOfPlayability; QMap map(m_dialog->anagramMap()); Dict::WordList intermediateList; QMap::Iterator end = map.end(); for (QMap::Iterator it = map.begin(); it != end; ++it) { Dict::Word newEntry; newEntry.word = it.key(); for (const auto& extIt : it.value()) { if (useProb) newEntry.probability += extIt.probability; else newEntry.playability += extIt.playability; } intermediateList.append(newEntry); } int index = 0; if (useProb) intermediateList.setSortBy(Dict::WordList::Probability); else intermediateList.setSortBy(Dict::WordList::Playability); qSort(intermediateList); Dict::WordList filteredList; for (const auto& it : intermediateList) { ++index; if (index > maximumRank) break; if (index < minimumRank) continue; filteredList += map[it.word]; } m_dialog->setWordList(filteredList); } void PlayabilityFilter::saveSettings(QSettings *settings) { settings->setValue("playabilityfilter/minRank", m_minRankSpinner->value()); settings->setValue("playabilityfilter/maxRank", m_maxRankSpinner->value()); } void PlayabilityFilter::loadSettings(QSettings *settings) { m_minRankSpinner->setValue(settings->value("playabilityfilter/minRank", 0).toInt()); m_maxRankSpinner->setValue(settings->value("playabilityfilter/maxRank", 0).toInt()); } /////////////// RegexFilter::RegexFilter(ListerDialog *dialog) : Filter(dialog) { m_lineEdit = new QLineEdit; m_vbox->addWidget(m_lineEdit); connect(m_lineEdit, SIGNAL(returnPressed()), this, SLOT(apply())); } void RegexFilter::apply() { QRegExp regexp(m_lineEdit->text()); regexp.setCaseSensitivity(Qt::CaseInsensitive); Dict::WordList filteredList; const Dict::WordList &list = m_dialog->wordList();; for (const auto& it : list) if (regexp.indexIn(it.word) >= 0) filteredList.append(it); m_dialog->setWordList(filteredList); } //////////////////// NumAnagramsFilter::NumAnagramsFilter(ListerDialog *dialog) : Filter(dialog) { QHBoxLayout *twlAnagramsLayout = new QHBoxLayout; m_vbox->addLayout(twlAnagramsLayout); QLabel *twlAnagramsLabel = new QLabel(tr("Number of &TWL anagrams:")); twlAnagramsLayout->addWidget(twlAnagramsLabel); m_twlAnagramsSpinner = ListerDialog::makeSpinBox(0, 15, 1); twlAnagramsLabel->setBuddy(m_twlAnagramsSpinner); twlAnagramsLayout->addWidget(m_twlAnagramsSpinner); QHBoxLayout *oswOnlyAnagramsLayout = new QHBoxLayout; QLabel *oswOnlyAnagramsLabel = new QLabel(tr("Number of &OSW-only anagrams:")); oswOnlyAnagramsLayout->addWidget(oswOnlyAnagramsLabel); m_oswOnlyAnagramsSpinner = ListerDialog::makeSpinBox(0, 15, 1); oswOnlyAnagramsLabel->setBuddy(m_oswOnlyAnagramsSpinner); oswOnlyAnagramsLayout->addWidget(m_oswOnlyAnagramsSpinner); if (!(m_dialog->flags() & ListerDialog::IgnoreBritishness)) m_vbox->addLayout(oswOnlyAnagramsLayout); } void NumAnagramsFilter::apply() { int numTwlAnagrams = m_twlAnagramsSpinner->value(); int numOswOnlyAnagrams = m_oswOnlyAnagramsSpinner->value(); Dict::WordList filteredList; QMap map(m_dialog->anagramMap()); for (const auto& it : m_dialog->wordList()) { int twl = 0; int british = 0; QString alphagram(QuackleIO::DictFactory::querier()->alphagram(it.word)); for (const auto& word : map[alphagram]) { if (word.british) british++; else twl++; } if ((twl == numTwlAnagrams) && (british == numOswOnlyAnagrams)) filteredList.append(it); } m_dialog->setWordList(filteredList); } void NumAnagramsFilter::saveSettings(QSettings *settings) { settings->setValue("numanagramsfilter/twlAnagrams", m_twlAnagramsSpinner->value()); settings->setValue("numanagramsfilter/oswOnlyAnagrams", m_oswOnlyAnagramsSpinner->value()); } void NumAnagramsFilter::loadSettings(QSettings *settings) { m_twlAnagramsSpinner->setValue(settings->value("numanagramsfilter/twlAnagrams", 1).toInt()); m_oswOnlyAnagramsSpinner->setValue(settings->value("numanagramsfilter/oswOnlyAnagrams", 0).toInt()); } //////////////////// KeepBritishFilter::KeepBritishFilter(ListerDialog *dialog) : Filter(dialog) { } void KeepBritishFilter::apply() { Dict::WordList filteredList; const Dict::WordList &list = m_dialog->wordList();; for (const auto& it : list) if (it.british) filteredList.append(it); m_dialog->setWordList(filteredList); }