/
abstract_widget.rb
224 lines (190 loc) · 6.83 KB
/
abstract_widget.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
require "abstract_method"
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