rubytext 0.1.22 → 0.1.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/menu.rb CHANGED
@@ -1,17 +1,331 @@
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, fg: Green, bg: Black)
5
- r, c = 0, 0
6
- high = 1
7
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)
8
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
9
326
  if items.is_a?(Hash)
10
- results = items.values
11
- items = items.keys
327
+ results, items = items.values, items.keys
12
328
  hash_flag = true
13
- else
14
- results = items
15
329
  end
16
330
 
17
331
  width = 0 # total width
@@ -45,25 +359,30 @@ module RubyText
45
359
  when Esc, " " # spacebar also quits
46
360
  self.restback(high, width, r, c)
47
361
  RubyText.show_cursor
362
+ STDSCR.go r, c
48
363
  return [nil, nil]
49
364
  when Down, Enter
50
365
  self.restback(high, width, r, c)
51
366
  RubyText.show_cursor
367
+ STDSCR.go r, c
52
368
  choice = results[sel]
53
369
  return [sel, choice] if choice.is_a? String
54
370
  result = choice.call
55
- next if result.nil?
56
- next if result.empty?
57
- return result
58
- else Curses.beep
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
59
376
  end
60
377
  RubyText.show_cursor
61
378
  end
62
379
  end
63
380
 
381
+ # Simple menu with rows of strings (or Procs)
382
+
64
383
  def menu(r: :center, c: :center, items:, curr: 0,
65
- border: true,
66
- title: nil, fg: Green, bg: Black)
384
+ border: true, sticky: false,
385
+ title: nil, fg: Green, bg: Black, wrap: false)
67
386
  RubyText.hide_cursor
68
387
  if items.is_a?(Hash)
69
388
  results = items.values
@@ -103,15 +422,23 @@ module RubyText
103
422
  ch = getch
104
423
  case ch
105
424
  when Up
106
- sel -= 1 if sel > 0
107
- when Down
108
- sel += 1 if sel < max
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
109
436
  when Esc
110
437
  self.restback(high, wide, r, c)
111
438
  RubyText.show_cursor
112
439
  return [nil, nil]
113
440
  when Enter
114
- self.restback(high, wide, r, c)
441
+ self.restback(high, wide, r, c) unless sticky
115
442
  RubyText.show_cursor
116
443
  choice = results[sel]
117
444
  return [sel, choice] if choice.is_a? String
@@ -124,6 +451,8 @@ module RubyText
124
451
  end
125
452
  end
126
453
 
454
+ # Menu for multiple selections (buggy/unused?)
455
+
127
456
  def multimenu(r: :center, c: :center,
128
457
  items:, curr: 0, selected: [],
129
458
  title: nil, sel_fg: Yellow, fg: White, bg: Blue)
@@ -175,6 +504,8 @@ module RubyText
175
504
  end
176
505
  end
177
506
 
507
+ # Simple yes/no decision
508
+
178
509
  def yesno
179
510
  # TODO: Accept YyNn
180
511
  r, c = STDSCR.rc
@@ -182,6 +513,8 @@ module RubyText
182
513
  num == 0
183
514
  end
184
515
 
