ncumbra 0.1.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 +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
|