diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f7a02d7e9..4bc65a525 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -294,7 +294,7 @@ IF(wxWidgets_FOUND) endif() add_executable(gui_test ${UI_TEST_SOURCES}) add_test(NAME TestGUI COMMAND gui_test) - target_link_libraries(gui_test PUBLIC libslic3r slic3r_gui Catch ${wxWidgets_LIBRARIES}) + target_link_libraries(gui_test PUBLIC libslic3r slic3r_gui Catch ${wxWidgets_LIBRARIES} ${LIBSLIC3R_DEPENDS}) endif() ELSE(wxWidgets_FOUND) # For convenience. When we cannot continue, inform the user diff --git a/src/GUI/OptionsGroup/Field.hpp b/src/GUI/OptionsGroup/Field.hpp index 203d3172e..6397a02a5 100644 --- a/src/GUI/OptionsGroup/Field.hpp +++ b/src/GUI/OptionsGroup/Field.hpp @@ -2,16 +2,24 @@ #define SLIC3R_FIELD_HPP #include +#include +#include "ConfigBase.hpp" namespace Slic3r { namespace GUI { class UI_Checkbox { public: - UI_Checkbox(wxWindow* parent, wxWindowID checkid = wxID_ANY) : parent(parent) { + UI_Checkbox(wxWindow* parent, Slic3r::ConfigOptionDef _opt, wxWindowID checkid = wxID_ANY) : parent(parent), opt(_opt) { ui_window = new wxCheckBox(parent, checkid, ""); _check = dynamic_cast(ui_window); + + // Set some defaults. + if (this->opt.readonly) { this->_check->Disable(); } + if (this->opt.default_value != nullptr) { this->_check->SetValue(this->opt.default_value->getBool()); } + + // Set up event handlers _check->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent& e) { this->_on_change(""); e.Skip(); }); - _check->Bind(wxEVT_KILL_FOCUS, [this](wxFocusEvent& e) { this->on_kill_focus(""); e.Skip(); }); + _check->Bind(wxEVT_KILL_FOCUS, [this](wxFocusEvent& e) { if (this->on_kill_focus != nullptr) {this->on_kill_focus("");} e.Skip(); }); } ~UI_Checkbox() { wxDELETE(_check); ui_window = _check = nullptr; } /// Function to call when the contents of this change. @@ -21,18 +29,29 @@ public: /// Don't trigger on_change when this is true. bool disable_change_event {false}; - void enable() { this->ui_window->Enable(); } + /// Enables the underlying UI widget. + void enable() { this->_check->Enable(); } + + /// Disables the underlying UI widget. void disable() { this->ui_window->Disable(); } + /// Set the underlying widget to either enabled or disabled. void toggle(bool enable = true) { enable ? this->enable() : this->disable(); } wxCheckBox* check() { return _check; } + /// Returns the value of the enclosed checkbox. + /// Implements get_bool bool get_bool() { return _check->GetValue();} + + /// Casts the containing value to boolean and sets the built-in checkbox appropriately. + /// implements set_value + void set_value(boost::any value) { this->_check->SetValue(boost::any_cast(value)); } private: wxWindow* parent {nullptr}; wxWindow* ui_window {nullptr}; wxCheckBox* _check {nullptr}; + const Slic3r::ConfigOptionDef opt; //< Reference to the UI-specific bits of this option void _on_change(std::string opt_id) { if (!this->disable_change_event && this->on_change != nullptr) { diff --git a/src/test/GUI/test_field_checkbox.cpp b/src/test/GUI/test_field_checkbox.cpp index 111855a88..42cea2eb7 100644 --- a/src/test/GUI/test_field_checkbox.cpp +++ b/src/test/GUI/test_field_checkbox.cpp @@ -8,7 +8,10 @@ #endif // WX_PRECOMP #include #include "testableframe.h" + #include "OptionsGroup/Field.hpp" +#include "ConfigBase.hpp" + using namespace Slic3r::GUI; SCENARIO( "GUI Checkbox option items fire their on_kill_focus when focus leaves the checkbox." ) { @@ -19,7 +22,7 @@ SCENARIO( "GUI Checkbox option items fire their on_kill_focus when focus leaves wxMilliSleep(500); GIVEN( "A checkbox field item exists on a window") { auto exec_counter {0}; - auto* test_field {new UI_Checkbox(wxTheApp->GetTopWindow(), wxID_ANY)}; + auto* test_field {new Slic3r::GUI::UI_Checkbox(wxTheApp->GetTopWindow(), Slic3r::ConfigOptionDef())}; auto killfunc {[&exec_counter](const std::string& opt_id) { exec_counter += 1; }}; @@ -42,21 +45,80 @@ SCENARIO( "GUI Checkbox option items fire their on_kill_focus when focus leaves REQUIRE(exec_counter == 1); } } + + WHEN ( "focus leaves the checkbox and no callback is assigned") { + test_field->on_kill_focus = nullptr; + exec_counter = 0; + + test_field->check()->SetFocus(); + wxMilliSleep(500); + + auto ev {wxFocusEvent(wxEVT_KILL_FOCUS, test_field->check()->GetId())}; + ev.SetEventObject(test_field->check()); + test_field->check()->ProcessWindowEvent(ev); + wxYield(); + wxMilliSleep(500); + THEN( "on_focus_kill doesn't try to execute nullptr") { + REQUIRE(1 == 1); + REQUIRE(exec_counter == 0); + } + } delete test_field; } } +SCENARIO( "GUI Checkbox set_value and get_bool work as expected." ) { + wxTestableFrame* old = dynamic_cast(wxTheApp->GetTopWindow()); + old->Destroy(); + wxTheApp->SetTopWindow(new wxTestableFrame()); + wxMilliSleep(500); + + auto* test_field {new Slic3r::GUI::UI_Checkbox(wxTheApp->GetTopWindow(), Slic3r::ConfigOptionDef())}; + + GIVEN( "A checkbox field item exists on a window") { + WHEN ( "set_value is an bool and true") { + test_field->set_value(true); + THEN( " Result is converted correctly.") { + REQUIRE( test_field->get_bool() == true); + } + } + WHEN ( "set_value is an bool and false") { + test_field->set_value(false); + THEN( " Result is converted correctly.") { + REQUIRE( test_field->get_bool() == false); + } + } + WHEN ( "set_value is a floating point number > 0") { + test_field->set_value(true); + try { + test_field->set_value(10.2); + } catch (boost::bad_any_cast &e) { + THEN( " Nothing happens; exception was thrown (and caught).") { + REQUIRE(true); + } + } + THEN( " Value did not change.") { + REQUIRE( test_field->get_bool() == true); + } + } + } + + delete test_field; + +} SCENARIO( "GUI Checkbox option items fire their on_change event when clicked and appropriate." ) { wxUIActionSimulator sim; wxTestableFrame* old = dynamic_cast(wxTheApp->GetTopWindow()); old->Destroy(); wxTheApp->SetTopWindow(new wxTestableFrame()); + auto* boolopt { new Slic3r::ConfigOptionBool(true) }; + wxMilliSleep(500); GIVEN( "A checkbox field item and disable_change = false") { auto exec_counter {0}; auto changefunc {[&exec_counter](const std::string& opt_id, bool value) { exec_counter += 1; }}; - auto* test_field {new UI_Checkbox(wxTheApp->GetTopWindow(), wxID_ANY)}; + auto* test_field {new Slic3r::GUI::UI_Checkbox(wxTheApp->GetTopWindow(), Slic3r::ConfigOptionDef())}; test_field->disable_change_event = false; test_field->on_change = changefunc; @@ -97,7 +159,7 @@ SCENARIO( "GUI Checkbox option items fire their on_change event when clicked and GIVEN( "A checkbox field item and disable_change = true") { auto exec_counter {0}; auto changefunc {[&exec_counter] (const std::string& opt_id, bool value) { exec_counter++; }}; - auto* test_field {new Slic3r::GUI::UI_Checkbox(wxTheApp->GetTopWindow(), wxID_ANY)}; + auto* test_field {new Slic3r::GUI::UI_Checkbox(wxTheApp->GetTopWindow(), Slic3r::ConfigOptionDef())}; wxTheApp->GetTopWindow()->Show(); wxTheApp->GetTopWindow()->Fit(); test_field->disable_change_event = true; @@ -133,4 +195,133 @@ SCENARIO( "GUI Checkbox option items fire their on_change event when clicked and } delete test_field; } + GIVEN( "A checkbox field item and readonly") { + + struct Slic3r::ConfigOptionDef simple_option; + simple_option.default_value = boolopt; + simple_option.readonly = true; + + auto exec_counter {0}; + auto changefunc {[&exec_counter] (const std::string& opt_id, bool value) { exec_counter++; }}; + auto* test_field {new Slic3r::GUI::UI_Checkbox(wxTheApp->GetTopWindow(), simple_option)}; + wxTheApp->GetTopWindow()->Show(); + wxTheApp->GetTopWindow()->Fit(); + test_field->disable_change_event = false; // don't disable :D + test_field->on_change = changefunc; + + WHEN ( "the box is clicked and readonly") { + exec_counter = 0; + test_field->set_value(false); + sim.MouseMove(test_field->check()->GetScreenPosition() + wxPoint(10, 10)); + wxYield(); + sim.MouseMove(test_field->check()->GetScreenPosition() + wxPoint(10, 10)); + wxYield(); + sim.MouseClick(wxMOUSE_BTN_LEFT); + wxYield(); + sim.MouseClick(wxMOUSE_BTN_LEFT); + wxYield(); + THEN ( "on_change is not executed.") { + REQUIRE(exec_counter == 0); + } + THEN ( "Box is not checked.") { + REQUIRE(test_field->get_bool() == false); + } + } + WHEN ( "the box is clicked and readonly") { + exec_counter = 0; + test_field->set_value(true); + sim.MouseMove(test_field->check()->GetScreenPosition() + wxPoint(10, 10)); + wxYield(); + sim.MouseMove(test_field->check()->GetScreenPosition() + wxPoint(10, 10)); + wxYield(); + sim.MouseClick(wxMOUSE_BTN_LEFT); + wxYield(); + sim.MouseClick(wxMOUSE_BTN_LEFT); + wxYield(); + THEN ( "on_change is not executed.") { + REQUIRE(exec_counter == 0); + } + THEN ( "Box is checked.") { + REQUIRE(test_field->get_bool() == true); + } + } + WHEN ( "the box is clicked and enabled") { + exec_counter = 0; + test_field->enable(); + test_field->set_value(true); + wxMilliSleep(500); + sim.MouseMove(test_field->check()->GetScreenPosition() + wxPoint(10, 10)); + wxYield(); + sim.MouseMove(test_field->check()->GetScreenPosition() + wxPoint(10, 10)); + wxYield(); + sim.MouseClick(wxMOUSE_BTN_LEFT); + wxYield(); + sim.MouseClick(wxMOUSE_BTN_LEFT); + wxYield(); + THEN ( "on_change is executed.") { + REQUIRE(exec_counter == 1); + } + THEN ( "Box is not checked.") { + REQUIRE(test_field->get_bool() == false); + } + } + WHEN ( "the box is clicked and disabled") { + exec_counter = 0; + test_field->set_value(true); + test_field->disable(); + sim.MouseMove(test_field->check()->GetScreenPosition() + wxPoint(10, 10)); + wxYield(); + sim.MouseMove(test_field->check()->GetScreenPosition() + wxPoint(10, 10)); + wxYield(); + sim.MouseClick(wxMOUSE_BTN_LEFT); + wxYield(); + sim.MouseClick(wxMOUSE_BTN_LEFT); + wxYield(); + THEN ( "on_change is not executed.") { + REQUIRE(exec_counter == 0); + } + THEN ( "Box is checked.") { + REQUIRE(test_field->get_bool() == true); + } + } + WHEN ( "the box is clicked and toggled true") { + exec_counter = 0; + test_field->set_value(true); + test_field->toggle(true); + sim.MouseMove(test_field->check()->GetScreenPosition() + wxPoint(10, 10)); + wxYield(); + sim.MouseMove(test_field->check()->GetScreenPosition() + wxPoint(10, 10)); + wxYield(); + sim.MouseClick(wxMOUSE_BTN_LEFT); + wxYield(); + sim.MouseClick(wxMOUSE_BTN_LEFT); + wxYield(); + THEN ( "on_change is executed.") { + REQUIRE(exec_counter == 1); + } + THEN ( "Box is not checked.") { + REQUIRE(test_field->get_bool() == false); + } + } + WHEN ( "the box is clicked and toggled false") { + exec_counter = 0; + test_field->set_value(true); + test_field->toggle(false); + sim.MouseMove(test_field->check()->GetScreenPosition() + wxPoint(10, 10)); + wxYield(); + sim.MouseMove(test_field->check()->GetScreenPosition() + wxPoint(10, 10)); + wxYield(); + sim.MouseClick(wxMOUSE_BTN_LEFT); + wxYield(); + sim.MouseClick(wxMOUSE_BTN_LEFT); + wxYield(); + THEN ( "on_change is not executed.") { + REQUIRE(exec_counter == 0); + } + THEN ( "Box is checked.") { + REQUIRE(test_field->get_bool() == true); + } + } + delete test_field; + } }