7
\$\begingroup\$

This code was reworked and repostet in Console based Vocabulary Trainer Version 2

I wrote a console based Vocabulary Trainer to practice C++.

Its possible to add and remove vocabulary and practice it with it.

I save and read the vocabulary from a file to be able to read it.

Also certain settings like target and source language color of the console get saved in a settings file.

I know currently it is not portable because I use Windows functions.

I tested it on a Mexican Windows 10 system and a German Windows 7 system.

I wonder if I'm handling the widestrings correctly. During the development I got into the problem that special signs like öäüß don't get displayed properly, so I switched to widestrings in the program. I wonder if my approach is right for this.

Also, I wonder if it's a good idea to make classes for the Menus.

In the next step I want to make this a GUI application to practice making GUIs, so I think it's a good time to review the code. Feel free to comment on any part you like.

File_Vocabulary.h

#ifndef FILE_VOCABULARY_GUARD100420181849
#define FILE_VOCABULARY_GUARD100420181849

#pragma once

#include <fstream>
#include <sstream>
#include <cstdio>
#include <fcntl.h>
#include <io.h>

#include "Vocabulary.h"

namespace voc {

    class File_Vocabulary {
    public:
        File_Vocabulary(const std::string& in_filename);

        void add(const Vocabulary& v);
        bool erase(const std::wstring& s);      // erase by word
        bool erase(const int row);              // erasse by row
        bool is_match(const Vocabulary& v);     // checks if vocabulary is exactly like this in file

        int find(const std::vector<std::wstring> s);        // checks if words are in file and returns row of it. 0 if no row
        int find(std::wstring s);                           // checks if words are in file and returns row of it. 0 if no row

        std::vector<Vocabulary> get_part(const int begin, const int end);   // return in chuncks specify how many rows of Vocabulary you want
        Vocabulary get_row(const int row);                                  // return row
        int size()const { return count; }

        size_t max_size_source()const;
        size_t max_size_target()const;

        void reset_learned();                   //resets learned to 0 in all vocabulary
    private:
        std::string filename;
        int count;                      //count of vocabulary;
    };
}

#endif

File_Vocabulary.cpp

#include "File_Vocabulary.h"

namespace voc {

    //____________________________Member functions

    File_Vocabulary::File_Vocabulary(const std::string& in_filename)
        :filename{in_filename},count{0}
    {
        _setmode(_fileno(stdout), _O_U16TEXT);//wcout instead of cout
        _setmode(_fileno(stdin), _O_U16TEXT); //wcin  instead of cin

        std::wifstream ifs{ filename };
        if (!ifs)  return; // file doesnt exist yet

        for (Vocabulary curr; ifs >> curr;) {
            ++count;
        }
    }

    void File_Vocabulary::add(const Vocabulary& v)
    {
        const std::string out_filename = "tmp_" + filename;
        {   //to close the files
            std::wifstream ifs{ filename };
            if (ifs) {      // dont check if file doesnst exist yet

                std::wofstream ofs{ out_filename };
                bool added = false;

                for (Vocabulary curr; ifs >> curr;) {
                    if (curr.source_words() == v.source_words() && !added) {                //replace definition of vocabulary
                        curr.set_target_words(v.target_words());
                        curr.set_practiced_right(v.practiced_right());
                        added = true;
                    }
                    else if (curr.source_words() > v.source_words() && !added) {
                        ofs << v << '\n';
                        ++count;
                        added = true;
                    }
                    ofs << curr << '\n';
                }

                if (!added) ofs << v << '\n';               // case file was empty

            }
            else { //special case first word to add
                std::wofstream ofs{ filename };
                ofs << v << '\n';
                return;
            }
        }
        std::remove(filename.c_str());
        std::rename(out_filename.c_str(), filename.c_str());
    }

    bool File_Vocabulary::erase(const std::wstring& s)      //search by word to erase
    {
        const std::string out_filename = "tmp_" + filename;
        bool erased = false;
        {
            std::wifstream ifs{ filename };
            if (!ifs) return false;

            std::wofstream ofs{ out_filename };

            for (Vocabulary curr; ifs >> curr;) {
                if ((curr.source_words() == extract_words(s) || curr.target_words() == extract_words(s)) && !erased) {
                    erased = true;
                    --count;
                    continue;
                }
                ofs << curr << '\n';
            }
        }

        if (std::remove(filename.c_str()) != 0)
            throw std::runtime_error("bool File_Vocabulary::erase(const std::wstring& s)\n"
                "file operation delete old vocabulary failed\n");
        if (std::rename(out_filename.c_str(), filename.c_str()) != 0)
            throw std::runtime_error("bool File_Vocabulary::erase(const std::wstring& s)\n"
                "file operation rename new to old failed\n");

        return erased;
    }

