diff --git a/library/cwm/examples/replace_point_example.rb b/library/cwm/examples/replace_point_example.rb new file mode 100644 index 000000000..d7a199281 --- /dev/null +++ b/library/cwm/examples/replace_point_example.rb @@ -0,0 +1,65 @@ +# Simple example to demonstrate object oriented replace_point widget + +require_relative "example_helper" + +require "yast" + +require "cwm/widget" + +Yast.import "UI" +Yast.import "CWM" +Yast.import "Wizard" + +class SwitchWidget < CWM::PushButton + def initialize(replace_point, widgets) + @replace_point = replace_point + @widgets = widgets + end + + def label + "Switch" + end + + def handle + @widgets.rotate! + @replace_point.replace(@widgets.first) + end +end + +class PopupButtonWidget < CWM::PushButton + def label + "Popup" + end + + def handle + Yast::Popup.Message("Click!") + end +end + +class StoreWidget < CWM::InputField + def label + "write here" + end + + def validate + return true unless value.empty? + + Yast::Popup.Error("Empty value!") + false + end + + def store + Yast::Popup.Message(value) + end +end + +widgets = [PopupButtonWidget.new, CWM::Empty.new(:empty), StoreWidget.new] +replace_point = CWM::ReplacePoint.new(widget: widgets.first) + +content = Yast::Term.new(:VBox, + SwitchWidget.new(replace_point, widgets), + replace_point) + +Yast::Wizard.CreateDialog +Yast::CWM.show(content) +Yast::Wizard.CloseDialog diff --git a/library/cwm/src/lib/cwm/widget.rb b/library/cwm/src/lib/cwm/widget.rb index 983ef1807..4e32fe0d0 100644 --- a/library/cwm/src/lib/cwm/widget.rb +++ b/library/cwm/src/lib/cwm/widget.rb @@ -821,4 +821,76 @@ def replace_point ReplacePoint(Id(replace_point_id), VBox(VStretch(), HStretch())) end end + + # Placeholder widget that is used to replace content on demand. + # The most important method is {#replace} which allows switching content + class ReplacePoint < CustomWidget + # @param id [Object] id of widget. Needed to redefine only if more than one + # placeholder needed to be in dialog. Parameter type is limited by component + # system + # @param widget [CWM::AbstractWidget] initial widget in placeholder + def initialize(id: :_placeholder, widget: Empty.new(:_initial_placeholder)) + self.handle_all_events = true + self.widget_id = id + @widget = widget + end + + def contents + ReplacePoint(Id(widget_id), widget_content(@widget)) + end + + def init + @widget.init if @widget.respond_to?(:init) + end + + # Replaces content with different widget. All its events are properly + # handled. + # @param widget [CWM::AbstractWidget] widget to display and process events + def replace(widget) + log.info "replacing with new widget #{widget.inspect}" + Yast::UI.ReplaceWidget(@id, widget_content(widget)) + @widget = widget + init + end + + def help + @widget.respond_to?(:help) ? @widget.help : "" + end + + def handle(event) + return unless @widget.respond_to?(:handle) + + if !@widget.handle_all_events + return if event["ID"] != @widget.widget_id + end + + m = @widget.method(:handle) + if m.arity == 0 + m.call + else + m.call(event) + end + end + + def validate + @widget.respond_to?(:validate) ? @widget.validate : true + end + + def store + @widget.store if @widget.respond_to?(:store) + end + + def cleanup + @widget.cleanup if @widget.respond_to?(:cleanup) + end + + private + + def widget_content(widget) + definition = widget.cwm_definition + definition["_cwm_key"] = widget.widget_id # a bit hacky way to pass widget id + definition = Yast::CWM.prepareWidget(definition) + definition["widget"] + end + end end diff --git a/library/cwm/src/modules/CWM.rb b/library/cwm/src/modules/CWM.rb index 4cf186f76..cd5d715f5 100644 --- a/library/cwm/src/modules/CWM.rb +++ b/library/cwm/src/modules/CWM.rb @@ -787,7 +787,8 @@ def handleDebug # @param [Hash] functions map initialize/save/handle fallbacks if not specified # with the widgets. # @param [Array] skip_store_for list of events for which the value of the widget will not be stored - # Useful mainly for non-standard redraw of widgets, like :reset or :redraw + # Useful mainly for non-standard redraw of widgets, like :reset or :redraw. It will skip also + # validation, because it is not needed as nothing is stored. # @return [Symbol] wizard sequencer symbol def Run(widgets, functions, skip_store_for: []) widgets = deep_copy(widgets) @@ -855,11 +856,12 @@ def Run(widgets, functions, skip_store_for: []) next if ret.nil? - ret = nil if save && !validateWidgets(widgets, event_descr) - - if ret.nil? + # ok, so what happens here? event want to save widgets, so check that there is no explicit + # skip of storing for this event and there is a widget containing invalid value. + # In such case do not save and clear ret, so we are still in loop + if save && !skip_store_for.include?(ret) && !validateWidgets(widgets, event_descr) + ret = nil save = false - next end end saveWidgets(widgets, event_descr) if save && !skip_store_for.include?(ret) @@ -928,7 +930,8 @@ def SetValidationFailedHandler(handler) # @param [String] abort_button label for dialog abort button # @param [Array] skip_store_for list of events for which the value of the widget will not be stored. # Useful mainly when some widget returns an event that should not trigger the storing, - # like a reset button or a redrawing + # like a reset button or a redrawing. It will skip also validation, because it is not needed + # as nothing is stored. # @return [Symbol] wizard sequencer symbol def show(contents, caption: nil, back_button: nil, next_button: nil, abort_button: nil, skip_store_for: []) widgets = widgets_in_contents(contents) diff --git a/library/cwm/test/widgets_test.rb b/library/cwm/test/widgets_test.rb index b193d711a..388fbf9f0 100755 --- a/library/cwm/test/widgets_test.rb +++ b/library/cwm/test/widgets_test.rb @@ -213,3 +213,131 @@ def contents end end end + +describe CWM::ReplacePoint do + + class ReplacePointTestWidget < CWM::InputField + def label + "test" + end + + def init + end + + def handle + end + + def help + "help" + end + + def validate + false + end + + def store + end + + def cleanup + end + end + + describe ".new" do + it "has widget_id as passed" do + subject = described_class.new(id: "test") + expect(subject.widget_id).to eq "test" + end + + it "uses passed widget as initial content" do + widget = ReplacePointTestWidget.new + subject = described_class.new(widget: widget) + expect(widget).to receive(:init) + subject.init + end + end + + describe "#contents" do + it "generates contents including current widget UI definition" do + widget = ReplacePointTestWidget.new + subject = described_class.new(widget: widget) + + expect(subject.contents).to eq( + ReplacePoint( + Id(subject.widget_id), + InputField(Id(widget.widget_id), Opt(:hstretch), "test") + ) + ) + end + end + + describe "#init" do + it "passes init to enclosed widget" do + widget = ReplacePointTestWidget.new + subject = described_class.new(widget: widget) + expect(widget).to receive(:init) + subject.init + end + end + + describe "#replace" do + it "changes enclosed widget" do + subject = described_class.new(widget: CWM::Empty.new(:initial)) + widget = ReplacePointTestWidget.new + expect(widget).to receive(:store) + subject.replace(widget) + subject.store + end + end + + describe "#help" do + it "returns help of enclosed widget" do + widget = ReplacePointTestWidget.new + subject = described_class.new(widget: widget) + expect(subject.help).to eq "help" + end + end + + class ComplexHandleTest < CWM::Empty + def handle(_event) + nil + end + end + + describe "#handle" do + # Cannot test arity based dispatcher, because if we mock expect call of widget.handle, it is + # replaced by rspec method with -1 arity, causing wrong dispatcher functionality + + it "do nothing if passed event is not widget_id and enclosed widget do not handle all events" do + widget = ReplacePointTestWidget.new + subject = described_class.new(widget: widget) + expect(widget).to_not receive(:handle) + subject.handle("ID" => "Not mine") + end + end + + describe "#validate" do + it "passes validate to enclosed widget" do + widget = ReplacePointTestWidget.new + subject = described_class.new(widget: widget) + expect(subject.validate).to eq false + end + end + + describe "#store" do + it "passes store to enclosed widget" do + widget = ReplacePointTestWidget.new + subject = described_class.new(widget: widget) + expect(widget).to receive(:store) + subject.store + end + end + + describe "#cleanup" do + it "passes cleanup to enclosed widget" do + widget = ReplacePointTestWidget.new + subject = described_class.new(widget: widget) + expect(widget).to receive(:cleanup) + subject.cleanup + end + end +end diff --git a/package/yast2.changes b/package/yast2.changes index 28bdf3625..8a650c521 100644 --- a/package/yast2.changes +++ b/package/yast2.changes @@ -1,3 +1,16 @@ +------------------------------------------------------------------- +Thu Jan 19 13:49:10 UTC 2017 - jreidinger@suse.com + +- Added a CWM::ReplacePoint widget +- 3.1.210.4 + +------------------------------------------------------------------- +Wed Jan 18 13:56:27 UTC 2017 - jreidinger@suse.com + +- CWM: when skipping storing of widget values, skip also its + validation (FATE#322328) +- 3.1.210.3 + ------------------------------------------------------------------- Tue Dec 20 16:28:45 UTC 2016 - igonzalezsosa@suse.com diff --git a/package/yast2.spec b/package/yast2.spec index 7150c3b8c..e1bcdb8c1 100644 --- a/package/yast2.spec +++ b/package/yast2.spec @@ -17,7 +17,7 @@ Name: yast2 -Version: 3.1.210.2 +Version: 3.1.210.4 Release: 0 Summary: YaST2 - Main Package License: GPL-2.0