diff --git a/src/utils/EntryModel.cxxtest b/src/utils/EntryModel.cxxtest new file mode 100644 index 000000000..033853e46 --- /dev/null +++ b/src/utils/EntryModel.cxxtest @@ -0,0 +1,503 @@ +#include "utils/test_main.hxx" +#include "utils/EntryModel.hxx" + +TEST(EntryModelTest, Create) +{ + EntryModel em; + + EXPECT_EQ(0, em.get_value()); + EXPECT_EQ("", em.get_string()); + EXPECT_EQ(0U, em.size()); + EXPECT_FALSE(em.is_at_initial_value()); + EXPECT_TRUE(em.empty()); +} + +TEST(EntryModelTest, InitEmpty) +{ + EntryModel em; + EntryModel uem; + + // signed + em.init(4, 10); + + EXPECT_EQ(4U, em.max_size()); + EXPECT_EQ(0, em.get_value()); + EXPECT_EQ("", em.get_string()); + EXPECT_EQ(0U, em.size()); + EXPECT_FALSE(em.is_at_initial_value()); + EXPECT_TRUE(em.empty()); + + // unsigned + em.init(4, 10); + + EXPECT_EQ(4U, em.max_size()); + EXPECT_EQ(0U, em.get_value()); + EXPECT_EQ("", em.get_string()); + EXPECT_EQ(0U, em.size()); + EXPECT_FALSE(em.is_at_initial_value()); + EXPECT_TRUE(em.empty()); +} + +TEST(EntryModelTest, InitValue) +{ + EntryModel em; + EntryModel uem; + + // signed + // non-zero positive init + em.init(4, 10, 24); + + EXPECT_EQ(4U, em.max_size()); + EXPECT_EQ(24, em.get_value()); + EXPECT_EQ("24", em.get_string()); + EXPECT_EQ(" 24", em.get_string(true)); + EXPECT_EQ(0U, em.size()); + EXPECT_TRUE(em.is_at_initial_value()); + EXPECT_FALSE(em.empty()); + + // non-zero negagive init + em.init(4, 10, -24); + + EXPECT_EQ(4U, em.max_size()); + EXPECT_EQ(-24, em.get_value()); + EXPECT_EQ("-24", em.get_string()); + EXPECT_EQ(" -24", em.get_string(true)); + EXPECT_EQ(0U, em.size()); + EXPECT_TRUE(em.is_at_initial_value()); + EXPECT_FALSE(em.empty()); + + // zero init + em.init(4, 10, 0); + + EXPECT_EQ(0, em.get_value()); + EXPECT_EQ("0", em.get_string()); + EXPECT_EQ(" 0", em.get_string(true)); + EXPECT_EQ(0U, em.size()); + EXPECT_TRUE(em.is_at_initial_value()); + EXPECT_FALSE(em.empty()); + + // unsigned + // non-zero positive init + uem.init(4, 10, 24); + + EXPECT_EQ(4U, em.max_size()); + EXPECT_EQ(24U, uem.get_value()); + EXPECT_EQ("24", uem.get_string()); + EXPECT_EQ(" 24", uem.get_string(true)); + EXPECT_EQ(0U, uem.size()); + EXPECT_TRUE(uem.is_at_initial_value()); + EXPECT_FALSE(uem.empty()); + + // zero init + uem.init(4, 10, 0); + + EXPECT_EQ(4U, em.max_size()); + EXPECT_EQ(0U, uem.get_value()); + EXPECT_EQ("0", uem.get_string()); + EXPECT_EQ(" 0", uem.get_string(true)); + EXPECT_EQ(0U, uem.size()); + EXPECT_TRUE(uem.is_at_initial_value()); + EXPECT_FALSE(uem.empty()); +} + +TEST(EntryModelTest, InitValueClear) +{ + EntryModel em; + EntryModel uem; + + em.init(4, 10, -24); + em.clear(); + + EXPECT_EQ(4U, em.max_size()); + EXPECT_EQ(0, em.get_value()); + EXPECT_EQ("", em.get_string()); + EXPECT_EQ(0U, em.size()); + EXPECT_FALSE(em.is_at_initial_value()); + EXPECT_TRUE(em.empty()); + + uem.init(4, 10, 24); + uem.clear(); + + EXPECT_EQ(4U, em.max_size()); + EXPECT_EQ(0U, uem.get_value()); + EXPECT_EQ("", uem.get_string()); + EXPECT_EQ(0U, uem.size()); + EXPECT_FALSE(uem.is_at_initial_value()); + EXPECT_TRUE(uem.empty()); +} + +TEST(EntryModelTest, InitValuePopBack) +{ + EntryModel em; + EntryModel uem; + + // signed + em.init(4, 10, -24); + em.pop_back(); + + EXPECT_EQ(4U, em.max_size()); + EXPECT_EQ(-2, em.get_value()); + EXPECT_EQ("-2", em.get_string()); + EXPECT_EQ(" -2", em.get_string(true)); + EXPECT_EQ(2U, em.size()); + EXPECT_FALSE(em.is_at_initial_value()); + EXPECT_FALSE(em.empty()); + + // unsigned + uem.init(4, 10, 24); + uem.pop_back(); + + EXPECT_EQ(4U, em.max_size()); + EXPECT_EQ(2U, uem.get_value()); + EXPECT_EQ("2", uem.get_string()); + EXPECT_EQ(" 2", uem.get_string(true)); + EXPECT_EQ(1U, uem.size()); + EXPECT_FALSE(uem.is_at_initial_value()); + EXPECT_FALSE(uem.empty()); + + // satureate the pop_back operations + uem.pop_back(); + EXPECT_EQ("", uem.get_string()); + EXPECT_EQ(" ", uem.get_string(true)); + uem.pop_back(); + EXPECT_EQ("", uem.get_string()); + EXPECT_EQ(" ", uem.get_string(true)); + + // unsigned 0 value with leading zeros + uem.init(4, 10, 0); + + EXPECT_EQ(4U, em.max_size()); + uem.push_back_char('0'); + EXPECT_EQ("0", uem.get_string()); + EXPECT_EQ(" 0", uem.get_string(true)); + EXPECT_FALSE(uem.has_leading_zeros()); + uem.push_back(0); + EXPECT_EQ("00", uem.get_string()); + EXPECT_EQ(" 00", uem.get_string(true)); + EXPECT_TRUE(uem.has_leading_zeros()); + uem.pop_back(); + EXPECT_EQ("0", uem.get_string()); + EXPECT_EQ(" 0", uem.get_string(true)); + EXPECT_FALSE(uem.has_leading_zeros()); + uem.pop_back(); + EXPECT_EQ("", uem.get_string()); + EXPECT_EQ(" ", uem.get_string(true)); + EXPECT_FALSE(uem.has_leading_zeros()); +} + +TEST(EntryModelTest, PushBackAndAppend) +{ + EntryModel em; + EntryModel uem; + + // + // signed base 10 + // + em.init(4, 10, 0); + EXPECT_EQ(4U, em.max_size()); + em.push_back_char('1'); + EXPECT_EQ("1", em.get_string()); + EXPECT_EQ(" 1", em.get_string(true)); + EXPECT_EQ(1, em.get_value()); + + em.change_sign(); + EXPECT_EQ("-1", em.get_string()); + EXPECT_EQ(" -1", em.get_string(true)); + EXPECT_EQ(-1, em.get_value()); + + em.push_back_char('2'); + EXPECT_EQ("-12", em.get_string()); + EXPECT_EQ(" -12", em.get_string(true)); + EXPECT_EQ(-12, em.get_value()); + + em.push_back_char('3'); + EXPECT_EQ("-123", em.get_string()); + EXPECT_EQ("-123", em.get_string(true)); + EXPECT_EQ(-123, em.get_value()); + + em.push_back_char('4'); + EXPECT_EQ("", em.get_string()); + EXPECT_EQ(" ", em.get_string(true)); + EXPECT_EQ(0, em.get_value()); + EXPECT_TRUE(em.empty()); + + em.push_back_char('5'); + EXPECT_EQ("5", em.get_string()); + EXPECT_EQ(" 5", em.get_string(true)); + EXPECT_EQ(5, em.get_value()); + + // + // unsigned base 10 + // + uem.init(4, 10, 0); + EXPECT_EQ(4U, em.max_size()); + uem.push_back_char('1'); + EXPECT_EQ("1", uem.get_string()); + EXPECT_EQ(" 1", uem.get_string(true)); + EXPECT_EQ(1U, uem.get_value()); + + uem.push_back_char('2'); + EXPECT_EQ("12", uem.get_string()); + EXPECT_EQ(" 12", uem.get_string(true)); + EXPECT_EQ(12U, uem.get_value()); + + uem.push_back_char('3'); + EXPECT_EQ("123", uem.get_string()); + EXPECT_EQ(" 123", uem.get_string(true)); + EXPECT_EQ(123U, uem.get_value()); + + uem.push_back_char('4'); + EXPECT_EQ("1234", uem.get_string()); + EXPECT_EQ("1234", uem.get_string(true)); + EXPECT_EQ(1234U, uem.get_value()); + + uem.push_back_char('5'); + EXPECT_EQ("", uem.get_string()); + EXPECT_EQ(" ", uem.get_string(true)); + EXPECT_EQ(0U, uem.get_value()); + EXPECT_TRUE(uem.empty()); + + uem.push_back_char('6'); + EXPECT_EQ("6", uem.get_string()); + EXPECT_EQ(" 6", uem.get_string(true)); + EXPECT_EQ(6U, uem.get_value()); + + // + // signed base 16 + // + em.init(4, 16, 0); + EXPECT_EQ(4U, em.max_size()); + em.push_back_char('1'); + EXPECT_EQ("1", em.get_string()); + EXPECT_EQ(" 1", em.get_string(true)); + EXPECT_EQ(0x1, em.get_value()); + + em.change_sign(); + EXPECT_EQ("-1", em.get_string()); + EXPECT_EQ(" -1", em.get_string(true)); + EXPECT_EQ(-0x1, em.get_value()); + + em.push_back_char('A'); + EXPECT_EQ("-1A", em.get_string()); + EXPECT_EQ(" -1A", em.get_string(true)); + EXPECT_EQ(-0x1A, em.get_value()); + + em.change_sign(); + EXPECT_EQ("1A", em.get_string()); + EXPECT_EQ(" 1A", em.get_string(true)); + EXPECT_EQ(0x1A, em.get_value()); + + em.push_back_char('3'); + EXPECT_EQ("1A3", em.get_string()); + EXPECT_EQ(" 1A3", em.get_string(true)); + EXPECT_EQ(0x1A3, em.get_value()); + + em.push_back_char('b'); + EXPECT_EQ("1A3B", em.get_string()); + EXPECT_EQ("1A3B", em.get_string(true)); + EXPECT_EQ(0x1A3B, em.get_value()); + + em.push_back_char('5'); + EXPECT_EQ("", em.get_string()); + EXPECT_EQ(" ", em.get_string(true)); + EXPECT_EQ(0x0, em.get_value()); + EXPECT_TRUE(em.empty()); + + em.push_back_char('6'); + EXPECT_EQ("6", em.get_string()); + EXPECT_EQ(" 6", em.get_string(true)); + EXPECT_EQ(0x6, em.get_value()); + + // + // unsigned base 16 + // + uem.init(4, 16, 0); + EXPECT_EQ(4U, em.max_size()); + uem.push_back_char('1'); + EXPECT_EQ("1", uem.get_string()); + EXPECT_EQ(" 1", uem.get_string(true)); + EXPECT_EQ(0x1U, uem.get_value()); + + uem.push_back_char('A'); + EXPECT_EQ("1A", uem.get_string()); + EXPECT_EQ(" 1A", uem.get_string(true)); + EXPECT_EQ(0x1AU, uem.get_value()); + + uem.push_back_char('3'); + EXPECT_EQ("1A3", uem.get_string()); + EXPECT_EQ(" 1A3", uem.get_string(true)); + EXPECT_EQ(0x1A3U, uem.get_value()); + + uem.push_back_char('b'); + EXPECT_EQ("1A3B", uem.get_string()); + EXPECT_EQ("1A3B", uem.get_string(true)); + EXPECT_EQ(0x1A3BU, uem.get_value()); + + uem.push_back_char('5'); + EXPECT_EQ("", uem.get_string()); + EXPECT_EQ(" ", uem.get_string(true)); + EXPECT_EQ(0x0U, uem.get_value()); + EXPECT_TRUE(uem.empty()); + + uem.push_back_char('6'); + EXPECT_EQ("6", uem.get_string()); + EXPECT_EQ(" 6", uem.get_string(true)); + EXPECT_EQ(0x6U, uem.get_value()); + + // + // append unsigned base 10 + // + uem.init(4, 10, 0); + EXPECT_EQ(4U, em.max_size()); + uem.append_char('1').append(2).append_char('3'); + EXPECT_EQ("123", uem.get_string()); + EXPECT_EQ(" 123", uem.get_string(true)); + EXPECT_EQ(123U, uem.get_value()); +} + +TEST(EntryModelTest, IncrementDecrementClamp) +{ + EntryModel em; + + // + // signed base 10, decrement + // + em.init(4, 10, 0); + EXPECT_EQ(4U, em.max_size()); + --em; + --em; + EXPECT_EQ("-2", em.get_string()); + EXPECT_EQ(" -2", em.get_string(true)); + EXPECT_EQ(-2, em.get_value()); + for (unsigned i = 0; i < 997; ++i) + { + --em; + } + EXPECT_EQ("-999", em.get_string()); + EXPECT_EQ("-999", em.get_string(true)); + EXPECT_EQ(-999, em.get_value()); + --em; + EXPECT_EQ("-999", em.get_string()); + EXPECT_EQ("-999", em.get_string(true)); + EXPECT_EQ(-999, em.get_value()); + + // increment + em.set_value(0); + ++em; + ++em; + EXPECT_EQ("2", em.get_string()); + EXPECT_EQ(" 2", em.get_string(true)); + EXPECT_EQ(2, em.get_value()); + for (unsigned i = 0; i < 9997; ++i) + { + ++em; + } + EXPECT_EQ("9999", em.get_string()); + EXPECT_EQ("9999", em.get_string(true)); + EXPECT_EQ(9999, em.get_value()); + ++em; + EXPECT_EQ("9999", em.get_string()); + EXPECT_EQ("9999", em.get_string(true)); + EXPECT_EQ(9999, em.get_value()); + + // minimum initial value, then modify/clamp + em.set_value(INT16_MIN); + EXPECT_EQ("-32768", em.get_string()); + EXPECT_EQ("-32768", em.get_string(true)); + EXPECT_EQ(-32768, em.get_value()); + --em; + EXPECT_EQ("-999", em.get_string()); + EXPECT_EQ("-999", em.get_string(true)); + EXPECT_EQ(-999, em.get_value()); + em.set_value(INT16_MIN); + ++em; + EXPECT_EQ("-999", em.get_string()); + EXPECT_EQ("-999", em.get_string(true)); + EXPECT_EQ(-999, em.get_value()); + + // maximum initial value, then modify/clamp + em.set_value(INT16_MAX); + EXPECT_EQ("32767", em.get_string()); + EXPECT_EQ("32767", em.get_string(true)); + EXPECT_EQ(32767, em.get_value()); + ++em; + EXPECT_EQ("9999", em.get_string()); + EXPECT_EQ("9999", em.get_string(true)); + EXPECT_EQ(9999, em.get_value()); + em.set_value(INT16_MAX); + --em; + EXPECT_EQ("9999", em.get_string()); + EXPECT_EQ("9999", em.get_string(true)); + EXPECT_EQ(9999, em.get_value()); +} + +TEST(EntryModelTest, SetMinMax) +{ + EntryModel em; + + // + // signed base 10, set_min() + // + em.init(4, 10, 0); + EXPECT_EQ(4U, em.max_size()); + em.set_min(); + EXPECT_EQ("-999", em.get_string()); + EXPECT_EQ("-999", em.get_string(true)); + EXPECT_EQ(-999, em.get_value()); + + // set_max() + em.set_max(); + EXPECT_EQ("9999", em.get_string()); + EXPECT_EQ("9999", em.get_string(true)); + EXPECT_EQ(9999, em.get_value()); +} + +TEST(EntryModelBoundedTest, SetMinMax) +{ + EntryModelBounded em; + + // intial value + em.init(4, 10, 9, -100, 100, 11); + EXPECT_EQ(4U, em.max_size()); + EXPECT_EQ(4U, em.max_size()); + EXPECT_EQ(9, em.get_value()); + EXPECT_EQ("9", em.get_string()); + EXPECT_EQ(" 9", em.get_string(true)); + EXPECT_EQ(0U, em.size()); + EXPECT_TRUE(em.is_at_initial_value()); + EXPECT_FALSE(em.empty()); + + // set default + em.set_default(); + EXPECT_EQ(11, em.get_value()); + EXPECT_EQ("11", em.get_string()); + EXPECT_EQ(" 11", em.get_string(true)); + EXPECT_EQ(0U, em.size()); + EXPECT_TRUE(em.is_at_initial_value()); + EXPECT_FALSE(em.empty()); + + // boundary checks + em.push_back(1); + em.push_back(2); + em.push_back(3); + em.push_back(4); + EXPECT_EQ(100, em.get_value()); + EXPECT_EQ("100", em.get_string()); + EXPECT_EQ(" 100", em.get_string(true)); + EXPECT_EQ(3U, em.size()); + EXPECT_FALSE(em.is_at_initial_value()); + EXPECT_FALSE(em.empty()); + + em.init(4, 10, 9, -100, 1003, 11); + EXPECT_EQ(4U, em.max_size()); + em.push_back(1); + em.push_back(2); + em.push_back(3); + em.push_back(4); + EXPECT_EQ(1003, em.get_value()); + EXPECT_EQ("1003", em.get_string()); + EXPECT_EQ("1003", em.get_string(true)); + EXPECT_EQ(4U, em.size()); + EXPECT_FALSE(em.is_at_initial_value()); + EXPECT_FALSE(em.empty()); +} diff --git a/src/utils/EntryModel.hxx b/src/utils/EntryModel.hxx index 6a95495c4..1c873a937 100644 --- a/src/utils/EntryModel.hxx +++ b/src/utils/EntryModel.hxx @@ -41,389 +41,471 @@ #include "utils/format_utils.hxx" -/** Implementation of a text entry menu. - * @tparam T the data type up to 64-bits in size - * @tparam N the size of the entry in max number of visible digits. - */ -template +/// Implementation of a text entry menu. +/// @tparam T the data type up to 64-bits in size, signed or unsigned +/// +template class EntryModel { public: - /** Constructor. - * @param transform force characters to be upper case - * @param clamp_callback callback method to clamp min/max - */ - EntryModel(bool transform = false, - std::function clamp_callback = nullptr) - : clampCallback_(clamp_callback) - , digits_(0) - , index_(0) - , hasInitial_(false) - , transform_(transform) + /// Constructor. + EntryModel() + : value_(0) + , valueMin_(std::numeric_limits::lowest()) + , valueMax_(std::numeric_limits::max()) + , numLeadingZeros_(0) + , maxSize_(0) + , size_(0) + , isAtInitialValue_(false) + , empty_(true) , base_(10) { - clear(); - data_[N] = '\0'; } - /** Initialize empty. - * @param digits max number of significant digits in the base type - * @param base base type, 10 or 16 - */ - void init(unsigned digits, int base) + ///Clear the entry string. + void clear() { - HASSERT(digits <= N); - digits_ = digits; - clear(); - hasInitial_ = true; + value_ = 0; + numLeadingZeros_ = 0; + size_ = 0; + empty_ = true; + isAtInitialValue_ = false; } - /** Initialize with a value. - * @param digits max number of significant digits in the base type - * @param base base type, 10 or 16 - * @param value value to initialize with - */ - void init(unsigned digits, int base, T value) + /// Initialize empty. + /// @param max_size max number of digits in the base type + /// @param base base type, 10 or 16 + void init(unsigned max_size, int base) { - HASSERT(digits <= N); - digits_ = digits; + maxSize_ = max_size; clear(); + set_base(base); + set_boundaries(); + } - string str; - switch (base) + /// Initialize with a value. + /// @param max_size max number of digits in the base type + /// @param base base type, 10 or 16 + /// @param value value to initialize with + void init(unsigned max_size, int base, T value) + { + init(max_size, base); + value_ = value; + isAtInitialValue_ = true; + empty_ = false; + } + + /// Append a value to the "back". + /// @param val value to append, base 10: 0 - 9, base 16: 0x0 - 0xF + void push_back(uint8_t val) + { + HASSERT(val < base_); + if (size_ >= maxSize_) { - default: - HASSERT(0); - case 10: - if (std::is_signed::value) - { - str = int64_to_string(value, digits); - } - else - { - str = uint64_to_string(value, digits); - } - break; - case 16: - if (std::is_signed::value) - { - str = int64_to_string_hex(value, digits); - } - else - { - str = uint64_to_string_hex(value, digits); - } - if (transform_) - { - /* equires all characters in upper case */ - transform(str.begin(), str.end(), str.begin(), toupper); - } - break; + // clear entry, return without appending the character + clear(); + return; } - str_populate(data_, str.c_str()); - hasInitial_ = true; + if (isAtInitialValue_) + { + // clear entry before inserting character + clear(); + } + value_ *= base_; + if (value_ < 0) + { + value_ -= val; + } + else + { + value_ += val; + } + if (value_ == 0 && !empty_) + { + if (numLeadingZeros_ < 31) + { + ++numLeadingZeros_; + } + } + empty_ = false; + ++size_; + clamp(); } - /** Clear the entry string. - * @param data data to fill in the buffer with - */ - void clear(const char *data = nullptr) + /// Append a character to the "back". + /// @param c character to append, base 10: 0 - 9, base 16: 0 - F + void push_back_char(char c) { - memset(data_, ' ', digits_); - if (data) + switch (base_) { - memcpy(data_, data, strlen(data)); + case 10: + HASSERT(c >= '0' && c <= '9'); + push_back(c - '0'); + break; + case 16: + c = toupper(c); + HASSERT((c >= '0' && c <= '9') || + (c >= 'A' && c <= 'F')); + push_back(c <= '9' ? c - '0' : c - 'A' + 10); + break; } - data_[digits_] = '\0'; - index_ = 0; } - /** Get the current index. - * @return current cursor index - */ - unsigned cursor_index() + /// Append a value to the "back". + /// @param val value to append, base 10: 0 - 9, base 16: 0x0 - 0xF + /// @return *this + EntryModel &append(uint8_t val) { - return index_; + push_back(val); + return *this; } - /** Test if cursor is visible. - * @return true if cursor is visiable, else false - */ - bool cursor_visible() + /// Append a character to the "back". + /// @param c character to append, base 10: 0 - 9, base 16: 0 - F + /// @return *this + EntryModel &append_char(char c) { - return index_ < digits_; + push_back_char(c); + return *this; } - /** Put a character at the current index, and increment the index by 1. - * @param c Character to place - * @return true if the string was cleared out and an LCD refresh is - * may be required. - */ - bool putc_inc(char c) + /// Removes (deletes) a character off the end. + void pop_back() { - bool refresh = false; - if (index_ >= digits_) + if (value_ == 0 && numLeadingZeros_) { - refresh = true; - clear(); + --numLeadingZeros_; } else { - if (hasInitial_) - { - hasInitial_ = false; - refresh = true; - clear(); - } - data_[index_++] = transform_ ? toupper(c) : c; + value_ /= base_; } - clamp(); - return refresh; - } - - /** Delete a character off the end. - */ - void backspace() - { - if (index_ == 0) + if (size_) + { + --size_; + } + if (isAtInitialValue_) + { + isAtInitialValue_ = false; + clamp(); + // need to compute the size now that the initial value is false + calculate_size(); + } + if (size_ == 0) { - index_ = parsed().size(); + // no more characters left, so the entry is "empty" + empty_ = true; } - data_[--index_] = ' '; - hasInitial_ = false; clamp(); } - /** Set the radix base. - * @param base new radix base to set. - */ + /// Set the radix base. + /// @param base new radix base to set. void set_base(int base) { HASSERT(base == 10 || base == 16); base_ = base; } - /** Set the value, keep the digits and base the same. - * @param value value to initialize with - */ + /// Set the value, keep the max number of digits and base the same. + /// @param value value to initialize with void set_value(T value) { - init(digits_, base_, value); + init(maxSize_, base_, value); } - /** Get the entry as an unsigned integer value. - * @param start_index starting index in string to start conversion - * @return value representation of the string - */ - T get_value(unsigned start_index = 0) + /// Get the size (actual number of digits). Note, if the entry is still + /// at its initial value, the result will be 0. + /// @return size actual size in number of digits + size_t size() { - HASSERT(start_index < digits_); - if (std::is_signed::value) - { - return strtoll(data_ + start_index, NULL, base_); - } - else - { - return strtoull(data_ + start_index, NULL, base_); - } + return size_; } - /** Get the C style string representing the menu entry. - * @return the string data representing the menu entry - */ - const char *c_str() + /// Get the max size (in digits). + /// @return max size in number of digits + size_t max_size() { - return data_; + return maxSize_; } - /** Get a copy of the string without any whitespace. - * @param strip_leading true to also strip off leading '0' or ' ' - * @return the string data representing the menu entry - */ - string parsed(bool strip_leading = false) + /// Test if the entry is "empty". Having an initial value is not empty. + /// @return true if empty, else false + bool empty() { - const char *parse = data_; - string result; - result.reserve(N); - if (strip_leading) + return (!isAtInitialValue_ && empty_); + } + + /// Test if cursor is visible. + /// @return true if cursor is visible, else false + bool cursor_visible() + { + return size_ < maxSize_; + } + + /// Determine if this object is holding an initial or modified value. + /// @return true if if holding an initial value, else false if modified + bool is_at_initial_value() + { + return isAtInitialValue_; + } + + /// It is not always possible with get_string() to return the leading + /// zeros. Furthermore, get_value() does not tell the caller if there are + /// leading zeros. Therefore, this API provides a definitive answer. + bool has_leading_zeros() + { + return numLeadingZeros_ > 0; + } + + /// Get the entry as an unsigned integer value. Note, that '0' is returned + /// both when the actual value is '0' and when the entry is "empty". If the + /// caller needs to distinguish between these two states, check for + /// "empty()". + /// @param force_clamp Normally, clamping doesn't occur if the entry is + /// "empty". However, if force is set to true, we will + /// clamp anyways. This may be valuable when wanting an + /// "empty" entry to return a valid value and '0' is out + /// of bounds. + /// @return value representation of the entry + T get_value(bool force_clamp = false) + { + if (force_clamp) { - while (*parse == '0' || *parse == ' ') + clamp(true); + } + return value_; + } + + /// Get the value as a string. The number of characters will not be trimmed + /// to maxSize_. If trimming is required, it must be done by the caller. + /// @param right_justify true to right justify. + string get_string(bool right_justify = false) + { + string str; + if (isAtInitialValue_ || !empty_) + { + switch (base_) { - ++parse; + default: + // should never get here. + break; + case 10: + if (std::is_signed::value) + { + str = int64_to_string(value_); + } + else + { + str = uint64_to_string(value_); + } + break; + case 16: + if (std::is_signed::value) + { + str = int64_to_string_hex(value_); + } + else + { + str = uint64_to_string_hex(value_); + } + // requires all characters in upper case + transform(str.begin(), str.end(), str.begin(), toupper); + break; } } - while (*parse != ' ' && *parse != '\0') + // Assert that we do not have more leading zeros than space allows. + // The logic of push_back should never allow it. + HASSERT(numLeadingZeros_ == 0 || + (str.size() + numLeadingZeros_) <= maxSize_); + + if (numLeadingZeros_) + { + str.insert(0, numLeadingZeros_, '0'); + } + if (right_justify && str.size() < maxSize_) { - result.push_back(*parse++); + str.insert(0, maxSize_ - str.size(), ' '); } - return result; + return str; + } + + /// Set the value to the minimum. + void set_min() + { + set_value(valueMin_); } - /** Copy the entry data into the middle of a buffer. - * @param buf pointer to destination buffer - * @param start_index starting index of buffer for the copy destination - * @param digits number of digits to copy - */ - void copy_to_buffer(char *buf, int start_index, int digits) + /// Set the value to the maximum. + void set_max() { - HASSERT(digits <= digits_); - memcpy(buf + start_index, data_, digits); + set_value(valueMax_); } - /** Change the sign of the data. - */ + /// Change the sign of the data. void change_sign() { - HASSERT(std::is_signed::value); - if (hasInitial_) + if (value_ < 0) { - set_value(-get_value()); + --size_; } - else if (data_[0] == '-') + value_ = -value_; + if (value_ < 0) { - memmove(data_, data_ + 1, digits_ - 1); - data_[digits_] = ' '; + ++size_; } - else + clamp(); + } + + /// Clamp the value at the min or max. + /// @param force Normally, clamping doesn't occur if the entry is "empty". + /// However, if force is set to true, we will clamp anyways. + virtual void clamp(bool force = false) + { + if (force || !empty_) + { + // purposely do not reset the numLeadingZeros_ + empty_ = false; + if (value_ < valueMin_) + { + value_ = valueMin_; + calculate_size(); + } + else if (value_ > valueMax_) + { + value_ = valueMax_; + calculate_size(); + } + } + } + + /// Pre-increment value. While this method does prevent wrap around of + /// the native type limits, it is incumbent on the caller to limit the + /// resulting number of digits. + T operator ++() + { + isAtInitialValue_ = false; + if (value_ < std::numeric_limits::max()) { - memmove(data_ + 1, data_, digits_ - 1); - data_[0] = '-'; + ++value_; } clamp(); + return value_; } - /// Get the number of significant digits - /// @return number of significant digits - unsigned digits() + /// Pre-decrement value. While this method does prevent wrap around of + /// the native type limits, it is incumbent on the caller to limit the + /// resulting number of digits. + T operator --() { - return digits_; + isAtInitialValue_ = false; + if (value_ > std::numeric_limits::lowest()) + { + --value_; + } + clamp(); + return value_; } - /// Determine if this object is holding an initial or modified value. - /// @return true if holding an initial value, else false if modified - bool has_initial() +protected: + /// Set min and max boundaries supported based on maxSize_ (digit count). + virtual void set_boundaries() { - return hasInitial_; + valueMax_ = 0; + for (unsigned i = 0; i < maxSize_; ++i) + { + valueMax_ *= base_; + valueMax_ += base_ - 1; + } + valueMin_ = std::is_signed::value ? valueMax_ / -base_ : 0; } - /// Clamp the value at the min or max. - void clamp() + /// Calculate the size in digits + void calculate_size() { - if (clampCallback_) + // calculate new size_ + size_ = value_ < 0 ? numLeadingZeros_ + 1 : numLeadingZeros_; + for (T tmp = value_ < 0 ? -value_ : value_; tmp != 0; tmp /= base_) { - clampCallback_(); + ++size_; } } -private: - std::function clampCallback_; /**< callback to clamp value */ - unsigned digits_ : 5; /**< number of significant digits */ - unsigned index_ : 5; /**< present write index */ - unsigned hasInitial_ : 1; /**< has an initial value */ - unsigned transform_ : 1; /**< force characters to be upper case */ - unsigned reserved_ : 20; /**< reserved bit space */ + T value_; ///< present value held + T valueMin_; ///< minimum value representable by maxSize_ + T valueMax_; ///< maximum value representable by maxSize_ - int base_; /**< radix base */ - char data_[N + 1]; /**< data string */ + unsigned numLeadingZeros_ : 5; ///< number of leading zeros + unsigned maxSize_ : 5; ///< maximum number of digits + unsigned size_ : 5; ///< actual number of digits + unsigned isAtInitialValue_ : 1; ///< true if still has the initial value + unsigned empty_ : 1; ///< true if the value_ is "empty" + unsigned base_ : 6; ///< radix base DISALLOW_COPY_AND_ASSIGN(EntryModel); }; -/** Specialization of EntryModel with upper and lower bounds - * @tparam T the data type up to 64-bits in size - * @tparam N the size of the entry in max number of visible digits. - */ -template class EntryModelBounded : public EntryModel +/// Specialization of EntryModel with upper and lower bounds +/// @tparam T the data type up to 64-bits in size +/// @tparam N the size of the entry in max number of visible digits. +template class EntryModelBounded : public EntryModel { public: - /** Constructor. - * @param transform force characters to be upper case - */ - EntryModelBounded(bool transform = false) - : EntryModel(transform, - std::bind(&EntryModelBounded::clamp, this)) - { - } - - /** Initialize with a value. - * @param digits max number of significant digits in the base type - * @param base base type, 10 or 16 - * @param value unsigned value to initialize with - * @param min minumum value - * @param max maximum value - * @param default_val default value - */ - void init(unsigned digits, int base, T value, T min, T max, T default_val) - { - min_ = min; - max_ = max; - default_ = default_val; - EntryModel::init(digits, base, value); - } - - /// Set the value to the minimum. - void set_min() + /// Constructor. + EntryModelBounded() + : EntryModel() { - EntryModel::set_value(min_); } - /// Set the value to the maximum. - void set_max() + /// Initialize with a value. + /// @param max_size max number of digits in the base type + /// @param base base type, 10 or 16 + /// @param value value to initialize with + /// @param min minumum value + /// @param max maximum value + /// @param default_val default value + void init(unsigned max_size, int base, T value, T min, T max, T default_val) { - EntryModel::set_value(max_); + // purposely do not boundary check the min, max, and default values + EntryModel::init(max_size, base, value); + // override type min/max values + EntryModel::valueMin_ = min; + EntryModel::valueMax_ = max; + valueDefault_ = default_val; } /// Set the value to the default. void set_default() { - EntryModel::set_value(default_); + EntryModel::set_value(valueDefault_); } - /// Pre-increment value. - T operator ++() +private: + /// Clamp the value at the min or max. + /// @param force Normally, clamping doesn't occur if the entry is "empty". + /// However, if force is set to true, we will clamp anyways. + void clamp(bool force = false) override { - T value = EntryModel::get_value(); - if (value < max_) + if (force && EntryModel::empty_) { - ++value; - EntryModel::set_value(value); + set_default(); } - return value; - } - - /// Pre-decrement value. - T operator --() - { - T value = EntryModel::get_value(); - if (value > min_) + else { - --value; - EntryModel::set_value(value); + EntryModel::clamp(force); } - return value; } -private: - /// Clamp the value at the min or max. - void clamp() + /// Override base class to do nothing. The boundaries come from + /// EntryModelBounded::init(). + void set_boundaries() override { - volatile T value = EntryModel::get_value(); - if (value < min_) - { - EntryModel::set_value(min_); - } - else if (value > max_) - { - EntryModel::set_value(max_); - } } - T min_; ///< minimum value - T max_; ///< maximum value - T default_; ///< default value + T valueDefault_; ///< default value DISALLOW_COPY_AND_ASSIGN(EntryModelBounded); }; -#endif /* _UTILS_ENTRYMODEL_HXX_ */ +#endif // _UTILS_ENTRYMODEL_HXX_