rubytext 0.1.21 → 0.1.25
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 +4 -4
- data/examples/topmenu.rb +56 -0
- data/lib/.yardoc/checksums +11 -0
- data/lib/.yardoc/complete +0 -0
- data/lib/.yardoc/object_types +0 -0
- data/lib/.yardoc/objects/root.dat +0 -0
- data/lib/.yardoc/proxy_types +0 -0
- data/lib/color.rb +22 -0
- data/lib/doc/RubyText/Color.html +366 -0
- data/lib/doc/RubyText/Effects.html +658 -0
- data/lib/doc/RubyText/Keys.html +300 -0
- data/lib/doc/RubyText/Settings.html +593 -0
- data/lib/doc/RubyText/Window/GetString.html +812 -0
- data/lib/doc/RubyText/Window.html +5074 -0
- data/lib/doc/RubyText.html +1410 -0
- data/lib/doc/WindowIO.html +541 -0
- data/lib/doc/_index.html +195 -0
- data/lib/doc/class_list.html +51 -0
- data/lib/doc/css/common.css +1 -0
- data/lib/doc/css/full_list.css +58 -0
- data/lib/doc/css/style.css +496 -0
- data/lib/doc/file_list.html +51 -0
- data/lib/doc/frames.html +17 -0
- data/lib/doc/index.html +195 -0
- data/lib/doc/js/app.js +314 -0
- data/lib/doc/js/full_list.js +216 -0
- data/lib/doc/js/jquery.js +4 -0
- data/lib/doc/method_list.html +987 -0
- data/lib/doc/top-level-namespace.html +483 -0
- data/lib/effects.rb +15 -1
- data/lib/keys.rb +3 -0
- data/lib/menu.rb +495 -44
- data/lib/navigation.rb +35 -0
- data/lib/output.rb +27 -8
- data/lib/rubytext_version.rb +1 -1
- data/lib/settings.rb +5 -4
- data/lib/widgets.rb +10 -2
- data/lib/window.rb +1 -1
- metadata +33 -6
data/lib/menu.rb
CHANGED
@@ -1,22 +1,333 @@
|
|
1
|
+
#### FIXME LATER
|
2
|
+
|
3
|
+
# The top-level module
|
4
|
+
|
1
5
|
module RubyText
|
2
6
|
|
7
|
+
# Wrapper for a curses window
|
8
|
+
|
3
9
|
class Window
|
4
|
-
def topmenu(items:, curr: 0,
|
5
|
-
title: nil, fg: Green, bg: Black)
|
6
|
-
r, c = 0, 0
|
7
|
-
border = false
|
8
|
-
high = 1
|
9
10
|
|
11
|
+
class Menu2D
|
12
|
+
|
13
|
+
class Vertical
|
14
|
+
attr_reader :widest, :height, :header, :hash
|
15
|
+
def initialize(vlist)
|
16
|
+
@header = vlist[0]
|
17
|
+
@hash = vlist[1]
|
18
|
+
@widest = @header.length
|
19
|
+
@hash.each_pair {|k,v| puts "k = #{k.inspect}"; getch; @widest = [@widest, k.length].max }
|
20
|
+
@height = @hash.size
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize(win:, r: :center, c: :center, items:, colrow: [0, 0],
|
25
|
+
border: true, title: nil, fg: Green, bg: Black)
|
26
|
+
@win = win
|
27
|
+
@list = []
|
28
|
+
@header = @list.map {|x| x.header }
|
29
|
+
items.each {|vlist| @list << Vertical.new(vlist) }
|
30
|
+
@highest = @list.map {|x| x.height }.max
|
31
|
+
@full_width = @list.inject(0) {|sum, vlist| sum += vlist.widest + 2 }
|
32
|
+
@nlists = items.size
|
33
|
+
@grid = Array.new(@nlists) # column major order
|
34
|
+
@grid.map! {|x| [" "] * @highest }
|
35
|
+
@list.each.with_index do |vlist, i|
|
36
|
+
vlist.hash.each_pair.with_index do |kv, j|
|
37
|
+
k, v = kv
|
38
|
+
@grid[i][j] = [k, v]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
RubyText.hide_cursor
|
42
|
+
@high = @highest
|
43
|
+
@wide = @full_width
|
44
|
+
@high += 2 if border
|
45
|
+
@wide += 2 if border
|
46
|
+
|
47
|
+
tlen = title.length + 8 rescue 0
|
48
|
+
# wide = [wide, tlen].max
|
49
|
+
row, col = @win.coords(r, c)
|
50
|
+
row = row - @high/2 if r == :center
|
51
|
+
col = col - @wide/2 if c == :center
|
52
|
+
r, c = row, col
|
53
|
+
@win.saveback(@high+1, @wide, r, c)
|
54
|
+
mr, mc = r+@win.r0, c+@win.c0
|
55
|
+
title = nil unless border
|
56
|
+
|
57
|
+
@mwin = RubyText.window(@high+1, @wide, r: mr, c: mc, border: true,
|
58
|
+
fg: fg, bg: bg, title: title)
|
59
|
+
@header.each {|head| printf "%-#{maxw}s", head }
|
60
|
+
puts # after header
|
61
|
+
Curses.stdscr.keypad(true)
|
62
|
+
maxcol = items.size - 1
|
63
|
+
sizes = items.map {|x| x.size }
|
64
|
+
max = sizes.max
|
65
|
+
# mwin.go(r, c)
|
66
|
+
r += 1 # account for header
|
67
|
+
@selc, @selr = colrow
|
68
|
+
end
|
69
|
+
|
70
|
+
def show(r, c, colrow: [0, 0])
|
71
|
+
@selc, @selr = colrow
|
72
|
+
@grid.each.with_index do |column, cix|
|
73
|
+
column.each.with_index do |pairs, rix| # {Jan: ..., Feb: ..., Mar: ..., ...}
|
74
|
+
# STDSCR.puts "go: #{r}+#{rix}, #{c}+#{cix}*#{maxw}"
|
75
|
+
@mwin.go(rix, cix) # FIXME wrong?
|
76
|
+
style = ([@selc, @selr] == [cix, rix]) ? :reverse : :normal
|
77
|
+
key, val = pairs
|
78
|
+
label = key.to_s
|
79
|
+
@mwin.print label # fx(label, style)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def handle(r, c)
|
85
|
+
loop do
|
86
|
+
show(r, c)
|
87
|
+
ch = getch
|
88
|
+
case ch
|
89
|
+
when RubyText::Window::Up
|
90
|
+
@selr -= 1 if @selr > 0
|
91
|
+
when RubyText::Window::Down
|
92
|
+
# puts "PAUSE r,c = #@selr #@selc highest=#@highest"; getch
|
93
|
+
@selr += 1 if @selr < @highest - 1
|
94
|
+
when RubyText::Window::Left
|
95
|
+
@selc -= 1 if @selc > 0
|
96
|
+
when RubyText::Window::Right
|
97
|
+
@selc += 1 if @selc < @full_width
|
98
|
+
when RubyText::Window::Esc
|
99
|
+
@win.restback(@high+1, @wide, r-1, c)
|
100
|
+
RubyText.show_cursor
|
101
|
+
return [nil, nil, nil]
|
102
|
+
when RubyText::Window::Enter
|
103
|
+
@win.restback(@high+1, @wide, r-1, c)
|
104
|
+
RubyText.show_cursor
|
105
|
+
choice = @grid[@selc][@selr][1]
|
106
|
+
case choice
|
107
|
+
when String;
|
108
|
+
puts "Returning #{[@selc, @selr, choice].inspect}"; getch
|
109
|
+
return [@selc, @selr, choice]
|
110
|
+
when NilClass; return [nil, nil, nil]
|
111
|
+
end
|
112
|
+
result = choice.call # should be a Proc
|
113
|
+
return [nil, nil, nil] if result.nil? || result.empty?
|
114
|
+
return result
|
115
|
+
else Curses.beep
|
116
|
+
end
|
117
|
+
end
|
118
|
+
RubyText.show_cursor
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
|
123
|
+
def rectmenu(r: :center, c: :center, items:, colrow: [0, 0],
|
124
|
+
border: true,
|
125
|
+
title: nil, fg: Green, bg: Black)
|
10
126
|
RubyText.hide_cursor
|
127
|
+
maxh, maxw = _rectmenu_maxes(items)
|
128
|
+
header, stuff = _rect_hash2array(items, maxh, maxw)
|
129
|
+
wide = items.size * maxw
|
130
|
+
high = maxh
|
131
|
+
high += 2 if border
|
132
|
+
wide += 2 if border
|
133
|
+
|
134
|
+
tlen = title.length + 8 rescue 0
|
135
|
+
# wide = [wide, tlen].max
|
136
|
+
row, col = @win.coords(r, c)
|
137
|
+
row = row - high/2 if r == :center
|
138
|
+
col = col - wide/2 if c == :center
|
139
|
+
r, c = row, col
|
140
|
+
@win.saveback(high+1, wide, r, c)
|
141
|
+
mr, mc = r+@win.r0, c+@win.c0
|
142
|
+
title = nil unless border
|
143
|
+
|
144
|
+
mwin = RubyText.window(high+1, wide, r: mr, c: mc, border: true,
|
145
|
+
fg: fg, bg: bg, title: title)
|
146
|
+
header.each {|head| printf "%-#{maxw}s", head }
|
147
|
+
puts # after header
|
148
|
+
Curses.stdscr.keypad(true)
|
149
|
+
maxcol = items.size - 1
|
150
|
+
sizes = items.map {|x| x.size }
|
151
|
+
max = sizes.max
|
152
|
+
# mwin.go(r, c)
|
153
|
+
r += 1 # account for header
|
154
|
+
selc, selr = colrow
|
155
|
+
|
156
|
+
loop do
|
157
|
+
RubyText.hide_cursor # FIXME should be unnecessary
|
158
|
+
stuff.each.with_index do |column, cix|
|
159
|
+
column.each.with_index do |pairs, rix| # {Jan: ..., Feb: ..., Mar: ..., ...}
|
160
|
+
STDSCR.puts "go: #{r}+#{rix}, #{c}+#{cix}*#{maxw}"
|
161
|
+
mwin.go(rix+1, cix*maxw)
|
162
|
+
style = ([selc, selr] == [cix, rix]) ? :reverse : :normal
|
163
|
+
key, val = pairs
|
164
|
+
label = key.to_s
|
165
|
+
# mwin.print fx(label, style)
|
166
|
+
mwin.print label # fx(label, style)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
ch = getch
|
170
|
+
case ch
|
171
|
+
when Up
|
172
|
+
selr -= 1 if selr > 0
|
173
|
+
when Down
|
174
|
+
selr += 1 if selr < maxh - 1
|
175
|
+
when Left
|
176
|
+
selc -= 1 if selc > 0
|
177
|
+
when Right
|
178
|
+
selc += 1 if selc < maxcol
|
179
|
+
when Esc
|
180
|
+
self.restback(high+1, wide, r-1, c)
|
181
|
+
RubyText.show_cursor
|
182
|
+
return [nil, nil, nil]
|
183
|
+
when Enter
|
184
|
+
self.restback(high+1, wide, r-1, c)
|
185
|
+
RubyText.show_cursor
|
186
|
+
choice = stuff[selc][selr][1]
|
187
|
+
case choice
|
188
|
+
when String; return [selc, selr, choice]
|
189
|
+
when NilClass; return [nil, nil, nil]
|
190
|
+
end
|
191
|
+
result = choice.call # should be a Proc
|
192
|
+
return [nil, nil, nil] if result.nil? || result.empty?
|
193
|
+
return result
|
194
|
+
else Curses.beep
|
195
|
+
end
|
196
|
+
RubyText.show_cursor
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
module RubyText
|
203
|
+
|
204
|
+
# Two-paned widget with menu on left, informtional area on right
|
205
|
+
|
206
|
+
def self.selector(win: STDSCR, r: 0, c: 0, rows: 10, cols: 20,
|
207
|
+
items:, fg: White, bg: Blue,
|
208
|
+
win2:, callback:, enter: nil, quit: "q")
|
209
|
+
high = rows
|
210
|
+
wide = cols
|
211
|
+
mwin = RubyText.window(high, wide, r: r, c: c, fg: fg, bg: bg)
|
212
|
+
handler = callback
|
213
|
+
Curses.stdscr.keypad(true)
|
214
|
+
RubyText.hide_cursor
|
215
|
+
sel = 0
|
216
|
+
max = items.size - 1
|
217
|
+
handler.call(sel, items[sel], win2)
|
218
|
+
loop do
|
219
|
+
mwin.home
|
220
|
+
items.each.with_index do |item, row|
|
221
|
+
mwin.crlf
|
222
|
+
style = (sel == row) ? :reverse : :normal
|
223
|
+
mwin.print fx(" #{item}", style)
|
224
|
+
end
|
225
|
+
ch = getch
|
226
|
+
case ch
|
227
|
+
when Up
|
228
|
+
if sel > 0
|
229
|
+
sel -= 1
|
230
|
+
handler.call(sel, items[sel], win2)
|
231
|
+
end
|
232
|
+
when Down
|
233
|
+
if sel < max
|
234
|
+
sel += 1
|
235
|
+
handler.call(sel, items[sel], win2)
|
236
|
+
end
|
237
|
+
when Enter
|
238
|
+
if enter
|
239
|
+
del = enter.call(sel, items[sel], win2)
|
240
|
+
if del
|
241
|
+
items -= [items[sel]]
|
242
|
+
raise
|
243
|
+
end
|
244
|
+
end
|
245
|
+
when Tab
|
246
|
+
Curses.flash
|
247
|
+
when quit # parameter
|
248
|
+
exit
|
249
|
+
else Curses.beep # all else is trash
|
250
|
+
end
|
251
|
+
end
|
252
|
+
rescue
|
253
|
+
retry
|
254
|
+
end
|
255
|
+
|
256
|
+
# "Menu" for checklists
|
257
|
+
|
258
|
+
def checklist(r: :center, c: :center,
|
259
|
+
items:, curr: 0, selected: [],
|
260
|
+
title: nil, sel_fg: Yellow, fg: White, bg: Blue)
|
261
|
+
RubyText.hide_cursor
|
262
|
+
high = items.size + 2
|
263
|
+
wide = items.map(&:length).max + 8
|
264
|
+
tlen = title.length + 8 rescue 0
|
265
|
+
wide = [wide, tlen].max
|
266
|
+
row, col = self.coords(r, c)
|
267
|
+
row = row - high/2 if r == :center
|
268
|
+
col = col - wide/2 if c == :center
|
269
|
+
r, c = row, col
|
270
|
+
self.saveback(high, wide, r, c)
|
271
|
+
mr, mc = r+self.r0, c+self.c0
|
272
|
+
mwin = RubyText.window(high, wide, r: mr, c: mc,
|
273
|
+
fg: fg, bg: bg, title: title)
|
274
|
+
Curses.stdscr.keypad(true)
|
275
|
+
sel = curr
|
276
|
+
max = items.size - 1
|
277
|
+
loop do
|
278
|
+
RubyText.hide_cursor # FIXME should be unnecessary
|
279
|
+
items.each.with_index do |item, row|
|
280
|
+
mwin.go row, 0
|
281
|
+
style = (sel == row) ? :reverse : :normal
|
282
|
+
color = selected.find {|x| x[0] == row } ? sel_fg : fg
|
283
|
+
label = "[ ]" + item
|
284
|
+
mwin.print fx(label, color, style)
|
285
|
+
end
|
286
|
+
ch = getch
|
287
|
+
case ch
|
288
|
+
when Up
|
289
|
+
sel -= 1 if sel > 0
|
290
|
+
when Down
|
291
|
+
sel += 1 if sel < max
|
292
|
+
when Esc
|
293
|
+
self.restback(high, wide, r, c)
|
294
|
+
RubyText.show_cursor
|
295
|
+
return []
|
296
|
+
when Enter
|
297
|
+
self.restback(high, wide, r, c)
|
298
|
+
RubyText.show_cursor
|
299
|
+
return selected.map {|i| items[i] }
|
300
|
+
when " "
|
301
|
+
selected << [sel, items[sel]]
|
302
|
+
sel += 1 if sel < max
|
303
|
+
else Curses.beep
|
304
|
+
end
|
305
|
+
RubyText.show_cursor
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
end
|
310
|
+
|
311
|
+
# The top-level module
|
312
|
+
|
313
|
+
module RubyText
|
314
|
+
|
315
|
+
# Wrapper for a curses window
|
316
|
+
|
317
|
+
class Window
|
318
|
+
|
319
|
+
# One-line menu at top of window
|
320
|
+
|
321
|
+
def topmenu(items:, curr: 0, fg: Green, bg: Black)
|
322
|
+
r, c, high = 0, 0, 1
|
323
|
+
RubyText.hide_cursor
|
324
|
+
hash_flag = false
|
325
|
+
results = items
|
11
326
|
if items.is_a?(Hash)
|
12
|
-
results = items.values
|
13
|
-
items = items.keys
|
327
|
+
results, items = items.values, items.keys
|
14
328
|
hash_flag = true
|
15
|
-
else
|
16
|
-
results = items
|
17
329
|
end
|
18
330
|
|
19
|
-
tlen = title.length + 8 rescue 0
|
20
331
|
width = 0 # total width
|
21
332
|
cols = [] # start-column of each item
|
22
333
|
items.each do |item|
|
@@ -26,13 +337,9 @@ module RubyText
|
|
26
337
|
end
|
27
338
|
|
28
339
|
r, c = self.coords(r, c)
|
29
|
-
# puts "topmenu saved"
|
30
|
-
# sleep 2
|
31
340
|
self.saveback(high, width, r, c)
|
32
341
|
mr, mc = r+self.r0, c+self.c0
|
33
|
-
|
34
|
-
mwin = RubyText.window(high, width, r: mr, c: mc, border: border,
|
35
|
-
fg: fg, bg: bg, title: title)
|
342
|
+
mwin = RubyText.window(high, width, r: mr, c: mc, fg: fg, bg: bg, border: false, title: nil)
|
36
343
|
Curses.stdscr.keypad(true)
|
37
344
|
sel = curr
|
38
345
|
max = items.size - 1
|
@@ -45,34 +352,37 @@ module RubyText
|
|
45
352
|
end
|
46
353
|
ch = getch
|
47
354
|
case ch
|
48
|
-
when
|
355
|
+
when Left
|
49
356
|
sel -= 1 if sel > 0
|
50
|
-
when
|
357
|
+
when Right
|
51
358
|
sel += 1 if sel < max
|
52
|
-
when
|
359
|
+
when Esc, " " # spacebar also quits
|
53
360
|
self.restback(high, width, r, c)
|
54
361
|
RubyText.show_cursor
|
362
|
+
STDSCR.go r, c
|
55
363
|
return [nil, nil]
|
56
|
-
when
|
364
|
+
when Down, Enter
|
57
365
|
self.restback(high, width, r, c)
|
58
|
-
# puts "topmenu restored"
|
59
|
-
# sleep 2
|
60
366
|
RubyText.show_cursor
|
367
|
+
STDSCR.go r, c
|
61
368
|
choice = results[sel]
|
62
369
|
return [sel, choice] if choice.is_a? String
|
63
370
|
result = choice.call
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
371
|
+
return [nil, nil, nil] if result.nil? || result.empty?
|
372
|
+
# next if result.nil?
|
373
|
+
# next if result.empty?
|
374
|
+
# return result
|
375
|
+
else Curses.beep
|
68
376
|
end
|
69
377
|
RubyText.show_cursor
|
70
378
|
end
|
71
379
|
end
|
72
380
|
|
381
|
+
# Simple menu with rows of strings (or Procs)
|
382
|
+
|
73
383
|
def menu(r: :center, c: :center, items:, curr: 0,
|
74
|
-
border: true,
|
75
|
-
title: nil, fg: Green, bg: Black)
|
384
|
+
border: true, sticky: false,
|
385
|
+
title: nil, fg: Green, bg: Black, wrap: false)
|
76
386
|
RubyText.hide_cursor
|
77
387
|
if items.is_a?(Hash)
|
78
388
|
results = items.values
|
@@ -93,8 +403,6 @@ module RubyText
|
|
93
403
|
row = row - high/2 if r == :center
|
94
404
|
col = col - wide/2 if c == :center
|
95
405
|
r, c = row, col
|
96
|
-
# puts "menu2 saved"
|
97
|
-
# sleep 2
|
98
406
|
self.saveback(high, wide, r, c)
|
99
407
|
mr, mc = r+self.r0, c+self.c0
|
100
408
|
title = nil unless border
|
@@ -113,16 +421,24 @@ module RubyText
|
|
113
421
|
end
|
114
422
|
ch = getch
|
115
423
|
case ch
|
116
|
-
when
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
424
|
+
when Up
|
425
|
+
if sel > 0
|
426
|
+
sel -= 1
|
427
|
+
else
|
428
|
+
sel = max if wrap # asteroids mode :)
|
429
|
+
end
|
430
|
+
when Down, " " # let space mean down?
|
431
|
+
if sel < max
|
432
|
+
sel += 1
|
433
|
+
else
|
434
|
+
sel = 0 if wrap # asteroids mode :)
|
435
|
+
end
|
436
|
+
when Esc
|
121
437
|
self.restback(high, wide, r, c)
|
122
438
|
RubyText.show_cursor
|
123
439
|
return [nil, nil]
|
124
|
-
when
|
125
|
-
self.restback(high, wide, r, c)
|
440
|
+
when Enter
|
441
|
+
self.restback(high, wide, r, c) unless sticky
|
126
442
|
RubyText.show_cursor
|
127
443
|
choice = results[sel]
|
128
444
|
return [sel, choice] if choice.is_a? String
|
@@ -135,6 +451,8 @@ module RubyText
|
|
135
451
|
end
|
136
452
|
end
|
137
453
|
|
454
|
+
# Menu for multiple selections (buggy/unused?)
|
455
|
+
|
138
456
|
def multimenu(r: :center, c: :center,
|
139
457
|
items:, curr: 0, selected: [],
|
140
458
|
title: nil, sel_fg: Yellow, fg: White, bg: Blue)
|
@@ -165,15 +483,15 @@ module RubyText
|
|
165
483
|
end
|
166
484
|
ch = getch
|
167
485
|
case ch
|
168
|
-
when
|
486
|
+
when Up
|
169
487
|
sel -= 1 if sel > 0
|
170
|
-
when
|
488
|
+
when Down
|
171
489
|
sel += 1 if sel < max
|
172
|
-
when
|
490
|
+
when Esc
|
173
491
|
self.restback(high, wide, r, c)
|
174
492
|
RubyText.show_cursor
|
175
493
|
return []
|
176
|
-
when
|
494
|
+
when Enter
|
177
495
|
self.restback(high, wide, r, c)
|
178
496
|
RubyText.show_cursor
|
179
497
|
return selected.map {|i| items[i] }
|
@@ -186,13 +504,91 @@ module RubyText
|
|
186
504
|
end
|
187
505
|
end
|
188
506
|
|
507
|
+
# Simple yes/no decision
|
508
|
+
|
189
509
|
def yesno
|
190
510
|
# TODO: Accept YyNn
|
191
511
|
r, c = STDSCR.rc
|
192
512
|
num, str = STDSCR.menu(r: r, c: c+6, items: ["yes", "no"])
|
193
513
|
num == 0
|
194
514
|
end
|
515
|
+
|
516
|
+
# Menu to choose a single setting and retain it
|
517
|
+
|
518
|
+
def radio_menu(r: :center, c: :center, items:, curr: 0,
|
519
|
+
# Handle current value better?
|
520
|
+
border: true,
|
521
|
+
title: nil, fg: Green, bg: Black)
|
522
|
+
RubyText.hide_cursor
|
523
|
+
if items.is_a?(Hash)
|
524
|
+
results = items.values
|
525
|
+
items = items.keys
|
526
|
+
hash_flag = true
|
527
|
+
else
|
528
|
+
results = items
|
529
|
+
end
|
530
|
+
|
531
|
+
high = items.size
|
532
|
+
wide = items.map(&:length).max + 3
|
533
|
+
high += 2 if border
|
534
|
+
wide += 2 if border
|
535
|
+
|
536
|
+
tlen = title.length + 8 rescue 0
|
537
|
+
wide = [wide, tlen].max
|
538
|
+
row, col = self.coords(r, c)
|
539
|
+
row = row - high/2 if r == :center
|
540
|
+
col = col - wide/2 if c == :center
|
541
|
+
r, c = row, col
|
542
|
+
self.saveback(high, wide, r, c)
|
543
|
+
mr, mc = r+self.r0, c+self.c0
|
544
|
+
title = nil unless border
|
545
|
+
mwin = RubyText.window(high, wide, r: mr, c: mc, border: border,
|
546
|
+
fg: fg, bg: bg, title: title)
|
547
|
+
Curses.stdscr.keypad(true)
|
548
|
+
sel = curr
|
549
|
+
max = items.size - 1
|
550
|
+
loop do
|
551
|
+
RubyText.hide_cursor # FIXME should be unnecessary
|
552
|
+
items.each.with_index do |item, row|
|
553
|
+
mark = row == curr ? ">" : " "
|
554
|
+
mwin.go row, 0
|
555
|
+
style = (sel == row) ? :reverse : :normal
|
556
|
+
label = "#{mark} #{item}"
|
557
|
+
mwin.print fx(label, style)
|
558
|
+
end
|
559
|
+
ch = getch
|
560
|
+
case ch
|
561
|
+
when Up
|
562
|
+
sel -= 1 if sel > 0
|
563
|
+
when Down
|
564
|
+
sel += 1 if sel < max
|
565
|
+
when Esc
|
566
|
+
self.restback(high, wide, r, c)
|
567
|
+
RubyText.show_cursor
|
568
|
+
return [nil, nil]
|
569
|
+
when " "
|
570
|
+
mwin[curr, 0] = " "
|
571
|
+
mwin[sel, 0] = ">"
|
572
|
+
curr = sel
|
573
|
+
when Enter
|
574
|
+
self.restback(high, wide, r, c)
|
575
|
+
RubyText.show_cursor
|
576
|
+
choice = results[sel]
|
577
|
+
return [sel, choice] if choice.is_a? String
|
578
|
+
result = choice.call
|
579
|
+
return [nil, nil] if result.nil? || result.empty?
|
580
|
+
return result
|
581
|
+
else Curses.beep
|
582
|
+
end
|
583
|
+
RubyText.show_cursor
|
584
|
+
end
|
585
|
+
end
|
195
586
|
end
|
587
|
+
end
|
588
|
+
|
589
|
+
module RubyText
|
590
|
+
|
591
|
+
# Two-paned widget with menu on left, informtional area on right
|
196
592
|
|
197
593
|
def self.selector(win: STDSCR, r: 0, c: 0, rows: 10, cols: 20,
|
198
594
|
items:, fg: White, bg: Blue,
|
@@ -215,17 +611,17 @@ module RubyText
|
|
215
611
|
end
|
216
612
|
ch = getch
|
217
613
|
case ch
|
218
|
-
when
|
614
|
+
when Up
|
219
615
|
if sel > 0
|
220
616
|
sel -= 1
|
221
617
|
handler.call(sel, items[sel], win2)
|
222
618
|
end
|
223
|
-
when
|
619
|
+
when Down
|
224
620
|
if sel < max
|
225
621
|
sel += 1
|
226
622
|
handler.call(sel, items[sel], win2)
|
227
623
|
end
|
228
|
-
when
|
624
|
+
when Enter
|
229
625
|
if enter
|
230
626
|
del = enter.call(sel, items[sel], win2)
|
231
627
|
if del
|
@@ -233,7 +629,7 @@ module RubyText
|
|
233
629
|
raise
|
234
630
|
end
|
235
631
|
end
|
236
|
-
when
|
632
|
+
when Tab
|
237
633
|
Curses.flash
|
238
634
|
when quit # parameter
|
239
635
|
exit
|
@@ -243,5 +639,60 @@ module RubyText
|
|
243
639
|
rescue
|
244
640
|
retry
|
245
641
|
end
|
642
|
+
|
643
|
+
# "Menu" for checklists
|
644
|
+
|
645
|
+
def checklist(r: :center, c: :center,
|
646
|
+
items:, curr: 0, selected: [],
|
647
|
+
title: nil, sel_fg: Yellow, fg: White, bg: Blue)
|
648
|
+
RubyText.hide_cursor
|
649
|
+
high = items.size + 2
|
650
|
+
wide = items.map(&:length).max + 8
|
651
|
+
tlen = title.length + 8 rescue 0
|
652
|
+
wide = [wide, tlen].max
|
653
|
+
row, col = self.coords(r, c)
|
654
|
+
row = row - high/2 if r == :center
|
655
|
+
col = col - wide/2 if c == :center
|
656
|
+
r, c = row, col
|
657
|
+
self.saveback(high, wide, r, c)
|
658
|
+
mr, mc = r+self.r0, c+self.c0
|
659
|
+
mwin = RubyText.window(high, wide, r: mr, c: mc,
|
660
|
+
fg: fg, bg: bg, title: title)
|
661
|
+
Curses.stdscr.keypad(true)
|
662
|
+
sel = curr
|
663
|
+
max = items.size - 1
|
664
|
+
loop do
|
665
|
+
RubyText.hide_cursor # FIXME should be unnecessary
|
666
|
+
items.each.with_index do |item, row|
|
667
|
+
mwin.go row, 0
|
668
|
+
style = (sel == row) ? :reverse : :normal
|
669
|
+
color = selected.find {|x| x[0] == row } ? sel_fg : fg
|
670
|
+
label = "[ ]" + item
|
671
|
+
mwin.print fx(label, color, style)
|
672
|
+
end
|
673
|
+
ch = getch
|
674
|
+
case ch
|
675
|
+
when Up
|
676
|
+
sel -= 1 if sel > 0
|
677
|
+
when Down
|
678
|
+
sel += 1 if sel < max
|
679
|
+
when Esc
|
680
|
+
self.restback(high, wide, r, c)
|
681
|
+
RubyText.show_cursor
|
682
|
+
return []
|
683
|
+
when Enter
|
684
|
+
self.restback(high, wide, r, c)
|
685
|
+
RubyText.show_cursor
|
686
|
+
return selected.map {|i| items[i] }
|
687
|
+
when " "
|
688
|
+
selected << [sel, items[sel]]
|
689
|
+
sel += 1 if sel < max
|
690
|
+
else Curses.beep
|
691
|
+
end
|
692
|
+
RubyText.show_cursor
|
693
|
+
end
|
694
|
+
end
|
695
|
+
|
246
696
|
end
|
247
697
|
|
698
|
+
|