Skip to content

Commit

Permalink
Split off some CWM widgets to separate files.
Browse files Browse the repository at this point in the history
  • Loading branch information
mvidner committed May 12, 2017
1 parent 8037f84 commit f6b1b4f
Show file tree
Hide file tree
Showing 4 changed files with 478 additions and 472 deletions.
222 changes: 222 additions & 0 deletions library/cwm/src/lib/cwm/abstract_widget.rb
@@ -0,0 +1,222 @@
module CWM
# Represent base for any widget used in CWM. It can be passed as "widget"
# argument. For more details about usage
# see {Yast::CWMClass#show Yast::CWM.show}
#
# Underneath there is a widget library with a procedural API, using symbol parameters as widget IDs.
#
# The call sequence is:
#
# - {#initialize} is called by the Ruby constructor {.new}
# - CWM#show builds a widget tree, using
# - the AbstractWidget concrete class
# - {#opt} widget options: `[:notify]` is needed if {#handle} is defined
# - {#label}
# - {#help}
#
# - {#init} may initialize the widget state from persistent storage
# - loop:
# - {#handle} may update other widgets if this one tells them to
# - {#validate} may decide whether the user input is valid
# - {#store} may store the widget state to persistent storage
class AbstractWidget
include Yast::UIShortcuts
include Yast::I18n
include Yast::Logger

# By default, {#handle} has no argument and it is called
# only for events of its own widget.
# If true, {#handle}(event) is called for events of any widget.
# @return [Boolean]
def handle_all_events
@handle_all_events.nil? ? false : @handle_all_events
end
attr_writer :handle_all_events

# @return [String] An ID, unique within a dialog, used for the widget.
# By default, the class name is used.
def widget_id
@widget_id || self.class.to_s
end
attr_writer :widget_id

# Declare widget type for {Yast::CWMClass}.
# Your derived widgets will not need to do this.
# @param type [Symbol]
# @return [void]
def self.widget_type=(type)
define_method(:widget_type) { type }
end

# The following methods are only documented but not defined
# because we do not want to accidentally override the subtle defaults
# used by Yast::CWMClass.

# @!method help
# @return [String] translated help text for the widget

# @!method label
# Derived classes must override this method to specify a label.
# @return [String] translated label text for the widget

# @!method opt
# Specifies options passed to widget. When {handle} method is defined, it
# is often expected to be immediatelly notified from widget and for that
# purpose opt method with `[:notify]` as return value is needed.
# @return [Array<Symbol>] options passed to widget
# like `[:hstretch, :vstretch]`

# @!method init
# Initialize the widget: set initial value
# @return [void]

# @!method handle(*args)
# @overload handle
# Process an event generated by this widget. This method is invoked
# if {#handle_all_events} is `false`.
# @note defining {handle} itself does not pass `:notify` to widget. It has
# to be done with {opt} method.
# @return [nil,Symbol] what to return from the dialog,
# or `nil` to continue processing
# @overload handle(event)
# Process an event generated a widget. This method is invoked
# if {#handle_all_events} is `true`.
# @note defining {handle} itself does not pass `:notify` to widget. It has
# to be done with {opt} method.
# @param event [Hash] see CWMClass
# @return [nil,Symbol] what to return from the dialog,
# or `nil` to continue processing

# @!method validate
# Validate widgets before ending the loop and storing.
# @return [Boolean] validate widget value.
# If it fails (`false`) the dialog will not return yet.

# @!method store
# Store the widget value for further processing
# @return [void]

# @!method cleanup
# Clean up after the widget is destroyed
# @return [void]

# Generate widget definition for {Yast::CWMClass}.
# It refers to
# {#help}, {#label}, {#opt}
# {#validate}, {#init}, {#handle}, {#store}, {#cleanup}.
# @return [Hash{String => Object}]
# @raise [RuntimeError] if a required method is not implemented
# or widget_type is not set.
def cwm_definition
if !respond_to?(:widget_type)
raise "Widget '#{self.class}' does set its widget type"
end

res = {}

