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