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,348 @@
|
|
1
|
+
# ----------------------------------------------------------------------------- #
|
2
|
+
# File: messagebox.rb
|
3
|
+
# Description: a small window with a list or message or fields and buttons which pops up.
|
4
|
+
# Author: j kepler http://github.com/mare-imbrium/canis/
|
5
|
+
# Date: 2018-04-13 - 23:10
|
6
|
+
# License: MIT
|
7
|
+
# Last update: 2018-04-21 14:44
|
8
|
+
# ----------------------------------------------------------------------------- #
|
9
|
+
# YFF Copyright (C) 2012-2018 j kepler
|
10
|
+
require 'umbra/window'
|
11
|
+
require 'umbra/form'
|
12
|
+
require 'umbra/widget'
|
13
|
+
require 'umbra/button'
|
14
|
+
require 'umbra/field'
|
15
|
+
require 'umbra/label'
|
16
|
+
require 'umbra/textbox'
|
17
|
+
|
18
|
+
module Umbra
|
19
|
+
class MessageBox
|
20
|
+
|
21
|
+
attr_reader :form
|
22
|
+
attr_reader :window
|
23
|
+
attr_accessor :title
|
24
|
+
attr_accessor :buttons # button labels. e.g. [Ok, Cancel]
|
25
|
+
#dsl_accessor :default_button
|
26
|
+
#
|
27
|
+
# a message to be printed, usually this will be the only thing supplied
|
28
|
+
# with an OK button. This should be a short string, a label will be used
|
29
|
+
# and input_config passed to it
|
30
|
+
|
31
|
+
#dsl_accessor :message
|
32
|
+
# you can also set button_orientation : :right, :left, :center
|
33
|
+
#
|
34
|
+
def initialize config={}, &block
|
35
|
+
|
36
|
+
h = config.fetch(:height, nil)
|
37
|
+
w = config.fetch(:width, nil)
|
38
|
+
t = config.fetch(:row, nil)
|
39
|
+
l = config.fetch(:col, nil)
|
40
|
+
if h && w && t && l
|
41
|
+
#@window = Window.new :height => h, :width => w, :top => t, :left => l
|
42
|
+
@window = Window.new h, w, t, l
|
43
|
+
# else window will be created in repaint, and form will pass it to widgets before their first
|
44
|
+
# repaint
|
45
|
+
end
|
46
|
+
@form = Form.new @window
|
47
|
+
@buttons = ["Ok", "Cancel"]
|
48
|
+
|
49
|
+
config.each_pair { |k,v| instance_variable_set("@#{k}",v) }
|
50
|
+
@config = config
|
51
|
+
@row = 0
|
52
|
+
@col = 0
|
53
|
+
@row_offset = 1
|
54
|
+
@col_offset = 2
|
55
|
+
|
56
|
+
#@color ||= :black
|
57
|
+
#@bgcolor ||= :white
|
58
|
+
@color_pair = CP_BLACK
|
59
|
+
# 2014-05-31 - 11:44 adding form color
|
60
|
+
# try not to set buttons color in this program, let button pick up user or form colors
|
61
|
+
#@form.color = @color
|
62
|
+
#@form.bgcolor = @bgcolor
|
63
|
+
#@form.color_pair = @color_pair
|
64
|
+
@maxrow = 3
|
65
|
+
|
66
|
+
instance_eval &block if block_given?
|
67
|
+
#yield_or_eval &block if block_given? TODO
|
68
|
+
|
69
|
+
end
|
70
|
+
def item widget
|
71
|
+
# # normalcolor gives a white on black stark title like links and elinks
|
72
|
+
# You can also do 'acolor' to give you a sober title that does not take attention away, like mc
|
73
|
+
# remove from existing form if set, problem with this is mnemonics -- rare situation.
|
74
|
+
#if widget.form
|
75
|
+
#f = widget.form
|
76
|
+
#f.remove_widget widget
|
77
|
+
#end
|
78
|
+
@maxrow ||= 3
|
79
|
+
#widget.set_form @form
|
80
|
+
@form.add_widget widget
|
81
|
+
widget.row ||= 0
|
82
|
+
widget.col ||= 0
|
83
|
+
if widget.row == 0
|
84
|
+
widget.row = [@maxrow+1, 3].max if widget.row == 0
|
85
|
+
else
|
86
|
+
widget.row += @row_offset
|
87
|
+
end
|
88
|
+
if widget.col == 0
|
89
|
+
widget.col = 5
|
90
|
+
else
|
91
|
+
# i don't know button_offset as yet
|
92
|
+
widget.col += @col_offset
|
93
|
+
end
|
94
|
+
# in most cases this override is okay, but what if user has set it
|
95
|
+
# The problem is that widget and field are doing a default setting so i don't know
|
96
|
+
# if user has set or widget has done a default setting. NOTE
|
97
|
+
# 2014-05-31 - 12:40 CANIS BUTTONCOLOR i have commented out since it should take from form
|
98
|
+
# to see effect
|
99
|
+
if false
|
100
|
+
widget.color ||= @color # we are overriding colors, how to avoid since widget sets it
|
101
|
+
widget.bgcolor ||= @bgcolor
|
102
|
+
widget.attr = @attr if @attr # we are overriding what user has put. DARN !
|
103
|
+
end
|
104
|
+
@maxrow = widget.row if widget.row > @maxrow
|
105
|
+
@suggested_h = @height || @maxrow+6
|
106
|
+
@suggested_w ||= 0
|
107
|
+
ww = widget.width || 5 # some widgets do no set a default width, and could be null
|
108
|
+
_w = [ww + 5, 15].max
|
109
|
+
@suggested_w = widget.col + _w if widget.col > @suggested_w
|
110
|
+
if ww >= @suggested_w
|
111
|
+
@suggested_w = ww + widget.col + 10
|
112
|
+
end
|
113
|
+
$log.debug " MESSAGEBOX add suggested_w #{@suggested_w} "
|
114
|
+
# if w's given col is > width then add to suggested_w or text.length
|
115
|
+
end
|
116
|
+
alias :add :item
|
117
|
+
# returns button index
|
118
|
+
# Call this after instantiating the window
|
119
|
+
def run
|
120
|
+
repaint
|
121
|
+
@form.pack # needs window
|
122
|
+
@form.repaint
|
123
|
+
@window.wrefresh
|
124
|
+
return handle_keys
|
125
|
+
end
|
126
|
+
def repaint
|
127
|
+
_create_window unless @window
|
128
|
+
#acolor = get_color $reverscolor, @color, @bgcolor
|
129
|
+
acolor = 0 # ??? FIXME
|
130
|
+
$log.debug " MESSAGE BOX bg:#{@bgcolor} , co:#{@color} , colorpair:#{acolor}"
|
131
|
+
@window.wbkgd(FFI::NCurses.COLOR_PAIR(acolor) | REVERSE);
|
132
|
+
|
133
|
+
@color_pair ||= CP_BLACK
|
134
|
+
bordercolor = @border_color || CP_BLACK
|
135
|
+
borderatt = @border_attrib || NORMAL
|
136
|
+
@window.wattron(FFI::NCurses.COLOR_PAIR(bordercolor) | (borderatt || FFI::NCurses::A_NORMAL))
|
137
|
+
print_border_mb @window, 1,2, @height, @width, nil, nil
|
138
|
+
@window.wattroff(FFI::NCurses.COLOR_PAIR(bordercolor) | (borderatt || FFI::NCurses::A_NORMAL))
|
139
|
+
@title ||= "+-+"
|
140
|
+
@title_color ||= CP_CYAN
|
141
|
+
@title_attr ||= REVERSE
|
142
|
+
title = " "+@title+" "
|
143
|
+
# normalcolor gives a white on black stark title like links and elinks
|
144
|
+
# You can also do 'acolor' to give you a sober title that does not take attention away, like mc
|
145
|
+
@window.printstring(@row=1,@col=(@width-title.length)/2,title, color=@title_color, @title_attr)
|
146
|
+
#print_message if @message
|
147
|
+
create_action_buttons(*@buttons) unless @action_buttons
|
148
|
+
end
|
149
|
+
def create_action_buttons *labels
|
150
|
+
@action_buttons = []
|
151
|
+
_row = @height-3
|
152
|
+
_col = (@width-(labels.count*8))/2
|
153
|
+
_col = 5 if _col < 1
|
154
|
+
|
155
|
+
labels.each_with_index do |l, ix|
|
156
|
+
b = Button.new text: l, row: _row, col: _col
|
157
|
+
_col += l.length+5
|
158
|
+
@action_buttons << b
|
159
|
+
@form.add_widget b
|
160
|
+
b.command do
|
161
|
+
@selected_index = ix
|
162
|
+
throw(:close, ix)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
# CLEAN THIS UP TODO
|
167
|
+
# Pass a short message to be printed.
|
168
|
+
# This creates a label for a short message, and a field for a long one.
|
169
|
+
# @yield field created
|
170
|
+
# @param [String] text to display
|
171
|
+
def message message # yield label or field being used for display for further customization
|
172
|
+
@suggested_h = @height || 10
|
173
|
+
message = message.gsub(/[\n\r\t]/,' ') rescue message
|
174
|
+
message_col = 5
|
175
|
+
$log.debug " MESSAGE w: #{@width}, size: #{message.size} "
|
176
|
+
_pad = 5
|
177
|
+
@suggested_w = @width || [message.size + _pad + message_col , FFI::NCurses.COLS-2].min
|
178
|
+
r = 3
|
179
|
+
len = message.length
|
180
|
+
#@suggested_w = len + _pad + message_col if len < @suggested_w - _pad - message_col
|
181
|
+
|
182
|
+
display_length = @suggested_w-_pad
|
183
|
+
display_length -= message_col
|
184
|
+
message_height = 2
|
185
|
+
#clr = @color || :white
|
186
|
+
#bgclr = @bgcolor || :black
|
187
|
+
|
188
|
+
color_pair = CP_WHITE
|
189
|
+
# trying this out. sometimes very long labels get truncated, so i give a field in wchich user
|
190
|
+
# can use arrow key or C-a and C-e
|
191
|
+
if message.size > display_length
|
192
|
+
message_label = Field.new({:text => message, :name=>"message_label",
|
193
|
+
:row => r, :col => message_col, :width => display_length,
|
194
|
+
:color_pair => color_pair, :editable => false})
|
195
|
+
else
|
196
|
+
message_label = Label.new({:text => message, :name=>"message_label",
|
197
|
+
:row => r, :col => message_col, :width => display_length,
|
198
|
+
:height => message_height, :color_pair => color_pair})
|
199
|
+
end
|
200
|
+
@form.add_widget message_label
|
201
|
+
@maxrow = 3
|
202
|
+
yield message_label if block_given?
|
203
|
+
end
|
204
|
+
alias :message= :message
|
205
|
+
|
206
|
+
# This is for larger messages, or messages where the size is not known.
|
207
|
+
# A textview object is created and yielded.
|
208
|
+
#
|
209
|
+
def text message
|
210
|
+
@suggested_w = @width || (FFI::NCurses.COLS * 0.80).floor
|
211
|
+
@suggested_h = @height || (FFI::NCurses.LINES * 0.80).floor
|
212
|
+
|
213
|
+
message_col = 3
|
214
|
+
r = 2
|
215
|
+
display_length = @suggested_w-4
|
216
|
+
display_length -= message_col
|
217
|
+
#clr = @color || :white
|
218
|
+
#bgclr = @bgcolor || :black
|
219
|
+
color_pair = CP_WHITE
|
220
|
+
|
221
|
+
if message.is_a? Array
|
222
|
+
l = longest_in_list message
|
223
|
+
if l > @suggested_w
|
224
|
+
if l < FFI::NCurses.COLS
|
225
|
+
#@suggested_w = l
|
226
|
+
@suggested_w = FFI::NCurses.COLS-2
|
227
|
+
else
|
228
|
+
@suggested_w = FFI::NCurses.COLS-2
|
229
|
+
end
|
230
|
+
display_length = @suggested_w-6
|
231
|
+
end
|
232
|
+
# reduce width and height if you can based on array contents
|
233
|
+
else
|
234
|
+
message = wrap_text(message, display_length).split("\n")
|
235
|
+
end
|
236
|
+
# now that we have moved to textpad that +8 was causing black lines to remain after the text
|
237
|
+
message_height = message.size #+ 8
|
238
|
+
# reduce if possible if its not required.
|
239
|
+
#
|
240
|
+
r1 = (FFI::NCurses.LINES-@suggested_h)/2
|
241
|
+
r1 = r1.floor
|
242
|
+
w = @suggested_w
|
243
|
+
c1 = (FFI::NCurses.COLS-w)/2
|
244
|
+
c1 = c1.floor
|
245
|
+
@suggested_row = r1
|
246
|
+
@suggested_col = c1
|
247
|
+
brow = @button_row || @suggested_h-4
|
248
|
+
available_ht = brow - r + 1
|
249
|
+
message_height = [message_height, available_ht].min
|
250
|
+
# replaced 2014-04-14 - 23:51
|
251
|
+
message_label = Textbox.new({:name=>"message_label", :list => message,
|
252
|
+
:row => r, :col => message_col, :width => display_length,
|
253
|
+
:height => message_height, :color_pair => color_pair})
|
254
|
+
#message_label.set_content message
|
255
|
+
@form.add_widget message_label
|
256
|
+
yield message_label if block_given?
|
257
|
+
|
258
|
+
end
|
259
|
+
alias :text= :text
|
260
|
+
# returns length of longest
|
261
|
+
def longest_in_list list #:nodoc:
|
262
|
+
longest = list.inject(0) do |memo,word|
|
263
|
+
memo >= word.length ? memo : word.length
|
264
|
+
end
|
265
|
+
longest
|
266
|
+
end
|
267
|
+
def wrap_text(s, width=78) # {{{
|
268
|
+
s.gsub(/(.{1,#{width}})(\s+|\Z)/, "\\1\n").split("\n")
|
269
|
+
end
|
270
|
+
def _create_window
|
271
|
+
|
272
|
+
$log.debug " MESSAGEBOX _create_window h:#{@height} w:#{@width} r:#{@row} c:#{@col} "
|
273
|
+
$log.debug " MESSAGEBOX _create_window h:#{@suggested_h} w:#{@suggested_w} "
|
274
|
+
@width ||= @suggested_w || 60
|
275
|
+
@height = @suggested_h || 10
|
276
|
+
$log.debug " MESSAGEBOX _create_window h:#{@height} w:#{@width} r:#{@row} c:#{@col} "
|
277
|
+
if @suggested_row
|
278
|
+
@row = @suggested_row
|
279
|
+
else
|
280
|
+
@row = ((FFI::NCurses.LINES-@height)/2).floor
|
281
|
+
end
|
282
|
+
if @suggested_col
|
283
|
+
@col = @suggested_col
|
284
|
+
else
|
285
|
+
w = @width
|
286
|
+
@col = ((FFI::NCurses.COLS-w)/2).floor
|
287
|
+
end
|
288
|
+
#@window = Window.new :height => @height, :width => @width, :top => @row, :left => @col
|
289
|
+
$log.debug " MESSAGEBOX _create_window h:#{@height} w:#{@width} r:#{@row} c:#{@col} "
|
290
|
+
@window = Window.new @height, @width, @row, @col
|
291
|
+
@graphic = @window
|
292
|
+
@form.window = @window
|
293
|
+
# in umbra, the widgets would not be having a window, if window was created after the widgets were added
|
294
|
+
end
|
295
|
+
def handle_keys
|
296
|
+
buttonindex = catch(:close) do
|
297
|
+
while((ch = @window.getch()) != FFI::NCurses::KEY_F10 )
|
298
|
+
break if ch == ?\C-q.getbyte(0) || ch == 2727 # added double esc
|
299
|
+
begin
|
300
|
+
# trying out repaint of window also if repaint all asked for. 12 is C-l
|
301
|
+
if ch == 1000 or ch == 12
|
302
|
+
repaint
|
303
|
+
end
|
304
|
+
@form.handle_key(ch)
|
305
|
+
@window.wrefresh
|
306
|
+
rescue => err
|
307
|
+
$log.debug( err) if err
|
308
|
+
$log.debug(err.backtrace.join("\n")) if err
|
309
|
+
#textdialog ["Error in Messagebox: #{err} ", *err.backtrace], :title => "Exception" # TODO
|
310
|
+
@window.refresh # otherwise the window keeps showing (new FFI-ncurses issue)
|
311
|
+
ensure
|
312
|
+
end
|
313
|
+
|
314
|
+
end # while loop
|
315
|
+
end # close
|
316
|
+
$log.debug "MESSAGEBOX: CALLING PROGRAM BEING RETURNED: #{buttonindex} "
|
317
|
+
@window.destroy
|
318
|
+
# added 2014-05-01 - 18:10 hopefully to refresh root_window.
|
319
|
+
#Window.refresh_all
|
320
|
+
return buttonindex
|
321
|
+
end
|
322
|
+
# this is identical to the border printed by dialogs.
|
323
|
+
# The border is printed not on the edge, but one row and column inside.
|
324
|
+
# This is purely cosmetic, otherwise windows.box should be used which prints a box
|
325
|
+
# on the edge.
|
326
|
+
private def print_border_mb window, row, col, height, width, color, attr # {{{
|
327
|
+
win = window.pointer
|
328
|
+
#att = attr
|
329
|
+
len = width
|
330
|
+
len = FFI::NCurses.COLS if len == 0
|
331
|
+
space_char = " ".codepoints.first
|
332
|
+
(row-1).upto(row+height-1) do |r|
|
333
|
+
# this loop clears the screen, printing spaces does not work since ncurses does not do anything
|
334
|
+
FFI::NCurses.mvwhline(win, r, col, space_char, len)
|
335
|
+
end
|
336
|
+
|
337
|
+
FFI::NCurses.mvwaddch win, row, col, FFI::NCurses::ACS_ULCORNER
|
338
|
+
FFI::NCurses.mvwhline( win, row, col+1, FFI::NCurses::ACS_HLINE, width-6)
|
339
|
+
FFI::NCurses.mvwaddch win, row, col+width-5, FFI::NCurses::ACS_URCORNER
|
340
|
+
FFI::NCurses.mvwvline( win, row+1, col, FFI::NCurses::ACS_VLINE, height-4)
|
341
|
+
|
342
|
+
FFI::NCurses.mvwaddch win, row+height-3, col, FFI::NCurses::ACS_LLCORNER
|
343
|
+
FFI::NCurses.mvwhline(win, row+height-3, col+1, FFI::NCurses::ACS_HLINE, width-6)
|
344
|
+
FFI::NCurses.mvwaddch win, row+height-3, col+width-5, FFI::NCurses::ACS_LRCORNER
|
345
|
+
FFI::NCurses.mvwvline( win, row+1, col+width-5, FFI::NCurses::ACS_VLINE, height-4)
|
346
|
+
end # }}}
|
347
|
+
end # class
|
348
|
+
end # module
|
data/lib/umbra/pad.rb
ADDED
@@ -0,0 +1,340 @@
|
|
1
|
+
=begin
|
2
|
+
* Name: PadReader.rb
|
3
|
+
* Description : This is an independent file viewer that uses a Pad and traps keys
|
4
|
+
I am using only ffi-ncurses and not window.rb or any other support classes
|
5
|
+
so this can be used anywhere else.
|
6
|
+
* Author: jkepler
|
7
|
+
* Date: 2018-03-28 14:30
|
8
|
+
* License: MIT
|
9
|
+
* Last update: 2018-04-17 11:25
|
10
|
+
|
11
|
+
== CHANGES
|
12
|
+
== TODO
|
13
|
+
- should have option to wrap text
|
14
|
+
- / search ?
|
15
|
+
NOTE:
|
16
|
+
in this the cursor does not move down, it starts to scroll straight away.
|
17
|
+
So we need another version for lists and textviews in which the cursor moves with up and down.
|
18
|
+
The cursor should be invisible in this.
|
19
|
+
== -----------------------
|
20
|
+
=end
|
21
|
+
require 'ffi-ncurses'
|
22
|
+
|
23
|
+
class Pad
|
24
|
+
|
25
|
+
# You may pass height, width, row and col for creating a window otherwise a fullscreen window
|
26
|
+
# will be created. If you pass a window from caller then that window will be used.
|
27
|
+
# Some keys are trapped, jkhl space, pgup, pgdown, end, home, t b
|
28
|
+
# This is currently very minimal and was created to get me started to integrating
|
29
|
+
# pads into other classes such as textview.
|
30
|
+
def initialize config={}, &block
|
31
|
+
|
32
|
+
$log.debug " inside pad contructor"
|
33
|
+
@config = config
|
34
|
+
@rows = FFI::NCurses.LINES-1
|
35
|
+
@cols = FFI::NCurses.COLS-1
|
36
|
+
@prow = @pcol = 0 # show many cols we are panning
|
37
|
+
@startrow = 0
|
38
|
+
@startcol = 0
|
39
|
+
|
40
|
+
h = config.fetch(:height, 0)
|
41
|
+
w = config.fetch(:width, 0)
|
42
|
+
t = config.fetch(:row, 0)
|
43
|
+
l = config.fetch(:col, 0)
|
44
|
+
@color_pair = config.fetch(:color_pair, 14)
|
45
|
+
@attr = config.fetch(:attr, FFI::NCurses::A_BOLD)
|
46
|
+
@rows = h unless h == 0
|
47
|
+
@cols = w unless w == 0
|
48
|
+
@startrow = t unless t == 0
|
49
|
+
@startcol = l unless l == 0
|
50
|
+
@suppress_border = config[:suppress_border]
|
51
|
+
top = t
|
52
|
+
left = l
|
53
|
+
@height = h
|
54
|
+
@width = w
|
55
|
+
#@pointer, @panel = create_window(h, w, t, l)
|
56
|
+
@pointer, @panel = create_centered_window(h, w, @color_pair, @attr)
|
57
|
+
|
58
|
+
@startrow, @startcol = FFI::NCurses.getbegyx(@pointer)
|
59
|
+
unless @suppress_border
|
60
|
+
@startrow += 1
|
61
|
+
@startcol += 1
|
62
|
+
@rows -=3 # 3 is since print_border_only reduces one from width, to check whether this is correct
|
63
|
+
@cols -=3
|
64
|
+
end
|
65
|
+
$log.debug "top and left are: #{top} #{left} "
|
66
|
+
#@window.box # 2018-03-28 -
|
67
|
+
FFI::NCurses.box @pointer, 0, 0
|
68
|
+
title(config[:title])
|
69
|
+
FFI::NCurses.wbkgd(@pointer, FFI::NCurses.COLOR_PAIR(@color_pair) | @attr);
|
70
|
+
FFI::NCurses.curs_set 0 # cursor invisible
|
71
|
+
if config[:filename]
|
72
|
+
self.filename=(config[:filename])
|
73
|
+
elsif config[:list]
|
74
|
+
self.list=(config[:list])
|
75
|
+
end
|
76
|
+
end
|
77
|
+
# minimum window creator method, not using a class.
|
78
|
+
# However, some methods do require windows width and ht etc
|
79
|
+
def create_window h, w, t, l
|
80
|
+
pointer = FFI::NCurses.newwin(h, w, t, l)
|
81
|
+
panel = FFI::NCurses.new_panel(pointer)
|
82
|
+
FFI::NCurses.keypad(pointer, true)
|
83
|
+
return pointer, panel
|
84
|
+
end
|
85
|
+
def create_centered_window height, width, color_pair=14, attr=FFI::NCurses::A_BOLD
|
86
|
+
row = ((FFI::NCurses.LINES-height)/2).floor
|
87
|
+
col = ((FFI::NCurses.COLS-width)/2).floor
|
88
|
+
pointer = FFI::NCurses.newwin(height, width, row, col)
|
89
|
+
FFI::NCurses.wbkgd(pointer, FFI::NCurses.COLOR_PAIR(color_pair) | attr);
|
90
|
+
panel = FFI::NCurses.new_panel(pointer)
|
91
|
+
FFI::NCurses.keypad(pointer, true)
|
92
|
+
return pointer, panel
|
93
|
+
end
|
94
|
+
def destroy_window pointer, panel
|
95
|
+
FFI::NCurses.del_panel(panel) if panel
|
96
|
+
FFI::NCurses.delwin(pointer) if pointer
|
97
|
+
panel = pointer = nil # prevent call twice
|
98
|
+
end
|
99
|
+
def destroy_pad
|
100
|
+
if @pad
|
101
|
+
FFI::NCurses.delwin(@pad)
|
102
|
+
@pad = nil
|
103
|
+
end
|
104
|
+
end
|
105
|
+
# print a title over the box on zeroth row
|
106
|
+
def title stitle
|
107
|
+
return unless stitle
|
108
|
+
stitle = "| #{stitle} |"
|
109
|
+
col = (@width-stitle.size)/2
|
110
|
+
FFI::NCurses.mvwaddstr(@pointer, 0, col, stitle)
|
111
|
+
end
|
112
|
+
private def display_content content
|
113
|
+
@pad = create_pad content
|
114
|
+
FFI::NCurses.wrefresh(@pointer)
|
115
|
+
padrefresh
|
116
|
+
end
|
117
|
+
|
118
|
+
private def create_pad content
|
119
|
+
# destroy pad if exists
|
120
|
+
destroy_pad
|
121
|
+
@content_rows, @content_cols = content_dimensions(content)
|
122
|
+
pad = FFI::NCurses.newpad(@content_rows, @content_cols)
|
123
|
+
FFI::NCurses.wbkgd(pad, FFI::NCurses.COLOR_PAIR(@color_pair) | @attr);
|
124
|
+
FFI::NCurses.keypad(pad, true); # function and arrow keys
|
125
|
+
|
126
|
+
FFI::NCurses.update_panels
|
127
|
+
cp = @color_pair
|
128
|
+
#@attr = FFI::NCurses::A_BOLD
|
129
|
+
FFI::NCurses.wattron(pad, FFI::NCurses.COLOR_PAIR(cp) | @attr)
|
130
|
+
# WRITE
|
131
|
+
filler = " "*@content_cols
|
132
|
+
content.each_index { |ix|
|
133
|
+
#FFI::NCurses.mvwaddstr(pad,ix, 0, filler)
|
134
|
+
FFI::NCurses.mvwaddstr(pad,ix, 0, content[ix])
|
135
|
+
}
|
136
|
+
FFI::NCurses.wattroff(pad, FFI::NCurses.COLOR_PAIR(cp) | @attr)
|
137
|
+
return pad
|
138
|
+
end
|
139
|
+
|
140
|
+
# receive array as content source
|
141
|
+
#
|
142
|
+
def list=(content)
|
143
|
+
display_content content
|
144
|
+
end
|
145
|
+
# source of data is a filename
|
146
|
+
def filename=(filename)
|
147
|
+
content = File.open(filename,"r").read.split("\n")
|
148
|
+
display_content content
|
149
|
+
end
|
150
|
+
private def content_dimensions content
|
151
|
+
content_rows = content.count
|
152
|
+
content_cols = content_cols(content)
|
153
|
+
return content_rows, content_cols
|
154
|
+
end
|
155
|
+
|
156
|
+
|
157
|
+
# write pad onto window
|
158
|
+
private
|
159
|
+
def padrefresh
|
160
|
+
raise "padrefresh: Pad not created" unless @pad
|
161
|
+
FFI::NCurses.prefresh(@pad,@prow,@pcol, @startrow,@startcol, @rows + @startrow,@cols+@startcol);
|
162
|
+
end
|
163
|
+
|
164
|
+
# returns button index
|
165
|
+
# Call this after instantiating the window
|
166
|
+
public
|
167
|
+
def run
|
168
|
+
return handle_keys
|
169
|
+
end
|
170
|
+
|
171
|
+
# convenience method
|
172
|
+
private
|
173
|
+
def key x
|
174
|
+
x.getbyte(0)
|
175
|
+
end
|
176
|
+
def content_cols content
|
177
|
+
# FIXME bombs if content contains integer or nil.
|
178
|
+
#longest = content.max_by(&:length)
|
179
|
+
#longest.length
|
180
|
+
max = 1
|
181
|
+
content.each do |line|
|
182
|
+
next unless line
|
183
|
+
l = 1
|
184
|
+
case line
|
185
|
+
when String
|
186
|
+
l = line.length
|
187
|
+
else
|
188
|
+
l = line.to_s.length
|
189
|
+
end
|
190
|
+
max = l if l > max
|
191
|
+
end
|
192
|
+
return max
|
193
|
+
end
|
194
|
+
# returns length of longest
|
195
|
+
def longest_in_list list #:nodoc:
|
196
|
+
longest = list.inject(0) do |memo,word|
|
197
|
+
memo >= word.length ? memo : word.length
|
198
|
+
end
|
199
|
+
longest
|
200
|
+
end
|
201
|
+
|
202
|
+
# returns button index
|
203
|
+
private
|
204
|
+
def handle_keys
|
205
|
+
@height = FFI::NCurses.LINES-1 if @height == 0
|
206
|
+
ht = @rows
|
207
|
+
scroll_lines = @height/2
|
208
|
+
buttonindex = catch(:close) do
|
209
|
+
maxrow = @content_rows - @rows
|
210
|
+
maxcol = @content_cols - @cols
|
211
|
+
while ((ch = FFI::NCurses.wgetch(@pointer)) != FFI::NCurses::KEY_F10)
|
212
|
+
break if ch == ?\C-q.getbyte(0)
|
213
|
+
begin
|
214
|
+
case ch
|
215
|
+
when key(?g), 279 # home as per iterm2
|
216
|
+
@prow = 0
|
217
|
+
@pcol = 0
|
218
|
+
when key(?b), key(?G), 277 # end as per iterm2
|
219
|
+
@prow = maxrow-1
|
220
|
+
@pcol = 0
|
221
|
+
when key(?j), FFI::NCurses::KEY_DOWN
|
222
|
+
@prow += 1
|
223
|
+
when key(?k), FFI::NCurses::KEY_UP
|
224
|
+
@prow -= 1
|
225
|
+
when 32, 338 # Page Down abd Page Up as per iTerm2
|
226
|
+
@prow += 10
|
227
|
+
when key(?\C-d)
|
228
|
+
@prow += scroll_lines
|
229
|
+
when key(?\C-b)
|
230
|
+
@prow -= scroll_lines
|
231
|
+
when key(?\C-f)
|
232
|
+
@prow += ht
|
233
|
+
when key(?\C-u)
|
234
|
+
@prow -= ht
|
235
|
+
when 339
|
236
|
+
@prow -= 10
|
237
|
+
when key(?l), FFI::NCurses::KEY_RIGHT
|
238
|
+
@pcol += 1
|
239
|
+
when key(?$)
|
240
|
+
@pcol = maxcol - 1
|
241
|
+
when key(?h), FFI::NCurses::KEY_LEFT
|
242
|
+
@pcol -= 1
|
243
|
+
when key(?0)
|
244
|
+
@pcol = 0
|
245
|
+
when key(?q)
|
246
|
+
throw :close
|
247
|
+
else
|
248
|
+
#alert " #{ch} not mapped "
|
249
|
+
end
|
250
|
+
@prow = 0 if @prow < 0
|
251
|
+
@pcol = 0 if @pcol < 0
|
252
|
+
if @prow > maxrow-1
|
253
|
+
@prow = maxrow-1
|
254
|
+
end
|
255
|
+
if @pcol > maxcol-1
|
256
|
+
@pcol = maxcol-1
|
257
|
+
end
|
258
|
+
padrefresh
|
259
|
+
#FFI::NCurses::Panel.update_panels # 2018-03-28 - this bombs elsewhere
|
260
|
+
rescue => err
|
261
|
+
if $log
|
262
|
+
$log.debug err.to_s
|
263
|
+
$log.debug err.backtrace.join("\n")
|
264
|
+
end
|
265
|
+
FFI::NCurses.endwin
|
266
|
+
puts "INSIDE pad.rb"
|
267
|
+
puts err
|
268
|
+
puts err.backtrace.join("\n")
|
269
|
+
ensure
|
270
|
+
end
|
271
|
+
|
272
|
+
end # while loop
|
273
|
+
end # close
|
274
|
+
rescue => err
|
275
|
+
if $log
|
276
|
+
$log.debug err.to_s
|
277
|
+
$log.debug err.backtrace.join("\n")
|
278
|
+
end
|
279
|
+
FFI::NCurses.endwin
|
280
|
+
puts err
|
281
|
+
puts err.backtrace.join("\n")
|
282
|
+
ensure
|
283
|
+
#@window.destroy #unless @config[:window]
|
284
|
+
destroy_window @pointer, @panel
|
285
|
+
#FFI::NCurses.delwin(@pad) if @pad
|
286
|
+
destroy_pad
|
287
|
+
FFI::NCurses.curs_set 1 # cursor visible again
|
288
|
+
return buttonindex
|
289
|
+
end
|
290
|
+
end
|
291
|
+
if __FILE__ == $PROGRAM_NAME
|
292
|
+
def startup
|
293
|
+
require 'logger'
|
294
|
+
require 'date'
|
295
|
+
|
296
|
+
path = File.join(ENV["LOGDIR"] || "./" ,"v.log")
|
297
|
+
file = File.open(path, File::WRONLY|File::TRUNC|File::CREAT)
|
298
|
+
$log = Logger.new(path)
|
299
|
+
$log.level = Logger::DEBUG
|
300
|
+
today = Time.now.to_s
|
301
|
+
$log.info "Pad demo #{$0} started on #{today}"
|
302
|
+
end
|
303
|
+
def std_colors
|
304
|
+
FFI::NCurses.use_default_colors
|
305
|
+
# 2018-03-17 - changing it to ncurses defaults
|
306
|
+
FFI::NCurses.init_pair(0, FFI::NCurses::BLACK, -1)
|
307
|
+
FFI::NCurses.init_pair(1, FFI::NCurses::RED, -1)
|
308
|
+
FFI::NCurses.init_pair(2, FFI::NCurses::GREEN, -1)
|
309
|
+
FFI::NCurses.init_pair(3, FFI::NCurses::YELLOW, -1)
|
310
|
+
FFI::NCurses.init_pair(4, FFI::NCurses::BLUE, -1)
|
311
|
+
FFI::NCurses.init_pair(5, FFI::NCurses::MAGENTA, -1)
|
312
|
+
FFI::NCurses.init_pair(6, FFI::NCurses::CYAN, -1)
|
313
|
+
FFI::NCurses.init_pair(7, FFI::NCurses::WHITE, -1)
|
314
|
+
FFI::NCurses.init_pair(14, FFI::NCurses::WHITE, FFI::NCurses::CYAN)
|
315
|
+
end
|
316
|
+
|
317
|
+
|
318
|
+
FFI::NCurses.initscr
|
319
|
+
FFI::NCurses.curs_set 1
|
320
|
+
FFI::NCurses.raw
|
321
|
+
FFI::NCurses.noecho
|
322
|
+
FFI::NCurses.keypad FFI::NCurses.stdscr, true
|
323
|
+
FFI::NCurses.scrollok FFI::NCurses.stdscr, true
|
324
|
+
if FFI::NCurses.has_colors
|
325
|
+
FFI::NCurses.start_color
|
326
|
+
std_colors
|
327
|
+
end
|
328
|
+
|
329
|
+
startup
|
330
|
+
begin
|
331
|
+
file = ARGV[0] || $0
|
332
|
+
h = 20
|
333
|
+
w = 50
|
334
|
+
p = Pad.new :filename => "#{file}", :height => FFI::NCurses.LINES-1, :width => w, :row => 0, :col => 0, title: "pad.rb", color_pair: 14, attr: FFI::NCurses::A_BOLD
|
335
|
+
p.run
|
336
|
+
ensure
|
337
|
+
FFI::NCurses.endwin
|
338
|
+
FFI::NCurses.curs_set 1 # cursor visible again
|
339
|
+
end
|
340
|
+
end
|