    bool File_Vocabulary::erase(const int row)
    {
        if (row < 0 || row > count) return false;       

        const std::string out_filename = "tmp_" + filename;
        bool erased = false;
        {                                       // to close file before rename
            std::wifstream ifs{ filename };
            if (!ifs) return false;

            std::wofstream ofs{ out_filename };

            bool erased = false;
            int curr_pos = 0;

            for (Vocabulary curr; ifs >> curr;) {
                if (curr_pos == row && !erased) {
                    erased = true;
                    --count;
                    continue;
                }
                ofs << curr << '\n';
                ++curr_pos;
            }
        }

        if (std::remove(filename.c_str()) != 0)
            throw std::runtime_error("bool File_Vocabulary::erase(const int row)\n"
                "file operation delete old vocabulary failed\n");
        if (std::rename(out_filename.c_str(), filename.c_str()) != 0)
            throw std::runtime_error("bool File_Vocabulary::erase(const int row)\n"
                "file operation rename new to old failed\n");

        return erased;
    }

    bool File_Vocabulary::is_match(const Vocabulary& v)     // checks if vocabulary is exactly like this in file
    {
        std::wifstream ifs{ filename };
        if (!ifs) return false;

        for (Vocabulary curr; ifs >> curr;) {
            if (v.source_words() == curr.source_words()) {
                if (v.target_words() == curr.target_words())
                    return true;
                else
                    return false;
            }
        }
        throw std::runtime_error("bool File_Vocabulary::erase(const int row)\nDatabase corrupt. Original word is not in database.\n");
        return false;
    }

    int File_Vocabulary::find(const std::vector<std::wstring> s)        //checks if word is in file and returns row of it
    {
        std::wifstream ifs{ filename };
        if (!ifs) return 0;

        int row = 0;
        for (Vocabulary curr; ifs >> curr;) {
            if (curr.source_words() == s || curr.target_words() == s)
                return row;
            ++row;
        }
        return -1;
    }

    int File_Vocabulary::find(std::wstring s)       //checks if word is in file and returns row of it
    {
        std::wistringstream ist{ s };

        std::vector<std::wstring> vs;
        for (std::wstring in; ist >> in;) {
            vs.push_back(in);
        }   
        return find(vs);            // call the other find routine
    }

    std::vector<Vocabulary> File_Vocabulary::get_part(const int begin, const int end) //return in chuncks specify how many rows of Vocabulary you want
    {
        if(begin > end || begin > count || end > count || begin < 0)
            throw std::runtime_error("std::vector<Vocabulary> File_Vocabulary::get_part(const int begin, const int end)\nbegin > end || begin > count || end > count || begin < 0");

        std::wifstream ifs{ filename };
        if (!ifs) throw std::runtime_error("std::vector<Vocabulary> File_Vocabulary::get_part(const int begin, const int end)\nFile " + filename + " could not be opened\n");

        int curr_pos = 0;
        for (; curr_pos < begin; ++curr_pos) {
            Vocabulary curr;
            ifs >> curr;
        }

        std::vector<Vocabulary> ret;

        for (; curr_pos <= end; ++curr_pos) {
            Vocabulary curr;
            ifs >> curr;
            ret.push_back(curr);
        }
        return ret;
    }

    Vocabulary File_Vocabulary::get_row(const int row)                              // return row
    {
        std::vector<Vocabulary> ret = get_part(row, row);
        return *ret.begin();
    }

    void File_Vocabulary::reset_learned()
    {
        std::wifstream ifs{ filename };
        if (!ifs) return;

        const std::string out_filename = "tmp_" + filename;
        std::wofstream ofs{ out_filename };

        for (Vocabulary curr; ifs >> curr;) {
            curr.set_practiced_right(0);
            ofs << curr << '\n';
        }

        std::remove(filename.c_str());
        std::rename(out_filename.c_str(), filename.c_str());
    }

    size_t File_Vocabulary::max_size_source() const
    {
        std::wifstream ifs{ filename };
        if (!ifs) return 0;

        size_t ret = 0;
        for (Vocabulary curr; ifs >> curr;) {
            if (curr.source_words().size() > ret)
                ret = curr.source_words().size();
        }
        return ret;
    }

    size_t File_Vocabulary::max_size_target() const
    {
        std::wifstream ifs{ filename };
        if (!ifs) return 0;

        size_t ret = 0;
        for (Vocabulary curr; ifs >> curr;) {
            if (curr.target_words().size() > ret)
                ret = curr.target_words().size();
        }
        return ret;
    }
}

Menu.h

#ifndef MENU_GUARD110420181806
#define MENU_GUARD110420181806

#pragma once

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

#include "Menu_settings.h"
#include "Menu_show.h"

namespace voc {
    class Menu {
    public:
        Menu(const std::string& file_settings, const std::string& file_vocabulary);
    private:
        void init_settings();
        void menu_main();
        void menu_practice();
        void menu_add();

        File_Vocabulary file_voc;
        Settings settings;
        std::string fname_settings;
        std::string fname_vocabulary;
    };
}

#endif

Menu.cpp

#include"Menu.h"

namespace voc {
    //____________________________Member functions
    Menu::Menu(const std::string& file_settings, const std::string& file_vocabulary)
        :settings{ 10, L"spanish", L"german", 0, 15 }, 
        file_voc{ file_vocabulary }, 
        fname_settings{file_settings}, 
        fname_vocabulary{file_vocabulary}
    {
        _setmode(_fileno(stdout), _O_U16TEXT);//wcout instead of cout
        _setmode(_fileno(stdin), _O_U16TEXT); //wcin  instead of cin

        init_settings();
        menu_main();
    }

