// device liquid crystal display menu // // Date: 2024-01-31 // // Keyboard layout: Four buttons // // Enter - to access current selection // // Up - to select previous item // // Down - to select next item // // Stop - to stop scanning right now #pragma once #include #include #include #include #include #include #include #include #define MENU_ID_RETURN -1 // ID of menu item that return to parent #define LINK_DEFINE(x, y) x##y #define MEMNU_CMD_HANDLER_PARAM dev_menu* menu, int id #define MENU_CMD_HANDLER_RET bool #define MENU_CMD_CALLBACK std::function class dev_menu : public refer { dev_menu* parent_ = nullptr; typedef struct _menu_item { std::string text; bool leaf; union { int id; // valid on leaf dev_menu *child; // valid on !leaf }; }MITEM; std::vector items_; int init_pos_ = 0; int cur_ = 0; // focus item int sel_ = -1; // current checked item bool check_item_ = false; bool need_ret_parent_ = true; std::function reset_pos_ = std::function(); int find_item(const char* text); public: dev_menu(bool check_item = false, bool need_ret_parent = true); protected: virtual ~dev_menu(); void set_parent(dev_menu* parent); public: bool add_menu(const char* text, int id); bool add_menu(const char* text, dev_menu* submenu); bool remove_menu(const char* text); void set_need_return_parent(bool need); bool move_to(bool next); // true - move to next, false - move to previous. if at end position of move direction, return false bool select(const char* txt, bool apply_cur = false); void reset_pos(void); void set_default_pos(int pos = 0); void set_default_pos(std::function f); // Function: access current menu // // Parameter: id - to receive ID of current menu item if leaf, fill '-1' if a submenu // // Return: current menu, user should call 'release' after use dev_menu* enter(int* id); // Function: get all menus text // // Parameter: text - to receive the menu text // // sel - to receive current setting index // // Return: current menu index int get_menu_text(std::vector& text, int& sel); }; class KeyMonitor; class Lcd; class ui_mgr : public refer { dev_menu* root_ = nullptr; dev_menu* cur_ = nullptr; const int seq_hour_ = 24; const int seq_min_ = 84; volatile bool menu_mode_ = false; // whether LCD is displaying menu volatile bool run_ = true; volatile bool ready_enable_ = true; bool scanning_ = false; bool stopped_by_ui_ = false; // enable ready message when error occurs in auto-scan bool int_by_status_ = false; bool peer_connected_ = false; int scan_mode_ = 0; int paper_total_ = 0; int paper_cnt_ = 0; SIZE font_size_ = {16, 16}; POINT hold_pos_ = {0, 0}; std::map handler_; std::unique_ptr lcd_; std::unique_ptr keyboard_; class permanent_data : public refer { uint64_t history_cnt_ = 0; uint32_t roller_cnt_ = 0; uint32_t adden_ = 0; uint32_t speed_ = 0; std::string root_; const std::string his_name_ = "history-count"; const std::string rol_name_ = "roller-count"; #pragma pack(push) #pragma pack(1) typedef struct _speed_time { uint64_t ms; uint32_t speed; }SPEEDTM, *LPSPEEDTM; typedef struct _times { uint64_t history; uint64_t roller; }TIMES; #pragma pack(pop) std::map speed_times_; public: permanent_data() { root_ = utils::get_local_data_path() + PATH_SEPARATOR + "record"; utils::create_folder(root_.c_str()); root_ += PATH_SEPARATOR; safe_file file((root_ + his_name_).c_str()); std::string cont(file.load()); char *ptr = &cont[0]; LPSPEEDTM pspt = nullptr; if(!cont.empty()) { history_cnt_ = *(uint64_t*)ptr; ptr += sizeof(history_cnt_); pspt = (LPSPEEDTM)ptr; for(int i = 0; i < (cont.length() - sizeof(history_cnt_)) / sizeof(*pspt); ++i) { TIMES t = {0}; t.history = pspt[i].ms; speed_times_[pspt[i].speed] = t; } } else { // test data: TIMES t = {0}; history_cnt_ = 711; t.history = 0x65642; speed_times_[816] = t; } file.set_file_path((root_ + rol_name_).c_str()); cont = file.load(); if(!cont.empty()) { ptr = &cont[0]; roller_cnt_ = *(uint32_t*)ptr; ptr += sizeof(roller_cnt_); pspt = (LPSPEEDTM)ptr; for(int i = 0; i < (cont.length() - sizeof(roller_cnt_)) / sizeof(*pspt); ++i) { if(speed_times_.count(pspt[i].speed)) { speed_times_[pspt[i].speed].roller = pspt[i].ms; } else { TIMES t = {0}; t.history = t.roller = pspt[i].ms; speed_times_[pspt[i].speed] = t; } } } else { // test data: roller_cnt_ = 257; speed_times_[816].roller = 0x2e54b; } } protected: virtual ~permanent_data() { save(); } public: uint64_t history_count(void) { return history_cnt_; } uint32_t roller_count(void) { return roller_cnt_; } uint32_t increase_count(uint32_t run_ms, uint64_t* hiscnt = nullptr) { ++adden_; ++roller_cnt_; ++history_cnt_; speed_times_[speed_].history += run_ms; speed_times_[speed_].roller += run_ms; if(hiscnt) *hiscnt = history_cnt_; if(adden_ > 10) { save(); adden_ = 0; } return roller_cnt_; } uint32_t clear_roller_count(void) { roller_cnt_ = 0; for(auto& v: speed_times_) v.second.roller = 0; save(); return roller_cnt_; } void set_speed(uint32_t speed) { speed_ = speed; if(speed_times_.count(speed) == 0) { TIMES t = {0}; speed_times_[speed] = t; } } void save(void) { safe_file file((root_ + his_name_).c_str()); std::string cont((char*)&history_cnt_, sizeof(history_cnt_)); for(auto& v: speed_times_) { SPEEDTM stm = {0}; stm.speed = v.first; stm.ms = v.second.history; cont += std::string((char*)&stm, sizeof(stm)); } file.save(cont.c_str(), cont.length()); cont = std::string((char*)&roller_cnt_, sizeof(roller_cnt_)); for(auto& v: speed_times_) { if(v.second.roller) { SPEEDTM stm = {0}; stm.speed = v.first; stm.ms = v.second.roller; cont += std::string((char*)&stm, sizeof(stm)); } } file.set_file_path((root_ + rol_name_).c_str()); file.save(cont.c_str(), cont.length()); } }; refer_guard perm_data_; bool do_menu_command(int cmd); // return whether should hold UI ? void init(void); void clear(void); void refresh_lcd(bool cur_at_top); void move_to(bool next); void enter(void); enum dispaly_method { DISP_METHOD_CLEAR = 0, DISP_METHOD_PART_LINE, DISP_METHOD_WHOLE_LINE, }; typedef struct _disp_data { uint8_t x; uint8_t y : 6; uint8_t method : 2; uint8_t cnt; // clear width when ptr[0] == nullptr uint8_t mask; // clear height when ptr[0] == nullptr uint8_t *ptr[16]; }DISPDATA; MUTEX ready_lck_; chronograph ready_watch_; DISPDATA ready_; safe_fifo disp_data_; safe_thread disp_thrd_; void thread_display(void); void display_scan_title(void); void display_ready(bool time_only); bool display_status(void* data); void set_ready_status_enabled(bool enable); // disable ready message, the last message will display until keyboard event triggered void reset_ready_watch(void); int get_ready_watch_ms(void); public: ui_mgr(); protected: virtual ~ui_mgr(); public: void key_event(int key); };