3
\$\begingroup\$

After a long break I took the suggestions from Console based Vocabulary Trainer and reworked the Trainer.

It contains of the following parts

  • Vocabulary.h/cpp: Save and read vocabulary from/to text file

  • Settings.h/cpp: Save and read some basic settings like color and languages from / to text file

  • Menu.h/cpp: The Text based menus of the programm.

  • Menu_utility.h/cpp: Helper functions used in the menu functions

  • Init_to_wide_char.h/cpp: Commands to init the console into wide char. According to my research the first time wcin /wcout facilities are used the console has to turned to wide char with these commands.

The biggest things changed since the last Version: - All Vocabulary gets readed and writed at once. This drastically simplifies the management of the vocabulary - Eliminated all Menu classes. Now all submenus can be used on its own theoretically

Now the the questions I have.

  • Do I really have to use wchar everywere in the program? I introduced it in the first Version to enable the correct display of special signs which are language dependend. For example öäüß in german language or ñ in spanish language. From my understanding operating under Windows i have to use widechar to display these correct which is quite a mess. Also i would like to know if I really need the commands in Init_to_wide_char.h

  • Im using Microsoft Visual Studio 2017 with Core guidelines checker turned on. I get two warnings from it which I cant get rid of:

    Severity Code Description Project File Line Suppression State Warning C26495 Variable 'settings::Settings::m_threshold_learnd' is uninitialized. Always initialize a member variable (type.6).

    Severity Code Description Project File Line Suppression State Warning C26495 Variable 'vocabulary::Vocabulary::m_practice_right' is uninitialized. Always initialize a member variable (type.6).

Is this a bug? I see everything initalized in the Settings and Vocabulary Classes.

  • In menu_show_vocabulary I use a call to _getch() (it is hidden in the function read_single_char() which is called) to achieve that I can move the pages with the arrow keys <- -> or enter number 1 to start searching for a word. Is there a better way to archieve this in c++?

  • Anything else you would do to improve the code?

  • Can it be more simplified and made cleaner?

And here is the full code.

Vocabulary.h

#pragma once

#include <string>
#include <vector>

namespace vocabulary {

    std::vector<std::wstring> line_to_words(const std::wstring& line);
    std::wstring words_to_line(const std::vector<std::wstring>& words);

    class Vocabulary {
    public:
        Vocabulary() = default;
        Vocabulary(const std::vector<std::wstring>& source_words, const std::vector<std::wstring>& target_words)
            :m_source_words{source_words},m_target_words{target_words}
        {
        }

        Vocabulary(const std::wstring& source_words_as_line, const std::wstring& target_words_as_line)
            :m_source_words(line_to_words(source_words_as_line)),
            m_target_words(line_to_words(target_words_as_line))
        {
        }

        std::vector<std::wstring> get_source_words()const   { return m_source_words; }
        std::vector<std::wstring> get_target_words()const   { return m_target_words; }
        std::wstring get_source_word_as_line()const         { return words_to_line(m_source_words); }
        std::wstring get_target_word_as_line()const         { return words_to_line(m_target_words); }
        int get_practiced_right() const                     { return m_practice_right; }

        void set_source_words(const std::vector<std::wstring>& source_words)    { m_source_words = source_words; }
        void set_target_words(const std::vector<std::wstring>& target_words)    { m_target_words = target_words; }
        void set_practiced_right(int practice_right)                            { m_practice_right = practice_right; }
    private:
        std::vector<std::wstring> m_source_words{};
        std::vector<std::wstring> m_target_words{};
        int m_practice_right{ 0 };          //practiced_right
    };

    std::wostream& operator<<(std::wostream& os, const Vocabulary& obj);
    std::wistream& operator>>(std::wistream& is, Vocabulary& obj);

    inline bool operator==(const Vocabulary& lhs, const Vocabulary& rhs)
        // practiced right is not considered and may differ
    {
        return (lhs.get_source_words() == rhs.get_target_words()) && (lhs.get_target_words() == rhs.get_target_words());
    }

    std::vector<Vocabulary> read_from_file(const std::string& filename);
    void write_to_file(const std::string& filename, std::vector<Vocabulary> vocabulary);
}

Vocabulary.cpp

#include "Vocabulary.h"

#include "Init_to_wide_char.h"

#include <fstream>
#include <sstream>
#include <cstdio>