    void Menu::init_settings()
        // try to read settings from file
        // if not take default values and write to new file
        // change color from settings
    {
        if (!read_settings_from_file(fname_settings, settings)) 
            write_settings_to_file(fname_settings, settings);

        change_color(settings.color_background(), settings.color_font());
    }

    void Menu::menu_main()
    {
        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();break;
            case Menu_main_choice::add:         menu_add();     break;
            case Menu_main_choice::show: {
                Menu_show ms{ fname_settings,settings ,file_voc };
                break;
            }
            case Menu_main_choice::settings: {
                Menu_settings ms{ fname_settings,settings ,file_voc };
                settings = ms.get_settings();
                break;
            }
            case Menu_main_choice::exit:        return;
            }
        }
    }

    void Menu::menu_practice()
    {
        clear_screen();
        int count_of_rows = file_voc.size()-1;
        if (count_of_rows == 0) {       // file empty fill first with vocabulary
            std::wcout << "\n Error: Database is empty. Fill database first with vocabulary\n";
            utility::keep_window_open(L"q");
            return;
        }

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

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

            int repeat_rnd = 0;

            do {
                rnd_voc = file_voc.get_row(utility::get_random(1, count_of_rows));
                ++repeat_rnd;
                if (repeat_rnd == 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.threshold_learned() << " times correct \n";
                    utility::keep_window_open(L"q");
                    return;
                }
            } while (rnd_voc.practiced_right() >= settings.threshold_learned());

            clear_screen();
            std::wcout << bissmann_print
            << "\n Word nummber " << i << " out of " << amount << ":\n"
            << "\n The " << settings.name_of_source_language() << " word is:\n\n " << rnd_voc.source() << "\n"
            << "\n Enter the translation in " << settings.name_of_target_language() << ":\n\n";

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

            if (input_words == rnd_voc.target_words()) {    //word translated right
                ++correct_words;

                int practiced_right = rnd_voc.practiced_right();
                ++practiced_right;
                rnd_voc.set_practiced_right(practiced_right);

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

                if (rnd_voc.practiced_right() >= settings.threshold_learned())
                    std::wcout << "\n You learned the word. You answered it correct " << rnd_voc.practiced_right() << " times\n";
            }
            else {                                              //translated wrong
                rnd_voc.set_practiced_right(0);
                std::wcout  << "\n That is wrong !!!"
                            << "\n The correct translation is: " << rnd_voc.target()<<'\n';
            }
            utility::keep_window_open(L"q");
            file_voc.add(rnd_voc);
        }
        clear_screen();
        std::wcout << bissmann_print
         << "\n You translatet " << correct_words << " out of " << amount << " words correctly to " << settings.name_of_target_language()<<'\n';
        utility::keep_window_open(L"q");
        return;
    }

    void Menu::menu_add()
    {
        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.name_of_source_language() << " word\n";

            Vocabulary add_voc;
            std::wcin.ignore();
            std::vector<std::wstring> org_words = get_words();
            add_voc.set_source_words(org_words);

            clear_screen();
            std::wcout << bissmann_print << "\n Enter the translation for '" << add_voc.source() << "' in " << settings.name_of_target_language() << '\n';

            std::vector<std::wstring> tar_words = get_words();
            add_voc.set_target_words(tar_words);

            file_voc.add(add_voc);
        }
    }

}

Menu_settings.h

#ifndef MENU_SETTINGS_GUARD160420182054
#define MENU_SETTINGS_GUARD160420182054

#pragma once

#include <string>
#include <iostream>
#include <sstream>
#include <fcntl.h>
#include <io.h>

#include "Settings.h"
#include "misc.h"
#include "File_Vocabulary.h"

namespace voc {
    class Menu_settings {
    public:
        Menu_settings(const std::string& file_settings, const Settings& set, const File_Vocabulary& fv);
        Settings get_settings() const { return settings; }
    private:
        void menu_change_color();
        void menu_set_learned();
        void menu_reset_learned();
        void menu_set_languages();

        std::string filename_settings;
        Settings settings;
        File_Vocabulary file_voc;
    };

}

#endif

Menu_settings.cpp

#include "Menu_settings.h"

namespace voc {