516
+ # Menu to choose a single setting and retain it
517
+
185
518
  def radio_menu(r: :center, c: :center, items:, curr: 0,
186
519
  # Handle current value better?
187
520
  border: true,
@@ -251,6 +584,11 @@ module RubyText
251
584
  end
252
585
  end
253
586
  end
587
+ end
588
+
589
+ module RubyText
590
+
591
+ # Two-paned widget with menu on left, informtional area on right
254
592
 
255
593
  def self.selector(win: STDSCR, r: 0, c: 0, rows: 10, cols: 20,
256
594
  items:, fg: White, bg: Blue,
@@ -301,5 +639,60 @@ module RubyText
301
639
  rescue
302
640
  retry
303
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
+
304
696
  end
305
697
 
698
+
data/lib/navigation.rb CHANGED
@@ -1,5 +1,9 @@
1
+ # Reopening: Coordinate handling (1-based!)
2
+
1
3
  class RubyText::Window
2
4
 
5
+ # Handle special coordinate names (symbols)
6
+
3
7
  def coords(r, c)
4
8
  r = case
5
9
  when r == :center
@@ -24,10 +28,17 @@ class RubyText::Window
24
28
  [r, c]
25
29
  end
26
30
 
31
+ # Go to specified row/column in current window
32
+
27
33
  def goto(r, c) # only accepts numbers!
28
34
  @cwin.setpos(r, c)
35
+ @cwin.refresh
29
36
  end
30
37
 
38
+
39
+ # Go to specified row/column in current window,
40
+ # execute block, and return cursor
41
+
31
42
  def go(r0, c0)
32
43
  r, c = coords(r0, c0)
33
44
  save = self.rc
@@ -38,60 +49,84 @@ class RubyText::Window
38
49
  end
39
50
  end
40
51
 
52
+ # Move cursor up
53
+
41
54
  def up(n=1)
42
55
  r, c = rc
43
56
  go r-n, c
44
57
  end
45
58
 
59
+ # Move cursor down
60
+
46
61
  def down(n=1)
47
62
  r, c = rc
48
63
  go r+n, c
49
64
  end
50
65
 
66
+ # Move cursor left
67
+
51
68
  def left(n=1)
52
69
  r, c = rc
53
70
  go r, c-n
54
71
  end
55
72
 
73
+ # Move cursor right
74
+
56
75
  def right(n=1)
57
76
  r, c = rc
58
77
  go r, c+n
59
78
  end
60
79
 
80
+ # Move cursor to top of window
81
+
61
82
  def top
62
83
  r, c = rc
63
84
  go 0, c
64
85
  end
65
86
 
87
+ # Move cursor to bottom of window
88
+
66
89
  def bottom
67
90
  r, c = rc
68
91
  rmax = self.rows - 1
69
92
  go rmax, c
70
93
  end
71
94
 
95
+ # Move cursor to top of window
96
+
72
97
  def up!
73
98
  top
74
99
  end
75
100
 
101
+ # Move cursor to bottom of window
102
+
76
103
  def down!
77
104
  bottom
78
105
  end
79
106
 
107
+ # Move cursor to far left of window
108
+
80
109
  def left!
81
110
  r, c = rc
82
111
  go r, 0
83
112
  end
84
113
 
114
+ # Move cursor to far left of window
115
+
85
116
  def right!
86
117
  r, c = rc
87
118
  cmax = self.cols - 1
88
119
  go r, cmax
89
120
  end
90
121
 
122
+ # Move cursor to home (upper left)
123
+
91
124
  def home
92
125
  go 0, 0
93
126
  end
94
127
 
128
+ # Return current row/column
129
+
95
130
  def rc
96
131
  [@cwin.cury, @cwin.curx]
97
132
  end
data/lib/output.rb CHANGED
@@ -123,17 +123,31 @@ class RubyText::Window
123
123
  $stdscr = STDSCR
124
124
  end
125
125
 
126
+ =begin
127
+ def go(r0, c0)
128
+ r, c = coords(r0, c0)
129
+ save = self.rc
130
+ goto r, c
131
+ if block_given?
132
+ yield
133
+ goto *save
134
+ end
135
+ end
136
+ =end
137
+
126
138
  def [](r, c)
127
- ch = nil
128
- go(r, c) { ch = @cwin.inch }
129
- debug "ch = #{ch} ch.chr = #{ch.chr}"
139
+ r0, c0 = self.rc
140
+ @cwin.setpos(r, c)
141
+ ch = @cwin.inch
142
+ @cwin.setpos(r0, c0)
130
143
  ch.chr
131
144
  end
132
145
 
133
146
  def []=(r, c, char)
147
+ r0, c0 = self.rc
134
148
  @cwin.setpos(r, c)
135
149
  @cwin.addch(char[0].ord|Curses::A_NORMAL)
136
- @cwin.setpos(r, c)
150
+ @cwin.setpos(r0, c0)
137
151
  @cwin.refresh
138
152
  end
139
153
 
@@ -1,6 +1,6 @@
1
1
 
2
2
  module RubyText
3
- VERSION = "0.1.22"
3
+ VERSION = "0.1.26"
4
4
 
5
5
  Path = File.expand_path(File.join(File.dirname(__FILE__)))
6
6
  end
data/lib/settings.rb CHANGED
@@ -9,7 +9,7 @@ module RubyText
9
9
  @current = @defaults.dup
10
10
  @stack = []
11
11
  @stack.push @current # Note: Never let stack be empty
12
- set_curses(@current) # Set them for real
12
+ set_curses(**@current) # Set them for real
13
13
  # FIXME To be continued...
14
14
  end
15
15
 
@@ -39,7 +39,7 @@ module RubyText
39
39
  cursor ||= @current[:cursor]
40
40
  @stack.push @current
41
41
  @current = {raw: raw, echo: echo, cbreak: cbreak, keypad: keypad, cursor: cursor}
42
- set_curses(@current)
42
+ set_curses(**@current)
43
43
  end
44
44
 
45
45
  def reset_boolean
@@ -57,7 +57,7 @@ module RubyText
57
57
  sym0 = val ? sym : str[1..-1].to_sym
58
58
  list[sym0] = val
59
59
  end
60
- set_boolean(list)
60
+ set_boolean(**list)
61
61
  # allow a block here?
62
62
  end
63
63
 
@@ -101,6 +101,7 @@ module RubyText
101
101
  Object.const_set(:STDSCR, main) unless defined? STDSCR
102
102
  $stdscr = STDSCR # FIXME global needed?
103
103
  Object.include(WindowIO)
104
+ Curses.ESCDELAY = 10
104
105
  @started = true
105
106
  # rescue => err
106
107
  # puts(err.inspect)
@@ -129,7 +130,7 @@ module RubyText
129
130
  if name[0] == '_'
130
131
  Curses.send(name[1..-1], *args)
131
132
  else
132
- raise "#{name} #{args.inspect}" # NoMethodError
133
+ raise "Missing: #{name} #{args.inspect}" # NoMethodError
133
134
  end
134
135
  end
135
136