if respond_to?(:help)
res["help"] = help
else
res["no_help"] = ""
end
res["label"] = label if respond_to?(:label)
res["opt"] = opt if respond_to?(:opt)
if respond_to?(:validate)
res["validate_function"] = validate_method
res["validate_type"] = :function
end
res["handle_events"] = [widget_id] unless handle_all_events
res["init"] = init_method if respond_to?(:init)
res["handle"] = handle_method if respond_to?(:handle)
res["store"] = store_method if respond_to?(:store)
res["cleanup"] = cleanup_method if respond_to?(:cleanup)
res["widget"] = widget_type

res
end

# @return [Boolean] Is widget open for interaction?
def enabled?
Yast::UI.QueryWidget(Id(widget_id), :Enabled)
end

# Opens widget for interaction
# @return [void]
def enable
Yast::UI.ChangeWidget(Id(widget_id), :Enabled, true)
end

# Closes widget for interaction
# @return [void]
def disable
Yast::UI.ChangeWidget(Id(widget_id), :Enabled, false)
end

protected

# A helper to check if an event is invoked by this widget
# @param event [Hash] a UI event
def my_event?(event)
widget_id == event["ID"]
end

# shortcut from Yast namespace to avoid including whole namespace
# @todo kill converts in CWM module, to avoid this workaround for funrefs
# @return [Yast::FunRef]
def fun_ref(*args)
Yast::FunRef.new(*args)
end

private

# @note all methods here use wrappers to modify required parameters as CWM
# have not so nice callbacks API
def init_method
fun_ref(method(:init_wrapper), "void (string)")
end

def init_wrapper(_widget_id)
init
end

def handle_method
fun_ref(method(:handle_wrapper), "symbol (string, map)")
end

# allows both variant of handle. with event map and without.
# with map it make sense when handle_all_events is true or in custom widgets
# with multiple elements, that generate events, otherwise map is not needed
def handle_wrapper(_widget_id, event)
m = method(:handle)
if m.arity == 0
m.call
else
m.call(event)
end
end

def store_method
fun_ref(method(:store_wrapper), "void (string, map)")
end

def store_wrapper(_widget_id, _event)
store
end

def cleanup_method
fun_ref(method(:cleanup_wrapper), "void (string)")
end

def cleanup_wrapper(_widget_id)
cleanup
end

def validate_method
fun_ref(method(:validate_wrapper), "boolean (string, map)")
end

def validate_wrapper(_widget_id, _event)
validate
end
end
end
77 changes: 77 additions & 0 deletions library/cwm/src/lib/cwm/custom_widget.rb
@@ -0,0 +1,77 @@
module CWM
# A custom widget that has its UI content defined in the method {#contents}.
# Useful mainly when a specialized widget including more subwidgets should be
# reusable at more places.
#
# @example custom widget child
# class MyWidget < CWM::CustomWidget
# def initialize
# self.handle_all_events = true
# end
#
# def contents
# HBox(
# PushButton(Id(:reset), _("Reset")),
# PushButton(Id(:undo), _("Undo"))
# )
# end
#
# def handle(event)
# case event["ID"]
# when :reset then ...
# when :undo then ...
# else ...
# end
# nil
# end
# end
class CustomWidget < AbstractWidget
self.widget_type = :custom

# @!method contents
# Must be defined by subclasses
# @return [Yast::Term] a UI term; {AbstractWidget} are not allowed inside
abstract_method :contents

def cwm_definition
res = { "custom_widget" => cwm_contents }

res["handle_events"] = ids_in_contents unless handle_all_events

super.merge(res)
end

# Returns all nested widgets used in contents
def nested_widgets
Yast.import "CWM"

Yast::CWM.widgets_in_contents(contents)
end

protected

# return contents converted to format understandable by CWM module
# Basically it replace instance of AbstractWidget by its widget_id
def cwm_contents
Yast.import "CWM"

Yast::CWM.widgets_contents(contents)
end

def ids_in_contents
find_ids(contents) << widget_id
end

def find_ids(term)
term.each_with_object([]) do |arg, res|
next unless arg.is_a? Yast::Term

if arg.value == :id
res << arg.params[0]
else
res.concat(find_ids(arg))
end
end
end
end
end

0 comments on commit f6b1b4f

Please sign in to comment.