    Menu_settings::Menu_settings(const std::string& file_settings, const Settings& set, const File_Vocabulary& fv)
        :filename_settings{file_settings},settings{set}, file_voc{fv}
    {
        _setmode(_fileno(stdout), _O_U16TEXT);//wcout instead of cout
        _setmode(_fileno(stdin), _O_U16TEXT); //wcin  instead of cin

        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();    break;
            case Menu_settings_choice::set_learned:     menu_set_learned();     break;
            case Menu_settings_choice::reset_learned:   menu_reset_learned();   break;
            case Menu_settings_choice::set_languges:    menu_set_languages();   break;
            case Menu_settings_choice::exit: return;
            }
        }
    }

    void Menu_settings::menu_change_color()
    {
        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;

        char char_tmp = 0;
        std::wstring color_name;
        if (menu_choice == Menu_change_color_choice::change_letters) 
            color_name = hex_to_color(settings.color_font());
        else
            color_name = hex_to_color(settings.color_background());

        std::wostringstream ost;

        if (menu_choice == Menu_change_color_choice::change_letters) {
            ost << "\n The current color of the letters is [" << static_cast<int>(settings.color_font()) << "] " << color_name
                << "\n Choose a color to change the font by selecting an option:";
        }
        else {      // change background
            ost << "\n The current color of the background is ["<< static_cast<int>(settings.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.color_background())
            || (menu_choice == Menu_change_color_choice::change_background && color_choice == settings.color_font())) {
            std::wcout << "\n Invalid Input. Back and font color cant be the same!\n";
            utility::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.color_background(), settings.color_font());
        write_settings_to_file(filename_settings, settings);
    }

    void Menu_settings::menu_set_learned()
    {
        std::wostringstream ost;
        ost <<  "\n Currently each vocabulary has to be translated right"
                "\n " << settings.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()));
        write_settings_to_file(filename_settings, settings);
    }

    void Menu_settings::menu_reset_learned()
    {
        int 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;

        file_voc.reset_learned();
        std::wcout<<"\n All words changed to not learned\n";
        utility::keep_window_open(L"q");
    }

    void Menu_settings::menu_set_languages()
    {
        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.name_of_source_language()<<" to "<< settings.name_of_target_language()<<
                "\n Choose an option to change the language"
                "\n [1] Change the source language ("<< settings.name_of_source_language() <<")"
                "\n [2] Change the target language ("<< settings.name_of_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::cin.ignore();

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

            if (in.empty()) {
                std::wcerr << "Invalid empty string entered\n";
                utility::keep_window_open(L"q");
                return;
            }

            settings.set_name_of_source_language(in);
            write_settings_to_file(filename_settings, settings);
            std::wcout<<"\n New source language saved to settings";
            utility::keep_window_open(L"q");
            break;
        }
        case Menu_change_language_choice::change_target_language: {
            std::wcout << "Enter the new target language:\n";
            std::wstring in;
            getline(std::wcin, in);

            if (in.empty()) {
                std::wcerr << "Invalid empty string entered\n";
                utility::keep_window_open(L"q");
                return;
            }

            settings.set_name_of_target_language(in);
            write_settings_to_file(filename_settings, settings);
            std::cout<<"\n New target language saved to settings";
            utility::keep_window_open(L"q");
            break;
        }
        case Menu_change_language_choice::return_to_settings: 
            return;
        }
    }
}

Menu_show.h

#ifndef MENU_SHOW_GUARD170420182020
#define MENU_SHOW_GUARD170420182020

#pragma once

#include <string>
#include <iostream>
#include <sstream>
#include <fcntl.h>
#include <io.h>
#include <iomanip>

#include "Settings.h"
#include "misc.h"
#include "File_Vocabulary.h"

namespace voc {
    class Menu_show {
    public:
        Menu_show(const std::string& file_settings, const Settings& set, const File_Vocabulary& fv);
    private:
        enum class Menu_show_choice {
            start,
            search,
            erase,
            exit
        };

        void menu_search();
        void menu_erase();

        std::string filename_settings;
        Settings settings;
        File_Vocabulary file_voc;
        Menu_show_choice step;
        int delete_row;

        static constexpr int rows_per_page = 15;
        static constexpr char left_arrow = 75;
        static constexpr char right_arrow = 77;

        static constexpr int max_len_of_display = 35;       // complete window = 80
    };

}

#endif

Menu_show.cpp

#include "Menu_show.h"

namespace voc {

