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/lib/umbra/button.rb
ADDED
@@ -0,0 +1,130 @@
|
|
1
|
+
# ----------------------------------------------------------------------------- #
|
2
|
+
# File: button.rb
|
3
|
+
# Description: button widget that has an action associated with :PRESS event
|
4
|
+
# which by default is the SPACE key.
|
5
|
+
# Author: j kepler http://github.com/mare-imbrium/canis/
|
6
|
+
# Date: 2018-03-16
|
7
|
+
# License: MIT
|
8
|
+
# Last update: 2018-04-07 23:07
|
9
|
+
# ----------------------------------------------------------------------------- #
|
10
|
+
# button.rb Copyright (C) 2012-2018 j kepler
|
11
|
+
# == TODO
|
12
|
+
# - mnemonics with highlighting
|
13
|
+
# - default button
|
14
|
+
require 'umbra/widget'
|
15
|
+
# ----------------
|
16
|
+
module Umbra
|
17
|
+
class Button < Widget
|
18
|
+
attr_accessor :surround_chars # characters to use to surround the button, def is square brackets
|
19
|
+
# char to be underlined, and bound to Alt-char
|
20
|
+
attr_accessor :mnemonic
|
21
|
+
def initialize config={}, &block
|
22
|
+
@focusable = true
|
23
|
+
@editable = false
|
24
|
+
@highlight_attr = REVERSE
|
25
|
+
# hotkey denotes we should bind the key itself not alt-key (for menulinks)
|
26
|
+
#@hotkey = config.delete(:hotkey) 2018-03-22 -
|
27
|
+
# 2018-03-18 - FORM_ATTACHED deprecated to keep things simple
|
28
|
+
register_events([:PRESS])
|
29
|
+
@default_chars = ['> ', ' <'] # a default button is painted differently
|
30
|
+
super
|
31
|
+
|
32
|
+
|
33
|
+
@surround_chars ||= ['[ ', ' ]']
|
34
|
+
@col_offset = @surround_chars[0].length
|
35
|
+
@text_offset = 0 # used to determine where underline should fall TODO ???
|
36
|
+
map_keys
|
37
|
+
end
|
38
|
+
##
|
39
|
+
# set button based on Action
|
40
|
+
# 2018-03-22 - is this still used ?
|
41
|
+
# This allows action objects to be used in multiple places such as buttons, menus, popups etc.
|
42
|
+
def action a
|
43
|
+
text a.name
|
44
|
+
mnemonic a.mnemonic unless a.mnemonic.nil?
|
45
|
+
command { a.call }
|
46
|
+
end
|
47
|
+
|
48
|
+
def getvalue
|
49
|
+
@text
|
50
|
+
end
|
51
|
+
|
52
|
+
# ensure text has been passed or action
|
53
|
+
def getvalue_for_paint
|
54
|
+
ret = getvalue
|
55
|
+
@text_offset = @surround_chars[0].length
|
56
|
+
@surround_chars[0] + ret + @surround_chars[1]
|
57
|
+
end
|
58
|
+
|
59
|
+
def repaint # button
|
60
|
+
return unless @repaint_required
|
61
|
+
|
62
|
+
$log.debug("BUTTON repaint : #{self} r:#{@row} c:#{@col} , cp:#{@color_pair}, st:#{@state}, #{getvalue_for_paint}" )
|
63
|
+
r,c = @row, @col
|
64
|
+
_attr = @attr || NORMAL
|
65
|
+
_color = @color_pair
|
66
|
+
if @state == :HIGHLIGHTED
|
67
|
+
_color = @highlight_color_pair || @color_pair
|
68
|
+
_attr = @highlight_attr || _attr
|
69
|
+
elsif selected? # only for certain buttons lie toggle and radio
|
70
|
+
_color = @selected_color_pair || @color_pair
|
71
|
+
end
|
72
|
+
#$log.debug "XXX: button #{text} STATE is #{@state} color #{_color} , attr:#{_attr}"
|
73
|
+
value = getvalue_for_paint
|
74
|
+
#$log.debug("button repaint :#{self} r:#{r} c:#{c} col:#{_color} v: #{value} ul #{@underline} mnem #{@mnemonic} ")
|
75
|
+
#len = @width || value.length # 2018-04-07 - width is not serving a purpose right now
|
76
|
+
# # since surround chars still come where they do, and only highlight uses the width
|
77
|
+
# which looks wrong.
|
78
|
+
len = value.length
|
79
|
+
@graphic.printstring r, c, "%-*s" % [len, value], _color, _attr
|
80
|
+
|
81
|
+
# if a mnemonic character has been defined, then locate the index and highlight it.
|
82
|
+
# TODO a mnemonic can also be defined in the text with an ampersand.
|
83
|
+
if @mnemonic
|
84
|
+
index = value.index(@mnemonic) || value.index(@mnemonic.swapcase)
|
85
|
+
if index
|
86
|
+
y = c + index
|
87
|
+
x = r
|
88
|
+
@graphic.mvchgat(x, y, max=1, FFI::NCurses::A_BOLD|UNDERLINE, FFI::NCurses.COLOR_PAIR(_color || 1), nil)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
@repaint_required = false
|
92
|
+
end
|
93
|
+
|
94
|
+
## command of button (invoked on press, hotkey, space)
|
95
|
+
# added args 2008-12-20 19:22
|
96
|
+
def command *args, &block
|
97
|
+
bind_event :PRESS, *args, &block
|
98
|
+
end
|
99
|
+
## fires PRESS event of button
|
100
|
+
def fire
|
101
|
+
#$log.debug "firing PRESS #{text}"
|
102
|
+
fire_handler :PRESS, ActionEvent.new(self, :PRESS, text)
|
103
|
+
end
|
104
|
+
# for campatibility with all buttons, will apply to radio buttons mostly
|
105
|
+
def selected?; false; end
|
106
|
+
|
107
|
+
def map_keys
|
108
|
+
return if @keys_mapped
|
109
|
+
bind_key(32, "fire") { fire } if respond_to? :fire
|
110
|
+
end
|
111
|
+
|
112
|
+
# Button
|
113
|
+
def handle_key ch
|
114
|
+
super
|
115
|
+
end
|
116
|
+
|
117
|
+
# layout an array of buttons horizontally {{{
|
118
|
+
def self.button_layout buttons, row, startcol=0, cols=FFI::NCurses.COLS-1, gap=5
|
119
|
+
col = startcol
|
120
|
+
buttons.each_with_index do |b, ix|
|
121
|
+
$log.debug " BUTTON #{b}: #{b.col} "
|
122
|
+
b.row = row
|
123
|
+
b.col col
|
124
|
+
$log.debug " after BUTTON #{b}: #{b.col} "
|
125
|
+
len = b.text.length + gap
|
126
|
+
col += len
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end #BUTTON # }}}
|
130
|
+
end # module
|
@@ -0,0 +1,96 @@
|
|
1
|
+
# ----------------------------------------------------------------------------- #
|
2
|
+
# File: buttongroup.rb
|
3
|
+
# Description: Manages a group of radio buttons
|
4
|
+
# Author: j kepler http://github.com/mare-imbrium/canis/
|
5
|
+
# Date: 2018-04-02 - 08:47
|
6
|
+
# License: MIT
|
7
|
+
# Last update: 2018-04-02 23:30
|
8
|
+
# ----------------------------------------------------------------------------- #
|
9
|
+
# buttongroup.rb Copyright (C) 2012-2018 j kepler
|
10
|
+
# This is not a visual class or a widget.
|
11
|
+
# This class allows us to attach several RadioButtons to it, so it can maintain which one is the
|
12
|
+
# selected one. It also allows for assigning of commands to be executed whenever a button is pressed,
|
13
|
+
# akin to binding to the +fire+ of the button, except that one would not have to bind to each button,
|
14
|
+
# but only once here.
|
15
|
+
#
|
16
|
+
# @example
|
17
|
+
# group = ButtonGroup.new
|
18
|
+
# group.add(r1).add(r2).add(r3)
|
19
|
+
# group.command(somelabel) do |grp, label| label.text = grp.value; end
|
20
|
+
#
|
21
|
+
class ButtonGroup
|
22
|
+
|
23
|
+
# Array of buttons that have been added.
|
24
|
+
attr_reader :elements
|
25
|
+
# name for group, can be used in messages
|
26
|
+
attr_accessor :name
|
27
|
+
|
28
|
+
# the value of the radio button that is selected. To get the button itself, use +selection+.
|
29
|
+
attr_reader :value
|
30
|
+
|
31
|
+
def initialize name="Buttongroup"
|
32
|
+
@elements = []
|
33
|
+
@hash = {}
|
34
|
+
@name = name
|
35
|
+
end
|
36
|
+
|
37
|
+
# add a radio button to the group.
|
38
|
+
def add e
|
39
|
+
@elements << e
|
40
|
+
@hash[e.value] = e
|
41
|
+
e.button_group=(self)
|
42
|
+
self
|
43
|
+
end
|
44
|
+
# remove button from group
|
45
|
+
def remove e
|
46
|
+
@elements.delete e
|
47
|
+
@hash.delete e.value
|
48
|
+
self
|
49
|
+
end
|
50
|
+
|
51
|
+
# @return the radiobutton that is selected
|
52
|
+
def selection
|
53
|
+
@hash[@value]
|
54
|
+
end
|
55
|
+
|
56
|
+
# @param [String, RadioButton] +value+ of a button, or +Button+ itself to check if selected.
|
57
|
+
# @return [true or false] for whether the given value or button is the selected one
|
58
|
+
def selected? val
|
59
|
+
if val.is_a? String
|
60
|
+
@value == val
|
61
|
+
else
|
62
|
+
@hash[@value] == val
|
63
|
+
end
|
64
|
+
end
|
65
|
+
# install trigger to call whenever a value is updated
|
66
|
+
# @public called by user components
|
67
|
+
def command *args, &block
|
68
|
+
@commands ||= []
|
69
|
+
@args ||= []
|
70
|
+
@commands << block
|
71
|
+
@args << args
|
72
|
+
end
|
73
|
+
# select the given button or value.
|
74
|
+
# This may be called by user programs to programmatically select a button
|
75
|
+
def select button
|
76
|
+
if button.is_a? String
|
77
|
+
;
|
78
|
+
else
|
79
|
+
button = button.value
|
80
|
+
end
|
81
|
+
self.value = button
|
82
|
+
end
|
83
|
+
# whenever a radio button is pressed, it updates the value of the group with it;s value.
|
84
|
+
# since only one is true at a time.
|
85
|
+
def value=(value)
|
86
|
+
@value = value
|
87
|
+
# 2018-04-02 - need to repaint all the radio buttons so they become off
|
88
|
+
@elements.each {|e| e.repaint_required = true }
|
89
|
+
|
90
|
+
return unless @commands
|
91
|
+
@commands.each_with_index do |comm, ix|
|
92
|
+
comm.call(self, *@args[ix]) unless comm.nil?
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# ----------------------------------------------------------------------------- #
|
2
|
+
# File: checkbox.rb
|
3
|
+
# Description:
|
4
|
+
# Author: j kepler http://github.com/mare-imbrium/canis/
|
5
|
+
# Date: 2018-04-01 - 16:08
|
6
|
+
# License: MIT
|
7
|
+
# Last update: 2018-04-01 16:21
|
8
|
+
# ----------------------------------------------------------------------------- #
|
9
|
+
# checkbox.rb Copyright (C) 2012-2018 j kepler
|
10
|
+
module Umbra
|
11
|
+
##
|
12
|
+
# A checkbox, may be selected or unselected
|
13
|
+
#
|
14
|
+
class Checkbox < ToggleButton
|
15
|
+
attr_accessor :align_right # the button will be on the right 2008-12-09 23:41
|
16
|
+
# if a variable has been defined, off and on value will be set in it (default 0,1)
|
17
|
+
def initialize config={}, &block
|
18
|
+
@surround_chars = ['[', ']'] # 2008-12-23 23:16 added space in Button so overriding
|
19
|
+
super
|
20
|
+
end
|
21
|
+
def getvalue
|
22
|
+
@value
|
23
|
+
end
|
24
|
+
|
25
|
+
def getvalue_for_paint
|
26
|
+
buttontext = getvalue() ? "X" : " "
|
27
|
+
dtext = @width.nil? ? @text : "%-*s" % [@width, @text]
|
28
|
+
dtext = "" if @text.nil? # added 2009-01-13 00:41 since cbcellrenderer prints no text
|
29
|
+
if @align_right
|
30
|
+
@text_offset = 0
|
31
|
+
@col_offset = dtext.length + @surround_chars[0].length + 1
|
32
|
+
return "#{dtext} " + @surround_chars[0] + buttontext + @surround_chars[1]
|
33
|
+
else
|
34
|
+
pretext = @surround_chars[0] + buttontext + @surround_chars[1]
|
35
|
+
@text_offset = pretext.length + 1
|
36
|
+
@col_offset = @surround_chars[0].length
|
37
|
+
#@surround_chars[0] + buttontext + @surround_chars[1] + " #{@text}"
|
38
|
+
return pretext + " #{dtext}"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end # class
|
42
|
+
end # module
|
data/lib/umbra/dialog.rb
ADDED
@@ -0,0 +1,214 @@
|
|
1
|
+
# ----------------------------------------------------------------------------- #
|
2
|
+
# File: dialog.rb
|
3
|
+
# Description: A simple dialog box that only depends on window.
|
4
|
+
# This does not have forms or buttons, fields etc. That would introduce a circular
|
5
|
+
# dependence that I would like to avoid. This way widgets can include this to print an error
|
6
|
+
# or other message.
|
7
|
+
# NOTE: to check similar behavior, load "links" and press "q", its displays a dialog box with yes/no.
|
8
|
+
# Also check midnight-commander, press F10 to quit and see dialog box.
|
9
|
+
# Author: j kepler http://github.com/mare-imbrium/canis/
|
10
|
+
# Date: 2018-03-27 - 12:09
|
11
|
+
# License: MIT
|
12
|
+
# Last update: 2018-04-20 11:05
|
13
|
+
# ----------------------------------------------------------------------------- #
|
14
|
+
# dialog.rb Copyright (C) 2012-2018 j kepler
|
15
|
+
#
|
16
|
+
require 'umbra/window'
|
17
|
+
module Umbra
|
18
|
+
|
19
|
+
# A simple dialog box that only displays a line of text, centered.
|
20
|
+
# It can take an array of button labels (just strings) and display them, and return the index
|
21
|
+
# of the button pressed, when closed.
|
22
|
+
# If no buttons are supplied, an "Ok" button is displayed.
|
23
|
+
# Minimum requirements are `text` and `title`
|
24
|
+
class Dialog
|
25
|
+
|
26
|
+
attr_accessor :text # text or message to print centered
|
27
|
+
attr_accessor :title # title of dialog
|
28
|
+
attr_accessor :title_color_pair # color pair of title
|
29
|
+
attr_accessor :title_attr # attribute of title
|
30
|
+
attr_accessor :window_color_pair # color pair of window
|
31
|
+
attr_accessor :window_attr # attribute of window
|
32
|
+
attr_accessor :border_color_pair # color pair of border
|
33
|
+
attr_accessor :border_attr # attribute of border
|
34
|
+
attr_accessor :buttons # button text array
|
35
|
+
|
36
|
+
## currently color and attr for text is missing. I think it should be what window has.
|
37
|
+
|
38
|
+
def initialize config={}, &block
|
39
|
+
config.each_pair { |k,v| variable_set(k,v) }
|
40
|
+
if block_given?
|
41
|
+
if block.arity > 0
|
42
|
+
yield self
|
43
|
+
else
|
44
|
+
self.instance_eval(&block)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
@title ||= "Alert"
|
48
|
+
@buttons ||= ["Ok"]
|
49
|
+
end
|
50
|
+
|
51
|
+
private def variable_set var, val
|
52
|
+
send("#{var}=", val)
|
53
|
+
end
|
54
|
+
private def _create_window
|
55
|
+
text = @text || "Warning! Did not get text"
|
56
|
+
#h = 7
|
57
|
+
# increase height to 9 so we can put a fake button below text
|
58
|
+
h = 9
|
59
|
+
w = text.size + 20
|
60
|
+
# don't exceed max
|
61
|
+
w = [FFI::NCurses.COLS-10, w].min
|
62
|
+
@window_color_pair ||= CP_BLACK
|
63
|
+
@window_attr ||= REVERSE
|
64
|
+
win = create_centered_window h, w, @window_color_pair, @window_attr
|
65
|
+
|
66
|
+
## ---- border section --- {{{
|
67
|
+
row = 1
|
68
|
+
col = 2
|
69
|
+
borderatt = @border_attr || NORMAL
|
70
|
+
bordercolor = @border_color_pair || CP_BLACK
|
71
|
+
win.wattron(bordercolor | borderatt)
|
72
|
+
print_border_mb win, row, col, win.height, win.width, nil, nil
|
73
|
+
win.wattroff(bordercolor | borderatt)
|
74
|
+
## ---- border section --- }}}
|
75
|
+
|
76
|
+
## ---- title section ---- {{{
|
77
|
+
@title ||= "No title"
|
78
|
+
@title_color_pair ||= CP_CYAN
|
79
|
+
@title_attr ||= REVERSE
|
80
|
+
title = " "+@title+" "
|
81
|
+
# normalcolor gives a white on black stark title like links and elinks
|
82
|
+
# You can also do 'acolor' to give you a sober title that does not take attention away, like mc
|
83
|
+
win.printstring(row=1,col=(w-title.length)/2,title, color=@title_color_pair, @title_attr)
|
84
|
+
## ---- title section ---- }}}
|
85
|
+
|
86
|
+
# text can be longer than window. truncate or wrap
|
87
|
+
# tet.size can be longer than w
|
88
|
+
_col = (w-text.size)/2
|
89
|
+
_col = 3 if _col < 3
|
90
|
+
win.printstring 3, _col, text
|
91
|
+
## ---- button section ---- {{{
|
92
|
+
paint_buttons win, @buttons, 0
|
93
|
+
## ---- button section ---- }}}
|
94
|
+
@window = win
|
95
|
+
win.wrefresh
|
96
|
+
end
|
97
|
+
def paint_buttons win, buttons, active_index
|
98
|
+
brow = 6
|
99
|
+
bcol = (win.width-(buttons.size*10))/2
|
100
|
+
origbcol = bcol
|
101
|
+
FFI::NCurses.mvwhline(win.pointer, brow-1, 3, FFI::NCurses::ACS_HLINE, win.width-6)
|
102
|
+
#@button_color ||= create_color_pair(COLOR_BLACK, COLOR_MAGENTA)
|
103
|
+
@button_color ||= CP_BLACK
|
104
|
+
active_color = create_color_pair(COLOR_BLACK, COLOR_MAGENTA)
|
105
|
+
#active_color = create_color_pair(COLOR_MAGENTA, COLOR_BLACK)
|
106
|
+
active_col = bcol
|
107
|
+
buttons.each_with_index do |button, ix|
|
108
|
+
button_attr = NORMAL
|
109
|
+
button_color = @button_color
|
110
|
+
_button = "[ #{button} ]"
|
111
|
+
if ix == active_index
|
112
|
+
button_attr = BOLD
|
113
|
+
button_color = active_color
|
114
|
+
active_col = bcol
|
115
|
+
_button = "> #{button} <"
|
116
|
+
end
|
117
|
+
win.printstring brow, bcol, _button, button_color, button_attr
|
118
|
+
bcol += 10
|
119
|
+
end
|
120
|
+
FFI::NCurses.wmove(win.pointer, brow, active_col+2)
|
121
|
+
end
|
122
|
+
|
123
|
+
# convenience func to get int value of a key {{{
|
124
|
+
# added 2014-05-05
|
125
|
+
# instead of ?\C-a.getbyte(0)
|
126
|
+
# use key(?\C-a)
|
127
|
+
# or key(?a) or key(?\M-x)
|
128
|
+
def key ch
|
129
|
+
ch.getbyte(0)
|
130
|
+
end # }}}
|
131
|
+
def run
|
132
|
+
_create_window unless @window
|
133
|
+
win = @window
|
134
|
+
buttoncount = @buttons.count
|
135
|
+
buttonindex = 0
|
136
|
+
begin
|
137
|
+
while (ch = win.getkey) != FFI::NCurses::KEY_RETURN
|
138
|
+
begin
|
139
|
+
next if ch == -1
|
140
|
+
break if ch == 32 or key(?q) == ch
|
141
|
+
# go to next button if right or down or TAB pressed
|
142
|
+
if ch == FFI::NCurses::KEY_TAB or ch == FFI::NCurses::KEY_RIGHT or FFI::NCurses::KEY_DOWN
|
143
|
+
buttonindex += 1
|
144
|
+
elsif ch == FFI::NCurses::KEY_LEFT or FFI::NCurses::KEY_UP
|
145
|
+
buttonindex -= 1
|
146
|
+
else
|
147
|
+
# should check against first char of buttons TODO
|
148
|
+
#puts "Don't know #{ch}"
|
149
|
+
end
|
150
|
+
buttonindex = 0 if buttonindex > buttoncount-1
|
151
|
+
buttonindex = buttoncount-1 if buttonindex < 0
|
152
|
+
paint_buttons win, @buttons, buttonindex
|
153
|
+
rescue => e
|
154
|
+
puts e
|
155
|
+
puts e.backtrace.join("\n")
|
156
|
+
end
|
157
|
+
win.wrefresh
|
158
|
+
end
|
159
|
+
ensure
|
160
|
+
win.destroy
|
161
|
+
end
|
162
|
+
#FFI::NCurses.endwin # don't think this should be here if popped up by another window
|
163
|
+
return buttonindex
|
164
|
+
end
|
165
|
+
end # module
|
166
|
+
|
167
|
+
# create a centered window. # {{{
|
168
|
+
# NOTE: this should probably go into window class, or some util class.
|
169
|
+
# TODO: it hardcodes background color. fix this.
|
170
|
+
def create_centered_window height, width, color_pair=0, attrib=REVERSE
|
171
|
+
row = ((FFI::NCurses.LINES-height)/2).floor
|
172
|
+
col = ((FFI::NCurses.COLS-width)/2).floor
|
173
|
+
win = Window.new height, width, row, col
|
174
|
+
#FFI::NCurses.wbkgd(win.pointer, FFI::NCurses.COLOR_PAIR(0) | REVERSE); # does not work on xterm-256color
|
175
|
+
FFI::NCurses.wbkgd(win.pointer, FFI::NCurses.COLOR_PAIR(color_pair) | attrib); # does not work on xterm-256color
|
176
|
+
#FFI::NCurses.wbkgd(win.pointer, color_pair | attrib)
|
177
|
+
return win
|
178
|
+
end # }}}
|
179
|
+
private def print_border_mb window, row, col, height, width, color, attr # {{{
|
180
|
+
win = window.pointer
|
181
|
+
#att = attr
|
182
|
+
len = width
|
183
|
+
len = FFI::NCurses.COLS if len == 0
|
184
|
+
space_char = " ".codepoints.first
|
185
|
+
(row-1).upto(row+height-1) do |r|
|
186
|
+
# this loop clears the screen, printing spaces does not work since ncurses does not do anything
|
187
|
+
FFI::NCurses.mvwhline(win, r, col, space_char, len)
|
188
|
+
end
|
189
|
+
|
190
|
+
FFI::NCurses.mvwaddch win, row, col, FFI::NCurses::ACS_ULCORNER
|
191
|
+
FFI::NCurses.mvwhline( win, row, col+1, FFI::NCurses::ACS_HLINE, width-6)
|
192
|
+
FFI::NCurses.mvwaddch win, row, col+width-5, FFI::NCurses::ACS_URCORNER
|
193
|
+
FFI::NCurses.mvwvline( win, row+1, col, FFI::NCurses::ACS_VLINE, height-4)
|
194
|
+
|
195
|
+
FFI::NCurses.mvwaddch win, row+height-3, col, FFI::NCurses::ACS_LLCORNER
|
196
|
+
FFI::NCurses.mvwhline(win, row+height-3, col+1, FFI::NCurses::ACS_HLINE, width-6)
|
197
|
+
FFI::NCurses.mvwaddch win, row+height-3, col+width-5, FFI::NCurses::ACS_LRCORNER
|
198
|
+
FFI::NCurses.mvwvline( win, row+1, col+width-5, FFI::NCurses::ACS_VLINE, height-4)
|
199
|
+
end # }}}
|
200
|
+
end
|
201
|
+
|
202
|
+
if __FILE__ == $0
|
203
|
+
include Umbra
|
204
|
+
ch = nil
|
205
|
+
begin
|
206
|
+
init_curses
|
207
|
+
cp = create_color_pair( COLOR_BLUE, COLOR_WHITE )
|
208
|
+
m = Dialog.new text: ARGV[0], title: ARGV[1]||"Alert", buttons: ["Yes", "No"], window_color_pair: cp, window_attr: NORMAL
|
209
|
+
ch = m.run
|
210
|
+
ensure
|
211
|
+
FFI::NCurses.endwin
|
212
|
+
end
|
213
|
+
puts "got key: #{ch}"
|
214
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
module Umbra
|
2
|
+
# module containing methods to enable widgets and forms to handle events.
|
3
|
+
# Included by form and widget
|
4
|
+
module EventHandler
|
5
|
+
# register_events: widgets may register their events prior to calling super {{{
|
6
|
+
# This ensures that caller programs don't use wrong event names.
|
7
|
+
#
|
8
|
+
def register_events eves
|
9
|
+
@_events ||= []
|
10
|
+
case eves
|
11
|
+
when Array
|
12
|
+
@_events.push(*eves)
|
13
|
+
when Symbol
|
14
|
+
@_events << eves
|
15
|
+
else
|
16
|
+
raise ArgumentError "register_events: Don't know how to handle #{eves.class}"
|
17
|
+
end
|
18
|
+
end # }}}
|
19
|
+
##
|
20
|
+
# bind_event: bind an event to a block, optional args will also be passed when calling {{{
|
21
|
+
# 2018-04-01 - renamed +bind+ to +bind_event+
|
22
|
+
def bind_event event, *xargs, &blk
|
23
|
+
#$log.debug "#{self} called EventHandler BIND #{event}, args:#{xargs} "
|
24
|
+
if @_events
|
25
|
+
$log.warn "bind: #{self.class} does not support this event: #{event}. #{@_events} " if !event? event
|
26
|
+
#raise ArgumentError, "#{self.class} does not support this event: #{event}. #{@_events} " if !event? event
|
27
|
+
else
|
28
|
+
# it can come here if bind in initial block, since widgets add to @_event after calling super
|
29
|
+
# maybe we can change that.
|
30
|
+
$log.warn "BIND #{self.class} (#{event}) XXXXX no events defined in @_events. Please do so to avoid bugs and debugging. This will become a fatal error soon."
|
31
|
+
end
|
32
|
+
@handler ||= {}
|
33
|
+
@event_args ||= {}
|
34
|
+
@handler[event] ||= []
|
35
|
+
@handler[event] << blk
|
36
|
+
@event_args[event] ||= []
|
37
|
+
@event_args[event] << xargs
|
38
|
+
end # }}}
|
39
|
+
|
40
|
+
##
|
41
|
+
# fire_handler: Fire all bindings for given event {{{
|
42
|
+
# e.g. fire_handler :ENTER, self
|
43
|
+
# The first parameter passed to the calling block is either self, or some action event
|
44
|
+
# The second and beyond are any objects you passed when using `bind` or `command`.
|
45
|
+
# Exceptions are caught here itself, or else they prevent objects from updating, usually the error is
|
46
|
+
# in the block sent in by application, not our error.
|
47
|
+
# TODO: if an object throws a subclass of VetoException we should not catch it and throw it back for
|
48
|
+
# caller to catch and take care of, such as prevent LEAVE or update etc.
|
49
|
+
def fire_handler event, object
|
50
|
+
$log.debug "inside def fire_handler evt:#{event}, o: #{object.class}"
|
51
|
+
if !@handler.nil?
|
52
|
+
if @_events
|
53
|
+
raise ArgumentError, "fire_handler: #{self.class} does not support this event: #{event}. #{@_events} " if !event? event
|
54
|
+
else
|
55
|
+
$log.debug "fire_handler #{self.class} XXXXX no events defined in @_events "
|
56
|
+
end
|
57
|
+
ablk = @handler[event]
|
58
|
+
if !ablk.nil?
|
59
|
+
aeve = @event_args[event]
|
60
|
+
ablk.each_with_index do |blk, ix|
|
61
|
+
#$log.debug "#{self} called EventHandler firehander #{@name}, #{event}, obj: #{object},args: #{aeve[ix]}"
|
62
|
+
$log.debug "#{self} called EventHandler firehander #{@name}, #{event}"
|
63
|
+
begin
|
64
|
+
blk.call object, *aeve[ix]
|
65
|
+
rescue FieldValidationException => fve
|
66
|
+
# added 2011-09-26 1.3.0 so a user raised exception on LEAVE
|
67
|
+
# keeps cursor in same field.
|
68
|
+
raise fve
|
69
|
+
#rescue PropertyVetoException => pve
|
70
|
+
# 2018-03-18 - commented off
|
71
|
+
# added 2011-09-26 1.3.0 so a user raised exception on LEAVE
|
72
|
+
# keeps cursor in same field.
|
73
|
+
#raise pve
|
74
|
+
rescue => ex
|
75
|
+
## some don't have name
|
76
|
+
# FIXME this should be displayed somewhere. It just goes into log file quietly.
|
77
|
+
$log.error "======= Error ERROR in block event #{self}: #{event}"
|
78
|
+
$log.error ex
|
79
|
+
$log.error(ex.backtrace.join("\n"))
|
80
|
+
alert ex.to_s # added 2018-04-08 - 08:55 so it shows up
|
81
|
+
FFI::NCurses.beep # doesn't do anything, maybe switched off in preferences
|
82
|
+
end
|
83
|
+
end
|
84
|
+
else
|
85
|
+
# there is no block for this key/event
|
86
|
+
# we must behave exactly as processkey
|
87
|
+
# NOTE this is too risky since then buttons and radio buttons
|
88
|
+
# that don't have any command don;t update,so removing 2011-12-2
|
89
|
+
#return :UNHANDLED
|
90
|
+
return :NO_BLOCK
|
91
|
+
end # if
|
92
|
+
else
|
93
|
+
# there is no handler
|
94
|
+
# I've done this since list traps ENTER but rarely uses it.
|
95
|
+
# For buttons default, we'd like to trap ENTER even when focus is elsewhere
|
96
|
+
# we must behave exactly as processkey
|
97
|
+
# NOTE this is too risky since then buttons and radio buttons
|
98
|
+
# that don't have any command don;t update,so removing 2011-12-2
|
99
|
+
#return :UNHANDLED
|
100
|
+
# If caller wants, can return UNHANDLED such as list and ENTER.
|
101
|
+
return :NO_BLOCK
|
102
|
+
end # if
|
103
|
+
end # }}}
|
104
|
+
|
105
|
+
# event? : returns boolean depending on whether this widget has registered the given event {{{
|
106
|
+
def event? eve
|
107
|
+
@_events.include? eve
|
108
|
+
end # }}}
|
109
|
+
|
110
|
+
# ActionEvent # {{{
|
111
|
+
# source - as always is the object whose event has been fired
|
112
|
+
# id - event identifier (seems redundant since we bind events often separately.
|
113
|
+
# event - is :PRESS
|
114
|
+
# action_command - command string associated with event (such as title of button that changed
|
115
|
+
ActionEvent = Struct.new(:source, :event, :action_command) do
|
116
|
+
# This should always return the most relevant text associated with this object
|
117
|
+
# so the user does not have to go through the source object's documentation.
|
118
|
+
# It should be a user-friendly string
|
119
|
+
# @return text associated with source (label of button)
|
120
|
+
def text
|
121
|
+
source.text
|
122
|
+
end
|
123
|
+
|
124
|
+
# This is similar to text and can often be just an alias.
|
125
|
+
# However, i am putting this for backward compatibility with programs
|
126
|
+
# that received the object and called it's getvalue. It is better to use text.
|
127
|
+
# @return text associated with source (label of button)
|
128
|
+
def getvalue
|
129
|
+
raise "getvalue in eventhandler. remove if does not happen in 2018"
|
130
|
+
source.getvalue
|
131
|
+
end
|
132
|
+
end # }}}
|
133
|
+
end # module eventh
|
134
|
+
end # module
|