2015-07-08 08:39:24 -07:00
/*
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
2017-01-29 06:50:48 +00:00
* Copyright (C) 2016 Furrtek
2015-07-08 08:39:24 -07:00
*
* 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.
*/
2023-07-24 09:09:22 -07:00
#include "irq_controls.hpp"
#include "max283x.hpp"
#include "portapack.hpp"
#include "string_format.hpp"
2015-07-08 08:39:24 -07:00
#include "ui_receiver.hpp"
2016-12-06 13:31:38 +01:00
#include "ui_freqman.hpp"
2015-07-08 08:39:24 -07:00
2015-08-01 13:42:27 -07:00
using namespace portapack ;
2015-07-08 08:39:24 -07:00
namespace ui {
/* FrequencyField ********************************************************/
FrequencyField :: FrequencyField (
2023-05-19 08:16:05 +12:00
const Point parent_pos )
: Widget {{ parent_pos , { 8 * 10 , 16 }}},
length_ { 11 },
2023-07-24 09:09:22 -07:00
range_ { rf :: tuning_range } {
initial_switch_config_ = get_switches_long_press_config ();
2023-05-19 08:16:05 +12:00
set_focusable ( true );
2015-07-08 08:39:24 -07:00
}
2023-10-23 01:58:14 -04:00
FrequencyField :: FrequencyField (
const Point parent_pos ,
const rf :: FrequencyRange range )
: Widget {{ parent_pos , { 8 * 10 , 16 }}},
length_ { 11 },
range_ { range } {
initial_switch_config_ = get_switches_long_press_config ();
set_focusable ( true );
}
2023-07-24 09:09:22 -07:00
FrequencyField ::~ FrequencyField () {
2023-07-25 06:06:13 -07:00
reset_switch_config ();
2023-07-24 09:09:22 -07:00
}
2015-07-08 08:39:24 -07:00
rf :: Frequency FrequencyField :: value () const {
2023-05-19 08:16:05 +12:00
return value_ ;
2015-07-08 08:39:24 -07:00
}
void FrequencyField :: set_value ( rf :: Frequency new_value ) {
2023-07-25 06:06:13 -07:00
// While in digit mode, don't update if the value is
// out of range. That way going out of range doesn't
// cause all the other digits to reset.
if ( digit_mode_ && ! range_ . contains_inc ( new_value ))
return ;
2023-05-19 08:16:05 +12:00
new_value = clamp_value ( new_value );
2015-07-08 08:39:24 -07:00
2023-05-19 08:16:05 +12:00
if ( new_value != value_ ) {
value_ = new_value ;
if ( on_change ) {
on_change ( value_ );
}
set_dirty ();
}
2015-07-08 08:39:24 -07:00
}
void FrequencyField :: set_step ( rf :: Frequency new_value ) {
2023-05-19 08:16:05 +12:00
// TODO: Quantize current frequency to a step of the new size?
2023-07-24 09:09:22 -07:00
step_ = new_value ;
2015-07-08 08:39:24 -07:00
}
2023-07-25 06:06:13 -07:00
void FrequencyField :: set_allow_digit_mode ( bool allowed ) {
allow_digit_mode_ = allowed ;
if ( ! allowed && digit_mode_ ) {
digit_mode_ = false ;
reset_switch_config ();
set_dirty ();
}
}
2024-01-05 07:43:30 +01:00
void FrequencyField :: getAccessibilityText ( std :: string & result ) {
result = to_string_dec_int ( value_ );
}
void FrequencyField :: getWidgetName ( std :: string & result ) {
result = "FrequencyField" ;
}
2015-07-08 08:39:24 -07:00
void FrequencyField :: paint ( Painter & painter ) {
2023-07-24 09:09:22 -07:00
const auto str_value = to_string_short_freq ( value_ );
2023-05-19 08:16:05 +12:00
const auto paint_style = has_focus () ? style (). invert () : style ();
2015-07-08 08:39:24 -07:00
2023-05-19 08:16:05 +12:00
painter . draw_string (
screen_pos (),
paint_style ,
str_value );
2023-07-24 09:09:22 -07:00
// Highlight current digit in digit_mode.
if ( digit_mode_ ) {
auto p = screen_pos ();
p += { digit_ * char_width , 0 };
2024-05-27 21:02:52 +02:00
painter . draw_char ( p , * Theme :: getInstance () -> option_active , str_value [ digit_ ]);
2023-07-24 09:09:22 -07:00
}
2015-07-08 08:39:24 -07:00
}
2023-07-24 09:09:22 -07:00
bool FrequencyField :: on_key ( KeyEvent event ) {
if ( event == KeyEvent :: Select ) {
// Toggle 'digit' mode with long-press.
if ( digit_mode_ || key_is_long_pressed ( event )) {
digit_mode_ = ! digit_mode_ ;
set_dirty ();
return true ;
}
2023-05-19 08:16:05 +12:00
if ( on_edit ) {
on_edit ();
return true ;
}
}
2023-07-24 09:09:22 -07:00
if ( digit_mode_ ) {
2023-07-25 06:06:13 -07:00
constexpr uint8_t decimal_pos = 4 ;
int8_t delta = 0 ;
2023-07-24 09:09:22 -07:00
switch ( event ) {
case KeyEvent :: Up :
set_value ( value_ + digit_step ());
break ;
case KeyEvent :: Down :
set_value ( value_ - digit_step ());
break ;
case KeyEvent :: Left :
2023-07-25 06:06:13 -07:00
delta = - 1 ;
2023-07-24 09:09:22 -07:00
break ;
case KeyEvent :: Right :
2023-07-25 06:06:13 -07:00
delta = 1 ;
2023-07-24 09:09:22 -07:00
break ;
default :
return false ;
}
2023-07-25 06:06:13 -07:00
if ( delta != 0 ) {
digit_ += delta ;
// If on decimal, skip it.
if ( digit_ == decimal_pos )
digit_ += delta ;
// Otherwise ensure in bounds.
else
digit_ = clip < int8_t > ( digit_ , 0 , 8 );
set_dirty ();
}
2023-07-24 09:09:22 -07:00
return true ;
}
2023-05-19 08:16:05 +12:00
return false ;
2015-07-08 08:39:24 -07:00
}
2024-01-04 17:36:31 +01:00
bool FrequencyField :: on_keyboard ( KeyboardEvent key ) {
if ( key == '+' || key == ' ' ) return on_encoder ( 1 );
if ( key == '-' ) return on_encoder ( - 1 );
return false ;
}
2015-07-08 08:39:24 -07:00
bool FrequencyField :: on_encoder ( const EncoderEvent delta ) {
2023-07-24 09:09:22 -07:00
if ( digit_mode_ )
set_value ( value_ + ( delta * digit_step ()));
else
set_value ( value_ + ( delta * step_ ));
2023-05-19 08:16:05 +12:00
return true ;
2015-07-08 08:39:24 -07:00
}
bool FrequencyField :: on_touch ( const TouchEvent event ) {
2023-05-19 08:16:05 +12:00
if ( event . type == TouchEvent :: Type :: Start ) {
focus ();
}
return true ;
2015-07-08 08:39:24 -07:00
}
void FrequencyField :: on_focus () {
2023-07-25 06:06:13 -07:00
if ( on_show_options )
2023-05-19 08:16:05 +12:00
on_show_options ();
2023-07-24 09:09:22 -07:00
2023-07-25 06:06:13 -07:00
enable_switch_config ();
2023-07-24 09:09:22 -07:00
}
void FrequencyField :: on_blur () {
2023-07-25 06:06:13 -07:00
reset_switch_config ();
2023-07-24 09:09:22 -07:00
}
rf :: Frequency FrequencyField :: digit_step () const {
constexpr rf :: Frequency steps [] = {
1'000'000'000 ,
100'000'000 ,
10'000'000 ,
1'000'000 ,
0 , // Decimal point position.
100'000 ,
10'000 ,
1'000 ,
100 ,
};
return digit_ < std :: size ( steps ) ? steps [ digit_ ] : 0 ;
2015-07-08 08:39:24 -07:00
}
rf :: Frequency FrequencyField :: clamp_value ( rf :: Frequency value ) {
2023-07-24 09:09:22 -07:00
return range_ . clip ( value );
2015-07-08 08:39:24 -07:00
}
2023-07-25 06:06:13 -07:00
void FrequencyField :: enable_switch_config () {
// Don't enable long press testing when not allowed.
if ( ! allow_digit_mode_ )
return ;
// Enable long press on "Select".
SwitchesState config ;
config [ toUType ( Switch :: Sel )] = true ;
set_switches_long_press_config ( config );
}
void FrequencyField :: reset_switch_config () {
set_switches_long_press_config ( initial_switch_config_ );
}
2015-07-08 08:39:24 -07:00
/* FrequencyKeypadView ***************************************************/
2019-10-29 23:53:54 +02:00
bool FrequencyKeypadView :: on_encoder ( const EncoderEvent delta ) {
2023-05-19 08:16:05 +12:00
focused_button += delta ;
if ( focused_button < 0 ) {
focused_button = buttons . size () - 1 ;
} else if ( focused_button >= ( int16_t ) buttons . size ()) {
focused_button = 0 ;
}
buttons [ focused_button ]. focus ();
return true ;
2019-10-29 23:53:54 +02:00
}
2015-07-08 08:39:24 -07:00
FrequencyKeypadView :: FrequencyKeypadView (
2023-05-19 08:16:05 +12:00
NavigationView & nav ,
const rf :: Frequency value ) {
2025-10-03 19:10:10 +02:00
button_w = screen_width / 3 ;
2023-05-19 08:16:05 +12:00
add_child ( & text_value );
2015-07-08 08:39:24 -07:00
2023-05-19 08:16:05 +12:00
const auto button_fn = [ this ]( Button & button ) {
this -> on_button ( button );
};
2015-07-08 08:39:24 -07:00
2023-05-19 08:16:05 +12:00
const char * const key_caps = "123456789<0." ;
2015-07-08 08:39:24 -07:00
2023-05-19 08:16:05 +12:00
int n = 0 ;
for ( auto & button : buttons ) {
add_child ( & button );
const std :: string label {
key_caps [ n ]};
button . id = n ;
button . on_highlight = [ this ]( Button & button ) {
focused_button = button . id ;
};
button . on_select = button_fn ;
button . set_parent_rect ({( n % 3 ) * button_w ,
2025-08-13 20:25:21 +08:00
( n / 3 ) * button_h + 25 ,
2023-05-19 08:16:05 +12:00
button_w , button_h });
button . set_text ( label );
n ++ ;
}
2015-07-08 08:39:24 -07:00
2025-08-13 20:25:21 +08:00
add_children ({ & button_save_ghz ,
& button_save_khz ,
& button_save_mhz ,
2023-05-19 08:16:05 +12:00
& button_load ,
2025-08-13 20:25:21 +08:00
& button_clear ,
& button_done_ghz ,
& button_done_mhz ,
& button_done_khz });
2015-07-08 08:39:24 -07:00
2025-08-13 20:25:21 +08:00
button_save_mhz . on_select = [ this , & nav ]( Button & ) {
nav . push < FrequencySaveView > ( this -> value ( FrequencyUnit :: MHZ )); // pass as mhz and handle unit convert logic there, cuz idk if pass others would loss something
2023-05-19 08:16:05 +12:00
};
2025-08-13 20:25:21 +08:00
button_save_ghz . on_select = [ this , & nav ]( Button & ) {
nav . push < FrequencySaveView > ( this -> value ( FrequencyUnit :: GHZ ));
};
button_save_khz . on_select = [ this , & nav ]( Button & ) {
nav . push < FrequencySaveView > ( this -> value ( FrequencyUnit :: KHZ ));
};
2023-05-19 08:16:05 +12:00
button_load . on_select = [ this , & nav ]( Button & ) {
auto load_view = nav . push < FrequencyLoadView > ();
load_view -> on_frequency_loaded = [ this ]( rf :: Frequency value ) {
set_value ( value );
};
};
2025-08-13 20:25:21 +08:00
button_clear . on_select = [ this ]( Button & ) {
mhz . clear ();
submhz . clear ();
clear_field_if_digits_entered = true ;
if ( state == State :: DigitSubMHz ) {
state = State :: DigitMHz ;
}
draw_input_hint ();
update_text ();
};
2023-05-19 08:16:05 +12:00
2025-08-13 20:25:21 +08:00
button_done_ghz . on_select = [ this , & nav ]( Button & ) {
2023-09-27 12:03:02 -07:00
if ( on_changed )
2025-08-13 20:25:21 +08:00
on_changed ( this -> value ( FrequencyUnit :: GHZ ));
nav . pop ();
};
button_done_mhz . on_select = [ this , & nav ]( Button & ) {
if ( on_changed )
on_changed ( this -> value ( FrequencyUnit :: MHZ ));
nav . pop ();
};
button_done_khz . on_select = [ this , & nav ]( Button & ) {
if ( on_changed )
on_changed ( this -> value ( FrequencyUnit :: KHZ ));
2023-05-19 08:16:05 +12:00
nav . pop ();
};
set_value ( value );
2015-07-08 08:39:24 -07:00
}
void FrequencyKeypadView :: focus () {
2025-08-13 20:25:21 +08:00
button_done_mhz . focus ();
2015-07-08 08:39:24 -07:00
}
2025-08-13 20:25:21 +08:00
rf :: Frequency FrequencyKeypadView :: value ( FrequencyUnit frequency_uni ) const {
switch ( frequency_uni ) {
case FrequencyUnit :: GHZ :
return mhz . as_int () * 1000000000ULL + submhz . as_int () * submhz_base * 1000 ;
case FrequencyUnit :: MHZ :
return mhz . as_int () * 1000000ULL + submhz . as_int () * submhz_base ;
case FrequencyUnit :: KHZ :
return mhz . as_int () * 1000ULL + submhz . as_int () * submhz_base / 1000 ;
default :
return mhz . as_int () * 1000000ULL + submhz . as_int () * submhz_base ;
}
2015-07-08 08:39:24 -07:00
}
void FrequencyKeypadView :: set_value ( const rf :: Frequency new_value ) {
2023-05-19 08:16:05 +12:00
mhz . set ( new_value / 1000000 );
mhz . remove_leading_zeros ();
2015-07-08 08:39:24 -07:00
2023-05-19 08:16:05 +12:00
submhz . set (( new_value % 1000000 ) / submhz_base );
submhz . remove_trailing_zeros ();
2015-07-08 08:39:24 -07:00
2023-05-19 08:16:05 +12:00
update_text ();
2015-07-08 08:39:24 -07:00
}
void FrequencyKeypadView :: on_button ( Button & button ) {
2023-05-19 08:16:05 +12:00
const auto s = button . text ();
if ( s == "." ) {
field_toggle ();
} else if ( s == "<" ) {
digit_delete ();
} else {
digit_add ( s [ 0 ]);
}
update_text ();
2015-07-08 08:39:24 -07:00
}
2024-01-04 17:36:31 +01:00
bool FrequencyKeypadView :: on_keyboard ( const KeyboardEvent key ) {
if ( key == 8 ) {
digit_delete ();
update_text ();
return true ;
}
if ( key >= '0' && key <= '9' ) {
digit_add ( key );
update_text ();
return true ;
}
if ( key == '.' ) {
field_toggle ();
update_text ();
return true ;
}
return false ;
}
2015-07-08 08:39:24 -07:00
void FrequencyKeypadView :: digit_add ( const char c ) {
2023-05-19 08:16:05 +12:00
if ( state == State :: DigitMHz ) {
if ( clear_field_if_digits_entered ) {
mhz . clear ();
}
mhz . add_digit ( c );
} else {
submhz . add_digit ( c );
}
clear_field_if_digits_entered = false ;
2015-07-08 08:39:24 -07:00
}
void FrequencyKeypadView :: digit_delete () {
2023-05-19 08:16:05 +12:00
if ( state == State :: DigitMHz ) {
mhz . delete_digit ();
} else {
submhz . delete_digit ();
}
2015-07-08 08:39:24 -07:00
}
void FrequencyKeypadView :: field_toggle () {
2023-05-19 08:16:05 +12:00
if ( state == State :: DigitMHz ) {
state = State :: DigitSubMHz ;
submhz . clear ();
} else {
state = State :: DigitMHz ;
clear_field_if_digits_entered = true ;
}
2025-08-13 20:25:21 +08:00
draw_input_hint ();
}
void FrequencyKeypadView :: draw_input_hint () {
set_dirty ();
}
void FrequencyKeypadView :: paint ( Painter & painter ) {
View :: paint ( painter );
const bool s = state == State :: DigitMHz ;
painter . draw_hline (
{ 0 , 36 },
8 * 4 ,
s ? Color :: white () : Color :: black ());
painter . draw_hline (
{ 5 * 8 , 36 },
8 * 4 ,
s ? Color :: black () : Color :: white ());
2015-07-08 08:39:24 -07:00
}
void FrequencyKeypadView :: update_text () {
2023-05-19 08:16:05 +12:00
const auto s = mhz . as_string () + "." + submhz . as_string ();
text_value . set ( s );
2015-07-08 08:39:24 -07:00
}
/* FrequencyOptionsView **************************************************/
FrequencyOptionsView :: FrequencyOptionsView (
2023-05-19 08:16:05 +12:00
const Rect parent_rect ,
const Style * const style )
: View { parent_rect } {
set_style ( style );
2015-07-08 08:39:24 -07:00
2023-05-19 08:16:05 +12:00
field_step . on_change = [ this ]( size_t n , OptionsField :: value_t v ) {
( void ) n ;
this -> on_step_changed ( v );
};
2015-07-08 08:39:24 -07:00
2023-05-19 08:16:05 +12:00
field_ppm . on_change = [ this ]( int32_t v ) {
this -> on_reference_ppm_correction_changed ( v );
};
2015-07-29 16:09:00 -07:00
2023-05-19 08:16:05 +12:00
add_children ({
& text_step ,
& field_step ,
});
2019-01-11 07:56:21 +01:00
2023-05-19 08:16:05 +12:00
if ( portapack :: clock_manager . get_reference (). source == ClockManager :: ReferenceSource :: Xtal ) {
add_child ( & field_ppm );
add_child ( & text_ppm );
}
2015-07-08 08:39:24 -07:00
}
void FrequencyOptionsView :: set_step ( rf :: Frequency f ) {
2023-07-04 16:26:26 -07:00
field_step . set_by_nearest_value ( f );
2015-07-08 08:39:24 -07:00
}
2015-07-29 16:09:00 -07:00
void FrequencyOptionsView :: set_reference_ppm_correction ( int32_t v ) {
2023-05-19 08:16:05 +12:00
field_ppm . set_value ( v );
2015-07-29 16:09:00 -07:00
}
2015-07-08 08:39:24 -07:00
void FrequencyOptionsView :: on_step_changed ( rf :: Frequency v ) {
2023-05-19 08:16:05 +12:00
if ( on_change_step ) {
on_change_step ( v );
}
2015-07-08 08:39:24 -07:00
}
2015-07-29 16:09:00 -07:00
void FrequencyOptionsView :: on_reference_ppm_correction_changed ( int32_t v ) {
2023-05-19 08:16:05 +12:00
if ( on_change_reference_ppm_correction ) {
on_change_reference_ppm_correction ( v );
}
2015-07-29 16:09:00 -07:00
}
2016-06-06 10:13:36 -07:00
/* RFAmpField ************************************************************/
RFAmpField :: RFAmpField (
2023-05-19 08:16:05 +12:00
Point parent_pos )
: NumberField {
parent_pos ,
1 ,
{ 0 , 1 },
1 ,
' ' ,
} {
set_value ( receiver_model . rf_amp ());
2016-06-06 10:13:36 -07:00
2023-05-19 08:16:05 +12:00
on_change = []( int32_t v ) {
receiver_model . set_rf_amp ( v );
};
2016-06-06 10:13:36 -07:00
}
2015-07-08 08:39:24 -07:00
/* RadioGainOptionsView **************************************************/
RadioGainOptionsView :: RadioGainOptionsView (
2023-05-19 08:16:05 +12:00
const Rect parent_rect ,
const Style * const style )
: View { parent_rect } {
set_style ( style );
2015-07-08 08:39:24 -07:00
2023-05-19 08:16:05 +12:00
add_children ({
& label_rf_amp ,
& field_rf_amp ,
});
2015-07-08 08:39:24 -07:00
}
/* LNAGainField **********************************************************/
LNAGainField :: LNAGainField (
2023-05-19 08:16:05 +12:00
Point parent_pos )
: NumberField {
parent_pos ,
2 ,
{ max283x :: lna :: gain_db_range . minimum , max283x :: lna :: gain_db_range . maximum },
max283x :: lna :: gain_db_step ,
' ' ,
} {
set_value ( receiver_model . lna ());
2016-06-06 10:27:33 -07:00
2023-05-19 08:16:05 +12:00
on_change = []( int32_t v ) {
receiver_model . set_lna ( v );
};
2015-07-08 08:39:24 -07:00
}
void LNAGainField :: on_focus () {
2023-05-19 08:16:05 +12:00
if ( on_show_options ) {
on_show_options ();
}
2015-07-08 08:39:24 -07:00
}
2016-04-12 10:48:24 -07:00
/* VGAGainField **********************************************************/
VGAGainField :: VGAGainField (
2023-05-19 08:16:05 +12:00
Point parent_pos )
: NumberField {
parent_pos ,
2 ,
{ max283x :: vga :: gain_db_range . minimum , max283x :: vga :: gain_db_range . maximum },
max283x :: vga :: gain_db_step ,
' ' ,
} {
set_value ( receiver_model . vga ());
2016-06-06 10:27:33 -07:00
2023-05-19 08:16:05 +12:00
on_change = []( int32_t v ) {
receiver_model . set_vga ( v );
};
2016-04-12 10:48:24 -07:00
}
void VGAGainField :: on_focus () {
2023-05-19 08:16:05 +12:00
if ( on_show_options ) {
on_show_options ();
}
2016-04-12 10:48:24 -07:00
}
2023-06-04 12:56:46 -07:00
/* AudioVolumeField *******************************************************/
AudioVolumeField :: AudioVolumeField (
Point parent_pos )
: NumberField {
parent_pos ,
/* length */ 2 ,
/* range */ { 0 , 99 },
/* step */ 1 ,
/* fill char */ ' ' } {
set_value ( receiver_model . normalized_headphone_volume ());
on_change = []( int32_t v ) {
receiver_model . set_normalized_headphone_volume ( v );
};
}
2015-07-08 08:39:24 -07:00
} /* namespace ui */