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/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: []
|