namespace vocabulary {
    //____________________________Helper functions
    std::wostream& operator<<(std::wostream& os, const Vocabulary& obj)
    {
        init_wchar::init_stdout();

        for (const auto& word : obj.get_source_words()) {
            os << word + L" ";
        }
        os << "# ";

        for (const auto& word : obj.get_target_words()) {
            os << word + L" ";
        }
        os << "# ";

        os << obj.get_practiced_right();

        return os;
    }
    std::wistream& operator>>(std::wistream& is, Vocabulary& obj)
        // vocabulary format  source name # target name # practiced
        // e.g
        //así es # genau # 0
        //atrás # hinter # 0
        //bonito pero no práctico # schön aber nicht praktisch # 0
    {
        init_wchar::init_stdin();

        std::wstring line;
        std::getline(is,line);
        std::wistringstream ifs{ line };

        Vocabulary tmp;

        std::vector<std::wstring> source_word;
        bool first = false;
        for (std::wstring s; ifs >> s;) {
            if (s == L"#") {
                first = true;
                break;
            }
            source_word.push_back(s);
        }

        if (!first) {       // end of stream but first # not found
            is.setstate(std::ios::failbit);
            return is;
        }

        tmp.set_source_words(source_word);

        std::vector<std::wstring> target_word;
        bool second = false;
        for (std::wstring s; ifs >> s;) {
            if (s == L"#") {
                second = true;
                break;
            }
            target_word.push_back(s);
        }

        if (!second) {      // same for second
            is.setstate(std::ios::failbit);
            return is;
        }

        std::wstring practiced_right;

        ifs >> practiced_right;

        tmp.set_target_words(target_word);
        try {
            tmp.set_practiced_right(std::stoi(practiced_right)); 
        }
        catch (...) {
            is.setstate(std::ios::failbit);
            return is;
        }

        obj = tmp;

        return is;
    }

    std::vector<std::wstring> line_to_words(const std::wstring& line)
    {
        std::wistringstream ifs{ line };

        std::vector<std::wstring> words;

        for (std::wstring word; ifs >> word;) {
            words.push_back(word);
        }
        return  words;
    }

    std::wstring words_to_line(const std::vector<std::wstring>& words)
        //makes a vector of strings to a line seperated by whitespace
    {
        std::wstring line;

        for (const auto&word : words) {
            line = line + word + L' ';
        }
        if (!line.empty()) {        // remove last ' '
            line.pop_back();
        }
        return line;
    }

    std::vector<Vocabulary> read_from_file(const std::string& filename)
    {
        init_wchar::init_stdin();

        std::wifstream ifs{ filename };
        if (!ifs) {
            throw std::runtime_error(
                "std::vector<Vocabulary> read_from_file(const std::wstring& filename)\n"
                "File " + filename + " could not be opened\n"
            );
        }

        std::vector<Vocabulary> vocabularys;

        for (Vocabulary vocabulary; ifs >> vocabulary;) {
            vocabularys.push_back(vocabulary);
        }

        return vocabularys;
    }

    void write_to_file(const std::string& filename, std::vector<Vocabulary> vocabularys)
    {
        init_wchar::init_stdout();

        std::wofstream ofs{ filename };

        for (const auto& vocabulary : vocabularys) {
            ofs << vocabulary << '\n';
        }
    }
}

settings.h

#pragma once

#include <string>

namespace settings {

    class Settings {
    public:
        Settings() = default;
        Settings(int threshold_learned, const std::wstring& source_language, const std::wstring& target_language, char color_background, char color_font);

        void set_threshold_learned(int threshold_learned)                       { m_threshold_learnd = threshold_learned; }
        void set_name_of_source_language(const std::wstring& source_language)   { m_source_language = source_language; }
        void set_name_of_target_language(const std::wstring& target_language)   { m_target_language = target_language; }
        void set_color_background(int color_background)                         { m_color_background = static_cast<char>(color_background); }
        void set_color_font(int color_font)                                     { m_color_font = static_cast<char>(color_font); }

        int get_threshold_learned() const           { return m_threshold_learnd; }
        std::wstring get_source_language() const    { return m_source_language; }
        std::wstring get_target_language() const    { return m_target_language; }
        char get_color_background() const           { return m_color_background; }
        char get_color_font() const                 { return m_color_font; }
    private:
        int             m_threshold_learnd{ 10 };
        std::wstring    m_source_language{ L"spanish" };
        std::wstring    m_target_language{ L"german" };
        char            m_color_background{ 0 };
        char            m_color_font{ 15 };
    };

    std::wostream& operator<<(std::wostream& os, const Settings& obj);
    std::wistream& operator>>(std::wistream& is, Settings& obj);

    Settings read_from_file(const std::string& filename);
    void write_to_file(const std::string& filename, Settings& settings);
}

settings.cpp

#include "Settings.h"

#include "Init_to_wide_char.h"

#include <fstream>

namespace settings {

    Settings::Settings(int threshold_learned, const std::wstring& source_language, const std::wstring& target_language, char color_background, char color_font)
        :m_threshold_learnd{ threshold_learned },
        m_source_language{ source_language },
        m_target_language{ target_language },
        m_color_background{ color_background },
        m_color_font{ color_font }
    {
        init_wchar::init_stdin();
        init_wchar::init_stdout();

        if (threshold_learned < 0)
            throw std::runtime_error("Settings(int threshold_learned, const std::wstring& source_language, const std::wstring& target_language, char color_background, char color_font)\n"
                "tthreshold_learned < 0\n");

        if(color_background < 0 || color_background > 15  || color_font < 0 || color_font > 15)
            throw std::runtime_error("Settings(int threshold_learned, const std::wstring& source_language, const std::wstring& target_language, char color_background, char color_font)\n"
                "(color_background < 0 || color_background > 15  || color_font < 0 || color_font > 15)");
    }

