From e8bb3f7e370d5e9e77ddfbce5797d4c379544696 Mon Sep 17 00:00:00 2001 From: Joseph Lenox Date: Thu, 28 Jun 2018 22:23:57 -0500 Subject: [PATCH] Added NumericChoice implementation along with tests. Also added get_double() to interface. --- src/CMakeLists.txt | 2 + src/GUI/OptionsGroup/Field.hpp | 47 ++++ src/GUI/OptionsGroup/UI_NumChoice.cpp | 135 +++++++++++ src/test/GUI/test_field_numchoice.cpp | 312 ++++++++++++++++++++++++++ xs/src/libslic3r/Log.hpp | 9 + 5 files changed, 505 insertions(+) create mode 100644 src/GUI/OptionsGroup/UI_NumChoice.cpp create mode 100644 src/test/GUI/test_field_numchoice.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d6d7418f6..83e68dfc1 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -180,6 +180,7 @@ set(UI_TEST_SOURCES ${GUI_TESTDIR}/test_field_spinctrl.cpp ${GUI_TESTDIR}/test_field_textbox.cpp ${GUI_TESTDIR}/test_field_choice.cpp + ${GUI_TESTDIR}/test_field_numchoice.cpp ) set(SLIC3R_TEST_SOURCES ${TESTDIR}/test_harness.cpp @@ -258,6 +259,7 @@ IF(wxWidgets_FOUND) ${GUI_LIBDIR}/ProgressStatusBar.cpp ${GUI_LIBDIR}/Settings.cpp ${GUI_LIBDIR}/misc_ui.cpp + ${GUI_LIBDIR}/OptionsGroup/UI_NumChoice.cpp ) target_compile_features(slic3r_gui PUBLIC cxx_std_14) #only build GUI lib if building with wx diff --git a/src/GUI/OptionsGroup/Field.hpp b/src/GUI/OptionsGroup/Field.hpp index 09604f45c..ef0fc7938 100644 --- a/src/GUI/OptionsGroup/Field.hpp +++ b/src/GUI/OptionsGroup/Field.hpp @@ -4,6 +4,8 @@ #include #include #include +#include + #include #include "ConfigBase.hpp" #include "Log.hpp" @@ -40,6 +42,7 @@ public: /// Getter functions for UI_Window items. virtual bool get_bool() { Slic3r::Log::warn(this->LogChannel(), "get_bool does not exist"s); return false; } //< return false all the time if this is not implemented. + virtual double get_double() { Slic3r::Log::warn(this->LogChannel(), "get_double does not exist"s); return 0.0; } //< return 0.0 all the time if this is not implemented. virtual int get_int() { Slic3r::Log::warn(this->LogChannel(), "get_int does not exist"s); return 0; } //< return 0 all the time if this is not implemented. virtual std::string get_string() { Slic3r::Log::warn(this->LogChannel(), "get_string does not exist"s); return 0; } //< return 0 all the time if this is not implemented. @@ -247,6 +250,50 @@ private: wxComboBox* _choice {nullptr}; }; + +class UI_NumChoice : public UI_Window { +public: + UI_NumChoice(wxWindow* parent, Slic3r::ConfigOptionDef _opt, wxWindowID id = wxID_ANY); + ~UI_NumChoice() { _choice->Destroy(); } + + /// Returns the underlying value for the selected value (defined by enum_values). If there are labels, this may not + /// match the underlying combobox->GetValue(). + std::string get_string() override; + + /// Returns the item in the value field of the associated option as an integer. + int get_int() override { return std::stoi(this->get_string()); } + + /// Returns the item in the value field of the associated option as a double. + double get_double() override { return std::stod(this->get_string()); } + + + /// Returns a bare pointer to the underlying combobox, usually for test interface + wxComboBox* choice() { return this->_choice; } + + /// For NumChoice, value can be the actual input value or an index into the value. + void set_value(boost::any value) override; + + /// Function to call when the contents of this change. + std::function on_change {nullptr}; +protected: + virtual std::string LogChannel() override { return "UI_NumChoice"s; } + + void _set_value(int value, bool show_value = false); + void _set_value(double value, bool show_value = false); + void _set_value(std::string value); + + void _on_change(std::string opt_id) { + if (!this->disable_change_event && this->window->IsEnabled() && this->on_change != nullptr) { + this->on_change(opt_id, this->get_string()); + } + } +private: + wxComboBox* _choice {nullptr}; + std::regex show_value_flag {"\bshow_value\b"}; +}; + + + } } // Namespace Slic3r::GUI #endif // SLIC3R_FIELD_HPP diff --git a/src/GUI/OptionsGroup/UI_NumChoice.cpp b/src/GUI/OptionsGroup/UI_NumChoice.cpp new file mode 100644 index 000000000..726cdbb57 --- /dev/null +++ b/src/GUI/OptionsGroup/UI_NumChoice.cpp @@ -0,0 +1,135 @@ +#include "OptionsGroup/Field.hpp" +#include "misc_ui.hpp" +#include + +namespace Slic3r { namespace GUI { + +void UI_NumChoice::set_value(boost::any value) { + const bool show_value { std::regex_search(this->opt.gui_flags, show_value_flag) }; + + if (value.type() == typeid(int)) { + this->_set_value(boost::any_cast(value), show_value); return; + } else if (value.type() == typeid(double)) { + this->_set_value(boost::any_cast(value), show_value); return; + } else if (value.type() == typeid(float)) { + this->_set_value(boost::any_cast(value), show_value); return; + } else if (value.type() == typeid(std::string)) { + this->_set_value(boost::any_cast(value)); return; + } else if (value.type() == typeid(wxString)) { + this->_set_value(boost::any_cast(value).ToStdString()); return; + } else { + Slic3r::Log::warn(this->LogChannel(), LOG_WSTRING(wxString("Unsupported type ") + value.type().name() + wxString(" for set_value") )); + } +} + +// Specialized set_value function for an input that is either a direct input or +// an index to the label vector. +void UI_NumChoice::_set_value(int value, bool show_value) { + auto& vlist {this->opt.enum_values}; // alias the vector to type less + auto& llist {this->opt.enum_labels}; + if (show_value) { + Slic3r::Log::info(this->LogChannel(), "Using show_value branch"); + this->_choice->ChangeValue(std::to_string(value)); + } else { + if (vlist.size() > 0) { + Slic3r::Log::info(this->LogChannel(), LOG_WSTRING(wxString("Searching values vector for ") + wxString(std::to_string(value)))); + // search the values vector for our input + auto result {std::find(vlist.cbegin(), vlist.cend(), std::to_string(value))}; + if (result != vlist.cend()) { + auto value_idx {std::distance(vlist.cbegin(), result)}; + Slic3r::Log::info(this->LogChannel(), LOG_WSTRING(wxString("Found. Setting selection to ") + wxString(std::to_string(value_idx)))); + this->_choice->SetSelection(value_idx); + this->disable_change_event = false; + return; + } + } else if (llist.size() > 0 && static_cast(value) < llist.size()) { + Slic3r::Log::info(this->LogChannel(), LOG_WSTRING(wxString("Setting label value to ") + wxString(llist.at(value)))); + /// if there isn't a value list but there is a label list, this is an index to that list. + this->_choice->SetValue(wxString(llist.at(value))); + this->disable_change_event = false; + return; + } + this->_choice->SetValue(wxString(std::to_string(value))); + } + this->disable_change_event = false; +} + +/// In this case, can't be a numeric index. +void UI_NumChoice::_set_value(double value, bool show_value) { + if (show_value) { + this->_choice->ChangeValue(std::to_string(value)); + } +} + +/// In this case, can't be a numeric index. +void UI_NumChoice::_set_value(std::string value) { + this->_choice->ChangeValue(value); +} + +std::string UI_NumChoice::get_string() { + if (opt.enum_values.size() > 0) { + auto idx = this->_choice->GetSelection(); + Slic3r::Log::debug(this->LogChannel(), LOG_WSTRING(this->_choice->GetString(idx) + " <-- label")); + Slic3r::Log::debug(this->LogChannel(), LOG_WSTRING(wxString("Selection for ") + this->_choice->GetValue() + wxString(": ") + wxString(std::to_string(idx)))); + if (idx != wxNOT_FOUND) return this->opt.enum_values.at(idx); + } + Slic3r::Log::debug(this->LogChannel(), "Returning label as value"); + return this->_choice->GetValue().ToStdString(); +} + +UI_NumChoice::UI_NumChoice(wxWindow* parent, Slic3r::ConfigOptionDef _opt, wxWindowID id) : UI_Window(parent, _opt) { + int style {0}; + style |= wxTE_PROCESS_ENTER; + if (opt.gui_type.size() > 0 && opt.gui_type.compare("select_open"s)) style |= wxCB_READONLY; + + /// Load the values + auto values {wxArrayString()}; + if (opt.enum_labels.size() > 0) // if we have labels, use those instead. + for (auto v : opt.enum_labels) values.Add(wxString(v)); + else + for (auto v : opt.enum_values) values.Add(wxString(v)); + + + _choice = new wxComboBox(parent, id, + (opt.default_value != nullptr ? opt.default_value->getString() : ""), + wxDefaultPosition, _default_size(), values, style); + window = _choice; + + this->set_value(opt.default_value != nullptr ? opt.default_value->getString() : ""); + + + // Event handler for data entry and changing the combobox + auto pickup = [this](wxCommandEvent& e) + { + auto disable_change {this->disable_change_event}; + this->disable_change_event = true; + + auto idx {this->_choice->GetSelection()}; + wxString lbl {""}; + + if (this->opt.enum_labels.size() > 0 && (unsigned)idx <= this->opt.enum_labels.size()) { + lbl << this->opt.enum_labels.at(idx); + } else if (this->opt.enum_values.size() > 0 && (unsigned)idx <= this->opt.enum_values.size()) { + lbl << this->opt.enum_values.at(idx); + } else { + lbl << idx; + } + + // avoid leaving field blank on wxMSW + this->_choice->CallAfter([this,lbl]() { + auto dce {this->disable_change_event}; + this->disable_change_event = true; + this->_choice->SetValue(lbl); + this->disable_change_event = dce; + }); + + this->disable_change_event = disable_change; + this->_on_change(""); + }; + _choice->Bind(wxEVT_COMBOBOX, pickup); + _choice->Bind(wxEVT_TEXT_ENTER, [this](wxCommandEvent& e) { this->set_value(this->_choice->GetValue()); this->_on_change(""); } ); + _choice->Bind(wxEVT_KILL_FOCUS, [this](wxFocusEvent& e) { if (this->on_kill_focus != nullptr) {this->on_kill_focus(""); this->_on_change("");} e.Skip(); }); +} + + +} } // Namespace Slic3r::GUI diff --git a/src/test/GUI/test_field_numchoice.cpp b/src/test/GUI/test_field_numchoice.cpp new file mode 100644 index 000000000..a09923104 --- /dev/null +++ b/src/test/GUI/test_field_numchoice.cpp @@ -0,0 +1,312 @@ +#include + +#ifndef WX_PRECOMP +#include "wx/app.h" +#include "wx/uiaction.h" +#endif // WX_PRECOMP + +#include +#include "testableframe.h" +#include "OptionsGroup/Field.hpp" +#include "ConfigBase.hpp" + +using namespace std::string_literals; + +SCENARIO( "UI_NumChoice: default values from options") { + wxTestableFrame* old = dynamic_cast(wxTheApp->GetTopWindow()); + old->Destroy(); + wxTheApp->SetTopWindow(new wxTestableFrame()); + wxUIActionSimulator sim; + wxMilliSleep(500); + + GIVEN( "I have a UI NumChoice with 3 options from ConfigOptionDef that has only values and a default_value that is not in the enumeration.") { + auto simple_option {ConfigOptionDef()}; + auto* default_string {new ConfigOptionString("1")}; + + simple_option.default_value = default_string; // owned by ConfigOptionDef + simple_option.enum_values.push_back("2"); + simple_option.enum_values.push_back("3"); + simple_option.enum_values.push_back("4"); + + auto test_field {Slic3r::GUI::UI_NumChoice(wxTheApp->GetTopWindow(), simple_option)}; + + wxTheApp->GetTopWindow()->Show(); + wxTheApp->GetTopWindow()->Fit(); + + WHEN( "I don't explicitly select any option in the drop-down") { + THEN( "get_value() returns the the value associated with the listed default option in the related ConfigOptionDef") { + REQUIRE(test_field.get_string() == simple_option.default_value->getString()); + } + } + WHEN( "I select the first option in the drop-down") { + test_field.choice()->SetSelection(0); + THEN( "get_value() returns the the value associated with the first option in the related ConfigOptionDef") { + REQUIRE(test_field.get_string() == simple_option.enum_values[0]); + } + } + WHEN( "I select the second option in the drop-down") { + test_field.choice()->SetSelection(1); + THEN( "get_value() returns the the value associated with the second option in the related ConfigOptionDef") { + REQUIRE(test_field.get_string() == simple_option.enum_values[1]); + } + } + WHEN( "I select the third option in the drop-down") { + test_field.choice()->SetSelection(2); + THEN( "get_value() returns the the value associated with the third option in the related ConfigOptionDef") { + REQUIRE(test_field.get_string() == simple_option.enum_values[2]); + } + } + } + GIVEN( "I have a UI NumChoice with 3 options from ConfigOptionDef that has only values and a default_value that is in the enumeration.") { + auto simple_option {ConfigOptionDef()}; + auto* default_string {new ConfigOptionString("2"s)}; + + simple_option.default_value = default_string; // owned by ConfigOptionDef + simple_option.enum_values.push_back("2"); + simple_option.enum_values.push_back("3"); + simple_option.enum_values.push_back("4"); + + auto test_field {Slic3r::GUI::UI_NumChoice(wxTheApp->GetTopWindow(), simple_option)}; + wxTheApp->GetTopWindow()->Show(); + wxTheApp->GetTopWindow()->Fit(); + + WHEN( "I don't explicitly select any option in the drop-down") { + THEN( "get_value() returns the first value in the related ConfigOptionDef") { + REQUIRE(simple_option.enum_values[0] == test_field.get_string()); + REQUIRE(test_field.choice()->FindString(simple_option.enum_values[0]) == 0); + } + } + WHEN( "I set the string value to another item in the enumeration") { + test_field.choice()->SetValue("3"s); + THEN( "get_string() returns the matching item in ConfigOptionDef") { + REQUIRE(test_field.get_string() == "3"s); + } + THEN( "get_int() returns the matching item in ConfigOptionDef as an integer") { + REQUIRE(test_field.get_int() == 3); + } + THEN( "get_double() returns the matching item in ConfigOptionDef as a double") { + REQUIRE(test_field.get_double() == 3.0); + } + THEN( "Combobox string shows up as the first enumeration value.") { + REQUIRE(test_field.choice()->GetValue() == simple_option.enum_values[1]); + } + } + WHEN( "I set the string value to another item that is not in the enumeration") { + test_field.choice()->SetValue("7"s); + THEN( "get_string() returns the matching item in ConfigOptionDef") { + REQUIRE(test_field.get_string() == "7"s); + REQUIRE(test_field.get_int() == 7); + REQUIRE(test_field.get_double() == 7.0); + REQUIRE(test_field.choice()->GetSelection() == wxNOT_FOUND); + } + } + } + + GIVEN( "I have a UI NumChoice with 3 options from ConfigOptionDef that has values that are doubles and labels and a default_value that is not in the enumeration.") { + auto simple_option {ConfigOptionDef()}; + auto* default_int {new ConfigOptionFloat(1.0)}; + + simple_option.default_value = default_int; // owned by ConfigOptionDef + simple_option.enum_values.push_back("2.2"); + simple_option.enum_values.push_back("3.3"); + simple_option.enum_values.push_back("4.4"); + + simple_option.enum_labels.push_back("B"); + simple_option.enum_labels.push_back("C"); + simple_option.enum_labels.push_back("D"); + + auto test_field {Slic3r::GUI::UI_NumChoice(wxTheApp->GetTopWindow(), simple_option)}; + wxTheApp->GetTopWindow()->Show(); + wxTheApp->GetTopWindow()->Fit(); + + WHEN( "I don't explicitly select any option in the drop-down") { + THEN( "get_value() returns the the value associated with the listed default option in the related ConfigOptionDef") { + REQUIRE(simple_option.default_value->getString() == test_field.get_string()); + } + } + WHEN( "I select the first option in the drop-down") { + test_field.choice()->SetSelection(0); + THEN( "get_value() returns the the value associated with the first option in the related ConfigOptionDef") { + REQUIRE(simple_option.enum_values[0] == test_field.get_string()); + } + } + WHEN( "I select the second option in the drop-down") { + test_field.choice()->SetSelection(1); + THEN( "get_value() returns the the value associated with the second option in the related ConfigOptionDef") { + REQUIRE(simple_option.enum_values[1] == test_field.get_string()); + } + } + WHEN( "I select the third option in the drop-down") { + test_field.choice()->SetSelection(2); + THEN( "get_value() returns the the value associated with the third option in the related ConfigOptionDef") { + REQUIRE(simple_option.enum_values[2] == test_field.get_string()); + } + } + } + + GIVEN( "I have a UI NumChoice with 3 options from ConfigOptionDef that has values and labels and a default_value that is not in the enumeration.") { + auto simple_option {ConfigOptionDef()}; + auto* default_int {new ConfigOptionInt(1)}; + + simple_option.default_value = default_int; // owned by ConfigOptionDef + simple_option.enum_values.push_back("2"); + simple_option.enum_values.push_back("3"); + simple_option.enum_values.push_back("4"); + + simple_option.enum_labels.push_back("B"); + simple_option.enum_labels.push_back("C"); + simple_option.enum_labels.push_back("D"); + + auto test_field {Slic3r::GUI::UI_NumChoice(wxTheApp->GetTopWindow(), simple_option)}; + wxTheApp->GetTopWindow()->Show(); + wxTheApp->GetTopWindow()->Fit(); + + WHEN( "I don't explicitly select any option in the drop-down") { + THEN( "get_value() returns the the value associated with the listed default option in the related ConfigOptionDef") { + REQUIRE(simple_option.default_value->getString() == test_field.get_string()); + } + } + WHEN( "I select the first option in the drop-down") { + test_field.choice()->SetSelection(0); + THEN( "get_value() returns the the value associated with the first option in the related ConfigOptionDef") { + REQUIRE(simple_option.enum_values[0] == test_field.get_string()); + } + } + WHEN( "I select the second option in the drop-down") { + test_field.choice()->SetSelection(1); + THEN( "get_value() returns the the value associated with the second option in the related ConfigOptionDef") { + REQUIRE(simple_option.enum_values[1] == test_field.get_string()); + } + } + WHEN( "I select the third option in the drop-down") { + test_field.choice()->SetSelection(2); + THEN( "get_value() returns the the value associated with the third option in the related ConfigOptionDef") { + REQUIRE(simple_option.enum_values[2] == test_field.get_string()); + } + } + } + GIVEN( "I have a UI NumChoice with 3 options from ConfigOptionDef that has values and labels and a default_value that is in the enumeration.") { + auto simple_option {ConfigOptionDef()}; + auto* default_string {new ConfigOptionString("2"s)}; + + simple_option.default_value = default_string; // owned by ConfigOptionDef + simple_option.enum_values.push_back("2"); + simple_option.enum_values.push_back("3"); + simple_option.enum_values.push_back("4"); + + simple_option.enum_labels.push_back("B"); + simple_option.enum_labels.push_back("C"); + simple_option.enum_labels.push_back("D"); + + auto test_field {Slic3r::GUI::UI_NumChoice(wxTheApp->GetTopWindow(), simple_option)}; + + wxTheApp->GetTopWindow()->Show(); + wxTheApp->GetTopWindow()->Fit(); + + WHEN( "I don't explicitly select any option in the drop-down") { + THEN( "get_value functions return the first value in the related ConfigOptionDef") { + REQUIRE(test_field.get_string() == simple_option.enum_values[0]); + } + THEN( "get_int() returns the matching item in ConfigOptionDef as an integer") { + REQUIRE(test_field.get_int() == 2); + } + THEN( "get_double() returns the matching item in ConfigOptionDef as a double") { + REQUIRE(test_field.get_double() == 2.0); + } + THEN( "choice.FindString returns the label matching the item") { + REQUIRE(test_field.choice()->FindString(simple_option.enum_labels[0]) == 0); + } + } + WHEN( "I set the string value to another item in the enumeration") { + test_field.set_value(3); + THEN( "get_string() returns the matching item in ConfigOptionDef") { + REQUIRE(test_field.get_string() == "3"s); + } + THEN( "get_int() returns the matching item in ConfigOptionDef as an integer") { + REQUIRE(test_field.get_int() == 3); + } + THEN( "get_double() returns the matching item in ConfigOptionDef as a double") { + REQUIRE(test_field.get_double() == 3.0); + } + THEN( "choice.GetValue() returns the label.") { + REQUIRE(test_field.choice()->GetValue() == "C"s); + REQUIRE(test_field.choice()->FindString(simple_option.enum_labels[1]) == 1); + } + } + WHEN( "I set the string value to another item that is not in the enumeration") { + test_field.set_value(7); + THEN( "get_string() returns the entered value.") { + REQUIRE(test_field.get_string() == "7"s); + } + THEN( "get_int() returns the entered value as an integer.") { + REQUIRE(test_field.get_int() == 7); + } + THEN( "get_double() returns the entered value as a double.") { + REQUIRE(test_field.get_double() == 7.0); + } + THEN( "Underlying selection is wxNOT_FOUND") { + REQUIRE(test_field.choice()->GetSelection() == wxNOT_FOUND); + } + } + } +} + + + +SCENARIO( "UI_NumChoice: event handling for on_change and on_kill_focus") { + auto event_count {0}; + auto killfunc {[&event_count](const std::string& opt_id) { event_count += 1; }}; + auto changefunc {[&event_count](const std::string& opt_id, std::string value) { event_count += 1; }}; + GIVEN( "I have a UI NumChoice with 2 options from ConfigOptionDef, no default value, and an on_change handler and on_kill_focus handler.") { + auto simple_option {ConfigOptionDef()}; + auto* default_string {new ConfigOptionString("2")}; + + simple_option.default_value = default_string; // owned by ConfigOptionDef + simple_option.enum_values.push_back("2"); + simple_option.enum_values.push_back("3"); + + auto test_field {Slic3r::GUI::UI_NumChoice(wxTheApp->GetTopWindow(), simple_option)}; + + wxTheApp->GetTopWindow()->Show(); + wxTheApp->GetTopWindow()->Fit(); + + test_field.on_kill_focus = killfunc; + test_field.on_change = changefunc; + + WHEN( "I receive a wxEVT_COMBOBOX event") { + event_count = 0; + wxMilliSleep(250); + + auto ev {wxCommandEvent(wxEVT_COMBOBOX, test_field.choice()->GetId())}; + ev.SetEventObject(test_field.choice()); + test_field.choice()->ProcessWindowEvent(ev); + THEN( "on_change handler is executed.") { + REQUIRE(event_count == 1); + } + } + WHEN( "I receive a wxEVT_TEXT_ENTER event") { + event_count = 0; + wxMilliSleep(250); + + auto ev {wxCommandEvent(wxEVT_TEXT_ENTER, test_field.choice()->GetId())}; + ev.SetEventObject(test_field.choice()); + test_field.choice()->ProcessWindowEvent(ev); + THEN( "on_change handler is executed.") { + REQUIRE(event_count == 1); + } + } + WHEN( "My control loses focus.") { + event_count = 0; + test_field.choice()->SetFocus(); + wxMilliSleep(250); + + auto ev {wxFocusEvent(wxEVT_KILL_FOCUS, test_field.choice()->GetId())}; + ev.SetEventObject(test_field.choice()); + test_field.choice()->ProcessWindowEvent(ev); + + THEN( "on_change handler is executed and on_kill_focus handler is executed.") { + REQUIRE(event_count == 2); + } + } + } +} diff --git a/xs/src/libslic3r/Log.hpp b/xs/src/libslic3r/Log.hpp index 0593aa695..0b3592eb8 100644 --- a/xs/src/libslic3r/Log.hpp +++ b/xs/src/libslic3r/Log.hpp @@ -42,6 +42,15 @@ public: std::cerr << message << std::endl; } + static void debug(std::string topic, std::wstring message) { + std::cerr << topic << " DEBUG" << ": "; + std::wcerr << message << std::endl; + } + static void debug(std::string topic, std::string message) { + std::cerr << topic << " DEBUG" << ": "; + std::cerr << message << std::endl; + } + }; /// Utility debug function to transform a std::vector of anything that