    Menu_show::Menu_show(const std::string& file_settings, const Settings& set, const File_Vocabulary& fv)
        :filename_settings{ file_settings }, settings{ set }, file_voc{ fv },step{Menu_show_choice::start},delete_row{0}
    {
        _setmode(_fileno(stdout), _O_U16TEXT);//wcout instead of cout
        _setmode(_fileno(stdin), _O_U16TEXT); //wcin  instead of cin

        int page = 1;
        while (true) {
            if (file_voc.size() < 1) {
                clear_screen();
                std::wcout << "\n Database is empty\n";
                utility::keep_window_open(L"q");
                return;
            }


            int count_of_pages = file_voc.size() / rows_per_page;
            if ((file_voc.size() % rows_per_page) != 0)     //if fracture there must be one more page
                ++count_of_pages;

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

            // check what is possible max len and then break to the next line
            for (size_t i = 0; i < max_len_of_display - settings.name_of_source_language().size(); ++i) //max len  = longest word in database !!!
                std::wcout << ' ';

            std::wcout << settings.name_of_target_language() << ':';

            for (size_t i = 0; i < max_len_of_display - settings.name_of_target_language().size(); ++i)  //max len  = longest word in database !!!
                std::wcout << ' ';

            std::wcout << "OK ";

            for (int i = 0; i < 80; ++i)
                std::wcout << '-';

            int start_row = 0 + (page - 1) * rows_per_page;
            int end_row = 0;
            if (page == count_of_pages)
                end_row = file_voc.size()-1;
            else
                end_row = start_row + rows_per_page - 1;

            //read vocabulary from file in chunks
            std::vector<Vocabulary> print_voc{ file_voc.get_part(start_row, end_row) };

            for (size_t i=0; i<print_voc.size(); ++i){
                 std::wcout << std::setw(4) << std::left << start_row+1 + i
                            << "|"
                            << std::setw(max_len_of_display)<< std::left<<print_voc[i].source().substr(0, max_len_of_display)
                            << "|"
                            << std::setw(max_len_of_display)<< std::left<< print_voc[i].target().substr(0, max_len_of_display)
                            << "|"
                            << std::setw(2)                 << std::left<< print_voc[i].practiced_right();
                if (print_voc[i].practiced_right() >= settings.threshold_learned())
                    std::wcout << static_cast<char>(2);     //add a 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;

            switch (step) {
            case Menu_show_choice::exit:
                return;
            case Menu_show_choice::start: {
                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')
                    step = Menu_show_choice::search;
                else if (choice == '2') // Back to main Menue
                    return;
                else {
                    choice = read_single_char();    //Call twice because first time return always 0xE0 second returns code
                    step = Menu_show_choice::start;
                }
                if (choice == left_arrow && page != 1)
                    --page;
                if (choice == right_arrow && page != count_of_pages)
                    ++page;
                break;
            }
            case Menu_show_choice::search: menu_search();
                break;
            case Menu_show_choice::erase: menu_erase();
                break;
            }
        }
    }

    void Menu_show::menu_search()       //ugly better with a class
    {
        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);

        bool is_number = true;

        try {
            delete_row = std::stoi(input);  
            --delete_row;//because humans start on 1 not on 0
        }
        catch (...) {   // catch stoi std::invalid_argument  std::out_of_range
            is_number = false;
        }

        if (!is_number){    //search by name
            delete_row = file_voc.find(input);      // ??? delete row == 0

            if (delete_row == 0) {          //No match
                std::wcout << "\n No match found in file\n";
                utility::keep_window_open(L"q");
                step = Menu_show_choice::start; // break and set global menu 
                return;
            }
        }
        else  { // search by row number
            if (delete_row < 0 || delete_row > file_voc.size()-1) {
                std::wcout << "\n Error: No match. Invalid row number enterd\n";
                utility::keep_window_open(L"q");
                step = Menu_show_choice::start; // break and set global menu 
                return;
            }
        }
        step = Menu_show_choice::erase;     // go to next step erase
    }

    void Menu_show::menu_erase()            
    {
        Vocabulary delete_voc;
        delete_voc = file_voc.get_row(delete_row);  // get word to delte for display

        int choice = 0;
        bool repeat = false;

        for (int i = 0; i <2; ++i) {
            clear_screen();

            std::wostringstream ost;

            ost << "Found an entry:\n";

            ost << std::setw(4) << std::left << delete_row + 1
                << "|"
                << std::setw(max_len_of_display) << std::left << delete_voc.source().substr(0, max_len_of_display)
                << "|"
                << std::setw(max_len_of_display) << std::left << delete_voc.target().substr(0, max_len_of_display)
                << "|"
                << std::setw(2) << std::left << delete_voc.practiced_right();
            if (delete_voc.practiced_right() >= settings.threshold_learned())
                ost << static_cast<char>(2);        //add a smilie
            else
                ost << '\n';

            if (i == 0) ost << "\n Choose an option:";
            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
                step = Menu_show_choice::start;
                return;
            }
        }
        file_voc.erase(delete_row);     
        std::wcout  << "\n Hasta la vista baby!!!!!"
                    << "\n Entry has been terminated.\n";
        utility::keep_window_open(L"q");

        step = Menu_show_choice::start;
    }
}

misc.h

#ifndef MISC_GUARD120420181946
#define MISC_GUARD120420181946

#pragma once

#include <string>
#include <sstream>
#include <map>
#include <cctype>
#include <cwctype>
#include <iostream>
#include <conio.h>
#include <Windows.h>

#include "Utility.h"

namespace voc {

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

    void clear_screen();                            //Windows only not portable
    int read_single_char(void);
    void change_color(char background, char font);  //Windows only not portable
    std::wstring hex_to_color(int hex);
    char hex_to_ascii(char 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();
}

#endif

misc.cpp

#include "misc.h"

namespace voc {

    void clear_screen()
    {
        system("cls");  //WINDOWS ONLY 
    }