    //____________________________Helper functions
    std::wostream& operator<<(std::wostream& os, const Settings& obj)
    {

        os  << obj.get_threshold_learned() << '\t'
            << obj.get_source_language() << '\t'
            << obj.get_target_language() << '\t'
            << static_cast<int>(obj.get_color_background()) << '\t'
            << static_cast<int>(obj.get_color_font());

        return os;
    }
    std::wistream& operator>>(std::wistream& is, Settings& obj)
    {
        int threshold_learned;
        is >> threshold_learned;
        if (!is) {
            is.setstate(std::ios::failbit);
            return is;
        }

        std::wstring source_language;
        is >> source_language;
        if (!is) {
            is.setstate(std::ios::failbit);
            return is;
        }

        std::wstring target_language;
        is >> target_language;
        if (!is) {
            is.setstate(std::ios::failbit);
            return is;
        }

        int color_background;
        is >> color_background;
        if (!is) {
            is.setstate(std::ios::failbit);
            return is;
        }

        int color_font;
        is >> color_font;
        if (!is) {
            is.setstate(std::ios::failbit);
            return is;
        }

        Settings tmp(threshold_learned, source_language, target_language, static_cast<char>(color_background), static_cast<char>(color_font));
        obj = tmp;

        return is;
    }

    Settings read_from_file(const std::string& filename)
    {
        init_wchar::init_stdin();

        std::wifstream ifs{ filename };
        if (!ifs) {
            throw std::runtime_error(
                "Settings read_from_file(const std::string& filename)\n"
                "File " + filename + " could not be opened\n"
            );
        }

        Settings settings;
        ifs >> settings;
        return settings;
    }

    void write_to_file(const std::string& filename, Settings& settings)
    {
        init_wchar::init_stdout();

        std::wofstream ofs{ filename };
        ofs << settings;
    }
}

Menu.h

    #pragma once

    #include "Vocabulary.h"

    namespace menu {
        void menu_main(const std::string& filename_vocabulary, const std::string& filename_settings);
        void menu_practice_vocabulary(const std::string& filename_vocabulary, const std::string& filename_settings);
        void menu_add_vocabulary(const std::string& filename_vocabulary, const std::string& filename_settings);
        void menu_show_vocabulary(const std::string& filename_vocabulary, const std::string& filename_settings);
        void menu_search_vocabulary(const std::string& filename_vocabulary, const std::string& filename_settings);
        void menu_erase_vocabulary(const std::vector<vocabulary::Vocabulary>::iterator& it_delete, std::vector<vocabulary::Vocabulary>& vocabulary, const std::string& filename_settings);
        void menu_settings(const std::string& filename_vocabulary, const std::string& filename_settings);
        void menu_change_color(const std::string& filename_settings);
        void menu_set_threshold_vocabulary_learned(const std::string& filename_settings);
        void menu_reset_vocabulary_learned(const std::string& filename_vocabulary);
        void menu_set_languages(const std::string& filename_settings);
    }


<b> Menu.cpp </b>

#include"Menu.h"

#include "Menu_utility.h"
#include "Settings.h"

#include <algorithm>
#include <iostream>
#include <iomanip>
#include <sstream>

#include "Init_to_wide_char.h"

namespace menu {

    void menu_main(const std::string& filename_vocabulary, const std::string& filename_settings)
    {
        init_wchar::init_stdin();
        init_wchar::init_stdout();

        settings::Settings settings;
        if (file_exists(filename_settings)) {
            // overwrite default settings if there is a settings file
            settings = settings::read_from_file(filename_settings);
        }
        else {
            settings::write_to_file(filename_settings,settings);
        }
        change_color(settings.get_color_background(), settings.get_color_font());

        enum class Menu_main_choice {
            practice = 1,
            add = 2,
            show = 3,
            settings = 4,
            exit = 5
        };

        while (true) {
            Menu_main_choice choice = static_cast<Menu_main_choice>(get_int(1, 5,
                bissmann_print +
                L"\n Enter a number to choose an option:"
                "\n [1] Practice vocabulary"
                "\n [2] Add new vocabulary to database"
                "\n [3] Show all words currently in the database. Delete words in database"
                "\n [4] Settings"
                "\n [5] End program"
                "\n"));

            switch (choice) {
            case Menu_main_choice::practice:    
                menu_practice_vocabulary(filename_vocabulary,filename_settings);
                break;
            case Menu_main_choice::add:         
                menu_add_vocabulary(filename_vocabulary, filename_settings);
                break;
            case Menu_main_choice::show: {
                menu_show_vocabulary(filename_vocabulary, filename_settings);
                break;
            }
            case Menu_main_choice::settings: {
                menu_settings(filename_vocabulary, filename_settings);
                break;
            }
            case Menu_main_choice::exit:        
                return;
            }
        }
    }

