rbcurse-extras 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|