From 058ae78de655aa72fa5310fca41ac5216fb1de24 Mon Sep 17 00:00:00 2001 From: Ahasil Date: Tue, 14 Mar 2017 20:29:57 +0900 Subject: [PATCH] =?UTF-8?q?=EB=8C=80=EA=B3=B5=EC=82=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nana-practice/error_handler.hpp | 42 -- nana-practice/fff.bmp | Bin 1954 -> 0 bytes nana-practice/file_io.cpp | 107 ---- nana-practice/file_io.hpp | 127 ----- nana-practice/file_system.cpp | 58 --- nana-practice/gui.cpp | 280 ---------- nana-practice/gui.hpp | 124 ----- nana-practice.sln => text_overseer.sln | 2 +- text_overseer/error_handler.hpp | 45 ++ text_overseer/file_io.cpp | 98 ++++ text_overseer/file_io.hpp | 113 ++++ text_overseer/file_system.cpp | 48 ++ .../file_system.hpp | 131 +++-- text_overseer/gui.cpp | 490 ++++++++++++++++++ text_overseer/gui.hpp | 189 +++++++ {nana-practice => text_overseer}/main.cpp | 2 +- .../singleton.hpp | 0 .../text_overseer.vcxproj | 1 + .../text_overseer.vcxproj.filters | 0 19 files changed, 1060 insertions(+), 797 deletions(-) delete mode 100644 nana-practice/error_handler.hpp delete mode 100644 nana-practice/fff.bmp delete mode 100644 nana-practice/file_io.cpp delete mode 100644 nana-practice/file_io.hpp delete mode 100644 nana-practice/file_system.cpp delete mode 100644 nana-practice/gui.cpp delete mode 100644 nana-practice/gui.hpp rename nana-practice.sln => text_overseer.sln (90%) create mode 100644 text_overseer/error_handler.hpp create mode 100644 text_overseer/file_io.cpp create mode 100644 text_overseer/file_io.hpp create mode 100644 text_overseer/file_system.cpp rename {nana-practice => text_overseer}/file_system.hpp (69%) create mode 100644 text_overseer/gui.cpp create mode 100644 text_overseer/gui.hpp rename {nana-practice => text_overseer}/main.cpp (70%) rename {nana-practice => text_overseer}/singleton.hpp (100%) rename nana-practice/nana-practice.vcxproj => text_overseer/text_overseer.vcxproj (99%) rename nana-practice/nana-practice.vcxproj.filters => text_overseer/text_overseer.vcxproj.filters (100%) diff --git a/nana-practice/error_handler.hpp b/nana-practice/error_handler.hpp deleted file mode 100644 index 26edf34..0000000 --- a/nana-practice/error_handler.hpp +++ /dev/null @@ -1,42 +0,0 @@ -#pragma once - -#include "file_io.hpp" -#include "singleton.hpp" - -class ErrorHandler final : public Singleton -{ -public: - enum class Interface - { - ignore, - file_log, - gui_msgbox - }; - - enum class Priority - { - info, - warning, - critical - }; - - ErrorHandler() = default; - - void report(Priority priority, int error_code, const char* u8_str) - { - - } - - void report(Priority priority, int error_code, const char* u8_str, const char* postfix_u8_str) - { - - } - - void report(Priority priority, int error_code, const char* u8_str, const wchar_t* postfix_wstr) - { - - } - -private: - -}; \ No newline at end of file diff --git a/nana-practice/fff.bmp b/nana-practice/fff.bmp deleted file mode 100644 index 1ced9a7e9cfa3eea3092b55d510394ba6f43f2a9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1954 zcmZ?rUBu1+24+A~1BfM|n2|vOES>|DKguRAk;# zf`CpzG3(XMndBj$Be0mY#B(vJ2xvV{v;LC;fEJIMMM8j$hSy>eLhJwk(eN4(;YGFb Ui)!U6)yjLS)kDB4ger&u0KgI_2LJ#7 diff --git a/nana-practice/file_io.cpp b/nana-practice/file_io.cpp deleted file mode 100644 index 984fd70..0000000 --- a/nana-practice/file_io.cpp +++ /dev/null @@ -1,107 +0,0 @@ -#include "file_io.hpp" - -#include -#include - -bool FileIO::open(std::ios::openmode mode) // needs std::ios::binary -{ - if (filename_.empty() || file_.is_open()) - return false; - file_openmode_ = mode; - file_.open(filename_, file_openmode_); - return file_.good(); -} - -void FileIO::locale(Locale locale) noexcept -{ - if (locale == Locale::unknown) - file_locale_ = Locale::system; - else - file_locale_ = locale; -} - -FileIO::Locale FileIO::read_bom(std::exception& e) noexcept -{ - std::array buf; - if (!_read_file_check(e)) - return Locale::unknown; - try - { - file_.seekg(0, std::ios::end); - auto file_size = file_.tellg(); - file_.seekg(0, std::ios::beg); - if (file_size > 1) - { - file_.read(&buf[0], 2); - if (buf[0] == bom::k_u16_le[0] && buf[1] == bom::k_u16_le[1]) - return Locale::utf16_le; - if (file_size > 2) - { - file_.read(&buf[2], 1); - if (buf[0] == bom::k_u8[0] && buf[1] == bom::k_u8[1] && buf[2] == bom::k_u8[2]) - return Locale::utf8; - } - } - file_.seekg(0, std::ios::beg); - } - catch (std::exception& _e) - { - e = _e; - } - return Locale::system; -} - -bool FileIO::update_locale_by_read_bom(std::exception& e) noexcept -{ - auto locale = read_bom(e); - if (locale == Locale::unknown) - { - if (*e.what() == '\0') - file_locale_ = Locale::system; - return false; - } - file_locale_ = locale; - return true; -} - -std::string FileIO::read_all(std::exception& e) noexcept -{ - std::string buf; - read_all(buf, 0U, true, e); - return buf; -} - -std::u16string FileIO::read_all_u16(std::exception& e) noexcept -{ - std::u16string buf; - read_all(buf, 0U, true, e); - return buf; -} - -bool FileIO::_read_file_check(std::exception& e) noexcept -{ - auto result = false; - if (!file_) - e = std::runtime_error("invaild file stream"); - else if ((file_openmode_ & std::ios::in) == false) - e = std::runtime_error("file stream is not input mode"); - else if ((file_openmode_ & std::ios::binary) == false) - e = std::runtime_error("file stream is not binary mode"); - else - result = true; - return result; -} - -bool FileIO::_write_file_check(std::exception& e) noexcept -{ - auto result = false; - if (!file_) - e = std::runtime_error("invaild file stream"); - else if ((file_openmode_ & std::ios::out) == false) - e = std::runtime_error("file stream is not output mode"); - else if ((file_openmode_ & std::ios::binary) == false) - e = std::runtime_error("file stream is not binary mode"); - else - result = true; - return result; -} \ No newline at end of file diff --git a/nana-practice/file_io.hpp b/nana-practice/file_io.hpp deleted file mode 100644 index d450b9f..0000000 --- a/nana-practice/file_io.hpp +++ /dev/null @@ -1,127 +0,0 @@ -#pragma once - -#include - -namespace bom // Byte Order Mark -{ - constexpr unsigned char k_u8[3]{ 0xEF, 0xBB, 0xBF }; - constexpr unsigned char k_u16_le[2]{ 0xFF, 0xFE }; - //constexpr unsigned char bom_u16_be[]{ 0xFE, 0xFF }; -} - -class FileIO -{ -public: - enum class Locale - { - unknown, - system, // ANSI(system locale); can be treated as "UTF-8 without BOM" - utf8, // UTF-8 with BOM - utf16_le // UTF-16LE - }; - - FileIO() = default; - FileIO( - const std::wstring& filename, - Locale file_locale = Locale::system - ) : filename_(filename), file_locale_(file_locale) { } - FileIO( - std::wstring&& filename, - Locale file_locale = Locale::system - ) : filename_(std::move(filename)), file_locale_(file_locale) { } - - bool open(std::ios::openmode mode); - void close() noexcept { file_.close(); } - decltype(auto) filename() const noexcept { return filename_; } - - template - void filename(StringT&& filename) noexcept - { - filename_ = std::forward(filename); - } - - Locale locale() const noexcept { return file_locale_; } - void locale(Locale locale) noexcept; - Locale read_bom(std::exception& e) noexcept; // includes _read_file_check() - bool update_locale_by_read_bom(std::exception& e) noexcept; // includes read_bom() - - template - std::size_t read_all( - MutableStringBuffer& buf, - std::size_t buf_size, - bool is_resizable, - std::exception& e - ) noexcept - { - auto do_write_16_bit = false; - if (!update_locale_by_read_bom(e)) // includes _read_file_check() - return 0U; - try - { - if (file_locale_ == Locale::utf16_le) - { - if (sizeof(buf[0]) == 2) - do_write_16_bit = true; - else if (sizeof(buf[0]) != 1) // buffer type size != 1, 2 - throw std::runtime_error("a mutable byte or 16-bit sequence buffer is needed for UTF-16LE"); - } - std::streamoff bom_length = file_.tellg(); - file_.seekg(0, std::ios::end); - auto size = static_cast(file_.tellg() - bom_length); // string length - auto sequence_length = do_write_16_bit ? (size / 2 + size % 2) : size; - if (buf_size < size) - { - if (is_resizable) - _resize_buf(buf, sequence_length); - else - throw std::length_error("data size is larger than the buffer size"); - } - file_.seekg(bom_length, std::ios::beg); - file_.read(reinterpret_cast(&buf[0]), size); - return sequence_length; - } - catch (std::exception& _e) - { - e = _e; - } - return 0U; - } - - std::string read_all(std::exception& e) noexcept; - std::u16string read_all_u16(std::exception& e) noexcept; - - template - void write_all(ConstStringBuffer buf, std::size_t length, std::exception& e) noexcept - { - if (!_write_file_check(e)) - return; - try - { - file_.seekg(0, std::ios::beg); - if (file_locale_ == Locale::utf8) - file_.write(bom::k_u8, 3); - else if (file_locale_ == Locale::utf16_le) - file_.write(bom::k_u16_le, 2); - file_.write(reinterpret_cast(&buf[0]), length); - } - catch (std::exception& _e) - { - e = _e; - } - } - -private: - bool FileIO::_read_file_check(std::exception& e) noexcept; - bool FileIO::_write_file_check(std::exception& e) noexcept; - - template - void _resize_buf(ResizableStringBuffer& buf, std::size_t size) - { - buf.resize(size); // throws std::length_error if the size is too big - } - - std::basic_fstream file_; - std::ios::openmode file_openmode_; - Locale file_locale_{ Locale::system }; - std::wstring filename_; -}; \ No newline at end of file diff --git a/nana-practice/file_system.cpp b/nana-practice/file_system.cpp deleted file mode 100644 index 608678e..0000000 --- a/nana-practice/file_system.cpp +++ /dev/null @@ -1,58 +0,0 @@ -#include "file_system.hpp" - -#include - -namespace file_system -{ - TimePointOfSys file_last_write_time( - const std::wstring& file_path, - boost::system::error_code& ec - ) noexcept - { - std::time_t time = filesys::last_write_time(filesys::path(file_path), ec); - return std::chrono::system_clock::from_time_t(time); // from_time_t(): noexcept - } - - std::pair, std::vector> - search_input_output_files( - const std::wstring& input_filename, - const std::wstring& output_filename, - bool do_always_create_output_path = true, - const std::wstring& dir_path = filesys::current_path().wstring() - ) noexcept - { - std::vector io_files; - std::vector ecs; - std::vector input_files; - - // search for the input files - search_files(input_filename, dir_path, input_files, ecs, true); - - if (do_always_create_output_path) - { - for (const auto& input_file : input_files) - { - auto output_file = input_file.substr(0, input_file.find_last_of(L'/')); - output_file += output_filename; - io_files.push_back(std::make_pair(input_file, std::move(output_file))); - } - } - else - { - // search for the output files - for (const auto& input_file : input_files) - { - auto output_file = input_file.substr(0, input_file.find_last_of(L'/')); - output_file += output_filename; - - boost::system::error_code ec; - if (filesys::is_regular_file(filesys::path(output_file), ec)) - io_files.push_back(std::make_pair(input_file, std::move(output_file))); - else - io_files.push_back(std::make_pair(input_file, std::wstring())); - } - } - - return std::make_pair(io_files, ecs); - } -} \ No newline at end of file diff --git a/nana-practice/gui.cpp b/nana-practice/gui.cpp deleted file mode 100644 index 458e31c..0000000 --- a/nana-practice/gui.cpp +++ /dev/null @@ -1,280 +0,0 @@ -#include "gui.hpp" -#include "error_handler.hpp" - -#include - -using namespace nana; - -AbstractBoxUnit::AbstractBoxUnit(nana::window wd) : nana::panel(wd) -{ - lab_name_.format(true); - lab_name_.text_align(align::left, align_v::center); - - lab_state_.caption(u8"상태"); - lab_name_.format(true); - lab_name_.text_align(align::left, align_v::center); -} - -TextBoxUnit::TextBoxUnit(window wd) : AbstractBoxUnit(wd) -{ - place_.div(R"( - - - >)"); - place_["lab_name"] << lab_name_; - place_["textbox"] << textbox_; - place_["lab_state"] << lab_state_; - - lab_name_.caption(u8"문제에서 제시한 출력"); -} - -bool TextBoxUnit::update_label_state() noexcept -{ - return false; -} - -AbstractIOFileBoxUnit::AbstractIOFileBoxUnit(nana::window wd) : AbstractBoxUnit(wd) -{ - // combo box order relys on FileIO::Locale - combo_locale_.push_back(u8"자동"); - combo_locale_.push_back("ANSI"); - combo_locale_.push_back("UTF-8"); - combo_locale_.push_back("UTF-16LE"); - - // make event - _make_event_combo_locale(); -} - -bool AbstractIOFileBoxUnit::update_label_state() noexcept -{ - return false; -} - -bool AbstractIOFileBoxUnit::register_file(const std::wstring& file_path) noexcept -{ - return false; -} - -bool AbstractIOFileBoxUnit::read_file() noexcept -{ - std::lock_guard g(file_mutex_); - std::exception e; - - if (!file_.open(std::ios::in | std::ios::binary)) - { - ErrorHandler::instance().report( - ErrorHandler::Priority::info, 0, "can't open file to read: ", file_.filename().c_str() - ); - return false; - } - - std::string str = file_.read_all(e); - if (*e.what() != '\0') - { - ErrorHandler::instance().report(ErrorHandler::Priority::info, 0, e.what()); - file_.close(); - return false; - } - auto locale = file_.locale(); - - if (locale == FileIO::Locale::system) - textbox_.caption(charset(str).to_bytes(unicode::utf8)); - else if (locale == FileIO::Locale::utf8) - textbox_.caption(str); - else // UTF-16LE - textbox_.caption(charset(str, unicode::utf16).to_bytes(unicode::utf8)); - - file_.close(); - combo_locale_.text(std::underlying_type_t(locale)); - return true; - - //#if _MSC_VER <= 1900 // Visual Studio bug: https://connect.microsoft.com/VisualStudio/feedback/details/1403302 - // std::wstring_convert, int16_t> converter; - // auto u16_ptr = reinterpret_cast(u16_str.data()); - // textbox_.caption( converter.to_bytes(u16_ptr, u16_ptr + u16_str.size()) ); - // - //#else - // std::wstring_convert, char16_t> converter; - // textbox_.caption(converter.to_bytes(u16_str)); - //#endif -} - -bool AbstractIOFileBoxUnit::check_last_write_time() const noexcept -{ - return false; -} - -void AbstractIOFileBoxUnit::_make_event_combo_locale() noexcept -{ - combo_locale_.events().selected([this](const nana::arg_combox& arg_combo) mutable - { - // update file locale - std::lock_guard g(file_mutex_); - file_.locale(static_cast(arg_combo.widget.option())); - }); -} - -InputFileBoxUnit::InputFileBoxUnit(nana::window wd) : AbstractIOFileBoxUnit(wd) -{ - place_.div(R"( - - > - - - < <> > - > - - > - >)"); - place_["lab_name"] << lab_name_; - place_["btn_folder"] << btn_folder_; - place_["textbox"] << textbox_; - place_["lab_state"] << lab_state_; - place_["combo_locale"] << combo_locale_; - place_["btn_save"] << btn_save_; - - lab_name_.caption(u8"input.txt"); -} - -bool InputFileBoxUnit::write_file() noexcept -{ - std::lock_guard g(file_mutex_); - std::exception e; - - if (!file_.open(std::ios::out | std::ios::binary)) - { - ErrorHandler::instance().report( - ErrorHandler::Priority::info, 0, "can't open file to write: ", file_.filename().c_str() - ); - return false; - } - - std::string buf; - auto locale = file_.locale(); - - if (locale == FileIO::Locale::system) - { - std::wstring wstr = textbox_.caption_wstring(); - std::wstring_convert> converter; - buf = converter.to_bytes(wstr); - } - else if (locale == FileIO::Locale::utf8) - { - buf = textbox_.caption(); - } - else // UTF-16LE - { - std::string u8_str = textbox_.caption(); - buf = charset(u8_str, unicode::utf8).to_bytes(unicode::utf16); - } - - file_.write_all(buf, buf.size(), e); - if (*e.what() != '\0') - { - ErrorHandler::instance().report(ErrorHandler::Priority::info, 0, e.what()); - file_.close(); - return false; - } - - file_.close(); - combo_locale_.text(std::underlying_type_t(locale)); - return true; -} - -OutputFileBoxUnit::OutputFileBoxUnit(nana::window wd) : AbstractIOFileBoxUnit(wd) -{ - place_.div(R"( - - > - - - < <> > - > - - > - >)"); - place_["lab_name"] << lab_name_; - place_["btn_folder"] << btn_folder_; - place_["textbox"] << textbox_; - place_["lab_state"] << lab_state_; - place_["combo_locale"] << combo_locale_; - - lab_name_.caption(u8"output.txt"); -} - -IOTextTapPage::IOTextTapPage(window wd) : panel(wd) -{ - place_.div(R"(< - - < - - | - | - > - - >)"); - place_["input_box"] << input_box_; - place_["output_box"] << output_box_; - place_["answer_box"] << answer_result_box_; - - label_.caption( - file_system::time_duration_to_string( - std::chrono::seconds(10), false, file_system::time_period_strings::k_english) - ); -} - -MainWindow::MainWindow() - : form(API::make_center(640, 400), - appear::decorate()) -{ - caption(std::string(u8"입출력 텍스트 파일 감시기 v") + VERSION_STRING); - - // div - place_.div(R"( - - > - - - >)"); - place_["lab_welcome"] << lab_welcome_; - place_["btn_refresh"] << btn_refresh_; - place_["tabbar"] << tabbar_; - - // widget initiation - lab_welcome_.format(true); - - // initiation of tap pages - tab_pages_.emplace_back(std::make_shared(*this)); - tab_pages_.emplace_back(std::make_shared(*this)); - tab_pages_.emplace_back(std::make_shared(*this)); - tabbar_.append(u8"테스트", *tab_pages_[0]); - tabbar_.append(u8"테스트2", *tab_pages_[1]); - tabbar_.append(u8"테스트3", *tab_pages_[2]); - place_["tab_frame"].fasten(*tab_pages_[0]) - .fasten(*tab_pages_[1]) - .fasten(*tab_pages_[2]); - tabbar_.tab_bgcolor(0, colors::yellow_green); - tabbar_.tab_fgcolor(1, colors::chocolate); - - place_.collocate(); - - // event - this->events().unload([this](const arg_unload& ei) - { - msgbox mb(*this, u8"프로그램 종료", msgbox::yes_no); - mb.icon(msgbox::icon_question) << u8"정말로 종료하시겠습니까?"; - ei.cancel = (mb() == msgbox::pick_no); - }); -} - diff --git a/nana-practice/gui.hpp b/nana-practice/gui.hpp deleted file mode 100644 index 9a15ca7..0000000 --- a/nana-practice/gui.hpp +++ /dev/null @@ -1,124 +0,0 @@ -#pragma once - -#include "file_system.hpp" -#include "file_io.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define VERSION_STRING "0.1" - -class AbstractBoxUnit : public nana::panel -{ -public: - AbstractBoxUnit(nana::window wd); - virtual ~AbstractBoxUnit() = default; - - virtual bool update_label_state() noexcept = 0; - -protected: - nana::place place_{ *this }; - nana::label lab_name_{ *this }; - nana::label lab_state_{ *this }; - nana::textbox textbox_{ *this }; -}; - -class TextBoxUnit : public AbstractBoxUnit -{ -public: - TextBoxUnit(nana::window wd); - - virtual bool update_label_state() noexcept override; - -private: -}; - -class AbstractIOFileBoxUnit : public AbstractBoxUnit -{ -public: - AbstractIOFileBoxUnit(nana::window wd); - virtual ~AbstractIOFileBoxUnit() = default; - - virtual bool update_label_state() noexcept override; - - bool register_file(const std::wstring& file_path) noexcept; - bool read_file() noexcept; - virtual bool write_file() noexcept = 0; - - decltype(auto) last_write_time() const noexcept { return file_system::TimePointOfSys(); } - bool check_last_write_time() const noexcept; - -protected: - nana::button btn_folder_{ *this }; - nana::combox combo_locale_{ *this, u8"파일 인코딩" }; - - FileIO file_; - std::mutex file_mutex_; - file_system::TimePointOfSys last_write_time_; - -private: - void _make_event_combo_locale() noexcept; -}; - -class InputFileBoxUnit : public AbstractIOFileBoxUnit -{ -public: - InputFileBoxUnit(nana::window wd); - - virtual bool write_file() noexcept override; - -private: - nana::button btn_save_{ *this, u8"저장" }; -}; - -class OutputFileBoxUnit : public AbstractIOFileBoxUnit -{ -public: - OutputFileBoxUnit(nana::window wd); - - virtual bool write_file() noexcept override { return false; } - -private: - -}; - -class IOTextTapPage : public nana::panel -{ -public: - IOTextTapPage(nana::window wd); - -private: - nana::place place_{ *this }; - - InputFileBoxUnit input_box_{ *this }; - OutputFileBoxUnit output_box_{ *this }; - TextBoxUnit answer_result_box_{ *this }; - - nana::label label_{ *this, u8"테스트 레이블" }; -}; - -class MainWindow : public nana::form -{ -public: - MainWindow(); - -private: - nana::place place_{ *this }; - nana::label lab_welcome_{ - *this, - u8"이 프로그램은 이 프로그램이 위치한 폴더와 하위 폴더에 있는 " - u8"input.txt와 output.txt의 변동 여부를 실시간으로 감지합니다.\n" - u8"사용한 라이브러리: Nana C++ GUI Library, Boost.Filesystem" - }; - nana::button btn_refresh_{ *this, u8"입출력 파일 다시 찾기" }; - nana::tabbar tabbar_{ *this }; - std::vector>> tab_pages_; -}; \ No newline at end of file diff --git a/nana-practice.sln b/text_overseer.sln similarity index 90% rename from nana-practice.sln rename to text_overseer.sln index 779a118..2728e99 100644 --- a/nana-practice.sln +++ b/text_overseer.sln @@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 14 VisualStudioVersion = 14.0.25420.1 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nana-practice", "nana-practice\nana-practice.vcxproj", "{FC578F77-F990-4554-8CA8-1BE3AFFC2F58}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "text_overseer", "text_overseer\text_overseer.vcxproj", "{FC578F77-F990-4554-8CA8-1BE3AFFC2F58}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/text_overseer/error_handler.hpp b/text_overseer/error_handler.hpp new file mode 100644 index 0000000..538a637 --- /dev/null +++ b/text_overseer/error_handler.hpp @@ -0,0 +1,45 @@ +#pragma once + +#include "file_io.hpp" +#include "singleton.hpp" + +class ErrorHandler; +using ErrorHdr = ErrorHandler; + +class ErrorHandler final : public Singleton +{ +public: + enum class interface + { + ignore, + file_log, + gui_msgbox + }; + + enum class priority + { + info, + warning, + critical + }; + + ErrorHandler() = default; + + void report(priority p, int error_code, const char* u8_str) + { + + } + + void report(priority p, int error_code, const char* u8_str, const char* postfix_u8_str) + { + + } + + void report(priority p, int error_code, const char* u8_str, const wchar_t* postfix_wstr) + { + + } + +private: + +}; \ No newline at end of file diff --git a/text_overseer/file_io.cpp b/text_overseer/file_io.cpp new file mode 100644 index 0000000..0bc3498 --- /dev/null +++ b/text_overseer/file_io.cpp @@ -0,0 +1,98 @@ +#include "file_io.hpp" + +#include +#include + +bool FileIO::open(std::ios::openmode mode) // needs std::ios::binary +{ + if (filename_.empty() || file_.is_open()) + return false; + file_openmode_ = mode; + file_.open(filename_, file_openmode_); + return file_.good(); +} + +void FileIO::locale(encoding locale) noexcept +{ + if (locale == encoding::unknown) + file_locale_ = encoding::system; + else + file_locale_ = locale; +} + +FileIO::encoding FileIO::read_bom() +{ + std::array buf; + if (!_read_file_check()) + return encoding::unknown; + file_.seekg(0, std::ios::end); + auto file_size = file_.tellg(); + file_.seekg(0, std::ios::beg); + if (file_size > 1) + { + file_.read(&buf[0], 2); + if (buf[0] == bom::k_u16_le[0] && buf[1] == bom::k_u16_le[1]) + return encoding::utf16_le; + if (file_size > 2) + { + file_.read(&buf[2], 1); + if (buf[0] == bom::k_u8[0] && buf[1] == bom::k_u8[1] && buf[2] == bom::k_u8[2]) + return encoding::utf8; + } + } + file_.seekg(0, std::ios::beg); + return encoding::system; +} + +bool FileIO::update_locale_by_read_bom() +{ + auto locale = read_bom(); + if (locale == encoding::unknown) + { + // UTF-8 without BOM detection needed (not implemented yet) + file_locale_ = encoding::system; + return false; + } + file_locale_ = locale; + return true; +} + +std::string FileIO::read_all() +{ + std::string buf; + read_all(buf, 0U, true); + return buf; +} + +std::u16string FileIO::read_all_u16() +{ + std::u16string buf; + read_all(buf, 0U, true); + return buf; +} + +bool FileIO::_read_file_check() +{ + if (!file_) + return false; + if ((file_openmode_ & std::ios::in) == false) + throw std::runtime_error("file stream is not input mode"); + else if ((file_openmode_ & std::ios::binary) == false) + throw std::runtime_error("file stream is not binary mode"); + else + return true; + return false; +} + +bool FileIO::_write_file_check() +{ + if (!file_) + return false; + else if ((file_openmode_ & std::ios::out) == false) + throw std::runtime_error("file stream is not output mode"); + else if ((file_openmode_ & std::ios::binary) == false) + throw std::runtime_error("file stream is not binary mode"); + else + return true; + return false; +} \ No newline at end of file diff --git a/text_overseer/file_io.hpp b/text_overseer/file_io.hpp new file mode 100644 index 0000000..f2de02d --- /dev/null +++ b/text_overseer/file_io.hpp @@ -0,0 +1,113 @@ +#pragma once + +#include + +namespace bom // Byte Order Mark +{ + constexpr unsigned char k_u8[3]{ 0xEF, 0xBB, 0xBF }; + constexpr unsigned char k_u16_le[2]{ 0xFF, 0xFE }; + //constexpr unsigned char bom_u16_be[]{ 0xFE, 0xFF }; +} + +class FileIO +{ +public: + enum class encoding + { + unknown, + system, // ANSI(system locale); can be treated as "UTF-8 without BOM" + utf8, // UTF-8 with BOM + utf16_le // UTF-16LE + }; + + FileIO() = default; + FileIO( + const std::wstring& filename, + encoding file_locale = encoding::system + ) : filename_(filename), file_locale_(file_locale) { } + FileIO( + std::wstring&& filename, + encoding file_locale = encoding::system + ) : filename_(std::move(filename)), file_locale_(file_locale) { } + + bool open(std::ios::openmode mode); + void close() noexcept { file_.close(); } + const wchar_t* filename() const noexcept { return filename_.c_str(); } + const std::wstring& filename_wstring() const noexcept { return filename_; } + + template + void filename(StringT&& filename) noexcept + { + filename_ = std::forward(filename); + } + + encoding locale() const noexcept { return file_locale_; } + void locale(encoding locale) noexcept; + encoding read_bom(); // includes _read_file_check() + bool update_locale_by_read_bom(); // includes read_bom() + + template + std::size_t read_all( + MutableStringBuffer& buf, + std::size_t buf_size, + bool is_resizable + ) + { + auto do_write_16_bit = false; + if (!update_locale_by_read_bom()) // includes _read_file_check() + return 0U; + if (file_locale_ == encoding::utf16_le) + { + if (sizeof(buf[0]) == 2) + do_write_16_bit = true; + else if (sizeof(buf[0]) != 1) // buffer type size != 1, 2 + throw std::runtime_error("a mutable byte or 16-bit sequence buffer is needed for UTF-16LE"); + } + std::streamoff bom_length = file_.tellg(); + file_.seekg(0, std::ios::end); + auto size = static_cast(file_.tellg() - bom_length); // string length + auto sequence_length = do_write_16_bit ? (size / 2 + size % 2) : size; + if (buf_size < size) + { + if (is_resizable) + _resize_buf(buf, sequence_length); + else + throw std::length_error("data size is larger than the buffer size"); + } + file_.seekg(bom_length, std::ios::beg); + file_.read(reinterpret_cast(&buf[0]), size); + return sequence_length; + return 0U; + } + + std::string read_all(); + std::u16string read_all_u16(); + + template + void write_all(ConstStringBuffer buf, std::size_t length) + { + if (!_write_file_check()) + return; + file_.seekg(0, std::ios::beg); + if (file_locale_ == encoding::utf8) + file_.write(bom::k_u8, 3); + else if (file_locale_ == encoding::utf16_le) + file_.write(bom::k_u16_le, 2); + file_.write(reinterpret_cast(&buf[0]), length); + } + +private: + bool FileIO::_read_file_check(); + bool FileIO::_write_file_check(); + + template + void _resize_buf(ResizableStringBuffer& buf, std::size_t size) + { + buf.resize(size); // throws std::length_error if the size is too big + } + + std::basic_fstream file_; + std::ios::openmode file_openmode_; + encoding file_locale_{ encoding::system }; + std::wstring filename_; +}; \ No newline at end of file diff --git a/text_overseer/file_system.cpp b/text_overseer/file_system.cpp new file mode 100644 index 0000000..7f5715a --- /dev/null +++ b/text_overseer/file_system.cpp @@ -0,0 +1,48 @@ +#include "file_system.hpp" + +namespace file_system +{ + TimePointOfSys file_last_write_time( + const std::wstring& file_path, + boost::system::error_code& ec + ) noexcept + { + std::time_t time = filesys::last_write_time(filesys::path(file_path), ec); + if (ec) + return TimePointOfSys(); + return std::chrono::system_clock::from_time_t(time); // from_time_t(): noexcept + } + + std::pair, std::vector> + search_input_output_files( + const std::wstring& input_filename, + const std::wstring& output_filename, + bool do_always_create_both_path, + const std::wstring& dir_path + ) noexcept + { + std::vector io_files; + std::vector ecs_with_path; + + search_file_pairs(std::make_pair(input_filename, output_filename), dir_path, io_files, ecs_with_path, true); + + if (do_always_create_both_path) + { + for (auto& file_pair : io_files) + { + if (file_pair.second.empty()) + { + file_pair.second = file_pair.first.substr(0, file_pair.first.find_last_of(L"/\\") + 1) + + output_filename; + } + else if (file_pair.first.empty()) + { + file_pair.first = file_pair.second.substr(0, file_pair.second.find_last_of(L"/\\") + 1) + + input_filename; + } + } + } + + return std::make_pair(io_files, ecs_with_path); + } +} \ No newline at end of file diff --git a/nana-practice/file_system.hpp b/text_overseer/file_system.hpp similarity index 69% rename from nana-practice/file_system.hpp rename to text_overseer/file_system.hpp index f9a3d7d..7191468 100644 --- a/nana-practice/file_system.hpp +++ b/text_overseer/file_system.hpp @@ -4,13 +4,15 @@ #include #include #include -#include // std::pair +#include #include namespace file_system { namespace filesys = boost::filesystem; + using IOFilePathPair = std::pair; + class FilePathErrorCode { public: @@ -28,9 +30,8 @@ namespace file_system boost::system::error_code ec_; }; - using TimePointOfSys = std::chrono::time_point; - - // @param file_path: + using TimePointOfSys = std::chrono::time_point; + TimePointOfSys file_last_write_time( const std::wstring& file_path, boost::system::error_code& ec @@ -43,11 +44,11 @@ namespace file_system // @param ecs_with_path: buffer for error codes with files' path string // @param do_search_subfolders: boolean switch whether to search subfolders or not; // a default value is false - template + template void search_files( const std::wstring& filename, std::wstring dir_path, - MutableContainerForWstring& initialized_buf, + MutableWstringContainer& initialized_buf, std::vector& ecs_with_path, bool do_search_subfolders ) noexcept @@ -63,7 +64,7 @@ namespace file_system for (const filesys::directory_entry& x : filesys::directory_iterator(dir_path, ec)) { ec.clear(); - if (filesys::is_regular_file(x.path(), ec) && x.path().filename() == filename) + if (filesys::is_regular_file(x.path(), ec) && x.path().filename().wstring() == filename) initialized_buf.insert(std::end(initialized_buf), x.path().wstring()); else if (do_search_subfolders && filesys::is_directory(x.path(), ec)) // search on the subfolders @@ -73,7 +74,51 @@ namespace file_system } } - using IOFilePathPair = std::pair; + template + void search_file_pairs( + const IOFilePathPair& filenames, + std::wstring dir_path, + MutableWstringPairContainer& initialized_buf, + std::vector& ecs_with_path, + bool do_search_subfolders + ) noexcept + { + boost::system::error_code ec; + + if (!filesys::is_directory(dir_path, ec)) + { + ecs_with_path.emplace_back(dir_path, ec); + return; + } + + IOFilePathPair result; + std::vector subfolders; + + for (const filesys::directory_entry& x : filesys::directory_iterator(dir_path, ec)) + { + ec.clear(); + if (filesys::is_regular_file(x.path(), ec)) + { + if (x.path().filename().wstring() == filenames.first) + result.first = x.path().wstring(); + else if (x.path().filename().wstring() == filenames.second) + result.second = x.path().wstring(); + } + else if (do_search_subfolders && filesys::is_directory(x.path(), ec)) + { + subfolders.emplace_back(x.path().wstring()); + } + if (ec) + ecs_with_path.emplace_back(x.path().wstring(), ec); + } + + if (!result.first.empty() || !result.second.empty()) + initialized_buf.insert(std::end(initialized_buf), result); + + // search on the subfolders + for (const auto& subfolder : subfolders) + search_file_pairs(filenames, subfolder, initialized_buf, ecs_with_path, true); + } // @param input_filename: file name of input file // @param output_filename: file name of output file @@ -83,17 +128,17 @@ namespace file_system search_input_output_files( const std::wstring& input_filename, const std::wstring& output_filename, - bool do_always_create_output_path, - const std::wstring& dir_path + bool do_always_create_both_path = true, + const std::wstring& dir_path = filesys::current_path().wstring() ) noexcept; namespace time_period_strings { template - struct TimePeriodStrings + struct TimePeriodStringsT { // Most of strings, which mean a period, have blanks at both sides - StringT just_a_few; // has no blank + StringT just_a_moment; // has a blank at back only StringT msecs_singular; StringT msecs_plural; StringT secs_singular; @@ -106,32 +151,31 @@ namespace file_system StringT days_plural; }; - const TimePeriodStrings k_english{ - "just a few", " ms ", " ms ", " second ", " seconds ", + const TimePeriodStringsT k_english{ + "just a moment ", " ms ", " ms ", " second ", " seconds ", " minute ", " minutes ", " hour ", " hours ", " day ", " days " }; - const TimePeriodStrings k_korean_u8{ - u8"몇", " ms ", " ms ", u8" 초 ", u8" 초 ", - u8" 분 ", u8" 분 ", u8" 시간 ", u8" 시간 ", u8" 일 ", u8" 일 " + const TimePeriodStringsT k_korean_u8{ + u8"조금 ", "ms ", "ms ", u8"초 ", u8"초 ", + u8"분 ", u8"분 ", u8"시간 ", u8"시간 ", u8"일 ", u8"일 " }; } template - using TimePeriodStrings = time_period_strings::TimePeriodStrings; + using TimePeriodStringsT = time_period_strings::TimePeriodStringsT; template StringT time_duration_to_string( const std::chrono::duration& duration, bool do_cut_smaller_periods, - const TimePeriodStrings& periods + const TimePeriodStringsT& periods ) { enum class PeriodEnum { msecs, secs, mins, hours, days } base_period; auto counted = std::chrono::duration_cast(duration).count(); - auto do_return_just = false; // return "just ~ " if true if (counted == 0) - do_return_just = true; + return periods.just_a_moment; if (std::ratio_greater_equal>()) base_period = PeriodEnum::days; @@ -157,41 +201,14 @@ namespace file_system // a pointer to the period string const PeriodStringT* period = nullptr; // check if the duration is smaller than a base period - if ((do_return_just || msecs == 0) && base_period == PeriodEnum::msecs) - { - period = &periods.msecs_plural; - do_return_just = true; - } - else if ((do_return_just || secs == 0) && base_period == PeriodEnum::secs) - { - period = &periods.secs_plural; - do_return_just = true; - } - else if ((do_return_just || mins == 0) && base_period == PeriodEnum::mins) - { - period = &periods.mins_plural; - do_return_just = true; - } - else if ((do_return_just || hours == 0) && base_period == PeriodEnum::hours) - { - period = &periods.hours_plural; - do_return_just = true; - } - else if ((do_return_just || days == 0) && base_period == PeriodEnum::days) - { - period = &periods.days_plural; - do_return_just = true; - } - + if ((msecs == 0 && base_period == PeriodEnum::msecs) + || (secs == 0 && base_period == PeriodEnum::secs) + || (mins == 0 && base_period == PeriodEnum::mins) + || (hours == 0 && base_period == PeriodEnum::hours) + || (days == 0 && base_period == PeriodEnum::days)) + return periods.just_a_moment; + StringT str; - - if (do_return_just) // return just a few period(for example: second) - { - str = periods.just_a_few; - str += *period; - return str; - } - auto did_eariler = false; if (days >= 1) @@ -229,12 +246,12 @@ namespace file_system } // a function template of time_duration_to_string for the pointer character types - // it is called if TimePeriodStrings equals CharT* and StringT equals std::basic_string. + // it is called if TimePeriodStringsT equals CharT* and StringT equals std::basic_string. template decltype(auto) time_duration_to_string( const std::chrono::duration& duration, bool do_cut_smaller_periods, - const TimePeriodStrings& periods + const TimePeriodStringsT& periods ) { return time_duration_to_string>(duration, do_cut_smaller_periods, periods); diff --git a/text_overseer/gui.cpp b/text_overseer/gui.cpp new file mode 100644 index 0000000..80ed30f --- /dev/null +++ b/text_overseer/gui.cpp @@ -0,0 +1,490 @@ +#include "gui.hpp" +#include "error_handler.hpp" + +#include + +using namespace nana; + +namespace text_overseer +{ + AbstractBoxUnit::AbstractBoxUnit(nana::window wd) : nana::panel(wd) + { + lab_name_.format(true); + lab_name_.text_align(align::left, align_v::center); + + lab_name_.format(true); + lab_name_.text_align(align::left, align_v::center); + } + + TextBoxUnit::TextBoxUnit(window wd) : AbstractBoxUnit(wd) + { + place_.div( + "" + " " + " " + ">" + ); + place_["lab_name"] << lab_name_; + place_["textbox"] << textbox_; + place_["lab_state"] << lab_state_; + + lab_name_.caption(u8"문제에서 제시한 출력"); + } + + AbstractIOFileBoxUnit::AbstractIOFileBoxUnit(nana::window wd) : AbstractBoxUnit(wd) + { + // combo box order relys on FileIO::encoding + combo_locale_.push_back(u8"자동"); + combo_locale_.push_back("ANSI"); + combo_locale_.push_back("UTF-8"); + combo_locale_.push_back("UTF-16LE"); + + // make event + _make_event_combo_locale(); + } + + bool AbstractIOFileBoxUnit::update_label_state() noexcept + { + auto file_is_changed = _check_last_write_time(); + + if (file_is_changed) + { + bool did_read_file = false; + + for (auto i = 0; i < k_max_read_file_count; i++) + { + did_read_file = _read_file(); + if (did_read_file) + break; + } + + if (did_read_file) + combo_locale_.option(static_cast(file_.locale())); + else + lab_state_.caption(u8"파일을 열지 못했습니다."); + } + else if (!last_write_time_is_vaild_) + { + lab_state_.caption(u8"파일을 찾지 못했습니다."); + return false; + } + + auto term = std::chrono::system_clock::now() - last_write_time_; + auto str = file_system::time_duration_to_string( + std::chrono::duration_cast(term), + true, + file_system::time_period_strings::k_korean_u8 + ); + str += u8"전"; + lab_state_.caption(str); + + return file_is_changed; + } + + bool AbstractIOFileBoxUnit::_read_file() noexcept + { + std::lock_guard g(file_mutex_); + + if (!file_.open(std::ios::in | std::ios::binary)) + { + ErrorHdr::instance().report( + ErrorHdr::priority::info, 0, "can't open file to read", file_.filename() + ); + return false; + } + + std::string str; + FileIO::encoding locale; + + try + { + str = file_.read_all(); + } + catch (std::exception& e) + { + ErrorHdr::instance().report(ErrorHdr::priority::info, 0, e.what()); + file_.close(); + return false; + } + + locale = file_.locale(); + + if (locale == FileIO::encoding::system) + textbox_.caption(charset(str).to_bytes(unicode::utf8)); + else if (locale == FileIO::encoding::utf8) + textbox_.caption(str); + else // UTF-16LE + textbox_.caption(charset(str, unicode::utf16).to_bytes(unicode::utf8)); + + file_.close(); + return true; + + //#if _MSC_VER == 1900 + // // Visual Studio bug: https://connect.microsoft.com/VisualStudio/feedback/details/1403302 + // std::wstring_convert, int16_t> converter; + // auto u16_ptr = reinterpret_cast(u16_str.data()); + // textbox_.caption( converter.to_bytes(u16_ptr, u16_ptr + u16_str.size()) ); + // + //#else + // std::wstring_convert, char16_t> converter; + // textbox_.caption(converter.to_bytes(u16_str)); + //#endif + } + + bool AbstractIOFileBoxUnit::_check_last_write_time() noexcept + { + if (*file_.filename() == '\0') + return false; + + boost::system::error_code ec; + file_system::TimePointOfSys time_gotton; + + for (auto i = 0; i < k_max_check_count_last_file_write; i++) + { + time_gotton = file_system::file_last_write_time(file_.filename(), ec); + if (!ec) + break; + } + + if (last_write_time_ < time_gotton) + { + last_write_time_ = time_gotton; + last_write_time_is_vaild_ = true; + return true; + } + + if (ec) + { + if (last_write_time_is_vaild_) + { + ErrorHdr::instance().report(ErrorHdr::priority::info, ec.value(), ec.message().c_str()); + last_write_time_is_vaild_ = false; + } + } + + return false; + } + + void AbstractIOFileBoxUnit::_make_event_combo_locale() noexcept + { + combo_locale_.events().selected([this](const nana::arg_combox& arg_combo) mutable + { + // update file locale + std::lock_guard g(file_mutex_); + file_.locale(static_cast(arg_combo.widget.option())); + }); + } + + InputFileBoxUnit::InputFileBoxUnit(nana::window wd) : AbstractIOFileBoxUnit(wd) + { + place_.div( + "" + " " + " >" + " " + " " + " < <> >" + " >" + " " + " >" + ">"); + place_["lab_name"] << lab_name_; + place_["btn_folder"] << btn_folder_; + place_["textbox"] << textbox_; + place_["lab_state"] << lab_state_; + place_["combo_locale"] << combo_locale_; + place_["btn_save"] << btn_save_; + + lab_name_.caption(u8"input.txt"); + + btn_save_.events().click([this](const arg_click&) { + this->_write_file(); + }); + } + + bool InputFileBoxUnit::_write_file() noexcept + { + std::lock_guard g(file_mutex_); + + if (!file_.open(std::ios::out | std::ios::binary)) + { + ErrorHdr::instance().report( + ErrorHdr::priority::info, 0, "can't open file to write", file_.filename() + ); + return false; + } + + std::string buf; + auto locale = file_.locale(); + + if (locale == FileIO::encoding::unknown || locale == FileIO::encoding::system) + { + std::wstring wstr = textbox_.caption_wstring(); + using LocalFacet = DeletableFacet>; + std::wstring_convert converter(new LocalFacet("")); + buf = converter.to_bytes(wstr); + } + else if (locale == FileIO::encoding::utf8) + { + buf = textbox_.caption(); + } + else // UTF-16LE + { + std::string u8_str = textbox_.caption(); + buf = charset(u8_str, unicode::utf8).to_bytes(unicode::utf16); + } + + try + { + file_.write_all(buf, buf.size()); + } + catch (std::exception& e) + { + ErrorHdr::instance().report(ErrorHdr::priority::info, 0, e.what()); + file_.close(); + return false; + } + + file_.close(); + combo_locale_.option(std::underlying_type_t(locale)); + return true; + } + + OutputFileBoxUnit::OutputFileBoxUnit(nana::window wd) : AbstractIOFileBoxUnit(wd) + { + place_.div( + "" + " " + " >" + " " + " " + " < <> >" + " >" + " " + " >" + ">"); + place_["lab_name"] << lab_name_; + place_["btn_folder"] << btn_folder_; + place_["textbox"] << textbox_; + place_["lab_state"] << lab_state_; + place_["combo_locale"] << combo_locale_; + + lab_name_.caption(u8"output.txt"); + + combo_locale_.enabled(false); + } + + IOFilesTabPage::IOFilesTabPage(window wd) : panel(wd) + { + place_.div( + "<" + " " + " <" + " " + " | " + " | " + " >" + " " + ">" + ); + place_["input_box"] << input_box_; + place_["output_box"] << output_box_; + place_["answer_box"] << answer_result_box_; + } + + MainWindow::MainWindow() + : form(API::make_center(640, 400), + appear::decorate()) + { + caption(std::string(u8"Text I/O File Overseer v") + VERSION_STRING); + + // div + place_.div( + "" + " " + " >" + " " + " >" + " " + " " + ">" + ); + place_["lab_title"] << lab_title_; + place_["lab_welcome"] << lab_welcome_; + place_["btn_refresh"] << btn_refresh_; + place_["tabbar"] << tabbar_; + place_.collocate(); + + // widget initiation + lab_title_.format(true); + lab_welcome_.format(true); + + // initiation of tap pages + _search_io_files(); + + // make events and etc. + _make_events(); + _make_timer_io_tab_state(); + } + + void MainWindow::make_timers_tabbar_color_animation(std::size_t pos) noexcept + { + const auto func_color_level = [](unsigned int time) -> double { + if (time >= 400 && time <= 1600) + return 1.0 - static_cast(time - 400) / 1200; + return 1.0; + }; + + if (timers_tabbar_color_animation_.size() <= pos) + timers_tabbar_color_animation_.resize(pos + 1); + + if (timers_tabbar_color_animation_[pos].timer_ptr) + return; + + auto timer_tca = std::make_shared(); // tca = tabbar_color_animation + timer_tca->interval(20); + timer_tca->elapse([this, index = pos, interval = timer_tca->interval(), &func_color_level]{ + auto& time = this->timers_tabbar_color_animation_[index].elapsed_time; + auto factor = func_color_level(time); + this->tabbar_.tab_bgcolor( + index, + color(0xff - static_cast(factor * 0x14), + 0xff - static_cast(factor * 0x9e), + 0xff - static_cast(factor * 0xff)) + ); + time += interval; + if (time >= 1600) + this->remove_timer_tabbar_color_animation(index); + }); + + auto& timer_data = timers_tabbar_color_animation_[pos]; + timer_data.timer_ptr = std::move(timer_tca); + timer_data.elapsed_time = 0U; + + timer_data.timer_ptr->start(); + } + + void MainWindow::remove_timer_tabbar_color_animation(std::size_t pos) noexcept + { + auto& timer_data = timers_tabbar_color_animation_[pos]; + if (!timer_data.timer_ptr) + return; + timer_data.timer_ptr->stop(); + timer_data.timer_ptr.reset(); + } + + void MainWindow::_create_io_tab_page( + std::string tab_name_u8, + std::wstring input_filename, + std::wstring output_filename + ) noexcept + { + std::shared_ptr page = std::make_shared(*this); + page->register_files(input_filename, output_filename); + //->update_io_file_box_state(); + place_["tab_frame"].fasten(*page); + tabbar_.push_back(std::move(tab_name_u8)); + auto pos = io_tab_pages_.size(); + tabbar_.attach(pos, *page); + tabbar_.tab_bgcolor(pos, colors::white); + io_tab_pages_.push_back(std::move(page)); + } + + void MainWindow::_make_events() noexcept + { + btn_refresh_.events().click([this](const arg_click&) { + this->_search_io_files(); + }); + + this->events().unload([this](const arg_unload& ei) { + msgbox mb(*this, u8"프로그램 종료", msgbox::yes_no); + mb.icon(msgbox::icon_question) << u8"정말로 종료하시겠습니까?"; + ei.cancel = (mb() == msgbox::pick_no); + }); + } + + void MainWindow::_make_timer_io_tab_state() noexcept + { + timer_io_tab_state_.elapse([this] { + const auto size = io_tab_pages().size(); + for (std::size_t i = 0; i < size; i++) + { + if (io_tab_pages()[i]->update_io_file_box_state()) + this->make_timers_tabbar_color_animation(i); + } + }); + timer_io_tab_state_.interval(100); + timer_io_tab_state_.start(); + } + + void MainWindow::_search_io_files() noexcept + { + auto timer_io_tab_state_was_going = timer_io_tab_state_.started(); + timer_io_tab_state_.stop(); + + for (std::size_t i = 0U; i < timers_tabbar_color_animation_.size(); i++) + remove_timer_tabbar_color_animation(i); + + auto pair_file_pairs_and_path_ecs = file_system::search_input_output_files(L"input.txt", L"output.txt"); + auto file_pairs = std::move(pair_file_pairs_and_path_ecs.first); + const auto path_ecs = std::move(pair_file_pairs_and_path_ecs.second); + + for (const auto& path_ec : path_ecs) + { + const auto ec = path_ec.error_code(); + ErrorHdr::instance().report( + ErrorHdr::priority::info, ec.value(), ec.message().c_str(), path_ec.path_str().c_str() + ); + } + + for (std::size_t i = 0; i < io_tab_pages_.size(); i++) + { + auto are_already_in_tabs = false; + for (std::size_t j = 0; j < file_pairs.size(); j++) + { + if (io_tab_pages_[i]->is_same_files(file_pairs[j].first.c_str(), file_pairs[j].second.c_str())) + { + are_already_in_tabs = true; + file_pairs.erase(file_pairs.begin() + j); + break; + } + } + if (!are_already_in_tabs) + { + tabbar_.erase(i); + io_tab_pages_.erase(io_tab_pages_.begin() + i--); + } + } + + for (auto& file_pair : file_pairs) + { + auto before_last_slash = file_pair.first.find_last_of(L"/\\") - 1; + auto after_second_last_slash = file_pair.first.find_last_of(L"/\\", before_last_slash) + 1; + auto folder_wstr = file_pair.first.substr( + after_second_last_slash, + before_last_slash - after_second_last_slash + 1 + ); + auto folder_u8 = charset(folder_wstr).to_bytes(unicode::utf8); + if (folder_u8.empty()) + folder_u8 = "root"; + _create_io_tab_page(std::move(folder_u8), std::move(file_pair.first), std::move(file_pair.second)); + } + + place_.collocate(); + + if (timer_io_tab_state_was_going) + timer_io_tab_state_.start(); + } +} \ No newline at end of file diff --git a/text_overseer/gui.hpp b/text_overseer/gui.hpp new file mode 100644 index 0000000..cbfde52 --- /dev/null +++ b/text_overseer/gui.hpp @@ -0,0 +1,189 @@ +#pragma once + +#include "file_system.hpp" +#include "file_io.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define VERSION_STRING "0.2" + +namespace text_overseer +{ + constexpr int k_max_read_file_count = 3; + constexpr int k_max_check_count_last_file_write = 5; + + + class AbstractBoxUnit : public nana::panel + { + public: + AbstractBoxUnit(nana::window wd); + virtual ~AbstractBoxUnit() = default; + + virtual bool update_label_state() noexcept = 0; + + protected: + nana::place place_{ *this }; + nana::label lab_name_{ *this }; + nana::label lab_state_{ *this, u8"상태" }; + nana::textbox textbox_{ *this }; + }; + + class TextBoxUnit : public AbstractBoxUnit + { + public: + TextBoxUnit(nana::window wd); + + bool update_label_state() noexcept override { return false; } + + private: + }; + + class AbstractIOFileBoxUnit : public AbstractBoxUnit + { + public: + AbstractIOFileBoxUnit(nana::window wd); + virtual ~AbstractIOFileBoxUnit() = default; + + virtual bool update_label_state() noexcept override; + + bool is_same_file(const wchar_t* file_path) const noexcept + { + return file_.filename_wstring().compare(file_path) == 0; + } + + template + void register_file(StringT&& file_path) noexcept + { + file_.filename(std::forward(file_path)); + } + + decltype(auto) last_write_time() const noexcept { return file_system::TimePointOfSys(); } + + protected: + bool _read_file() noexcept; + virtual bool _write_file() noexcept = 0; + + nana::button btn_folder_{ *this }; + nana::combox combo_locale_{ *this, u8"파일 인코딩" }; + + FileIO file_; + std::mutex file_mutex_; + file_system::TimePointOfSys last_write_time_; + bool last_write_time_is_vaild_{ false }; + + private: + bool _check_last_write_time() noexcept; + void _make_event_combo_locale() noexcept; + }; + + class InputFileBoxUnit : public AbstractIOFileBoxUnit + { + public: + InputFileBoxUnit(nana::window wd); + + protected: + template + struct DeletableFacet : Facet + { + using Facet::Facet; // inherit constructors + ~DeletableFacet() {} + }; + + virtual bool _write_file() noexcept override; + + nana::button btn_save_{ *this, u8"저장" }; + }; + + class OutputFileBoxUnit : public AbstractIOFileBoxUnit + { + public: + OutputFileBoxUnit(nana::window wd); + + protected: + virtual bool _write_file() noexcept override { return false; } + }; + + class IOFilesTabPage : public nana::panel + { + public: + IOFilesTabPage(nana::window wd); + + bool is_same_files(const wchar_t* input_filename, const wchar_t* output_filename) const noexcept + { + return input_box_.is_same_file(input_filename) && output_box_.is_same_file(output_filename); + } + + void register_files(std::wstring input_filename, std::wstring output_filename) + { + input_box_.register_file(input_filename); + output_box_.register_file(output_filename); + } + + decltype(auto) update_io_file_box_state() + { + return input_box_.update_label_state() || output_box_.update_label_state(); + } + + protected: + nana::place place_{ *this }; + + InputFileBoxUnit input_box_{ *this }; + OutputFileBoxUnit output_box_{ *this }; + TextBoxUnit answer_result_box_{ *this }; + }; + + class MainWindow : public nana::form + { + public: + MainWindow(); + + auto& io_tab_pages() noexcept { return io_tab_pages_; } + void make_timers_tabbar_color_animation(std::size_t pos) noexcept; + void remove_timer_tabbar_color_animation(std::size_t pos) noexcept; + + private: + void _create_io_tab_page( + std::string tab_name_u8, + std::wstring input_filename, + std::wstring output_filename + ) noexcept; + void _make_events() noexcept; + void _make_timer_io_tab_state() noexcept; + void _search_io_files() noexcept; + + nana::place place_{ *this }; + nana::label lab_title_{ + *this, + u8"텍스트 입출력 파일 감시기(Text Input/Output File Overseer)" + }; + nana::label lab_welcome_{ + *this, + u8"이 프로그램은 이 프로그램이 위치한 폴더와 하위 폴더에 있는 " + u8"input.txt와 output.txt의 변동 여부를 실시간으로 감시합니다.\n" + u8"사용한 라이브러리: Nana C++ GUI Library, Boost.Filesystem" + }; + nana::button btn_refresh_{ *this, u8"입출력 파일 다시 찾기" }; + nana::tabbar tabbar_{ *this }; + std::vector> io_tab_pages_; + //nana::panel empty_tab_page_{ *this }; + + nana::timer timer_io_tab_state_; + + struct TimersTabbarColorAnimationType + { + std::shared_ptr timer_ptr; + unsigned int elapsed_time; + }; + std::vector timers_tabbar_color_animation_; + }; +} \ No newline at end of file diff --git a/nana-practice/main.cpp b/text_overseer/main.cpp similarity index 70% rename from nana-practice/main.cpp rename to text_overseer/main.cpp index 1c73778..8bf93e4 100644 --- a/nana-practice/main.cpp +++ b/text_overseer/main.cpp @@ -2,7 +2,7 @@ int main() { - MainWindow window; + text_overseer::MainWindow window; window.show(); diff --git a/nana-practice/singleton.hpp b/text_overseer/singleton.hpp similarity index 100% rename from nana-practice/singleton.hpp rename to text_overseer/singleton.hpp diff --git a/nana-practice/nana-practice.vcxproj b/text_overseer/text_overseer.vcxproj similarity index 99% rename from nana-practice/nana-practice.vcxproj rename to text_overseer/text_overseer.vcxproj index 91f6bb9..9c1d057 100644 --- a/nana-practice/nana-practice.vcxproj +++ b/text_overseer/text_overseer.vcxproj @@ -23,6 +23,7 @@ Win32Proj nanapractice 8.1 + text_overseer diff --git a/nana-practice/nana-practice.vcxproj.filters b/text_overseer/text_overseer.vcxproj.filters similarity index 100% rename from nana-practice/nana-practice.vcxproj.filters rename to text_overseer/text_overseer.vcxproj.filters