    void menu_practice_vocabulary(const std::string& filename_vocabulary,const std::string& filename_settings)
    {
        init_wchar::init_stdin();
        init_wchar::init_stdout();

        clear_screen();

        if (!file_exists(filename_vocabulary)) {
            std::wcout << "\n Error: Database is empty. Fill database first with vocabulary\n";
            keep_window_open(L"q");
            return;
        }

        auto vocabulary = vocabulary::read_from_file(filename_vocabulary);

        if (vocabulary.empty()) {
            clear_screen();
            std::wcout << "\n Error: Database is empty. Fill database first with vocabulary\n";
            keep_window_open(L"q");
            return;
        }

        const auto settings = settings::read_from_file(filename_settings);

        int amount = get_int(1, 1000, bissmann_print + L"\n Enter how many words you want to practice.Range 1 - 1000 \n");

        int correct_words = 0;
        for (int i = 1; i <= amount; ++i) {
            clear_screen();
            std::wcout <<bissmann_print;

            auto repeat_random_generator{ 0 };
            auto index{ 0 };
            do {
                index = get_random(0, static_cast<int>(vocabulary.size()) - 1);

                ++repeat_random_generator;
                if (repeat_random_generator == 1000) { //assuming after 1000 rolls no valid word could be found so all are "learned"
                    std::wcout << "\n You learned all vocabulary by repeating each word " << settings.get_threshold_learned() << " times correct \n";
                    keep_window_open(L"q");
                    return;
                }
            } while (vocabulary[index].get_practiced_right() >= settings.get_threshold_learned());

            clear_screen();
            std::wcout << bissmann_print
            << "\n Word nummber " << i << " out of " << amount << ":\n"
            << "\n The " << settings.get_source_language() << " word is:\n\n " << vocabulary[index].get_source_word_as_line() << "\n"
            << "\n Enter the translation in " << settings.get_target_language() << ":\n\n";

            std::wcin.ignore();
            std::vector<std::wstring> input_words = get_words();

            if (input_words == vocabulary[index].get_target_words()) {  //word translated right
                ++correct_words;

                auto practiced_right = vocabulary[index].get_practiced_right();
                ++practiced_right;
                vocabulary[index].set_practiced_right(practiced_right);

                std::wcout << "\n That is correct!!!\n";

                if (vocabulary[index].get_practiced_right() >= settings.get_threshold_learned())
                    std::wcout << "\n You learned the word. You answered it correct " << vocabulary[index].get_practiced_right() << " times\n";
            }
            else {                                              //translated wrong
                vocabulary[index].set_practiced_right(0);
                std::wcout  << "\n That is wrong !!!"
                            << "\n The correct translation is: " << vocabulary[index].get_target_word_as_line() << '\n';
            }
            keep_window_open(L"q");
        }
        clear_screen();
        std::wcout << bissmann_print
         << "\n You translatet " << correct_words << " out of " << amount << " words correctly to " << settings.get_target_language()<<'\n';
        keep_window_open(L"q");

        vocabulary::write_to_file(filename_vocabulary, vocabulary);
        return;
    }

    void menu_add_vocabulary(const std::string& filename_vocabulary, const std::string& filename_settings)
    {
        init_wchar::init_stdin();
        init_wchar::init_stdout();

        std::vector<vocabulary::Vocabulary> vocabulary;
        if (file_exists(filename_vocabulary)) {
            vocabulary = vocabulary::read_from_file(filename_vocabulary);
        }

        const auto settings = settings::read_from_file(filename_settings);

        while (true) {
            clear_screen();
            int choice = get_int(1, 2,
                bissmann_print +
                L"\n [1] Add a new word to the database"
                "\n [2] Return to main menue"
                "\n");
            if (choice == 2) return;

            clear_screen();
            std::wcout << bissmann_print << "\n Enter the " << settings.get_source_language() << " word\n";
            std::wcin.ignore();

            vocabulary::Vocabulary new_vocabulary;
            std::vector<std::wstring> source_language_words = get_words();
            new_vocabulary.set_source_words(source_language_words);

            clear_screen();
            std::wcout << bissmann_print << "\n Enter the translation for '" << new_vocabulary.get_source_word_as_line() << "' in " << settings.get_target_language() << '\n';

            std::vector<std::wstring> target_language_words = get_words();
            new_vocabulary.set_target_words(target_language_words);

            vocabulary.push_back(new_vocabulary);
            std::sort(vocabulary.begin(), vocabulary.end(), [](const vocabulary::Vocabulary& a, const vocabulary::Vocabulary& b)
            { return a.get_source_word_as_line() < b.get_source_word_as_line(); });
            vocabulary::write_to_file(filename_vocabulary,vocabulary);
        }
    }

