rbhex-core 1.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.
- checksums.yaml +7 -0
- data/.gitignore +20 -0
- data/CHANGELOG +2000 -0
- data/LICENSE +56 -0
- data/README.md +44 -0
- data/examples/abasiclist.rb +179 -0
- data/examples/alpmenu.rb +50 -0
- data/examples/app.sample +19 -0
- data/examples/atree.rb +100 -0
- data/examples/bline.rb +136 -0
- data/examples/common/file.rb +45 -0
- data/examples/data/README.markdown +9 -0
- data/examples/data/brew.txt +38 -0
- data/examples/data/color.2 +37 -0
- data/examples/data/gemlist.txt +60 -0
- data/examples/data/lotr.txt +12 -0
- data/examples/data/ports.txt +136 -0
- data/examples/data/table.txt +37 -0
- data/examples/data/tasks.csv +88 -0
- data/examples/data/tasks.txt +27 -0
- data/examples/data/todo.txt +10 -0
- data/examples/data/todo.txt.bak +10 -0
- data/examples/data/todocsv.csv +28 -0
- data/examples/data/unix1.txt +21 -0
- data/examples/data/unix2.txt +11 -0
- data/examples/dbdemo.rb +502 -0
- data/examples/dirtree.rb +94 -0
- data/examples/newtabbedwindow.rb +100 -0
- data/examples/newtesttabp.rb +92 -0
- data/examples/tabular.rb +146 -0
- data/examples/tasks.rb +178 -0
- data/examples/term2.rb +84 -0
- data/examples/testbuttons.rb +296 -0
- data/examples/testcombo.rb +102 -0
- data/examples/testfields.rb +195 -0
- data/examples/testkeypress.rb +72 -0
- data/examples/testlistbox.rb +170 -0
- data/examples/testmessagebox.rb +140 -0
- data/examples/testprogress.rb +116 -0
- data/examples/testree.rb +106 -0
- data/examples/testwsshortcuts.rb +66 -0
- data/examples/testwsshortcuts2.rb +128 -0
- data/lib/rbhex.rb +6 -0
- data/lib/rbhex/core/docs/index.txt +73 -0
- data/lib/rbhex/core/include/action.rb +80 -0
- data/lib/rbhex/core/include/actionmanager.rb +49 -0
- data/lib/rbhex/core/include/appmethods.rb +214 -0
- data/lib/rbhex/core/include/bordertitle.rb +48 -0
- data/lib/rbhex/core/include/chunk.rb +203 -0
- data/lib/rbhex/core/include/io.rb +553 -0
- data/lib/rbhex/core/include/listbindings.rb +74 -0
- data/lib/rbhex/core/include/listcellrenderer.rb +140 -0
- data/lib/rbhex/core/include/listeditable.rb +317 -0
- data/lib/rbhex/core/include/listscrollable.rb +663 -0
- data/lib/rbhex/core/include/listselectable.rb +271 -0
- data/lib/rbhex/core/include/multibuffer.rb +83 -0
- data/lib/rbhex/core/include/orderedhash.rb +77 -0
- data/lib/rbhex/core/include/ractionevent.rb +73 -0
- data/lib/rbhex/core/include/rchangeevent.rb +27 -0
- data/lib/rbhex/core/include/rhistory.rb +95 -0
- data/lib/rbhex/core/include/rinputdataevent.rb +47 -0
- data/lib/rbhex/core/include/vieditable.rb +172 -0
- data/lib/rbhex/core/include/widgetmenu.rb +66 -0
- data/lib/rbhex/core/system/colormap.rb +165 -0
- data/lib/rbhex/core/system/keyboard.rb +150 -0
- data/lib/rbhex/core/system/keydefs.rb +30 -0
- data/lib/rbhex/core/system/ncurses.rb +236 -0
- data/lib/rbhex/core/system/panel.rb +162 -0
- data/lib/rbhex/core/system/window.rb +913 -0
- data/lib/rbhex/core/util/ansiparser.rb +119 -0
- data/lib/rbhex/core/util/app.rb +1228 -0
- data/lib/rbhex/core/util/basestack.rb +410 -0
- data/lib/rbhex/core/util/bottomline.rb +1859 -0
- data/lib/rbhex/core/util/colorparser.rb +77 -0
- data/lib/rbhex/core/util/focusmanager.rb +31 -0
- data/lib/rbhex/core/util/padreader.rb +192 -0
- data/lib/rbhex/core/util/rcommandwindow.rb +604 -0
- data/lib/rbhex/core/util/rdialogs.rb +574 -0
- data/lib/rbhex/core/util/viewer.rb +149 -0
- data/lib/rbhex/core/util/widgetshortcuts.rb +506 -0
- data/lib/rbhex/core/version.rb +5 -0
- data/lib/rbhex/core/widgets/applicationheader.rb +103 -0
- data/lib/rbhex/core/widgets/box.rb +58 -0
- data/lib/rbhex/core/widgets/divider.rb +310 -0
- data/lib/rbhex/core/widgets/keylabelprinter.rb +194 -0
- data/lib/rbhex/core/widgets/rcombo.rb +253 -0
- data/lib/rbhex/core/widgets/rcontainer.rb +415 -0
- data/lib/rbhex/core/widgets/rlink.rb +30 -0
- data/lib/rbhex/core/widgets/rlist.rb +696 -0
- data/lib/rbhex/core/widgets/rmenu.rb +958 -0
- data/lib/rbhex/core/widgets/rmenulink.rb +22 -0
- data/lib/rbhex/core/widgets/rmessagebox.rb +387 -0
- data/lib/rbhex/core/widgets/rprogress.rb +118 -0
- data/lib/rbhex/core/widgets/rtabbedpane.rb +634 -0
- data/lib/rbhex/core/widgets/rtabbedwindow.rb +70 -0
- data/lib/rbhex/core/widgets/rtextarea.rb +960 -0
- data/lib/rbhex/core/widgets/rtextview.rb +739 -0
- data/lib/rbhex/core/widgets/rtree.rb +768 -0
- data/lib/rbhex/core/widgets/rwidget.rb +3277 -0
- data/lib/rbhex/core/widgets/scrollbar.rb +143 -0
- data/lib/rbhex/core/widgets/statusline.rb +113 -0
- data/lib/rbhex/core/widgets/tabular.rb +264 -0
- data/lib/rbhex/core/widgets/tabularwidget.rb +1142 -0
- data/lib/rbhex/core/widgets/textpad.rb +995 -0
- data/lib/rbhex/core/widgets/tree/treecellrenderer.rb +150 -0
- data/lib/rbhex/core/widgets/tree/treemodel.rb +428 -0
- data/rbhex-core.gemspec +32 -0
- metadata +172 -0
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Common stack flow functionality
|
|
3
|
+
# * Name: basestack.rb
|
|
4
|
+
# * Description: Classes that allow user to stack and flow components
|
|
5
|
+
#
|
|
6
|
+
# * Date: 30.10.11 - 12:57
|
|
7
|
+
# * Last update: 2014-05-02 19:16
|
|
8
|
+
#
|
|
9
|
+
module RubyCurses
|
|
10
|
+
module ModStack
|
|
11
|
+
#
|
|
12
|
+
# Base class for stacks and flows.
|
|
13
|
+
# Will manage determining row col and width height of objects
|
|
14
|
+
# Stacks place objects one below another. Flows place objects to the
|
|
15
|
+
# right of the previous. Orientation can be reversed.
|
|
16
|
+
#
|
|
17
|
+
class BaseStack
|
|
18
|
+
attr_accessor :components
|
|
19
|
+
attr_reader :config
|
|
20
|
+
attr_accessor :form
|
|
21
|
+
def initialize config={}, components=[]
|
|
22
|
+
@config = config
|
|
23
|
+
config.each do |k, v|
|
|
24
|
+
instance_variable_set "@#{k}", v
|
|
25
|
+
end
|
|
26
|
+
@components = components
|
|
27
|
+
@calc_needed = true
|
|
28
|
+
end
|
|
29
|
+
# XXX if user sets later, we won't be checking the config
|
|
30
|
+
# We check the actual variables which config sets in init
|
|
31
|
+
%w[ parent_component width height weight row col orientation margin_top margin_bottom margin_left margin_right].each { |e|
|
|
32
|
+
eval(
|
|
33
|
+
"def #{e}
|
|
34
|
+
@config[:#{e}]
|
|
35
|
+
end
|
|
36
|
+
def #{e}=(val)
|
|
37
|
+
@config[:#{e}]=val
|
|
38
|
+
instance_variable_set \"@#{e}\", val
|
|
39
|
+
@calc_needed = true
|
|
40
|
+
end"
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
alias :parent :parent_component
|
|
44
|
+
#alias :parent= :parent_component
|
|
45
|
+
def repaint # stack
|
|
46
|
+
$log.debug "XXX: stack repaint recalc #{@calc_needed} "
|
|
47
|
+
@components.each { |e| e.form = @form unless e.form } #unless @calc_needed
|
|
48
|
+
recalc if @calc_needed
|
|
49
|
+
@components.each { |e| e.repaint }
|
|
50
|
+
end
|
|
51
|
+
def repaint_all x
|
|
52
|
+
@calc_needed = true
|
|
53
|
+
end
|
|
54
|
+
def override_graphic gr
|
|
55
|
+
@graphic = gr
|
|
56
|
+
end
|
|
57
|
+
def focusable; false; end
|
|
58
|
+
# Calculates row col and width height
|
|
59
|
+
# for each subc-omponent based on coords of Container
|
|
60
|
+
# This is to be called only when the container has got its coordinates (i.e
|
|
61
|
+
# Containers repaint). This should be in this objects repaint.
|
|
62
|
+
def recalc
|
|
63
|
+
@calc_needed = false
|
|
64
|
+
comp = self
|
|
65
|
+
if comp.is_a? BaseStack
|
|
66
|
+
check_coords comp
|
|
67
|
+
@margin_left ||= 0
|
|
68
|
+
@margin_right ||= 0
|
|
69
|
+
@margin_top ||= 0
|
|
70
|
+
@margin_bottom ||= 0
|
|
71
|
+
if comp.is_a? Stack
|
|
72
|
+
r = row + @margin_top
|
|
73
|
+
rem = 0
|
|
74
|
+
ht = height - (@margin_top + @margin_bottom)
|
|
75
|
+
if @orientation == :bottom
|
|
76
|
+
mult = -1
|
|
77
|
+
comps = @components.reverse
|
|
78
|
+
r = row + height - @margin_bottom
|
|
79
|
+
|
|
80
|
+
else
|
|
81
|
+
mult = 1
|
|
82
|
+
comps = @components
|
|
83
|
+
|
|
84
|
+
end
|
|
85
|
+
comps.each { |e|
|
|
86
|
+
# should only happen if expandable FIXME
|
|
87
|
+
e.margin_top ||= 0
|
|
88
|
+
e.margin_bottom ||= 0
|
|
89
|
+
e.height = 0.01 * e.weight * (ht - (e.margin_top + e.margin_bottom))
|
|
90
|
+
hround = e.height.floor
|
|
91
|
+
rem += e.height - hround
|
|
92
|
+
e.height = hround #- (@margin_top + @margin_bottom)
|
|
93
|
+
# rounding creates a problem, since 0.5 gets rounded up and we can exceed bound
|
|
94
|
+
# So i floor, and maintain the lost space, and add it back when it exceeds 1
|
|
95
|
+
# This way the last components gets stretched to meet the end, which is required
|
|
96
|
+
# when the height of the stack is odd and there's a left-over row
|
|
97
|
+
if rem >= 1
|
|
98
|
+
e.height += 1
|
|
99
|
+
rem = 0
|
|
100
|
+
end
|
|
101
|
+
# Item level margins have not been accounted for when calculating weightages, and
|
|
102
|
+
# should not be used on the weightage axis
|
|
103
|
+
r += e.margin_top
|
|
104
|
+
if @orientation == :bottom
|
|
105
|
+
r += e.height * mult
|
|
106
|
+
e.row = r
|
|
107
|
+
else
|
|
108
|
+
e.row = r
|
|
109
|
+
r += e.height + 0
|
|
110
|
+
end
|
|
111
|
+
e.margin_left ||= 0
|
|
112
|
+
e.margin_right ||= 0
|
|
113
|
+
e.width = width - (@margin_left + @margin_right + e.margin_left + e.margin_right)
|
|
114
|
+
e.col = col + @margin_left + e.margin_left # ??? XXX
|
|
115
|
+
#$log.debug "XXX: recalc stack #{e.widget.class} r:#{e.row} c:#{e.col} h:#{e.height} = we:#{e.weight} * h:#{height} "
|
|
116
|
+
#e.col_offset = col_offset # ??? XXX
|
|
117
|
+
check_coords e
|
|
118
|
+
e.repaint_all(true)
|
|
119
|
+
e.recalc if e.is_a? BaseStack
|
|
120
|
+
}
|
|
121
|
+
elsif comp.is_a? Flow
|
|
122
|
+
c = col + @margin_left #+ col_offset
|
|
123
|
+
rem = 0
|
|
124
|
+
wd = width - (@margin_left + @margin_right)
|
|
125
|
+
# right_to_left orientation
|
|
126
|
+
if @orientation == :right
|
|
127
|
+
mult = -1
|
|
128
|
+
comps = @components.reverse
|
|
129
|
+
c = col + width - @margin_right
|
|
130
|
+
$log.debug "XXX: ORIENT1f recalc #{@orientation} "
|
|
131
|
+
else
|
|
132
|
+
mult = 1
|
|
133
|
+
comps = @components
|
|
134
|
+
$log.debug "XXX: ORIENT2f recalc #{@orientation} "
|
|
135
|
+
end
|
|
136
|
+
comps.each { |e|
|
|
137
|
+
e.width = e.weight * wd * 0.01
|
|
138
|
+
wround = e.width.floor
|
|
139
|
+
rem += e.width - wround
|
|
140
|
+
e.width = wround
|
|
141
|
+
# see comment in prev block regarding remaininder
|
|
142
|
+
if rem >= 1
|
|
143
|
+
e.width += 1
|
|
144
|
+
rem = 0
|
|
145
|
+
end
|
|
146
|
+
e.height = height - (@margin_top + @margin_bottom) #* weight * 0.01
|
|
147
|
+
#e.height = e.height.round
|
|
148
|
+
if @orientation == :right
|
|
149
|
+
c += e.width * mult # mult 1 or -1
|
|
150
|
+
e.col = c
|
|
151
|
+
else
|
|
152
|
+
e.col = c
|
|
153
|
+
c += e.width * mult # mult 1 or -1
|
|
154
|
+
end
|
|
155
|
+
e.row = row + @margin_top
|
|
156
|
+
check_coords e
|
|
157
|
+
$log.debug "XXX: recalc flow #{e.widget.class} r:#{e.row} c:#{e.col} h:#{e.height} = we:#{e.weight} * w:#{width} "
|
|
158
|
+
e.repaint_all(true) # why not happening when we change row, hieght etc
|
|
159
|
+
e.recalc if e.is_a? BaseStack
|
|
160
|
+
}
|
|
161
|
+
end
|
|
162
|
+
else
|
|
163
|
+
alert "in else recalc DOES NOT COME HERE "
|
|
164
|
+
comp.col = comp.parent.col
|
|
165
|
+
comp.row = comp.parent.row
|
|
166
|
+
comp.height = comp.parent.height
|
|
167
|
+
comp.width = comp.parent.width
|
|
168
|
+
$log.debug "XXX: recalc else #{comp.class} r #{comp.row} c #{comp.col} . h #{comp} height w #{comp.width} "
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
# Traverses the comopnent tree and calculates weightages for all components
|
|
172
|
+
# based on what has been specified by user
|
|
173
|
+
def check_coords e # stack
|
|
174
|
+
r = e.row
|
|
175
|
+
c = e.col
|
|
176
|
+
if r >= row + height
|
|
177
|
+
$log.warn "XXX: WARN e.class is out of bounds row #{r} "
|
|
178
|
+
e.visible = false
|
|
179
|
+
end
|
|
180
|
+
if c >= col + width
|
|
181
|
+
$log.warn "XXX: WARN e.class is out of bounds col #{c} "
|
|
182
|
+
e.visible = false
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
def increase c=@current_component
|
|
186
|
+
p = self #c.parent_component
|
|
187
|
+
ci = p.components.index(c)
|
|
188
|
+
ni = ci + 1
|
|
189
|
+
if p.components[ni].nil?
|
|
190
|
+
ni = nil
|
|
191
|
+
end
|
|
192
|
+
case p
|
|
193
|
+
when Flow
|
|
194
|
+
# increase width of current and reduce from neighbor
|
|
195
|
+
if ni
|
|
196
|
+
n = p.components[ni]
|
|
197
|
+
$log.debug "XXX: INC fl current #{ci}, total#{p.components.count}, next #{n} "
|
|
198
|
+
|
|
199
|
+
c.width += 1
|
|
200
|
+
n.width -= 1
|
|
201
|
+
n.col += 1
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
when Stack
|
|
205
|
+
if ni
|
|
206
|
+
n = p.components[ni]
|
|
207
|
+
$log.debug "XXX: INC fl current #{ci}, total#{p.components.count}, next #{n} "
|
|
208
|
+
|
|
209
|
+
c.height += 1
|
|
210
|
+
n.height -= 1
|
|
211
|
+
n.row += 1
|
|
212
|
+
end
|
|
213
|
+
$log.debug "XXX: INC st current #{ci}, total#{p.components.count} "
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
end
|
|
217
|
+
def decrease c=@current_component
|
|
218
|
+
p = self #c.parent_component
|
|
219
|
+
ci = p.components.index(c)
|
|
220
|
+
ni = ci + 1
|
|
221
|
+
if p.components[ni].nil?
|
|
222
|
+
ni = nil
|
|
223
|
+
end
|
|
224
|
+
case p
|
|
225
|
+
when Flow
|
|
226
|
+
# increase width of current and reduce from neighbor
|
|
227
|
+
if ni
|
|
228
|
+
n = p.components[ni]
|
|
229
|
+
$log.debug "XXX: INC fl current #{ci}, total#{p.components.count}, next #{n} "
|
|
230
|
+
|
|
231
|
+
c.width -= 1
|
|
232
|
+
n.width += 1
|
|
233
|
+
n.col -= 1
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
when Stack
|
|
237
|
+
if ni
|
|
238
|
+
n = p.components[ni]
|
|
239
|
+
$log.debug "XXX: INC fl current #{ci}, total#{p.components.count}, next #{n} "
|
|
240
|
+
|
|
241
|
+
c.height -= 1
|
|
242
|
+
n.height += 1
|
|
243
|
+
n.row -= 1
|
|
244
|
+
end
|
|
245
|
+
$log.debug "XXX: INC st current #{ci}, total#{p.components.count} "
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
end
|
|
249
|
+
def to_s
|
|
250
|
+
@components
|
|
251
|
+
end
|
|
252
|
+
end # class Base
|
|
253
|
+
# A stack positions objects one below the other
|
|
254
|
+
class Stack < BaseStack; end
|
|
255
|
+
# A flow positions objects in a left to right
|
|
256
|
+
class Flow < BaseStack; end
|
|
257
|
+
#
|
|
258
|
+
# A wrapper over widget mostly because it adds weight and margins
|
|
259
|
+
#
|
|
260
|
+
class Item
|
|
261
|
+
attr_reader :config, :widget
|
|
262
|
+
attr_reader :margin_top, :margin_left, :margin_bottom, :margin_right
|
|
263
|
+
def initialize config={}, widget
|
|
264
|
+
@config = config
|
|
265
|
+
config.each do |k, v|
|
|
266
|
+
instance_variable_set "@#{k}", v
|
|
267
|
+
end
|
|
268
|
+
@margin_left ||= 0
|
|
269
|
+
@margin_right ||= 0
|
|
270
|
+
@margin_top ||= 0
|
|
271
|
+
@margin_bottom ||= 0
|
|
272
|
+
@widget = widget
|
|
273
|
+
end
|
|
274
|
+
def weight; @config[:weight]||100; end
|
|
275
|
+
def weight=(val); @config[:weight]=val; end
|
|
276
|
+
def repaint; @widget.repaint; end
|
|
277
|
+
%w[ form parent parent_component width height row col row_offset col_offset focusable].each { |e|
|
|
278
|
+
eval(
|
|
279
|
+
"def #{e}
|
|
280
|
+
@widget.#{e}
|
|
281
|
+
end
|
|
282
|
+
def #{e}=(val)
|
|
283
|
+
@widget.#{e}=val
|
|
284
|
+
end"
|
|
285
|
+
)
|
|
286
|
+
}
|
|
287
|
+
def method_missing(sym, *args, &block)
|
|
288
|
+
@widget.send sym, *args, &block
|
|
289
|
+
end
|
|
290
|
+
end # class Item
|
|
291
|
+
|
|
292
|
+
# --------------------- module level ------------------------------#
|
|
293
|
+
# General routin to traverse components and their components
|
|
294
|
+
def traverse c, &block
|
|
295
|
+
if c.is_a? BaseStack
|
|
296
|
+
yield c
|
|
297
|
+
c.components.each { |e|
|
|
298
|
+
yield e
|
|
299
|
+
}
|
|
300
|
+
c.components.each { |e| traverse(e, &block) }
|
|
301
|
+
@ctr -= 1
|
|
302
|
+
else
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
# traverse the components and their children
|
|
307
|
+
#
|
|
308
|
+
def each &block
|
|
309
|
+
@components.each { |e| traverse e, &block }
|
|
310
|
+
end
|
|
311
|
+
# module level
|
|
312
|
+
private
|
|
313
|
+
def _stack type, config={}, &block
|
|
314
|
+
case type
|
|
315
|
+
when :stack
|
|
316
|
+
s = Stack.new(config)
|
|
317
|
+
when :flow
|
|
318
|
+
s = Flow.new(config)
|
|
319
|
+
end
|
|
320
|
+
_add s
|
|
321
|
+
@active << s
|
|
322
|
+
yield_or_eval &block if block_given?
|
|
323
|
+
@active.pop
|
|
324
|
+
# if active is empty then this is where we could calculate
|
|
325
|
+
# percentatges and do recalc, thus making it independent
|
|
326
|
+
end
|
|
327
|
+
# module level
|
|
328
|
+
private
|
|
329
|
+
def _add s
|
|
330
|
+
if @active.empty?
|
|
331
|
+
$log.debug "XXX: ADDING TO components #{s} "
|
|
332
|
+
unless s.is_a? BaseStack
|
|
333
|
+
raise "No stack or flow to add to. Results may not be what you want"
|
|
334
|
+
end
|
|
335
|
+
@components << s
|
|
336
|
+
else
|
|
337
|
+
@active.last.components << s
|
|
338
|
+
end
|
|
339
|
+
__add s
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
# module level
|
|
343
|
+
public
|
|
344
|
+
def stack config={}, &block
|
|
345
|
+
_stack :stack, config, &block
|
|
346
|
+
end
|
|
347
|
+
def flow config={}, &block
|
|
348
|
+
_stack :flow, config, &block
|
|
349
|
+
end
|
|
350
|
+
# module level
|
|
351
|
+
private
|
|
352
|
+
def add w, config={}
|
|
353
|
+
i = Item.new config, w
|
|
354
|
+
_add i
|
|
355
|
+
end
|
|
356
|
+
alias :add_widget :add
|
|
357
|
+
# module level
|
|
358
|
+
private
|
|
359
|
+
def calc_weightages2 components, parent
|
|
360
|
+
#puts " #{@ctr} --> #{c.type}, wt: #{c.config[:weight]} "
|
|
361
|
+
@ctr += 1
|
|
362
|
+
wt = 0
|
|
363
|
+
cnt = 0
|
|
364
|
+
sz = components.count
|
|
365
|
+
$log.debug "XXX: calc COMP COUNT #{sz} "
|
|
366
|
+
# calculate how much weightage has been given by user
|
|
367
|
+
# so we can allocate average to other components
|
|
368
|
+
components.each { |e|
|
|
369
|
+
if e.config[:weight]
|
|
370
|
+
wt += e.config[:weight]
|
|
371
|
+
cnt += 1
|
|
372
|
+
end
|
|
373
|
+
$log.debug "XXX: INC setting parent #{parent} to #{e} "
|
|
374
|
+
e.config[:parent] = parent
|
|
375
|
+
e.config[:level] = @ctr
|
|
376
|
+
}
|
|
377
|
+
used = sz - cnt
|
|
378
|
+
$log.debug "XXX: ADDING calc COMP COUNT #{sz} - #{cnt} "
|
|
379
|
+
if used > 0
|
|
380
|
+
avg = (100-wt)/used
|
|
381
|
+
# Allocate average to other components
|
|
382
|
+
components.each { |e| e.config[:weight] = avg unless e.config[:weight] }
|
|
383
|
+
end
|
|
384
|
+
components.each { |e| calc_weightages2(e.components, e) if e.respond_to? :components }
|
|
385
|
+
@ctr -= 1
|
|
386
|
+
end
|
|
387
|
+
# module level
|
|
388
|
+
#private
|
|
389
|
+
public
|
|
390
|
+
# given an widget, return the item, so we can change weight or some other config
|
|
391
|
+
def item_for widget
|
|
392
|
+
each do |e|
|
|
393
|
+
if e.is_a? Item
|
|
394
|
+
if e.widget == widget
|
|
395
|
+
return e
|
|
396
|
+
end
|
|
397
|
+
end
|
|
398
|
+
end
|
|
399
|
+
return nil
|
|
400
|
+
end
|
|
401
|
+
# module level
|
|
402
|
+
# returns the parent (flow or stack) for a given widget
|
|
403
|
+
# allowing user to change configuration such as weight
|
|
404
|
+
def parent_of widget
|
|
405
|
+
f = item_for widget
|
|
406
|
+
return f.config[:parent] if f
|
|
407
|
+
return nil
|
|
408
|
+
end
|
|
409
|
+
end # mod modstack
|
|
410
|
+
end # mod
|
|
@@ -0,0 +1,1859 @@
|
|
|
1
|
+
require "date"
|
|
2
|
+
require "erb"
|
|
3
|
+
require 'pathname'
|
|
4
|
+
=begin
|
|
5
|
+
* Name : bottomline.rb
|
|
6
|
+
* Description : routines for input at bottom of screen like vim, or anyother line
|
|
7
|
+
* :
|
|
8
|
+
* Author : rkumar
|
|
9
|
+
* Date : 2010-10-25 12:45
|
|
10
|
+
* License :
|
|
11
|
+
Same as Ruby's License (http://www.ruby-lang.org/LICENSE.txt)
|
|
12
|
+
|
|
13
|
+
The character input routines are from io.rb, however, the user-interface to the input
|
|
14
|
+
is copied from the Highline project (James Earl Gray) with permission.
|
|
15
|
+
|
|
16
|
+
May later use a Label and Field.
|
|
17
|
+
|
|
18
|
+
NOTE : Pls avoid directly using this class. I am trying to redo this so ask, agree and say
|
|
19
|
+
can create their own window and be done with it. The hurdle in that is that ask calls
|
|
20
|
+
say, so when to close the window is not clear within say. Some shakeup is expected by
|
|
21
|
+
1.4.0 or so.
|
|
22
|
+
|
|
23
|
+
=end
|
|
24
|
+
module RubyCurses
|
|
25
|
+
|
|
26
|
+
# just so the program does not bomb due to a tiny feature
|
|
27
|
+
# I do not raise error on nil array, i create a dummy array
|
|
28
|
+
# which you likely will not be able to use, in any case it will have only one value
|
|
29
|
+
class History < Struct.new(:array, :current_index)
|
|
30
|
+
attr_reader :last_index
|
|
31
|
+
attr_reader :current_index
|
|
32
|
+
attr_reader :array
|
|
33
|
+
def initialize a=nil, c=0
|
|
34
|
+
#raise "Array passed to History cannot be nil" unless a
|
|
35
|
+
#@max_index = a.size
|
|
36
|
+
@array = a || []
|
|
37
|
+
@current_index = c
|
|
38
|
+
@last_index = c
|
|
39
|
+
end
|
|
40
|
+
def last
|
|
41
|
+
@current_index = max_index
|
|
42
|
+
@array.last
|
|
43
|
+
end
|
|
44
|
+
def first
|
|
45
|
+
@current_index = 0
|
|
46
|
+
@array.first
|
|
47
|
+
end
|
|
48
|
+
def max_index
|
|
49
|
+
@array.size - 1
|
|
50
|
+
end
|
|
51
|
+
def up
|
|
52
|
+
item = @array[@current_index]
|
|
53
|
+
previous
|
|
54
|
+
return item
|
|
55
|
+
end
|
|
56
|
+
def next
|
|
57
|
+
@last_index = @current_index
|
|
58
|
+
if @current_index + 1 > max_index
|
|
59
|
+
@current_index = 0
|
|
60
|
+
else
|
|
61
|
+
@current_index += 1
|
|
62
|
+
end
|
|
63
|
+
@array[@current_index]
|
|
64
|
+
end
|
|
65
|
+
def previous
|
|
66
|
+
@last_index = @current_index
|
|
67
|
+
if @current_index - 1 < 0
|
|
68
|
+
@current_index = max_index()
|
|
69
|
+
else
|
|
70
|
+
@current_index -= 1
|
|
71
|
+
end
|
|
72
|
+
@array[@current_index]
|
|
73
|
+
end
|
|
74
|
+
def is_last?
|
|
75
|
+
@current_index == max_index()
|
|
76
|
+
end
|
|
77
|
+
def push item
|
|
78
|
+
$log.debug " XXX history push #{item} " if $log.debug?
|
|
79
|
+
@array.push item
|
|
80
|
+
@current_index = max_index
|
|
81
|
+
end
|
|
82
|
+
end # class
|
|
83
|
+
# some variables are polluting space of including app,
|
|
84
|
+
# we should make this a class.
|
|
85
|
+
class Bottomline
|
|
86
|
+
attr_accessor :window
|
|
87
|
+
attr_accessor :message_row
|
|
88
|
+
attr_accessor :name # for debugging
|
|
89
|
+
def initialize win=nil, row=nil
|
|
90
|
+
@window = win
|
|
91
|
+
#@window.wrefresh
|
|
92
|
+
#Ncurses::Panel.update_panels
|
|
93
|
+
#@message_row = row
|
|
94
|
+
@message_row = 0 # 2011-10-8
|
|
95
|
+
end
|
|
96
|
+
#
|
|
97
|
+
# create a window at bottom and show and hide it.
|
|
98
|
+
#
|
|
99
|
+
def _create_footer_window h = 1 , w = Ncurses.COLS, t = Ncurses.LINES-1, l = 0
|
|
100
|
+
ewin = VER::Window.new(h, w , t, l)
|
|
101
|
+
#ewin.bkgd(Ncurses.COLOR_PAIR($promptcolor));
|
|
102
|
+
@window = ewin
|
|
103
|
+
return ewin
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
class QuestionError < StandardError
|
|
107
|
+
# do nothing, just creating a unique error type
|
|
108
|
+
end
|
|
109
|
+
class Question
|
|
110
|
+
# An internal HighLine error. User code does not need to trap this.
|
|
111
|
+
class NoAutoCompleteMatch < StandardError
|
|
112
|
+
# do nothing, just creating a unique error type
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
#
|
|
116
|
+
# Create an instance of HighLine::Question. Expects a _question_ to ask
|
|
117
|
+
# (can be <tt>""</tt>) and an _answer_type_ to convert the answer to.
|
|
118
|
+
# The _answer_type_ parameter must be a type recognized by
|
|
119
|
+
# Question.convert(). If given, a block is yeilded the new Question
|
|
120
|
+
# object to allow custom initializaion.
|
|
121
|
+
#
|
|
122
|
+
def initialize( question, answer_type )
|
|
123
|
+
# initialize instance data
|
|
124
|
+
@question = question
|
|
125
|
+
@answer_type = answer_type
|
|
126
|
+
|
|
127
|
+
@character = nil
|
|
128
|
+
@limit = nil
|
|
129
|
+
@echo = true
|
|
130
|
+
@readline = false
|
|
131
|
+
@whitespace = :strip
|
|
132
|
+
@_case = nil
|
|
133
|
+
@default = nil
|
|
134
|
+
@validate = nil
|
|
135
|
+
@above = nil
|
|
136
|
+
@below = nil
|
|
137
|
+
@in = nil
|
|
138
|
+
@confirm = nil
|
|
139
|
+
@gather = false
|
|
140
|
+
@first_answer = nil
|
|
141
|
+
@directory = Pathname.new(File.expand_path(File.dirname($0)))
|
|
142
|
+
@glob = "*"
|
|
143
|
+
@responses = Hash.new
|
|
144
|
+
@overwrite = false
|
|
145
|
+
@history = nil
|
|
146
|
+
|
|
147
|
+
# allow block to override settings
|
|
148
|
+
yield self if block_given?
|
|
149
|
+
|
|
150
|
+
#$log.debug " XXX default #{@default}" if $log.debug?
|
|
151
|
+
#$log.debug " XXX history #{@history}" if $log.debug?
|
|
152
|
+
|
|
153
|
+
# finalize responses based on settings
|
|
154
|
+
build_responses
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# The ERb template of the question to be asked.
|
|
158
|
+
attr_accessor :question
|
|
159
|
+
# The type that will be used to convert this answer.
|
|
160
|
+
attr_accessor :answer_type
|
|
161
|
+
#
|
|
162
|
+
# Can be set to +true+ to use HighLine's cross-platform character reader
|
|
163
|
+
# instead of fetching an entire line of input. (Note: HighLine's character
|
|
164
|
+
# reader *ONLY* supports STDIN on Windows and Unix.) Can also be set to
|
|
165
|
+
# <tt>:getc</tt> to use that method on the input stream.
|
|
166
|
+
#
|
|
167
|
+
# *WARNING*: The _echo_ and _overwrite_ attributes for a question are
|
|
168
|
+
# ignored when using the <tt>:getc</tt> method.
|
|
169
|
+
#
|
|
170
|
+
attr_accessor :character
|
|
171
|
+
#
|
|
172
|
+
# Allows you to set a character limit for input.
|
|
173
|
+
#
|
|
174
|
+
# If not set, a default of 100 is used
|
|
175
|
+
#
|
|
176
|
+
attr_accessor :limit
|
|
177
|
+
#
|
|
178
|
+
# Can be set to +true+ or +false+ to control whether or not input will
|
|
179
|
+
# be echoed back to the user. A setting of +true+ will cause echo to
|
|
180
|
+
# match input, but any other true value will be treated as to String to
|
|
181
|
+
# echo for each character typed.
|
|
182
|
+
#
|
|
183
|
+
# This requires HighLine's character reader. See the _character_
|
|
184
|
+
# attribute for details.
|
|
185
|
+
#
|
|
186
|
+
# *Note*: When using HighLine to manage echo on Unix based systems, we
|
|
187
|
+
# recommend installing the termios gem. Without it, it's possible to type
|
|
188
|
+
# fast enough to have letters still show up (when reading character by
|
|
189
|
+
# character only).
|
|
190
|
+
#
|
|
191
|
+
attr_accessor :echo
|
|
192
|
+
#
|
|
193
|
+
# Use the Readline library to fetch input. This allows input editing as
|
|
194
|
+
# well as keeping a history. In addition, tab will auto-complete
|
|
195
|
+
# within an Array of choices or a file listing.
|
|
196
|
+
#
|
|
197
|
+
# *WARNING*: This option is incompatible with all of HighLine's
|
|
198
|
+
# character reading modes and it causes HighLine to ignore the
|
|
199
|
+
# specified _input_ stream.
|
|
200
|
+
#
|
|
201
|
+
# this messes up in ncurses RK 2010-10-24 12:23
|
|
202
|
+
attr_accessor :readline
|
|
203
|
+
#
|
|
204
|
+
# Used to control whitespace processing for the answer to this question.
|
|
205
|
+
# See HighLine::Question.remove_whitespace() for acceptable settings.
|
|
206
|
+
#
|
|
207
|
+
attr_accessor :whitespace
|
|
208
|
+
#
|
|
209
|
+
# Used to control character case processing for the answer to this question.
|
|
210
|
+
# See HighLine::Question.change_case() for acceptable settings.
|
|
211
|
+
#
|
|
212
|
+
attr_accessor :_case
|
|
213
|
+
# Used to provide a default answer to this question.
|
|
214
|
+
attr_accessor :default
|
|
215
|
+
#
|
|
216
|
+
# If set to a Regexp, the answer must match (before type conversion).
|
|
217
|
+
# Can also be set to a Proc which will be called with the provided
|
|
218
|
+
# answer to validate with a +true+ or +false+ return.
|
|
219
|
+
#
|
|
220
|
+
attr_accessor :validate
|
|
221
|
+
# Used to control range checks for answer.
|
|
222
|
+
attr_accessor :above, :below
|
|
223
|
+
# If set, answer must pass an include?() check on this object.
|
|
224
|
+
attr_accessor :in
|
|
225
|
+
#
|
|
226
|
+
# Asks a yes or no confirmation question, to ensure a user knows what
|
|
227
|
+
# they have just agreed to. If set to +true+ the question will be,
|
|
228
|
+
# "Are you sure? " Any other true value for this attribute is assumed
|
|
229
|
+
# to be the question to ask. When +false+ or +nil+ (the default),
|
|
230
|
+
# answers are not confirmed.
|
|
231
|
+
#
|
|
232
|
+
attr_accessor :confirm
|
|
233
|
+
#
|
|
234
|
+
# When set, the user will be prompted for multiple answers which will
|
|
235
|
+
# be collected into an Array or Hash and returned as the final answer.
|
|
236
|
+
#
|
|
237
|
+
# You can set _gather_ to an Integer to have an Array of exactly that
|
|
238
|
+
# many answers collected, or a String/Regexp to match an end input which
|
|
239
|
+
# will not be returned in the Array.
|
|
240
|
+
#
|
|
241
|
+
# Optionally _gather_ can be set to a Hash. In this case, the question
|
|
242
|
+
# will be asked once for each key and the answers will be returned in a
|
|
243
|
+
# Hash, mapped by key. The <tt>@key</tt> variable is set before each
|
|
244
|
+
# question is evaluated, so you can use it in your question.
|
|
245
|
+
#
|
|
246
|
+
attr_accessor :gather
|
|
247
|
+
#
|
|
248
|
+
# When set to a non *nil* value, this will be tried as an answer to the
|
|
249
|
+
# question. If this answer passes validations, it will become the result
|
|
250
|
+
# without the user ever being prompted. Otherwise this value is discarded,
|
|
251
|
+
# and this Question is resolved as a normal call to HighLine.ask().
|
|
252
|
+
#
|
|
253
|
+
attr_writer :first_answer
|
|
254
|
+
#
|
|
255
|
+
# The directory from which a user will be allowed to select files, when
|
|
256
|
+
# File or Pathname is specified as an _answer_type_. Initially set to
|
|
257
|
+
# <tt>Pathname.new(File.expand_path(File.dirname($0)))</tt>.
|
|
258
|
+
#
|
|
259
|
+
attr_accessor :directory
|
|
260
|
+
#
|
|
261
|
+
# The glob pattern used to limit file selection when File or Pathname is
|
|
262
|
+
# specified as an _answer_type_. Initially set to <tt>"*"</tt>.
|
|
263
|
+
#
|
|
264
|
+
attr_accessor :glob
|
|
265
|
+
#
|
|
266
|
+
# A Hash that stores the various responses used by HighLine to notify
|
|
267
|
+
# the user. The currently used responses and their purpose are as
|
|
268
|
+
# follows:
|
|
269
|
+
#
|
|
270
|
+
# <tt>:ambiguous_completion</tt>:: Used to notify the user of an
|
|
271
|
+
# ambiguous answer the auto-completion
|
|
272
|
+
# system cannot resolve.
|
|
273
|
+
# <tt>:ask_on_error</tt>:: This is the question that will be
|
|
274
|
+
# redisplayed to the user in the event
|
|
275
|
+
# of an error. Can be set to
|
|
276
|
+
# <tt>:question</tt> to repeat the
|
|
277
|
+
# original question.
|
|
278
|
+
# <tt>:invalid_type</tt>:: The error message shown when a type
|
|
279
|
+
# conversion fails.
|
|
280
|
+
# <tt>:no_completion</tt>:: Used to notify the user that their
|
|
281
|
+
# selection does not have a valid
|
|
282
|
+
# auto-completion match.
|
|
283
|
+
# <tt>:not_in_range</tt>:: Used to notify the user that a
|
|
284
|
+
# provided answer did not satisfy
|
|
285
|
+
# the range requirement tests.
|
|
286
|
+
# <tt>:not_valid</tt>:: The error message shown when
|
|
287
|
+
# validation checks fail.
|
|
288
|
+
#
|
|
289
|
+
attr_reader :responses
|
|
290
|
+
#
|
|
291
|
+
# When set to +true+ the question is asked, but output does not progress to
|
|
292
|
+
# the next line. The Cursor is moved back to the beginning of the question
|
|
293
|
+
# line and it is cleared so that all the contents of the line disappear from
|
|
294
|
+
# the screen.
|
|
295
|
+
#
|
|
296
|
+
attr_accessor :overwrite
|
|
297
|
+
|
|
298
|
+
#
|
|
299
|
+
# If the user presses tab in ask(), then this proc is used to fill in
|
|
300
|
+
# values. Typically, for files. e.g.
|
|
301
|
+
#
|
|
302
|
+
# q.completion_proc = Proc.new {|str| Dir.glob(str +"*") }
|
|
303
|
+
#
|
|
304
|
+
attr_accessor :completion_proc
|
|
305
|
+
|
|
306
|
+
#
|
|
307
|
+
# Called when any character is pressed with the string.
|
|
308
|
+
#
|
|
309
|
+
# q.change_proc = Proc.new {|str| Dir.glob(str +"*") }
|
|
310
|
+
#
|
|
311
|
+
attr_accessor :change_proc
|
|
312
|
+
#
|
|
313
|
+
# Called when any control-key is pressed, one that we are not handling
|
|
314
|
+
#
|
|
315
|
+
# q.key_handler_proc = Proc.new {|ch| xxxx) }
|
|
316
|
+
#
|
|
317
|
+
attr_accessor :key_handler_proc
|
|
318
|
+
|
|
319
|
+
#
|
|
320
|
+
# text to be shown if user presses M-h
|
|
321
|
+
#
|
|
322
|
+
attr_accessor :help_text
|
|
323
|
+
attr_accessor :color_pair
|
|
324
|
+
attr_accessor :history
|
|
325
|
+
|
|
326
|
+
#
|
|
327
|
+
# Returns the provided _answer_string_ or the default answer for this
|
|
328
|
+
# Question if a default was set and the answer is empty.
|
|
329
|
+
# NOTE: in our case, the user actually edits this value (in highline it
|
|
330
|
+
# is used if user enters blank)
|
|
331
|
+
#
|
|
332
|
+
def answer_or_default( answer_string )
|
|
333
|
+
if answer_string.length == 0 and not @default.nil?
|
|
334
|
+
@default
|
|
335
|
+
else
|
|
336
|
+
answer_string
|
|
337
|
+
end
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
#
|
|
341
|
+
# Called late in the initialization process to build intelligent
|
|
342
|
+
# responses based on the details of this Question object.
|
|
343
|
+
#
|
|
344
|
+
def build_responses( )
|
|
345
|
+
### WARNING: This code is quasi-duplicated in ###
|
|
346
|
+
### Menu.update_responses(). Check there too when ###
|
|
347
|
+
### making changes! ###
|
|
348
|
+
append_default unless default.nil?
|
|
349
|
+
@responses = { :ambiguous_completion =>
|
|
350
|
+
"Ambiguous choice. " +
|
|
351
|
+
"Please choose one of #{@answer_type.inspect}.",
|
|
352
|
+
:ask_on_error =>
|
|
353
|
+
"? ",
|
|
354
|
+
:invalid_type =>
|
|
355
|
+
"You must enter a valid #{@answer_type}.",
|
|
356
|
+
:no_completion =>
|
|
357
|
+
"You must choose one of " +
|
|
358
|
+
"#{@answer_type.inspect}.",
|
|
359
|
+
:not_in_range =>
|
|
360
|
+
"Your answer isn't within the expected range " +
|
|
361
|
+
"(#{expected_range}).",
|
|
362
|
+
:not_valid =>
|
|
363
|
+
"Your answer isn't valid (must match " +
|
|
364
|
+
"#{@validate.inspect})." }.merge(@responses)
|
|
365
|
+
### WARNING: This code is quasi-duplicated in ###
|
|
366
|
+
### Menu.update_responses(). Check there too when ###
|
|
367
|
+
### making changes! ###
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
#
|
|
371
|
+
# Returns the provided _answer_string_ after changing character case by
|
|
372
|
+
# the rules of this Question. Valid settings for whitespace are:
|
|
373
|
+
#
|
|
374
|
+
# +nil+:: Do not alter character case.
|
|
375
|
+
# (Default.)
|
|
376
|
+
# <tt>:up</tt>:: Calls upcase().
|
|
377
|
+
# <tt>:upcase</tt>:: Calls upcase().
|
|
378
|
+
# <tt>:down</tt>:: Calls downcase().
|
|
379
|
+
# <tt>:downcase</tt>:: Calls downcase().
|
|
380
|
+
# <tt>:capitalize</tt>:: Calls capitalize().
|
|
381
|
+
#
|
|
382
|
+
# An unrecognized choice (like <tt>:none</tt>) is treated as +nil+.
|
|
383
|
+
#
|
|
384
|
+
def change_case( answer_string )
|
|
385
|
+
if [:up, :upcase].include?(@_case)
|
|
386
|
+
answer_string.upcase
|
|
387
|
+
elsif [:down, :downcase].include?(@_case)
|
|
388
|
+
answer_string.downcase
|
|
389
|
+
elsif @_case == :capitalize
|
|
390
|
+
answer_string.capitalize
|
|
391
|
+
else
|
|
392
|
+
answer_string
|
|
393
|
+
end
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
#
|
|
397
|
+
# Transforms the given _answer_string_ into the expected type for this
|
|
398
|
+
# Question. Currently supported conversions are:
|
|
399
|
+
#
|
|
400
|
+
# <tt>[...]</tt>:: Answer must be a member of the passed Array.
|
|
401
|
+
# Auto-completion is used to expand partial
|
|
402
|
+
# answers.
|
|
403
|
+
# <tt>lambda {...}</tt>:: Answer is passed to lambda for conversion.
|
|
404
|
+
# Date:: Date.parse() is called with answer.
|
|
405
|
+
# DateTime:: DateTime.parse() is called with answer.
|
|
406
|
+
# File:: The entered file name is auto-completed in
|
|
407
|
+
# terms of _directory_ + _glob_, opened, and
|
|
408
|
+
# returned.
|
|
409
|
+
# Float:: Answer is converted with Kernel.Float().
|
|
410
|
+
# Integer:: Answer is converted with Kernel.Integer().
|
|
411
|
+
# +nil+:: Answer is left in String format. (Default.)
|
|
412
|
+
# Pathname:: Same as File, save that a Pathname object is
|
|
413
|
+
# returned.
|
|
414
|
+
# String:: Answer is converted with Kernel.String().
|
|
415
|
+
# Regexp:: Answer is fed to Regexp.new().
|
|
416
|
+
# Symbol:: The method to_sym() is called on answer and
|
|
417
|
+
# the result returned.
|
|
418
|
+
# <i>any other Class</i>:: The answer is passed on to
|
|
419
|
+
# <tt>Class.parse()</tt>.
|
|
420
|
+
#
|
|
421
|
+
# This method throws ArgumentError, if the conversion cannot be
|
|
422
|
+
# completed for any reason.
|
|
423
|
+
#
|
|
424
|
+
def convert( answer_string )
|
|
425
|
+
if @answer_type.nil?
|
|
426
|
+
answer_string
|
|
427
|
+
elsif [Float, Integer, String].include?(@answer_type)
|
|
428
|
+
Kernel.send(@answer_type.to_s.to_sym, answer_string)
|
|
429
|
+
elsif @answer_type == Symbol
|
|
430
|
+
answer_string.to_sym
|
|
431
|
+
elsif @answer_type == Regexp
|
|
432
|
+
Regexp.new(answer_string)
|
|
433
|
+
elsif @answer_type.is_a?(Array) or [File, Pathname].include?(@answer_type)
|
|
434
|
+
# cheating, using OptionParser's Completion module
|
|
435
|
+
choices = selection
|
|
436
|
+
#choices.extend(OptionParser::Completion)
|
|
437
|
+
#answer = choices.complete(answer_string)
|
|
438
|
+
answer = choices # bug in completion of optparse
|
|
439
|
+
if answer.nil?
|
|
440
|
+
raise NoAutoCompleteMatch
|
|
441
|
+
end
|
|
442
|
+
if @answer_type.is_a?(Array)
|
|
443
|
+
#answer.last # we don't need this anylonger
|
|
444
|
+
answer_string # we have already selected
|
|
445
|
+
elsif @answer_type == File
|
|
446
|
+
File.open(File.join(@directory.to_s, answer_string))
|
|
447
|
+
else
|
|
448
|
+
#Pathname.new(File.join(@directory.to_s, answer.last))
|
|
449
|
+
Pathname.new(File.join(@directory.to_s, answer_string))
|
|
450
|
+
end
|
|
451
|
+
elsif [Date, DateTime].include?(@answer_type) or @answer_type.is_a?(Class)
|
|
452
|
+
@answer_type.parse(answer_string)
|
|
453
|
+
elsif @answer_type.is_a?(Proc)
|
|
454
|
+
@answer_type[answer_string]
|
|
455
|
+
end
|
|
456
|
+
end
|
|
457
|
+
|
|
458
|
+
# Returns a english explination of the current range settings.
|
|
459
|
+
def expected_range( )
|
|
460
|
+
expected = [ ]
|
|
461
|
+
|
|
462
|
+
expected << "above #{@above}" unless @above.nil?
|
|
463
|
+
expected << "below #{@below}" unless @below.nil?
|
|
464
|
+
expected << "included in #{@in.inspect}" unless @in.nil?
|
|
465
|
+
|
|
466
|
+
case expected.size
|
|
467
|
+
when 0 then ""
|
|
468
|
+
when 1 then expected.first
|
|
469
|
+
when 2 then expected.join(" and ")
|
|
470
|
+
else expected[0..-2].join(", ") + ", and #{expected.last}"
|
|
471
|
+
end
|
|
472
|
+
end
|
|
473
|
+
|
|
474
|
+
# Returns _first_answer_, which will be unset following this call.
|
|
475
|
+
def first_answer( )
|
|
476
|
+
@first_answer
|
|
477
|
+
ensure
|
|
478
|
+
@first_answer = nil
|
|
479
|
+
end
|
|
480
|
+
|
|
481
|
+
# Returns true if _first_answer_ is set.
|
|
482
|
+
def first_answer?( )
|
|
483
|
+
not @first_answer.nil?
|
|
484
|
+
end
|
|
485
|
+
|
|
486
|
+
#
|
|
487
|
+
# Returns +true+ if the _answer_object_ is greater than the _above_
|
|
488
|
+
# attribute, less than the _below_ attribute and included?()ed in the
|
|
489
|
+
# _in_ attribute. Otherwise, +false+ is returned. Any +nil+ attributes
|
|
490
|
+
# are not checked.
|
|
491
|
+
#
|
|
492
|
+
def in_range?( answer_object )
|
|
493
|
+
(@above.nil? or answer_object > @above) and
|
|
494
|
+
(@below.nil? or answer_object < @below) and
|
|
495
|
+
(@in.nil? or @in.include?(answer_object))
|
|
496
|
+
end
|
|
497
|
+
|
|
498
|
+
#
|
|
499
|
+
# Returns the provided _answer_string_ after processing whitespace by
|
|
500
|
+
# the rules of this Question. Valid settings for whitespace are:
|
|
501
|
+
#
|
|
502
|
+
# +nil+:: Do not alter whitespace.
|
|
503
|
+
# <tt>:strip</tt>:: Calls strip(). (Default.)
|
|
504
|
+
# <tt>:chomp</tt>:: Calls chomp().
|
|
505
|
+
# <tt>:collapse</tt>:: Collapses all whitspace runs to a
|
|
506
|
+
# single space.
|
|
507
|
+
# <tt>:strip_and_collapse</tt>:: Calls strip(), then collapses all
|
|
508
|
+
# whitspace runs to a single space.
|
|
509
|
+
# <tt>:chomp_and_collapse</tt>:: Calls chomp(), then collapses all
|
|
510
|
+
# whitspace runs to a single space.
|
|
511
|
+
# <tt>:remove</tt>:: Removes all whitespace.
|
|
512
|
+
#
|
|
513
|
+
# An unrecognized choice (like <tt>:none</tt>) is treated as +nil+.
|
|
514
|
+
#
|
|
515
|
+
# This process is skipped, for single character input.
|
|
516
|
+
#
|
|
517
|
+
def remove_whitespace( answer_string )
|
|
518
|
+
if @whitespace.nil?
|
|
519
|
+
answer_string
|
|
520
|
+
elsif [:strip, :chomp].include?(@whitespace)
|
|
521
|
+
answer_string.send(@whitespace)
|
|
522
|
+
elsif @whitespace == :collapse
|
|
523
|
+
answer_string.gsub(/\s+/, " ")
|
|
524
|
+
elsif [:strip_and_collapse, :chomp_and_collapse].include?(@whitespace)
|
|
525
|
+
result = answer_string.send(@whitespace.to_s[/^[a-z]+/])
|
|
526
|
+
result.gsub(/\s+/, " ")
|
|
527
|
+
elsif @whitespace == :remove
|
|
528
|
+
answer_string.gsub(/\s+/, "")
|
|
529
|
+
else
|
|
530
|
+
answer_string
|
|
531
|
+
end
|
|
532
|
+
end
|
|
533
|
+
|
|
534
|
+
#
|
|
535
|
+
# Returns an Array of valid answers to this question. These answers are
|
|
536
|
+
# only known when _answer_type_ is set to an Array of choices, File, or
|
|
537
|
+
# Pathname. Any other time, this method will return an empty Array.
|
|
538
|
+
#
|
|
539
|
+
def selection( )
|
|
540
|
+
if @answer_type.is_a?(Array)
|
|
541
|
+
@answer_type
|
|
542
|
+
elsif [File, Pathname].include?(@answer_type)
|
|
543
|
+
Dir[File.join(@directory.to_s, @glob)].map do |file|
|
|
544
|
+
File.basename(file)
|
|
545
|
+
end
|
|
546
|
+
else
|
|
547
|
+
[ ]
|
|
548
|
+
end
|
|
549
|
+
end
|
|
550
|
+
|
|
551
|
+
# Stringifies the question to be asked.
|
|
552
|
+
def to_str( )
|
|
553
|
+
@question
|
|
554
|
+
end
|
|
555
|
+
|
|
556
|
+
#
|
|
557
|
+
# Returns +true+ if the provided _answer_string_ is accepted by the
|
|
558
|
+
# _validate_ attribute or +false+ if it's not.
|
|
559
|
+
#
|
|
560
|
+
# It's important to realize that an answer is validated after whitespace
|
|
561
|
+
# and case handling.
|
|
562
|
+
#
|
|
563
|
+
def valid_answer?( answer_string )
|
|
564
|
+
@validate.nil? or
|
|
565
|
+
(@validate.is_a?(Regexp) and answer_string =~ @validate) or
|
|
566
|
+
(@validate.is_a?(Proc) and @validate[answer_string])
|
|
567
|
+
end
|
|
568
|
+
|
|
569
|
+
private
|
|
570
|
+
|
|
571
|
+
#
|
|
572
|
+
# Adds the default choice to the end of question between <tt>|...|</tt>.
|
|
573
|
+
# Trailing whitespace is preserved so the function of HighLine.say() is
|
|
574
|
+
# not affected.
|
|
575
|
+
#
|
|
576
|
+
def append_default( )
|
|
577
|
+
if @question =~ /([\t ]+)\Z/
|
|
578
|
+
@question << "|#{@default}|#{$1}"
|
|
579
|
+
elsif @question == ""
|
|
580
|
+
@question << "|#{@default}| "
|
|
581
|
+
elsif @question[-1, 1] == "\n"
|
|
582
|
+
@question[-2, 0] = " |#{@default}|"
|
|
583
|
+
else
|
|
584
|
+
@question << " |#{@default}|"
|
|
585
|
+
end
|
|
586
|
+
end
|
|
587
|
+
end # class
|
|
588
|
+
|
|
589
|
+
# Menu objects encapsulate all the details of a call to HighLine.choose().
|
|
590
|
+
# Using the accessors and Menu.choice() and Menu.choices(), the block passed
|
|
591
|
+
# to HighLine.choose() can detail all aspects of menu display and control.
|
|
592
|
+
#
|
|
593
|
+
class Menu < Question
|
|
594
|
+
#
|
|
595
|
+
# Create an instance of HighLine::Menu. All customization is done
|
|
596
|
+
# through the passed block, which should call accessors and choice() and
|
|
597
|
+
# choices() as needed to define the Menu. Note that Menus are also
|
|
598
|
+
# Questions, so all that functionality is available to the block as
|
|
599
|
+
# well.
|
|
600
|
+
#
|
|
601
|
+
def initialize( )
|
|
602
|
+
#
|
|
603
|
+
# Initialize Question objects with ignored values, we'll
|
|
604
|
+
# adjust ours as needed.
|
|
605
|
+
#
|
|
606
|
+
super("Ignored", [ ], &nil) # avoiding passing the block along
|
|
607
|
+
|
|
608
|
+
@items = [ ]
|
|
609
|
+
@hidden_items = [ ]
|
|
610
|
+
@help = Hash.new("There's no help for that topic.")
|
|
611
|
+
|
|
612
|
+
@index = :number
|
|
613
|
+
@index_suffix = ". "
|
|
614
|
+
@select_by = :index_or_name
|
|
615
|
+
@flow = :rows
|
|
616
|
+
@list_option = nil
|
|
617
|
+
@header = nil
|
|
618
|
+
@prompt = "? "
|
|
619
|
+
@layout = :list
|
|
620
|
+
@shell = false
|
|
621
|
+
@nil_on_handled = false
|
|
622
|
+
|
|
623
|
+
# Override Questions responses, we'll set our own.
|
|
624
|
+
@responses = { }
|
|
625
|
+
# Context for action code.
|
|
626
|
+
@highline = nil
|
|
627
|
+
|
|
628
|
+
yield self if block_given?
|
|
629
|
+
|
|
630
|
+
init_help if @shell and not @help.empty?
|
|
631
|
+
end
|
|
632
|
+
|
|
633
|
+
#
|
|
634
|
+
# An _index_ to append to each menu item in display. See
|
|
635
|
+
# Menu.index=() for details.
|
|
636
|
+
#
|
|
637
|
+
attr_reader :index
|
|
638
|
+
#
|
|
639
|
+
# The String placed between an _index_ and a menu item. Defaults to
|
|
640
|
+
# ". ". Switches to " ", when _index_ is set to a String (like "-").
|
|
641
|
+
#
|
|
642
|
+
attr_accessor :index_suffix
|
|
643
|
+
#
|
|
644
|
+
# The _select_by_ attribute controls how the user is allowed to pick a
|
|
645
|
+
# menu item. The available choices are:
|
|
646
|
+
#
|
|
647
|
+
# <tt>:index</tt>:: The user is allowed to type the numerical
|
|
648
|
+
# or alphetical index for their selection.
|
|
649
|
+
# <tt>:index_or_name</tt>:: Allows both methods from the
|
|
650
|
+
# <tt>:index</tt> option and the
|
|
651
|
+
# <tt>:name</tt> option.
|
|
652
|
+
# <tt>:name</tt>:: Menu items are selected by typing a portion
|
|
653
|
+
# of the item name that will be
|
|
654
|
+
# auto-completed.
|
|
655
|
+
#
|
|
656
|
+
attr_accessor :select_by
|
|
657
|
+
#
|
|
658
|
+
# This attribute is passed directly on as the mode to HighLine.list() by
|
|
659
|
+
# all the preset layouts. See that method for appropriate settings.
|
|
660
|
+
#
|
|
661
|
+
attr_accessor :flow
|
|
662
|
+
#
|
|
663
|
+
# This setting is passed on as the third parameter to HighLine.list()
|
|
664
|
+
# by all the preset layouts. See that method for details of its
|
|
665
|
+
# effects. Defaults to +nil+.
|
|
666
|
+
#
|
|
667
|
+
attr_accessor :list_option
|
|
668
|
+
#
|
|
669
|
+
# Used by all the preset layouts to display title and/or introductory
|
|
670
|
+
# information, when set. Defaults to +nil+.
|
|
671
|
+
#
|
|
672
|
+
attr_accessor :header
|
|
673
|
+
#
|
|
674
|
+
# Used by all the preset layouts to ask the actual question to fetch a
|
|
675
|
+
# menu selection from the user. Defaults to "? ".
|
|
676
|
+
#
|
|
677
|
+
attr_accessor :prompt
|
|
678
|
+
#
|
|
679
|
+
# An ERb _layout_ to use when displaying this Menu object. See
|
|
680
|
+
# Menu.layout=() for details.
|
|
681
|
+
#
|
|
682
|
+
attr_reader :layout
|
|
683
|
+
#
|
|
684
|
+
# When set to +true+, responses are allowed to be an entire line of
|
|
685
|
+
# input, including details beyond the command itself. Only the first
|
|
686
|
+
# "word" of input will be matched against the menu choices, but both the
|
|
687
|
+
# command selected and the rest of the line will be passed to provided
|
|
688
|
+
# action blocks. Defaults to +false+.
|
|
689
|
+
#
|
|
690
|
+
attr_accessor :shell
|
|
691
|
+
#
|
|
692
|
+
# When +true+, any selected item handled by provided action code, will
|
|
693
|
+
# return +nil+, instead of the results to the action code. This may
|
|
694
|
+
# prove handy when dealing with mixed menus where only the names of
|
|
695
|
+
# items without any code (and +nil+, of course) will be returned.
|
|
696
|
+
# Defaults to +false+.
|
|
697
|
+
#
|
|
698
|
+
attr_accessor :nil_on_handled
|
|
699
|
+
|
|
700
|
+
#
|
|
701
|
+
# Adds _name_ to the list of available menu items. Menu items will be
|
|
702
|
+
# displayed in the order they are added.
|
|
703
|
+
#
|
|
704
|
+
# An optional _action_ can be associated with this name and if provided,
|
|
705
|
+
# it will be called if the item is selected. The result of the method
|
|
706
|
+
# will be returned, unless _nil_on_handled_ is set (when you would get
|
|
707
|
+
# +nil+ instead). In _shell_ mode, a provided block will be passed the
|
|
708
|
+
# command chosen and any details that followed the command. Otherwise,
|
|
709
|
+
# just the command is passed. The <tt>@highline</tt> variable is set to
|
|
710
|
+
# the current HighLine context before the action code is called and can
|
|
711
|
+
# thus be used for adding output and the like.
|
|
712
|
+
#
|
|
713
|
+
def choice( name, help = nil, &action )
|
|
714
|
+
@items << [name, action]
|
|
715
|
+
|
|
716
|
+
@help[name.to_s.downcase] = help unless help.nil?
|
|
717
|
+
update_responses # rebuild responses based on our settings
|
|
718
|
+
end
|
|
719
|
+
|
|
720
|
+
#
|
|
721
|
+
# A shortcut for multiple calls to the sister method choice(). <b>Be
|
|
722
|
+
# warned:</b> An _action_ set here will apply to *all* provided
|
|
723
|
+
# _names_. This is considered to be a feature, so you can easily
|
|
724
|
+
# hand-off interface processing to a different chunk of code.
|
|
725
|
+
#
|
|
726
|
+
def choices( *names, &action )
|
|
727
|
+
names.each { |n| choice(n, &action) }
|
|
728
|
+
end
|
|
729
|
+
|
|
730
|
+
# Identical to choice(), but the item will not be listed for the user.
|
|
731
|
+
def hidden( name, help = nil, &action )
|
|
732
|
+
@hidden_items << [name, action]
|
|
733
|
+
|
|
734
|
+
@help[name.to_s.downcase] = help unless help.nil?
|
|
735
|
+
end
|
|
736
|
+
|
|
737
|
+
#
|
|
738
|
+
# Sets the indexing style for this Menu object. Indexes are appended to
|
|
739
|
+
# menu items, when displayed in list form. The available settings are:
|
|
740
|
+
#
|
|
741
|
+
# <tt>:number</tt>:: Menu items will be indexed numerically, starting
|
|
742
|
+
# with 1. This is the default method of indexing.
|
|
743
|
+
# <tt>:letter</tt>:: Items will be indexed alphabetically, starting
|
|
744
|
+
# with a.
|
|
745
|
+
# <tt>:none</tt>:: No index will be appended to menu items.
|
|
746
|
+
# <i>any String</i>:: Will be used as the literal _index_.
|
|
747
|
+
#
|
|
748
|
+
# Setting the _index_ to <tt>:none</tt> a literal String, also adjusts
|
|
749
|
+
# _index_suffix_ to a single space and _select_by_ to <tt>:none</tt>.
|
|
750
|
+
# Because of this, you should make a habit of setting the _index_ first.
|
|
751
|
+
#
|
|
752
|
+
def index=( style )
|
|
753
|
+
@index = style
|
|
754
|
+
|
|
755
|
+
# Default settings.
|
|
756
|
+
if @index == :none or @index.is_a?(String)
|
|
757
|
+
@index_suffix = " "
|
|
758
|
+
@select_by = :name
|
|
759
|
+
end
|
|
760
|
+
end
|
|
761
|
+
|
|
762
|
+
#
|
|
763
|
+
# Initializes the help system by adding a <tt>:help</tt> choice, some
|
|
764
|
+
# action code, and the default help listing.
|
|
765
|
+
#
|
|
766
|
+
def init_help( )
|
|
767
|
+
return if @items.include?(:help)
|
|
768
|
+
|
|
769
|
+
topics = @help.keys.sort
|
|
770
|
+
help_help = @help.include?("help") ? @help["help"] :
|
|
771
|
+
"This command will display helpful messages about " +
|
|
772
|
+
"functionality, like this one. To see the help for " +
|
|
773
|
+
"a specific topic enter:\n\thelp [TOPIC]\nTry asking " +
|
|
774
|
+
"for help on any of the following:\n\n" +
|
|
775
|
+
"<%= list(#{topics.inspect}, :columns_across) %>"
|
|
776
|
+
choice(:help, help_help) do |command, topic|
|
|
777
|
+
topic.strip!
|
|
778
|
+
topic.downcase!
|
|
779
|
+
if topic.empty?
|
|
780
|
+
@highline.say(@help["help"])
|
|
781
|
+
else
|
|
782
|
+
@highline.say("= #{topic}\n\n#{@help[topic]}")
|
|
783
|
+
end
|
|
784
|
+
end
|
|
785
|
+
end
|
|
786
|
+
|
|
787
|
+
#
|
|
788
|
+
# Used to set help for arbitrary topics. Use the topic <tt>"help"</tt>
|
|
789
|
+
# to override the default message.
|
|
790
|
+
#
|
|
791
|
+
def help( topic, help )
|
|
792
|
+
@help[topic] = help
|
|
793
|
+
end
|
|
794
|
+
|
|
795
|
+
#
|
|
796
|
+
# Setting a _layout_ with this method also adjusts some other attributes
|
|
797
|
+
# of the Menu object, to ideal defaults for the chosen _layout_. To
|
|
798
|
+
# account for that, you probably want to set a _layout_ first in your
|
|
799
|
+
# configuration block, if needed.
|
|
800
|
+
#
|
|
801
|
+
# Accepted settings for _layout_ are:
|
|
802
|
+
#
|
|
803
|
+
# <tt>:list</tt>:: The default _layout_. The _header_ if set
|
|
804
|
+
# will appear at the top on its own line with
|
|
805
|
+
# a trailing colon. Then the list of menu
|
|
806
|
+
# items will follow. Finally, the _prompt_
|
|
807
|
+
# will be used as the ask()-like question.
|
|
808
|
+
# <tt>:one_line</tt>:: A shorter _layout_ that fits on one line.
|
|
809
|
+
# The _header_ comes first followed by a
|
|
810
|
+
# colon and spaces, then the _prompt_ with menu
|
|
811
|
+
# items between trailing parenthesis.
|
|
812
|
+
# <tt>:menu_only</tt>:: Just the menu items, followed up by a likely
|
|
813
|
+
# short _prompt_.
|
|
814
|
+
# <i>any ERb String</i>:: Will be taken as the literal _layout_. This
|
|
815
|
+
# String can access <tt>@header</tt>,
|
|
816
|
+
# <tt>@menu</tt> and <tt>@prompt</tt>, but is
|
|
817
|
+
# otherwise evaluated in the typical HighLine
|
|
818
|
+
# context, to provide access to utilities like
|
|
819
|
+
# HighLine.list() primarily.
|
|
820
|
+
#
|
|
821
|
+
# If set to either <tt>:one_line</tt>, or <tt>:menu_only</tt>, _index_
|
|
822
|
+
# will default to <tt>:none</tt> and _flow_ will default to
|
|
823
|
+
# <tt>:inline</tt>.
|
|
824
|
+
#
|
|
825
|
+
def layout=( new_layout )
|
|
826
|
+
@layout = new_layout
|
|
827
|
+
|
|
828
|
+
# Default settings.
|
|
829
|
+
case @layout
|
|
830
|
+
when :one_line, :menu_only
|
|
831
|
+
self.index = :none
|
|
832
|
+
@flow = :inline
|
|
833
|
+
end
|
|
834
|
+
end
|
|
835
|
+
|
|
836
|
+
#
|
|
837
|
+
# This method returns all possible options for auto-completion, based
|
|
838
|
+
# on the settings of _index_ and _select_by_.
|
|
839
|
+
#
|
|
840
|
+
def options( )
|
|
841
|
+
# add in any hidden menu commands
|
|
842
|
+
@items.concat(@hidden_items)
|
|
843
|
+
|
|
844
|
+
by_index = if @index == :letter
|
|
845
|
+
l_index = "`"
|
|
846
|
+
@items.map { "#{l_index.succ!}" }
|
|
847
|
+
else
|
|
848
|
+
(1 .. @items.size).collect { |s| String(s) }
|
|
849
|
+
end
|
|
850
|
+
by_name = @items.collect { |c| c.first }
|
|
851
|
+
|
|
852
|
+
case @select_by
|
|
853
|
+
when :index then
|
|
854
|
+
by_index
|
|
855
|
+
when :name
|
|
856
|
+
by_name
|
|
857
|
+
else
|
|
858
|
+
by_index + by_name
|
|
859
|
+
end
|
|
860
|
+
ensure
|
|
861
|
+
# make sure the hidden items are removed, before we return
|
|
862
|
+
@items.slice!(@items.size - @hidden_items.size, @hidden_items.size)
|
|
863
|
+
end
|
|
864
|
+
|
|
865
|
+
#
|
|
866
|
+
# This method processes the auto-completed user selection, based on the
|
|
867
|
+
# rules for this Menu object. If an action was provided for the
|
|
868
|
+
# selection, it will be executed as described in Menu.choice().
|
|
869
|
+
#
|
|
870
|
+
def select( highline_context, selection, details = nil )
|
|
871
|
+
# add in any hidden menu commands
|
|
872
|
+
@items.concat(@hidden_items)
|
|
873
|
+
|
|
874
|
+
# Find the selected action.
|
|
875
|
+
name, action = if selection =~ /^\d+$/
|
|
876
|
+
@items[selection.to_i - 1]
|
|
877
|
+
else
|
|
878
|
+
l_index = "`"
|
|
879
|
+
index = @items.map { "#{l_index.succ!}" }.index(selection)
|
|
880
|
+
$log.debug "iindex #{index}, #{@items} " if $log.debug?
|
|
881
|
+
@items.find { |c| c.first == selection } or @items[index]
|
|
882
|
+
end
|
|
883
|
+
|
|
884
|
+
# Run or return it.
|
|
885
|
+
if not @nil_on_handled and not action.nil?
|
|
886
|
+
@highline = highline_context
|
|
887
|
+
if @shell
|
|
888
|
+
action.call(name, details)
|
|
889
|
+
else
|
|
890
|
+
action.call(name)
|
|
891
|
+
end
|
|
892
|
+
elsif action.nil?
|
|
893
|
+
name
|
|
894
|
+
else
|
|
895
|
+
nil
|
|
896
|
+
end
|
|
897
|
+
ensure
|
|
898
|
+
# make sure the hidden items are removed, before we return
|
|
899
|
+
@items.slice!(@items.size - @hidden_items.size, @hidden_items.size)
|
|
900
|
+
end
|
|
901
|
+
|
|
902
|
+
#
|
|
903
|
+
# Allows Menu objects to pass as Arrays, for use with HighLine.list().
|
|
904
|
+
# This method returns all menu items to be displayed, complete with
|
|
905
|
+
# indexes.
|
|
906
|
+
#
|
|
907
|
+
def to_ary( )
|
|
908
|
+
case @index
|
|
909
|
+
when :number
|
|
910
|
+
@items.map { |c| "#{@items.index(c) + 1}#{@index_suffix}#{c.first}" }
|
|
911
|
+
when :letter
|
|
912
|
+
l_index = "`"
|
|
913
|
+
@items.map { |c| "#{l_index.succ!}#{@index_suffix}#{c.first}" }
|
|
914
|
+
when :none
|
|
915
|
+
@items.map { |c| "#{c.first}" }
|
|
916
|
+
else
|
|
917
|
+
@items.map { |c| "#{index}#{@index_suffix}#{c.first}" }
|
|
918
|
+
end
|
|
919
|
+
end
|
|
920
|
+
|
|
921
|
+
#
|
|
922
|
+
# Allows Menu to behave as a String, just like Question. Returns the
|
|
923
|
+
# _layout_ to be rendered, which is used by HighLine.say().
|
|
924
|
+
#
|
|
925
|
+
def to_str( )
|
|
926
|
+
case @layout
|
|
927
|
+
when :list
|
|
928
|
+
'<%= if @header.nil? then '' else "#{@header}:\n" end %>' +
|
|
929
|
+
"<%= list( @menu, #{@flow.inspect},
|
|
930
|
+
#{@list_option.inspect} ) %>" +
|
|
931
|
+
"<%= @prompt %>"
|
|
932
|
+
when :one_line
|
|
933
|
+
'<%= if @header.nil? then '' else "#{@header}: " end %>' +
|
|
934
|
+
"<%= @prompt %>" +
|
|
935
|
+
"(<%= list( @menu, #{@flow.inspect},
|
|
936
|
+
#{@list_option.inspect} ) %>)" +
|
|
937
|
+
"<%= @prompt[/\s*$/] %>"
|
|
938
|
+
when :menu_only
|
|
939
|
+
"<%= list( @menu, #{@flow.inspect},
|
|
940
|
+
#{@list_option.inspect} ) %><%= @prompt %>"
|
|
941
|
+
else
|
|
942
|
+
@layout
|
|
943
|
+
end
|
|
944
|
+
end
|
|
945
|
+
|
|
946
|
+
#
|
|
947
|
+
# This method will update the intelligent responses to account for
|
|
948
|
+
# Menu specific differences. This overrides the work done by
|
|
949
|
+
# Question.build_responses().
|
|
950
|
+
#
|
|
951
|
+
def update_responses( )
|
|
952
|
+
append_default unless default.nil?
|
|
953
|
+
@responses = @responses.merge(
|
|
954
|
+
:ambiguous_completion =>
|
|
955
|
+
"Ambiguous choice. " +
|
|
956
|
+
"Please choose one of #{options.inspect}.",
|
|
957
|
+
:ask_on_error =>
|
|
958
|
+
"? ",
|
|
959
|
+
:invalid_type =>
|
|
960
|
+
"You must enter a valid #{options}.",
|
|
961
|
+
:no_completion =>
|
|
962
|
+
"You must choose one of " +
|
|
963
|
+
"#{options.inspect}.",
|
|
964
|
+
:not_in_range =>
|
|
965
|
+
"Your answer isn't within the expected range " +
|
|
966
|
+
"(#{expected_range}).",
|
|
967
|
+
:not_valid =>
|
|
968
|
+
"Your answer isn't valid (must match " +
|
|
969
|
+
"#{@validate.inspect})."
|
|
970
|
+
)
|
|
971
|
+
end
|
|
972
|
+
end
|
|
973
|
+
def ask(question, answer_type=String, &details)
|
|
974
|
+
$log.debug "XXXX inside ask win #{@window} "
|
|
975
|
+
@window ||= _create_footer_window
|
|
976
|
+
#@window.show #unless @window.visible?
|
|
977
|
+
|
|
978
|
+
@question ||= Question.new(question, answer_type, &details)
|
|
979
|
+
say(@question) #unless @question.echo == true
|
|
980
|
+
|
|
981
|
+
@completion_proc = @question.completion_proc
|
|
982
|
+
@change_proc = @question.change_proc
|
|
983
|
+
@key_handler_proc = @question.key_handler_proc
|
|
984
|
+
@default = @question.default
|
|
985
|
+
$log.debug "XXX: ASK RBGETS got default: #{@default} "
|
|
986
|
+
@help_text = @question.help_text
|
|
987
|
+
@answer_type = @question.answer_type
|
|
988
|
+
if @question.answer_type.is_a? Array
|
|
989
|
+
@completion_proc = Proc.new{|str| @answer_type.dup.grep Regexp.new("^#{str}") }
|
|
990
|
+
end
|
|
991
|
+
|
|
992
|
+
begin
|
|
993
|
+
# FIXME a C-c still returns default to user !
|
|
994
|
+
@answer = @question.answer_or_default(get_response)
|
|
995
|
+
unless @question.valid_answer?(@answer)
|
|
996
|
+
explain_error(:not_valid)
|
|
997
|
+
raise QuestionError
|
|
998
|
+
end
|
|
999
|
+
|
|
1000
|
+
@answer = @question.convert(@answer)
|
|
1001
|
+
|
|
1002
|
+
if @question.in_range?(@answer)
|
|
1003
|
+
if @question.confirm
|
|
1004
|
+
# need to add a layer of scope to ask a question inside a
|
|
1005
|
+
# question, without destroying instance data
|
|
1006
|
+
context_change = self.class.new(@input, @output, @wrap_at, @page_at)
|
|
1007
|
+
if @question.confirm == true
|
|
1008
|
+
confirm_question = "Are you sure? "
|
|
1009
|
+
else
|
|
1010
|
+
# evaluate ERb under initial scope, so it will have
|
|
1011
|
+
# access to @question and @answer
|
|
1012
|
+
template = ERB.new(@question.confirm, nil, "%")
|
|
1013
|
+
confirm_question = template.result(binding)
|
|
1014
|
+
end
|
|
1015
|
+
unless context_change.agree(confirm_question)
|
|
1016
|
+
explain_error(nil)
|
|
1017
|
+
raise QuestionError
|
|
1018
|
+
end
|
|
1019
|
+
end
|
|
1020
|
+
|
|
1021
|
+
@answer
|
|
1022
|
+
else
|
|
1023
|
+
explain_error(:not_in_range)
|
|
1024
|
+
raise QuestionError
|
|
1025
|
+
end
|
|
1026
|
+
rescue QuestionError
|
|
1027
|
+
retry
|
|
1028
|
+
rescue ArgumentError, NameError => error
|
|
1029
|
+
#raise
|
|
1030
|
+
raise if error.is_a?(NoMethodError)
|
|
1031
|
+
if error.message =~ /ambiguous/
|
|
1032
|
+
# the assumption here is that OptionParser::Completion#complete
|
|
1033
|
+
# (used for ambiguity resolution) throws exceptions containing
|
|
1034
|
+
# the word 'ambiguous' whenever resolution fails
|
|
1035
|
+
explain_error(:ambiguous_completion)
|
|
1036
|
+
else
|
|
1037
|
+
explain_error(:invalid_type)
|
|
1038
|
+
end
|
|
1039
|
+
retry
|
|
1040
|
+
rescue Question::NoAutoCompleteMatch
|
|
1041
|
+
explain_error(:no_completion)
|
|
1042
|
+
retry
|
|
1043
|
+
rescue Interrupt
|
|
1044
|
+
$log.warn "User interrupted ask() get_response does not want operation to proceed"
|
|
1045
|
+
return nil
|
|
1046
|
+
ensure
|
|
1047
|
+
@question = nil # Reset Question object.
|
|
1048
|
+
$log.debug "XXX: HIDE B AT ENSURE OF ASK"
|
|
1049
|
+
hide_bottomline # assuming this method made it visible, not sure if this is called.
|
|
1050
|
+
end
|
|
1051
|
+
end
|
|
1052
|
+
#
|
|
1053
|
+
# bottomline user has to hide window if he called say().
|
|
1054
|
+
# Call this if you find the window persists after using some method from here
|
|
1055
|
+
# usually say or ask.
|
|
1056
|
+
#
|
|
1057
|
+
# NOTE: after callign this you must call window.show. Otherwise, next time
|
|
1058
|
+
# you call this, it will not hide.
|
|
1059
|
+
#
|
|
1060
|
+
# @param [int, float] time to sleep before hiding window.
|
|
1061
|
+
#
|
|
1062
|
+
def hide wait=nil
|
|
1063
|
+
if @window
|
|
1064
|
+
$log.debug "XXX: HIDE BOTTOMLINE INSIDE"
|
|
1065
|
+
sleep(wait) if wait
|
|
1066
|
+
#if @window.visible?
|
|
1067
|
+
#@window.hide # THIS HAS SUDDENLY STOPPED WORKING
|
|
1068
|
+
@window.destroy
|
|
1069
|
+
@window = nil
|
|
1070
|
+
#@window.wrefresh
|
|
1071
|
+
#Ncurses::Panel.update_panels
|
|
1072
|
+
#end
|
|
1073
|
+
end
|
|
1074
|
+
end
|
|
1075
|
+
alias :hide_bottomline :hide
|
|
1076
|
+
#
|
|
1077
|
+
# destroy window, to be called by app when shutting down
|
|
1078
|
+
# since we are normally hiding the window only.
|
|
1079
|
+
def destroy
|
|
1080
|
+
$log.debug "bottomline destroy... #{@window} "
|
|
1081
|
+
@window.destroy if @window
|
|
1082
|
+
@window = nil
|
|
1083
|
+
end
|
|
1084
|
+
|
|
1085
|
+
#
|
|
1086
|
+
# The basic output method for HighLine objects.
|
|
1087
|
+
#
|
|
1088
|
+
# The _statement_ parameter is processed as an ERb template, supporting
|
|
1089
|
+
# embedded Ruby code. The template is evaluated with a binding inside
|
|
1090
|
+
# the HighLine instance.
|
|
1091
|
+
# NOTE: modified from original highline, does not care about space at end of
|
|
1092
|
+
# question. Also, ansi color constants will not work. Be careful what ruby code
|
|
1093
|
+
# you pass in.
|
|
1094
|
+
#
|
|
1095
|
+
# NOTE: This uses a window, so it will persist in the last row. You must call
|
|
1096
|
+
# hide_bottomline to remove the window. It is preferable to call say_with_pause
|
|
1097
|
+
# from user programs
|
|
1098
|
+
#
|
|
1099
|
+
def say statement, config={}
|
|
1100
|
+
@window ||= _create_footer_window
|
|
1101
|
+
#@window.show #unless @window.visible?
|
|
1102
|
+
$log.debug "XXX: inside say win #{@window} !"
|
|
1103
|
+
case statement
|
|
1104
|
+
when Question
|
|
1105
|
+
|
|
1106
|
+
if config.has_key? :color_pair
|
|
1107
|
+
$log.debug "INSIDE QUESTION 2 " if $log.debug?
|
|
1108
|
+
else
|
|
1109
|
+
$log.debug "XXXX SAY using colorpair: #{statement.color_pair} " if $log.debug?
|
|
1110
|
+
config[:color_pair] = statement.color_pair
|
|
1111
|
+
end
|
|
1112
|
+
else
|
|
1113
|
+
$log.debug "XXX INSDIE SAY #{statement.class} " if $log.debug?
|
|
1114
|
+
end
|
|
1115
|
+
statement = statement.to_str
|
|
1116
|
+
template = ERB.new(statement, nil, "%")
|
|
1117
|
+
statement = template.result(binding)
|
|
1118
|
+
|
|
1119
|
+
@prompt_length = statement.length # required by ask since it prints after
|
|
1120
|
+
@statement = statement #
|
|
1121
|
+
clear_line
|
|
1122
|
+
print_str statement, config
|
|
1123
|
+
end
|
|
1124
|
+
#
|
|
1125
|
+
# display some text at bottom and wait for a key before hiding window
|
|
1126
|
+
#
|
|
1127
|
+
def say_with_pause statement, config={}
|
|
1128
|
+
@window ||= _create_footer_window
|
|
1129
|
+
#@window.show #unless @window.visible? # 2011-10-14 23:52:52
|
|
1130
|
+
say statement, config
|
|
1131
|
+
@window.wrefresh
|
|
1132
|
+
Ncurses::Panel.update_panels
|
|
1133
|
+
ch=@window.getchar()
|
|
1134
|
+
hide_bottomline
|
|
1135
|
+
## return char so we can use for asking for one character
|
|
1136
|
+
return ch
|
|
1137
|
+
end
|
|
1138
|
+
# since say does not leave the screen, it is not exactly recommended
|
|
1139
|
+
# as it will hide what's below. It's better to call pause, or this, which
|
|
1140
|
+
# will quickly go off. If the message is not important enough to ask for a pause,
|
|
1141
|
+
# the will flicker on screen, but not for too long.
|
|
1142
|
+
def say_with_wait statement, config={}
|
|
1143
|
+
@window ||= _create_footer_window
|
|
1144
|
+
#@window.show #unless @window.visible? # 2011-10-14 23:52:59
|
|
1145
|
+
say statement, config
|
|
1146
|
+
@window.wrefresh
|
|
1147
|
+
Ncurses::Panel.update_panels
|
|
1148
|
+
sleep 0.5
|
|
1149
|
+
hide_bottomline
|
|
1150
|
+
end
|
|
1151
|
+
# A helper method for sending the output stream and error and repeat
|
|
1152
|
+
# of the question.
|
|
1153
|
+
#
|
|
1154
|
+
# FIXME: since we write on one line in say, this often gets overidden
|
|
1155
|
+
# by next say or ask
|
|
1156
|
+
def explain_error( error )
|
|
1157
|
+
say_with_pause(@question.responses[error]) unless error.nil?
|
|
1158
|
+
if @question.responses[:ask_on_error] == :question
|
|
1159
|
+
say(@question)
|
|
1160
|
+
elsif @question.responses[:ask_on_error]
|
|
1161
|
+
say(@question.responses[:ask_on_error])
|
|
1162
|
+
end
|
|
1163
|
+
end
|
|
1164
|
+
|
|
1165
|
+
#
|
|
1166
|
+
# Internal method for printing a string
|
|
1167
|
+
#
|
|
1168
|
+
def print_str(text, config={})
|
|
1169
|
+
win = config.fetch(:window, @window) # assuming its in App
|
|
1170
|
+
x = config.fetch :x, 0 # @message_row # Ncurses.LINES-1, 0 since one line window 2011-10-8
|
|
1171
|
+
y = config.fetch :y, 0
|
|
1172
|
+
$log.debug "XXX: print_str #{win} with text : #{text} at #{x} #{y} "
|
|
1173
|
+
color = config[:color_pair] || $datacolor
|
|
1174
|
+
raise "no window for ask print in #{self.class} name: #{name} " unless win
|
|
1175
|
+
color=Ncurses.COLOR_PAIR(color);
|
|
1176
|
+
win.attron(color);
|
|
1177
|
+
#win.mvprintw(x, y, "%-40s" % text);
|
|
1178
|
+
win.mvprintw(x, y, "%s" % text);
|
|
1179
|
+
win.attroff(color);
|
|
1180
|
+
win.refresh # FFI NW 2011-09-9 , added back gets overwritten
|
|
1181
|
+
end
|
|
1182
|
+
|
|
1183
|
+
# actual input routine, gets each character from user, taking care of echo, limit,
|
|
1184
|
+
# completion proc, and some control characters such as C-a, C-e, C-k
|
|
1185
|
+
# Taken from io.rb, has some improvements to it. However, does not print the prompt
|
|
1186
|
+
# any longer
|
|
1187
|
+
# Completion proc is vim style, on pressing tab it cycles through options
|
|
1188
|
+
def rbgetstr
|
|
1189
|
+
r = @message_row
|
|
1190
|
+
c = 0
|
|
1191
|
+
win = @window
|
|
1192
|
+
@limit = @question.limit
|
|
1193
|
+
@history = @question.history
|
|
1194
|
+
@history_list = History.new(@history)
|
|
1195
|
+
maxlen = @limit || 100 # fixme
|
|
1196
|
+
|
|
1197
|
+
|
|
1198
|
+
raise "rbgetstr got no window. bottomline.rb" if win.nil?
|
|
1199
|
+
ins_mode = false
|
|
1200
|
+
oldstr = nil # for tab completion, origal word entered by user
|
|
1201
|
+
default = @default || ""
|
|
1202
|
+
$log.debug "XXX: RBGETS got default: #{@default} "
|
|
1203
|
+
if @default && @history
|
|
1204
|
+
if !@history.include?(default)
|
|
1205
|
+
@history_list.push default
|
|
1206
|
+
end
|
|
1207
|
+
end
|
|
1208
|
+
|
|
1209
|
+
len = @prompt_length
|
|
1210
|
+
|
|
1211
|
+
# clear the area of len+maxlen
|
|
1212
|
+
color = $datacolor
|
|
1213
|
+
str = ""
|
|
1214
|
+
#str = default
|
|
1215
|
+
cpentries = nil
|
|
1216
|
+
#clear_line len+maxlen+1
|
|
1217
|
+
#print_str(prompt+str)
|
|
1218
|
+
print_str(str, :y => @prompt_length+0) if @default
|
|
1219
|
+
len = @prompt_length + str.length
|
|
1220
|
+
begin
|
|
1221
|
+
Ncurses.noecho();
|
|
1222
|
+
curpos = str.length
|
|
1223
|
+
prevchar = 0
|
|
1224
|
+
entries = nil
|
|
1225
|
+
while true
|
|
1226
|
+
ch=win.getchar()
|
|
1227
|
+
$log.debug " XXXX FFI rbgetstr got ch:#{ch}, str:#{str}. "
|
|
1228
|
+
case ch
|
|
1229
|
+
when 3 # -1 # C-c # sometimes this causes an interrupt and crash
|
|
1230
|
+
return -1, nil
|
|
1231
|
+
when ?\C-g.getbyte(0) # ABORT, emacs style
|
|
1232
|
+
return -1, nil
|
|
1233
|
+
when 10, 13 # hits ENTER, complete entry and return
|
|
1234
|
+
@history_list.push str
|
|
1235
|
+
break
|
|
1236
|
+
when ?\C-h.getbyte(0), ?\C-?.getbyte(0), KEY_BSPACE, 263 # delete previous character/backspace
|
|
1237
|
+
# C-h is giving 263 i/o 8. 2011-09-19
|
|
1238
|
+
len -= 1 if len > @prompt_length
|
|
1239
|
+
curpos -= 1 if curpos > 0
|
|
1240
|
+
str.slice!(curpos)
|
|
1241
|
+
clear_line len+maxlen+1, @prompt_length
|
|
1242
|
+
when 330 # delete character on cursor
|
|
1243
|
+
str.slice!(curpos) #rescue next
|
|
1244
|
+
clear_line len+maxlen+1, @prompt_length
|
|
1245
|
+
when ?\M-h.getbyte(0) # HELP KEY
|
|
1246
|
+
help_text = @help_text || "No help provided..."
|
|
1247
|
+
print_help(help_text)
|
|
1248
|
+
clear_line len+maxlen+1
|
|
1249
|
+
print_str @statement # UGH
|
|
1250
|
+
#return 7, nil
|
|
1251
|
+
#next
|
|
1252
|
+
when KEY_LEFT
|
|
1253
|
+
curpos -= 1 if curpos > 0
|
|
1254
|
+
len -= 1 if len > @prompt_length
|
|
1255
|
+
win.move r, c+len # since getchar is not going back on del and bs wmove to move FFIWINDOW
|
|
1256
|
+
win.wrefresh
|
|
1257
|
+
next
|
|
1258
|
+
when KEY_RIGHT
|
|
1259
|
+
if curpos < str.length
|
|
1260
|
+
curpos += 1 #if curpos < str.length
|
|
1261
|
+
len += 1
|
|
1262
|
+
win.move r, c+len # since getchar is not going back on del and bs
|
|
1263
|
+
win.wrefresh
|
|
1264
|
+
end
|
|
1265
|
+
next
|
|
1266
|
+
when ?\C-a.getbyte(0)
|
|
1267
|
+
#olen = str.length
|
|
1268
|
+
clear_line len+maxlen+1, @prompt_length
|
|
1269
|
+
len -= curpos
|
|
1270
|
+
curpos = 0
|
|
1271
|
+
win.move r, c+len # since getchar is not going back on del and bs
|
|
1272
|
+
when ?\C-e.getbyte(0)
|
|
1273
|
+
olen = str.length
|
|
1274
|
+
len += (olen - curpos)
|
|
1275
|
+
curpos = olen
|
|
1276
|
+
clear_line len+maxlen+1, @prompt_length
|
|
1277
|
+
win.move r, c+len # since getchar is not going back on del and bs
|
|
1278
|
+
|
|
1279
|
+
when ?\M-i.getbyte(0)
|
|
1280
|
+
ins_mode = !ins_mode
|
|
1281
|
+
next
|
|
1282
|
+
when ?\C-k.getbyte(0) # delete forward
|
|
1283
|
+
@delete_buffer = str.slice!(curpos..-1) #rescue next
|
|
1284
|
+
clear_line len+maxlen+1, @prompt_length
|
|
1285
|
+
when ?\C-u.getbyte(0) # delete to the left of cursor till start of line
|
|
1286
|
+
@delete_buffer = str.slice!(0..curpos-1) #rescue next
|
|
1287
|
+
curpos = 0
|
|
1288
|
+
clear_line len+maxlen+1, @prompt_length
|
|
1289
|
+
len = @prompt_length
|
|
1290
|
+
when ?\C-y.getbyte(0) # paste what's in delete buffer
|
|
1291
|
+
if @delete_buffer
|
|
1292
|
+
olen = str.length
|
|
1293
|
+
str << @delete_buffer if @delete_buffer
|
|
1294
|
+
curpos = str.length
|
|
1295
|
+
len += str.length - olen
|
|
1296
|
+
end
|
|
1297
|
+
when KEY_TAB # TAB
|
|
1298
|
+
if !@completion_proc.nil?
|
|
1299
|
+
# place cursor at end of completion
|
|
1300
|
+
# after all completions, what user entered should come back so he can edit it
|
|
1301
|
+
if prevchar == 9
|
|
1302
|
+
if !entries.nil? and !entries.empty?
|
|
1303
|
+
olen = str.length
|
|
1304
|
+
str = entries.delete_at(0)
|
|
1305
|
+
str = str.to_s.dup
|
|
1306
|
+
#str = entries[@current_index].dup
|
|
1307
|
+
#@current_index += 1
|
|
1308
|
+
#@current_index = 0 if @current_index == entries.length
|
|
1309
|
+
curpos = str.length
|
|
1310
|
+
len += str.length - olen
|
|
1311
|
+
clear_line len+maxlen+1, @prompt_length
|
|
1312
|
+
else
|
|
1313
|
+
olen = str.length
|
|
1314
|
+
str = oldstr if oldstr
|
|
1315
|
+
curpos = str.length
|
|
1316
|
+
len += str.length - olen
|
|
1317
|
+
clear_line len+maxlen+1, @prompt_length
|
|
1318
|
+
prevchar = ch = nil # so it can start again completing
|
|
1319
|
+
end
|
|
1320
|
+
else
|
|
1321
|
+
#@current_index = 0
|
|
1322
|
+
tabc = @completion_proc unless tabc
|
|
1323
|
+
next unless tabc
|
|
1324
|
+
oldstr = str.dup
|
|
1325
|
+
olen = str.length
|
|
1326
|
+
entries = tabc.call(str).dup
|
|
1327
|
+
$log.debug "XXX tab [#{str}] got #{entries} "
|
|
1328
|
+
str = entries.delete_at(0) unless entries.nil? or entries.empty?
|
|
1329
|
+
#str = entries[@current_index].dup unless entries.nil? or entries.empty?
|
|
1330
|
+
#@current_index += 1
|
|
1331
|
+
#@current_index = 0 if @current_index == entries.length
|
|
1332
|
+
str = str.to_s.dup
|
|
1333
|
+
if str
|
|
1334
|
+
curpos = str.length
|
|
1335
|
+
len += str.length - olen
|
|
1336
|
+
else
|
|
1337
|
+
alert "NO MORE 2"
|
|
1338
|
+
end
|
|
1339
|
+
end
|
|
1340
|
+
else
|
|
1341
|
+
# there's another type of completion that bash does, which is irritating
|
|
1342
|
+
# compared to what vim does, it does partial completion
|
|
1343
|
+
if cpentries
|
|
1344
|
+
olen = str.length
|
|
1345
|
+
if cpentries.size == 1
|
|
1346
|
+
str = cpentries.first.dup
|
|
1347
|
+
elsif cpentries.size > 1
|
|
1348
|
+
str = shortest_match(cpentries).dup
|
|
1349
|
+
end
|
|
1350
|
+
curpos = str.length
|
|
1351
|
+
len += str.length - olen
|
|
1352
|
+
end
|
|
1353
|
+
end
|
|
1354
|
+
when ?\C-a.getbyte(0) .. ?\C-z.getbyte(0)
|
|
1355
|
+
# here' swhere i wish i could pass stuff back without closing
|
|
1356
|
+
# I'd like the user to be able to scroll list or do something based on
|
|
1357
|
+
# control or other keys
|
|
1358
|
+
if @key_handler_proc # added 2011-11-3 7:38 PM
|
|
1359
|
+
@key_handler_proc.call(ch)
|
|
1360
|
+
next
|
|
1361
|
+
else
|
|
1362
|
+
Ncurses.beep
|
|
1363
|
+
end
|
|
1364
|
+
when KEY_UP
|
|
1365
|
+
if @history && !@history.empty?
|
|
1366
|
+
olen = str.length
|
|
1367
|
+
str = if prevchar == KEY_UP
|
|
1368
|
+
@history_list.previous
|
|
1369
|
+
elsif prevchar == KEY_DOWN
|
|
1370
|
+
@history_list.previous
|
|
1371
|
+
else
|
|
1372
|
+
@history_list.last
|
|
1373
|
+
end
|
|
1374
|
+
str = str.dup
|
|
1375
|
+
curpos = str.length
|
|
1376
|
+
len += str.length - olen
|
|
1377
|
+
clear_line len+maxlen+1, @prompt_length
|
|
1378
|
+
else # try to pick up default, seems we don't get it 2011-10-14
|
|
1379
|
+
if @default
|
|
1380
|
+
olen = str.length
|
|
1381
|
+
str = @default
|
|
1382
|
+
str = str.dup
|
|
1383
|
+
curpos = str.length
|
|
1384
|
+
len += str.length - olen
|
|
1385
|
+
clear_line len+maxlen+1, @prompt_length
|
|
1386
|
+
end
|
|
1387
|
+
end
|
|
1388
|
+
when KEY_DOWN
|
|
1389
|
+
if @history && !@history.empty?
|
|
1390
|
+
olen = str.length
|
|
1391
|
+
str = if prevchar == KEY_UP
|
|
1392
|
+
@history_list.next
|
|
1393
|
+
elsif prevchar == KEY_DOWN
|
|
1394
|
+
@history_list.next
|
|
1395
|
+
else
|
|
1396
|
+
@history_list.first
|
|
1397
|
+
end
|
|
1398
|
+
str = str.dup
|
|
1399
|
+
curpos = str.length
|
|
1400
|
+
len += str.length - olen
|
|
1401
|
+
clear_line len+maxlen+1, @prompt_length
|
|
1402
|
+
end
|
|
1403
|
+
|
|
1404
|
+
else
|
|
1405
|
+
if ch < 0 || ch > 255
|
|
1406
|
+
Ncurses.beep
|
|
1407
|
+
next
|
|
1408
|
+
end
|
|
1409
|
+
# if control char, beep
|
|
1410
|
+
if ch.chr =~ /[[:cntrl:]]/
|
|
1411
|
+
Ncurses.beep
|
|
1412
|
+
next
|
|
1413
|
+
end
|
|
1414
|
+
# we need to trap KEY_LEFT and RIGHT and what of UP for history ?
|
|
1415
|
+
if ins_mode
|
|
1416
|
+
str[curpos] = ch.chr
|
|
1417
|
+
else
|
|
1418
|
+
str.insert(curpos, ch.chr) # FIXME index out of range due to changeproc
|
|
1419
|
+
end
|
|
1420
|
+
len += 1
|
|
1421
|
+
curpos += 1
|
|
1422
|
+
break if str.length >= maxlen
|
|
1423
|
+
end
|
|
1424
|
+
case @question.echo
|
|
1425
|
+
when true
|
|
1426
|
+
begin
|
|
1427
|
+
cpentries = @change_proc.call(str) if @change_proc # added 2010-11-09 23:28
|
|
1428
|
+
rescue => exc
|
|
1429
|
+
$log.error "bottomline: change_proc EXC #{exc} " if $log.debug?
|
|
1430
|
+
$log.error( exc) if exc
|
|
1431
|
+
$log.error(exc.backtrace.join("\n")) if exc
|
|
1432
|
+
Ncurses.error
|
|
1433
|
+
end
|
|
1434
|
+
print_str(str, :y => @prompt_length+0)
|
|
1435
|
+
when false
|
|
1436
|
+
# noop, no echoing what is typed
|
|
1437
|
+
else
|
|
1438
|
+
print_str(@question.echo * str.length, :y => @prompt_length+0)
|
|
1439
|
+
end
|
|
1440
|
+
win.move r, c+len # more for arrow keys, curpos may not be end
|
|
1441
|
+
win.wrefresh # 2011-10-10
|
|
1442
|
+
prevchar = ch
|
|
1443
|
+
end
|
|
1444
|
+
$log.debug "XXXW bottomline: after while loop"
|
|
1445
|
+
|
|
1446
|
+
str = default if str == ""
|
|
1447
|
+
ensure
|
|
1448
|
+
Ncurses.noecho();
|
|
1449
|
+
end
|
|
1450
|
+
return 0, str
|
|
1451
|
+
end
|
|
1452
|
+
|
|
1453
|
+
# compares entries in array and returns longest common starting string
|
|
1454
|
+
# as happens in bash when pressing tab
|
|
1455
|
+
# abc abd abe will return ab
|
|
1456
|
+
def shortest_match a
|
|
1457
|
+
#return "" if a.nil? || a.empty? # should not be called in such situations
|
|
1458
|
+
raise "shortest_match should not be called with nil or empty array" if a.nil? || a.empty? # should not be called in such situations as caller program will err.
|
|
1459
|
+
|
|
1460
|
+
l = a.inject do |memo,word|
|
|
1461
|
+
str = ""
|
|
1462
|
+
0.upto(memo.size) do |i|
|
|
1463
|
+
if memo[0..i] == word[0..i]
|
|
1464
|
+
str = memo[0..i]
|
|
1465
|
+
else
|
|
1466
|
+
break
|
|
1467
|
+
end
|
|
1468
|
+
end
|
|
1469
|
+
str
|
|
1470
|
+
end
|
|
1471
|
+
end
|
|
1472
|
+
# clears line from 0, not okay in some cases
|
|
1473
|
+
def clear_line len=100, from=0
|
|
1474
|
+
print_str("%-*s" % [len," "], :y => from)
|
|
1475
|
+
end
|
|
1476
|
+
|
|
1477
|
+
def print_help(help_text)
|
|
1478
|
+
# best to popup a window and hsow that with ENTER to dispell
|
|
1479
|
+
print_str("%-*s" % [help_text.length+2," "])
|
|
1480
|
+
print_str("%s" % help_text)
|
|
1481
|
+
sleep(5)
|
|
1482
|
+
end
|
|
1483
|
+
def get_response
|
|
1484
|
+
return @question.first_answer if @question.first_answer?
|
|
1485
|
+
# we always use character reader, so user's value does not matter
|
|
1486
|
+
|
|
1487
|
+
#if @question.character.nil?
|
|
1488
|
+
# if @question.echo == true #and @question.limit.nil?
|
|
1489
|
+
$log.debug "XXX: before RBGETS got default: #{@default} "
|
|
1490
|
+
ret, str = rbgetstr
|
|
1491
|
+
if ret == 0
|
|
1492
|
+
return @question.change_case(@question.remove_whitespace(str))
|
|
1493
|
+
end
|
|
1494
|
+
if ret == -1
|
|
1495
|
+
raise Interrupt
|
|
1496
|
+
end
|
|
1497
|
+
return ""
|
|
1498
|
+
end
|
|
1499
|
+
def agree( yes_or_no_question, character = nil )
|
|
1500
|
+
ask(yes_or_no_question, lambda { |yn| yn.downcase[0] == ?y}) do |q|
|
|
1501
|
+
q.validate = /\Ay(?:es)?|no?\Z/i
|
|
1502
|
+
q.responses[:not_valid] = 'Please enter "yes" or "no".'
|
|
1503
|
+
q.responses[:ask_on_error] = :question
|
|
1504
|
+
q.character = character
|
|
1505
|
+
q.limit = 1 if character
|
|
1506
|
+
|
|
1507
|
+
yield q if block_given?
|
|
1508
|
+
end
|
|
1509
|
+
end
|
|
1510
|
+
|
|
1511
|
+
# presents given list in numbered format in a window above last line
|
|
1512
|
+
# and accepts input on last line
|
|
1513
|
+
# The list is a list of strings. e.g.
|
|
1514
|
+
# %w{ ruby perl python haskell }
|
|
1515
|
+
# Multiple levels can be given as:
|
|
1516
|
+
# list = %w{ ruby perl python haskell }
|
|
1517
|
+
# list[0] = %w{ ruby ruby1.9 ruby 1.8 rubinius jruby }
|
|
1518
|
+
# In this case, "ruby" is the first level option. The others are used
|
|
1519
|
+
# in the second level. This might make it clearer. first3 has 2 choices under it.
|
|
1520
|
+
# [ "first1" , "first2", ["first3", "second1", "second2"], "first4"]
|
|
1521
|
+
#
|
|
1522
|
+
# Currently, we return an array containing each selected level
|
|
1523
|
+
#
|
|
1524
|
+
# @return [Array] selected option/s from list
|
|
1525
|
+
def numbered_menu list1, config={}
|
|
1526
|
+
if list1.nil? || list1.empty?
|
|
1527
|
+
say_with_pause "empty list passed to numbered_menu"
|
|
1528
|
+
return nil
|
|
1529
|
+
end
|
|
1530
|
+
prompt = config[:prompt] || "Select one: "
|
|
1531
|
+
require 'rbhex/core/util/rcommandwindow'
|
|
1532
|
+
layout = { :height => 5, :width => Ncurses.COLS-1, :top => Ncurses.LINES-6, :left => 0 }
|
|
1533
|
+
rc = CommandWindow.new nil, :layout => layout, :box => true, :title => config[:title]
|
|
1534
|
+
w = rc.window
|
|
1535
|
+
# should we yield rc, so user can bind keys or whatever
|
|
1536
|
+
# attempt a loop so we do levels.
|
|
1537
|
+
retval = []
|
|
1538
|
+
begin
|
|
1539
|
+
while true
|
|
1540
|
+
rc.display_menu list1, :indexing => :number
|
|
1541
|
+
ret = ask(prompt, Integer ) { |q| q.in = 1..list1.size }
|
|
1542
|
+
val = list1[ret-1]
|
|
1543
|
+
if val.is_a? Array
|
|
1544
|
+
retval << val[0]
|
|
1545
|
+
$log.debug "NL: #{retval} "
|
|
1546
|
+
list1 = val[1..-1]
|
|
1547
|
+
rc.clear
|
|
1548
|
+
else
|
|
1549
|
+
retval << val
|
|
1550
|
+
$log.debug "NL1: #{retval} "
|
|
1551
|
+
break
|
|
1552
|
+
end
|
|
1553
|
+
end
|
|
1554
|
+
ensure
|
|
1555
|
+
rc.destroy
|
|
1556
|
+
rc = nil
|
|
1557
|
+
end
|
|
1558
|
+
#list1[ret-1]
|
|
1559
|
+
$log.debug "NL2: #{retval} , #{retval.class} "
|
|
1560
|
+
retval
|
|
1561
|
+
end
|
|
1562
|
+
# Allows a selection in which options are shown over prompt. As user types
|
|
1563
|
+
# options are narrowed down.
|
|
1564
|
+
# NOTE: For a directory we are not showing a slash, so currently you
|
|
1565
|
+
# have to enter the slash manually when searching.
|
|
1566
|
+
# FIXME we can put remarks in fron as in memacs such as [No matches] or [single completion]
|
|
1567
|
+
# @param [Array] a list of items to select from
|
|
1568
|
+
# NOTE: if you use this please copy it to your app. This does not conform to highline's
|
|
1569
|
+
# choose, and I'd like to somehow get it to be identical.
|
|
1570
|
+
#
|
|
1571
|
+
def choose list1, config={}
|
|
1572
|
+
dirlist = true
|
|
1573
|
+
start = 0
|
|
1574
|
+
case list1
|
|
1575
|
+
when NilClass
|
|
1576
|
+
#list1 = Dir.glob("*")
|
|
1577
|
+
list1 = Dir.glob("*").collect { |f| File.directory?(f) ? f+"/" : f }
|
|
1578
|
+
when String
|
|
1579
|
+
list1 = Dir.glob(list1).collect { |f| File.directory?(f) ? f+"/" : f }
|
|
1580
|
+
when Array
|
|
1581
|
+
dirlist = false
|
|
1582
|
+
# let it be, that's how it should come
|
|
1583
|
+
else
|
|
1584
|
+
# Dir listing as default
|
|
1585
|
+
#list1 = Dir.glob("*")
|
|
1586
|
+
list1 = Dir.glob("*").collect { |f| File.directory?(f) ? f+"/" : f }
|
|
1587
|
+
end
|
|
1588
|
+
require 'rbhex/core/util/rcommandwindow'
|
|
1589
|
+
prompt = config[:prompt] || "Choose: "
|
|
1590
|
+
layout = { :height => 5, :width => Ncurses.COLS-1, :top => Ncurses.LINES-6, :left => 0 }
|
|
1591
|
+
rc = CommandWindow.new nil, :layout => layout, :box => true, :title => config[:title]
|
|
1592
|
+
begin
|
|
1593
|
+
w = rc.window
|
|
1594
|
+
rc.display_menu list1
|
|
1595
|
+
# earlier wmove bombed, now move is (window.rb 121)
|
|
1596
|
+
str = ask(prompt) { |q| q.help_text = config[:help_text] ; q.change_proc = Proc.new { |str| w.wmove(1,1) ; w.wclrtobot;
|
|
1597
|
+
|
|
1598
|
+
l = list1.select{|e| e.index(str)==0} ; # select those starting with str
|
|
1599
|
+
|
|
1600
|
+
if (l.size == 0 || str[-1]=='/') && dirlist
|
|
1601
|
+
# used to help complete directories so we can drill up and down
|
|
1602
|
+
#l = Dir.glob(str+"*")
|
|
1603
|
+
l = Dir.glob(str +"*").collect { |f| File.directory?(f) ? f+"/" : f }
|
|
1604
|
+
end
|
|
1605
|
+
rc.display_menu l;
|
|
1606
|
+
l
|
|
1607
|
+
}
|
|
1608
|
+
q.key_handler_proc = Proc.new { |ch|
|
|
1609
|
+
# this is not very good since it does not respect above list which is filtered
|
|
1610
|
+
# # need to clear the screen before printing - FIXME
|
|
1611
|
+
case ch
|
|
1612
|
+
when ?\C-n.getbyte(0)
|
|
1613
|
+
start += 2 if start < list1.length - 2
|
|
1614
|
+
|
|
1615
|
+
w.wmove(1,1) ; w.wclrtobot;
|
|
1616
|
+
rc.display_menu list1, :startindex => start
|
|
1617
|
+
when ?\C-p.getbyte(0)
|
|
1618
|
+
start -= 2 if start > 2
|
|
1619
|
+
w.wmove(1,1) ; w.wclrtobot;
|
|
1620
|
+
rc.display_menu list1, :startindex => start
|
|
1621
|
+
else
|
|
1622
|
+
alert "unhalderlind by jey "
|
|
1623
|
+
end
|
|
1624
|
+
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
# need some validation here that its in the list TODO
|
|
1628
|
+
ensure
|
|
1629
|
+
rc.destroy
|
|
1630
|
+
rc = nil
|
|
1631
|
+
$log.debug "XXX: HIDE B IN ENSURE"
|
|
1632
|
+
hide_bottomline # since we called ask() we need to close bottomline
|
|
1633
|
+
end
|
|
1634
|
+
$log.debug "XXX: HIDE B AT END OF ASK"
|
|
1635
|
+
#hide_bottomline # since we called ask() we need to close bottomline
|
|
1636
|
+
return str
|
|
1637
|
+
end
|
|
1638
|
+
def display_text_interactive text, config={}
|
|
1639
|
+
require 'rbhex/core/util/rcommandwindow'
|
|
1640
|
+
ht = config[:height] || 15
|
|
1641
|
+
layout = { :height => ht, :width => Ncurses.COLS-1, :top => Ncurses.LINES-ht+1, :left => 0 }
|
|
1642
|
+
rc = CommandWindow.new nil, :layout => layout, :box => true, :title => config[:title]
|
|
1643
|
+
w = rc.window
|
|
1644
|
+
#rc.text "There was a quick brown fox who ran over the lazy dog and then went over the moon over and over again and again"
|
|
1645
|
+
rc.display_interactive(text) { |l|
|
|
1646
|
+
l.focussed_attrib = 'bold' # Ncurses::A_UNDERLINE
|
|
1647
|
+
l.focussed_symbol = '>'
|
|
1648
|
+
}
|
|
1649
|
+
rc = nil
|
|
1650
|
+
end
|
|
1651
|
+
#def display_list_interactive text, config={}
|
|
1652
|
+
# returns a ListObject since you may not know what the list itself contained
|
|
1653
|
+
# You can do ret.list[ret.current_index] to get value
|
|
1654
|
+
def display_list text, config={}
|
|
1655
|
+
require 'rbhex/core/util/rcommandwindow'
|
|
1656
|
+
ht = config[:height] || 15
|
|
1657
|
+
layout = { :height => ht, :width => Ncurses.COLS-1, :top => Ncurses.LINES-ht+1, :left => 0 }
|
|
1658
|
+
rc = CommandWindow.new nil, :layout => layout, :box => true, :title => config[:title]
|
|
1659
|
+
w = rc.window
|
|
1660
|
+
ret = rc.display_interactive text
|
|
1661
|
+
rc = nil
|
|
1662
|
+
ret
|
|
1663
|
+
end
|
|
1664
|
+
#
|
|
1665
|
+
# This method is HighLine's menu handler. For simple usage, you can just
|
|
1666
|
+
# pass all the menu items you wish to display. At that point, choose() will
|
|
1667
|
+
# build and display a menu, walk the user through selection, and return
|
|
1668
|
+
# their choice amoung the provided items. You might use this in a case
|
|
1669
|
+
# statement for quick and dirty menus.
|
|
1670
|
+
#
|
|
1671
|
+
# However, choose() is capable of much more. If provided, a block will be
|
|
1672
|
+
# passed a HighLine::Menu object to configure. Using this method, you can
|
|
1673
|
+
# customize all the details of menu handling from index display, to building
|
|
1674
|
+
# a complete shell-like menuing system. See HighLine::Menu for all the
|
|
1675
|
+
# methods it responds to.
|
|
1676
|
+
#
|
|
1677
|
+
# Raises EOFError if input is exhausted.
|
|
1678
|
+
#
|
|
1679
|
+
def XXXchoose( *items, &details )
|
|
1680
|
+
@menu = @question = Menu.new(&details)
|
|
1681
|
+
@menu.choices(*items) unless items.empty?
|
|
1682
|
+
|
|
1683
|
+
# Set _answer_type_ so we can double as the Question for ask().
|
|
1684
|
+
@menu.answer_type = if @menu.shell
|
|
1685
|
+
lambda do |command| # shell-style selection
|
|
1686
|
+
first_word = command.to_s.split.first || ""
|
|
1687
|
+
|
|
1688
|
+
options = @menu.options
|
|
1689
|
+
options.extend(OptionParser::Completion)
|
|
1690
|
+
answer = options.complete(first_word)
|
|
1691
|
+
|
|
1692
|
+
if answer.nil?
|
|
1693
|
+
raise Question::NoAutoCompleteMatch
|
|
1694
|
+
end
|
|
1695
|
+
|
|
1696
|
+
[answer.last, command.sub(/^\s*#{first_word}\s*/, "")]
|
|
1697
|
+
end
|
|
1698
|
+
else
|
|
1699
|
+
@menu.options # normal menu selection, by index or name
|
|
1700
|
+
end
|
|
1701
|
+
|
|
1702
|
+
# Provide hooks for ERb layouts.
|
|
1703
|
+
@header = @menu.header
|
|
1704
|
+
@prompt = @menu.prompt
|
|
1705
|
+
|
|
1706
|
+
if @menu.shell
|
|
1707
|
+
selected = ask("Ignored", @menu.answer_type)
|
|
1708
|
+
@menu.select(self, *selected)
|
|
1709
|
+
else
|
|
1710
|
+
selected = ask("Ignored", @menu.answer_type)
|
|
1711
|
+
@menu.select(self, selected)
|
|
1712
|
+
end
|
|
1713
|
+
end
|
|
1714
|
+
|
|
1715
|
+
# Each member of the _items_ Array is passed through ERb and thus can contain
|
|
1716
|
+
# their own expansions. Color escape expansions do not contribute to the
|
|
1717
|
+
# final field width.
|
|
1718
|
+
#
|
|
1719
|
+
def list( items, mode = :rows, option = nil )
|
|
1720
|
+
items = items.to_ary.map do |item|
|
|
1721
|
+
ERB.new(item, nil, "%").result(binding)
|
|
1722
|
+
end
|
|
1723
|
+
|
|
1724
|
+
case mode
|
|
1725
|
+
when :inline
|
|
1726
|
+
option = " or " if option.nil?
|
|
1727
|
+
|
|
1728
|
+
case items.size
|
|
1729
|
+
when 0
|
|
1730
|
+
""
|
|
1731
|
+
when 1
|
|
1732
|
+
items.first
|
|
1733
|
+
when 2
|
|
1734
|
+
"#{items.first}#{option}#{items.last}"
|
|
1735
|
+
else
|
|
1736
|
+
items[0..-2].join(", ") + "#{option}#{items.last}"
|
|
1737
|
+
end
|
|
1738
|
+
when :columns_across, :columns_down
|
|
1739
|
+
max_length = actual_length(
|
|
1740
|
+
items.max { |a, b| actual_length(a) <=> actual_length(b) }
|
|
1741
|
+
)
|
|
1742
|
+
|
|
1743
|
+
if option.nil?
|
|
1744
|
+
limit = @wrap_at || 80
|
|
1745
|
+
option = (limit + 2) / (max_length + 2)
|
|
1746
|
+
end
|
|
1747
|
+
|
|
1748
|
+
items = items.map do |item|
|
|
1749
|
+
pad = max_length + (item.length - actual_length(item))
|
|
1750
|
+
"%-#{pad}s" % item
|
|
1751
|
+
end
|
|
1752
|
+
row_count = (items.size / option.to_f).ceil
|
|
1753
|
+
|
|
1754
|
+
if mode == :columns_across
|
|
1755
|
+
rows = Array.new(row_count) { Array.new }
|
|
1756
|
+
items.each_with_index do |item, index|
|
|
1757
|
+
rows[index / option] << item
|
|
1758
|
+
end
|
|
1759
|
+
|
|
1760
|
+
rows.map { |row| row.join(" ") + "\n" }.join
|
|
1761
|
+
else
|
|
1762
|
+
columns = Array.new(option) { Array.new }
|
|
1763
|
+
items.each_with_index do |item, index|
|
|
1764
|
+
columns[index / row_count] << item
|
|
1765
|
+
end
|
|
1766
|
+
|
|
1767
|
+
list = ""
|
|
1768
|
+
columns.first.size.times do |index|
|
|
1769
|
+
list << columns.map { |column| column[index] }.
|
|
1770
|
+
compact.join(" ") + "\n"
|
|
1771
|
+
end
|
|
1772
|
+
list
|
|
1773
|
+
end
|
|
1774
|
+
else
|
|
1775
|
+
items.map { |i| "#{i}\n" }.join
|
|
1776
|
+
end
|
|
1777
|
+
end
|
|
1778
|
+
end # module
|
|
1779
|
+
end # module
|
|
1780
|
+
#require 'rbhex/core/util/bottomline'
|
|
1781
|
+
$tt ||= RubyCurses::Bottomline.new
|
|
1782
|
+
$tt.name = "$tt"
|
|
1783
|
+
require 'forwardable'
|
|
1784
|
+
module Kernel
|
|
1785
|
+
extend Forwardable
|
|
1786
|
+
def_delegators :$tt, :ask, :say, :agree, :choose, :numbered_menu, :display_text, :display_text_interactive, :display_list, :say_with_pause, :hide_bottomline, :say_with_wait
|
|
1787
|
+
end
|
|
1788
|
+
if __FILE__ == $PROGRAM_NAME
|
|
1789
|
+
|
|
1790
|
+
#tabc = Proc.new {|str| Dir.glob(str +"*") }
|
|
1791
|
+
require 'rbhex/core/util/app'
|
|
1792
|
+
require 'forwardable'
|
|
1793
|
+
#include Bottomline
|
|
1794
|
+
|
|
1795
|
+
#$tt = Bottomline.new
|
|
1796
|
+
#module Kernel
|
|
1797
|
+
#extend Forwardable
|
|
1798
|
+
#def_delegators :$tt, :ask, :say, :agree, :choose, :numbered_menu
|
|
1799
|
+
#end
|
|
1800
|
+
App.new do
|
|
1801
|
+
header = app_header "rbhex 1.2.0", :text_center => "**** Demo", :text_right =>"New Improved!", :color => :black, :bgcolor => :white, :attr => :bold
|
|
1802
|
+
message "Press F1 to exit from here"
|
|
1803
|
+
|
|
1804
|
+
#stack :margin_top => 2, :margin => 5, :width => 30 do
|
|
1805
|
+
#end # stack
|
|
1806
|
+
#-----------------#------------------
|
|
1807
|
+
|
|
1808
|
+
#choose do |menu|
|
|
1809
|
+
#menu.prompt = "Please choose your favorite programming language? "
|
|
1810
|
+
##menu.layout = :one_line
|
|
1811
|
+
#
|
|
1812
|
+
#menu.choice :ruby do say("Good choice!") end
|
|
1813
|
+
#menu.choice(:python) do say("python Not from around here, are you?") end
|
|
1814
|
+
#menu.choice(:perl) do say("perl Not from around here, are you?") end
|
|
1815
|
+
#menu.choice(:rake) do say("rake Not from around here, are you?") end
|
|
1816
|
+
#end
|
|
1817
|
+
entry = {}
|
|
1818
|
+
entry[:file] = ask("File? ", Pathname) do |q|
|
|
1819
|
+
q.completion_proc = Proc.new {|str| Dir.glob(str +"*") }
|
|
1820
|
+
q.help_text = "Enter start of filename and tab to get completion"
|
|
1821
|
+
end
|
|
1822
|
+
alert "file: #{entry[:file]} "
|
|
1823
|
+
$log.debug "FILE: #{entry[:file]} "
|
|
1824
|
+
entry[:command] = ask("Command? ", %w{archive delete read refresh delete!})
|
|
1825
|
+
exit unless agree("Wish to continue? ", false)
|
|
1826
|
+
entry[:address] = ask("Address? ") { |q| q.color_pair = $promptcolor }
|
|
1827
|
+
entry[:company] = ask("Company? ") { |q| q.default = "none" }
|
|
1828
|
+
entry[:password] = ask("password? ") { |q|
|
|
1829
|
+
q.echo = '*'
|
|
1830
|
+
q.limit = 4
|
|
1831
|
+
}
|
|
1832
|
+
=begin
|
|
1833
|
+
entry[:state] = ask("State? ") do |q|
|
|
1834
|
+
q._case = :up
|
|
1835
|
+
q.validate = /\A[A-Z]{2}\Z/
|
|
1836
|
+
q.help_text = "Enter 2 characters for your state"
|
|
1837
|
+
end
|
|
1838
|
+
entry[:zip] = ask("Zip? ") do |q|
|
|
1839
|
+
q.validate = /\A\d{5}(?:-?\d{4})?\Z/
|
|
1840
|
+
end
|
|
1841
|
+
entry[:phone] = ask( "Phone? ",
|
|
1842
|
+
lambda { |p| p.delete("^0-9").
|
|
1843
|
+
sub(/\A(\d{3})/, '(\1) ').
|
|
1844
|
+
sub(/(\d{4})\Z/, '-\1') } ) do |q|
|
|
1845
|
+
q.validate = lambda { |p| p.delete("^0-9").length == 10 }
|
|
1846
|
+
q.responses[:not_valid] = "Enter a phone numer with area code."
|
|
1847
|
+
end
|
|
1848
|
+
entry[:age] = ask("Age? ", Integer) { |q| q.in = 0..105 }
|
|
1849
|
+
entry[:birthday] = ask("Birthday? ", Date)
|
|
1850
|
+
entry[:interests] = ask( "Interests? (comma separated list) ",
|
|
1851
|
+
lambda { |str| str.split(/,\s*/) } )
|
|
1852
|
+
entry[:description] = ask("Enter a description for this contact.") do |q|
|
|
1853
|
+
q.whitespace = :strip_and_collapse
|
|
1854
|
+
end
|
|
1855
|
+
=end
|
|
1856
|
+
$log.debug "ENTRY: #{entry} " if $log.debug?
|
|
1857
|
+
#puts entry
|
|
1858
|
+
end # app
|
|
1859
|
+
end # FILE
|