diff options
| author | Matthias Melcher <github@matthiasm.com> | 2023-02-23 15:42:05 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-02-23 15:42:05 +0100 |
| commit | 9f87af8ad9a3d8112518b6bbb5b6075881a5b9a2 (patch) | |
| tree | fe96512734abb7b29bc6b8fac9816d7cc0c4ae3f /src | |
| parent | 92818939267fc7fbb6a33d86fb78576f55ce6aca (diff) | |
Fl_String refactoring and extension (#683)
- add true unittest and Fl_String testing
- interface and printout are similar to gtest
without requiring external linkage.
just run `unittest --core`.
- new Fl_String API
- extended API to fl_input_str and fl_password_str
- co-authored-by: Albrecht Schlosser <albrechts.fltk@online.de>
Diffstat (limited to 'src')
| -rw-r--r-- | src/Fl_String.cxx | 600 | ||||
| -rw-r--r-- | src/fl_ask.cxx | 72 |
2 files changed, 564 insertions, 108 deletions
diff --git a/src/Fl_String.cxx b/src/Fl_String.cxx index e07f23a94..222e34583 100644 --- a/src/Fl_String.cxx +++ b/src/Fl_String.cxx @@ -17,137 +17,520 @@ #include <FL/Fl_String.H> #include <stdio.h> +#include <stdlib.h> #include <string.h> +#include <limits.h> + /** \file src/Fl_String.cxx - Basic Fl_String class for FLTK. -*/ + Basic Fl_String class for FLTK. + */ + + +/* + If buffer_ is NULL, c_str() and buffer() will point here. + */ +const char Fl_String::NUL = 0; + +/** + Indicate a maximum value or error. + This value is generally used as end of string indicator or as the error + indicator by the functions that return a string index. + */ +const int Fl_String::npos = INT_MAX; + +/** + Initialise the class instance. + */ +void Fl_String::init_() { + buffer_ = NULL; + size_ = 0; + capacity_ = 0; +} + +/** + Grow the buffer to a capacity of at least n bytes. + + This method will always grow the buffer size, or keep it as is, but never + shrink it. Use shrink_to_fit() to shrink the buffer as much as possible. + + \param[in] n number of bytes needed, not counting the trailing NUL + */ +void Fl_String::grow_(int n) { + if (n <= capacity_) + return; + int alloc_size_ = n + 1; // trailing NUL + // round n up so we can grow in chunks + if (alloc_size_ <= 24) { // allocate at least 24 bytes + alloc_size_ = 24; + } else if (alloc_size_ < 1024) { + alloc_size_ = (alloc_size_+128) & ~127; // allocate in 128 byte chunks + } else { + alloc_size_ = (alloc_size_+2048) & ~2047; // allocate in 2k chunks + } + // allocate now + char *new_buffer = (char*)::malloc(alloc_size_); + if (buffer_ && (size_ > 0)) { + memcpy(new_buffer, buffer_, size_); + ::free(buffer_); + } + if (size_ >= 0) + new_buffer[size_] = 0; // trailing NUL + buffer_ = new_buffer; + capacity_ = alloc_size_-1; // trailing NUL +} -/** Constructs an empty string */ +/** + Shrink the buffer to n bytes, or size, if size > n. + + Shrink the buffer as much as possible. If \p n is 0 and the string is empty, + the buffer will be released. + + \param[in] n shrink buffer to n bytes, not counting the trailing NUL + */ +void Fl_String::shrink_(int n) { + if (n < size_) + n = size_; + if (n == capacity_) + return; + if (n == 0) { + if (buffer_) + ::free(buffer_); + buffer_ = NULL; + } else { + buffer_ = (char*)::realloc(buffer_, n+1); // NUL + buffer_[size_] = 0; // trailing NUL + } + capacity_ = n; +} + +/** + Remove \p n_del bytes at \p at and insert \p n_ins bytes from \p src. + + String will remain NUL terminated. Data in \p ins may contain NULs. + + \param[in] at remove and insert bytes at this index + \param[in] n_del number of bytes to remove + \param[in] ins insert bytes from here, can be NULL if \p n_ins is also 0 + \param[in] n_ins number of bytes to insert + \return self + */ +Fl_String &Fl_String::replace_(int at, int n_del, const char *ins, int n_ins) { + if (at > size_) at = size_; + if (n_del > size_ - at) n_del = size_ - at; + int diff = n_ins - n_del, new_size = size_ + diff; + if (diff) { + int src = at + n_del, dst = at + n_ins, n = size_ - src; + grow_(new_size); + if (n > 0) ::memmove(buffer_+dst, buffer_+src, n); + } + if (n_ins > 0) { + ::memmove(buffer_+at, ins, n_ins); + } + size_ = new_size; + buffer_[size_] = 0; + return *this; +} + + +// ---- Assignment ----------------------------------------------------- MARK: - + +/** + Allocate an empty string. + */ Fl_String::Fl_String() { - init(); + init_(); +} + +/** + Copy constructor. + \param[in] str copy from another Fl_String + */ +Fl_String::Fl_String(const Fl_String &str) { + init_(); + assign(str); } -/** Constructor from a C-style string */ -Fl_String::Fl_String(const char *str) { - init(); - value(str); +/** + Constructor from a C-style string. + \param[in] cstr a NUL terminated C-style string + */ +Fl_String::Fl_String(const char *cstr) { + init_(); + assign(cstr); } -/** Constructor from a buffer of \c size bytes */ +/** + Constructor from data of \p size bytes. + \param[in] str a block of data that may contain NUL characters + \paran[in] size number of bytes to copy + */ Fl_String::Fl_String(const char *str, int size) { - init(); - value(str, size); + init_(); + assign(str, size); } -void Fl_String::init() { - size_ = 0; - value_ = 0; - capacity_ = 0; +/** + Destructor. + */ +Fl_String::~Fl_String() { + if (buffer_) + ::free(buffer_); } -/** copy constructor */ -Fl_String::Fl_String(const Fl_String &in) { - init(); - value(in.value(), in.size()); +/** + Copy assignment operator + \param[in] str copy from another Fl_String + \return self + */ +Fl_String &Fl_String::operator=(const Fl_String &str) { + return assign(str); } -/** copy assignment operator */ -Fl_String& Fl_String::operator=(const Fl_String &in) { - if (this == &in) - return *this; - value(in.value(), in.size()); - // debug("copy assigned"); +/** + Assign a C-style string. + \param[in] cstr a NUL terminated C-style string + \return self + */ +Fl_String &Fl_String::operator=(const char *cstr) { + return assign(cstr); +} + +/** + Copy another string. + \param[in] str copy from another Fl_String + \return self + */ +Fl_String &Fl_String::assign(const Fl_String &str) { + if (&str == this) return *this; + return assign(str.data(), str.size()); +} + +/** + Assign a C-style string. + \param[in] cstr a NUL terminated C-style string + \return self + */ +Fl_String &Fl_String::assign(const char *cstr) { + if (cstr && *cstr) { + int len = (int)::strlen(cstr); + return assign(cstr, len); + } else { + resize(0); + } return *this; } -/** assignment operator for 'const char *' */ -Fl_String& Fl_String::operator=(const char *in) { - value(in); - // debug("*STRING* assigned"); +/** + Assign a data block of \p size bytes. + \param[in] str a block of data that may contain NUL characters + \paran[in] size number of bytes to copy + \return self + */ +Fl_String &Fl_String::assign(const char *str, int size) { + if (size > 0) { + grow_(size); + memcpy(buffer_, str, size); + buffer_[size] = 0; + size_ = size; + } else { + resize(0); + } return *this; } -/** Destructor */ -Fl_String::~Fl_String() { - delete[] value_; +// ---- Element Access ------------------------------------------------- MARK: - + +/** + Returns the character at specified bounds checked location. + \param[in] n index of character + \return character at that index, or NUL if out of bounds + */ +char Fl_String::at(int n) const { + if ((n < 0) || (n >= size_)) + return 0; + return operator[](n); } -/** Grow the buffer size to at least size+1 bytes. - By default, this call destroys the contents of the current buffer. - \param size in bytes - \param preserve_text copy existing text into the new buffer +/** + Returns the character at specified location. + \param[in] n index of character + \return character at that index */ -void Fl_String::alloc_buf(int size, bool preserve_text) { - if (size < 0) - return; - if (size > 0 && size <= capacity_) - return; +char Fl_String::operator[](int n) const { + if (buffer_) + return buffer_[n]; + else + return 0; +} - int new_size = (size + 1 + 15) & (~15); // round upwards - char *new_value = new char[new_size]; - capacity_ = new_size - 1; +/** + Returns a reference to the character at specified location. + \param[in] n index of character + \return reference to that character, so it can be used as lvalue + */ +char &Fl_String::operator[](int n) { + if (!buffer_) + reserve(1); + return buffer_[n]; +} - if (preserve_text) { - size_ = (int)strlen(value_); - // the new buffer always has a higher capacity than the old one - // make sure we copy the trailing NUL. - memcpy(new_value, value_, size_+1); - } else { - size_ = 0; - } - delete[] value_; - value_ = new_value; +/** + Return a pointer to the NUL terminated string. + \return reference to non-mutable string + */ +const char *Fl_String::data() const { + if (buffer_) + return buffer_; + else + return &NUL; } -/** Assigns the string value to a C-style string */ -void Fl_String::value(const char *str) { - value(str, str ? (int)strlen(str) : 0); +/** + Return a pointer to the writable NUL terminated string. + \return reference to mutable string + */ +char *Fl_String::data() { + if (!buffer_) + reserve(1); + return buffer_; } -/** Returns the number of non-null bytes in the object */ -int Fl_String::slen() const { - if (!value_) return 0; - return (int)strlen(value_); +/** + Return a pointer to the NUL terminated string. + \return reference to non-mutable string + \note same as `const char *Fl_String::data() const` + */ +const char *Fl_String::c_str() const { + return data(); } -/** Assigns the string value to a buffer of \c len bytes */ -void Fl_String::value(const char *str, int len) { - if (str) { - alloc_buf(len); - size_ = len; - memcpy(value_, str, size_); - value_[size_] = '\0'; - } else { // str == NULL - size_ = 0; // ignore len ! - delete[] value_; // free buffer - value_ = NULL; // set null pointer (!) - capacity_ = 0; // reset capacity - } +// ---- Capacity ------------------------------------------------------- MARK: - + +/** + Checks if the string is empty. + \return true if string contains no data + */ +bool Fl_String::empty() const { + return (size_ == 0); } -/** Returns the minimum capacity of the object */ +/** + Returns the number of bytes in the string. + \return number of bytes in string, not counting trailing NUL + */ +int Fl_String::size() const { + return size_; +} + +/** + Reserve n bytes for storage. + If n is less or equal than size, the capacity is set to size. + \param[in] n requested minimum size, not counting trailing NUL + */ +void Fl_String::reserve(int n) { + grow_(n); +} + +/** + Return the number of chars that are allocated for storage. + \return string capacity, not counting trailing NUL + */ int Fl_String::capacity() const { - return capacity_; // > 0 ? capacity_ - 1 : capacity_; + return capacity_; } -/** Set the minimum capacity to \c num_bytes plus one for a terminating NUL. - The contents of the string buffer will be copied if needed. - \param num_bytes minimum size of buffer +/** + Shrink the capacity to fit the current size. */ -void Fl_String::capacity(int num_bytes) { - alloc_buf(num_bytes, true); +void Fl_String::shrink_to_fit() { + shrink_(size_); } +// ---- Operations ----------------------------------------------------- MARK: - -void Fl_String::release() { - delete[] value_; - value_ = 0; - size_ = 0; - capacity_ = 0; +/** + Set an empty string. + */ +void Fl_String::clear() { + resize(0); +} + +/** + Insert a C-style string or data. + \param[in] at insert at this index + \param[in] src copy bytes from here + \param[in] n_ins optional number of bytes to copy - if not set, copy C-style string + \return self + */ +Fl_String &Fl_String::insert(int at, const char *src, int n_ins) { + if (n_ins == npos) n_ins = src ? (int)::strlen(src) : 0; + return replace_(at, 0, src, n_ins); +} + +/** + Insert another string. + \param[in] at insert at this index + \param[in] src copy string from here + \return self + */ +Fl_String &Fl_String::insert(int at, const Fl_String &src) { + return replace_(at, 0, src.buffer_, src.size_); +} + +/** + Erase some bytes within a string. + \param[in] at erase at this index + \param[in] n_del number of bytes to erase + \return self + */ +Fl_String &Fl_String::erase(int at, int n_del) { + return replace_(at, n_del, NULL, 0); +} + +/** + Append a single character. + \param[in] c append this byte + */ +void Fl_String::push_back(char c) { + replace_(size_, 0, &c, 1); +} + +/** + Remove the last character. + */ +void Fl_String::pop_back() { + replace_(size_-1, 1, NULL, 0); +} + +/** + Append a C-style string or data. + \param[in] src copy bytes from here + \param[in] n_ins optional number of bytes to copy - if not set, copy C-style string + \return self + */ +Fl_String &Fl_String::append(const char *src, int n_ins) { + if (n_ins == npos) n_ins = src ? (int)::strlen(src) : 0; + return replace_(size_, 0, src, n_ins); +} + +/** + Append another string. + \param[in] src copy string from here + \return self + */ +Fl_String &Fl_String::append(const Fl_String &src) { + return replace_(size_, 0, src.buffer_, src.size_); +} + +/** + Append a single byte. + \param[in] c single byte character + \return self + */ +Fl_String &Fl_String::append(char c) { + push_back(c); + return *this; +} + +/** + Append a C-style string or data. + \param[in] src copy C-style string from here + \return self + */ +Fl_String &Fl_String::operator+=(const char *src) { + return append(src); +} + +/** + Append another string. + \param[in] src copy string from here + \return self + */ +Fl_String &Fl_String::operator+=(const Fl_String &src) { + return append(src); +} + +/** + Append a single byte. + \param[in] c single byte character + \return self + */ +Fl_String &Fl_String::operator+=(char c) { + return append(c); +} + +/** + Replace part of the string with a C-style string or data. + \param[in] at erase and insert at this index + \param[in] n_del number of bytes to erase + \param[in] src copy bytes from here + \param[in] n_ins optional number of bytes to copy - if not set, copy C-style string + \return self + */ +Fl_String &Fl_String::replace(int at, int n_del, const char *src, int n_ins) { + if (n_ins == npos) n_ins = src ? (int)::strlen(src) : 0; + return replace_(at, n_del, src, n_ins); +} + +/** + Replace part of the string with another string. + \param[in] at erase and insert at this index + \param[in] n_del number of bytes to erase + \param[in] src copy string from here + \return self + */ +Fl_String &Fl_String::replace(int at, int n_del, const Fl_String &src) { + return replace_(at, n_del, src.buffer_, src.size_); +} + +/** + Return a substring from a string. + \param[in] pos copy string from here - if omitted, copy from start + \param[in] n number of bytes - if omitted, copy all bytes + \return a new string + */ +Fl_String Fl_String::substr(int pos, int n) const { + if (n > size_) n = size_; + int first = pos, last = pos + n; + if ((first < 0) || (first > size_) || (last <= first)) + return Fl_String(); + if (last > size_) last = size_; + return Fl_String(buffer_+first, last-first); +} + +/** + Resizes the string to n characters. + + If \p n is less than the current size, the string will be cropped. If \p n + is more than the current size, the new space will be filled with + NUL characters. + + \param[in] n new size of string + */ +void Fl_String::resize(int n) { + if (n == size_) + return; + if (n < size_) { + size_ = n; + if (buffer_) buffer_[size_] = 0; + } else { + grow_(n); + if (buffer_) ::memset(buffer_+size_, 0, n-size_+1); + } + size_ = n; } -// ============================= DEBUG ============================= +// --- Non Standard ---------------------------------------------------- MARK: - + +/** + Returns the number of bytes until the first NUL byte. + \return number of bytes in C-style string + */ +int Fl_String::strlen() const { + if (!buffer_) return 0; + return (int)::strlen(buffer_); +} /** Write some details about the string to stdout. @@ -163,7 +546,7 @@ void Fl_String::release() { void Fl_String::debug(const char *info) const { if (info) { printf("Fl_String '%-20s': %p, value = %p (%d/%d):\n%s\n", - info, this, value_, size_, capacity_, value_ ? value_ : "<NULL>"); + info, this, buffer_, size_, capacity_, buffer_ ? buffer_ : "<NULL>"); } } @@ -191,7 +574,46 @@ void Fl_String::hexdump(const char *info) const { } else if ((i & 3) == 0) { // separator after 4 bytes printf(" "); } - printf(" %02x", (unsigned char)value_[i]); + printf(" %02x", (unsigned char)buffer_[i]); } printf("\n"); } + +// ---- Non-member functions ------------------------------------------- MARK: - + +/** + Concatenate two strings. + \param[in] lhs first string + \param[in] rhs second string + \return self + */ +Fl_String operator+(const Fl_String &lhs, const Fl_String &rhs) { + Fl_String ret = lhs; + return ret += rhs; +} + +/** + Concatenate two strings. + \param[in] lhs first string + \param[in] rhs second C-style string + \return self + */ +Fl_String operator+(const Fl_String &lhs, const char *rhs) { + Fl_String ret = lhs; + return ret += rhs; +} + +/** + Compare two strings. + \param[in] lhs first string + \param[in] rhs second string + \return true if strings are the same size and have the same content + */ +bool operator==(const Fl_String &lhs, const Fl_String &rhs) { + if (lhs.size() == rhs.size()) { + int sz = lhs.size(); + if (sz == 0) return true; + if (memcmp(lhs.data(), rhs.data(), sz) == 0) return true; + } + return false; +} diff --git a/src/fl_ask.cxx b/src/fl_ask.cxx index 5657cd3b0..6ed6b2667 100644 --- a/src/fl_ask.cxx +++ b/src/fl_ask.cxx @@ -320,7 +320,7 @@ const char *fl_input(const char *fmt, const char *defstr, ...) { /** Shows an input dialog displaying the \p fmt message with variable arguments. - Like fl_input(), but this method has an additional (first) argument \p maxchar + Like fl_input(), but this method has the additional argument \p maxchar that limits the number of \b characters that can be input. Since the string is encoded in UTF-8 it is possible that the number of bytes in the string is larger than \p maxchar. @@ -329,36 +329,56 @@ const char *fl_input(const char *fmt, const char *defstr, ...) { returns the string in an Fl_String object that must be released after use. This can be a local/automatic variable. + The \p ret variable is set to 0 if the user clicked OK, and to a negative + value if the user canceled the dialog. If the dialog was canceled, the returned + string will be empty. + \code #include <FL/fl_ask.H> \endcode Example: \code - { Fl_String str = fl_input_str(0, "Enter text:", ""); - printf("Text is: '%s'\n", str.value() ? str.value() : "<cancelled>"); + { int ret; + Fl_String str = fl_input_str(ret, 0, "Enter text:", ""); + if (ret < 0) + printf("Text input was canceled.\n"); + else + printf("Text is: '%s'\n", str.c_str()); } // (str goes out of scope) \endcode - If the user hits \c Escape or closes the window \c str.value() returns NULL. - + \param[out] ret 0 if user clicked OK, negative if dialog was canceled \param[in] maxchar input size limit in characters (not bytes), use 0 for no limit \param[in] fmt can be used as an sprintf-like format and variables for the message text \param[in] defstr defines the default returned string if no text is entered - \return the user string input if OK was pushed or NULL in Fl_String::value() - \retval Fl_String::value() == NULL if Cancel was pushed or the window was closed by the user + \return the user string input if OK was clicked which can be empty + \return an empty string and set \p ret to a negative value if the user canceled the dialog \since 1.4.0 */ -Fl_String fl_input_str(int maxchar, const char *fmt, const char *defstr, ...) { +Fl_String fl_input_str(int &ret, int maxchar, const char *fmt, const char *defstr, ...) { + Fl_Message msg("?"); + if (maxchar < 0) maxchar = 0; + va_list ap; + va_start(ap, defstr); + const char *r = msg.input_innards(fmt, ap, defstr, FL_NORMAL_INPUT, maxchar); + va_end(ap); + ret = (r == NULL) ? -1 : 0; + return Fl_String(r); +} +/** Shows an input dialog displaying the \p fmt message with variable arguments. + \note No information is given if the user canceled the dialog or clicked OK. + \see fl_input_str(int &ret, int maxchar, const char *label, const char *deflt = 0, ...) + */ +Fl_String fl_input_str(int maxchar, const char *fmt, const char *defstr, ...) { Fl_Message msg("?"); - if (maxchar < 0) - maxchar = 0; + if (maxchar < 0) maxchar = 0; va_list ap; va_start(ap, defstr); const char *r = msg.input_innards(fmt, ap, defstr, FL_NORMAL_INPUT, maxchar); va_end(ap); - return r; // Fl_String(r) + return Fl_String(r); } /** Shows an input dialog displaying the \p fmt message with variable arguments. @@ -378,7 +398,6 @@ Fl_String fl_input_str(int maxchar, const char *fmt, const char *defstr, ...) { \retval NULL if Cancel was pushed or the window was closed by the user */ const char *fl_password(const char *fmt, const char *defstr, ...) { - Fl_Message msg("?"); va_list ap; va_start(ap, defstr); @@ -400,27 +419,42 @@ const char *fl_password(const char *fmt, const char *defstr, ...) { \code #include <FL/fl_ask.H> \endcode - \param[in] maxchar input size limit in characters (not bytes); use 0 for no limit + \param[out] ret 0 if user clicked OK, negative if dialog was canceled + \param[in] maxchar input size limit in characters (not bytes), use 0 for no limit \param[in] fmt can be used as an sprintf-like format and variables for the message text \param[in] defstr defines the default returned string if no text is entered - \return the user string input if OK was pushed or NULL in Fl_String::value() - \retval Fl_String::value() == NULL if Cancel was pushed or the window was closed by the user + \return the user string input if OK was clicked which can be empty + \return an empty string and set \p ret to a negative value if the user canceled the dialog \since 1.4.0 */ -Fl_String fl_password_str(int maxchar, const char *fmt, const char *defstr, ...) { +Fl_String fl_password_str(int &ret, int maxchar, const char *fmt, const char *defstr, ...) { + Fl_Message msg("?"); + if (maxchar < 0) maxchar = 0; + va_list ap; + va_start(ap, defstr); + const char *r = msg.input_innards(fmt, ap, defstr, FL_SECRET_INPUT, maxchar); + va_end(ap); + ret = (r == NULL) ? -1 : 0; + return Fl_String(r); +} +/** Shows an input dialog displaying the \p fmt message with variable arguments. + \note No information is given if the user canceled the dialog or clicked OK. + \see fl_password_str(int &ret, int maxchar, const char *label, const char *deflt = 0, ...) + */ +Fl_String fl_password_str(int maxchar, const char *fmt, const char *defstr, ...) { Fl_Message msg("?"); - if (maxchar < 0) - maxchar = 0; + if (maxchar < 0) maxchar = 0; va_list ap; va_start(ap, defstr); const char *r = msg.input_innards(fmt, ap, defstr, FL_SECRET_INPUT, maxchar); va_end(ap); - return r; // Fl_String(r) + return Fl_String(r); } + /** Sets the preferred position for the message box used in many common dialogs like fl_message(), fl_alert(), fl_ask(), fl_choice(), fl_input(), fl_password(). |