    void menu_show_vocabulary(const std::string& filename_vocabulary, const std::string& filename_settings)
    {
        init_wchar::init_stdin();
        init_wchar::init_stdout();

        if (!file_exists(filename_vocabulary)) {
            clear_screen();
            std::wcout << "\n Database is empty\n";
            keep_window_open(L"q");
            return;
        }

        auto vocabulary = vocabulary::read_from_file(filename_vocabulary);

        if (vocabulary.empty()) {
            clear_screen();
            std::wcout << "\n Database is empty\n";
            keep_window_open(L"q");
            return;
        }

        const auto settings = settings::read_from_file(filename_settings);

        constexpr auto vocabulary_per_page{ 15 };
        constexpr char keyboard_button_left_arrow{ 75 };
        constexpr char keyboard_button_right_arrow{ 77 };
        constexpr auto max_displayed_signs{ 35 };       // complete window = 80
        constexpr auto ASCII_smilie{ 2 };

        //auto step = Menu_show_choice::start;
        auto page = 1;

        while (true) {
            auto count_of_pages = vocabulary.size() / vocabulary_per_page;
            if ((vocabulary.size() % vocabulary_per_page) != 0) {       //if fracture there must be one more page
                ++count_of_pages;
            }

            clear_screen();
            std::wcout << "row  ";
            std::wcout << settings.get_source_language() << ':';

            // check what is possible max len and then break to the next line
            for (size_t i = 0; i < max_displayed_signs - settings.get_source_language().size(); ++i) {
                std::wcout << ' ';
            }
            std::wcout << settings.get_target_language() << ':';

            for (size_t i = 0; i < max_displayed_signs - settings.get_target_language().size(); ++i) {
                std::wcout << ' ';
            }
            std::wcout << "OK ";

            for (auto i = 0; i < 80; ++i) {
                std::wcout << '-';
            }

            auto start_row = 0 + (page - 1) * vocabulary_per_page;
            auto end_row{ 0 };
            if (page == count_of_pages) {
                end_row = static_cast<int>(vocabulary.size());
            }
            else {
                end_row = start_row + vocabulary_per_page;
            }

            for (auto i = start_row; i < end_row; ++i) {
                std::wcout << std::setw(4) << std::left << i+1
                    << "|"
                    << std::setw(max_displayed_signs) << std::left << vocabulary[i].get_source_word_as_line().substr(0, max_displayed_signs)
                    << "|"
                    << std::setw(max_displayed_signs) << std::left << vocabulary[i].get_target_word_as_line().substr(0, max_displayed_signs)
                    << "|"
                    << std::setw(2) << std::left << vocabulary[i].get_practiced_right();
                if (vocabulary[i].get_practiced_right() >= settings.get_threshold_learned()) {
                    std::wcout << static_cast<char>(ASCII_smilie);      
                }
                else {
                    std::wcout << '\n';
                }
            }
            for (int i = 0; i < 80; ++i) {
                std::wcout << '-';
            }

            if (page != 1) {
                std::wcout << "<-";
            }
            else {
                std::wcout << "  ";
            }
            if (page != count_of_pages) {
                std::wcout << " ->";
            }
            else {
                std::wcout << "   ";
            }
            std::wcout << "                                                            page " << page << " of " << count_of_pages;

            std::wcout <<
                "\n Choose an option:"
                "\n [1] Search for a word to delete it"
                "\n [2] Return to main menue"
                "\n";
            int choice = read_single_char();

            if (choice == '1') {
                menu_search_vocabulary(filename_vocabulary, filename_settings);
                vocabulary = vocabulary::read_from_file(filename_vocabulary);
                read_single_char();     // dirty hack? somehow has to be there to make <- and -> work after going back
            }
            else if (choice == '2') {   // Back to main Menue
                vocabulary::write_to_file(filename_vocabulary, vocabulary);
                return;
            }
            else {
                choice = read_single_char();    //Call twice because first time return always 0xE0 second returns code

                if (choice == keyboard_button_left_arrow && page != 1) {
                    --page;
                }
                else if (choice == keyboard_button_right_arrow && page != count_of_pages) {
                    ++page;
                }
            }
        }
    }

    void menu_search_vocabulary(const std::string& filename_vocabulary, const std::string& filename_settings)
    {
        init_wchar::init_stdin();
        init_wchar::init_stdout();

        auto vocabulary = vocabulary::read_from_file(filename_vocabulary);

        std::wcout << "\n Enter a word or the specific row to search\n a entry in the vocabulary database:\n";
        std::wstring input;
        std::wcin.ignore();

        std::getline(std::wcin, input);
        auto is_number{ true };
        auto delete_row{ 0 };
        try {
            delete_row = std::stoi(input);
            --delete_row;
        }
        catch (...) {   
            is_number = false;
        }

        if (is_number) {    
            if (delete_row < 0 || delete_row >= vocabulary.size()) {
                std::wcout << "\n Error: No match. Invalid row number enterd\n";
                keep_window_open(L"q");
            }
            else {
                auto source_words = vocabulary[delete_row].get_source_words();
                auto it_erase = std::find_if(vocabulary.begin(), vocabulary.end(), [&source_words](const vocabulary::Vocabulary& a)
                {return a.get_source_words() == source_words; });
                menu_erase_vocabulary(it_erase, vocabulary, filename_settings);
            }
        }
        else {  // erase by name of vocabulary
            auto it_erase = std::find_if(
                vocabulary.begin(), vocabulary.end(), 
                [&input](const vocabulary::Vocabulary& voc) 
                { return voc.get_source_words() == vocabulary::line_to_words(input); }
            );

            if (it_erase == vocabulary.end()) {
                it_erase = std::find_if(
                    vocabulary.begin(), vocabulary.end(),
                    [&input](const vocabulary::Vocabulary& voc)
                    { return voc.get_target_words() == vocabulary::line_to_words(input); }
                );

                if (it_erase == vocabulary.end()) {
                    std::wcout << "\n No match found in file\n";
                    keep_window_open(L"q");
                }
                else {
                    menu_erase_vocabulary(it_erase,vocabulary, filename_settings);
                }
            }
            else {
                menu_erase_vocabulary(it_erase, vocabulary, filename_settings);
            }
        }
        vocabulary::write_to_file(filename_vocabulary, vocabulary);
    }

