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