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/widget.rb
ADDED
@@ -0,0 +1,220 @@
|
|
1
|
+
##
|
2
|
+
# Basic widget class superclass. Anything embedded in a form should
|
3
|
+
# extend this, if it wants to be repainted or wants focus. Otherwise.
|
4
|
+
# form will be unaware of it.
|
5
|
+
# 2018-03-08 -
|
6
|
+
#require 'umbra/form' # for EventHandler !!!
|
7
|
+
require 'umbra/eventhandler' # for register_events and fire_handler etc
|
8
|
+
require 'umbra/keymappinghandler' # for bind_key and process_key
|
9
|
+
|
10
|
+
module Umbra
|
11
|
+
class FieldValidationException < RuntimeError
|
12
|
+
end
|
13
|
+
class Widget
|
14
|
+
include EventHandler
|
15
|
+
include KeyMappingHandler
|
16
|
+
# common interface for text related to a field, label, textview, button etc
|
17
|
+
attr_accessor :text, :width, :height
|
18
|
+
|
19
|
+
# foreground and background colors when focussed. Currently used with buttons and field
|
20
|
+
# Form checks and repaints on entry if these are set.
|
21
|
+
attr_accessor :highlight_color_pair
|
22
|
+
attr_accessor :highlight_attr
|
23
|
+
|
24
|
+
# NOTE: 2018-03-04 - user will have to call repaint_required if he changes color or coordinates.
|
25
|
+
attr_accessor :row, :col # location of object
|
26
|
+
#attr_writer :color, :bgcolor # normal foreground and background 2018-03-08 - now color_pair
|
27
|
+
# moved to a method which calculates color 2011-11-12
|
28
|
+
attr_accessor :color_pair # instead of colors give just color_pair
|
29
|
+
attr_accessor :attr # attribute bold, normal, reverse
|
30
|
+
attr_accessor :name # name to refr to or recall object by_name
|
31
|
+
attr_accessor :curpos # cursor position inside object - column, not row.
|
32
|
+
attr_reader :config # can be used for popping user objects too
|
33
|
+
#attr_accessor :form # made accessor 2008-11-27 22:32 so menu can set
|
34
|
+
attr_accessor :graphic # window which should be set by form when adding 2018-03-19
|
35
|
+
attr_accessor :state # normal, selected, highlighted
|
36
|
+
attr_reader :row_offset, :col_offset # where should the cursor be placed to start with
|
37
|
+
attr_accessor :visible # boolean # 2008-12-09 11:29
|
38
|
+
# if changing focusable property of a field after form creation, you may need to call
|
39
|
+
# pack again, or atl east update_focusables
|
40
|
+
attr_reader :focusable # boolean can this get focus # 2018-03-21 - 23:13
|
41
|
+
# 2018-03-04 - we should use modified as accessor unless it is due to setting forms modified
|
42
|
+
# 2018-03-22 - making it accessor
|
43
|
+
attr_accessor :modified # boolean, value modified or not (moved from field 2009-01-18 00:14 )
|
44
|
+
#attr_accessor :help_text # added 2009-01-22 17:41 can be used for status/tooltips
|
45
|
+
|
46
|
+
#attr_accessor :parent_component # added 2010-01-12 23:28 BUFFERED - to bubble up
|
47
|
+
|
48
|
+
# NOTE state takes care of this and is set by form. boolean
|
49
|
+
attr_reader :focussed # is this widget in focus, so they may paint differently
|
50
|
+
|
51
|
+
# height percent and width percent used in stacks and flows.
|
52
|
+
#attr_accessor :height_pc, :width_pc # may bring this back
|
53
|
+
|
54
|
+
# descriptions for each key set in _key_map
|
55
|
+
# 2018-03-07 - NOT_SURE
|
56
|
+
attr_reader :key_label
|
57
|
+
attr_reader :handler # event handler
|
58
|
+
# adding as attr_accessor 2018-03-22 -
|
59
|
+
# is a repaint required or not, boolean
|
60
|
+
attr_accessor :repaint_required
|
61
|
+
|
62
|
+
def initialize aconfig={}, &block
|
63
|
+
@row_offset ||= 0
|
64
|
+
@col_offset ||= 0
|
65
|
+
@state = :NORMAL
|
66
|
+
|
67
|
+
@handler = nil # we can avoid firing if nil
|
68
|
+
# These are standard events for most widgets which will be fired by
|
69
|
+
# Form. In the case of CHANGED, form fires if it's editable property is set, so
|
70
|
+
# it does not apply to all widgets.
|
71
|
+
# 2018-03-18 - proporty change is deprecated since we don't use dsl_property any longer
|
72
|
+
register_events( [:ENTER, :LEAVE, :CHANGED, :PROPERTY_CHANGE])
|
73
|
+
@repaint_required = true # added 2018-03-20 - so all widgets get it
|
74
|
+
|
75
|
+
aconfig.each_pair { |k,v| variable_set(k,v) }
|
76
|
+
#instance_eval &block if block_given?
|
77
|
+
if block_given?
|
78
|
+
if block.arity > 0
|
79
|
+
yield self
|
80
|
+
else
|
81
|
+
self.instance_eval(&block)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def variable_set var, val
|
87
|
+
send("#{var}=", val)
|
88
|
+
end
|
89
|
+
def init_vars
|
90
|
+
# just in case anyone does a super. Not putting anything here
|
91
|
+
# since i don't want anyone accidentally overriding
|
92
|
+
end
|
93
|
+
|
94
|
+
# modified
|
95
|
+
##
|
96
|
+
# typically read will be overridden to check if value changed from what it was on enter.
|
97
|
+
# getter and setter for modified (added 2009-01-18 12:31 )
|
98
|
+
def modified?
|
99
|
+
@modified
|
100
|
+
end
|
101
|
+
#def set_modified tf=true
|
102
|
+
#@modified = tf
|
103
|
+
#end
|
104
|
+
#alias :modified :set_modified
|
105
|
+
|
106
|
+
# triggered whenever a widget is entered.
|
107
|
+
# TODO should we not fix cursor at this point ?
|
108
|
+
def on_enter
|
109
|
+
@state = :HIGHLIGHTED # duplicating since often these are inside containers
|
110
|
+
@focussed = true
|
111
|
+
if @handler && @handler.has_key?(:ENTER)
|
112
|
+
fire_handler :ENTER, self
|
113
|
+
end
|
114
|
+
end
|
115
|
+
## Called when user exits a widget
|
116
|
+
# 2018-03-04 - Are we keeping this at all, can we avoid NOT_SURE
|
117
|
+
def on_leave
|
118
|
+
@state = :NORMAL # duplicating since often these are inside containers
|
119
|
+
@focussed = false
|
120
|
+
if @handler && @handler.has_key?(:LEAVE)
|
121
|
+
fire_handler :LEAVE, self
|
122
|
+
end
|
123
|
+
end
|
124
|
+
##
|
125
|
+
# @return row and col of a widget where painting data actually starts
|
126
|
+
# row and col is where a widget starts. offsets usually take into account borders.
|
127
|
+
# the offsets typically are where the cursor should be positioned inside, upon on_enter.
|
128
|
+
def rowcol
|
129
|
+
return @row+@row_offset, @col+@col_offset
|
130
|
+
end
|
131
|
+
## return the value of the widget.
|
132
|
+
def getvalue
|
133
|
+
@text
|
134
|
+
end
|
135
|
+
##
|
136
|
+
# Am making a separate method since often value for print differs from actual value
|
137
|
+
def getvalue_for_paint
|
138
|
+
getvalue
|
139
|
+
end
|
140
|
+
##
|
141
|
+
# default repaint method. Called by form for all widgets.
|
142
|
+
# widget does not have display_length.
|
143
|
+
def repaint
|
144
|
+
r,c = rowcol
|
145
|
+
$log.debug("widget repaint : r:#{r} c:#{c} col:#{@color_pair}" )
|
146
|
+
value = getvalue_for_paint
|
147
|
+
len = @width || value.length
|
148
|
+
acolor = @color_pair
|
149
|
+
@graphic.printstring r, c, "%-*s" % [len, value], acolor, attr()
|
150
|
+
end
|
151
|
+
|
152
|
+
def destroy
|
153
|
+
$log.debug "DESTROY : widget #{@name} "
|
154
|
+
panel = @window.panel
|
155
|
+
Ncurses::Panel.del_panel(panel.pointer) if !panel.nil?
|
156
|
+
@window.delwin if !@window.nil?
|
157
|
+
end
|
158
|
+
|
159
|
+
# puts cursor on correct row.
|
160
|
+
def set_form_row
|
161
|
+
raise "uncalled set_form_row"
|
162
|
+
r, c = rowcol
|
163
|
+
setrowcol row, nil # does not exist any longer
|
164
|
+
end
|
165
|
+
# set cursor on correct column, widget
|
166
|
+
# Ideally, this should be overriden, as it is not likely to be correct.
|
167
|
+
# NOTE: this is okay for some widgets but NOT for containers
|
168
|
+
# that will call their own components SFR and SFC
|
169
|
+
#Currently, Field has overriden this. +setrowcol+ does not exist any longer.
|
170
|
+
def set_form_col col1=@curpos
|
171
|
+
@curpos = col1 || 0 # 2010-01-14 21:02
|
172
|
+
#@form.col = @col + @col_offset + @curpos
|
173
|
+
c = @col + @col_offset + @curpos
|
174
|
+
#$log.warn " #{@name} empty set_form_col #{c}, curpos #{@curpos} , #{@col} + #{@col_offset} #{@form} "
|
175
|
+
setrowcol nil, c
|
176
|
+
end
|
177
|
+
|
178
|
+
##
|
179
|
+
# to be added at end of handle_key of widgets so instlalled actions can be checked
|
180
|
+
def handle_key(ch)
|
181
|
+
ret = process_key ch, self
|
182
|
+
return :UNHANDLED if ret == :UNHANDLED
|
183
|
+
0
|
184
|
+
end
|
185
|
+
|
186
|
+
# is the entire widget to be repainted including things like borders and titles
|
187
|
+
# earlier took a default of true, now must be explicit. Perhaps, not used currently.
|
188
|
+
def repaint_all(tf)
|
189
|
+
@repaint_all = tf
|
190
|
+
@repaint_required = tf
|
191
|
+
end
|
192
|
+
# shortcut for users to indicate that a widget should be redrawn since some property has been changed.
|
193
|
+
def touch
|
194
|
+
@repaint_required = true
|
195
|
+
end
|
196
|
+
|
197
|
+
|
198
|
+
# a general method for all widgets to override with their favorite or most meaninful event
|
199
|
+
# Ideally this is where the block in the constructor should land up.
|
200
|
+
# @since 1.5.0 2011-11-21
|
201
|
+
# 2018-03-08 - NOT_SURE
|
202
|
+
def command *args, &block
|
203
|
+
if event? :PRESS
|
204
|
+
bind_event :PRESS, *args, &block
|
205
|
+
else
|
206
|
+
bind_event :CHANGED, *args, &block
|
207
|
+
end
|
208
|
+
end
|
209
|
+
def _form=(aform)
|
210
|
+
@_form = aform
|
211
|
+
end
|
212
|
+
def focusable=(bool)
|
213
|
+
$log.debug " inside focusable= with #{bool} "
|
214
|
+
@focusable = bool
|
215
|
+
@_form.update_focusables if @_form
|
216
|
+
end
|
217
|
+
#
|
218
|
+
## ADD HERE WIDGET
|
219
|
+
end #
|
220
|
+
end # module
|
data/lib/umbra/window.rb
ADDED
@@ -0,0 +1,270 @@
|
|
1
|
+
require 'ffi-ncurses'
|
2
|
+
require 'ffi-ncurses/widechars'
|
3
|
+
|
4
|
+
module Umbra
|
5
|
+
|
6
|
+
# attribute constants, use them to specify an attrib for a widget or window.
|
7
|
+
BOLD = FFI::NCurses::A_BOLD
|
8
|
+
REVERSE = FFI::NCurses::A_REVERSE
|
9
|
+
UNDERLINE = FFI::NCurses::A_UNDERLINE
|
10
|
+
NORMAL = FFI::NCurses::A_NORMAL
|
11
|
+
|
12
|
+
# color constants, use these when creating a color
|
13
|
+
COLOR_BLACK = FFI::NCurses::BLACK
|
14
|
+
COLOR_WHITE = FFI::NCurses::WHITE
|
15
|
+
COLOR_BLUE = FFI::NCurses::BLUE
|
16
|
+
COLOR_RED = FFI::NCurses::RED
|
17
|
+
COLOR_GREEN = FFI::NCurses::GREEN
|
18
|
+
COLOR_CYAN = FFI::NCurses::CYAN
|
19
|
+
COLOR_MAGENTA = FFI::NCurses::MAGENTA
|
20
|
+
|
21
|
+
# Initialize ncurses before any program.
|
22
|
+
# Reduce the value of $ncurses_timeout if you want a quicker response to Escape keys or continuous updates.
|
23
|
+
# I have set the default to 1000.
|
24
|
+
def init_curses
|
25
|
+
FFI::NCurses.initscr
|
26
|
+
FFI::NCurses.curs_set 1
|
27
|
+
FFI::NCurses.raw
|
28
|
+
FFI::NCurses.noecho
|
29
|
+
FFI::NCurses.keypad FFI::NCurses.stdscr, true
|
30
|
+
FFI::NCurses.scrollok FFI::NCurses.stdscr, true
|
31
|
+
if FFI::NCurses.has_colors
|
32
|
+
FFI::NCurses.start_color
|
33
|
+
std_colors
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
# COLOR_BLACK 0
|
39
|
+
# COLOR_RED 1
|
40
|
+
# COLOR_GREEN 2
|
41
|
+
# COLOR_YELLOW 3
|
42
|
+
# COLOR_BLUE 4
|
43
|
+
# COLOR_MAGENTA 5
|
44
|
+
# COLOR_CYAN 6
|
45
|
+
# COLOR_WHITE 7
|
46
|
+
|
47
|
+
# In case, the init_pairs are changed, then update these as well, so that the programs use the correct pairs.
|
48
|
+
CP_BLACK = 0
|
49
|
+
CP_RED = 1
|
50
|
+
CP_GREEN = 2
|
51
|
+
CP_YELLOW = 3
|
52
|
+
CP_BLUE = 4
|
53
|
+
CP_MAGENTA = 5
|
54
|
+
CP_CYAN = 6
|
55
|
+
CP_WHITE = 7
|
56
|
+
# defining various colors
|
57
|
+
# NOTE this should be done by application or else we will be changing this all the time.
|
58
|
+
def std_colors
|
59
|
+
FFI::NCurses.use_default_colors
|
60
|
+
# 2018-03-17 - changing it to ncurses defaults
|
61
|
+
FFI::NCurses.init_pair(0, FFI::NCurses::BLACK, -1)
|
62
|
+
FFI::NCurses.init_pair(1, FFI::NCurses::RED, -1)
|
63
|
+
FFI::NCurses.init_pair(2, FFI::NCurses::GREEN, -1)
|
64
|
+
FFI::NCurses.init_pair(3, FFI::NCurses::YELLOW, -1)
|
65
|
+
FFI::NCurses.init_pair(4, FFI::NCurses::BLUE, -1)
|
66
|
+
FFI::NCurses.init_pair(5, FFI::NCurses::MAGENTA, -1)
|
67
|
+
FFI::NCurses.init_pair(6, FFI::NCurses::CYAN, -1)
|
68
|
+
FFI::NCurses.init_pair(7, FFI::NCurses::WHITE, -1)
|
69
|
+
# ideally the rest should be done by application
|
70
|
+
#FFI::NCurses.init_pair(8, FFI::NCurses::WHITE, -1)
|
71
|
+
#FFI::NCurses.init_pair(9, FFI::NCurses::BLUE, -1)
|
72
|
+
FFI::NCurses.init_pair(10, FFI::NCurses::BLACK, FFI::NCurses::CYAN)
|
73
|
+
FFI::NCurses.init_pair(12, FFI::NCurses::BLACK, FFI::NCurses::BLUE)
|
74
|
+
FFI::NCurses.init_pair(13, FFI::NCurses::BLACK, FFI::NCurses::MAGENTA)
|
75
|
+
|
76
|
+
FFI::NCurses.init_pair(14, FFI::NCurses::WHITE, FFI::NCurses::CYAN)
|
77
|
+
=begin
|
78
|
+
FFI::NCurses.init_pair(8, FFI::NCurses::WHITE, FFI::NCurses::BLUE)
|
79
|
+
FFI::NCurses.init_pair(9, FFI::NCurses::BLUE, FFI::NCurses::BLUE)
|
80
|
+
FFI::NCurses.init_pair(10, FFI::NCurses::BLACK, FFI::NCurses::GREEN)
|
81
|
+
FFI::NCurses.init_pair(11, FFI::NCurses::BLACK, FFI::NCurses::YELLOW)
|
82
|
+
FFI::NCurses.init_pair(12, FFI::NCurses::BLACK, FFI::NCurses::BLUE)
|
83
|
+
FFI::NCurses.init_pair(13, FFI::NCurses::BLACK, FFI::NCurses::MAGENTA)
|
84
|
+
FFI::NCurses.init_pair(14, FFI::NCurses::BLACK, FFI::NCurses::CYAN)
|
85
|
+
FFI::NCurses.init_pair(15, FFI::NCurses::BLACK, FFI::NCurses::WHITE)
|
86
|
+
=end
|
87
|
+
end
|
88
|
+
|
89
|
+
# create and return a color_pair for a combination of bg and fg.
|
90
|
+
# This will always return the same color_pair so a duplicate one will not be created.
|
91
|
+
# @param bgcolor [Integer] color of background e.g., COLOR_BLACK
|
92
|
+
# @param fgcolor [Integer] color of foreground e.g., COLOR_WHITE
|
93
|
+
# @return [Integer] - color_pair which can be passed to #printstring, or used directly as #COLOR_PAIR(int)
|
94
|
+
def create_color_pair(bgcolor, fgcolor)
|
95
|
+
code = (bgcolor*10) + fgcolor
|
96
|
+
FFI::NCurses.init_pair(code, fgcolor, bgcolor)
|
97
|
+
return code
|
98
|
+
end
|
99
|
+
#
|
100
|
+
## Window class
|
101
|
+
# Creates and manages the underlying window in which we write or place a form and fields.
|
102
|
+
# The two important methods here are the constructor, and +destroy()+.
|
103
|
+
# +pointer+ is important for making further direct calls to FFI::NCurses.
|
104
|
+
class Window
|
105
|
+
# pointer to FFI routines, use when calling FFI directly.
|
106
|
+
attr_reader :pointer # window pointer
|
107
|
+
attr_reader :panel # panel associated with window
|
108
|
+
attr_reader :width, :height, :top, :left
|
109
|
+
|
110
|
+
# creates a window with given height, width, top and left.
|
111
|
+
# If no args given, creates a root window (i.e. full size).
|
112
|
+
# @param height [Integer]
|
113
|
+
# @param width [Integer]
|
114
|
+
# @param top [Integer]
|
115
|
+
# @param left [Integer]
|
116
|
+
def initialize h=0, w=0, top=0, left=0
|
117
|
+
@height, @width, @top, @left = h, w, top, left
|
118
|
+
|
119
|
+
@height = FFI::NCurses.LINES if @height == 0 # 2011-11-14 added since tired of checking for zero
|
120
|
+
@width = FFI::NCurses.COLS if @width == 0
|
121
|
+
@pointer = FFI::NCurses.newwin(@height, @width, @top, @left) # added FFI 2011-09-6
|
122
|
+
|
123
|
+
@panel = FFI::NCurses.new_panel(@pointer)
|
124
|
+
FFI::NCurses.keypad(@pointer, true)
|
125
|
+
return @pointer
|
126
|
+
end
|
127
|
+
|
128
|
+
# print string at x, y coordinates. replace this with the original one below
|
129
|
+
# @deprecated
|
130
|
+
def printstr(str, x=0,y=0)
|
131
|
+
win = @pointer
|
132
|
+
FFI::NCurses.wmove(win, x, y)
|
133
|
+
FFI::NCurses.waddstr win, str
|
134
|
+
end
|
135
|
+
|
136
|
+
# 2018-03-08 - taken from canis reduced
|
137
|
+
# print given string at row, col with given color and attributes
|
138
|
+
# @param row [Integer] row to print on
|
139
|
+
# @param col [Integer] column to print on
|
140
|
+
# @param color [Integer] color_pair created earlier
|
141
|
+
# @param attr [Integer] any of the four FFI attributes, e.g. A_BOLD, A_REVERSE
|
142
|
+
def printstring(r,c,string, color=0, att = FFI::NCurses::A_NORMAL)
|
143
|
+
|
144
|
+
#$log.debug "printstring recvd nil row #{r} or col #{c}, color:#{color},att:#{att}." if $log
|
145
|
+
raise "printstring recvd nil row #{r} or col #{c}, color:#{color},att:#{att} " if r.nil? || c.nil?
|
146
|
+
att ||= FFI::NCurses::A_NORMAL
|
147
|
+
color ||= 0
|
148
|
+
raise "color is nil " unless color
|
149
|
+
raise "att is nil " unless att
|
150
|
+
|
151
|
+
FFI::NCurses.wattron(@pointer, FFI::NCurses.COLOR_PAIR(color) | att)
|
152
|
+
FFI::NCurses.mvwprintw(@pointer, r, c, "%s", :string, string);
|
153
|
+
FFI::NCurses.wattroff(@pointer, FFI::NCurses.COLOR_PAIR(color) | att)
|
154
|
+
end
|
155
|
+
##
|
156
|
+
# Get a key from the standard input.
|
157
|
+
#
|
158
|
+
# This will get control keys and function keys but not Alt keys.
|
159
|
+
# This is usually called in a loop by the main program.
|
160
|
+
# It returns the ascii code (integer).
|
161
|
+
# 1 is Ctrl-a .... 27 is Esc
|
162
|
+
# FFI already has constants declared for function keys and control keys for checkin against.
|
163
|
+
# Can return a 3 or -1 if user pressed Control-C.
|
164
|
+
#
|
165
|
+
# NOTE: For ALT keys we need to check for 27/Esc and if so, then do another read
|
166
|
+
# with a timeout. If we get a key, then resolve. Otherwise, it is just ESC
|
167
|
+
# NOTE: this was working fine with nodelay. However, that would not allow an app to continuously
|
168
|
+
# update the screen, as the getch was blocked. wtimeout allows screen to update without a key
|
169
|
+
# being pressed. 2018-04-07
|
170
|
+
# @return [Integer] ascii code of key. For undefined keys, returns a String representation.
|
171
|
+
def getch
|
172
|
+
FFI::NCurses.wtimeout(@pointer, $ncurses_timeout || 1000)
|
173
|
+
c = FFI::NCurses.wgetch(@pointer)
|
174
|
+
if c == 27
|
175
|
+
|
176
|
+
# don't wait for another key
|
177
|
+
FFI::NCurses.nodelay(@pointer, true)
|
178
|
+
k = FFI::NCurses.wgetch(@pointer)
|
179
|
+
if k == -1
|
180
|
+
# wait for key
|
181
|
+
#FFI::NCurses.nodelay(@pointer, false)
|
182
|
+
return 27
|
183
|
+
else
|
184
|
+
buf = ""
|
185
|
+
loop do
|
186
|
+
n = FFI::NCurses.wgetch(@pointer)
|
187
|
+
break if n == -1
|
188
|
+
buf += n.chr
|
189
|
+
end
|
190
|
+
# wait for next key
|
191
|
+
#FFI::NCurses.nodelay(@pointer, false)
|
192
|
+
|
193
|
+
# this works for all alt-keys but it messes with shift-function keys
|
194
|
+
# shift-function keys start with M-[ (91) and then have more keys
|
195
|
+
if buf == ""
|
196
|
+
return k + 128
|
197
|
+
end
|
198
|
+
#$log.debug " getch buf is #{k.chr}#{buf} "
|
199
|
+
# returning a string key here which is for Shift-Function keys or other undefined keys.
|
200
|
+
key = 27.chr + k.chr + buf
|
201
|
+
return key
|
202
|
+
|
203
|
+
end
|
204
|
+
end
|
205
|
+
#FFI::NCurses.nodelay(@pointer, false) # this works but trying out for continueous updates
|
206
|
+
c
|
207
|
+
rescue SystemExit, Interrupt
|
208
|
+
3 # is C-c
|
209
|
+
rescue StandardError
|
210
|
+
-1 # is C-c
|
211
|
+
end
|
212
|
+
|
213
|
+
# this works fine for basic, control and function keys
|
214
|
+
def OLDgetch
|
215
|
+
c = FFI::NCurses.wgetch(@pointer)
|
216
|
+
rescue SystemExit, Interrupt
|
217
|
+
3 # is C-c
|
218
|
+
rescue StandardError
|
219
|
+
-1 # is C-c
|
220
|
+
end
|
221
|
+
alias :getkey :getch
|
222
|
+
|
223
|
+
# refresh the window (wrapper)
|
224
|
+
# To be called after printing on a window.
|
225
|
+
def wrefresh
|
226
|
+
FFI::NCurses.wrefresh(@pointer)
|
227
|
+
end
|
228
|
+
# destroy the window and the panel.
|
229
|
+
# This is important. It should be placed in the ensure block of caller application, so it happens.
|
230
|
+
def destroy
|
231
|
+
FFI::NCurses.del_panel(@panel) if @panel
|
232
|
+
FFI::NCurses.delwin(@pointer) if @pointer
|
233
|
+
@panel = @pointer = nil # prevent call twice
|
234
|
+
end
|
235
|
+
# route other methods to ffi. {{{
|
236
|
+
# This should preferable NOT be used. Better to use the direct call itself.
|
237
|
+
# It attempts to route other calls to FFI::NCurses by trying to add w to the name and passing the pointer.
|
238
|
+
# I would like to remove this at some time.
|
239
|
+
def method_missing(name, *args)
|
240
|
+
name = name.to_s
|
241
|
+
if (name[0,2] == "mv")
|
242
|
+
test_name = name.dup
|
243
|
+
test_name[2,0] = "w" # insert "w" after"mv"
|
244
|
+
if (FFI::NCurses.respond_to?(test_name))
|
245
|
+
return FFI::NCurses.send(test_name, @pointer, *args)
|
246
|
+
end
|
247
|
+
end
|
248
|
+
test_name = "w" + name
|
249
|
+
if (FFI::NCurses.respond_to?(test_name))
|
250
|
+
return FFI::NCurses.send(test_name, @pointer, *args)
|
251
|
+
end
|
252
|
+
FFI::NCurses.send(name, @pointer, *args)
|
253
|
+
end # }}}
|
254
|
+
# make a box around the window. Just a wrapper
|
255
|
+
def box
|
256
|
+
FFI::NCurses.box(@pointer, 0, 0)
|
257
|
+
end
|
258
|
+
# print a centered title on top of window
|
259
|
+
# This should be called after box, or else box will erase the title
|
260
|
+
# @param str [String] title to print
|
261
|
+
# @param color [Integer] color_pair
|
262
|
+
# @param att [Integer] attribute constant
|
263
|
+
def title str, color=0, att=BOLD
|
264
|
+
strl = str.length
|
265
|
+
col = (@width - strl)/2
|
266
|
+
printstring(0,col, str, color, att)
|
267
|
+
end
|
268
|
+
|
269
|
+
end # window
|
270
|
+
end # module
|
data/lib/umbra.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
require "umbra/version"
|
2
|
+
#require 'ffi-ncurses'
|
3
|
+
require "umbra/window"
|
4
|
+
require "umbra/form"
|
5
|
+
|
6
|
+
module Umbra
|
7
|
+
def alert str, config={}
|
8
|
+
require 'umbra/dialog'
|
9
|
+
#title = config[:title] || "Alert"
|
10
|
+
#cp = config[:color_pair] || create_color_pair( COLOR_BLUE, COLOR_WHITE )
|
11
|
+
#attr = config[:attrib] || NORMAL
|
12
|
+
|
13
|
+
config[:text] ||= str
|
14
|
+
config[:title] ||= "Alert"
|
15
|
+
config[:window_color_pair] ||= create_color_pair( COLOR_BLUE, COLOR_WHITE )
|
16
|
+
config[:window_attr] ||= NORMAL
|
17
|
+
config[:buttons] ||= ["Ok"]
|
18
|
+
|
19
|
+
#m = Dialog.new text: str, title: title, window_color_pair: cp, window_attr: attr
|
20
|
+
m = Dialog.new config
|
21
|
+
m.run
|
22
|
+
end
|
23
|
+
# confirmation dialog which prompts message with Ok and Cancel and returns true or false.
|
24
|
+
def confirm str, config={}
|
25
|
+
require 'umbra/dialog'
|
26
|
+
|
27
|
+
config[:text] ||= str
|
28
|
+
config[:title] ||= "Confirm"
|
29
|
+
config[:window_color_pair] ||= create_color_pair( COLOR_BLUE, COLOR_WHITE )
|
30
|
+
config[:window_attr] ||= NORMAL
|
31
|
+
config[:buttons] ||= ["Ok", "Cancel"]
|
32
|
+
|
33
|
+
m = Dialog.new config
|
34
|
+
ret = m.run
|
35
|
+
return ret == 0
|
36
|
+
end
|
37
|
+
|
38
|
+
# view an array in a popup window
|
39
|
+
def view array, config={}
|
40
|
+
require 'umbra/pad'
|
41
|
+
title = config[:title] || "Viewer"
|
42
|
+
cp = config[:color_pair] || create_color_pair( COLOR_BLUE, COLOR_WHITE )
|
43
|
+
attr = config[:attrib] || NORMAL
|
44
|
+
m = Pad.new list: array, height: FFI::NCurses.LINES-2, width: FFI::NCurses.COLS-10, title: title, color_pair: cp, attrib: attr
|
45
|
+
m.run
|
46
|
+
end
|
47
|
+
end
|
data/umbra.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require "umbra/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "ncumbra"
|
8
|
+
spec.version = Umbra::VERSION
|
9
|
+
spec.authors = ["kepler"]
|
10
|
+
spec.email = ["githubkepler.50s@gishpuppy.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{tiny ncurses library for creating simple apps}
|
13
|
+
spec.description = %q{minimal, provides forms and a few basic widgets}
|
14
|
+
spec.homepage = "https://github.com/mare-imbrium/umbra"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
18
|
+
f.match(%r{^(test|spec|features)/})
|
19
|
+
end
|
20
|
+
spec.bindir = "exe"
|
21
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
22
|
+
spec.require_paths = ["lib"]
|
23
|
+
|
24
|
+
spec.add_development_dependency "bundler", "~> 1.16"
|
25
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
26
|
+
spec.add_runtime_dependency "ffi-ncurses", ">= 0.4.0", ">= 0.4.0"
|
27
|
+
end
|
metadata
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ncumbra
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- kepler
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-04-22 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.16'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.16'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: ffi-ncurses
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 0.4.0
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.4.0
|
55
|
+
description: minimal, provides forms and a few basic widgets
|
56
|
+
email:
|
57
|
+
- githubkepler.50s@gishpuppy.com
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- ".gitignore"
|
63
|
+
- Gemfile
|
64
|
+
- LICENSE
|
65
|
+
- README.md
|
66
|
+
- README.md.bak
|
67
|
+
- Rakefile
|
68
|
+
- bin/console
|
69
|
+
- bin/setup
|
70
|
+
- examples/ex1.rb
|
71
|
+
- examples/ex2.rb
|
72
|
+
- examples/ex21.rb
|
73
|
+
- examples/ex3.rb
|
74
|
+
- examples/ex4.rb
|
75
|
+
- examples/ex5.rb
|
76
|
+
- examples/exbox.rb
|
77
|
+
- examples/exm1.rb
|
78
|
+
- examples/keys.rb
|
79
|
+
- examples/tt.rb
|
80
|
+
- lib/umbra.rb
|
81
|
+
- lib/umbra/box.rb
|
82
|
+
- lib/umbra/button.rb
|
83
|
+
- lib/umbra/buttongroup.rb
|
84
|
+
- lib/umbra/checkbox.rb
|
85
|
+
- lib/umbra/dialog.rb
|
86
|
+
- lib/umbra/eventhandler.rb
|
87
|
+
- lib/umbra/field.rb
|
88
|
+
- lib/umbra/form.rb
|
89
|
+
- lib/umbra/keymappinghandler.rb
|
90
|
+
- lib/umbra/label.rb
|
91
|
+
- lib/umbra/labeledfield.rb
|
92
|
+
- lib/umbra/listbox.rb
|
93
|
+
- lib/umbra/menu.rb
|
94
|
+
- lib/umbra/messagebox.rb
|
95
|
+
- lib/umbra/pad.rb
|
96
|
+
- lib/umbra/radiobutton.rb
|
97
|
+
- lib/umbra/textbox.rb
|
98
|
+
- lib/umbra/togglebutton.rb
|
99
|
+
- lib/umbra/version.rb
|
100
|
+
- lib/umbra/widget.rb
|
101
|
+
- lib/umbra/window.rb
|
102
|
+
- umbra.gemspec
|
103
|
+
homepage: https://github.com/mare-imbrium/umbra
|
104
|
+
licenses:
|
105
|
+
- MIT
|
106
|
+
metadata: {}
|
107
|
+
post_install_message:
|
108
|
+
rdoc_options: []
|
109
|
+
require_paths:
|
110
|
+
- lib
|
111
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
112
|
+
requirements:
|
113
|
+
- - ">="
|
114
|
+
- !ruby/object:Gem::Version
|
115
|
+
version: '0'
|
116
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
117
|
+
requirements:
|
118
|
+
- - ">="
|
119
|
+
- !ruby/object:Gem::Version
|
120
|
+
version: '0'
|
121
|
+
requirements: []
|
122
|
+
rubyforge_project:
|
123
|
+
rubygems_version: 2.7.6
|
124
|
+
signing_key:
|
125
|
+
specification_version: 4
|
126
|
+
summary: tiny ncurses library for creating simple apps
|
127
|
+
test_files: []
|