    void menu_erase_vocabulary(const std::vector<vocabulary::Vocabulary>::iterator& it_delete, std::vector<vocabulary::Vocabulary>& vocabulary, const std::string& filename_settings)
    {
        init_wchar::init_stdin();
        init_wchar::init_stdout();

        auto choice{ 0 };
        constexpr auto max_displayed_signs{ 35 };       // complete window = 80
        constexpr auto ASCII_smilie = 2;
        const auto settings = settings::read_from_file(filename_settings);

        for (int i = 0; i < 2; ++i) {
            clear_screen();
            std::wostringstream ost;
            ost << "Found an entry:\n";
            ost << std::setw(4) << std::left << (it_delete - vocabulary.begin()) + 1
                << "|"
                << std::setw(max_displayed_signs) << std::left << it_delete->get_source_word_as_line().substr(0, max_displayed_signs)
                << "|"
                << std::setw(max_displayed_signs) << std::left << it_delete->get_target_word_as_line().substr(0, max_displayed_signs)
                << "|"
                << std::setw(2) << std::left << it_delete->get_practiced_right();
            if (it_delete->get_practiced_right() >= settings.get_threshold_learned()) {
                ost << static_cast<char>(ASCII_smilie);
            }
            else {
                ost << '\n';
            }

            if (i == 0) {
                ost << "\n Choose an option:";
            }
            else if (i == 1) {
                ost << "\n ARE YOU SUPER SUPER SURE????";
            }
            ost << "\n [1] Delete found entry"
                << "\n [2] Cancel"
                << "\n";
            choice = get_int(1, 2, ost.str());

            if (choice == 2) {      //return to start menue
                return;
            }
        }
        vocabulary.erase(it_delete);
        std::wcout  << "\n Hasta la vista baby!!!!!"
                    << "\n Entry has been terminated.\n";
        keep_window_open(L"q");
    }

    void menu_settings(const std::string& filename_vocabulary, const std::string& filename_settings)
    {
        init_wchar::init_stdin();
        init_wchar::init_stdout();

        enum class Menu_settings_choice {
            change_color = 1,
            set_learned = 2,
            reset_learned = 3,
            set_languges = 4,
            exit = 5
        };

        while (true) {
            Menu_settings_choice choice = static_cast<Menu_settings_choice>(
                get_int(1, 5,bissmann_print +
                    L"\n Enter a number to choose an option:"
                    "\n [1] Change color of the background or the letters"
                    "\n [2] Define how often to repeat a word until it counts as learned"
                    "\n [3] Set all vocabulary to not learned"
                    "\n [4] Define target and source language"
                    "\n [5] Return to main menue"
                    "\n"
                )
            );

            switch (choice) {
            case Menu_settings_choice::change_color:    
                menu_change_color(filename_settings);   
                break;
            case Menu_settings_choice::set_learned:     
                menu_set_threshold_vocabulary_learned(filename_settings);       
                break;
            case Menu_settings_choice::reset_learned:   
                menu_reset_vocabulary_learned(filename_vocabulary); 
                break;
            case Menu_settings_choice::set_languges:    
                menu_set_languages(filename_settings);
                break;
            case Menu_settings_choice::exit: return;
            }
        }
    }

    void menu_change_color(const std::string& filename_settings)
    {
        init_wchar::init_stdin();
        init_wchar::init_stdout();

        auto settings = settings::read_from_file(filename_settings);

        enum class Menu_change_color_choice {
            change_background = 1,
            change_letters = 2,
            return_to_settings = 3
        };

        Menu_change_color_choice menu_choice = static_cast<Menu_change_color_choice>(get_int(1, 3,
            bissmann_print +
            L"\n Enter a number to choose an option:"
            "\n [1] Change the color of the background"
            "\n [2] Change the color of the letters"
            "\n [3] Return to settings\n")
            );

        if (menu_choice == Menu_change_color_choice::return_to_settings) {
            return;
        }

        std::wstring color_name;
        std::wostringstream ost;

        if (menu_choice == Menu_change_color_choice::change_letters) {
            color_name = hex_to_color(settings.get_color_font());
            ost << "\n The current color of the letters is [" << static_cast<int>(settings.get_color_font()) << "] " << color_name
                << "\n Choose a color to change the font by selecting an option:";
        }
        else {      // change background
            color_name = hex_to_color(settings.get_color_background());
            ost << "\n The current color of the background is [" << static_cast<int>(settings.get_color_background()) << "] " << color_name
                << "\n Choose a color to change the background by selecting an option:";
        }
        ost << "\n"
            "\n [0] Black \t [4] Red   \t [8]  Gray       \t [12] Light Red"
            "\n [1] Blue  \t [5] Purple\t [9]  Light Blue \t [13] Light Purple"
            "\n [2] Green \t [6] Yellow\t [10] Light Green\t [14] Light Yellow"
            "\n [3] Aqua  \t [7] White \t [11] Light Aqua \t [15] Bright White\n\n";


        char color_choice = static_cast<char>(get_int(0, 15, bissmann_print + ost.str()));

        //prevent both have same color == invisible screen
        if ((menu_choice == Menu_change_color_choice::change_letters && color_choice == settings.get_color_background())
            || (menu_choice == Menu_change_color_choice::change_background && color_choice == settings.get_color_font())) {
            std::wcout << "\n Invalid Input. Back and font color can't be the same!\n";
            keep_window_open(L"q");
            return;
        }

        if (menu_choice == Menu_change_color_choice::change_letters) {
            settings.set_color_font(color_choice);
        }
        else {  //change color back
            settings.set_color_background(color_choice);
        }

        change_color(settings.get_color_background(), settings.get_color_font());
        settings::write_to_file(filename_settings, settings);
    }

