rubytext 0.1.22 → 0.1.26

Sign up to get free protection for your applications and to get access to all the features.
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