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,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
|