mirror of https://github.com/fltk/fltk
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>
This commit is contained in:
parent
9281893926
commit
9f87af8ad9
|
@ -44,6 +44,12 @@ jobs:
|
|||
# Execute the build. You can specify a specific target with "--target <NAME>"
|
||||
run: cmake --build . --config $BUILD_TYPE
|
||||
|
||||
- name: Unittest
|
||||
working-directory: ${{github.workspace}}/build
|
||||
shell: bash
|
||||
run: ./bin/test/unittests --core
|
||||
|
||||
|
||||
build-wayland:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
|
|
136
FL/Fl_String.H
136
FL/Fl_String.H
|
@ -21,94 +21,110 @@
|
|||
Basic Fl_String class for FLTK.
|
||||
*/
|
||||
|
||||
// See: https://en.cppreference.com/w/cpp/string/basic_string/basic_string
|
||||
|
||||
/**
|
||||
Fl_String is the basic string class for FLTK.
|
||||
|
||||
In this version Fl_String can be used to store strings, copy strings,
|
||||
and move strings. There are no string manipulation methods yet.
|
||||
move strings, and do basic string manipulation. Fl_String implements a
|
||||
subset of std::string with a couple of extensions. std::string should be
|
||||
a drop-in if we ever decide to allow templates and the std library.
|
||||
|
||||
Fl_String can hold the value of an Fl_Input widget including \e nul bytes
|
||||
if the constructor Fl_String(const char *str, int size) is used.
|
||||
Fl_String always maintains a trailing \e nul byte, but can also contain
|
||||
\e nul bytes insde the string if the constructor
|
||||
Fl_String(const char *str, int size) is used.
|
||||
|
||||
Assignment and copy constructors \b copy the string value such that the
|
||||
source string can be freed immediately after the assignment.
|
||||
|
||||
The string value() can be an empty string \c "" or \c NULL.
|
||||
|
||||
If value() is not \c NULL it is guaranteed that the string is terminated by
|
||||
a trailing \c nul byte even if the string contains embedded \c nul bytes.
|
||||
c_str() and data() can be an empty string \c "", but never be \c NULL.
|
||||
|
||||
The method size() returns the full string size, whether the string contains
|
||||
embedded \c nul bytes or not. The special method slen() returns 0 if value()
|
||||
is \c NULL, otherwise the same as \c strlen() would do.
|
||||
embedded \c nul bytes or not. The special method Fl_String::strlen() returns
|
||||
the length of the string up to the first \e nul.
|
||||
|
||||
Examples:
|
||||
\code
|
||||
Fl_String np(NULL);
|
||||
printf(" np : value = %p, size = %d, slen = %d\n", np.value(), np.size(), np.slen());
|
||||
Fl_String empty("");
|
||||
printf(" empty : value = %p, size = %d\n", empty.value(), empty.size());
|
||||
Fl_String fltk("FLTK");
|
||||
Fl_Input i(0, 0, 0, 0);
|
||||
i.value("abc\0def", 7);
|
||||
Fl_String str(i.value(), i.size());
|
||||
printf(" str : strlen = %lu, size = %d, capacity = %d\n",
|
||||
strlen(str.value()), str.size(), str.capacity());
|
||||
|
||||
Output:
|
||||
|
||||
np : value = (nil), size = 0, slen = 0
|
||||
empty : value = 0x562840befbf0, size = 0
|
||||
str : strlen = 3, size = 7, capacity = 15
|
||||
\endcode
|
||||
All methods of Fl_String work on a byte level. They are not UTF-8 aware,
|
||||
but may hold and manipulate UTF-8 strings if done with care.
|
||||
|
||||
\since 1.4.0
|
||||
*/
|
||||
|
||||
class Fl_String {
|
||||
|
||||
private:
|
||||
/*
|
||||
FLTK does no small string optimisation.
|
||||
If the string is empty and capacity is not set, buffer_ will be NULL.
|
||||
*/
|
||||
char *buffer_;
|
||||
int size_;
|
||||
char *value_;
|
||||
int capacity_;
|
||||
|
||||
void init_();
|
||||
void grow_(int n);
|
||||
void shrink_(int n);
|
||||
Fl_String &replace_(int at, int n_del, const char *src, int n_ins);
|
||||
|
||||
protected:
|
||||
static const char NUL;
|
||||
|
||||
public:
|
||||
static const int npos;
|
||||
|
||||
// ---- Assignment
|
||||
Fl_String();
|
||||
Fl_String(const char *str);
|
||||
Fl_String(const Fl_String &str);
|
||||
Fl_String(const char *cstr);
|
||||
Fl_String(const char *str, int size);
|
||||
~Fl_String();
|
||||
Fl_String& operator=(const Fl_String &str);
|
||||
Fl_String& operator=(const char *cstr);
|
||||
Fl_String &assign(const Fl_String &str);
|
||||
Fl_String &assign(const char *cstr);
|
||||
Fl_String &assign(const char *str, int size);
|
||||
|
||||
// copy constructor
|
||||
Fl_String(const Fl_String &in);
|
||||
// ---- Element Access
|
||||
char at(int pos) const;
|
||||
char operator[](int n) const;
|
||||
char &operator[](int n);
|
||||
const char *data() const;
|
||||
char *data();
|
||||
const char *c_str() const;
|
||||
|
||||
// copy assignment operator
|
||||
Fl_String& operator=(const Fl_String &in);
|
||||
|
||||
// assignment operator for 'const char *'
|
||||
Fl_String& operator=(const char *in);
|
||||
|
||||
virtual ~Fl_String();
|
||||
|
||||
private:
|
||||
void init();
|
||||
void alloc_buf(int size, bool preserve_text=false);
|
||||
void release();
|
||||
|
||||
public:
|
||||
void value(const char *str);
|
||||
void value(const char *str, int len);
|
||||
/** Returns a pointer to the first character stored in the object */
|
||||
const char *value() const { return value_; }
|
||||
/** Returns a pointer to the first byte stored in the object */
|
||||
char *buffer() { return value_; }
|
||||
/** Returns the number of bytes currently stored in the object */
|
||||
int size() const { return size_; }
|
||||
|
||||
int slen() const;
|
||||
// ---- Capacity
|
||||
bool empty() const;
|
||||
int size() const;
|
||||
void reserve(int n);
|
||||
int capacity() const;
|
||||
void capacity(int num_bytes);
|
||||
void shrink_to_fit();
|
||||
|
||||
void debug(const char *info = 0) const; // output string info
|
||||
void hexdump(const char *info = 0) const; // output string info + hexdump
|
||||
// --- Operations
|
||||
void clear();
|
||||
Fl_String &insert(int at, const char *src, int n_ins=npos);
|
||||
Fl_String &insert(int at, const Fl_String &src);
|
||||
Fl_String &erase(int at, int n_del);
|
||||
void push_back(char c);
|
||||
void pop_back();
|
||||
Fl_String &append(const char *src, int n_ins=npos);
|
||||
Fl_String &append(const Fl_String &src);
|
||||
Fl_String &append(char c);
|
||||
Fl_String &operator+=(const char *src);
|
||||
Fl_String &operator+=(const Fl_String &src);
|
||||
Fl_String &operator+=(char c);
|
||||
Fl_String &replace(int at, int n_del, const char *src, int n_ins=npos);
|
||||
Fl_String &replace(int at, int n_del, const Fl_String &src);
|
||||
Fl_String substr(int pos=0, int n=npos) const;
|
||||
void resize(int n);
|
||||
|
||||
// --- Non Standard
|
||||
int strlen() const;
|
||||
void debug(const char *info = 0) const;
|
||||
void hexdump(const char *info = 0) const;
|
||||
}; // class Fl_String
|
||||
|
||||
// ---- Non-member functions
|
||||
extern Fl_String operator+(const Fl_String &lhs, const Fl_String &rhs);
|
||||
extern Fl_String operator+(const Fl_String &lhs, const char *rhs);
|
||||
extern bool operator==(const Fl_String &lhs, const Fl_String &rhs);
|
||||
|
||||
#endif // _FL_Fl_String_H_
|
||||
|
|
|
@ -72,9 +72,15 @@ FL_EXPORT int fl_choice_n(const char *q, const char *b0, const char *b1, const c
|
|||
FL_EXPORT Fl_String fl_input_str(int maxchar, const char *label, const char *deflt = 0, ...)
|
||||
__fl_attr((__format__(__printf__, 2, 4)));
|
||||
|
||||
FL_EXPORT Fl_String fl_input_str(int &ret, int maxchar, const char *label, const char *deflt = 0, ...)
|
||||
__fl_attr((__format__(__printf__, 3, 5)));
|
||||
|
||||
FL_EXPORT Fl_String fl_password_str(int maxchar, const char *label, const char *deflt = 0, ...)
|
||||
__fl_attr((__format__(__printf__, 2, 4)));
|
||||
|
||||
FL_EXPORT Fl_String fl_password_str(int &ret, int maxchar, const char *label, const char *deflt = 0, ...)
|
||||
__fl_attr((__format__(__printf__, 3, 5)));
|
||||
|
||||
FL_EXPORT Fl_Widget *fl_message_icon();
|
||||
extern FL_EXPORT Fl_Font fl_message_font_;
|
||||
extern FL_EXPORT Fl_Fontsize fl_message_size_;
|
||||
|
|
|
@ -299,7 +299,7 @@ const char* ExternalCodeEditor::tmp_filename() {
|
|||
static char path[FL_PATH_MAX+1];
|
||||
const char *tmpdir = create_tmpdir();
|
||||
if ( !tmpdir ) return 0;
|
||||
const char *ext = g_project.code_file_name; // e.g. ".cxx"
|
||||
const char *ext = g_project.code_file_name.c_str(); // e.g. ".cxx"
|
||||
snprintf(path, FL_PATH_MAX, "%s/%p%s", tmpdir, (void*)this, ext);
|
||||
path[FL_PATH_MAX] = 0;
|
||||
return path;
|
||||
|
@ -385,7 +385,7 @@ int ExternalCodeEditor::start_editor(const char *editor_cmd,
|
|||
editor_cmd, filename);
|
||||
char cmd[1024];
|
||||
snprintf(cmd, sizeof(cmd), "%s %s", editor_cmd, filename);
|
||||
command_line_.value(editor_cmd);
|
||||
command_line_ = editor_cmd;
|
||||
open_alert_pipe();
|
||||
// Fork editor to background..
|
||||
switch ( pid_ = fork() ) {
|
||||
|
@ -559,7 +559,7 @@ void ExternalCodeEditor::alert_pipe_cb(FL_SOCKET s, void* d) {
|
|||
self->last_error_ = 0;
|
||||
if (::read(s, &self->last_error_, sizeof(int)) != sizeof(int))
|
||||
return;
|
||||
const char* cmd = self->command_line_.value();
|
||||
const char* cmd = self->command_line_.c_str();
|
||||
if (cmd && *cmd) {
|
||||
if (cmd[0] == '/') { // is this an absoluet filename?
|
||||
fl_alert("Can't launch external editor '%s':\n%s\n\ncmd: \"%s\"",
|
||||
|
|
|
@ -24,7 +24,7 @@ class ExternalCodeEditor {
|
|||
time_t file_mtime_; // last modify time of the file (used to determine if file changed)
|
||||
size_t file_size_; // last file size (used to determine if changed)
|
||||
const char *filename_;
|
||||
Fd_String command_line_;
|
||||
Fl_String command_line_;
|
||||
int last_error_;
|
||||
int alert_pipe_[2];
|
||||
bool alert_pipe_open_;
|
||||
|
|
|
@ -389,7 +389,7 @@ const char* ExternalCodeEditor::tmp_filename() {
|
|||
static char path[512];
|
||||
const char *tmpdir = create_tmpdir();
|
||||
if ( !tmpdir ) return 0;
|
||||
const char *ext = g_project.code_file_name; // e.g. ".cxx"
|
||||
const char *ext = g_project.code_file_name.c_str(); // e.g. ".cxx"
|
||||
_snprintf(path, sizeof(path), "%s\\%p%s", tmpdir, (void*)this, ext);
|
||||
path[sizeof(path)-1] = 0;
|
||||
return path;
|
||||
|
|
|
@ -381,7 +381,7 @@ void Fl_Menu_Item_Type::write_item(Fd_Code_Writer& f) {
|
|||
switch (g_project.i18n_type) {
|
||||
case 1:
|
||||
// we will call i18n when the menu is instantiated for the first time
|
||||
f.write_c("%s(", g_project.i18n_static_function.value());
|
||||
f.write_c("%s(", g_project.i18n_static_function.c_str());
|
||||
f.write_cstring(label());
|
||||
f.write_c(")");
|
||||
break;
|
||||
|
@ -482,11 +482,11 @@ void Fl_Menu_Item_Type::write_code1(Fd_Code_Writer& f) {
|
|||
f.write_c("%sml->labelb = o->label();\n", f.indent());
|
||||
} else if (g_project.i18n_type==1) {
|
||||
f.write_c("%sml->labelb = %s(o->label());\n",
|
||||
f.indent(), g_project.i18n_function.value());
|
||||
f.indent(), g_project.i18n_function.c_str());
|
||||
} else if (g_project.i18n_type==2) {
|
||||
f.write_c("%sml->labelb = catgets(%s,%s,i+%d,o->label());\n",
|
||||
f.indent(), g_project.i18n_file[0] ? g_project.i18n_file.value() : "_catalog",
|
||||
g_project.i18n_set.value(), msgnum());
|
||||
f.indent(), g_project.i18n_file[0] ? g_project.i18n_file.c_str() : "_catalog",
|
||||
g_project.i18n_set.c_str(), msgnum());
|
||||
}
|
||||
f.write_c("%sml->typea = FL_IMAGE_LABEL;\n", f.indent());
|
||||
f.write_c("%sml->typeb = FL_NORMAL_LABEL;\n", f.indent());
|
||||
|
@ -504,11 +504,11 @@ void Fl_Menu_Item_Type::write_code1(Fd_Code_Writer& f) {
|
|||
start_menu_initialiser(f, menuItemInitialized, mname, i);
|
||||
if (g_project.i18n_type==1) {
|
||||
f.write_c("%so->label(%s(o->label()));\n",
|
||||
f.indent(), g_project.i18n_function.value());
|
||||
f.indent(), g_project.i18n_function.c_str());
|
||||
} else if (g_project.i18n_type==2) {
|
||||
f.write_c("%so->label(catgets(%s,%s,i+%d,o->label()));\n",
|
||||
f.indent(), g_project.i18n_file[0] ? g_project.i18n_file.value() : "_catalog",
|
||||
g_project.i18n_set.value(), msgnum());
|
||||
f.indent(), g_project.i18n_file[0] ? g_project.i18n_file.c_str() : "_catalog",
|
||||
g_project.i18n_set.c_str(), msgnum());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2917,13 +2917,13 @@ void Fl_Widget_Type::write_code1(Fd_Code_Writer& f) {
|
|||
f.write_cstring(label());
|
||||
break;
|
||||
case 1 : /* GNU gettext */
|
||||
f.write_c("%s(", g_project.i18n_function.value());
|
||||
f.write_c("%s(", g_project.i18n_function.c_str());
|
||||
f.write_cstring(label());
|
||||
f.write_c(")");
|
||||
break;
|
||||
case 2 : /* POSIX catgets */
|
||||
f.write_c("catgets(%s,%s,%d,", g_project.i18n_file[0] ? g_project.i18n_file.value() : "_catalog",
|
||||
g_project.i18n_set.value(), msgnum());
|
||||
f.write_c("catgets(%s,%s,%d,", g_project.i18n_file[0] ? g_project.i18n_file.c_str() : "_catalog",
|
||||
g_project.i18n_set.c_str(), msgnum());
|
||||
f.write_cstring(label());
|
||||
f.write_c(")");
|
||||
break;
|
||||
|
@ -2990,13 +2990,13 @@ void Fl_Widget_Type::write_widget_code(Fd_Code_Writer& f) {
|
|||
f.write_cstring(tooltip());
|
||||
break;
|
||||
case 1 : /* GNU gettext */
|
||||
f.write_c("%s(", g_project.i18n_function.value());
|
||||
f.write_c("%s(", g_project.i18n_function.c_str());
|
||||
f.write_cstring(tooltip());
|
||||
f.write_c(")");
|
||||
break;
|
||||
case 2 : /* POSIX catgets */
|
||||
f.write_c("catgets(%s,%s,%d,", g_project.i18n_file[0] ? g_project.i18n_file.value() : "_catalog",
|
||||
g_project.i18n_set.value(), msgnum() + 1);
|
||||
f.write_c("catgets(%s,%s,%d,", g_project.i18n_file[0] ? g_project.i18n_file.c_str() : "_catalog",
|
||||
g_project.i18n_set.c_str(), msgnum() + 1);
|
||||
f.write_cstring(tooltip());
|
||||
f.write_c(")");
|
||||
break;
|
||||
|
|
|
@ -196,15 +196,15 @@ void show_project_cb(Fl_Widget *, void *) {
|
|||
use_FL_COMMAND_button->value(g_project.use_FL_COMMAND);
|
||||
utf8_in_src_button->value(g_project.utf8_in_src);
|
||||
avoid_early_includes_button->value(g_project.avoid_early_includes);
|
||||
header_file_input->value(g_project.header_file_name);
|
||||
code_file_input->value(g_project.code_file_name);
|
||||
header_file_input->value(g_project.header_file_name.c_str());
|
||||
code_file_input->value(g_project.code_file_name.c_str());
|
||||
i18n_type_chooser->value(g_project.i18n_type);
|
||||
i18n_function_input->value(g_project.i18n_function);
|
||||
i18n_static_function_input->value(g_project.i18n_static_function);
|
||||
i18n_file_input->value(g_project.i18n_file);
|
||||
i18n_set_input->value(g_project.i18n_set);
|
||||
i18n_include_input->value(g_project.i18n_include);
|
||||
i18n_conditional_input->value(g_project.i18n_conditional);
|
||||
i18n_function_input->value(g_project.i18n_function.c_str());
|
||||
i18n_static_function_input->value(g_project.i18n_static_function.c_str());
|
||||
i18n_file_input->value(g_project.i18n_file.c_str());
|
||||
i18n_set_input->value(g_project.i18n_set.c_str());
|
||||
i18n_include_input->value(g_project.i18n_include.c_str());
|
||||
i18n_conditional_input->value(g_project.i18n_conditional.c_str());
|
||||
switch (g_project.i18n_type) {
|
||||
case 0 : /* None */
|
||||
i18n_include_input->hide();
|
||||
|
@ -258,12 +258,12 @@ void show_settings_cb(Fl_Widget *, void *) {
|
|||
}
|
||||
|
||||
void header_input_cb(Fl_Input* i, void*) {
|
||||
if (strcmp(g_project.header_file_name, i->value()))
|
||||
if (strcmp(g_project.header_file_name.c_str(), i->value()))
|
||||
set_modflag(1);
|
||||
g_project.header_file_name = i->value();
|
||||
}
|
||||
void code_input_cb(Fl_Input* i, void*) {
|
||||
if (strcmp(g_project.code_file_name, i->value()))
|
||||
if (strcmp(g_project.code_file_name.c_str(), i->value()))
|
||||
set_modflag(1);
|
||||
g_project.code_file_name = i->value();
|
||||
}
|
||||
|
|
|
@ -134,7 +134,7 @@ int write_strings(const char *sfile) {
|
|||
case 2 : /* POSIX catgets, put a .msg file out */
|
||||
fprintf(fp, "$ generated by Fast Light User Interface Designer (fluid) version %.4f\n",
|
||||
FL_VERSION);
|
||||
fprintf(fp, "$set %s\n", g_project.i18n_set.value());
|
||||
fprintf(fp, "$set %s\n", g_project.i18n_set.c_str());
|
||||
fputs("$quote \"\n", fp);
|
||||
|
||||
for (i = 1, p = Fl_Type::first; p; p = p->next) {
|
||||
|
@ -768,7 +768,7 @@ int Fd_Code_Writer::write_code(const char *s, const char *t, bool to_sourceview)
|
|||
if (t && g_project.include_H_from_C) {
|
||||
if (to_sourceview) {
|
||||
write_c("#include \"CodeView.h\"\n");
|
||||
} else if (g_project.header_file_name[0] == '.' && strchr(g_project.header_file_name, '/') == NULL) {
|
||||
} else if (g_project.header_file_name[0] == '.' && strchr(g_project.header_file_name.c_str(), '/') == NULL) {
|
||||
write_c("#include \"%s\"\n", fl_filename_name(t));
|
||||
} else {
|
||||
write_c("#include \"%s\"\n", t);
|
||||
|
@ -777,30 +777,30 @@ int Fd_Code_Writer::write_code(const char *s, const char *t, bool to_sourceview)
|
|||
if (g_project.i18n_type && g_project.i18n_include[0]) {
|
||||
int conditional = (g_project.i18n_conditional[0]!=0);
|
||||
if (conditional) {
|
||||
write_c("#ifdef %s\n", g_project.i18n_conditional.value());
|
||||
write_c("#ifdef %s\n", g_project.i18n_conditional.c_str());
|
||||
indentation++;
|
||||
}
|
||||
if (g_project.i18n_include[0] != '<' &&
|
||||
g_project.i18n_include[0] != '\"')
|
||||
write_c("#%sinclude \"%s\"\n", indent(), g_project.i18n_include.value());
|
||||
write_c("#%sinclude \"%s\"\n", indent(), g_project.i18n_include.c_str());
|
||||
else
|
||||
write_c("#%sinclude %s\n", indent(), g_project.i18n_include.value());
|
||||
write_c("#%sinclude %s\n", indent(), g_project.i18n_include.c_str());
|
||||
if (g_project.i18n_type == 2) {
|
||||
if (g_project.i18n_file[0]) {
|
||||
write_c("extern nl_catd %s;\n", g_project.i18n_file.value());
|
||||
write_c("extern nl_catd %s;\n", g_project.i18n_file.c_str());
|
||||
} else {
|
||||
write_c("// Initialize I18N stuff now for menus...\n");
|
||||
write_c("#%sinclude <locale.h>\n", indent());
|
||||
write_c("static char *_locale = setlocale(LC_MESSAGES, \"\");\n");
|
||||
write_c("static nl_catd _catalog = catopen(\"%s\", 0);\n", g_project.i18n_program.value());
|
||||
write_c("static nl_catd _catalog = catopen(\"%s\", 0);\n", g_project.i18n_program.c_str());
|
||||
}
|
||||
}
|
||||
if (conditional) {
|
||||
write_c("#else\n");
|
||||
if (g_project.i18n_type == 1) {
|
||||
if (g_project.i18n_function[0]) {
|
||||
write_c("#%sifndef %s\n", indent(), g_project.i18n_function.value());
|
||||
write_c("#%sdefine %s(text) text\n", indent_plus(1), g_project.i18n_function.value());
|
||||
write_c("#%sifndef %s\n", indent(), g_project.i18n_function.c_str());
|
||||
write_c("#%sdefine %s(text) text\n", indent_plus(1), g_project.i18n_function.c_str());
|
||||
write_c("#%sendif\n", indent());
|
||||
}
|
||||
}
|
||||
|
@ -813,8 +813,8 @@ int Fd_Code_Writer::write_code(const char *s, const char *t, bool to_sourceview)
|
|||
write_c("#endif\n");
|
||||
}
|
||||
if (g_project.i18n_type == 1 && g_project.i18n_static_function[0]) {
|
||||
write_c("#ifndef %s\n", g_project.i18n_static_function.value());
|
||||
write_c("#%sdefine %s(text) text\n", indent_plus(1), g_project.i18n_static_function.value());
|
||||
write_c("#ifndef %s\n", g_project.i18n_static_function.c_str());
|
||||
write_c("#%sdefine %s(text) text\n", indent_plus(1), g_project.i18n_static_function.c_str());
|
||||
write_c("#endif\n");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -771,26 +771,26 @@ int Fd_Project_Writer::write_project(const char *filename, int selected_only) {
|
|||
write_string("\navoid_early_includes");
|
||||
if (g_project.i18n_type) {
|
||||
write_string("\ni18n_type %d", g_project.i18n_type);
|
||||
write_string("\ni18n_include"); write_word(g_project.i18n_include);
|
||||
write_string("\ni18n_conditional"); write_word(g_project.i18n_conditional);
|
||||
write_string("\ni18n_include"); write_word(g_project.i18n_include.c_str());
|
||||
write_string("\ni18n_conditional"); write_word(g_project.i18n_conditional.c_str());
|
||||
switch (g_project.i18n_type) {
|
||||
case 1 : /* GNU gettext */
|
||||
write_string("\ni18n_function"); write_word(g_project.i18n_function);
|
||||
write_string("\ni18n_static_function"); write_word(g_project.i18n_static_function);
|
||||
write_string("\ni18n_function"); write_word(g_project.i18n_function.c_str());
|
||||
write_string("\ni18n_static_function"); write_word(g_project.i18n_static_function.c_str());
|
||||
break;
|
||||
case 2 : /* POSIX catgets */
|
||||
if (g_project.i18n_file[0]) {
|
||||
write_string("\ni18n_file");
|
||||
write_word(g_project.i18n_file);
|
||||
write_word(g_project.i18n_file.c_str());
|
||||
}
|
||||
write_string("\ni18n_set"); write_word(g_project.i18n_set);
|
||||
write_string("\ni18n_set"); write_word(g_project.i18n_set.c_str());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!selected_only) {
|
||||
write_string("\nheader_name"); write_word(g_project.header_file_name);
|
||||
write_string("\ncode_name"); write_word(g_project.code_file_name);
|
||||
write_string("\nheader_name"); write_word(g_project.header_file_name.c_str());
|
||||
write_string("\ncode_name"); write_word(g_project.code_file_name.c_str());
|
||||
|
||||
#if 0
|
||||
// https://github.com/fltk/fltk/issues/328
|
||||
|
|
|
@ -164,8 +164,8 @@ int compile_strings = 0; // fluic -cs
|
|||
int batch_mode = 0; // if set (-c, -u) don't open display
|
||||
|
||||
/// command line arguments override settings in the projectfile
|
||||
Fd_String g_code_filename_arg;
|
||||
Fd_String g_header_filename_arg;
|
||||
Fl_String g_code_filename_arg;
|
||||
Fl_String g_header_filename_arg;
|
||||
|
||||
/** \var int Fluid_Project::header_file_set
|
||||
If set, commandline overrides header file name in .fl file.
|
||||
|
@ -1001,19 +1001,20 @@ int write_code_files() {
|
|||
char cname[FL_PATH_MAX+1];
|
||||
char hname[FL_PATH_MAX+1];
|
||||
g_project.i18n_program = fl_filename_name(filename);
|
||||
g_project.i18n_program.capacity(FL_PATH_MAX);
|
||||
fl_filename_setext(g_project.i18n_program.buffer(), FL_PATH_MAX, "");
|
||||
if (g_project.code_file_name[0] == '.' && strchr(g_project.code_file_name, '/') == NULL) {
|
||||
g_project.i18n_program.resize(FL_PATH_MAX);
|
||||
fl_filename_setext(g_project.i18n_program.data(), FL_PATH_MAX, "");
|
||||
g_project.i18n_program.resize(g_project.i18n_program.strlen());
|
||||
if (g_project.code_file_name[0] == '.' && strchr(g_project.code_file_name.c_str(), '/') == NULL) {
|
||||
strlcpy(cname, fl_filename_name(filename), FL_PATH_MAX);
|
||||
fl_filename_setext(cname, FL_PATH_MAX, g_project.code_file_name);
|
||||
fl_filename_setext(cname, FL_PATH_MAX, g_project.code_file_name.c_str());
|
||||
} else {
|
||||
strlcpy(cname, g_project.code_file_name, FL_PATH_MAX);
|
||||
strlcpy(cname, g_project.code_file_name.c_str(), FL_PATH_MAX);
|
||||
}
|
||||
if (g_project.header_file_name[0] == '.' && strchr(g_project.header_file_name, '/') == NULL) {
|
||||
if (g_project.header_file_name[0] == '.' && strchr(g_project.header_file_name.c_str(), '/') == NULL) {
|
||||
strlcpy(hname, fl_filename_name(filename), FL_PATH_MAX);
|
||||
fl_filename_setext(hname, FL_PATH_MAX, g_project.header_file_name);
|
||||
fl_filename_setext(hname, FL_PATH_MAX, g_project.header_file_name.c_str());
|
||||
} else {
|
||||
strlcpy(hname, g_project.header_file_name, FL_PATH_MAX);
|
||||
strlcpy(hname, g_project.header_file_name.c_str(), FL_PATH_MAX);
|
||||
}
|
||||
if (!batch_mode) enter_project_dir();
|
||||
Fd_Code_Writer f;
|
||||
|
@ -1805,7 +1806,7 @@ void set_modflag(int mf, int mfc) {
|
|||
#endif // _WIN32
|
||||
else basename = filename;
|
||||
|
||||
code_ext = fl_filename_ext(g_project.code_file_name);
|
||||
code_ext = fl_filename_ext(g_project.code_file_name.c_str());
|
||||
char mod_star = modflag ? '*' : ' ';
|
||||
char mod_c_star = modflag_c ? '*' : ' ';
|
||||
snprintf(title, sizeof(title), "%s%c %s%c",
|
||||
|
@ -1927,11 +1928,12 @@ void update_sourceview_cb(Fl_Button*, void*)
|
|||
sv_strings->scroll(top, 0);
|
||||
} else if (sv_source->visible_r() || sv_header->visible_r()) {
|
||||
g_project.i18n_program = fl_filename_name(sv_source_filename);
|
||||
g_project.i18n_program.capacity(FL_PATH_MAX);
|
||||
fl_filename_setext(g_project.i18n_program.buffer(), FL_PATH_MAX, "");
|
||||
Fd_String code_file_name_bak = g_project.code_file_name;
|
||||
g_project.i18n_program.resize(FL_PATH_MAX);
|
||||
fl_filename_setext(g_project.i18n_program.data(), FL_PATH_MAX, "");
|
||||
g_project.i18n_program.resize(g_project.i18n_program.strlen());
|
||||
Fl_String code_file_name_bak = g_project.code_file_name;
|
||||
g_project.code_file_name = sv_source_filename;
|
||||
Fd_String header_file_name_bak = g_project.header_file_name;
|
||||
Fl_String header_file_name_bak = g_project.header_file_name;
|
||||
g_project.header_file_name = sv_header_filename;
|
||||
|
||||
// generate the code and load the files
|
||||
|
|
|
@ -78,18 +78,6 @@ extern int batch_mode;
|
|||
|
||||
extern int pasteoffset;
|
||||
|
||||
// ---- string handling
|
||||
|
||||
class Fd_String : public Fl_String
|
||||
{
|
||||
public:
|
||||
Fd_String() : Fl_String("") { }
|
||||
Fd_String(const char* s) : Fl_String(s) { }
|
||||
int empty() { return size()==0; }
|
||||
void operator=(const char* s) { value(s); }
|
||||
operator const char* () const { return value(); }
|
||||
};
|
||||
|
||||
// ---- project settings
|
||||
|
||||
class Fluid_Project {
|
||||
|
@ -99,27 +87,27 @@ public:
|
|||
void reset();
|
||||
|
||||
int i18n_type;
|
||||
Fd_String i18n_include;
|
||||
Fd_String i18n_conditional;
|
||||
Fd_String i18n_function;
|
||||
Fd_String i18n_static_function;
|
||||
Fd_String i18n_file;
|
||||
Fd_String i18n_set;
|
||||
Fd_String i18n_program;
|
||||
Fl_String i18n_include;
|
||||
Fl_String i18n_conditional;
|
||||
Fl_String i18n_function;
|
||||
Fl_String i18n_static_function;
|
||||
Fl_String i18n_file;
|
||||
Fl_String i18n_set;
|
||||
Fl_String i18n_program;
|
||||
int include_H_from_C;
|
||||
int use_FL_COMMAND;
|
||||
int utf8_in_src;
|
||||
int avoid_early_includes;
|
||||
int header_file_set;
|
||||
int code_file_set;
|
||||
Fd_String header_file_name;
|
||||
Fd_String code_file_name;
|
||||
Fl_String header_file_name;
|
||||
Fl_String code_file_name;
|
||||
};
|
||||
|
||||
extern Fluid_Project g_project;
|
||||
|
||||
extern Fd_String g_code_filename_arg;
|
||||
extern Fd_String g_header_filename_arg;
|
||||
extern Fl_String g_code_filename_arg;
|
||||
extern Fl_String g_header_filename_arg;
|
||||
|
||||
// ---- public functions
|
||||
|
||||
|
|
|
@ -66,7 +66,7 @@ Use Ctrl-J for newlines.} xywh {95 40 190 20} labelfont 1 labelsize 11 when 15 t
|
|||
xywh {95 65 309 20} labelfont 1 labelsize 11 align 4
|
||||
} {
|
||||
Fl_Input {} {
|
||||
callback image_cb
|
||||
callback image_cb selected
|
||||
tooltip {The active image for the widget.} xywh {95 65 200 20} labelfont 1 labelsize 11 textsize 11 resizable
|
||||
}
|
||||
Fl_Button {} {
|
||||
|
@ -471,7 +471,7 @@ h, ph, sh, ch, and i} xywh {275 150 55 20} labelsize 11 align 5 textsize 11
|
|||
} {}
|
||||
Fl_Button {} {
|
||||
callback shortcut_in_cb
|
||||
comment {This is a special button that grabs keystrokes directly} selected
|
||||
comment {This is a special button that grabs keystrokes directly}
|
||||
tooltip {The shortcut key for the widget.
|
||||
Use 'Backspace' key to clear.} xywh {95 210 310 20} box DOWN_BOX color 7 selection_color 12 labelsize 11 when 1
|
||||
code0 {\#include <FL/Fl_Shortcut_Button.H>}
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
||||
/** Constructs an empty string */
|
||||
Fl_String::Fl_String() {
|
||||
init();
|
||||
}
|
||||
|
||||
/** Constructor from a C-style string */
|
||||
Fl_String::Fl_String(const char *str) {
|
||||
init();
|
||||
value(str);
|
||||
}
|
||||
/*
|
||||
If buffer_ is NULL, c_str() and buffer() will point here.
|
||||
*/
|
||||
const char Fl_String::NUL = 0;
|
||||
|
||||
/** Constructor from a buffer of \c size bytes */
|
||||
Fl_String::Fl_String(const char *str, int size) {
|
||||
init();
|
||||
value(str, size);
|
||||
}
|
||||
/**
|
||||
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;
|
||||
|
||||
void Fl_String::init() {
|
||||
/**
|
||||
Initialise the class instance.
|
||||
*/
|
||||
void Fl_String::init_() {
|
||||
buffer_ = NULL;
|
||||
size_ = 0;
|
||||
value_ = 0;
|
||||
capacity_ = 0;
|
||||
}
|
||||
|
||||
/** copy constructor */
|
||||
Fl_String::Fl_String(const Fl_String &in) {
|
||||
init();
|
||||
value(in.value(), in.size());
|
||||
}
|
||||
/**
|
||||
Grow the buffer to a capacity of at least n bytes.
|
||||
|
||||
/** 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");
|
||||
return *this;
|
||||
}
|
||||
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.
|
||||
|
||||
/** assignment operator for 'const char *' */
|
||||
Fl_String& Fl_String::operator=(const char *in) {
|
||||
value(in);
|
||||
// debug("*STRING* assigned");
|
||||
return *this;
|
||||
}
|
||||
|
||||
/** Destructor */
|
||||
Fl_String::~Fl_String() {
|
||||
delete[] value_;
|
||||
}
|
||||
|
||||
/** 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
|
||||
\param[in] n number of bytes needed, not counting the trailing NUL
|
||||
*/
|
||||
void Fl_String::alloc_buf(int size, bool preserve_text) {
|
||||
if (size < 0)
|
||||
void Fl_String::grow_(int n) {
|
||||
if (n <= capacity_)
|
||||
return;
|
||||
if (size > 0 && size <= capacity_)
|
||||
return;
|
||||
|
||||
int new_size = (size + 1 + 15) & (~15); // round upwards
|
||||
char *new_value = new char[new_size];
|
||||
capacity_ = new_size - 1;
|
||||
|
||||
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);
|
||||
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 {
|
||||
size_ = 0;
|
||||
alloc_size_ = (alloc_size_+2048) & ~2047; // allocate in 2k chunks
|
||||
}
|
||||
delete[] value_;
|
||||
value_ = new_value;
|
||||
// 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
|
||||
}
|
||||
|
||||
/** Assigns the string value to a C-style string */
|
||||
void Fl_String::value(const char *str) {
|
||||
value(str, str ? (int)strlen(str) : 0);
|
||||
}
|
||||
/**
|
||||
Shrink the buffer to n bytes, or size, if size > n.
|
||||
|
||||
/** Returns the number of non-null bytes in the object */
|
||||
int Fl_String::slen() const {
|
||||
if (!value_) return 0;
|
||||
return (int)strlen(value_);
|
||||
}
|
||||
Shrink the buffer as much as possible. If \p n is 0 and the string is empty,
|
||||
the buffer will be released.
|
||||
|
||||
/** 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
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the minimum capacity of the object */
|
||||
int Fl_String::capacity() const {
|
||||
return capacity_; // > 0 ? capacity_ - 1 : 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
|
||||
\param[in] n shrink buffer to n bytes, not counting the trailing NUL
|
||||
*/
|
||||
void Fl_String::capacity(int num_bytes) {
|
||||
alloc_buf(num_bytes, true);
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
void Fl_String::release() {
|
||||
delete[] value_;
|
||||
value_ = 0;
|
||||
size_ = 0;
|
||||
capacity_ = 0;
|
||||
// ---- Assignment ----------------------------------------------------- MARK: -
|
||||
|
||||
/**
|
||||
Allocate an empty string.
|
||||
*/
|
||||
Fl_String::Fl_String() {
|
||||
init_();
|
||||
}
|
||||
|
||||
// ============================= DEBUG =============================
|
||||
/**
|
||||
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.
|
||||
\param[in] cstr a NUL terminated C-style string
|
||||
*/
|
||||
Fl_String::Fl_String(const char *cstr) {
|
||||
init_();
|
||||
assign(cstr);
|
||||
}
|
||||
|
||||
/**
|
||||
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_();
|
||||
assign(str, size);
|
||||
}
|
||||
|
||||
/**
|
||||
Destructor.
|
||||
*/
|
||||
Fl_String::~Fl_String() {
|
||||
if (buffer_)
|
||||
::free(buffer_);
|
||||
}
|
||||
|
||||
/**
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
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;
|
||||
}
|
||||
|
||||
// ---- 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);
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the character at specified location.
|
||||
\param[in] n index of character
|
||||
\return character at that index
|
||||
*/
|
||||
char Fl_String::operator[](int n) const {
|
||||
if (buffer_)
|
||||
return buffer_[n];
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
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];
|
||||
}
|
||||
|
||||
/**
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
Return a pointer to the writable NUL terminated string.
|
||||
\return reference to mutable string
|
||||
*/
|
||||
char *Fl_String::data() {
|
||||
if (!buffer_)
|
||||
reserve(1);
|
||||
return buffer_;
|
||||
}
|
||||
|
||||
/**
|
||||
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();
|
||||
}
|
||||
|
||||
// ---- 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 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_;
|
||||
}
|
||||
|
||||
/**
|
||||
Shrink the capacity to fit the current size.
|
||||
*/
|
||||
void Fl_String::shrink_to_fit() {
|
||||
shrink_(size_);
|
||||
}
|
||||
|
||||
// ---- Operations ----------------------------------------------------- MARK: -
|
||||
|
||||
/**
|
||||
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;
|
||||
}
|
||||
|
||||
// --- 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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
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)
|
||||
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;
|
||||
va_list ap;
|
||||
va_start(ap, defstr);
|
||||
const char *r = msg.input_innards(fmt, ap, defstr, FL_NORMAL_INPUT, maxchar);
|
||||
va_end(ap);
|
||||
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;
|
||||
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)
|
||||
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;
|
||||
va_list ap;
|
||||
va_start(ap, defstr);
|
||||
const char *r = msg.input_innards(fmt, ap, defstr, FL_SECRET_INPUT, maxchar);
|
||||
va_end(ap);
|
||||
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().
|
||||
|
|
|
@ -154,6 +154,7 @@ SET (UNITTEST_SRCS
|
|||
unittests.h
|
||||
unittest_about.cxx
|
||||
unittest_points.cxx
|
||||
unittest_core.cxx
|
||||
unittest_complex_shapes.cxx
|
||||
unittest_fast_shapes.cxx
|
||||
unittest_circles.cxx
|
||||
|
|
|
@ -30,7 +30,8 @@ CPPUNITTEST = \
|
|||
unittest_viewport.cxx \
|
||||
unittest_scrollbarsize.cxx \
|
||||
unittest_schemes.cxx \
|
||||
unittest_simple_terminal.cxx
|
||||
unittest_simple_terminal.cxx \
|
||||
unittest_core.cxx
|
||||
|
||||
OBJUNITTEST = \
|
||||
unittests.o \
|
||||
|
|
|
@ -34,16 +34,17 @@
|
|||
|
||||
void rename_button(Fl_Widget *o, void *v) {
|
||||
int what = fl_int(v);
|
||||
int ret = 0;
|
||||
Fl_String input;
|
||||
if (what == 0) {
|
||||
fl_message_icon_label("§");
|
||||
input = fl_input_str(0, "Input (no size limit, use ctrl/j for newline):", o->label());
|
||||
input = fl_input_str(ret, 0, "Input (no size limit, use ctrl/j for newline):", o->label());
|
||||
} else {
|
||||
fl_message_icon_label("€");
|
||||
input = fl_password_str(20, "Enter password (max. 20 characters):", o->label());
|
||||
input = fl_password_str(ret, 20, "Enter password (max. 20 characters):", o->label());
|
||||
}
|
||||
if (input.value()) {
|
||||
o->copy_label(input.value());
|
||||
if (ret == 0) {
|
||||
o->copy_label(input.c_str());
|
||||
o->redraw();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,253 @@
|
|||
//
|
||||
// Unit tests for the Fast Light Tool Kit (FLTK).
|
||||
//
|
||||
// Copyright 1998-2023 by Bill Spitzak and others.
|
||||
//
|
||||
// This library is free software. Distribution and use rights are outlined in
|
||||
// the file "COPYING" which should have been included with this file. If this
|
||||
// file is missing or damaged, see the license at:
|
||||
//
|
||||
// https://www.fltk.org/COPYING.php
|
||||
//
|
||||
// Please see the following page on how to report bugs and issues:
|
||||
//
|
||||
// https://www.fltk.org/bugs.php
|
||||
//
|
||||
|
||||
#include "unittests.h"
|
||||
|
||||
#include <FL/Fl_Group.H>
|
||||
#include <FL/Fl_Simple_Terminal.H>
|
||||
#include <FL/Fl_String.H>
|
||||
|
||||
/* Test Fl_String constructor and assignment. */
|
||||
TEST(Fl_String, Assignment) {
|
||||
Fl_String null;
|
||||
EXPECT_STREQ(null.c_str(), ""); // default initialisation is an empty string
|
||||
EXPECT_TRUE(null.empty());
|
||||
|
||||
Fl_String null2(NULL);
|
||||
EXPECT_STREQ(null2.c_str(), ""); // initialise with a NULL pointer gets an empty string
|
||||
EXPECT_TRUE(null2.empty());
|
||||
|
||||
Fl_String empty("");
|
||||
EXPECT_STREQ(empty.c_str(), ""); // also, empty CString make empty Fl_String
|
||||
EXPECT_TRUE(empty.empty());
|
||||
|
||||
Fl_String text("hello");
|
||||
EXPECT_STREQ(text.c_str(), "hello"); // Load some text from a CString
|
||||
EXPECT_EQ(text.size(), 5); // did we get the size right?
|
||||
EXPECT_EQ(text.strlen(), 5); // do we have a trailing 0
|
||||
EXPECT_GE(text.capacity(), 5); // do we have the capacity
|
||||
EXPECT_TRUE(!text.empty()); // test the empty() method
|
||||
|
||||
Fl_String text2("abcdef", 3);
|
||||
EXPECT_STREQ(text2.c_str(), "abc");
|
||||
EXPECT_EQ(text2.size(), 3);
|
||||
|
||||
Fl_String text3("abc\0def", 7);
|
||||
EXPECT_EQ(text3.strlen(), 3);
|
||||
EXPECT_EQ(text3.size(), 7);
|
||||
|
||||
Fl_String text4(text);
|
||||
EXPECT_STREQ(text4.c_str(), "hello");
|
||||
|
||||
Fl_String text5 = text;
|
||||
EXPECT_STREQ(text5.c_str(), "hello");
|
||||
|
||||
Fl_String text6 = "yoohoo";
|
||||
EXPECT_STREQ(text6.c_str(), "yoohoo");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Test methods that access Fl_String content and parts of it. */
|
||||
TEST(Fl_String, Access) {
|
||||
Fl_String hello = "hello";
|
||||
EXPECT_STREQ(hello.c_str(), "hello");
|
||||
EXPECT_STREQ(hello.data(), "hello");
|
||||
EXPECT_EQ(hello[1], 'e');
|
||||
EXPECT_EQ(hello[hello.size()], 0);
|
||||
EXPECT_EQ(hello.at(1), 'e');
|
||||
EXPECT_EQ(hello.at(-1), 0);
|
||||
EXPECT_EQ(hello.at(11), 0);
|
||||
|
||||
hello[1] = 'a';
|
||||
EXPECT_STREQ(hello.c_str(), "hallo");
|
||||
|
||||
hello.data()[1] = 'e';
|
||||
EXPECT_STREQ(hello.c_str(), "hello");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Test the Fl_String capacity management. */
|
||||
TEST(Fl_String, Capacity) {
|
||||
Fl_String hello;
|
||||
EXPECT_EQ(hello.capacity(), 0);
|
||||
|
||||
hello = "hi";
|
||||
EXPECT_STREQ(hello.c_str(), "hi");
|
||||
EXPECT_GE(hello.capacity(), 2);
|
||||
|
||||
hello = "the quick brown fox jumps over the lazy dog";
|
||||
EXPECT_STREQ(hello.c_str(), "the quick brown fox jumps over the lazy dog");
|
||||
EXPECT_GE(hello.capacity(), 41);
|
||||
|
||||
int c = hello.capacity();
|
||||
hello.reserve(c+100);
|
||||
EXPECT_STREQ(hello.c_str(), "the quick brown fox jumps over the lazy dog");
|
||||
EXPECT_GE(hello.capacity(), 141);
|
||||
|
||||
hello = "hi";
|
||||
hello.shrink_to_fit();
|
||||
EXPECT_EQ(hello.capacity(), 2);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Test all methods that operate on Fl_String. */
|
||||
TEST(Fl_String, Operations) {
|
||||
Fl_String empty;
|
||||
Fl_String hello = "Hello", world = "World";
|
||||
hello.resize(4);
|
||||
EXPECT_STREQ(hello.c_str(), "Hell");
|
||||
|
||||
hello.clear();
|
||||
EXPECT_TRUE(hello.empty());
|
||||
|
||||
hello = "Hello";
|
||||
hello.insert(3, "-");
|
||||
EXPECT_STREQ(hello.c_str(), "Hel-lo");
|
||||
hello = "Hello";
|
||||
hello.erase(2, 2);
|
||||
EXPECT_STREQ(hello.c_str(), "Heo");
|
||||
|
||||
hello = "Hello";
|
||||
hello.push_back('!');
|
||||
EXPECT_STREQ(hello.c_str(), "Hello!");
|
||||
hello.pop_back();
|
||||
EXPECT_STREQ(hello.c_str(), "Hello");
|
||||
hello.append(world);
|
||||
EXPECT_STREQ(hello.c_str(), "HelloWorld");
|
||||
hello.append("!");
|
||||
EXPECT_STREQ(hello.c_str(), "HelloWorld!");
|
||||
hello = "Hello";
|
||||
hello += world;
|
||||
EXPECT_STREQ(hello.c_str(), "HelloWorld");
|
||||
hello += "!";
|
||||
EXPECT_STREQ(hello.c_str(), "HelloWorld!");
|
||||
hello += '?';
|
||||
EXPECT_STREQ(hello.c_str(), "HelloWorld!?");
|
||||
|
||||
hello = "Hello";
|
||||
hello.replace(0, 0, "Say ", 4);
|
||||
EXPECT_STREQ(hello.c_str(), "Say Hello");
|
||||
hello.replace(0, 4, "");
|
||||
EXPECT_STREQ(hello.c_str(), "Hello");
|
||||
hello.replace(2, 2, "bb");
|
||||
EXPECT_STREQ(hello.c_str(), "Hebbo");
|
||||
hello.replace(2, 2, "xxx");
|
||||
EXPECT_STREQ(hello.c_str(), "Hexxxo");
|
||||
hello.replace(2, 3, "ll");
|
||||
EXPECT_STREQ(hello.c_str(), "Hello");
|
||||
hello.replace(2, 0, NULL, 0);
|
||||
EXPECT_STREQ(hello.c_str(), "Hello");
|
||||
hello.replace(Fl_String::npos, Fl_String::npos, world);
|
||||
EXPECT_STREQ(hello.c_str(), "HelloWorld");
|
||||
|
||||
hello = "Hello";
|
||||
Fl_String sub = hello.substr();
|
||||
EXPECT_STREQ(sub.c_str(), "Hello"); // check correct usage
|
||||
sub = hello.substr(2);
|
||||
EXPECT_STREQ(sub.c_str(), "llo");
|
||||
sub = hello.substr(2, 2);
|
||||
EXPECT_STREQ(sub.c_str(), "ll");
|
||||
sub = hello.substr(-1, 2);
|
||||
EXPECT_TRUE(sub.empty()); // check faulty values
|
||||
sub = hello.substr(20, 2);
|
||||
EXPECT_TRUE(sub.empty());
|
||||
sub = empty.substr(0, 2);
|
||||
EXPECT_TRUE(sub.empty());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Test all Fl_String functions that are no part of the class. */
|
||||
TEST(Fl_String, Non-Member Functions) {
|
||||
Fl_String a = "a", b = "b", empty = "", result;
|
||||
result = a + b;
|
||||
EXPECT_STREQ(result.c_str(), "ab");
|
||||
result = a + empty;
|
||||
EXPECT_STREQ(result.c_str(), "a");
|
||||
result = a + "c";
|
||||
EXPECT_STREQ(result.c_str(), "ac");
|
||||
result = empty + "x";
|
||||
EXPECT_STREQ(result.c_str(), "x");
|
||||
EXPECT_TRUE(!(a == b));
|
||||
EXPECT_TRUE(a == a);
|
||||
EXPECT_TRUE(empty == empty);
|
||||
EXPECT_TRUE(a+b == "ab");
|
||||
EXPECT_TRUE(a+"b" == "a" + b);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//
|
||||
//------- test aspects of the FLTK core library ----------
|
||||
//
|
||||
|
||||
/*
|
||||
Create a tab with only a terminal window in it. When shown for the first time,
|
||||
unittest will visualize progress by runing all regsitered tests one-by-one
|
||||
every few miliseconds.
|
||||
|
||||
When run in command line mode (option `--core`), all tests are executed
|
||||
at full speed.
|
||||
*/
|
||||
class Ut_Core_Test : public Fl_Group {
|
||||
|
||||
Fl_Simple_Terminal *tty;
|
||||
bool suite_ran_;
|
||||
|
||||
public:
|
||||
|
||||
// Create the tab
|
||||
static Fl_Widget *create() {
|
||||
return new Ut_Core_Test(UT_TESTAREA_X, UT_TESTAREA_Y, UT_TESTAREA_W, UT_TESTAREA_H);
|
||||
}
|
||||
|
||||
// Constructor for this tab
|
||||
Ut_Core_Test(int x, int y, int w, int h)
|
||||
: Fl_Group(x, y, w, h),
|
||||
tty(NULL),
|
||||
suite_ran_(false)
|
||||
{
|
||||
tty = new Fl_Simple_Terminal(x+4, y+4, w-8, h-8, "Unittest Log");
|
||||
tty->ansi(true);
|
||||
end();
|
||||
Ut_Suite::tty = tty;
|
||||
}
|
||||
|
||||
// Run one single test and repeat calling this until all tests are done
|
||||
static void timer_cb(void*) {
|
||||
// Run a test every few miliseconds to visualize the progress
|
||||
if (Ut_Suite::run_next_test())
|
||||
Fl::repeat_timeout(0.2, timer_cb);
|
||||
}
|
||||
|
||||
// Showing this tab for the first time will trigger the tests
|
||||
void show() FL_OVERRIDE {
|
||||
Fl_Group::show();
|
||||
if (!suite_ran_) {
|
||||
Fl::add_timeout(0.5, timer_cb);
|
||||
suite_ran_ = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Register this tab with the unittest app.
|
||||
UnitTest core(UT_TEST_CORE, "Core Functionality", Ut_Core_Test::create);
|
||||
|
||||
|
||||
|
|
@ -29,10 +29,12 @@
|
|||
#include <FL/Fl_Double_Window.H>
|
||||
#include <FL/Fl_Hold_Browser.H>
|
||||
#include <FL/Fl_Help_View.H>
|
||||
#include <FL/Fl_Simple_Terminal.H>
|
||||
#include <FL/Fl_Group.H>
|
||||
#include <FL/Fl_Box.H>
|
||||
#include <FL/fl_draw.H> // fl_text_extents()
|
||||
#include <FL/fl_string_functions.h> // fl_strdup()
|
||||
#include <FL/fl_ask.H> // fl_message()
|
||||
#include <stdlib.h> // malloc, free
|
||||
|
||||
class Ut_Main_Window *mainwin = NULL;
|
||||
|
@ -41,6 +43,8 @@ class Fl_Hold_Browser *browser = NULL;
|
|||
int UnitTest::num_tests_ = 0;
|
||||
UnitTest *UnitTest::test_list_[200] = { 0 };
|
||||
|
||||
// ----- UnitTest ------------------------------------------------------ MARK: -
|
||||
|
||||
UnitTest::UnitTest(int index, const char* label, Fl_Widget* (*create)())
|
||||
: widget_(0L)
|
||||
{
|
||||
|
@ -77,6 +81,8 @@ void UnitTest::add(int index, UnitTest* t) {
|
|||
num_tests_ = index+1;
|
||||
}
|
||||
|
||||
// ----- Ut_Main_Window ------------------------------------------------ MARK: -
|
||||
|
||||
Ut_Main_Window::Ut_Main_Window(int w, int h, const char *l)
|
||||
: Fl_Double_Window(w, h, l),
|
||||
draw_alignment_test_(0)
|
||||
|
@ -118,10 +124,265 @@ void Ut_Main_Window::test_alignment(int v) {
|
|||
redraw();
|
||||
}
|
||||
|
||||
//------- include the various unit tests as inline code -------
|
||||
// ----- Ut_Test ------------------------------------------------------- MARK: -
|
||||
|
||||
/** Create a unit test that can contain many test cases using EXPECT_*.
|
||||
|
||||
Ut_Test should be instantiated by using the TEST(SUITE, NAME) macro. Multiple
|
||||
TEST() macros can be called within the same source file to generate an
|
||||
arbitrary number of tests. TEST() can be called in multiple source files
|
||||
of the same application.
|
||||
|
||||
The constructor also registers the test with Ut_Suite and groups it by name.
|
||||
*/
|
||||
Ut_Test::Ut_Test(const char *suitename, const char *testname, Ut_Test_Call call)
|
||||
: name_(testname),
|
||||
call_(call),
|
||||
failed_(false),
|
||||
done_(false)
|
||||
{
|
||||
Ut_Suite *suite = Ut_Suite::locate(suitename);
|
||||
suite->add(this);
|
||||
}
|
||||
|
||||
/** Run all cases inside the test and return false when the first case fails. */
|
||||
bool Ut_Test::run(const char *suite) {
|
||||
Ut_Suite::printf("%s[ RUN ]%s %s.%s\n",
|
||||
Ut_Suite::green, Ut_Suite::normal, suite, name_);
|
||||
bool ret = call_();
|
||||
if (ret) {
|
||||
Ut_Suite::printf("%s[ OK ]%s %s.%s\n",
|
||||
Ut_Suite::green, Ut_Suite::normal, suite, name_);
|
||||
failed_ = false;
|
||||
} else {
|
||||
Ut_Suite::printf("%s[ FAILED ]%s %s.%s\n",
|
||||
Ut_Suite::red, Ut_Suite::normal, suite, name_);
|
||||
failed_ = true;
|
||||
}
|
||||
done_ = true;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/** Print a message is the test was previously marked failed. */
|
||||
void Ut_Test::print_failed(const char *suite) {
|
||||
if (failed_) {
|
||||
Ut_Suite::printf("%s[ FAILED ]%s %s.%s\n",
|
||||
Ut_Suite::red, Ut_Suite::normal, suite, name_);
|
||||
}
|
||||
}
|
||||
|
||||
// ----- Ut_Suite ------------------------------------------------------ MARK: -
|
||||
|
||||
Ut_Suite **Ut_Suite::suite_list_ = NULL;
|
||||
int Ut_Suite::suite_list_size_ = 0;
|
||||
int Ut_Suite::num_tests_ = 0;
|
||||
int Ut_Suite::num_passed_ = 0;
|
||||
int Ut_Suite::num_failed_ = 0;
|
||||
const char *Ut_Suite::red = "\033[31m";
|
||||
const char *Ut_Suite::green = "\033[32m";
|
||||
const char *Ut_Suite::normal = "\033[0m";
|
||||
Fl_Simple_Terminal *Ut_Suite::tty = NULL;
|
||||
|
||||
/** Switch the user of color escape sequnces in the log text. */
|
||||
void Ut_Suite::color(int v) {
|
||||
if (v) {
|
||||
red = "\033[31m";
|
||||
green = "\033[32m";
|
||||
normal = "\033[0m";
|
||||
} else {
|
||||
red = "";
|
||||
green = "";
|
||||
normal = "";
|
||||
}
|
||||
}
|
||||
|
||||
/** Create a suite that will group tests by the suite name.
|
||||
|
||||
Ut_Suite is automatically instantiated by using the TEST(SUITE, NAME) macro.
|
||||
Multiple TEST() macros are grouped into suits by suite name.
|
||||
*/
|
||||
Ut_Suite::Ut_Suite(const char *name)
|
||||
: test_list_(NULL),
|
||||
test_list_size_(0),
|
||||
name_(name),
|
||||
done_(false)
|
||||
{
|
||||
}
|
||||
|
||||
/** Add a test to the suite. This is done automatically by the TEST() macro. */
|
||||
void Ut_Suite::add(Ut_Test *test) {
|
||||
if ( (test_list_size_ % 16) == 0 ) {
|
||||
test_list_ = (Ut_Test**)realloc(test_list_, (test_list_size_+16)*sizeof(Ut_Test*));
|
||||
}
|
||||
test_list_[test_list_size_++] = test;
|
||||
}
|
||||
|
||||
/** Static method that will find or create a suite by name. */
|
||||
Ut_Suite *Ut_Suite::locate(const char *name) {
|
||||
for (int i=0; i<suite_list_size_; i++) {
|
||||
if (strcmp(name, suite_list_[i]->name_)==0)
|
||||
return suite_list_[i];
|
||||
}
|
||||
if ( (suite_list_size_ % 16) == 0 ) {
|
||||
suite_list_ = (Ut_Suite**)realloc(suite_list_, (suite_list_size_+16)*sizeof(Ut_Suite*));
|
||||
}
|
||||
Ut_Suite *s = new Ut_Suite(name);
|
||||
suite_list_[suite_list_size_++] = s;
|
||||
return s;
|
||||
}
|
||||
|
||||
/** Logs the start of a test suite run. */
|
||||
void Ut_Suite::print_suite_epilog() {
|
||||
Ut_Suite::printf("%s[----------]%s %d test%s from %s\n", Ut_Suite::green, Ut_Suite::normal,
|
||||
test_list_size_, test_list_size_ == 1 ? "" : "s", name_);
|
||||
}
|
||||
|
||||
/** Run all tests in a single suite, returning the number of failed tests. */
|
||||
int Ut_Suite::run() {
|
||||
print_suite_epilog();
|
||||
int num_tests_failed = 0;
|
||||
for (int i=0; i<test_list_size_; i++) {
|
||||
if (!test_list_[i]->run(name_)) {
|
||||
num_tests_failed++;
|
||||
}
|
||||
}
|
||||
return num_tests_failed;
|
||||
}
|
||||
|
||||
/** Static method to log all tests that are marked failed. */
|
||||
void Ut_Suite::print_failed() {
|
||||
for (int i=0; i<test_list_size_; i++) {
|
||||
test_list_[i]->print_failed(name_);
|
||||
}
|
||||
}
|
||||
|
||||
/** Static method to log the start of a test run. */
|
||||
void Ut_Suite::print_prolog() {
|
||||
int i;
|
||||
num_tests_ = 0;
|
||||
num_passed_ = 0;
|
||||
num_failed_ = 0;
|
||||
for (i=0; i<suite_list_size_; i++) {
|
||||
num_tests_ += suite_list_[i]->size();
|
||||
}
|
||||
Ut_Suite::printf("%s[==========]%s Running %d tests from %d test case%s.\n",
|
||||
Ut_Suite::green, Ut_Suite::normal,
|
||||
num_tests_, suite_list_size_,
|
||||
suite_list_size_ == 1 ? "" : "s");
|
||||
}
|
||||
|
||||
/** Static method to log the end of a test run. */
|
||||
void Ut_Suite::print_epilog() {
|
||||
int i;
|
||||
Ut_Suite::printf("%s[==========]%s %d tests from %d test case%s ran.\n",
|
||||
Ut_Suite::green, Ut_Suite::normal,
|
||||
num_tests_, suite_list_size_,
|
||||
suite_list_size_ == 1 ? "" : "s");
|
||||
if (num_passed_) {
|
||||
Ut_Suite::printf("%s[ PASSED ]%s %d test%s.\n",
|
||||
Ut_Suite::green, Ut_Suite::normal, num_passed_,
|
||||
num_passed_ == 1 ? "" : "s");
|
||||
}
|
||||
if (num_failed_) {
|
||||
Ut_Suite::printf("%s[ FAILED ]%s %d test%s, listed below:\n",
|
||||
Ut_Suite::red, Ut_Suite::normal, num_failed_,
|
||||
num_failed_ == 1 ? "" : "s");
|
||||
}
|
||||
for (i=0; i<suite_list_size_; i++) {
|
||||
suite_list_[i]->print_failed();
|
||||
}
|
||||
}
|
||||
|
||||
/** Static method to run all tests in all test suites.
|
||||
Returns the number of failed tests.
|
||||
*/
|
||||
int Ut_Suite::run_all_tests() {
|
||||
print_prolog();
|
||||
// loop through all suites which then loop through all tests
|
||||
for (int i=0; i<suite_list_size_; i++) {
|
||||
int n = suite_list_[i]->run();
|
||||
num_passed_ += suite_list_[i]->size() - n;
|
||||
num_failed_ += n;
|
||||
}
|
||||
print_epilog();
|
||||
return num_failed_;
|
||||
}
|
||||
|
||||
/** Static method to run all test, one-by-one, until done.
|
||||
Run all tests by calling `while (Ut_Suite::run_next_test()) { }`.
|
||||
This is used to visualise test progress with the terminal window by runnig test
|
||||
asynchronously and adding a noticable delay between calls.
|
||||
*/
|
||||
bool Ut_Suite::run_next_test() {
|
||||
// if all suites are done, print the ending text and return
|
||||
Ut_Suite *last = suite_list_[suite_list_size_-1];
|
||||
if (last->done_) {
|
||||
print_epilog();
|
||||
return false;
|
||||
}
|
||||
// if no tests ran yet, print the starting text
|
||||
Ut_Suite *first = suite_list_[0];
|
||||
if (!first->done_ && !first->test_list_[0]->done_) {
|
||||
print_prolog();
|
||||
}
|
||||
// now find the next test that hasn't ran yet
|
||||
for (int i=0; i<suite_list_size_; i++) {
|
||||
Ut_Suite *st = suite_list_[i];
|
||||
if (st->done_) continue;
|
||||
if (!st->test_list_[0]->done_)
|
||||
st->print_suite_epilog();
|
||||
for (int j=0; j<st->test_list_size_; j++) {
|
||||
if (st->test_list_[j]->done_) continue;
|
||||
if (st->test_list_[j]->run(st->name_)) num_passed_++; else num_failed_++;
|
||||
return true;
|
||||
}
|
||||
st->done_ = true;
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/** A printf that is redirected to the terminal or stdout. */
|
||||
void Ut_Suite::printf(const char *format, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
if (tty) {
|
||||
tty->vprintf(format, args);
|
||||
} else {
|
||||
vprintf(format, args);
|
||||
}
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
/** Log the result of a boolean case fail. */
|
||||
void Ut_Suite::log_bool(const char *file, int line, const char *cond, bool result, bool expected) {
|
||||
Ut_Suite::printf("%s(%d): error:\n", file, line);
|
||||
Ut_Suite::printf("Value of: %s\n", cond);
|
||||
Ut_Suite::printf("Actual: %s\n", result ? "true" : "false");
|
||||
Ut_Suite::printf("Expected: %s\n", expected ? "true" : "false");
|
||||
}
|
||||
|
||||
/** Log the result of a string comparison case fail. */
|
||||
void Ut_Suite::log_string(const char *file, int line, const char *cond, const char *result, const char *expected) {
|
||||
Ut_Suite::printf("%s(%d): error:\n", file, line);
|
||||
Ut_Suite::printf("Value of: %s\n", cond);
|
||||
Ut_Suite::printf(" Actual: %s\n", result);
|
||||
Ut_Suite::printf("Expected: %s\n", expected);
|
||||
}
|
||||
|
||||
/** Log the result of an integer comparison case fail. */
|
||||
void Ut_Suite::log_int(const char *file, int line, const char *cond, int result, const char *expected) {
|
||||
Ut_Suite::printf("%s(%d): error:\n", file, line);
|
||||
Ut_Suite::printf("Value of: %s\n", cond);
|
||||
Ut_Suite::printf(" Actual: %d\n", result);
|
||||
Ut_Suite::printf("Expected: %s\n", expected);
|
||||
}
|
||||
|
||||
// ----- main ---------------------------------------------------------- MARK: -
|
||||
|
||||
// callback whenever the browser value changes
|
||||
void UT_BROWSER_CB(Fl_Widget*, void*) {
|
||||
void ui_browser_cb(Fl_Widget*, void*) {
|
||||
for ( int t=1; t<=browser->size(); t++ ) {
|
||||
UnitTest* ti = (UnitTest*)browser->data(t);
|
||||
if ( browser->selected(t) ) {
|
||||
|
@ -132,11 +393,57 @@ void UT_BROWSER_CB(Fl_Widget*, void*) {
|
|||
}
|
||||
}
|
||||
|
||||
static bool run_core_tests_only = false;
|
||||
|
||||
static int arg(int argc, char** argv, int& i) {
|
||||
if ( strcmp(argv[i], "--core") == 0 ) {
|
||||
run_core_tests_only = true;
|
||||
i++;
|
||||
return 1;
|
||||
}
|
||||
if ( strcmp(argv[i], "--color=0") == 0 ) {
|
||||
Ut_Suite::color(0);
|
||||
i++;
|
||||
return 1;
|
||||
}
|
||||
if ( strcmp(argv[i], "--color=1") == 0 ) {
|
||||
Ut_Suite::color(1);
|
||||
i++;
|
||||
return 1;
|
||||
}
|
||||
if ( (strcmp(argv[i], "--help") == 0) || (strcmp(argv[i], "-h") == 0) ) {
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// This is the main call. It creates the window and adds all previously
|
||||
// registered tests to the browser widget.
|
||||
int main(int argc, char** argv) {
|
||||
Fl::args(argc, argv);
|
||||
int i;
|
||||
if ( Fl::args(argc,argv,i,arg) == 0 ) { // unsupported argument found
|
||||
static const char *msg =
|
||||
"usage: %s <switches>\n"
|
||||
" --core : test core functionality only\n"
|
||||
" --color=1, --color=0 : print test output in color or plain text"
|
||||
" --help, -h : print this help page\n";
|
||||
const char *app_name = NULL;
|
||||
if ( (argc > 0) && argv[0] && argv[0][0] )
|
||||
app_name = fl_filename_name(argv[0]);
|
||||
if ( !app_name || !app_name[0])
|
||||
app_name = "unittests";
|
||||
#ifdef _MSC_VER
|
||||
fl_message(msg, app_name);
|
||||
#else
|
||||
fprintf(stderr, msg, app_name);
|
||||
#endif
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (run_core_tests_only) {
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
|
||||
Fl::get_system_colors();
|
||||
Fl::scheme(Fl::scheme()); // init scheme before instantiating tests
|
||||
Fl::visual(FL_RGB);
|
||||
|
@ -146,9 +453,9 @@ int main(int argc, char** argv) {
|
|||
browser = new Fl_Hold_Browser(UT_BROWSER_X, UT_BROWSER_Y, UT_BROWSER_W, UT_BROWSER_H, "Unit Tests");
|
||||
browser->align(FL_ALIGN_TOP|FL_ALIGN_LEFT);
|
||||
browser->when(FL_WHEN_CHANGED);
|
||||
browser->callback(UT_BROWSER_CB);
|
||||
browser->callback(ui_browser_cb);
|
||||
|
||||
int i, n = UnitTest::num_tests();
|
||||
int n = UnitTest::num_tests();
|
||||
for (i=0; i<n; i++) {
|
||||
UnitTest* t = UnitTest::test(i);
|
||||
if (t) {
|
||||
|
@ -163,6 +470,6 @@ int main(int argc, char** argv) {
|
|||
mainwin->show(argc, argv);
|
||||
// Select first test in browser, and show that test.
|
||||
browser->select(UT_TEST_ABOUT+1);
|
||||
UT_BROWSER_CB(browser, 0);
|
||||
ui_browser_cb(browser, 0);
|
||||
return Fl::run();
|
||||
}
|
||||
|
|
174
test/unittests.h
174
test/unittests.h
|
@ -20,6 +20,10 @@
|
|||
#include <FL/Fl.H>
|
||||
#include <FL/Fl_Double_Window.H>
|
||||
|
||||
#include <stdarg.h>
|
||||
|
||||
class Fl_Simple_Terminal;
|
||||
|
||||
// WINDOW/WIDGET SIZES
|
||||
const int UT_MAINWIN_W = 700; // main window w()
|
||||
const int UT_MAINWIN_H = 400; // main window h()
|
||||
|
@ -50,7 +54,8 @@ enum {
|
|||
UT_TEST_VIEWPORT,
|
||||
UT_TEST_SCROLLBARSIZE,
|
||||
UT_TEST_SCHEMES,
|
||||
UT_TEST_SIMPLE_TERMINAL
|
||||
UT_TEST_SIMPLE_TERMINAL,
|
||||
UT_TEST_CORE,
|
||||
};
|
||||
|
||||
// This class helps to automatically register a new test with the unittest app.
|
||||
|
@ -74,6 +79,173 @@ private:
|
|||
static UnitTest* test_list_[];
|
||||
};
|
||||
|
||||
// The following classes and macros implement a subset of the Google Test API
|
||||
// without creating any external dependencies.
|
||||
//
|
||||
// There is nothing to initialise or set up. Just by including these classes,
|
||||
// we can create tests anywhere inside the app by simply writing:
|
||||
//
|
||||
// TEST(Math, Addition) {
|
||||
// EXPECT_EQ(3+3, 6);
|
||||
// return true;
|
||||
// }
|
||||
// TEST(Math, Multiplication) {
|
||||
// EXPECT_EQ(3*3, 9);
|
||||
// return true;
|
||||
// }
|
||||
// RUN_ALL_TESTS();
|
||||
//
|
||||
// The test suite must only be run once.
|
||||
|
||||
typedef bool (*Ut_Test_Call)();
|
||||
|
||||
/**
|
||||
Implement a single test which can in turn contain many EXPECT_* macros.
|
||||
Ut_Test classes are automatically created using the TEST(suite_name, test_name)
|
||||
macro. Tests with identical suite names are grouped into a single suite.
|
||||
*/
|
||||
class Ut_Test {
|
||||
friend class Ut_Suite;
|
||||
const char *name_;
|
||||
Ut_Test_Call call_;
|
||||
bool failed_;
|
||||
bool done_;
|
||||
public:
|
||||
Ut_Test(const char *suitename, const char *testname, Ut_Test_Call call);
|
||||
bool run(const char *suite);
|
||||
void print_failed(const char *suite);
|
||||
};
|
||||
|
||||
/**
|
||||
Implement test registry and the grouping of tests into a suite. This class
|
||||
holds a number of static elements that register an arbitrary number of tests
|
||||
and groups them into suites via the TEST() macro.
|
||||
*/
|
||||
class Ut_Suite {
|
||||
static Ut_Suite **suite_list_;
|
||||
static int suite_list_size_;
|
||||
static int num_tests_;
|
||||
static int num_passed_;
|
||||
static int num_failed_;
|
||||
|
||||
Ut_Test **test_list_;
|
||||
int test_list_size_;
|
||||
const char *name_;
|
||||
bool done_;
|
||||
Ut_Suite(const char *name);
|
||||
public:
|
||||
void add(Ut_Test *test);
|
||||
int size() { return test_list_size_; }
|
||||
int run();
|
||||
void print_suite_epilog();
|
||||
void print_failed();
|
||||
static Ut_Suite *locate(const char *name);
|
||||
static int run_all_tests();
|
||||
static bool run_next_test();
|
||||
static void printf(const char *format, ...);
|
||||
static void log_bool(const char *file, int line, const char *cond, bool result, bool expected);
|
||||
static void log_string(const char *file, int line, const char *cond, const char *result, const char *expected);
|
||||
static void log_int(const char *file, int line, const char *cond, int result, const char *expected);
|
||||
static void print_prolog();
|
||||
static void print_epilog();
|
||||
static void color(int);
|
||||
static int failed() { return num_failed_; }
|
||||
static const char *red;
|
||||
static const char *green;
|
||||
static const char *normal;
|
||||
static Fl_Simple_Terminal *tty;
|
||||
};
|
||||
|
||||
#define UT_CONCAT_(prefix, suffix) prefix##suffix
|
||||
#define UT_CONCAT(prefix, suffix) UT_CONCAT_(prefix, suffix)
|
||||
|
||||
/** Create a test function and register it with the test suites.
|
||||
\param[in] SUITE naming of the test suite for grouping
|
||||
\param[in] CASE name this test
|
||||
*/
|
||||
#define TEST(SUITE, CASE) \
|
||||
static bool UT_CONCAT(test_call_, __LINE__)(); \
|
||||
Ut_Test UT_CONCAT(test__, __LINE__)(#SUITE, #CASE, UT_CONCAT(test_call_, __LINE__)); \
|
||||
static bool UT_CONCAT(test_call_, __LINE__)()
|
||||
|
||||
/** Create a test case where the result is expected to be a boolena with the value true */
|
||||
#define EXPECT_TRUE(COND) \
|
||||
bool UT_CONCAT(cond, __LINE__) = COND; \
|
||||
if (UT_CONCAT(cond, __LINE__) != true) { \
|
||||
Ut_Suite::log_bool(__FILE__, __LINE__, #COND, UT_CONCAT(cond, __LINE__), true); \
|
||||
return false; \
|
||||
}
|
||||
|
||||
/** Create a test case for string comparison. NULL is ok for both arguments. */
|
||||
#define EXPECT_STREQ(A, B) \
|
||||
const char *UT_CONCAT(a, __LINE__) = A; \
|
||||
const char *UT_CONCAT(b, __LINE__) = B; \
|
||||
if ( (UT_CONCAT(a, __LINE__)==NULL && UT_CONCAT(b, __LINE__)!=NULL) \
|
||||
|| (UT_CONCAT(a, __LINE__)!=NULL && UT_CONCAT(b, __LINE__)==NULL) \
|
||||
|| (UT_CONCAT(b, __LINE__)!=NULL && strcmp(UT_CONCAT(a, __LINE__), UT_CONCAT(b, __LINE__))!=0) ) { \
|
||||
Ut_Suite::log_string(__FILE__, __LINE__, #A, UT_CONCAT(a, __LINE__), #B); \
|
||||
return false; \
|
||||
}
|
||||
|
||||
/** Create a test case for integer comparison. */
|
||||
#define EXPECT_EQ(A, B) \
|
||||
int UT_CONCAT(a, __LINE__) = A; \
|
||||
int UT_CONCAT(b, __LINE__) = B; \
|
||||
if (UT_CONCAT(a, __LINE__) != UT_CONCAT(b, __LINE__)) { \
|
||||
Ut_Suite::log_int(__FILE__, __LINE__, #A, UT_CONCAT(a, __LINE__), #B); \
|
||||
return false; \
|
||||
}
|
||||
|
||||
/** Create a test case for integer comparison. */
|
||||
#define EXPECT_NE(A, B) \
|
||||
int UT_CONCAT(a, __LINE__) = A; \
|
||||
int UT_CONCAT(b, __LINE__) = B; \
|
||||
if (UT_CONCAT(a, __LINE__) == UT_CONCAT(b, __LINE__)) { \
|
||||
Ut_Suite::log_int(__FILE__, __LINE__, #A, UT_CONCAT(a, __LINE__), #B); \
|
||||
return false; \
|
||||
}
|
||||
|
||||
/** Create a test case for integer comparison. */
|
||||
#define EXPECT_LT(A, B) \
|
||||
int UT_CONCAT(a, __LINE__) = A; \
|
||||
int UT_CONCAT(b, __LINE__) = B; \
|
||||
if (UT_CONCAT(a, __LINE__) >= UT_CONCAT(b, __LINE__)) { \
|
||||
Ut_Suite::log_int(__FILE__, __LINE__, #A, UT_CONCAT(a, __LINE__), #B); \
|
||||
return false; \
|
||||
}
|
||||
|
||||
/** Create a test case for integer comparison. */
|
||||
#define EXPECT_LE(A, B) \
|
||||
int UT_CONCAT(a, __LINE__) = A; \
|
||||
int UT_CONCAT(b, __LINE__) = B; \
|
||||
if (UT_CONCAT(a, __LINE__) > UT_CONCAT(b, __LINE__)) { \
|
||||
Ut_Suite::log_int(__FILE__, __LINE__, #A, UT_CONCAT(a, __LINE__), #B); \
|
||||
return false; \
|
||||
}
|
||||
|
||||
/** Create a test case for integer comparison. */
|
||||
#define EXPECT_GT(A, B) \
|
||||
int UT_CONCAT(a, __LINE__) = A; \
|
||||
int UT_CONCAT(b, __LINE__) = B; \
|
||||
if (UT_CONCAT(a, __LINE__) <= UT_CONCAT(b, __LINE__)) { \
|
||||
Ut_Suite::log_int(__FILE__, __LINE__, #A, UT_CONCAT(a, __LINE__), #B); \
|
||||
return false; \
|
||||
}
|
||||
|
||||
/** Create a test case for integer comparison. */
|
||||
#define EXPECT_GE(A, B) \
|
||||
int UT_CONCAT(a, __LINE__) = A; \
|
||||
int UT_CONCAT(b, __LINE__) = B; \
|
||||
if (UT_CONCAT(a, __LINE__) < UT_CONCAT(b, __LINE__)) { \
|
||||
Ut_Suite::log_int(__FILE__, __LINE__, #A, UT_CONCAT(a, __LINE__), #B); \
|
||||
return false; \
|
||||
}
|
||||
|
||||
/** Run all registered suits and their tests, and return the number of failed tests. */
|
||||
#define RUN_ALL_TESTS() \
|
||||
Ut_Suite::run_all_tests()
|
||||
|
||||
|
||||
// The main window needs an additional drawing feature in order to support
|
||||
// the viewport alignment test.
|
||||
class Ut_Main_Window : public Fl_Double_Window {
|
||||
|
|
Loading…
Reference in New Issue