From dea6896c8ec0a6b4c48c67c3f232b5ea779c8bd1 Mon Sep 17 00:00:00 2001 From: Joseph Lenox Date: Wed, 27 Jun 2018 17:43:55 -0500 Subject: [PATCH] Implemented UI_Choice (a combobox) with tests. --- src/CMakeLists.txt | 1 + src/GUI/OptionsGroup/Field.hpp | 64 ++++++++++++- src/test/GUI/test_field_choice.cpp | 143 +++++++++++++++++++++++++++++ 3 files changed, 204 insertions(+), 4 deletions(-) create mode 100644 src/test/GUI/test_field_choice.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a110e062e..d6d7418f6 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -179,6 +179,7 @@ set(UI_TEST_SOURCES ${GUI_TESTDIR}/test_field_checkbox.cpp ${GUI_TESTDIR}/test_field_spinctrl.cpp ${GUI_TESTDIR}/test_field_textbox.cpp + ${GUI_TESTDIR}/test_field_choice.cpp ) set(SLIC3R_TEST_SOURCES ${TESTDIR}/test_harness.cpp diff --git a/src/GUI/OptionsGroup/Field.hpp b/src/GUI/OptionsGroup/Field.hpp index 4aa15b0ff..09604f45c 100644 --- a/src/GUI/OptionsGroup/Field.hpp +++ b/src/GUI/OptionsGroup/Field.hpp @@ -11,6 +11,8 @@ #include "wx/spinctrl.h" #include "wx/checkbox.h" #include "wx/textctrl.h" +#include "wx/combobox.h" +#include "wx/arrstr.h" namespace Slic3r { namespace GUI { @@ -90,8 +92,6 @@ public: std::function on_change {nullptr}; protected: - wxCheckBox* _check {nullptr}; - virtual std::string LogChannel() override { return "UI_Checkbox"s; } void _on_change(std::string opt_id) { @@ -99,6 +99,8 @@ protected: this->on_change(opt_id, this->get_bool()); } } +private: + wxCheckBox* _check {nullptr}; }; @@ -139,7 +141,6 @@ protected: private: wxSpinCtrl* _spin {nullptr}; - int _tmp {0}; }; class UI_TextCtrl : public UI_Window { @@ -188,7 +189,62 @@ protected: private: wxTextCtrl* _text {nullptr}; - int _tmp {0}; +}; + +class UI_Choice : public UI_Window { +public: + UI_Choice(wxWindow* parent, Slic3r::ConfigOptionDef _opt, wxWindowID id = wxID_ANY) : 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()}; + 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; + + _choice->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent& e) { this->_on_change(""); e.Skip(); }); + _choice->Bind(wxEVT_TEXT_ENTER, [this](wxCommandEvent& e) { this->_on_change(""); e.Skip(); }); + _choice->Bind(wxEVT_KILL_FOCUS, [this](wxFocusEvent& e) { if (this->on_kill_focus != nullptr) {this->on_kill_focus(""); this->_on_change("");} e.Skip(); }); + } + std::string get_string() override { + if (opt.enum_values.size() > 0) { + auto idx = this->_choice->GetSelection(); + if (idx != wxNOT_FOUND) return this->opt.enum_values.at(idx); + } + return this->_choice->GetValue().ToStdString(); + } + + + /// Returns a bare pointer to the underlying combobox, usually for test interface + wxComboBox* choice() { return this->_choice; } + + void set_value(boost::any value) override { + auto result {std::find(opt.enum_values.cbegin(), opt.enum_values.cend(), boost::any_cast(value))}; + if (result == opt.enum_values.cend()) { + this->_choice->SetValue(wxString(boost::any_cast(value))); + } else { + auto idx = std::distance(opt.enum_values.cbegin(), result); + this->_choice->SetSelection(idx); + } + } + + /// Function to call when the contents of this change. + std::function on_change {nullptr}; +protected: + virtual std::string LogChannel() override { return "UI_Choice"s; } + + 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}; }; } } // Namespace Slic3r::GUI diff --git a/src/test/GUI/test_field_choice.cpp b/src/test/GUI/test_field_choice.cpp new file mode 100644 index 000000000..753292508 --- /dev/null +++ b/src/test/GUI/test_field_choice.cpp @@ -0,0 +1,143 @@ +#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_Choice: 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 Choice with 3 options from ConfigOptionDef and a default_value that is not in the enumeration.") { + auto simple_option {ConfigOptionDef()}; + auto* default_string {new ConfigOptionString("A")}; + + simple_option.default_value = default_string; // owned by ConfigOptionDef + simple_option.enum_values.push_back("B"); + simple_option.enum_values.push_back("C"); + simple_option.enum_values.push_back("D"); + + auto test_field {Slic3r::GUI::UI_Choice(wxTheApp->GetTopWindow(), simple_option)}; + WHEN( "I don't explicitly select any option in the drop-down") { + THEN( "get_value() returns the the value associuted 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 associuted 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 associuted 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 associuted with the third option in the related ConfigOptionDef") { + REQUIRE(simple_option.enum_values[2] == test_field.get_string()); + } + } + } + GIVEN( "I have a UI Choice with 3 options from ConfigOptionDef and a default_value that is in the enumeration.") { + auto simple_option {ConfigOptionDef()}; + auto* default_string {new ConfigOptionString("B"s)}; + + simple_option.default_value = default_string; // owned by ConfigOptionDef + simple_option.enum_values.push_back("B"s); + simple_option.enum_values.push_back("C"s); + simple_option.enum_values.push_back("D"s); + + auto test_field {Slic3r::GUI::UI_Choice(wxTheApp->GetTopWindow(), simple_option)}; + 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("C"s); + THEN( "get_string() returns the matching item in ConfigOptionDef") { + REQUIRE(test_field.get_string() == "C"s); + REQUIRE(test_field.choice()->FindString(simple_option.enum_values[1]) == 1); + } + } + WHEN( "I set the string value to another item that is not in the enumeration") { + test_field.choice()->SetValue("F"s); + THEN( "get_string() returns the matching item in ConfigOptionDef") { + REQUIRE(test_field.get_string() == "F"s); + REQUIRE(test_field.choice()->GetSelection() == wxNOT_FOUND); + } + } + } +} + + +SCENARIO( "UI_Choice: 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 Choice 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("B")}; + + simple_option.default_value = default_string; // owned by ConfigOptionDef + simple_option.enum_values.push_back("B"); + simple_option.enum_values.push_back("C"); + + auto test_field {Slic3r::GUI::UI_Choice(wxTheApp->GetTopWindow(), simple_option)}; + + 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); + } + } + } +}