/***************************************************************************** * Eliot * Copyright (C) 2010-2012 Olivier Teulière * Authors: Olivier Teulière * * 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 St, Fifth Floor, Boston, MA 02110-1301 USA *****************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include "dic_wizard.h" #include "qtcommon.h" #include "compdic.h" #include "dic.h" #include "header.h" #include "dic_exception.h" #include "debug.h" using namespace std; INIT_LOGGER(qt, DicWizard); INIT_LOGGER(qt, WizardInfoPage); INIT_LOGGER(qt, WizardLettersDefPage); INIT_LOGGER(qt, WizardConclusionPage); // ---------- WizardInfoPage ---------- WizardInfoPage::WizardInfoPage(QWidget *parent) : QWizardPage(parent) { setupUi(this); // Define the labels properly setTitle(_q("General information")); setSubTitle(_q("On this page, you can define the main information " "needed to create a new dictionary.")); labelDicNameDesc->setText(_q("Choose a dictionary name. This name will " "appear in Eliot status bar when the " "dictionary is loaded.\nE.g.: My Dic 1.0")); labelGenDicDesc->setText(_q("Choose the output file. This file will be " "generated by the wizard, and will contain " "the compressed dictionary.\n" "It must have the .dawg extension.")); labelWordListDesc->setText(_q("Choose the file containing the word list.\n" "It must be encoded in UTF-8, and must " "contain one word on each line.")); // Handle the Browse buttons connect(buttonBrowseGenDic, SIGNAL(clicked(bool)), this, SLOT(onBrowseGenDicClicked())); connect(buttonBrowseWordList, SIGNAL(clicked(bool)), this, SLOT(onBrowseWordListClicked())); // Connection needed for proper calls to the isComplete() method connect(editGenDic, SIGNAL(textChanged(const QString&)), this, SIGNAL(completeChanged())); // Register fields and make them mandatory registerField("dicName*", editDicName); registerField("genDic*", editGenDic); registerField("wordList*", editWordList); // Auto-completion on the paths QCompleter *completer1 = new QCompleter(this); QFileSystemModel *model1 = new QFileSystemModel(this); model1->setRootPath(QDir::currentPath()); completer1->setModel(model1); editGenDic->setCompleter(completer1); QCompleter *completer2 = new QCompleter(this); QFileSystemModel *model2 = new QFileSystemModel(this); model2->setRootPath(QDir::currentPath()); completer2->setModel(model2); editWordList->setCompleter(completer2); } bool WizardInfoPage::isComplete() const { if (!QWizardPage::isComplete()) return false; // Make sure the word list file exists if (!QFile(editWordList->text()).exists()) return false; // Make sure the generated file has the .dawg extension return editGenDic->text().endsWith(".dawg"); } bool WizardInfoPage::validatePage() { // Parse the file to get all the characters QFile file(editWordList->text()); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) return false; QSet words; QMap lettersWithLine; int lineNb = 1; QTextStream in(&file); // Set the right codec, to avoid potential problems with the BOM in.setCodec("UTF-8"); while (!in.atEnd()) { QString line = in.readLine().toUpper(); words.insert(line); for (int i = 0; i < line.size(); ++i) { if (!lettersWithLine.contains(line[i])) lettersWithLine.insert(line[i], lineNb); } ++lineNb; } // Copy the bad chars (i.e. non letters) to a list QMap::const_iterator it; QList badChars; for (it = lettersWithLine.begin(); it != lettersWithLine.end(); ++it) { if (!it.key().isLetter()) badChars.push_back(it.key()); } // If the list is not empty, then the word list is invalid if (!badChars.empty()) { QString msg = _q("Some invalid (non-alphabetical) characters have " "been found in the word list. They are indicated " "below, with the first line on which they were found:"); foreach (QChar ch, badChars) { QString letterMsg = "\n\t" + _q("'%1' (ASCII code %2) at line %3"); msg += letterMsg.arg(ch).arg((int)ch.toLatin1()).arg(lettersWithLine[ch]); } emit notifyProblem(msg + "\n\n" + _q("Please correct the word list.")); return false; } // Detect duplicate entries in the word list if (words.size() != lineNb - 1) { QString msg = _q("The word list contains duplicate entries."); emit notifyProblem(msg + "\n\n" + _q("Please correct the word list.")); return false; } return true; } void WizardInfoPage::onBrowseGenDicClicked() { QString fileName = QFileDialog::getSaveFileName(this, _q("Choose a file for the generated dictionary"), "", "*.dawg"); if (fileName != "") { if (!fileName.endsWith(".dawg")) fileName += ".dawg"; editGenDic->setText(fileName); } } void WizardInfoPage::onBrowseWordListClicked() { QString fileName = QFileDialog::getOpenFileName(this, _q("Choose a word list file")); if (fileName != "") editWordList->setText(fileName); } // ---------- WizardLettersDefPage ---------- WizardLettersDefPage::WizardLettersDefPage(const Dictionary *iCurrDic, QWidget *parent) : QWizardPage(parent), m_currDic(iCurrDic) { setupUi(this); setTitle(_q("Letters characteristics")); labelDesc->setText(_q("The table below lists all the letters found in the word list (plus the joker). " "For each letter, you need to define:\n" " - its value (number of points);\n" " - its frequency (number of occurrences in the game);\n" " - whether the letter can be considered as a vowel;\n" " - whether the letter can be considered as a consonant.\n" "\n" "Note that a letter can be considered both as a vowel and as a consonant. " "This is usually the case for the joker and, in French, for the Y letter.")); // Create the model m_model = new QStandardItemModel(0, 5, this); m_model->setHeaderData(0, Qt::Horizontal, _q("Letter"), Qt::DisplayRole); m_model->setHeaderData(1, Qt::Horizontal, _q("Points"), Qt::DisplayRole); m_model->setHeaderData(2, Qt::Horizontal, _q("Frequency"), Qt::DisplayRole); m_model->setHeaderData(3, Qt::Horizontal, _q("Vowel?"), Qt::DisplayRole); m_model->setHeaderData(4, Qt::Horizontal, _q("Consonant?"), Qt::DisplayRole); treeLetters->setModel(m_model); treeLetters->header()->setDefaultAlignment(Qt::AlignCenter); treeLetters->setItemDelegate(new LettersDelegate); connect(buttonLoadLetters, SIGNAL(clicked(bool)), this, SLOT(loadLettersFromWordList())); connect(buttonLoadFromDic, SIGNAL(clicked(bool)), this, SLOT(loadValuesFromDic())); } void WizardLettersDefPage::loadLettersFromWordList() { // Parse the file to get all the letters QFile file(field("wordList").toString()); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) return; QSet fileLetters; QTextStream in(&file); // Set the right codec, to avoid potential problems with the BOM in.setCodec("UTF-8"); while (!in.atEnd()) { QString line = in.readLine().toUpper(); for (int i = 0; i < line.size(); ++i) { fileLetters.insert(line[i]); } } // Sort the letters alphabetically if possible QList sortedLetters = QList::fromSet(fileLetters); qSort(sortedLetters); // Rebuild the model m_model->removeRows(0, m_model->rowCount()); foreach (QChar ch, sortedLetters) { const int rowNum = m_model->rowCount(); bool res = m_model->insertRow(rowNum); if (!res) return; m_model->setData(m_model->index(rowNum, 0), ch); m_model->setData(m_model->index(rowNum, 1), 1); m_model->setData(m_model->index(rowNum, 2), 1); m_model->setData(m_model->index(rowNum, 3), (bool)QString("AEIOUY").contains(ch)); m_model->setData(m_model->index(rowNum, 4), !(bool)QString("AEIOU").contains(ch)); } // Add another line for the joker int rowNum = m_model->rowCount(); bool res = m_model->insertRow(rowNum); if (!res) return; m_model->setData(m_model->index(rowNum, 0), QChar('?')); m_model->setData(m_model->index(rowNum, 1), 0); m_model->setData(m_model->index(rowNum, 2), 2); m_model->setData(m_model->index(rowNum, 3), true); m_model->setData(m_model->index(rowNum, 4), true); // Align everything in the center and prevent editing in the first column for (int i = 0; i < m_model->rowCount(); ++i) { m_model->item(i, 0)->setEditable(false); for (int j = 0; j < m_model->columnCount(); ++j) { m_model->item(i, j)->setTextAlignment(Qt::AlignCenter); } } // Enable the second button only if there is a valid dictionary buttonLoadFromDic->setEnabled(m_currDic != 0); } void WizardLettersDefPage::loadValuesFromDic() { ASSERT(m_currDic != 0, "No current dictionary"); const Header &header = m_currDic->getHeader(); bool unknown = false; for (int rowNum = 0; rowNum < m_model->rowCount(); ++rowNum) { wstring letter = wfq(m_model->data(m_model->index(rowNum, 0)).toString()); if (letter.size() != 1) continue; try { unsigned code = header.getCodeFromChar(letter[0]); m_model->setData(m_model->index(rowNum, 1), header.getPoints(code)); m_model->setData(m_model->index(rowNum, 2), header.getFrequency(code)); m_model->setData(m_model->index(rowNum, 3), header.isVowel(code)); m_model->setData(m_model->index(rowNum, 4), header.isConsonant(code)); } catch (const DicException &e) { unknown = true; // The current letter is probably not known in the current // dictionary. Simply ignore it. continue; } } if (unknown) { emit notifyProblem(_q("Some letters were not found in the current dictionary.\n" "Please complete the values manually.")); } } // ---------- WizardConclusionPage ---------- WizardConclusionPage::WizardConclusionPage(QWidget *parent) : QWizardPage(parent) { setupUi(this); } void WizardConclusionPage::initializePage() { setTitle(_q("Conclusion")); // This code must not be in the constructor, because the call to wizard() // supposes that the page is already associated with the wizard // (and thus already constructed) QString finishText = wizard()->buttonText(QWizard::FinishButton); finishText.remove('&'); labelDesc->setText(_q("Click '%1' to generate the dictionary.\n\n").arg(finishText) + _q("You may now load it in Eliot using the checkbox below.\n" "You can also load it later, using the\n" "'Settings -> Change dictionary...' menu option.")); registerField("loadDic", checkBoxLoadDic); } // ---------- DicWizard ---------- DicWizard::DicWizard(QWidget *parent, const Dictionary *iCurrDic) : QWizard(parent) { setOption(QWizard::IndependentPages); setModal(true); QWizardPage *page = new WizardInfoPage; QObject::connect(page, SIGNAL(notifyProblem(QString)), this, SIGNAL(notifyProblem(QString))); addPage(page); page = new WizardLettersDefPage(iCurrDic); QObject::connect(page, SIGNAL(notifyProblem(QString)), this, SIGNAL(notifyProblem(QString))); m_lettersPageId = addPage(page); addPage(new WizardConclusionPage()); } void DicWizard::accept() { CompDic builder; try { // Retrieve the letters model const QStandardItemModel *model = static_cast(page(m_lettersPageId))->getModel(); // Define the letters for (int i = 0; i < model->rowCount(); ++i) { QString letter = model->data(model->index(i, 0)).toString(); int points = model->data(model->index(i, 1)).toInt(); int frequency = model->data(model->index(i, 2)).toInt(); bool isVowel = model->data(model->index(i, 3)).toBool(); bool isConsonant = model->data(model->index(i, 4)).toBool(); wstring wstr = wfq(letter); if (wstr.size() != 1) throw DicException("Invalid letter '" + lfq(letter) + "'"); builder.addLetter(wstr[0], points, frequency, isVowel, isConsonant, vector()); } // Build the dictionary builder.generateDawg(lfq(field("wordList").toString()), lfq(field("genDic").toString()), lfq(field("dicName").toString())); } catch (std::exception &e) { emit notifyProblem(_q("Could not create dictionary:") + "\n" + e.what()); return; } emit notifyInfo(_q("Dictionary successfully created")); bool shouldLoad = field("loadDic").toBool(); if (shouldLoad) { emit loadDictionary(field("genDic").toString()); } QDialog::accept(); } // ---------- LettersDelegate ---------- QWidget * LettersDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const { // For integer columns, bound the values in the [0, 20] range QWidget * editor = QStyledItemDelegate::createEditor(parent, option, index); if (editor->inherits("QSpinBox")) { static_cast(editor)->setMinimum(0); static_cast(editor)->setMaximum(20); } return editor; }