    void menu_set_threshold_vocabulary_learned(const std::string& filename_settings)
    {
        init_wchar::init_stdin();
        init_wchar::init_stdout();

        auto settings = settings::read_from_file(filename_settings);

        std::wostringstream ost;
        ost << "\n Currently each vocabulary has to be translated right"
            "\n " << settings.get_threshold_learned() << " times in a row to make it count as learned."
            "\n Enter a new value for practiced right. Range: 1 - 99\n";

        settings.set_threshold_learned(get_int(1, 99, bissmann_print + ost.str()));
        settings::write_to_file(filename_settings, settings);
    }

    void menu_reset_vocabulary_learned(const std::string& filename_vocabulary)
    {
        init_wchar::init_stdin();
        init_wchar::init_stdout();

        auto vocabulary = vocabulary::read_from_file(filename_vocabulary);

        auto choice = get_int(1, 2,
            bissmann_print +
            L"\n Choose an option:"
            "\n [1] Reset all words to not learned. Practice right = 0"
            "\n [2] Return to settings\n");

        if (choice == 2) return;

        choice = get_int(1, 2,
            bissmann_print +
            L"\n Are you SUPER SUPER SURE??"
            "\n [1] Reset all words to not learned. Practice right = 0"
            "\n [2] Return to settings\n");

        if (choice == 2) return;

        for (auto& entry : vocabulary) {
            entry.set_practiced_right(0);
        }

        std::wcout << "\n All words changed to not learned\n";
        keep_window_open(L"q");

        vocabulary::write_to_file(filename_vocabulary, vocabulary);
    }

    void menu_set_languages(const std::string& filename_settings)
    {
        init_wchar::init_stdin();
        init_wchar::init_stdout();

        auto settings = settings::read_from_file(filename_settings);

        enum class Menu_change_language_choice {
            change_source_language = 1,
            change_target_language = 2,
            return_to_settings = 3
        };

        std::wostringstream ost;
        ost << "\n Currently all words need to be translated from " << settings.get_source_language() << " to " << settings.get_target_language() <<
            "\n Choose an option to change the language"
            "\n [1] Change the source language (" << settings.get_source_language() << ")"
            "\n [2] Change the target language (" << settings.get_target_language() << ")"
            "\n [3] Return to settings\n";

        Menu_change_language_choice choice = static_cast<Menu_change_language_choice>(get_int(1, 3, bissmann_print + ost.str()));

        if (choice == Menu_change_language_choice::return_to_settings) return;
        std::wcin.ignore();

        switch (choice) {
            case Menu_change_language_choice::change_source_language: {
                std::wcout << "Enter the new source language:\n";
                std::wstring new_source_language;
                std::getline(std::wcin, new_source_language);

                if (new_source_language.empty()) {
                    std::wcerr << "Invalid empty string entered\n";
                }
                else {
                    settings.set_name_of_source_language(new_source_language);
                    settings::write_to_file(filename_settings, settings);
                    std::wcout << "\n New source language saved to settings";
                }
                keep_window_open(L"q");
                break;
            }
            case Menu_change_language_choice::change_target_language: {
                std::wcout << "Enter the new target language:\n";
                std::wstring new_target_language;
                std::getline(std::wcin, new_target_language);

                if (new_target_language.empty()) {
                    std::wcerr << "Invalid empty string entered\n";
                }
                else {
                    settings.set_name_of_target_language(new_target_language);
                    settings::write_to_file(filename_settings, settings);
                    std::wcout << "\n New target language saved to settings";
                }
                keep_window_open(L"q");
                break;
            }
        }
    }
}

Menu_utility.h

#pragma once

#include <iostream>
#include <fstream>
#include <string>
#include <vector>

#include "Init_to_wide_char.h"

#include <conio.h>

namespace menu {

