/* * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc. * Copyright (C) 2016 Furrtek * * This file is part of PortaPack. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #include "ui_setup.hpp" #include "ui_navigation.hpp" #include "ui_touch_calibration.hpp" #include "portapack_persistent_memory.hpp" #include "lpc43xx_cpp.hpp" using namespace lpc43xx; #include "portapack.hpp" using portapack::receiver_model; using namespace portapack; #include "string_format.hpp" #include "ui_font_fixed_8x16.hpp" #include "cpld_update.hpp" namespace ui { SetDateTimeView::SetDateTimeView( NavigationView& nav ) { button_ok.on_select = [&nav, this](Button&){ const auto model = this->form_collect(); const rtc::RTC new_datetime { model.year, model.month, model.day, model.hour, model.minute, model.second }; rtcSetTime(&RTCD1, &new_datetime); nav.pop(); }, button_cancel.on_select = [&nav](Button&){ nav.pop(); }, add_children({ &field_year, &text_slash1, &field_month, &text_slash2, &field_day, &field_hour, &text_colon1, &field_minute, &text_colon2, &field_second, &text_format, &button_ok, &button_cancel, }); rtc::RTC datetime; rtcGetTime(&RTCD1, &datetime); SetDateTimeModel model { datetime.year(), datetime.month(), datetime.day(), datetime.hour(), datetime.minute(), datetime.second() }; form_init(model); } void SetDateTimeView::focus() { button_cancel.focus(); } void SetDateTimeView::form_init(const SetDateTimeModel& model) { field_year.set_value(model.year); field_month.set_value(model.month); field_day.set_value(model.day); field_hour.set_value(model.hour); field_minute.set_value(model.minute); field_second.set_value(model.second); } SetDateTimeModel SetDateTimeView::form_collect() { return { .year = static_cast(field_year.value()), .month = static_cast(field_month.value()), .day = static_cast(field_day.value()), .hour = static_cast(field_hour.value()), .minute = static_cast(field_minute.value()), .second = static_cast(field_second.value()) }; } SetRadioView::SetRadioView( NavigationView& nav ) { button_cancel.on_select = [&nav](Button&){ nav.pop(); }, add_children({ &labels, &field_ppm, &text_description_1, &text_description_2, &text_description_3, &text_description_4, &check_bias, &button_done, &button_cancel }); SetFrequencyCorrectionModel model { static_cast(portapack::persistent_memory::correction_ppb() / 1000) }; form_init(model); check_bias.set_value(receiver_model.antenna_bias()); check_bias.on_select = [this](Checkbox&, bool v) { receiver_model.set_antenna_bias(v); StatusRefreshMessage message { }; EventDispatcher::send_message(message); }; button_done.on_select = [this, &nav](Button&){ const auto model = this->form_collect(); portapack::persistent_memory::set_correction_ppb(model.ppm * 1000); nav.pop(); }; } void SetRadioView::focus() { button_done.focus(); } void SetRadioView::form_init(const SetFrequencyCorrectionModel& model) { field_ppm.set_value(model.ppm); } SetFrequencyCorrectionModel SetRadioView::form_collect() { return { .ppm = static_cast(field_ppm.value()), }; } SetPlayDeadView::SetPlayDeadView(NavigationView& nav) { add_children({ &text_sequence, &button_enter, &button_cancel }); button_enter.on_select = [this, &nav](Button&){ if (!entermode) { sequence = 0; keycount = 0; memset(sequence_txt, '-', 10); text_sequence.set(sequence_txt); entermode = true; button_cancel.hidden(true); set_dirty(); } else { if (sequence == 0x8D1) // U D L R nav.display_modal("Warning", "Default sequence entered !", ABORT, nullptr); else { persistent_memory::set_playdead_sequence(sequence); nav.pop(); } } }; button_enter.on_dir = [this](Button&, KeyEvent key){ if ((entermode == true) && (keycount < 10)) { key_code = static_cast::type>(key); if (key_code == 0) sequence_txt[keycount] = 'R'; else if (key_code == 1) sequence_txt[keycount] = 'L'; else if (key_code == 2) sequence_txt[keycount] = 'D'; else if (key_code == 3) sequence_txt[keycount] = 'U'; text_sequence.set(sequence_txt); sequence = (sequence << 3) | (key_code + 1); keycount++; return true; } return false; }; button_cancel.on_select = [&nav](Button&){ nav.pop(); }; } void SetPlayDeadView::focus() { button_cancel.focus(); } SetUIView::SetUIView(NavigationView& nav) { uint32_t ui_config; add_children({ &checkbox_login, &checkbox_bloff, &options_bloff, &checkbox_showsplash, &button_ok }); ui_config = portapack::persistent_memory::ui_config(); if (ui_config & 1) checkbox_showsplash.set_value(true); if (ui_config & 2) checkbox_bloff.set_value(true); if (ui_config & 16) checkbox_login.set_value(true); options_bloff.set_selected_index((ui_config >> 5) & 7); button_ok.on_select = [&nav, &ui_config, this](Button&) { ui_config &= ~0b10011; if (checkbox_login.value()) { portapack::persistent_memory::set_playing_dead(0x5920C1DF); // Enable ui_config |= (1 << 4); } if (checkbox_showsplash.value()) ui_config |= (1 << 0); if (checkbox_bloff.value()) ui_config |= (1 << 1); portapack::persistent_memory::set_ui_config(ui_config); nav.pop(); }; } void SetUIView::focus() { checkbox_login.focus(); } /*void ModInfoView::on_show() { if (modules_nb) update_infos(0); } void ModInfoView::update_infos(uint8_t modn) { char info_str[27]; char ch; uint8_t c; Point pos = { 0, 0 }; Rect rect = { { 16, 144 }, { 208, 144 } }; info_str[0] = 0; strcat(info_str, module_list[modn].name); text_namestr.set(info_str); info_str[0] = 0; strcat(info_str, to_string_dec_uint(module_list[modn].size).c_str()); strcat(info_str, " bytes"); text_sizestr.set(info_str); info_str[0] = 0; for (c = 0; c < 8; c++) strcat(info_str, to_string_hex(module_list[modn].md5[c], 2).c_str()); text_md5_a.set(info_str); info_str[0] = 0; for (c = 8; c < 16; c++) strcat(info_str, to_string_hex(module_list[modn].md5[c], 2).c_str()); text_md5_b.set(info_str); // TODO: Use ui_console display.fill_rectangle(rect, Color::black()); const Font& font = font::fixed_8x16; const auto line_height = font.line_height(); c = 0; while((ch = module_list[modn].description[c++])) { const auto glyph = font.glyph(ch); const auto advance = glyph.advance(); if((pos.x + advance.x) > rect.width()) { pos.x = 0; pos.y += line_height; } const Point pos_glyph { static_cast(rect.pos.x + pos.x), static_cast(rect.pos.y + pos.y) }; display.draw_glyph(pos_glyph, glyph, Color::white(), Color::black()); pos.x += advance.x; } } ModInfoView::ModInfoView(NavigationView& nav) { const char magic[4] = {'P', 'P', 'M', ' '}; UINT bw; uint8_t i; char read_buf[16]; char info_str[25]; FILINFO modinfo; FIL modfile; DIR rootdir; FRESULT res; uint8_t c; using option_t = std::pair; using options_t = std::vector; option_t opt; options_t opts; static constexpr Style style_orange { .font = font::fixed_8x16, .background = Color::black(), .foreground = Color::orange(), }; add_children({{ &text_modcount, &text_name, &text_namestr, &text_size, &text_sizestr, &text_md5, &text_md5_a, &text_md5_b, &button_ok }}); text_name.set_style(&style_orange); text_size.set_style(&style_orange); text_md5.set_style(&style_orange); // TODO: Find a way to merge this with m4_load_image() in m4_startup.cpp // Scan SD card root directory for files starting with the magic bytes c = 0; if (f_opendir(&rootdir, "/") == FR_OK) { for (;;) { res = f_readdir(&rootdir, &modinfo); if (res != FR_OK || modinfo.fname[0] == 0) break; // Reached last file, abort // Only care about files with .bin extension if ((!(modinfo.fattrib & AM_DIR)) && (modinfo.fname[9] == 'B') && (modinfo.fname[10] == 'I') && (modinfo.fname[11] == 'N')) { f_open(&modfile, modinfo.fname, FA_OPEN_EXISTING | FA_READ); // Magic bytes check f_read(&modfile, &read_buf, 4, &bw); for (i = 0; i < 4; i++) if (read_buf[i] != magic[i]) break; if (i == 4) { memcpy(&module_list[c].filename, modinfo.fname, 8); module_list[c].filename[8] = 0; f_lseek(&modfile, 4); f_read(&modfile, &module_list[c].version, 2, &bw); f_lseek(&modfile, 6); f_read(&modfile, &module_list[c].size, 4, &bw); f_lseek(&modfile, 10); f_read(&modfile, &module_list[c].name, 16, &bw); f_lseek(&modfile, 26); f_read(&modfile, &module_list[c].md5, 16, &bw); f_lseek(&modfile, 42); f_read(&modfile, &module_list[c].description, 214, &bw); f_lseek(&modfile, 256); // Sanitize module_list[c].name[15] = 0; module_list[c].description[213] = 0; memcpy(info_str, module_list[c].filename, 16); strcat(info_str, "(V"); strcat(info_str, to_string_dec_uint(module_list[c].version, 4, '0').c_str()); strcat(info_str, ")"); while(strlen(info_str) < 24) strcat(info_str, " "); opt = std::make_pair(info_str, c); opts.insert(opts.end(), opt); c++; } f_close(&modfile); } if (c == 8) break; } } f_closedir(&rootdir); modules_nb = c; if (modules_nb) { strcpy(info_str, "Found "); strcat(info_str, to_string_dec_uint(modules_nb, 1).c_str()); strcat(info_str, " module(s)"); text_modcount.set(info_str); option_modules.set_options(opts); add_child(&option_modules); option_modules.on_change = [this](size_t n, OptionsField::value_t v) { (void)n; update_infos(v); }; } else { strcpy(info_str, "No modules found"); text_modcount.set(info_str); } button_ok.on_select = [&nav,this](Button&){ nav.pop(); }; } void ModInfoView::focus() { if (modules_nb) option_modules.focus(); else button_ok.focus(); }*/ SetupMenuView::SetupMenuView(NavigationView& nav) { add_items({ { "Radio", ui::Color::white(), nullptr, [&nav](){ nav.push(); } }, { "UI", ui::Color::white(), nullptr, [&nav](){ nav.push(); } }, //{ "SD card modules", ui::Color::white(), [&nav](){ nav.push(); } }, { "Date/Time", ui::Color::white(), nullptr, [&nav](){ nav.push(); } }, //{ "Frequency correction", ui::Color::white(), nullptr, [&nav](){ nav.push(); } }, //{ "Antenna Bias Voltage", ui::Color::white(), nullptr, [&nav](){ nav.push(); } }, { "Touch screen", ui::Color::white(), nullptr, [&nav](){ nav.push(); } }, { "Play dead", ui::Color::white(), &bitmap_icon_playdead, [&nav](){ nav.push(); } } }); on_left = [&nav](){ nav.pop(); }; } } /* namespace ui */