rbcurse-extras 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +75 -0
- data/VERSION +1 -0
- data/examples/data/list.txt +300 -0
- data/examples/data/lotr.txt +12 -0
- data/examples/data/table.txt +36 -0
- data/examples/data/tasks.txt +27 -0
- data/examples/data/unix1.txt +21 -0
- data/examples/inc/qdfilechooser.rb +70 -0
- data/examples/inc/rfe_renderer.rb +121 -0
- data/examples/newtabbedwindow.rb +100 -0
- data/examples/rfe.rb +1236 -0
- data/examples/test2.rb +670 -0
- data/examples/testeditlist.rb +78 -0
- data/examples/testtable.rb +270 -0
- data/examples/testvimsplit.rb +141 -0
- data/lib/rbcurse/extras/include/celleditor.rb +112 -0
- data/lib/rbcurse/extras/include/checkboxcellrenderer.rb +57 -0
- data/lib/rbcurse/extras/include/comboboxcellrenderer.rb +30 -0
- data/lib/rbcurse/extras/include/defaultlistselectionmodel.rb +79 -0
- data/lib/rbcurse/extras/include/listkeys.rb +37 -0
- data/lib/rbcurse/extras/include/listselectable.rb +144 -0
- data/lib/rbcurse/extras/include/tableextended.rb +40 -0
- data/lib/rbcurse/extras/widgets/horizlist.rb +203 -0
- data/lib/rbcurse/extras/widgets/menutree.rb +63 -0
- data/lib/rbcurse/extras/widgets/multilinelabel.rb +142 -0
- data/lib/rbcurse/extras/widgets/rcomboedit.rb +256 -0
- data/lib/rbcurse/extras/widgets/rlink.rb.moved +27 -0
- data/lib/rbcurse/extras/widgets/rlistbox.rb +1247 -0
- data/lib/rbcurse/extras/widgets/rmenulink.rb.moved +21 -0
- data/lib/rbcurse/extras/widgets/rmulticontainer.rb +304 -0
- data/lib/rbcurse/extras/widgets/rmultisplit.rb +722 -0
- data/lib/rbcurse/extras/widgets/rmultitextview.rb +306 -0
- data/lib/rbcurse/extras/widgets/rpopupmenu.rb +755 -0
- data/lib/rbcurse/extras/widgets/rtable.rb +1758 -0
- data/lib/rbcurse/extras/widgets/rvimsplit.rb +800 -0
- data/lib/rbcurse/extras/widgets/table/tablecellrenderer.rb +86 -0
- data/lib/rbcurse/extras/widgets/table/tabledatecellrenderer.rb +98 -0
- metadata +94 -0
@@ -0,0 +1,40 @@
|
|
1
|
+
# Provides extended abilities to a table
|
2
|
+
# so user does not need to code it into his app.
|
3
|
+
# At the same time, i don't want to weigh down Table with too much functionality/
|
4
|
+
# I am putting in some stuff, so that table columns can be resized, using multipliers.
|
5
|
+
# That allows us to redistribute the space taken or released across rows.
|
6
|
+
# Other options: dd, C, . (repeat) and how about range operations
|
7
|
+
#
|
8
|
+
module TableExtended
|
9
|
+
|
10
|
+
##
|
11
|
+
# increase the focused column
|
12
|
+
# If no size passed, then use numeric multiplier, else 1
|
13
|
+
# Typically, one would bind a key such as + to this method
|
14
|
+
# e.g. atable.bind_key(?+) { atable.increase_column ; }
|
15
|
+
# See examples/viewtodo.rb for usage
|
16
|
+
def increase_column num=(($multiplier.nil? or $multiplier == 0) ? 1 : $multiplier)
|
17
|
+
|
18
|
+
acolumn = column focussed_col()
|
19
|
+
#num = $multiplier || 1
|
20
|
+
$multiplier = 0
|
21
|
+
w = acolumn.width + num
|
22
|
+
acolumn.width w
|
23
|
+
#atable.table_structure_changed
|
24
|
+
end
|
25
|
+
# decrease the focused column
|
26
|
+
# If no size passed, then use numeric multiplier, else 1
|
27
|
+
# Typically, one would bind a key such as - to this method
|
28
|
+
def decrease_column num=(($multiplier.nil? or $multiplier == 0) ? 1 : $multiplier)
|
29
|
+
acolumn = column focussed_col()
|
30
|
+
w = acolumn.width - num
|
31
|
+
$multiplier = 0
|
32
|
+
if w > 3
|
33
|
+
acolumn.width w
|
34
|
+
#atable.table_structure_changed
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
|
40
|
+
end
|
@@ -0,0 +1,203 @@
|
|
1
|
+
require 'rbcurse'
|
2
|
+
|
3
|
+
# This is a horizontal scroller, to scroll a list of options much like
|
4
|
+
# vim often does on the statusline. I'd like to use it for scrolling
|
5
|
+
# menus or buttons like on TabbedPanes to avoid another Form.
|
6
|
+
|
7
|
+
# You may bind to the ENTER_ROW event to chagne some data elsewhere,
|
8
|
+
# or the PRESS event. In this case, PRESS will refer to pressing the
|
9
|
+
# space bar or ENTER. There is <em>no</em> LIST_SELECTION event for that.
|
10
|
+
|
11
|
+
|
12
|
+
|
13
|
+
module RubyCurses
|
14
|
+
|
15
|
+
# Horizontal list scrolling and selection.
|
16
|
+
#
|
17
|
+
# == Example
|
18
|
+
# require 'rbcurse/extras/widgets/horizlist'
|
19
|
+
# require 'fileutils'
|
20
|
+
# l = HorizList.new @form, :row => 5, :col => 5, :width => 80
|
21
|
+
# list = Dir.glob("*")
|
22
|
+
# l.list(list)
|
23
|
+
|
24
|
+
class HorizList < Widget
|
25
|
+
|
26
|
+
def initialize form, config={}, &block
|
27
|
+
super
|
28
|
+
@_events.push(*[:ENTER_ROW, :LEAVE_ROW, :PRESS])
|
29
|
+
@focusable = true
|
30
|
+
init_vars
|
31
|
+
@list = []
|
32
|
+
@toprow = 0
|
33
|
+
|
34
|
+
# last printed index
|
35
|
+
@last_index = 0
|
36
|
+
|
37
|
+
# item on which cursor is
|
38
|
+
@current_index = 0
|
39
|
+
end
|
40
|
+
def init_vars
|
41
|
+
end
|
42
|
+
# alias :text :getvalue # NEXT VERSION
|
43
|
+
|
44
|
+
def map_keys
|
45
|
+
@keys_mapped = true
|
46
|
+
bind_keys([KEY_RIGHT, ?l, ?.], :next_item)
|
47
|
+
bind_keys([KEY_LEFT, ?h,?,], :previous_item)
|
48
|
+
bind_keys([10,13,32]){ fire_action_event }
|
49
|
+
bind_keys([?\M-l,?>, KEY_DOWN], :scroll_right)
|
50
|
+
bind_keys([?\M-h,?<, KEY_UP], :scroll_left)
|
51
|
+
end
|
52
|
+
def list(strings)
|
53
|
+
@list = strings
|
54
|
+
end
|
55
|
+
def add item
|
56
|
+
@list << item
|
57
|
+
end
|
58
|
+
def repaint
|
59
|
+
return unless @repaint_required
|
60
|
+
@window ||= @form.window
|
61
|
+
$log.debug "XXX:HORIZ REPAINT Ci #{@current_index}, TR #{@toprow} "
|
62
|
+
$status_message.value = " #{@current_index}, TR #{@toprow} "
|
63
|
+
@window.printstring @row, @col, " "* @width, @color_pair || $reversecolor
|
64
|
+
c = @col + 1
|
65
|
+
t = @toprow
|
66
|
+
@list.each_with_index { |e, i|
|
67
|
+
next if i < t # sucks for large lists
|
68
|
+
break if c + e.length >= @width + @col
|
69
|
+
att = @attrib || FFI::NCurses::A_NORMAL
|
70
|
+
if i == @current_index
|
71
|
+
att = FFI::NCurses::A_REVERSE if i == @current_index
|
72
|
+
acolor = @focussed_color_pair || get_color($datacolor,'green', 'white')
|
73
|
+
else
|
74
|
+
acolor = @color_pair
|
75
|
+
end
|
76
|
+
@window.printstring @row, c, e, acolor || $reversecolor, att
|
77
|
+
c += e.length + 2
|
78
|
+
@last_index = i
|
79
|
+
break if c >= @width + @col
|
80
|
+
}
|
81
|
+
@repaint_required = false
|
82
|
+
end
|
83
|
+
def handle_key ch
|
84
|
+
map_keys unless @keys_mapped
|
85
|
+
ret = process_key ch, self
|
86
|
+
@multiplier = 0
|
87
|
+
return :UNHANDLED if ret == :UNHANDLED
|
88
|
+
end
|
89
|
+
def previous_item num=(($multiplier.nil? or $multiplier == 0) ? 1 : $multiplier)
|
90
|
+
|
91
|
+
return :NO_PREVIOUS_ROW if @current_index == 0
|
92
|
+
@old_toprow = @toprow
|
93
|
+
@oldrow = @current_index
|
94
|
+
@current_index -= num
|
95
|
+
@current_index = 0 if @current_index < 0
|
96
|
+
bounds_check
|
97
|
+
$multiplier = 0
|
98
|
+
if @current_index < @toprow
|
99
|
+
@toprow = @current_index
|
100
|
+
end
|
101
|
+
@toprow = 0 if @toprow < 0
|
102
|
+
@repaint_required = true
|
103
|
+
end
|
104
|
+
def next_item num=(($multiplier.nil? or $multiplier == 0) ? 1 : $multiplier)
|
105
|
+
rc = row_count
|
106
|
+
return :NO_NEXT_ROW if @current_index == rc-1 # changed 2011-10-5 so process can do something
|
107
|
+
@old_toprow = @toprow
|
108
|
+
@oldrow = @current_index
|
109
|
+
@current_index += num
|
110
|
+
@current_index = rc - 1 if @current_index >= rc
|
111
|
+
@toprow = @current_index if @current_index > @last_index
|
112
|
+
bounds_check
|
113
|
+
$multiplier = 0
|
114
|
+
@repaint_required = true
|
115
|
+
end
|
116
|
+
def scroll_right
|
117
|
+
rc = row_count
|
118
|
+
return :NO_NEXT_ROW if @current_index == rc-1 # changed 2011-10-5 so process can do something
|
119
|
+
@old_toprow = @toprow
|
120
|
+
@oldrow = @current_index
|
121
|
+
|
122
|
+
@current_index = @last_index + 1
|
123
|
+
|
124
|
+
@current_index = rc - 1 if @current_index >= rc
|
125
|
+
@toprow = @current_index if @current_index > @last_index
|
126
|
+
bounds_check
|
127
|
+
$multiplier = 0
|
128
|
+
@repaint_required = true
|
129
|
+
end
|
130
|
+
def scroll_left
|
131
|
+
return :NO_PREVIOUS_ROW if @current_index == 0
|
132
|
+
@old_toprow = @toprow
|
133
|
+
@oldrow = @current_index
|
134
|
+
@current_index = @toprow - 4
|
135
|
+
@current_index = 0 if @current_index < 0
|
136
|
+
bounds_check
|
137
|
+
$multiplier = 0
|
138
|
+
if @current_index < @toprow
|
139
|
+
@toprow = @current_index
|
140
|
+
end
|
141
|
+
@toprow = 0 if @toprow < 0
|
142
|
+
@repaint_required = true
|
143
|
+
end
|
144
|
+
def row_count; @list.size; end
|
145
|
+
def bounds_check
|
146
|
+
@row_changed = false
|
147
|
+
if @oldrow != @current_index
|
148
|
+
on_leave_row @oldrow if respond_to? :on_leave_row # to be defined by widget that has included this
|
149
|
+
on_enter_row @current_index if respond_to? :on_enter_row # to be defined by widget that has included this
|
150
|
+
set_form_row
|
151
|
+
@row_changed = true
|
152
|
+
end
|
153
|
+
if @old_toprow != @toprow # only if scrolling has happened should we repaint
|
154
|
+
@repaint_required = true
|
155
|
+
@widget_scrolled = true
|
156
|
+
end
|
157
|
+
end
|
158
|
+
def on_enter
|
159
|
+
if @list.nil? || @list.size == 0
|
160
|
+
Ncurses.beep
|
161
|
+
return :UNHANDLED
|
162
|
+
end
|
163
|
+
super
|
164
|
+
on_enter_row @current_index
|
165
|
+
set_form_row
|
166
|
+
true
|
167
|
+
end
|
168
|
+
def on_enter_row arow
|
169
|
+
fire_handler :ENTER_ROW, self
|
170
|
+
@repaint_required = true
|
171
|
+
end
|
172
|
+
def on_leave_row arow
|
173
|
+
fire_handler :LEAVE_ROW, self
|
174
|
+
end
|
175
|
+
def fire_action_event
|
176
|
+
require 'rbcurse/core/include/ractionevent'
|
177
|
+
fire_handler :PRESS, ActionEvent.new(self, :PRESS, text)
|
178
|
+
end
|
179
|
+
def current_value
|
180
|
+
@list[@current_index]
|
181
|
+
end
|
182
|
+
alias :text :current_value
|
183
|
+
##
|
184
|
+
end # class
|
185
|
+
end # module
|
186
|
+
if __FILE__ == $PROGRAM_NAME
|
187
|
+
require 'rbcurse/core/util/app'
|
188
|
+
|
189
|
+
App.new do
|
190
|
+
require 'rbcurse/extras/widgets/horizlist'
|
191
|
+
require 'fileutils'
|
192
|
+
l = HorizList.new @form, :row => 5, :col => 5, :width => 80
|
193
|
+
list = Dir.glob("*")
|
194
|
+
l.list(list)
|
195
|
+
l.bind(:PRESS){ |eve| alert "You pressed #{eve.text} " }
|
196
|
+
sl = status_line
|
197
|
+
sl.command do
|
198
|
+
" Status: #{$status_message} "
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
|
203
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module RubyCurses
|
2
|
+
# Create a simple tree-ish structure.
|
3
|
+
# Each node is not a tree, only submenus are trees
|
4
|
+
# Others contain a hash with menu character and code
|
5
|
+
# Typically the code is not a method symbol, it is to be
|
6
|
+
# used to decode a description or method symbol from anoterh hash
|
7
|
+
# @usage
|
8
|
+
# menu = MenuTree.new "Main", { c: :goprev, d: :gonext, e: :gonext, s: :submenu }
|
9
|
+
# menu.submenu :s, "submenu", {a: :next1, b: :next2, f: :next3 }
|
10
|
+
# puts menu.hash
|
11
|
+
# puts "each ..."
|
12
|
+
# menu.each { |e| puts e }
|
13
|
+
# menu.each_pair { |e, v| puts "#{e} #{v}" }
|
14
|
+
# puts " -- :c -- "
|
15
|
+
# puts menu[:c]
|
16
|
+
# puts " -- :s -- "
|
17
|
+
# puts menu[:s].children
|
18
|
+
class MenuTree
|
19
|
+
attr_reader :value
|
20
|
+
def initialize value, hash = {}
|
21
|
+
@value = [value, hash]
|
22
|
+
end
|
23
|
+
def << kv
|
24
|
+
@value[1][kv[0]] = kv[1]
|
25
|
+
end
|
26
|
+
def hash
|
27
|
+
@value[1]
|
28
|
+
end
|
29
|
+
alias :children :hash
|
30
|
+
def push hsh
|
31
|
+
hash().merge hsh
|
32
|
+
end
|
33
|
+
def [](x)
|
34
|
+
hash()[x]
|
35
|
+
end
|
36
|
+
def []=(x,y)
|
37
|
+
hash()[x] = y
|
38
|
+
end
|
39
|
+
def submenu key, value, hash = {}
|
40
|
+
m = MenuTree.new value, hash
|
41
|
+
#hash()[key] = [value, hash]
|
42
|
+
hash()[key] = m
|
43
|
+
end
|
44
|
+
def each
|
45
|
+
hash().keys.each { |e| yield e }
|
46
|
+
end
|
47
|
+
def each_pair
|
48
|
+
hash().each_pair { |name, val| yield name, val }
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
if __FILE__ == $PROGRAM_NAME
|
53
|
+
menu = RubyCurses::MenuTree.new "Main", { c: :goprev, d: :gonext, e: :gonext, s: :submenu }
|
54
|
+
menu.submenu :s, "submenu", {a: :next1, b: :next2, f: :next3 }
|
55
|
+
puts menu.hash
|
56
|
+
puts "each ..."
|
57
|
+
menu.each { |e| puts e }
|
58
|
+
menu.each_pair { |e, v| puts "#{e} #{v}" }
|
59
|
+
puts " -- :c -- "
|
60
|
+
puts menu[:c]
|
61
|
+
puts " -- :s -- "
|
62
|
+
puts menu[:s].children
|
63
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
# ----------------------------------------------------------------------------- #
|
2
|
+
# File: multilinelabel.rb
|
3
|
+
# Description: Prints a label on the screen.
|
4
|
+
# This is the original label that was present in rwidgets.rb
|
5
|
+
# It allowed for multiple lines. I am simplifying that to a simple
|
6
|
+
# single line label.
|
7
|
+
# I am basically moving multiline labels out of hte core package
|
8
|
+
# Author: rkumar http://github.com/rkumar/rbcurse/
|
9
|
+
# Date: 2011-11-12 - 12:04
|
10
|
+
# License: Same as Ruby's License (http://www.ruby-lang.org/LICENSE.txt)
|
11
|
+
# Last update: 2011-11-12 - 12:05
|
12
|
+
# ----------------------------------------------------------------------------- #
|
13
|
+
#
|
14
|
+
module RubyCurses
|
15
|
+
|
16
|
+
# print static text on a form, allowing for text to be wrapped.
|
17
|
+
#
|
18
|
+
class MultiLineLabel < Widget
|
19
|
+
dsl_accessor :mnemonic # keyboard focus is passed to buddy based on this key (ALT mask)
|
20
|
+
|
21
|
+
# justify required a display length, esp if center.
|
22
|
+
dsl_property :justify #:right, :left, :center
|
23
|
+
dsl_property :display_length #please give this to ensure the we only print this much
|
24
|
+
#dsl_property :height #if you want a multiline label. already added to widget
|
25
|
+
# for consistency with others 2011-11-5
|
26
|
+
alias :width :display_length
|
27
|
+
alias :width= :display_length=
|
28
|
+
|
29
|
+
def initialize form, config={}, &block
|
30
|
+
|
31
|
+
# this crap was used in position_label, find another way. where is it used ?
|
32
|
+
#@row = config.fetch("row",-1) # why on earth this monstrosity ? 2011-11-5
|
33
|
+
#@col = config.fetch("col",-1)
|
34
|
+
#@bgcolor = config.fetch("bgcolor", $def_bg_color)
|
35
|
+
#@color = config.fetch("color", $def_fg_color)
|
36
|
+
@text = config.fetch("text", "NOTFOUND")
|
37
|
+
@editable = false
|
38
|
+
@focusable = false
|
39
|
+
super
|
40
|
+
@justify ||= :left
|
41
|
+
@name ||= @text
|
42
|
+
@repaint_required = true
|
43
|
+
end
|
44
|
+
#
|
45
|
+
# get the value for the label
|
46
|
+
def getvalue
|
47
|
+
@text_variable && @text_variable.value || @text
|
48
|
+
end
|
49
|
+
def label_for field
|
50
|
+
@label_for = field
|
51
|
+
#$log.debug " label for: #{@label_for}"
|
52
|
+
if @form
|
53
|
+
bind_hotkey
|
54
|
+
else
|
55
|
+
@when_form ||= []
|
56
|
+
@when_form << lambda { bind_hotkey }
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
##
|
61
|
+
# for a button, fire it when label invoked without changing focus
|
62
|
+
# for other widgets, attempt to change focus to that field
|
63
|
+
def bind_hotkey
|
64
|
+
if !@mnemonic.nil?
|
65
|
+
ch = @mnemonic.downcase()[0].ord ## 1.9 DONE
|
66
|
+
# meta key
|
67
|
+
mch = ?\M-a.getbyte(0) + (ch - ?a.getbyte(0)) ## 1.9
|
68
|
+
if @label_for.is_a? RubyCurses::Button and @label_for.respond_to? :fire
|
69
|
+
@form.bind_key(mch, @label_for) { |_form, _butt| _butt.fire }
|
70
|
+
else
|
71
|
+
$log.debug " bind_hotkey label for: #{@label_for}"
|
72
|
+
@form.bind_key(mch, @label_for) { |_form, _field| _field.focus }
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
##
|
78
|
+
# XXX need to move wrapping etc up and done once.
|
79
|
+
def repaint
|
80
|
+
return unless @repaint_required
|
81
|
+
raise "Label row or col nil #{@row} , #{@col}, #{@text} " if @row.nil? || @col.nil?
|
82
|
+
r,c = rowcol
|
83
|
+
|
84
|
+
@bgcolor ||= $def_bg_color
|
85
|
+
@color ||= $def_fg_color
|
86
|
+
# value often nil so putting blank, but usually some application error
|
87
|
+
value = getvalue_for_paint || ""
|
88
|
+
lablist = []
|
89
|
+
# trying out array values 2011-10-16 more for messageboxes.
|
90
|
+
if value.is_a? Array
|
91
|
+
lablist = text
|
92
|
+
@height = text.size
|
93
|
+
elsif @height && @height > 1
|
94
|
+
lablist = wrap_text(value, @display_length).split("\n")
|
95
|
+
else
|
96
|
+
# ensure we do not exceed
|
97
|
+
if !@display_length.nil?
|
98
|
+
if value.length > @display_length
|
99
|
+
value = value[0..@display_length-1]
|
100
|
+
end
|
101
|
+
end
|
102
|
+
lablist << value
|
103
|
+
end
|
104
|
+
len = @display_length || value.length
|
105
|
+
acolor = get_color $datacolor
|
106
|
+
$log.debug "label :#{@text}, #{value}, r #{r}, c #{c} col= #{@color}, #{@bgcolor} acolor #{acolor} j:#{@justify} dlL: #{@display_length} "
|
107
|
+
firstrow = r
|
108
|
+
_height = @height || 1
|
109
|
+
str = @justify.to_sym == :right ? "%*s" : "%-*s" # added 2008-12-22 19:05
|
110
|
+
# loop added for labels that are wrapped.
|
111
|
+
# TODO clear separately since value can change in status like labels
|
112
|
+
|
113
|
+
@graphic = @form.window if @graphic.nil? ## HACK messagebox givig this in repaint, 423 not working ??
|
114
|
+
0.upto(_height-1) { |i|
|
115
|
+
@graphic.printstring r+i, c, " " * len , acolor,@attr
|
116
|
+
}
|
117
|
+
lablist.each_with_index do |_value, ix|
|
118
|
+
break if ix >= _height
|
119
|
+
if @justify.to_sym == :center
|
120
|
+
padding = (@display_length - _value.length)/2
|
121
|
+
_value = " "*padding + _value + " "*padding # so its cleared if we change it midway
|
122
|
+
end
|
123
|
+
@graphic.printstring r, c, str % [len, _value], acolor,@attr
|
124
|
+
r += 1
|
125
|
+
end
|
126
|
+
if !@mnemonic.nil?
|
127
|
+
ulindex = value.index(@mnemonic) || value.index(@mnemonic.swapcase)
|
128
|
+
@graphic.mvchgat(y=firstrow, x=c+ulindex, max=1, Ncurses::A_BOLD|Ncurses::A_UNDERLINE, acolor, nil)
|
129
|
+
end
|
130
|
+
#@form.window.mvchgat(y=r, x=c, max=len, Ncurses::A_NORMAL, color, nil)
|
131
|
+
@repaint_required = false
|
132
|
+
end
|
133
|
+
# Added 2011-10-22 to prevent some naive components from putting focus here.
|
134
|
+
def on_enter
|
135
|
+
raise "Cannot enter Label"
|
136
|
+
end
|
137
|
+
def on_leave
|
138
|
+
raise "Cannot leave Label"
|
139
|
+
end
|
140
|
+
# ADD HERE LABEL
|
141
|
+
end # class
|
142
|
+
end # module
|