    const std::wstring bissmann_print =
    {
        L"\n--------Bißmann's-------"
        "\n---Vocabulary Trainer---"
        "\n---------V3.0-----------"
        "\n"
    };

    inline void clear_screen()       //Windows only not portable
    {
        system("cls");  //WINDOWS ONLY 
    }

    inline int read_single_char()    //Windows only not portable
    {
        return _getch(); //WINDOWS ONLY 
    }

    void change_color(char background, char font);  //Windows only not portable
    std::wstring hex_to_color(int hex);


    inline char hex_to_ascii(char c)
        // convert number from hex to ascii. different offsets for 1-9 and a-z in ascii file
    {
        c < 10 ? c += 48 : c += 55;
        return c;
    }

    // functions to safely read in an integer
    void skip_to_int();
    int get_int();
    int get_int(int low, int high, const std::wstring& greeting, const std::wstring& sorry = L"");

    std::vector<std::wstring> get_words();

    inline void keep_window_open()
    {
        init_wchar::init_stdin();
        init_wchar::init_stdout();

        std::wcin.clear();
        std::wcin.ignore(120, '\n');
        std::wcout << "Please enter a character to exit\n";
        wchar_t ch;
        std::wcin >> ch;
    }

    inline void keep_window_open(std::wstring s)
    {
        init_wchar::init_stdin();
        init_wchar::init_stdout();

        if (s == L"") return;
        std::wcin.clear();
        std::wcin.ignore(120, '\n');
        for (;;) {
            std::wcout << "Please enter " << s << "to continue";
            std::wstring ss;
            while (std::wcin >> ss && ss != s)
                std::wcout << "Please enter " << s << " to exit\n";
            return;
        }
    }

    int get_random(int min, int max);

    inline bool file_exists(const std::string& filename) {
        std::ifstream ifs(filename.c_str());
        return ifs.good();
    }
}

Menu_utility.cpp

include "Menu_utility.h"

#include <cwctype>
#include <random>
#include <sstream>

#include <Windows.h>

namespace menu {

    void change_color(char background, char font)
    {
        std::string color = "COLOR ";
        color.push_back(hex_to_ascii(background));
        color.push_back(hex_to_ascii(font));
        system(color.c_str());          //WINDOWS ONLY 
    }

    std::wstring hex_to_color(int hex)
    {
        const std::vector<std::wstring> color_hex =
        {
            { L"Black"},
            { L"Blue"},
            { L"Green"},
            { L"Aqua"},
            { L"Red"},
            { L"Purple"},
            { L"Yellow"},
            { L"White"},
            { L"Gray"},
            { L"Light Blue"},
            { L"Light Green"},
            { L"Light Aqua"},
            { L"Light Red"},
            { L"Light Purple"},
            { L"Light Yellow"},
            { L"Bright White"}
        };

        return color_hex[hex];  
    }

    void skip_to_int()
    {
        init_wchar::init_stdin();
        init_wchar::init_stdout();

        if (std::wcin.fail()) {     // we found sth that wasnt an integer
            std::wcin.clear();      // wed like to look at the characters
            for (wchar_t ch; std::wcin >> ch;) {    //throw away non digits
                if (std::iswdigit(ch) || ch == '-') {
                    std::wcin.unget();      //put the digit back so that we can read the number
                    return;
                }

            }
        }
    }

    int get_int()
    {
        auto n{ 0 };
        while (true) {
            if (std::wcin >> n) return n;
            skip_to_int();
        }
    }

    int get_int(int low, int high, const std::wstring& greeting, const std::wstring& sorry)
    {
        init_wchar::init_stdin();
        init_wchar::init_stdout();

        while (true) {
            clear_screen();
            std::wcout << greeting;

            auto n = get_int();
            if (low <= n && n <= high) return n;
            if (!sorry.empty()) {
                std::wcout << sorry;
                keep_window_open();
            }
        }
    }

    std::vector<std::wstring> get_words()
        //reads in line and returns the individual words of it
    {
        init_wchar::init_stdin();
        init_wchar::init_stdout();

        std::wstring line;
        std::getline(std::wcin, line);

        std::wistringstream iss{ line };
        std::vector<std::wstring> ret;

        for (std::wstring in; iss >> in;) {
            ret.push_back(in);
        }
        return ret;
    }

    int get_random(int min, int max)
    {
        static std::random_device rd;
        static std::mt19937 mt(rd());
        std::uniform_int_distribution<int> distribution(min, max);
        return distribution(mt);
    }
}

Init_to_wide_char.h

#pragma once

#include <exception>

#include <fcntl.h>
#include <io.h>

namespace init_wchar 
{
    inline void init_stdout()
    {
        if (_setmode(_fileno(stdout), _O_U16TEXT) == -1) {//wcout instead of cout
            throw std::runtime_error("void init_stdout_to_wstring()\nCould not init stdout to wide char");
        }
    }

    inline void init_stdin()
    {
        if (_setmode(_fileno(stdin), _O_U16TEXT) == -1) //wcin  instead of cin{
            throw std::runtime_error("void init_stdout_to_wstring()\nCould not init stdin to wide char");
    }
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.