    int read_single_char(void)
{
    return _getch(); //WINDOWS ONLY 
}

    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];  
    }

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

    void skip_to_int()
    {
        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()
    {
        int 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)
    {
        while (true) {
            clear_screen();
            std::wcout << greeting;

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

    std::vector<std::wstring> get_words()
        //reads in line and returns the individual words of it
    {
        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;
    }
}

settings.h

#ifndef SETTINGS_GUARD110420181833
#define SETTINGS_GUARD110420181833

#pragma once

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

namespace voc {

    class Settings {
    public:
        Settings(int threshold, const std::wstring& source_name, const std::wstring& target_name, char color_back, char color_font);

        void set_threshold_learned(int threshold) { thr_learnd = threshold; }
        void set_name_of_source_language(const std::wstring& source_name) { s_language = source_name; }
        void set_name_of_target_language(const std::wstring& target_name) { t_language = target_name; }
        void set_color_background(int back) { clr_back = back; }
        void set_color_font(int font) { clr_font = font; }

        int threshold_learned() const { return thr_learnd; };
        std::wstring name_of_source_language() const { return s_language; }
        std::wstring name_of_target_language() const { return t_language; }
        char color_background() const { return clr_back; }
        char color_font() const { return clr_font; }
    private:
        int thr_learnd;     //learned
        std::wstring s_language;
        std::wstring t_language;
        char clr_back;
        char clr_font;
    };

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

    void write_settings_to_file(const std::string& filename, Settings& data);
    bool read_settings_from_file(const std::string& filename, Settings& data);
}

#endif

Settings.cpp

#include "Settings.h"

namespace voc {
    Settings::Settings(int threshold, const std::wstring& source_name, const std::wstring& target_name, char color_back, char color_font)
        :thr_learnd{ threshold },
        s_language{ source_name },
        t_language{ target_name },
        clr_back{ color_back },
        clr_font{ color_font }
    {
        _setmode(_fileno(stdout), _O_U16TEXT);//wcout instead of cout
        _setmode(_fileno(stdin), _O_U16TEXT); //wcin  instead of cin

        if (threshold < 0)
            throw std::runtime_error("Settings::Settings(int threshold, const std::string& source_name, const std::string& target_name, char color_back, char color_font)"
                "\nthreshold < 0");

        if(color_back < 0 || color_back > 15  || color_font < 0 || color_font > 15)
            throw std::runtime_error("Settings::Settings(int threshold, const std::string& source_name, const std::string& target_name, char color_back, char color_font)\n"
                "(color_back < 0 || color_back > 15  || color_font < 0 || color_font > 15)");
    }

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

        os  << obj.threshold_learned() << '\t'
            << obj.name_of_source_language() << '\t'
            << obj.name_of_target_language() << '\t'
            << static_cast<int>(obj.color_background()) << '\t'
            << static_cast<int>(obj.color_font());

        return os;
    }
    std::wistream& operator>>(std::wistream& is, Settings& obj)
    {
        int learned;
        std::wstring source;
        std::wstring target;
        int back;
        int font;

        is >> learned >> source >> target >> back >> font;

        Settings ret(learned, source, target, static_cast<char>(back), static_cast<char>(font));
        obj = ret;

        return is;
    }

    void write_settings_to_file(const std::string& filename, Settings& data)
    {
        std::wofstream ofs{ filename }; 
        ofs << data;
    }


    bool read_settings_from_file(const std::string& filename, Settings& data)
    {
        std::wifstream ifs{ filename };
        if (!ifs) return false;

        ifs >> data;
        return true;        
    }
}

Utility.h

#ifndef UTILITY_GUARD11042018
#define UTILITY_GUARD11042018

#pragma once

#include <iostream>
#include <string>
#include <random>
#include <chrono>

namespace utility {

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

    inline void keep_window_open()
    {
        std::wcin.clear();
        std::wcout << "Please enter a character to exit\n";
        wchar_t ch;
        std::wcin >> ch;
        return;
    }

    int get_random(int min, int max);
}

#endif

Utility.cpp

#include "Utility.h"


namespace utility {

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

Vocabulary.h

#ifndef VOCABULARY_GUARD100420181927
#define VOCABULARY_GUARD100420181927

#pragma once

#include <string>
#include <vector>
#include <sstream>

namespace voc {

    class Vocabulary {
    public:
        Vocabulary();
        Vocabulary(const std::vector<std::wstring>& org, const std::vector<std::wstring>& tar);
        Vocabulary(const std::wstring& org, const std::wstring& tar);

        std::vector<std::wstring> source_words()const { return src_words; }
        std::vector<std::wstring> target_words()const { return tar_words; }

        std::wstring source()const;
        std::wstring target()const;

        int practiced_right() const { return pr; }

        void set_source_words(const std::vector<std::wstring>& org) { src_words = org; }
        void set_target_words(const std::vector<std::wstring>& tar) { tar_words = tar; }
        void set_practiced_right(int p) { pr = p; }
    private:
        std::vector<std::wstring> src_words;
        std::vector<std::wstring> tar_words;
        int pr;         //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);

    std::vector<std::wstring> extract_words(const std::wstring& line);
    std::wstring words_to_line(const std::vector<std::wstring>& sv);
}

#endif

Vocabulary.cpp

#include "Vocabulary.h"

namespace voc {
    //____________________________Member functions
    Vocabulary::Vocabulary()
        :pr{0}
    {
    }

    Vocabulary::Vocabulary(const std::vector<std::wstring>& org, const std::vector<std::wstring>& tar)
        :src_words{org},tar_words{tar},pr{0}
    {
    }

    Vocabulary::Vocabulary(const std::wstring& org, const std::wstring& tar)
        : src_words{ extract_words(org) },tar_words{ extract_words(tar) },pr{0}
    {
    }

    std::wstring Vocabulary::source()const { return words_to_line(src_words); }
    std::wstring Vocabulary::target()const { return words_to_line(tar_words); }

    //____________________________Helper functions
    std::wostream& operator<<(std::wostream& os, const Vocabulary& obj)
    {
        for (const auto& x : obj.source_words()) 
            os << x + L" ";
        os << "# ";
        for (const auto& x : obj.target_words())
            os << x + L" ";
        os << "# ";
        os << obj.practiced_right();

        return os;
    }
    std::wistream& operator>>(std::wistream& is, Vocabulary& obj)
    {
        std::wstring line;
        std::getline(is,line);
        std::wistringstream ifs{ line };

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

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

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

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

        std::wstring s;
        ifs >> s;
        obj.set_source_words(org);
        obj.set_target_words(tar);
        obj.set_practiced_right(std::stoi(s));

        return is;
    }

    inline bool operator==(const Vocabulary& lhs, const Vocabulary& rhs)
        // no check of valid member
    {
        return (lhs.source_words() == rhs.target_words()) && (lhs.target_words() == rhs.target_words());
    }

    std::vector<std::wstring> extract_words(const std::wstring& line)
    {
        std::wistringstream ifs{ line };
        std::vector<std::wstring> ret;
        for (std::wstring s; ifs >> s;)
            ret.push_back(s);
        return ret;;
    }

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

        for (const auto&x : sv)
            ret = ret + x + L' ';
        if (!ret.empty())       // remove last ' '
            ret.pop_back();
        return ret;
    }
}

main.cpp

#include <iostream>
#include <string>

#include "Menu.h"


const std::string filename_settings = "settings.txt";
const std::string filename_vocabulary = "vocsafe.txt";

int main()
try {
    voc::Menu menu(filename_settings, filename_vocabulary);
}
catch (std::runtime_error& e) {
    std::wcerr << e.what() << "\n";
    utility::keep_window_open(L"~");
}
catch (...) {
    std::wcerr << "unknown error " << "\n";
    utility::keep_window_open(L"~");
}
\$\endgroup\$
3
\$\begingroup\$

Here are my remarks, without any further structure or grouping. I started reading the code top to bottom, but then started to jump around.

#ifndef FILE_VOCABULARY_GUARD100420181849
#define FILE_VOCABULARY_GUARD100420181849

#pragma once

Choose between the #ifndef inclusion guard and pragma once. Using both is redundant.

I don't understand the name File_Vocabulary. I'd rather call this VocabularyFile. Combining the underscore with PascalCase is not typical for C++, it looks more like Ada.

The class VocabularyFile does too much, in my opinion. It should only have two methods at all: save and load. All the other operations can be done in-memory instead. Since this program is handling vocabulary, there will not be more than a million entries in a single vocabulary file. (No human being could ever remember this many vocabularies.) Therefore you will probably not run into an out-of-memory situation.

What is the purpose of max_size_source and max_size_target? They are defined but never used. Your IDE should tell you this.

You should start the presentation of the code with Vocabulary.h and Vocabulary.cpp, since these are the most important data structures in the program. Without reading these files first, it is difficult to understand File_Vocabulary.cpp.

In Vocabulary.h (and many other places), you should name the variables properly. org and tar are not useful, since tar is the well-known Tape Archiver. Better name these variables source and target. Oh, I see, you did that for the source_words and target_words methods. In that case, I change my advice to "Be consistent in naming things". Since methods must not have the same name as fields, prefixing all fields with m_ or some similar prefix has become common sense. Then you have source_words() and m_source_words, and the reader doesn't have to guess anymore.

In Vocabulary.cpp, the operator>> looks misplaced to me. It clearly belongs to the other code that is concerned about the file format. By reading the code alone, the file format is not easy to guess. Therefore you should have included an example vocabulary file along with the code.

In Vocabulary.cpp, in method operator<<, you should replace os << x + L" " with os << x << L" ", to prevent unnecessary string allocation.

The variable names in words_to_line are bad. Rename sv to words and ret to line and x to word. This will make the code intuitive to read:

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

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

Switching to Utility.h, function keep_window_open.

The parameter s should better be named exit_command. Its type should be const&, since it is not modified in the function. Instead of std::wcin >> ss, you should rather use std::getline(std::wcin, ss), because otherwise pressing Enter does not provide a good user experience. Oh, and rename ss to line, to make the code clearer.

Since the string "Please enter" is duplicated, you could save it into a variable at the beginning of the function:

void keep_window_open(const std::wstring& exit_command)
{
    if (s == L"") return;
    std::wcin.clear();
    std::wcin.ignore(120, '\n');

    std::wstring prompt = "Please enter " + s + " to exit\n";
    while (true) {
        std::wcout << prompt;
        while (std::getline(std::wcin, line) && ...
    }
}

Wait … the for (;;) or while (true) is completely useless. As is the whole function. Why should the user enter a specific word to close the window? Just let them press Enter once. The whole function should be removed and replaced with the parameterless keep_window_open below it.

By the way, the inline is not useful for functions doing interactive I/O, and the return at the end is redundant. You can just remove both.

That's it for now. I think it is already enough to improve your code by a fair amount.

\$\endgroup\$
  • \$\begingroup\$ ⟪Choose between the #ifndef inclusion guard and pragma once. Using both is redundant.⟫ Using both is OK, since the slower #ifdef is a fallback if the fast #pragma is not supported. But, since this is not portable anyway, might as well count on the #pragma. \$\endgroup\$ – JDługosz Jun 22 '18 at 8:37
  • \$\begingroup\$ ⟪Since methods [sic] must not have the same name as fields, prefixing all fields with m_ or some similar prefix has become common sense.⟫ It has become common place, not common sense. Others will say that all such tags are to be avoided because the algorithm should not care where the value comes from, and in fact they tend to migrate as code is made more general. \$\endgroup\$ – JDługosz Jun 22 '18 at 8:40
4
\$\begingroup\$

Just some basic stuff, will add more later.

Strings

The implementation uses a lot of hard-coded strings and is inconsistent regarding their encodings. Some strings are "normal" ASCII ones whereas others are wide strings. This might cause a lot of unforeseen problems.

Randomness

utility::get_random might actually always return the same result for the same inputs. Depending on the standard library implementation, std::random_device might return the same sequence for each created object.

Since utility::get_random always creates a new object this basically "resets" the sequence for those implementations. (It's conceptually equivalent to srand(0); /* do something with */ rand();).

This can be fixed by marking rd as static so it only gets instantiated once. (And while we're at it, it might make sense to make mt static as well, since its construction is quite costly).

Variable naming

Many variables have very short and very cryptic names. This makes reasoning about code a lot harder than it has to be. Trying to understand the code gets interrupted by questions like "What is the difference between s and ss?", where a better choice of names would make their usage obvious.

Magic numbers/strings

There are very many hard-coded strings and numbers, and not always is it obvious what they are meant for.

Example:

settings{ 10, L"spanish", L"german", 0, 15 } // taken from the menu constructor

It isn't obvious at all what all those values represent. Compare this with:

settings{ threshold, source_lang, target_lang, colors::black, colors::white }

With this, I as a reader of the code don't have to guess what all those values mean.

Other stuff

  • Nearly all the work is done in the menu constructor. This makes it very hard to reuse or even test that class.
\$\endgroup\$
  • \$\begingroup\$ i guess it would be better to split the menues in smaller classes? do you have a suggestion? \$\endgroup\$ – Sandro4912 Jun 23 '18 at 9:48
2
\$\begingroup\$
int find(std::wstring s);

Why is this passed by value (making a copy)? And I find it odd that “checks if words are in file and returns row of it” would also modify the file object!

So, should this really be

rownum_t find (const std::wstring& s) const;

? Note also I changed the return type to indicate its actual domain. Even if you just make that a typedef with no compile-time checking, it still is good human documentation.

std::vector<Vocabulary> get_part(const int begin, const int end);

Good that you’re returning the vector by value as a normal return value — many people try to avoid that for outdated reasons. But, again you did not make the function itself const, so you are implying that calling this will modify the file object.

Second, the const in the parameters don’t have meaning there. It is useful in a function definition, but ignored in a declaration so omitted as not being part of the interface contract.

As far as the use of wide strings (note for others: On Windows wchar_t is 16 bits), it is necessary for calling Win32 API functions. But for most uses in the program, it is easier to just use utf8 everywhere.

Note that C++ supports (portable) UTF8 string literals now, so you can write

const char word[] = u8"Füße";

will show up in the source editor and code UTF-8 bytes in the character array, regardless of what ANSI code page is in effect when you run the compiler.

For display: if you are outputting to the Windows Console, the normal 8-bit stream will use the current code page. In the past, I’ve had inconsistent results using Microsoft’s library just switching to the wide calls — but that was years ago; maybe it’s fixed now. I got proper output by using the raw Win32 console output commands instead of the stream wrappers that feed standard output to the console.

Meanwhile, the font loaded in the Console will not display characters outside of the current code page (or closely related ones). You may need to switch to a Unicode font in the Console’s system menu. A simple program to print the alphabet (in each language) will tell you if it is OK to proceed.

\$\endgroup\$
  • \$\begingroup\$ does that mean i can live without using wstring in the whole programm? \$\endgroup\$ – Sandro4912 Jun 23 '18 at 9:44
  • \$\begingroup\$ Use wstring inside your wrapper functions that call the Win32 API xxxxW functions. \$\endgroup\$ – JDługosz Jun 23 '18 at 9:48
  • \$\begingroup\$ what do you mean with Win32 API xxxxW functions? Im sorry i cant follow. You mean if i use console and file io in the program? \$\endgroup\$ – Sandro4912 Jun 25 '18 at 15:35
  • \$\begingroup\$ Windows API functions come in pairs. E.g. you have WriteConsoleA and WriteConsoleW, CreateFileA and CreateFileW. I was referring to the –W forms, which take wchar_t encoded as UTF-16 or sometimes only UCS2. Wrap those functions so you only need to use wchar_t inside the wrappers. \$\endgroup\$ – JDługosz Jun 26 '18 at 18:12

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.