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
data/examples/tt.rb
ADDED
@@ -0,0 +1,462 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# ----------------------------------------------------------------------------- #
|
3
|
+
# File: tt.rb
|
4
|
+
# Description: a quick small directory lister aimed at being simple fast with minimal
|
5
|
+
# features, and mostly for viewing files quickly through PAGER
|
6
|
+
# Author: j kepler http://github.com/mare-imbrium/
|
7
|
+
# Date: 2018-03-09
|
8
|
+
# License: MIT
|
9
|
+
# Last update: 2018-04-12 08:45
|
10
|
+
# ----------------------------------------------------------------------------- #
|
11
|
+
# tt.rb Copyright (C) 2012-2018 j kepler
|
12
|
+
# == TODO
|
13
|
+
# [ ] make a help screen on ?
|
14
|
+
# [ ] move to lyra or some gem and publish
|
15
|
+
# [ ] pop directories
|
16
|
+
# [ ] go back to start directory
|
17
|
+
# [ ] go to given directory
|
18
|
+
# [x] open files on RIGHT arrow in view (?)
|
19
|
+
# [ ] in a long listing, how to get to a file name. first char or pattern TODO
|
20
|
+
# [ ] pressing p should open PAGER, e EDITOR, m MOST, v - view
|
21
|
+
# [x] on zip file show contents in pager. x to extract.
|
22
|
+
# [x] when going up a directory keep cursor on the directory we came from
|
23
|
+
# [x] space bar to page down. also page up on c-n c-p top bottom
|
24
|
+
# [x] hide dot files
|
25
|
+
# [x] reveal dot files on toggle
|
26
|
+
# [x] long listing files on toggle
|
27
|
+
# [x] long file names not getting cleared
|
28
|
+
# [ ] allow entry of command and page output or show in PAGER
|
29
|
+
# [x] pressing ENTER should invoke EDITOR
|
30
|
+
# [x] scrolling up behavior not correct. we should scroll up from first row not last.
|
31
|
+
# see vifm for correct way. mc has different behavior
|
32
|
+
# ----------
|
33
|
+
# == CHANGELOG
|
34
|
+
#
|
35
|
+
#
|
36
|
+
# --------
|
37
|
+
#
|
38
|
+
require 'umbra/window'
|
39
|
+
require 'umbra/menu'
|
40
|
+
TOPLINE="| ` Menu | = Toggle | q Quit | lyra 0.1"
|
41
|
+
$sorto = "on"
|
42
|
+
$hidden = nil
|
43
|
+
$long_listing = false
|
44
|
+
$patt = nil
|
45
|
+
_LINES = FFI::NCurses.LINES-1
|
46
|
+
include Umbra
|
47
|
+
def create_footer_window h = 2 , w = FFI::NCurses.COLS, t = FFI::NCurses.LINES-2, l = 0
|
48
|
+
ewin = Window.new(h, w , t, l)
|
49
|
+
end
|
50
|
+
def create_input_window h = 1 , w = FFI::NCurses.COLS, t = FFI::NCurses.LINES-1, l = 0
|
51
|
+
ewin = Window.new(h, w , t, l)
|
52
|
+
end
|
53
|
+
# accepts user input in current window
|
54
|
+
# and returns characters after RETURN pressed
|
55
|
+
def getchars win, max=20
|
56
|
+
str = ""
|
57
|
+
pos = 0
|
58
|
+
filler = " "*max
|
59
|
+
y, x = win.getyx()
|
60
|
+
pointer = win.pointer
|
61
|
+
while (ch = win.getkey) != FFI::NCurses::KEY_RETURN
|
62
|
+
#str << ch.chr
|
63
|
+
if ch > 27 and ch < 127
|
64
|
+
str.insert(pos, ch.chr)
|
65
|
+
pos += 1
|
66
|
+
#FFI::NCurses.waddstr(win.pointer, ch.chr)
|
67
|
+
end
|
68
|
+
case ch
|
69
|
+
when FFI::NCurses::KEY_LEFT
|
70
|
+
pos -= 1
|
71
|
+
pos = 0 if pos < 0
|
72
|
+
when FFI::NCurses::KEY_RIGHT
|
73
|
+
pos += 1
|
74
|
+
pos = str.size if pos >= str.size
|
75
|
+
when 127
|
76
|
+
pos -= 1 if pos > 0
|
77
|
+
str.slice!(pos,1) if pos >= 0 # no backspace if on first pos
|
78
|
+
when 27, FFI::NCurses::KEY_CTRL_C
|
79
|
+
return nil
|
80
|
+
end
|
81
|
+
FFI::NCurses.wmove(pointer, y,x)
|
82
|
+
FFI::NCurses.waddstr(pointer, filler)
|
83
|
+
FFI::NCurses.wmove(pointer, y,x)
|
84
|
+
FFI::NCurses.waddstr(pointer, str)
|
85
|
+
FFI::NCurses.wmove(pointer, y,pos+1) # set cursor to correct position
|
86
|
+
break if str.size >= max
|
87
|
+
end
|
88
|
+
str
|
89
|
+
end
|
90
|
+
# runs given command and returns.
|
91
|
+
# Does not wait, so command should be like an editor or be paged to less.
|
92
|
+
def shell_out command
|
93
|
+
FFI::NCurses.endwin
|
94
|
+
ret = system command
|
95
|
+
FFI::NCurses.refresh
|
96
|
+
end
|
97
|
+
|
98
|
+
## code related to long listing of files
|
99
|
+
GIGA_SIZE = 1073741824.0
|
100
|
+
MEGA_SIZE = 1048576.0
|
101
|
+
KILO_SIZE = 1024.0
|
102
|
+
|
103
|
+
# Return the file size with a readable style.
|
104
|
+
def readable_file_size(size, precision)
|
105
|
+
case
|
106
|
+
#when size == 1 : "1 B"
|
107
|
+
when size < KILO_SIZE then "%d B" % size
|
108
|
+
when size < MEGA_SIZE then "%.#{precision}f K" % (size / KILO_SIZE)
|
109
|
+
when size < GIGA_SIZE then "%.#{precision}f M" % (size / MEGA_SIZE)
|
110
|
+
else "%.#{precision}f G" % (size / GIGA_SIZE)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
## format date for file given stat
|
114
|
+
def date_format t
|
115
|
+
t.strftime "%Y/%m/%d"
|
116
|
+
end
|
117
|
+
# clears window but leaves top line
|
118
|
+
def clearwin(win)
|
119
|
+
win.wmove(1,0)
|
120
|
+
win.wclrtobot
|
121
|
+
end
|
122
|
+
##
|
123
|
+
def file_edit win, fp
|
124
|
+
#$log.debug " edit #{fp}"
|
125
|
+
editor = ENV['EDITOR'] || 'vi'
|
126
|
+
vimp = %x[which #{editor}].chomp
|
127
|
+
shell_out "#{vimp} #{fp}"
|
128
|
+
end
|
129
|
+
def file_open win, fp
|
130
|
+
unless File.exists? fp
|
131
|
+
pwd = %x[pwd]
|
132
|
+
#alert "No such file. My pwd is #{pwd} "
|
133
|
+
alert win, "No such file. My pwd is #{pwd} "
|
134
|
+
return
|
135
|
+
end
|
136
|
+
ft=%x[file #{fp}]
|
137
|
+
if ft.index("text")
|
138
|
+
file_edit win, fp
|
139
|
+
elsif ft.index(/zip/i)
|
140
|
+
shell_out "tar tvf #{fp} | less"
|
141
|
+
elsif ft.index(/directory/i)
|
142
|
+
shell_out "ls -lh #{fp} | less"
|
143
|
+
else
|
144
|
+
alert "#{fp} is not text, not opening (#{ft}) "
|
145
|
+
end
|
146
|
+
end
|
147
|
+
def file_page win, fp
|
148
|
+
unless File.exists? fp
|
149
|
+
pwd = %x[pwd]
|
150
|
+
alert "No such file. My pwd is #{pwd} "
|
151
|
+
return
|
152
|
+
end
|
153
|
+
ft=%x[file #{fp}]
|
154
|
+
if ft.index("text")
|
155
|
+
pager = ENV['PAGER'] || 'less'
|
156
|
+
vimp = %x[which #{pager}].chomp
|
157
|
+
shell_out "#{vimp} #{fp}"
|
158
|
+
elsif ft.index(/zip/i)
|
159
|
+
shell_out "tar tvf #{fp} | less"
|
160
|
+
elsif ft.index(/directory/i)
|
161
|
+
shell_out "ls -lh #{fp} | less"
|
162
|
+
else
|
163
|
+
alert "#{fp} is not text, not paging "
|
164
|
+
#use_on_file "als", fp # only zip or archive
|
165
|
+
end
|
166
|
+
end
|
167
|
+
def get_files
|
168
|
+
#files = Dir.glob("*")
|
169
|
+
files = `zsh -c 'print -rl -- *(#{$sorto}#{$hidden}M)'`.split("\n")
|
170
|
+
if $patt
|
171
|
+
files = files.grep(/#{$patt}/)
|
172
|
+
end
|
173
|
+
return files
|
174
|
+
end
|
175
|
+
# a quick simple list with highlight row, and scrolling
|
176
|
+
#
|
177
|
+
# mark directories in color
|
178
|
+
# @return start row
|
179
|
+
def listing win, path, files, cur=0, pstart
|
180
|
+
curpos = 1
|
181
|
+
width = win.width-1
|
182
|
+
y = x = 1
|
183
|
+
ht = win.height-2
|
184
|
+
#st = 0
|
185
|
+
st = pstart # previous start
|
186
|
+
pend = pstart + ht -1 # previous end
|
187
|
+
if cur > pend
|
188
|
+
st = (cur -ht) +1
|
189
|
+
elsif cur < pstart
|
190
|
+
st = cur
|
191
|
+
end
|
192
|
+
hl = cur
|
193
|
+
#if cur >= ht
|
194
|
+
#st = (cur - ht ) +1
|
195
|
+
#hl = cur
|
196
|
+
## we need to scroll the rows
|
197
|
+
#end
|
198
|
+
y = 0
|
199
|
+
ctr = 0
|
200
|
+
filler = " "*width
|
201
|
+
files.each_with_index {|f, y|
|
202
|
+
next if y < st
|
203
|
+
colr = CP_WHITE # white on bg -1
|
204
|
+
ctr += 1
|
205
|
+
mark = " "
|
206
|
+
if y == hl
|
207
|
+
attr = FFI::NCurses::A_REVERSE
|
208
|
+
mark = ">"
|
209
|
+
curpos = ctr
|
210
|
+
else
|
211
|
+
attr = FFI::NCurses::A_NORMAL
|
212
|
+
end
|
213
|
+
fullp = path + "/" + f
|
214
|
+
|
215
|
+
if $long_listing
|
216
|
+
begin
|
217
|
+
unless File.exist? f
|
218
|
+
last = f[-1]
|
219
|
+
if last == " " || last == "@" || last == '*'
|
220
|
+
stat = File.stat(f.chop)
|
221
|
+
end
|
222
|
+
else
|
223
|
+
stat = File.stat(f)
|
224
|
+
end
|
225
|
+
f = "%10s %s %s" % [readable_file_size(stat.size,1), date_format(stat.mtime), f]
|
226
|
+
rescue Exception => e
|
227
|
+
f = "%10s %s %s" % ["?", "??????????", f]
|
228
|
+
end
|
229
|
+
end
|
230
|
+
if File.directory? fullp
|
231
|
+
#ff = "#{mark} #{f}/"
|
232
|
+
# 2018-03-12 - removed slash at end since zsh puts it there
|
233
|
+
ff = "#{mark} #{f}"
|
234
|
+
colr = CP_BLUE # blue on background color_pair COLOR_PAIR
|
235
|
+
attr = attr | FFI::NCurses::A_BOLD
|
236
|
+
elsif File.executable? fullp
|
237
|
+
ff = "#{mark} #{f}*"
|
238
|
+
colr = CP_WHITE # yellow on background color_pair COLOR_PAIR
|
239
|
+
attr = attr | FFI::NCurses::A_BOLD
|
240
|
+
else
|
241
|
+
ff = "#{mark} #{f}"
|
242
|
+
end
|
243
|
+
win.printstring(ctr, x, filler, colr )
|
244
|
+
win.printstring(ctr, x, ff, colr, attr)
|
245
|
+
break if ctr >= ht
|
246
|
+
}
|
247
|
+
#curpos = cur + 1
|
248
|
+
#if curpos > ht
|
249
|
+
#curpos = ht
|
250
|
+
#end
|
251
|
+
#statusline(win, "#{cur+1}/#{files.size} #{files[cur]}. cur = #{cur}, pos:#{curpos},ht = #{ht} , hl #{hl}")
|
252
|
+
statusline(win, "#{cur+1}/#{files.size} #{files[cur]}. (#{$sorto}) ")
|
253
|
+
win.wmove( curpos , 0) # +1 depends on offset of ctr
|
254
|
+
win.wrefresh
|
255
|
+
return st
|
256
|
+
end
|
257
|
+
def statusline win, str
|
258
|
+
win.printstring(win.height-1, 2, str, 1) # white on default
|
259
|
+
#win.mvchgat(win.height-1,2, 1, FFI::NCurses::A_REVERSE, 0, nil)
|
260
|
+
end
|
261
|
+
def alert str
|
262
|
+
win = create_footer_window
|
263
|
+
# 10 is too much BLACK on CYAN
|
264
|
+
win.wbkgd(FFI::NCurses.COLOR_PAIR(12))
|
265
|
+
win.printstring(0,1, str)
|
266
|
+
win.wrefresh
|
267
|
+
win.getkey
|
268
|
+
win.destroy
|
269
|
+
end
|
270
|
+
def main_menu
|
271
|
+
h = { :s => :sort_menu, :M => :newdir, "%" => :newfile }
|
272
|
+
m = Menu.new "Main Menu", h
|
273
|
+
ch = m.getkey
|
274
|
+
return nil if !ch
|
275
|
+
|
276
|
+
binding = h[ch]
|
277
|
+
binding = h[ch.to_sym] unless binding
|
278
|
+
if binding
|
279
|
+
if respond_to?(binding, true)
|
280
|
+
send(binding)
|
281
|
+
end
|
282
|
+
end
|
283
|
+
return ch, binding
|
284
|
+
end
|
285
|
+
|
286
|
+
def sort_menu
|
287
|
+
lo = nil
|
288
|
+
h = { :n => :newest, :a => :accessed, :o => :oldest,
|
289
|
+
:l => :largest, :s => :smallest , :m => :name , :r => :rname, :d => :dirs, :c => :clear }
|
290
|
+
m = Menu.new "Sort Menu", h
|
291
|
+
ch = m.getkey
|
292
|
+
menu_text = h[ch.to_sym]
|
293
|
+
case menu_text
|
294
|
+
when :newest
|
295
|
+
lo="om"
|
296
|
+
when :accessed
|
297
|
+
lo="oa"
|
298
|
+
when :oldest
|
299
|
+
lo="Om"
|
300
|
+
when :largest
|
301
|
+
lo="OL"
|
302
|
+
when :smallest
|
303
|
+
lo="oL"
|
304
|
+
when :name
|
305
|
+
lo="on"
|
306
|
+
when :rname
|
307
|
+
lo="On"
|
308
|
+
when :dirs
|
309
|
+
lo="/"
|
310
|
+
when :clear
|
311
|
+
lo=""
|
312
|
+
end
|
313
|
+
## This needs to persist and be a part of all listings, put in change_dir.
|
314
|
+
$sorto = lo
|
315
|
+
#$files = `zsh -c 'print -rl -- *(#{lo}#{$hidden}M)'`.split("\n") if lo
|
316
|
+
#$title = nil
|
317
|
+
end
|
318
|
+
|
319
|
+
begin
|
320
|
+
init_curses
|
321
|
+
txt = "Press cursor keys to move window"
|
322
|
+
win = Window.new
|
323
|
+
$ht = win.height
|
324
|
+
$wid = win.width
|
325
|
+
$pagecols = $ht / 2
|
326
|
+
$spacecols = $ht
|
327
|
+
#win.printstr txt
|
328
|
+
win.printstr("Press Ctrl-Q to quit #{win.height}:#{win.width}", win.height-1, 20)
|
329
|
+
|
330
|
+
path = File.expand_path("./")
|
331
|
+
win.printstring(0,0, "PATH: #{path} #{TOPLINE}",0)
|
332
|
+
files = get_files
|
333
|
+
current = 0
|
334
|
+
prevstart = listing(win, path, files, current, 0)
|
335
|
+
|
336
|
+
ch = 0
|
337
|
+
xx = 1
|
338
|
+
yy = 1
|
339
|
+
y = x = 1
|
340
|
+
while (ch = win.getkey) != 113
|
341
|
+
#y, x = win.getbegyx(pointer)
|
342
|
+
old_y, old_x = y, x
|
343
|
+
case ch
|
344
|
+
when -1
|
345
|
+
next
|
346
|
+
when FFI::NCurses::KEY_RIGHT
|
347
|
+
# if directory then open it
|
348
|
+
fullp = path + "/" + files[current]
|
349
|
+
if File.directory? fullp
|
350
|
+
Dir.chdir(files[current])
|
351
|
+
$patt = nil
|
352
|
+
path = Dir.pwd
|
353
|
+
#win.printstring(0,0, "PATH: #{path} ",0)
|
354
|
+
win.printstring(0,0, "PATH: #{path} #{TOPLINE}",0)
|
355
|
+
files = get_files
|
356
|
+
current = 0
|
357
|
+
#FFI::NCurses.wclrtobot(pointer)
|
358
|
+
win.wclrtobot
|
359
|
+
elsif File.readable? fullp
|
360
|
+
file_page win, fullp
|
361
|
+
win.wrefresh
|
362
|
+
# open file
|
363
|
+
end
|
364
|
+
x += 1
|
365
|
+
when FFI::NCurses::KEY_LEFT
|
366
|
+
# go back higher level
|
367
|
+
oldpath = path
|
368
|
+
Dir.chdir("..")
|
369
|
+
path = Dir.pwd
|
370
|
+
$patt = nil
|
371
|
+
win.printstring(0,0, "PATH: #{path} #{TOPLINE}",0)
|
372
|
+
files = get_files
|
373
|
+
# when going up, keep focus on the dir we came from
|
374
|
+
current = files.index(File.basename(oldpath) + "/")
|
375
|
+
current = 0 if current.nil? or current == -1
|
376
|
+
win.wclrtobot
|
377
|
+
x -= 1
|
378
|
+
when FFI::NCurses::KEY_RETURN
|
379
|
+
# if directory then open it
|
380
|
+
fullp = path + "/" + files[current]
|
381
|
+
if File.directory? fullp
|
382
|
+
Dir.chdir(files[current])
|
383
|
+
$patt = nil
|
384
|
+
path = Dir.pwd
|
385
|
+
#win.printstring(0,0, "PATH: #{path} ",0)
|
386
|
+
win.printstring(0,0, "PATH: #{path} #{TOPLINE}",0)
|
387
|
+
files = get_files
|
388
|
+
#files = Dir.entries("./")
|
389
|
+
#files.delete(".")
|
390
|
+
#files.delete("..")
|
391
|
+
current = 0
|
392
|
+
win.wclrtobot
|
393
|
+
elsif File.readable? fullp
|
394
|
+
# open file
|
395
|
+
file_open win, fullp
|
396
|
+
win.wrefresh
|
397
|
+
end
|
398
|
+
when FFI::NCurses::KEY_UP
|
399
|
+
current -=1
|
400
|
+
when FFI::NCurses::KEY_DOWN
|
401
|
+
current +=1
|
402
|
+
when FFI::NCurses::KEY_CTRL_N
|
403
|
+
current += $pagecols
|
404
|
+
when FFI::NCurses::KEY_CTRL_P
|
405
|
+
current -= $pagecols
|
406
|
+
when 32
|
407
|
+
current += $spacecols
|
408
|
+
when FFI::NCurses::KEY_BACKSPACE, 127
|
409
|
+
current -= $spacecols
|
410
|
+
when FFI::NCurses::KEY_CTRL_X
|
411
|
+
when ?=.getbyte(0)
|
412
|
+
#list = ["x this", "y that","z other","a foo", "b bar"]
|
413
|
+
list = { "h" => "hidden files toggle", "l" => "long listing toggle", "z" => "the other", "a" => "another one", "b" => "yet another" }
|
414
|
+
m = Menu.new "Toggle Options", list
|
415
|
+
key = m.getkey
|
416
|
+
win.wrefresh # otherwise menu popup remains till next key press.
|
417
|
+
case key
|
418
|
+
when 'h'
|
419
|
+
$hidden = $hidden ? nil : "D"
|
420
|
+
files = get_files
|
421
|
+
clearwin(win)
|
422
|
+
when 'l'
|
423
|
+
$long_listing = !$long_listing
|
424
|
+
clearwin(win)
|
425
|
+
end
|
426
|
+
when ?/.getbyte(0)
|
427
|
+
# search grep
|
428
|
+
# this is writing over the last line of the listing
|
429
|
+
ewin = create_input_window
|
430
|
+
ewin.printstr("/", 0, 0)
|
431
|
+
#win.wmove(1, _LINES-1)
|
432
|
+
str = getchars(ewin, 10)
|
433
|
+
ewin.destroy
|
434
|
+
#alert "Got #{str}"
|
435
|
+
$patt = str #if str
|
436
|
+
files = get_files
|
437
|
+
clearwin(win)
|
438
|
+
when ?`.getbyte(0)
|
439
|
+
main_menu
|
440
|
+
files = get_files
|
441
|
+
clearwin(win)
|
442
|
+
else
|
443
|
+
alert("key #{ch} not known")
|
444
|
+
end
|
445
|
+
#win.printstr("Pressed #{ch} on #{files[current]} ", 0, 70)
|
446
|
+
current = 0 if current < 0
|
447
|
+
current = files.size-1 if current >= files.size
|
448
|
+
# listing does not refresh files, so if files has changed, you need to refresh
|
449
|
+
prevstart = listing(win, path, files, current, prevstart)
|
450
|
+
win.wrefresh
|
451
|
+
end
|
452
|
+
|
453
|
+
rescue Object => e
|
454
|
+
@window.destroy if @window
|
455
|
+
FFI::NCurses.endwin
|
456
|
+
puts e
|
457
|
+
puts e.backtrace.join("\n")
|
458
|
+
ensure
|
459
|
+
@window.destroy if @window
|
460
|
+
FFI::NCurses.endwin
|
461
|
+
puts
|
462
|
+
end
|
data/lib/umbra/box.rb
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
# ----------------------------------------------------------------------------- #
|
2
|
+
# File: box.rb
|
3
|
+
# Description: a box or border around a container
|
4
|
+
# Author: j kepler http://github.com/mare-imbrium/canis/
|
5
|
+
# Date: 2018-04-07
|
6
|
+
# License: MIT
|
7
|
+
# Last update: 2018-04-08 09:12
|
8
|
+
# ----------------------------------------------------------------------------- #
|
9
|
+
# box.rb Copyright (C) 2018 j kepler
|
10
|
+
module Umbra
|
11
|
+
##
|
12
|
+
# A box is a container around one, maybe more, widgets.
|
13
|
+
#
|
14
|
+
class Box < Widget
|
15
|
+
attr_accessor :title
|
16
|
+
attr_accessor :width
|
17
|
+
attr_accessor :height
|
18
|
+
attr_accessor :row_offset # not used yet
|
19
|
+
attr_accessor :col_offset # not used yet
|
20
|
+
attr_accessor :visible
|
21
|
+
attr_reader :widgets
|
22
|
+
attr_reader :widget
|
23
|
+
attr_accessor :justify # right, left or center TODO
|
24
|
+
|
25
|
+
def initialize config={}, &block
|
26
|
+
@focusable = false
|
27
|
+
@visible = true
|
28
|
+
super
|
29
|
+
@int_height = @height - 2
|
30
|
+
@int_width = @width - 2
|
31
|
+
@hlines = []
|
32
|
+
@vlines = []
|
33
|
+
end
|
34
|
+
def repaint
|
35
|
+
return unless @visible
|
36
|
+
print_border @row, @col, @height, @width, @color_pair || CP_BLACK, @attr || NORMAL
|
37
|
+
print_title @title
|
38
|
+
if !@hlines.empty?
|
39
|
+
@hlines.each {|e| hline(e.first, e[1]) }
|
40
|
+
end
|
41
|
+
# what about asking for painting of widgets
|
42
|
+
end
|
43
|
+
# should we take in an array and apportion them
|
44
|
+
# since we are keeping a row in between as divider, need to adjust heights.
|
45
|
+
# Depending on how many components
|
46
|
+
def add *w
|
47
|
+
@widgets = w
|
48
|
+
num = w.size
|
49
|
+
wid = @int_width
|
50
|
+
ht = (@int_height / num)
|
51
|
+
srow = @row + 1
|
52
|
+
scol = @col + 1
|
53
|
+
w.each_with_index do |e, ix|
|
54
|
+
e.width = wid
|
55
|
+
e.height = ht
|
56
|
+
e.row = srow
|
57
|
+
e.col = scol
|
58
|
+
srow += ht + 1
|
59
|
+
@hlines << [ srow-1, scol ]
|
60
|
+
end
|
61
|
+
# FIXME there will be one additional hline in the end.
|
62
|
+
w[-1].height -= (num-1)
|
63
|
+
end
|
64
|
+
# this is best used for widgets that can be resized.
|
65
|
+
# Prefer not to use for buttons since the looks gets messed (inconsistency betwewn button and highlight).
|
66
|
+
# Therefore now, button calculates its own width which means that this program cannot determine what the width is
|
67
|
+
# and thus cannot center it.
|
68
|
+
def flow *w
|
69
|
+
@widgets = w
|
70
|
+
num = w.size
|
71
|
+
wid = (@int_width / num).floor
|
72
|
+
ht = @int_height
|
73
|
+
srow = @row + 1
|
74
|
+
scol = @col + 1
|
75
|
+
w.each_with_index do |e, ix|
|
76
|
+
# unfortunately this is messing with button width calculation
|
77
|
+
# maybe field and button should have resizable or expandable ?
|
78
|
+
e.width = wid unless e.width
|
79
|
+
e.height = ht
|
80
|
+
e.row = srow
|
81
|
+
e.col = scol
|
82
|
+
scol += wid + 1
|
83
|
+
#@hlines << [ srow-1, scol ]
|
84
|
+
end
|
85
|
+
# FIXME there will be one additional hline in the end.
|
86
|
+
# we added 1 to the scol each time, so decrement
|
87
|
+
w[-1].width -= (num-1)
|
88
|
+
end
|
89
|
+
# use if only one widget will expand into this box
|
90
|
+
def fill w
|
91
|
+
# should have been nice if I could add widget to form, but then order might get wrong
|
92
|
+
w.row = @row + 1
|
93
|
+
w.col = @col + 1
|
94
|
+
w.width = @width - 2 if w.respond_to? :width
|
95
|
+
w.height = @height - 2 if w.respond_to? :height
|
96
|
+
@widget = w
|
97
|
+
end
|
98
|
+
def hline row, col
|
99
|
+
return if row >= @row + @height
|
100
|
+
$log.debug " hline: #{row} ... #{@row} #{@height} "
|
101
|
+
FFI::NCurses.mvwhline( @graphic.pointer, row, col, FFI::NCurses::ACS_HLINE, @width-2)
|
102
|
+
end
|
103
|
+
|
104
|
+
# print a title over the box on zeroth row
|
105
|
+
# TODO right or left or center align
|
106
|
+
private def print_title stitle
|
107
|
+
return unless stitle
|
108
|
+
stitle = "| #{stitle} |"
|
109
|
+
@justify ||= :center
|
110
|
+
col = case @justify
|
111
|
+
when :left
|
112
|
+
4
|
113
|
+
when :right
|
114
|
+
@width -stitle.size - 3
|
115
|
+
else
|
116
|
+
(@width-stitle.size)/2
|
117
|
+
end
|
118
|
+
#FFI::NCurses.mvwaddstr(@pointer, 0, col, stitle)
|
119
|
+
@graphic.printstring(@row, col, stitle)
|
120
|
+
end
|
121
|
+
|
122
|
+
private def print_border row, col, height, width, color, att=FFI::NCurses::A_NORMAL
|
123
|
+
pointer = @graphic.pointer
|
124
|
+
FFI::NCurses.wattron(pointer, FFI::NCurses.COLOR_PAIR(color) | att)
|
125
|
+
FFI::NCurses.mvwaddch pointer, row, col, FFI::NCurses::ACS_ULCORNER
|
126
|
+
FFI::NCurses.mvwhline( pointer, row, col+1, FFI::NCurses::ACS_HLINE, width-2)
|
127
|
+
FFI::NCurses.mvwaddch pointer, row, col+width-1, FFI::NCurses::ACS_URCORNER
|
128
|
+
FFI::NCurses.mvwvline( pointer, row+1, col, FFI::NCurses::ACS_VLINE, height-2)
|
129
|
+
|
130
|
+
FFI::NCurses.mvwaddch pointer, row+height-1, col, FFI::NCurses::ACS_LLCORNER
|
131
|
+
FFI::NCurses.mvwhline(pointer, row+height-1, col+1, FFI::NCurses::ACS_HLINE, width-2)
|
132
|
+
FFI::NCurses.mvwaddch pointer, row+height-1, col+width-1, FFI::NCurses::ACS_LRCORNER
|
133
|
+
FFI::NCurses.mvwvline( pointer, row+1, col+width-1, FFI::NCurses::ACS_VLINE, height-2)
|
134
|
+
FFI::NCurses.wattroff(pointer, FFI::NCurses.COLOR_PAIR(color) | att)
|
135
|
+
end
|
136
|
+
end # class
|
137
|
+
end # module
|