ncumbra 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +25 -0
- data/Gemfile +6 -0
- data/LICENSE +21 -0
- data/README.md +48 -0
- data/README.md.bak +15 -0
- data/Rakefile +2 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/examples/ex1.rb +85 -0
- data/examples/ex2.rb +128 -0
- data/examples/ex21.rb +136 -0
- data/examples/ex3.rb +163 -0
- data/examples/ex4.rb +142 -0
- data/examples/ex5.rb +103 -0
- data/examples/exbox.rb +141 -0
- data/examples/exm1.rb +137 -0
- data/examples/keys.rb +67 -0
- data/examples/tt.rb +462 -0
- data/lib/umbra/box.rb +137 -0
- data/lib/umbra/button.rb +130 -0
- data/lib/umbra/buttongroup.rb +96 -0
- data/lib/umbra/checkbox.rb +42 -0
- data/lib/umbra/dialog.rb +214 -0
- data/lib/umbra/eventhandler.rb +134 -0
- data/lib/umbra/field.rb +503 -0
- data/lib/umbra/form.rb +473 -0
- data/lib/umbra/keymappinghandler.rb +96 -0
- data/lib/umbra/label.rb +95 -0
- data/lib/umbra/labeledfield.rb +97 -0
- data/lib/umbra/listbox.rb +384 -0
- data/lib/umbra/menu.rb +93 -0
- data/lib/umbra/messagebox.rb +348 -0
- data/lib/umbra/pad.rb +340 -0
- data/lib/umbra/radiobutton.rb +71 -0
- data/lib/umbra/textbox.rb +417 -0
- data/lib/umbra/togglebutton.rb +140 -0
- data/lib/umbra/version.rb +3 -0
- data/lib/umbra/widget.rb +220 -0
- data/lib/umbra/window.rb +270 -0
- data/lib/umbra.rb +47 -0
- data/umbra.gemspec +27 -0
- metadata +127 -0
@@ -0,0 +1,97 @@
|
|
1
|
+
# ----------------------------------------------------------------------------- #
|
2
|
+
# File: labeledfield.rb
|
3
|
+
# Description:
|
4
|
+
# Author: j kepler http://github.com/mare-imbrium/canis/
|
5
|
+
# Date: 2018-04-12 - 23:35
|
6
|
+
# License: MIT
|
7
|
+
# Last update: 2018-04-21 23:58
|
8
|
+
# ----------------------------------------------------------------------------- #
|
9
|
+
# labeledfield.rb Copyright (C) 2018 j kepler
|
10
|
+
require 'umbra/field'
|
11
|
+
module Umbra
|
12
|
+
# TODO should be able to add a mnemonic here for the label since the association exists
|
13
|
+
# TODO we should consider creating a Label, so user can have more control. Or allow user
|
14
|
+
# to supply a Label i/o a String ???
|
15
|
+
#
|
16
|
+
# NOTE: If using LabeledField in a messagebox, pls specify messagebox width explicitly
|
17
|
+
# since the width here is the field width, and messagebox has no way of knowing that we are
|
18
|
+
# placing a label too.
|
19
|
+
|
20
|
+
# Other options:
|
21
|
+
# This could contain a Labal and a Field and extend Widget. Actually, it could be LabeledWidget
|
22
|
+
# so that any other widget is sent in and associated with a a label.
|
23
|
+
#
|
24
|
+
class LabeledField < Field
|
25
|
+
# This stores a +String+ and prints it before the +Field+.
|
26
|
+
# This label is gauranteed to print to the left of the Field.
|
27
|
+
# This label prints on +lrow+ and +lcol+ if supplied, else it will print on the left of the field
|
28
|
+
# at +col+ minus the width of the label.
|
29
|
+
#
|
30
|
+
# It is initialized exactly like a Field, with the addition of label (and optionally label_color_pair,
|
31
|
+
# label_attr, and lcol, lrow)
|
32
|
+
#
|
33
|
+
attr_accessor :label # label of field, just a String
|
34
|
+
# if lrow and lcol are specified then label is printed exactly at that spot.
|
35
|
+
# If they are omitted, then label is printed on left of field. Omit the lcol if you want
|
36
|
+
# the fields to be aligned, one under another, with the labels right-aligned.
|
37
|
+
attr_accessor :lrow, :lcol # coordinates of the label
|
38
|
+
attr_accessor :label_color_pair # label of field color_pair
|
39
|
+
attr_accessor :label_attr # label of field attribute
|
40
|
+
attr_accessor :label_highlight_color_pair # label of field high color_pair
|
41
|
+
attr_accessor :label_highlight_attr # label of field high attribute
|
42
|
+
attr_accessor :mnemonic # mnemonic of field which shows up on label
|
43
|
+
attr_accessor :related_widget # to keep sync with label
|
44
|
+
def initialize config={}, &block
|
45
|
+
@related_widget = self
|
46
|
+
super
|
47
|
+
end
|
48
|
+
|
49
|
+
def repaint
|
50
|
+
return unless @repaint_required
|
51
|
+
_lrow = @lrow || @row
|
52
|
+
# the next was nice, but in some cases this goes out of screen. and the container
|
53
|
+
# only sets row and col for whatever is added, it does not know that lcol has to be
|
54
|
+
# taken into account
|
55
|
+
_lcol = @lcol || (@col - @label.length - 2)
|
56
|
+
if _lcol < 1
|
57
|
+
@lcol = @col
|
58
|
+
@col = @lcol + @label.length + 2
|
59
|
+
_lcol = @lcol
|
60
|
+
end
|
61
|
+
|
62
|
+
=begin
|
63
|
+
# This actually uses the col of the field, and pushes field ahead. We need to get the above to work.
|
64
|
+
unless @lcol
|
65
|
+
@lcol = @col
|
66
|
+
@col = @lcol + @label.length + 2
|
67
|
+
end
|
68
|
+
_lcol = @lcol
|
69
|
+
=end
|
70
|
+
lcolor = @label_color_pair || CP_BLACK
|
71
|
+
lattr = @label_attr || NORMAL
|
72
|
+
|
73
|
+
# this gives the effect of `pine` (aka alpine) email client, of highlighting the label
|
74
|
+
# when the field is in focus.
|
75
|
+
if @state == :HIGHLIGHTED
|
76
|
+
lcolor = @label_highlight_color_pair || lcolor
|
77
|
+
lattr = @label_highlight_attr || lattr
|
78
|
+
end
|
79
|
+
|
80
|
+
$log.debug " repaint labeledfield lrow: #{_lrow} lcol #{_lcol} "
|
81
|
+
# print the label
|
82
|
+
@graphic.printstring _lrow, _lcol, @label, lcolor, lattr
|
83
|
+
# print the mnemonic
|
84
|
+
if @mnemonic
|
85
|
+
index = label.index(@mnemonic) || label.index(@mnemonic.swapcase)
|
86
|
+
if index
|
87
|
+
y = _lcol + index
|
88
|
+
x = _lrow
|
89
|
+
@graphic.mvchgat(x, y, max=1, FFI::NCurses::A_BOLD|UNDERLINE, FFI::NCurses.COLOR_PAIR(lcolor || 1), nil)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# print the field
|
94
|
+
super
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end # module
|
@@ -0,0 +1,384 @@
|
|
1
|
+
require 'umbra/widget'
|
2
|
+
# ----------------------------------------------------------------------------- #
|
3
|
+
# File: listbox.rb
|
4
|
+
# Description: list widget that displays a list of items
|
5
|
+
# Author: j kepler http://github.com/mare-imbrium/canis/
|
6
|
+
# Date: 2018-03-19
|
7
|
+
# License: MIT
|
8
|
+
# Last update: 2018-04-20 12:35
|
9
|
+
# ----------------------------------------------------------------------------- #
|
10
|
+
# listbox.rb Copyright (C) 2012-2018 j kepler
|
11
|
+
# == TODO
|
12
|
+
# currently only do single selection, we may do multiple at a later date.
|
13
|
+
# insert/delete a row ??
|
14
|
+
# ----------------
|
15
|
+
module Umbra
|
16
|
+
class Listbox < Widget
|
17
|
+
attr_reader :list # list containing data
|
18
|
+
#
|
19
|
+
# index of focussed row, starting 0, index into the list supplied
|
20
|
+
attr_reader :current_index
|
21
|
+
|
22
|
+
attr_accessor :selection_key # key used to select a row
|
23
|
+
attr_accessor :selected_index # row selected, may change to plural
|
24
|
+
attr_accessor :selected_color_pair # row selected color_pair
|
25
|
+
attr_accessor :selected_attr # row selected color_pair
|
26
|
+
attr_accessor :selected_mark # row selected character
|
27
|
+
attr_accessor :unselected_mark # row unselected character (usually blank)
|
28
|
+
attr_accessor :current_mark # row current character (default is >)
|
29
|
+
|
30
|
+
|
31
|
+
def initialize config={}, &block
|
32
|
+
@focusable = false
|
33
|
+
@editable = false
|
34
|
+
@pstart = 0 # which row does printing start from
|
35
|
+
@current_index = 0 # index of row on which cursor is
|
36
|
+
@selected_index = nil # index of row selected
|
37
|
+
@selection_key = ?s.getbyte(0) # 's' used to select/deselect
|
38
|
+
@selected_color_pair = CP_RED
|
39
|
+
@selected_attr = REVERSE
|
40
|
+
@row_offset = 0
|
41
|
+
@selected_mark = 'x' # row selected character
|
42
|
+
@unselected_mark = ' ' # row unselected character (usually blank)
|
43
|
+
@current_mark = '>' # row current character (default is >)
|
44
|
+
register_events([:LEAVE_ROW, :ENTER_ROW, :LIST_SELECTION_EVENT])
|
45
|
+
super
|
46
|
+
|
47
|
+
map_keys
|
48
|
+
@pcol = 0
|
49
|
+
@repaint_required = true
|
50
|
+
end
|
51
|
+
# set list of data to be displayed.
|
52
|
+
# NOTE this can be called again and again, so we need to take care of change in size of data
|
53
|
+
# as well as things like current_index and selected_index or indices.
|
54
|
+
# clear the listbox is list is smaller or empty FIXME
|
55
|
+
def list=(alist)
|
56
|
+
if !alist or alist.size == 0
|
57
|
+
$log.debug " setting focusable to false in listbox "
|
58
|
+
self.focusable=(false)
|
59
|
+
# should we return here
|
60
|
+
else
|
61
|
+
$log.debug " setting focusable to true in listbox #{alist.count} "
|
62
|
+
self.focusable=(true)
|
63
|
+
end
|
64
|
+
@list = alist
|
65
|
+
@repaint_required = true
|
66
|
+
@pstart = @current_index = 0
|
67
|
+
@selected_index = nil
|
68
|
+
@pcol = 0
|
69
|
+
fire_handler(:CHANGED, alist)
|
70
|
+
end
|
71
|
+
# Calculate dimensions as late as possible, since we can have some other container such as a box,
|
72
|
+
# determine the dimensions after creation.
|
73
|
+
private def _calc_dimensions
|
74
|
+
raise "Dimensions not supplied to listbox" if @row.nil? or @col.nil? or @width.nil? or @height.nil?
|
75
|
+
@_calc_dimensions = true
|
76
|
+
@int_width = @width # internal width NOT USED ELSEWHERE
|
77
|
+
@int_height = @height # internal height USED HERE ONLy REDUNDANT FIXME
|
78
|
+
@scroll_lines ||= @int_height/2
|
79
|
+
@page_lines = @int_height
|
80
|
+
end
|
81
|
+
# Each row can be in one of the following states:
|
82
|
+
# 1. HIGHLIGHTED: cursor is on the row, and the list is focussed (user is in it)
|
83
|
+
# 2. CURRENT : cursor was on this row, now user has exited the list
|
84
|
+
# 3. SELECTED : user has selected this row (this can also have above two states actually)
|
85
|
+
# 4. NORMAL : All other rows: not selected, not under cursor
|
86
|
+
# returns color, attrib and left marker for given row
|
87
|
+
# @param index of row in the list
|
88
|
+
# @param state of row in the list (see above states)
|
89
|
+
def _format_color index, state
|
90
|
+
arr = case state
|
91
|
+
when :SELECTED
|
92
|
+
[@selected_color_pair, @selected_attr]
|
93
|
+
when :HIGHLIGHTED
|
94
|
+
[@highlight_color_pair || CP_WHITE, @highlight_attr || REVERSE]
|
95
|
+
when :CURRENT
|
96
|
+
[@color_pair, @attr]
|
97
|
+
when :NORMAL
|
98
|
+
#@alt_color_pair ||= create_color_pair(COLOR_BLUE, COLOR_WHITE)
|
99
|
+
_color = CP_CYAN
|
100
|
+
_color = CP_WHITE if index % 2 == 0
|
101
|
+
#_color = @alt_color_pair if index % 2 == 0
|
102
|
+
[@color_pair || _color, @attr || NORMAL]
|
103
|
+
end
|
104
|
+
return arr
|
105
|
+
end
|
106
|
+
# do the actual printing of the row, depending on index and state
|
107
|
+
# This method starts with underscore since it is only required to be overriden
|
108
|
+
# if an object has special printing needs.
|
109
|
+
def _print_row(win, row, col, str, index, state)
|
110
|
+
arr = _format_color index, state
|
111
|
+
win.printstring(row, col, str, arr[0], arr[1])
|
112
|
+
end
|
113
|
+
def _format_mark index, state
|
114
|
+
mark = case state
|
115
|
+
when :SELECTED
|
116
|
+
@selected_mark
|
117
|
+
when :HIGHLIGHTED, :CURRENT
|
118
|
+
@current_mark
|
119
|
+
else
|
120
|
+
@unselected_mark
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def repaint
|
125
|
+
_calc_dimensions unless @_calc_dimensions
|
126
|
+
|
127
|
+
return unless @repaint_required
|
128
|
+
win = @graphic
|
129
|
+
r,c = @row, @col
|
130
|
+
_attr = @attr || NORMAL
|
131
|
+
_color = @color_pair || CP_WHITE
|
132
|
+
curpos = 1
|
133
|
+
coffset = 0
|
134
|
+
width = @width
|
135
|
+
#files = @list
|
136
|
+
files = getvalue
|
137
|
+
|
138
|
+
ht = @height
|
139
|
+
cur = @current_index
|
140
|
+
st = pstart = @pstart # previous start
|
141
|
+
pend = pstart + ht -1 # previous end
|
142
|
+
if cur > pend
|
143
|
+
st = (cur -ht) + 1
|
144
|
+
elsif cur < pstart
|
145
|
+
st = cur
|
146
|
+
end
|
147
|
+
$log.debug "LISTBOX: cur = #{cur} st = #{st} pstart = #{pstart} pend = #{pend} listsize = #{@list.size} "
|
148
|
+
hl = cur
|
149
|
+
y = 0
|
150
|
+
ctr = 0
|
151
|
+
filler = " "*(width)
|
152
|
+
files.each_with_index {|_f, y|
|
153
|
+
next if y < st
|
154
|
+
f = _format_value(_f)
|
155
|
+
|
156
|
+
# determine state of this row: NORMAL CURRENT HIGHLIGHTED SELECTED {{{
|
157
|
+
_st = :NORMAL
|
158
|
+
if y == hl # current row, row on which cursor is or was
|
159
|
+
# highlight only if object is focussed, otherwise just show mark
|
160
|
+
if @state == :HIGHLIGHTED
|
161
|
+
_st = :HIGHLIGHTED
|
162
|
+
else
|
163
|
+
# cursor was on this row, but now user has tabbed out
|
164
|
+
_st = :CURRENT
|
165
|
+
end
|
166
|
+
curpos = ctr
|
167
|
+
end
|
168
|
+
if y == @selected_index
|
169
|
+
_st = :SELECTED
|
170
|
+
end # }}}
|
171
|
+
#colr, attr, mark = _format_color y, _st
|
172
|
+
|
173
|
+
mark = _format_mark(y, _st)
|
174
|
+
=begin
|
175
|
+
mark = case _st
|
176
|
+
when :SELECTED
|
177
|
+
@selected_mark
|
178
|
+
when :HIGHLIGHTED, :CURRENT
|
179
|
+
@current_mark
|
180
|
+
else
|
181
|
+
@unselected_mark
|
182
|
+
end
|
183
|
+
=end
|
184
|
+
|
185
|
+
ff = "#{mark} #{f}"
|
186
|
+
# truncate string to width, and handle panning {{{
|
187
|
+
if ff
|
188
|
+
if ff.size > width
|
189
|
+
# pcol can be greater than width then we get null
|
190
|
+
if @pcol < ff.size
|
191
|
+
ff = ff[@pcol..@pcol+width-1]
|
192
|
+
else
|
193
|
+
ff = ""
|
194
|
+
end
|
195
|
+
else
|
196
|
+
if @pcol < ff.size
|
197
|
+
ff = ff[@pcol..-1]
|
198
|
+
else
|
199
|
+
ff = ""
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end # }}}
|
203
|
+
ff = "" unless ff
|
204
|
+
|
205
|
+
win.printstring(ctr + r, coffset+c, filler, _color ) # print filler
|
206
|
+
#win.printstring(ctr + r, coffset+c, ff, colr, attr)
|
207
|
+
_print_row(win, ctr + r, coffset+c, ff, y, _st)
|
208
|
+
ctr += 1
|
209
|
+
@pstart = st
|
210
|
+
break if ctr >= ht
|
211
|
+
}
|
212
|
+
## if counter < ht then we need to clear the rest in case there was data earlier {{{
|
213
|
+
if ctr < ht
|
214
|
+
while ctr < ht
|
215
|
+
win.printstring(ctr + r, coffset+c, filler, _color )
|
216
|
+
ctr += 1
|
217
|
+
end
|
218
|
+
end # }}}
|
219
|
+
@row_offset = curpos #+ border_offset
|
220
|
+
@col_offset = coffset
|
221
|
+
@repaint_required = false
|
222
|
+
end
|
223
|
+
|
224
|
+
def getvalue
|
225
|
+
@list
|
226
|
+
end
|
227
|
+
|
228
|
+
#
|
229
|
+
# how to convert the line of the array to a simple String.
|
230
|
+
# This is only required to be overridden if the list passed in is not an array of Strings.
|
231
|
+
# @param the current row which could be a string or array or whatever was passed in in +list=()+.
|
232
|
+
# @return [String] string to print. A String must be returned.
|
233
|
+
def _format_value line
|
234
|
+
line
|
235
|
+
end
|
236
|
+
#alias :_format_value :getvalue_for_paint
|
237
|
+
|
238
|
+
|
239
|
+
def map_keys
|
240
|
+
bind_keys([?k,FFI::NCurses::KEY_UP], "Up") { cursor_up }
|
241
|
+
bind_keys([?j,FFI::NCurses::KEY_DOWN], "Down") { cursor_down }
|
242
|
+
bind_keys([?l,FFI::NCurses::KEY_RIGHT], "Right") { cursor_forward }
|
243
|
+
bind_keys([?h,FFI::NCurses::KEY_LEFT], "Left") { cursor_backward }
|
244
|
+
bind_key(?g, 'goto_start') { goto_start }
|
245
|
+
bind_key(?G, 'goto_end') { goto_end }
|
246
|
+
bind_key(FFI::NCurses::KEY_CTRL_A, 'cursor_home') { cursor_home }
|
247
|
+
bind_key(FFI::NCurses::KEY_CTRL_E, 'cursor_end') { cursor_end }
|
248
|
+
bind_key(FFI::NCurses::KEY_CTRL_F, 'page_forward') { page_forward }
|
249
|
+
bind_key(32, 'page_forward') { page_forward }
|
250
|
+
bind_key(FFI::NCurses::KEY_CTRL_B, 'page_backward'){ page_backward }
|
251
|
+
bind_key(FFI::NCurses::KEY_CTRL_U, 'scroll_up') { scroll_up }
|
252
|
+
bind_key(FFI::NCurses::KEY_CTRL_D, 'scroll_down') { scroll_down }
|
253
|
+
return if @keys_mapped
|
254
|
+
end
|
255
|
+
|
256
|
+
def on_enter
|
257
|
+
super
|
258
|
+
on_enter_row @current_index
|
259
|
+
# basically I need to only highlight the current index, not repaint all OPTIMIZE
|
260
|
+
touch ; repaint
|
261
|
+
end
|
262
|
+
def on_leave
|
263
|
+
super
|
264
|
+
on_leave_row @current_index
|
265
|
+
# basically I need to only unhighlight the current index, not repaint all OPTIMIZE
|
266
|
+
touch ; repaint
|
267
|
+
end
|
268
|
+
# called when object leaves a row and when object is exited.
|
269
|
+
def on_leave_row index
|
270
|
+
fire_handler(:LEAVE_ROW, [index]) # 2018-03-26 - improve this
|
271
|
+
end
|
272
|
+
# called whenever a row entered.
|
273
|
+
# Call when object entered, also.
|
274
|
+
def on_enter_row index
|
275
|
+
fire_handler(:ENTER_ROW, [@current_index]) # 2018-03-26 - improve this
|
276
|
+
end
|
277
|
+
def cursor_up
|
278
|
+
@current_index -= 1
|
279
|
+
end
|
280
|
+
# go to next row
|
281
|
+
def cursor_down
|
282
|
+
@current_index += 1
|
283
|
+
end
|
284
|
+
# position cursor at start of field
|
285
|
+
def cursor_home
|
286
|
+
@curpos = 0 # UNUSED RIGHT NOW
|
287
|
+
@pcol = 0
|
288
|
+
end
|
289
|
+
# goto end of line.
|
290
|
+
# This should be consistent with moving the cursor to the end of the row with right arrow
|
291
|
+
def cursor_end
|
292
|
+
blen = current_row().length
|
293
|
+
if blen < @width
|
294
|
+
@pcol = 0
|
295
|
+
else
|
296
|
+
@pcol = blen-@width+2 # 2 is due to mark and space
|
297
|
+
end
|
298
|
+
@curpos = blen # this is position in array where editing or motion is to happen regardless of what you see
|
299
|
+
# regardless of pcol (panning)
|
300
|
+
end
|
301
|
+
# returns current row as String
|
302
|
+
# 2018-04-11 - NOTE this may not be a String so we convert it to string before returning
|
303
|
+
# @return [String] row the cursor/user is on
|
304
|
+
def current_row
|
305
|
+
s = @list[@current_index]
|
306
|
+
_format_value s
|
307
|
+
end
|
308
|
+
def cursor_forward
|
309
|
+
blen = current_row().size-1
|
310
|
+
@pcol += 1 if @pcol < blen
|
311
|
+
end
|
312
|
+
def cursor_backward
|
313
|
+
@pcol -= 1 if @pcol > 0
|
314
|
+
end
|
315
|
+
# go to start of file (first line)
|
316
|
+
def goto_start
|
317
|
+
@current_index = 0
|
318
|
+
@pcol = @curpos = 0
|
319
|
+
end
|
320
|
+
# go to end of file (last line)
|
321
|
+
def goto_end
|
322
|
+
@current_index = @list.size-1
|
323
|
+
@pcol = @curpos = 0
|
324
|
+
end
|
325
|
+
def scroll_down
|
326
|
+
@current_index += @scroll_lines
|
327
|
+
end
|
328
|
+
def scroll_up
|
329
|
+
@current_index -= @scroll_lines
|
330
|
+
end
|
331
|
+
def page_backward
|
332
|
+
@current_index -= @page_lines
|
333
|
+
end
|
334
|
+
def page_forward
|
335
|
+
@current_index += @page_lines
|
336
|
+
end
|
337
|
+
# listbox key handling
|
338
|
+
def handle_key ch
|
339
|
+
old_current_index = @current_index
|
340
|
+
old_pcol = @pcol
|
341
|
+
case ch
|
342
|
+
when @selection_key
|
343
|
+
@repaint_required = true
|
344
|
+
if @selected_index == @current_index
|
345
|
+
@selected_index = nil
|
346
|
+
else
|
347
|
+
@selected_index = @current_index
|
348
|
+
end
|
349
|
+
fire_handler :LIST_SELECTION_EVENT, self # use selected_index to know which one
|
350
|
+
else
|
351
|
+
ret = super
|
352
|
+
return ret
|
353
|
+
end
|
354
|
+
ensure
|
355
|
+
@current_index = 0 if @current_index < 0
|
356
|
+
@current_index = @list.size-1 if @current_index >= @list.size
|
357
|
+
if @current_index != old_current_index
|
358
|
+
on_leave_row old_current_index
|
359
|
+
on_enter_row @current_index
|
360
|
+
@repaint_required = true
|
361
|
+
end
|
362
|
+
@repaint_required = true if old_pcol != @pcol
|
363
|
+
end
|
364
|
+
|
365
|
+
def command *args, &block
|
366
|
+
bind_event :ENTER_ROW, *args, &block
|
367
|
+
end
|
368
|
+
def print_border row, col, height, width, color, att=FFI::NCurses::A_NORMAL
|
369
|
+
raise "deprecated"
|
370
|
+
pointer = @graphic.pointer
|
371
|
+
FFI::NCurses.wattron(pointer, FFI::NCurses.COLOR_PAIR(color) | att)
|
372
|
+
FFI::NCurses.mvwaddch pointer, row, col, FFI::NCurses::ACS_ULCORNER
|
373
|
+
FFI::NCurses.mvwhline( pointer, row, col+1, FFI::NCurses::ACS_HLINE, width-2)
|
374
|
+
FFI::NCurses.mvwaddch pointer, row, col+width-1, FFI::NCurses::ACS_URCORNER
|
375
|
+
FFI::NCurses.mvwvline( pointer, row+1, col, FFI::NCurses::ACS_VLINE, height-2)
|
376
|
+
|
377
|
+
FFI::NCurses.mvwaddch pointer, row+height-1, col, FFI::NCurses::ACS_LLCORNER
|
378
|
+
FFI::NCurses.mvwhline(pointer, row+height-1, col+1, FFI::NCurses::ACS_HLINE, width-2)
|
379
|
+
FFI::NCurses.mvwaddch pointer, row+height-1, col+width-1, FFI::NCurses::ACS_LRCORNER
|
380
|
+
FFI::NCurses.mvwvline( pointer, row+1, col+width-1, FFI::NCurses::ACS_VLINE, height-2)
|
381
|
+
FFI::NCurses.wattroff(pointer, FFI::NCurses.COLOR_PAIR(color) | att)
|
382
|
+
end
|
383
|
+
end
|
384
|
+
end # module
|
data/lib/umbra/menu.rb
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
# ----------------------------------------------------------------------------- #
|
2
|
+
# File: menu.rb
|
3
|
+
# Description: a popup menu like mc/midnight commander
|
4
|
+
# Author: j kepler http://github.com/mare-imbrium/canis/
|
5
|
+
# Date: 2018-03-13
|
6
|
+
# License: MIT
|
7
|
+
# Last update: 2018-04-16 15:20
|
8
|
+
# ----------------------------------------------------------------------------- #
|
9
|
+
# menu.rb Copyright (C) 2012-2018 j kepler
|
10
|
+
|
11
|
+
# a midnight commander like mc_menu
|
12
|
+
# Pass a hash of key and label.
|
13
|
+
# menu will only accept keys or arrow keys or C-c Esc to cancel
|
14
|
+
# returns nil if C-c or Esc pressed.
|
15
|
+
# Otherwise returns character pressed.
|
16
|
+
# == TODO
|
17
|
+
# depends on our window class which is minimal.
|
18
|
+
# [ ] cursor should show on the row that is highlighted
|
19
|
+
# [ ] Can we remove that dependency so this is independent
|
20
|
+
# Currently, we paint window each time user pressed up or down, but we can just repaint the attribute
|
21
|
+
# [ ] width of array items not checked. We could do that or have user pass it in.
|
22
|
+
# [ ] we are not scrolling if user sends in a large number of items. we should cap it to 10 or 20
|
23
|
+
# == CHANGELOG
|
24
|
+
#
|
25
|
+
require 'umbra/window'
|
26
|
+
module Umbra
|
27
|
+
class Menu
|
28
|
+
|
29
|
+
def initialize title, hash, config={}
|
30
|
+
|
31
|
+
@list = hash.values
|
32
|
+
@keys = hash.keys.collect { |x| x.to_s }
|
33
|
+
@hash = hash
|
34
|
+
bkgd = config[:bkgd] || FFI::NCurses.COLOR_PAIR(14) | BOLD
|
35
|
+
@attr = BOLD
|
36
|
+
@color_pair = config[:color_pair] || 14
|
37
|
+
ht = @list.size+2
|
38
|
+
wid = config[:width] || 40
|
39
|
+
top = (FFI::NCurses.LINES - ht)/2
|
40
|
+
left = (FFI::NCurses.COLS - wid)/2
|
41
|
+
@window = Window.new(ht, wid, top, left)
|
42
|
+
@window.wbkgd(bkgd)
|
43
|
+
@window.box
|
44
|
+
@window.title(title)
|
45
|
+
@current = 0
|
46
|
+
print_items @hash
|
47
|
+
end
|
48
|
+
def print_items hash
|
49
|
+
ix = 0
|
50
|
+
hash.each_pair {|k, val|
|
51
|
+
attr = @attr
|
52
|
+
attr = REVERSE if ix == @current
|
53
|
+
@window.printstring(ix+1 , 2, "#{k} #{val}", @color_pair, attr )
|
54
|
+
ix += 1
|
55
|
+
}
|
56
|
+
@window.refresh
|
57
|
+
end
|
58
|
+
def getkey
|
59
|
+
ch = 0
|
60
|
+
char = nil
|
61
|
+
begin
|
62
|
+
while (ch = @window.getkey) != FFI::NCurses::KEY_CTRL_C
|
63
|
+
break if ch == 27 # ESC
|
64
|
+
tmpchar = FFI::NCurses.keyname(ch) rescue '?'
|
65
|
+
if @keys.include? tmpchar
|
66
|
+
$log.debug " menu #{tmpchar.class}:#{tmpchar} "
|
67
|
+
char = ch.chr
|
68
|
+
$log.debug " menu #{ch.class}:#{char} "
|
69
|
+
#char = tmpchar
|
70
|
+
break
|
71
|
+
end
|
72
|
+
case ch
|
73
|
+
when FFI::NCurses::KEY_DOWN
|
74
|
+
@current += 1
|
75
|
+
when FFI::NCurses::KEY_UP
|
76
|
+
@current -= 1
|
77
|
+
when FFI::NCurses::KEY_RETURN
|
78
|
+
char = @keys[@current]
|
79
|
+
break
|
80
|
+
end
|
81
|
+
@current = 0 if @current < 0
|
82
|
+
@current = @list.size-1 if @current >= @list.size
|
83
|
+
print_items @hash
|
84
|
+
|
85
|
+
# trap arrow keys here
|
86
|
+
end
|
87
|
+
ensure
|
88
|
+
@window.destroy
|
89
|
+
end
|
90
|
+
return char
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end # module
|