rkumar-rbcurse 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.
- data/CHANGELOG +1577 -0
- data/README.txt +310 -0
- data/examples/qdfilechooser.rb +68 -0
- data/examples/rfe.rb +853 -0
- data/examples/rfe_renderer.rb +69 -0
- data/examples/test1.rb +242 -0
- data/examples/test2.rb +498 -0
- data/examples/testcombo.rb +95 -0
- data/examples/testkeypress.rb +61 -0
- data/examples/testmenu.rb +105 -0
- data/examples/testtable.rb +266 -0
- data/examples/testtabp.rb +106 -0
- data/examples/testtodo.rb +532 -0
- data/examples/viewtodo.rb +512 -0
- data/lib/rbcurse.rb +7 -0
- data/lib/rbcurse/action.rb +31 -0
- data/lib/rbcurse/applicationheader.rb +57 -0
- data/lib/rbcurse/celleditor.rb +120 -0
- data/lib/rbcurse/checkboxcellrenderer.rb +69 -0
- data/lib/rbcurse/colormap.rb +133 -0
- data/lib/rbcurse/comboboxcellrenderer.rb +45 -0
- data/lib/rbcurse/defaultlistselectionmodel.rb +49 -0
- data/lib/rbcurse/keylabelprinter.rb +143 -0
- data/lib/rbcurse/listcellrenderer.rb +99 -0
- data/lib/rbcurse/listkeys.rb +33 -0
- data/lib/rbcurse/listscrollable.rb +216 -0
- data/lib/rbcurse/listselectable.rb +67 -0
- data/lib/rbcurse/mapper.rb +108 -0
- data/lib/rbcurse/orderedhash.rb +77 -0
- data/lib/rbcurse/rcombo.rb +243 -0
- data/lib/rbcurse/rdialogs.rb +183 -0
- data/lib/rbcurse/rform.rb +845 -0
- data/lib/rbcurse/rinputdataevent.rb +36 -0
- data/lib/rbcurse/rlistbox.rb +804 -0
- data/lib/rbcurse/rmenu.rb +666 -0
- data/lib/rbcurse/rmessagebox.rb +325 -0
- data/lib/rbcurse/rpopupmenu.rb +754 -0
- data/lib/rbcurse/rtabbedpane.rb +259 -0
- data/lib/rbcurse/rtable.rb +1296 -0
- data/lib/rbcurse/rtextarea.rb +673 -0
- data/lib/rbcurse/rtextview.rb +335 -0
- data/lib/rbcurse/rwidget.rb +1731 -0
- data/lib/rbcurse/scrollable.rb +301 -0
- data/lib/rbcurse/selectable.rb +94 -0
- data/lib/rbcurse/table/tablecellrenderer.rb +85 -0
- data/lib/rbcurse/table/tabledatecellrenderer.rb +102 -0
- data/lib/ver/keyboard.rb +150 -0
- data/lib/ver/keyboard2.rb +170 -0
- data/lib/ver/ncurses.rb +102 -0
- data/lib/ver/window.rb +369 -0
- data/test/test_rbcurse.rb +0 -0
- metadata +117 -0
@@ -0,0 +1,335 @@
|
|
1
|
+
=begin
|
2
|
+
* Name: TextView
|
3
|
+
* $Id$
|
4
|
+
* Description View text in this widget.
|
5
|
+
* Author: rkumar (arunachalesha)
|
6
|
+
TODO
|
7
|
+
* file created 2009-01-08 15:23
|
8
|
+
--------
|
9
|
+
* License:
|
10
|
+
Same as Ruby's License (http://www.ruby-lang.org/LICENSE.txt)
|
11
|
+
|
12
|
+
=end
|
13
|
+
require 'rubygems'
|
14
|
+
require 'ncurses'
|
15
|
+
require 'logger'
|
16
|
+
require 'rbcurse'
|
17
|
+
require 'rbcurse/listscrollable'
|
18
|
+
|
19
|
+
include Ncurses
|
20
|
+
include RubyCurses
|
21
|
+
module RubyCurses
|
22
|
+
extend self
|
23
|
+
|
24
|
+
##
|
25
|
+
# A viewable read only box. Can scroll.
|
26
|
+
# Intention is to be able to change content dynamically - the entire list.
|
27
|
+
# Use set_content to set content, or just update the list attrib
|
28
|
+
# TODO -
|
29
|
+
# - searching, goto line - DONE
|
30
|
+
class TextView < Widget
|
31
|
+
include ListScrollable
|
32
|
+
dsl_accessor :height # height of viewport
|
33
|
+
dsl_accessor :title # set this on top
|
34
|
+
dsl_accessor :title_attrib # bold, reverse, normal
|
35
|
+
dsl_accessor :footer_attrib # bold, reverse, normal
|
36
|
+
dsl_accessor :list # the array of data to be sent by user
|
37
|
+
dsl_accessor :maxlen # max len to be displayed
|
38
|
+
attr_reader :toprow # the toprow in the view (offsets are 0)
|
39
|
+
# attr_reader :prow # the row on which cursor/focus is
|
40
|
+
attr_reader :winrow # the row in the viewport/window
|
41
|
+
dsl_accessor :print_footer
|
42
|
+
|
43
|
+
def initialize form, config={}, &block
|
44
|
+
@focusable = true
|
45
|
+
@editable = false
|
46
|
+
@left_margin = 1
|
47
|
+
@row = 0
|
48
|
+
@col = 0
|
49
|
+
@show_focus = false # don't highlight row under focus
|
50
|
+
@list = []
|
51
|
+
super
|
52
|
+
@row_offset = @col_offset = 1
|
53
|
+
@orig_col = @col
|
54
|
+
# this does result in a blank line if we insert after creating. That's required at
|
55
|
+
# present if we wish to only insert
|
56
|
+
@scrollatrow = @height-2
|
57
|
+
@content_rows = @list.length
|
58
|
+
@win = @form.window
|
59
|
+
#init_scrollable
|
60
|
+
print_borders
|
61
|
+
@maxlen ||= @width-2
|
62
|
+
init_vars
|
63
|
+
end
|
64
|
+
def init_vars
|
65
|
+
@curpos = @pcol = @toprow = @current_index = 0
|
66
|
+
end
|
67
|
+
##
|
68
|
+
# send in a list
|
69
|
+
# e.g. set_content File.open("README.txt","r").readlines
|
70
|
+
# set wrap at time of passing :WRAP_NONE :WRAP_WORD
|
71
|
+
def set_content list, wrap = :WRAP_NONE
|
72
|
+
@wrap_policy = wrap
|
73
|
+
if list.is_a? String
|
74
|
+
if @wrap_policy == :WRAP_WORD
|
75
|
+
data = wrap_text list
|
76
|
+
@list = data.split("\n")
|
77
|
+
else
|
78
|
+
@list = list.split("\n")
|
79
|
+
end
|
80
|
+
elsif list.is_a? Array
|
81
|
+
if @wrap_policy == :WRAP_WORD
|
82
|
+
data = wrap_text list.join(" ")
|
83
|
+
@list = data.split("\n")
|
84
|
+
else
|
85
|
+
@list = list
|
86
|
+
end
|
87
|
+
else
|
88
|
+
raise "set_content expects Array not #{list.class}"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
## display this row on top
|
92
|
+
def top_row(*val)
|
93
|
+
if val.empty?
|
94
|
+
@toprow
|
95
|
+
else
|
96
|
+
@toprow = val[0] || 0
|
97
|
+
#@prow = val[0] || 0
|
98
|
+
end
|
99
|
+
@repaint_required = true
|
100
|
+
end
|
101
|
+
## ---- for listscrollable ---- ##
|
102
|
+
def scrollatrow
|
103
|
+
@height - 2
|
104
|
+
end
|
105
|
+
def row_count
|
106
|
+
@list.length
|
107
|
+
end
|
108
|
+
##
|
109
|
+
# returns row of first match of given regex (or nil if not found)
|
110
|
+
def find_first_match regex
|
111
|
+
@list.each_with_index do |row, ix|
|
112
|
+
return ix if !row.match(regex).nil?
|
113
|
+
end
|
114
|
+
return nil
|
115
|
+
end
|
116
|
+
def rowcol
|
117
|
+
#$log.debug "textarea rowcol : #{@row+@row_offset+@winrow}, #{@col+@col_offset}"
|
118
|
+
#return @row+@row_offset+@winrow, @col+@col_offset
|
119
|
+
#return @row+@row_offset+@winrow, @col+@col_offset
|
120
|
+
return @row+@row_offset, @col+@col_offset
|
121
|
+
end
|
122
|
+
def wrap_text(txt, col = @maxlen)
|
123
|
+
$log.debug "inside wrap text for :#{txt}"
|
124
|
+
txt.gsub(/(.{1,#{col}})( +|$\n?)|(.{1,#{col}})/,
|
125
|
+
"\\1\\3\n")
|
126
|
+
end
|
127
|
+
def print_borders
|
128
|
+
window = @form.window
|
129
|
+
color = $datacolor
|
130
|
+
window.print_border @row, @col, @height, @width, color
|
131
|
+
print_title
|
132
|
+
=begin
|
133
|
+
hline = "+%s+" % [ "-"*(width-((1)*2)) ]
|
134
|
+
hline2 = "|%s|" % [ " "*(width-((1)*2)) ]
|
135
|
+
window.printstring(row=startrow, col=startcol, hline, color)
|
136
|
+
print_title
|
137
|
+
(startrow+1).upto(startrow+height-1) do |row|
|
138
|
+
window.printstring( row, col=startcol, hline2, color)
|
139
|
+
end
|
140
|
+
window.printstring( startrow+height, col=startcol, hline, color)
|
141
|
+
=end
|
142
|
+
|
143
|
+
end
|
144
|
+
def print_title
|
145
|
+
@form.window.printstring( @row, @col+(@width-@title.length)/2, @title, $datacolor, @title_attrib) unless @title.nil?
|
146
|
+
end
|
147
|
+
def print_foot
|
148
|
+
@footer_attrib ||= Ncurses::A_REVERSE
|
149
|
+
footer = "R: #{@current_index+1}, C: #{@curpos+@pcol}, #{@list.length} lines "
|
150
|
+
@form.window.printstring( @row + @height, @col+2, footer, $datacolor, @footer_attrib)
|
151
|
+
end
|
152
|
+
### FOR scrollable ###
|
153
|
+
def get_content
|
154
|
+
@list
|
155
|
+
end
|
156
|
+
def get_window
|
157
|
+
@form.window
|
158
|
+
end
|
159
|
+
### FOR scrollable ###
|
160
|
+
def repaint # textview
|
161
|
+
return unless @repaint_required
|
162
|
+
paint
|
163
|
+
print_foot if @print_footer
|
164
|
+
end
|
165
|
+
def getvalue
|
166
|
+
@list
|
167
|
+
end
|
168
|
+
# textview
|
169
|
+
# [ ] scroll left right DONE
|
170
|
+
def handle_key ch
|
171
|
+
@buffer = @list[@current_index]
|
172
|
+
if @buffer.nil? and row_count == 0
|
173
|
+
@list << "\r"
|
174
|
+
@buffer = @list[@current_index]
|
175
|
+
end
|
176
|
+
return if @buffer.nil?
|
177
|
+
$log.debug " before: curpos #{@curpos} blen: #{@buffer.length}"
|
178
|
+
if @curpos > @buffer.length
|
179
|
+
addcol((@buffer.length-@curpos)+1)
|
180
|
+
@curpos = @buffer.length
|
181
|
+
set_form_col
|
182
|
+
end
|
183
|
+
$log.debug "TV after loop : curpos #{@curpos} blen: #{@buffer.length}"
|
184
|
+
#pre_key
|
185
|
+
case ch
|
186
|
+
when ?\C-n
|
187
|
+
scroll_forward
|
188
|
+
when ?\C-p
|
189
|
+
scroll_backward
|
190
|
+
when ?0, ?\C-[
|
191
|
+
goto_start #start of buffer # cursor_start
|
192
|
+
when ?\C-]
|
193
|
+
goto_end # end / bottom cursor_end
|
194
|
+
when KEY_UP
|
195
|
+
#select_prev_row
|
196
|
+
ret = up
|
197
|
+
check_curpos
|
198
|
+
#addrowcol -1,0 if ret != -1 or @winrow != @oldwinrow # positions the cursor up
|
199
|
+
#@form.row = @row + 1 + @winrow
|
200
|
+
when KEY_DOWN
|
201
|
+
ret = down
|
202
|
+
check_curpos
|
203
|
+
#@form.row = @row + 1 + @winrow
|
204
|
+
when KEY_LEFT
|
205
|
+
cursor_backward
|
206
|
+
when KEY_RIGHT
|
207
|
+
cursor_forward
|
208
|
+
when KEY_BACKSPACE, 127
|
209
|
+
cursor_backward
|
210
|
+
when 330
|
211
|
+
cursor_backward
|
212
|
+
when ?\C-a
|
213
|
+
# take care of data that exceeds maxlen by scrolling and placing cursor at start
|
214
|
+
set_form_col 0
|
215
|
+
@pcol = 0
|
216
|
+
when ?\C-e
|
217
|
+
# take care of data that exceeds maxlen by scrolling and placing cursor at end
|
218
|
+
blen = @buffer.rstrip.length
|
219
|
+
if blen < @maxlen
|
220
|
+
set_form_col blen
|
221
|
+
else
|
222
|
+
@pcol = blen-@maxlen
|
223
|
+
#wrong curpos wiill be reported
|
224
|
+
set_form_col @maxlen-1
|
225
|
+
end
|
226
|
+
else
|
227
|
+
$log.debug("TEXTVIEW XXX ch #{ch}")
|
228
|
+
return :UNHANDLED
|
229
|
+
end
|
230
|
+
#post_key
|
231
|
+
# XXX 2008-11-27 13:57 trying out
|
232
|
+
set_form_row
|
233
|
+
end
|
234
|
+
# newly added to check curpos when moving up or down
|
235
|
+
def check_curpos
|
236
|
+
@buffer = @list[@current_index]
|
237
|
+
# if the cursor is ahead of data in this row then move it back
|
238
|
+
if @pcol+@curpos > @buffer.length
|
239
|
+
addcol((@pcol+@buffer.length-@curpos)+1)
|
240
|
+
@curpos = @buffer.length
|
241
|
+
|
242
|
+
# even this row is gt maxlen, i.e., scrolled right
|
243
|
+
if @curpos > @maxlen
|
244
|
+
@pcol = @curpos - @maxlen
|
245
|
+
@curpos = @maxlen-1
|
246
|
+
else
|
247
|
+
# this row is within maxlen, make scroll 0
|
248
|
+
@pcol=0
|
249
|
+
end
|
250
|
+
set_form_col
|
251
|
+
end
|
252
|
+
end
|
253
|
+
# set cursor on correct column tview
|
254
|
+
def set_form_col col=@curpos
|
255
|
+
@curpos = col
|
256
|
+
@curpos = @maxlen if @curpos > @maxlen
|
257
|
+
@form.col = @orig_col + @col_offset + @curpos
|
258
|
+
@repaint_required = true
|
259
|
+
end
|
260
|
+
def cursor_forward
|
261
|
+
if @curpos < @width and @curpos < @maxlen-1 # else it will do out of box
|
262
|
+
@curpos += 1
|
263
|
+
addcol 1
|
264
|
+
else
|
265
|
+
# XXX 2008-11-26 23:03 trying out
|
266
|
+
@pcol += 1 if @pcol <= @buffer.length
|
267
|
+
end
|
268
|
+
set_form_col
|
269
|
+
@repaint_required = true
|
270
|
+
end
|
271
|
+
def addcol num
|
272
|
+
@repaint_required = true
|
273
|
+
@form.addcol num
|
274
|
+
end
|
275
|
+
def addrowcol row,col
|
276
|
+
@repaint_required = true
|
277
|
+
@form.addrowcol row, col
|
278
|
+
end
|
279
|
+
def cursor_backward
|
280
|
+
if @curpos > 0
|
281
|
+
@curpos -= 1
|
282
|
+
set_form_col
|
283
|
+
#addcol -1
|
284
|
+
elsif @pcol > 0 # XXX added 2008-11-26 23:05
|
285
|
+
@pcol -= 1
|
286
|
+
end
|
287
|
+
@repaint_required = true
|
288
|
+
end
|
289
|
+
# gives offset of next line, does not move
|
290
|
+
def next_line
|
291
|
+
@list[@current_index+1]
|
292
|
+
end
|
293
|
+
def do_relative_row num
|
294
|
+
yield @list[@current_index+num]
|
295
|
+
end
|
296
|
+
def paint
|
297
|
+
print_borders if @to_print_borders == 1 # do this once only, unless everything changes
|
298
|
+
rc = row_count
|
299
|
+
maxlen = @maxlen ||= @width-2
|
300
|
+
tm = get_content
|
301
|
+
tr = @toprow
|
302
|
+
acolor = get_color $datacolor
|
303
|
+
h = scrollatrow()
|
304
|
+
r,c = rowcol
|
305
|
+
0.upto(h) do |hh|
|
306
|
+
crow = tr+hh
|
307
|
+
if crow < rc
|
308
|
+
#focussed = @current_index == crow ? true : false
|
309
|
+
#selected = is_row_selected crow
|
310
|
+
content = tm[crow].chomp
|
311
|
+
content.gsub!(/\t/, ' ') # don't display tab
|
312
|
+
content.gsub!(/[^[:print:]]/, '') # don't display non print characters
|
313
|
+
if !content.nil?
|
314
|
+
if content.length > maxlen # only show maxlen
|
315
|
+
content = content[@pcol..@pcol+maxlen-1]
|
316
|
+
else
|
317
|
+
content = content[@pcol..-1]
|
318
|
+
end
|
319
|
+
end
|
320
|
+
#renderer = get_default_cell_renderer_for_class content.class.to_s
|
321
|
+
#renderer = cell_renderer()
|
322
|
+
#renderer.repaint @form.window, r+hh, c+(colix*11), content, focussed, selected
|
323
|
+
#renderer.repaint @form.window, r+hh, c, content, focussed, selected
|
324
|
+
@form.window.printstring r+hh, c, "%-*s" % [@width-2,content], acolor, @attr
|
325
|
+
|
326
|
+
else
|
327
|
+
# clear rows
|
328
|
+
@form.window.printstring r+hh, c, " " * (@width-2), acolor,@attr
|
329
|
+
end
|
330
|
+
end
|
331
|
+
@table_changed = false
|
332
|
+
@repaint_required = false
|
333
|
+
end
|
334
|
+
end # class textview
|
335
|
+
end # modul
|
@@ -0,0 +1,1731 @@
|
|
1
|
+
=begin
|
2
|
+
* Name: rwidget: base class and then popup and other derived widgets
|
3
|
+
* $Id$
|
4
|
+
* Description
|
5
|
+
Some simple light widgets for creating ncurses applications. No reliance on ncurses
|
6
|
+
forms and fields.
|
7
|
+
I expect to pass through this world but once. Any good therefore that I can do,
|
8
|
+
or any kindness or ablities that I can show to any fellow creature, let me do it now.
|
9
|
+
Let me not defer it or neglect it, for I shall not pass this way again.
|
10
|
+
* Author: rkumar (arunachalesha)
|
11
|
+
* Date: 2008-11-19 12:49
|
12
|
+
* License: Same as Ruby's License (http://www.ruby-lang.org/LICENSE.txt)
|
13
|
+
TODO
|
14
|
+
- repaint only what is modified
|
15
|
+
- save data in a hash when called for.
|
16
|
+
- make some methods private/protected
|
17
|
+
- Add bottom bar also, perhaps allow it to be displayed on a key so it does not take
|
18
|
+
- Can key bindings be abstracted so they can be inherited /reused.
|
19
|
+
- some kind of CSS style sheet.
|
20
|
+
|
21
|
+
|
22
|
+
=end
|
23
|
+
require 'rubygems'
|
24
|
+
require 'ncurses'
|
25
|
+
require 'logger'
|
26
|
+
require 'rbcurse/mapper'
|
27
|
+
require 'rbcurse/colormap'
|
28
|
+
#require 'rbcurse/rdialogs'
|
29
|
+
#require 'rbcurse/listcellrenderer'
|
30
|
+
|
31
|
+
module DSL
|
32
|
+
## others may not want this, if = sent, it creates DSL and sets
|
33
|
+
# using this resulted in time lost in bedebugging why some method was not working.
|
34
|
+
def OLD_method_missing(sym, *args)
|
35
|
+
$log.debug "METHOD MISSING : #{sym} #{self} "
|
36
|
+
#if "#{sym}"[-1].chr=="="
|
37
|
+
# sym = "#{sym}"[0..-2]
|
38
|
+
#else
|
39
|
+
self.class.dsl_accessor sym
|
40
|
+
#end
|
41
|
+
send(sym, *args)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
class Module
|
45
|
+
## others may not want this, sets config, so there's a duplicate hash
|
46
|
+
# also creates a attr_writer so you can use =.
|
47
|
+
def dsl_accessor(*symbols)
|
48
|
+
symbols.each { |sym|
|
49
|
+
class_eval %{
|
50
|
+
def #{sym}(*val)
|
51
|
+
if val.empty?
|
52
|
+
@#{sym}
|
53
|
+
else
|
54
|
+
@#{sym} = val.size == 1 ? val[0] : val
|
55
|
+
@config["#{sym}"]=@#{sym}
|
56
|
+
end
|
57
|
+
end
|
58
|
+
attr_writer sym
|
59
|
+
}
|
60
|
+
}
|
61
|
+
end
|
62
|
+
def dsl_property(*symbols)
|
63
|
+
symbols.each { |sym|
|
64
|
+
class_eval %{
|
65
|
+
def #{sym}(*val)
|
66
|
+
if val.empty?
|
67
|
+
@#{sym}
|
68
|
+
else
|
69
|
+
oldvalue = @#{sym}
|
70
|
+
@#{sym} = val.size == 1 ? val[0] : val
|
71
|
+
newvalue = @#{sym}
|
72
|
+
@config["#{sym}"]=@#{sym}
|
73
|
+
if oldvalue != newvalue
|
74
|
+
fire_property_change("#{sym}", oldvalue, newvalue)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
#attr_writer sym
|
79
|
+
def #{sym}=val
|
80
|
+
#{sym}(val)
|
81
|
+
end
|
82
|
+
}
|
83
|
+
}
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
include Ncurses
|
89
|
+
module RubyCurses
|
90
|
+
extend self
|
91
|
+
include ColorMap
|
92
|
+
class FieldValidationException < RuntimeError
|
93
|
+
end
|
94
|
+
module Utils
|
95
|
+
##
|
96
|
+
# wraps text given max length, puts newlines in it.
|
97
|
+
# it does not take into account existing newlines
|
98
|
+
# Some classes have @maxlen or display_length which may be passed as the second parameter
|
99
|
+
def wrap_text(txt, max )
|
100
|
+
txt.gsub(/(.{1,#{max}})( +|$\n?)|(.{1,#{max}})/,
|
101
|
+
"\\1\\3\n")
|
102
|
+
end
|
103
|
+
def clean_string! content
|
104
|
+
content.chomp! # don't display newline
|
105
|
+
content.gsub!(/[\t\n]/, ' ') # don't display tab
|
106
|
+
content.gsub!(/[^[:print:]]/, '') # don't display non print characters
|
107
|
+
content
|
108
|
+
end
|
109
|
+
# needs to move to a keystroke class
|
110
|
+
def keycode_tos keycode
|
111
|
+
case keycode
|
112
|
+
when 33..126
|
113
|
+
return keycode.chr
|
114
|
+
when ?\C-a .. ?\C-z
|
115
|
+
return "C-" + (keycode + ?a -1).chr
|
116
|
+
when ?\M-A..?\M-z
|
117
|
+
return "M-"+ (keycode - 128).chr
|
118
|
+
when ?\M-\C-A..?\M-\C-Z
|
119
|
+
return "M-C-"+ (keycode - 32).chr
|
120
|
+
when ?\M-0..?\M-9
|
121
|
+
return "M-"+ (keycode-?\M-0).to_s
|
122
|
+
when 32:
|
123
|
+
return "Space"
|
124
|
+
when 27:
|
125
|
+
return "Esc"
|
126
|
+
when ?\C-]
|
127
|
+
return "C-]"
|
128
|
+
when 258
|
129
|
+
return "down"
|
130
|
+
when 259
|
131
|
+
return "up"
|
132
|
+
when 260
|
133
|
+
return "left"
|
134
|
+
when 261
|
135
|
+
return "right"
|
136
|
+
when KEY_F1..KEY_F12
|
137
|
+
return "F"+ (keycode-264).to_s
|
138
|
+
when 330
|
139
|
+
return "delete"
|
140
|
+
when 127
|
141
|
+
return "bs"
|
142
|
+
when 353
|
143
|
+
return "btab"
|
144
|
+
when 481
|
145
|
+
return "M-S-tab"
|
146
|
+
else
|
147
|
+
others=[?\M--,?\M-+,?\M-=,?\M-',?\M-",?\M-;,?\M-:,?\M-\,, ?\M-.,?\M-<,?\M->]
|
148
|
+
s_others=%w[M-- M-+ M-= M-' M-" M-; M-: M-\, M-. M-<]
|
149
|
+
if others.include? keycode
|
150
|
+
index = others.index keycode
|
151
|
+
return s_others[index]
|
152
|
+
end
|
153
|
+
# all else failed
|
154
|
+
return keycode.to_s
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def get_color default=$datacolor, color=@color, bgcolor=@bgcolor
|
159
|
+
if bgcolor.is_a? String and color.is_a? String
|
160
|
+
acolor = ColorMap.get_color(color, bgcolor)
|
161
|
+
else
|
162
|
+
acolor = default
|
163
|
+
end
|
164
|
+
return acolor
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
module EventHandler
|
169
|
+
##
|
170
|
+
# bind an event to a block, optional args will also be passed when calling
|
171
|
+
def bind event, *xargs, &blk
|
172
|
+
#$log.debug "#{self} called EventHandler BIND #{event}, args:#{xargs} "
|
173
|
+
@handler ||= {}
|
174
|
+
@event_args ||= {}
|
175
|
+
#@handler[event] = blk
|
176
|
+
#@event_args[event] = xargs
|
177
|
+
@handler[event] ||= []
|
178
|
+
@handler[event] << blk
|
179
|
+
@event_args[event] ||= []
|
180
|
+
@event_args[event] << xargs
|
181
|
+
end
|
182
|
+
alias :add_binding :bind # temporary, needs a proper name to point out that we are adding
|
183
|
+
|
184
|
+
# NOTE: Do we have a way of removing bindings
|
185
|
+
# # TODO check if event is valid. Classes need to define what valid event names are
|
186
|
+
|
187
|
+
##
|
188
|
+
# Fire all bindings for given event
|
189
|
+
# e.g. fire_handler :ENTER, self
|
190
|
+
# currently object usually contains self which is perhaps a bit of a waste,
|
191
|
+
# could contain an event object with source, and some relevant methods or values
|
192
|
+
def fire_handler event, object
|
193
|
+
#$log.debug " def fire_handler evt:#{event}, o: #{object}, #{self}, hdnler:#{@handler}"
|
194
|
+
if !@handler.nil?
|
195
|
+
#blk = @handler[event]
|
196
|
+
ablk = @handler[event]
|
197
|
+
if !ablk.nil?
|
198
|
+
aeve = @event_args[event]
|
199
|
+
ablk.each_with_index do |blk, ix|
|
200
|
+
#$log.debug "#{self} called EventHandler firehander #{@name}, #{event}, obj: #{object},args: #{aeve[ix]}"
|
201
|
+
blk.call object, *aeve[ix]
|
202
|
+
end
|
203
|
+
end # if
|
204
|
+
end # if
|
205
|
+
end
|
206
|
+
## added on 2009-01-08 00:33
|
207
|
+
# goes with dsl_property
|
208
|
+
# Need to inform listeners
|
209
|
+
def fire_property_change text, oldvalue, newvalue
|
210
|
+
#$log.debug " FPC #{self}: #{text} #{oldvalue}, #{newvalue}"
|
211
|
+
@repaint_required = true
|
212
|
+
end
|
213
|
+
|
214
|
+
end # module eventh
|
215
|
+
|
216
|
+
module ConfigSetup
|
217
|
+
# private
|
218
|
+
def variable_set var, val
|
219
|
+
nvar = "@#{var}"
|
220
|
+
send("#{var}", val) # 2009-01-08 01:30 BIG CHANGE calling methods too here.
|
221
|
+
#instance_variable_set(nvar, val) # we should not call this !!! bypassing
|
222
|
+
end
|
223
|
+
def configure(*val , &block)
|
224
|
+
case val.size
|
225
|
+
when 1
|
226
|
+
return @config[val[0]]
|
227
|
+
when 2
|
228
|
+
@config[val[0]] = val[1]
|
229
|
+
variable_set(val[0], val[1])
|
230
|
+
end
|
231
|
+
instance_eval &block if block_given?
|
232
|
+
end
|
233
|
+
##
|
234
|
+
# returns param from hash. Unused and untested.
|
235
|
+
def cget param
|
236
|
+
@config[param]
|
237
|
+
end
|
238
|
+
# this bypasses our methods and sets directly !
|
239
|
+
def config_setup aconfig
|
240
|
+
@config = aconfig
|
241
|
+
@config.each_pair { |k,v| variable_set(k,v) }
|
242
|
+
end
|
243
|
+
end # module config
|
244
|
+
##
|
245
|
+
# Basic widget class.
|
246
|
+
# NOTE: I may soon remove the config hash. I don't use it and its just making things heavy.
|
247
|
+
# Unless someone convinces me otherwise.
|
248
|
+
class Widget
|
249
|
+
include DSL
|
250
|
+
include EventHandler
|
251
|
+
include ConfigSetup
|
252
|
+
include RubyCurses::Utils
|
253
|
+
dsl_property :text
|
254
|
+
#dsl_accessor :text_variable
|
255
|
+
#dsl_accessor :underline # offset of text to underline DEPRECATED
|
256
|
+
dsl_property :width # desired width of text
|
257
|
+
#dsl_accessor :wrap_length # wrap length of text, if applic UNUSED
|
258
|
+
|
259
|
+
# next 3 to be checked if used or not. Copied from TK.
|
260
|
+
dsl_property :select_foreground, :select_background # color init_pair
|
261
|
+
dsl_property :highlight_foreground, :highlight_background # color init_pair
|
262
|
+
dsl_property :disabled_foreground, :disabled_background # color init_pair
|
263
|
+
|
264
|
+
# FIXME is enabled used?
|
265
|
+
dsl_accessor :focusable, :enabled # boolean
|
266
|
+
dsl_property :row, :col # location of object
|
267
|
+
dsl_property :color, :bgcolor # normal foreground and background
|
268
|
+
dsl_property :attr # attribute bold, normal, reverse
|
269
|
+
dsl_accessor :name # name to refr to or recall object by_name
|
270
|
+
attr_accessor :id #, :zorder
|
271
|
+
attr_accessor :curpos # cursor position inside object
|
272
|
+
attr_reader :config # COULD GET AXED SOON NOTE
|
273
|
+
attr_accessor :form # made accessor 2008-11-27 22:32 so menu can set
|
274
|
+
attr_accessor :state # normal, selected, highlighted
|
275
|
+
attr_reader :row_offset, :col_offset # where should the cursor be placed to start with
|
276
|
+
dsl_property :visible # boolean # 2008-12-09 11:29
|
277
|
+
#attr_accessor :modified # boolean, value modified or not (moved from field 2009-01-18 00:14 )
|
278
|
+
dsl_accessor :help_text # added 2009-01-22 17:41 can be used for status/tooltips
|
279
|
+
|
280
|
+
def initialize form, aconfig={}, &block
|
281
|
+
@form = form
|
282
|
+
@bgcolor ||= "black" # 0
|
283
|
+
@row_offset = @col_offset = 0
|
284
|
+
@state = :NORMAL
|
285
|
+
@color ||= "white" # $datacolor
|
286
|
+
@attr = nil
|
287
|
+
@handler = {}
|
288
|
+
@event_args = {}
|
289
|
+
config_setup aconfig # @config.each_pair { |k,v| variable_set(k,v) }
|
290
|
+
instance_eval &block if block_given?
|
291
|
+
# @id = form.add_widget(self) if !form.nil? and form.respond_to? :add_widget
|
292
|
+
set_form(form) unless form.nil?
|
293
|
+
end
|
294
|
+
def init_vars
|
295
|
+
# just in case anyone does a super. Not putting anything here
|
296
|
+
# since i don't want anyone accidentally overriding
|
297
|
+
end
|
298
|
+
|
299
|
+
# modified
|
300
|
+
##
|
301
|
+
# typically read will be overridden to check if value changed from what it was on enter.
|
302
|
+
# getter and setter for modified (added 2009-01-18 12:31 )
|
303
|
+
def modified?
|
304
|
+
@modified
|
305
|
+
end
|
306
|
+
def set_modified tf=true
|
307
|
+
@modified = tf
|
308
|
+
@form.modified = true if tf
|
309
|
+
end
|
310
|
+
alias :modified :set_modified
|
311
|
+
##
|
312
|
+
# getter and setter for text_variable
|
313
|
+
def text_variable(*val)
|
314
|
+
if val.empty?
|
315
|
+
@text_variable
|
316
|
+
else
|
317
|
+
@text_variable = val[0]
|
318
|
+
$log.debug " GOING TO CALL ADD DELPENDENT #{self}"
|
319
|
+
@text_variable.add_dependent(self)
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
323
|
+
## got left out by mistake 2008-11-26 20:20
|
324
|
+
def on_enter
|
325
|
+
fire_handler :ENTER, self
|
326
|
+
end
|
327
|
+
## got left out by mistake 2008-11-26 20:20
|
328
|
+
def on_leave
|
329
|
+
fire_handler :LEAVE, self
|
330
|
+
end
|
331
|
+
def rowcol
|
332
|
+
# $log.debug "widgte rowcol : #{@row+@row_offset}, #{@col+@col_offset}"
|
333
|
+
return @row+@row_offset, @col+@col_offset
|
334
|
+
end
|
335
|
+
## return the value of the widget.
|
336
|
+
# In cases where selection is possible, should return selected value/s
|
337
|
+
def getvalue
|
338
|
+
@text_variable && @text_variable.value || @text
|
339
|
+
end
|
340
|
+
##
|
341
|
+
# Am making a separate method since often value for print differs from actual value
|
342
|
+
def getvalue_for_paint
|
343
|
+
getvalue
|
344
|
+
end
|
345
|
+
##
|
346
|
+
# default repaint method. Called by form for all widgets.
|
347
|
+
def repaint
|
348
|
+
r,c = rowcol
|
349
|
+
$log.debug("widget repaint : r:#{r} c:#{c} col:#{@color}" )
|
350
|
+
value = getvalue_for_paint
|
351
|
+
len = @display_length || value.length
|
352
|
+
if @bgcolor.is_a? String and @color.is_a? String
|
353
|
+
acolor = ColorMap.get_color(@color, @bgcolor)
|
354
|
+
else
|
355
|
+
acolor = $datacolor
|
356
|
+
end
|
357
|
+
@form.window.printstring r, c, "%-*s" % [len, value], acolor, @attr
|
358
|
+
# next line should be in same color but only have @att so we can change att is nec
|
359
|
+
#@form.window.mvchgat(y=r, x=c, max=len, Ncurses::A_NORMAL, @bgcolor, nil)
|
360
|
+
end
|
361
|
+
|
362
|
+
def destroy
|
363
|
+
$log.debug "DESTROY : widget"
|
364
|
+
panel = @window.panel
|
365
|
+
Ncurses::Panel.del_panel(panel) if !panel.nil?
|
366
|
+
@window.delwin if !@window.nil?
|
367
|
+
end
|
368
|
+
# @ deprecated pls call windows method
|
369
|
+
def printstring(win, r,c,string, color, att = Ncurses::A_NORMAL)
|
370
|
+
|
371
|
+
att = Ncurses::A_NORMAL if att.nil?
|
372
|
+
case att.to_s.downcase
|
373
|
+
when 'underline'
|
374
|
+
att = Ncurses::A_UNDERLINE
|
375
|
+
$log.debug "UL att #{att}"
|
376
|
+
when 'bold'
|
377
|
+
att = Ncurses::A_BOLD
|
378
|
+
when 'blink'
|
379
|
+
att = Ncurses::A_BLINK
|
380
|
+
when 'reverse'
|
381
|
+
att = Ncurses::A_REVERSE
|
382
|
+
else
|
383
|
+
att = Ncurses::A_NORMAL
|
384
|
+
end
|
385
|
+
#$log.debug "att #{att}"
|
386
|
+
|
387
|
+
#att = bold ? Ncurses::A_BLINK|Ncurses::A_BOLD : Ncurses::A_NORMAL
|
388
|
+
# att = bold ? Ncurses::A_BOLD : Ncurses::A_NORMAL
|
389
|
+
win.attron(Ncurses.COLOR_PAIR(color) | att)
|
390
|
+
win.mvprintw(r, c, "%s", string);
|
391
|
+
win.attroff(Ncurses.COLOR_PAIR(color) | att)
|
392
|
+
end
|
393
|
+
# in those rare cases where we create widget without a form, and later give it to
|
394
|
+
# some other program which sets the form. Dirty, we should perhaps create widgets
|
395
|
+
# without forms, and add explicitly.
|
396
|
+
def set_form form
|
397
|
+
raise "Form is nil in set_form" if form.nil?
|
398
|
+
@form = form
|
399
|
+
@id = form.add_widget(self) if !form.nil? and form.respond_to? :add_widget
|
400
|
+
end
|
401
|
+
# puts cursor on correct row.
|
402
|
+
def set_form_row
|
403
|
+
raise "empty todo widget"
|
404
|
+
# @form.row = @row + 1 + @winrow
|
405
|
+
@form.row = @row + 1
|
406
|
+
end
|
407
|
+
# set cursor on correct column, widget
|
408
|
+
def set_form_col col=@curpos
|
409
|
+
@curpos = col
|
410
|
+
@form.col = @col + @col_offset + @curpos
|
411
|
+
end
|
412
|
+
def hide
|
413
|
+
@visible = false
|
414
|
+
end
|
415
|
+
def show
|
416
|
+
@visible = true
|
417
|
+
end
|
418
|
+
def remove
|
419
|
+
@form.remove_widget(self)
|
420
|
+
end
|
421
|
+
def move row, col
|
422
|
+
@row = row
|
423
|
+
@col = col
|
424
|
+
end
|
425
|
+
##
|
426
|
+
# moves focus to this field
|
427
|
+
# XXX we must look into running on_leave of previous field
|
428
|
+
def focus
|
429
|
+
return if !@focusable
|
430
|
+
if @form.validate_field != -1
|
431
|
+
@form.select_field @id
|
432
|
+
end
|
433
|
+
end
|
434
|
+
def get_color default=$datacolor, _color=@color, _bgcolor=@bgcolor
|
435
|
+
if _bgcolor.is_a? String and _color.is_a? String
|
436
|
+
acolor = ColorMap.get_color(_color, _bgcolor)
|
437
|
+
else
|
438
|
+
acolor = default
|
439
|
+
end
|
440
|
+
return acolor
|
441
|
+
end
|
442
|
+
##
|
443
|
+
# bind an action to a key, required if you create a button which has a hotkey
|
444
|
+
# or a field to be focussed on a key, or any other user defined action based on key
|
445
|
+
# e.g. bind_key ?\C-x, object, block
|
446
|
+
# added 2009-01-06 19:13 since widgets need to handle keys properly
|
447
|
+
def bind_key keycode, *args, &blk
|
448
|
+
$log.debug "called bind_key BIND #{keycode} #{keycode_tos(keycode)} #{args} "
|
449
|
+
@key_handler ||= {}
|
450
|
+
@key_args ||= {}
|
451
|
+
@key_handler[keycode] = blk
|
452
|
+
@key_args[keycode] = args
|
453
|
+
end
|
454
|
+
##
|
455
|
+
# remove a binding that you don't want
|
456
|
+
def unbind_key keycode
|
457
|
+
@key_args.delete keycode unless @key_args.nil?
|
458
|
+
@key_handler.delete keycode unless @key_handler.nil?
|
459
|
+
end
|
460
|
+
|
461
|
+
# e.g. process_key ch, self
|
462
|
+
# returns UNHANDLED if no block for it
|
463
|
+
# after form handles basic keys, it gives unhandled key to current field, if current field returns
|
464
|
+
# unhandled, then it checks this map.
|
465
|
+
# added 2009-01-06 19:13 since widgets need to handle keys properly
|
466
|
+
# added 2009-01-18 12:58 returns ret val of blk.call
|
467
|
+
# so that if block does not handle, the key can still be handled
|
468
|
+
# e.g. table last row, last col does not handle, so it will auto go to next field
|
469
|
+
def process_key keycode, object
|
470
|
+
return :UNHANDLED if @key_handler.nil?
|
471
|
+
blk = @key_handler[keycode]
|
472
|
+
return :UNHANDLED if blk.nil?
|
473
|
+
#$log.debug "called process_key #{object}, #{@key_args[keycode]}"
|
474
|
+
return blk.call object, *@key_args[keycode]
|
475
|
+
#0
|
476
|
+
end
|
477
|
+
##
|
478
|
+
# to be added at end of handle_key of widgets so instlalled actions can be checked
|
479
|
+
def handle_key(ch)
|
480
|
+
ret = process_key ch, self
|
481
|
+
return :UNHANDLED if ret == :UNHANDLED
|
482
|
+
end
|
483
|
+
## ADD HERE WIDGET
|
484
|
+
end
|
485
|
+
|
486
|
+
##
|
487
|
+
#
|
488
|
+
# TODO: we don't have an event for when form is entered and exited.
|
489
|
+
# Current ENTER and LEAVE are for when any widgt is entered, so a common event can be put for all widgets
|
490
|
+
# in one place.
|
491
|
+
class Form
|
492
|
+
include EventHandler
|
493
|
+
include RubyCurses::Utils
|
494
|
+
attr_reader :value
|
495
|
+
attr_reader :widgets
|
496
|
+
attr_accessor :window
|
497
|
+
attr_accessor :row, :col
|
498
|
+
# attr_accessor :color
|
499
|
+
# attr_accessor :bgcolor
|
500
|
+
attr_accessor :padx
|
501
|
+
attr_accessor :pady
|
502
|
+
attr_accessor :modified
|
503
|
+
attr_accessor :active_index
|
504
|
+
attr_reader :by_name # hash containing widgets by name for retrieval
|
505
|
+
attr_reader :menu_bar
|
506
|
+
attr_accessor :navigation_policy # :CYCLICAL will cycle around. Needed to move to other tabs
|
507
|
+
def initialize win, &block
|
508
|
+
@window = win
|
509
|
+
@widgets = []
|
510
|
+
@by_name = {}
|
511
|
+
@active_index = -1
|
512
|
+
@padx = @pady = 0
|
513
|
+
@row = @col = -1
|
514
|
+
@handler = {}
|
515
|
+
@modified = false
|
516
|
+
@focusable = true
|
517
|
+
@navigation_policy ||= :CYCLICAL
|
518
|
+
instance_eval &block if block_given?
|
519
|
+
@firsttime = true # internal, don't touch
|
520
|
+
end
|
521
|
+
##
|
522
|
+
# set this menubar as the form's menu bar.
|
523
|
+
# also bind the toggle_key for popping up.
|
524
|
+
# Should this not be at application level ?
|
525
|
+
def set_menu_bar mb
|
526
|
+
@menu_bar = mb
|
527
|
+
add_widget mb
|
528
|
+
mb.toggle_key ||= 27 # ESC
|
529
|
+
if !mb.toggle_key.nil?
|
530
|
+
ch = mb.toggle_key
|
531
|
+
bind_key(ch) do |_form|
|
532
|
+
if !@menu_bar.nil?
|
533
|
+
@menu_bar.toggle
|
534
|
+
@menu_bar.handle_keys
|
535
|
+
end
|
536
|
+
end
|
537
|
+
end
|
538
|
+
end
|
539
|
+
##
|
540
|
+
# Add given widget to widget list and returns an incremental id.
|
541
|
+
# Adding to widgets, results in it being painted, and focussed.
|
542
|
+
# removing a widget and adding can give the same ID's, however at this point we are not
|
543
|
+
# really using ID. But need to use an incremental int in future.
|
544
|
+
def add_widget widget
|
545
|
+
# this help to access widget by a name
|
546
|
+
if widget.respond_to? :name and !widget.name.nil?
|
547
|
+
@by_name[widget.name] = widget
|
548
|
+
end
|
549
|
+
|
550
|
+
@widgets << widget
|
551
|
+
return @widgets.length-1
|
552
|
+
end
|
553
|
+
# remove a widget
|
554
|
+
# added 2008-12-09 12:18
|
555
|
+
def remove_widget widget
|
556
|
+
if widget.respond_to? :name and !widget.name.nil?
|
557
|
+
$log.debug "removing from byname: #{widget.name} "
|
558
|
+
@by_name.delete(widget.name)
|
559
|
+
end
|
560
|
+
@widgets.delete widget
|
561
|
+
end
|
562
|
+
# form repaint
|
563
|
+
# to be called at some interval, such as after each keypress.
|
564
|
+
def repaint
|
565
|
+
@widgets.each do |f|
|
566
|
+
next if f.visible == false # added 2008-12-09 12:17
|
567
|
+
f.repaint
|
568
|
+
end
|
569
|
+
@window.clear_error
|
570
|
+
@window.print_status_message $status_message unless $status_message.nil?
|
571
|
+
@window.print_error_message $error_message unless $error_message.nil?
|
572
|
+
$error_message = $status_message = nil
|
573
|
+
# this can bomb if someone sets row. We need a better way!
|
574
|
+
if @row == -1 #or @firsttime == true
|
575
|
+
#set_field_cursor 0
|
576
|
+
$log.debug "form repaint calling select field 0"
|
577
|
+
#select_field 0
|
578
|
+
req_first_field
|
579
|
+
#@firsttime = false
|
580
|
+
end
|
581
|
+
setpos
|
582
|
+
@window.wrefresh
|
583
|
+
end
|
584
|
+
##
|
585
|
+
# move cursor to where the fields row and col are
|
586
|
+
# private
|
587
|
+
def setpos r=@row, c=@col
|
588
|
+
# $log.debug "setpos : #{r} #{c}"
|
589
|
+
@window.wmove r,c
|
590
|
+
end
|
591
|
+
def get_current_field
|
592
|
+
select_next_field if @active_index == -1
|
593
|
+
return nil if @active_index.nil? # for forms that have no focusable field 2009-01-08 12:22
|
594
|
+
@widgets[@active_index]
|
595
|
+
end
|
596
|
+
def req_first_field
|
597
|
+
@active_index = -1 # FIXME HACK
|
598
|
+
select_next_field
|
599
|
+
end
|
600
|
+
def req_last_field
|
601
|
+
@active_index = nil
|
602
|
+
select_prev_field
|
603
|
+
end
|
604
|
+
## do not override
|
605
|
+
# form's trigger, fired when any widget loses focus
|
606
|
+
# This wont get called in editor components in tables, since they are formless XXX
|
607
|
+
def on_leave f
|
608
|
+
return if f.nil?
|
609
|
+
f.state = :NORMAL
|
610
|
+
# on leaving update text_variable if defined. Should happen on modified only
|
611
|
+
# should this not be f.text_var ... f.buffer ? XXX 2008-11-25 18:58
|
612
|
+
#f.text_variable.value = f.buffer if !f.text_variable.nil? # 2008-12-20 23:36
|
613
|
+
f.on_leave if f.respond_to? :on_leave
|
614
|
+
fire_handler :LEAVE, f
|
615
|
+
## to test XXX in combo boxes the box may not be editable by be modified by selection.
|
616
|
+
if f.respond_to? :editable and f.modified?
|
617
|
+
$log.debug " Form about to fire CHANGED for #{f} "
|
618
|
+
f.fire_handler(:CHANGED, f)
|
619
|
+
end
|
620
|
+
end
|
621
|
+
def on_enter f
|
622
|
+
return if f.nil?
|
623
|
+
f.state = :HIGHLIGHTED
|
624
|
+
f.modified false
|
625
|
+
#f.set_modified false
|
626
|
+
f.on_enter if f.respond_to? :on_enter
|
627
|
+
fire_handler :ENTER, f
|
628
|
+
end
|
629
|
+
##
|
630
|
+
# puts focus on the given field/widget index
|
631
|
+
# XXX if called externally will not run a on_leave of previous field
|
632
|
+
def select_field ix0
|
633
|
+
return if @widgets.nil? or @widgets.empty? or !@widgets[ix0].focusable
|
634
|
+
# $log.debug "insdie select field : #{ix0} ai #{@active_index}"
|
635
|
+
f = @widgets[ix0]
|
636
|
+
if f.focusable
|
637
|
+
@active_index = ix0
|
638
|
+
@row, @col = f.rowcol
|
639
|
+
# $log.debug "insdie sele nxt field : ROW #{@row} COL #{@col} "
|
640
|
+
@window.wmove @row, @col
|
641
|
+
on_enter f
|
642
|
+
f.curpos = 0
|
643
|
+
repaint
|
644
|
+
@window.refresh
|
645
|
+
else
|
646
|
+
$log.debug "insdie sele nxt field ENABLED FALSE : act #{@active_index} ix0 #{ix0}"
|
647
|
+
end
|
648
|
+
end
|
649
|
+
##
|
650
|
+
# run validate_field on a field, usually whatevers current
|
651
|
+
# before transferring control
|
652
|
+
# We should try to automate this so developer does not have to remember to call it.
|
653
|
+
def validate_field f=@widgets[@active_index]
|
654
|
+
begin
|
655
|
+
on_leave f
|
656
|
+
rescue => err
|
657
|
+
$log.debug "form: validate_field caught EXCEPTION #{err}"
|
658
|
+
$log.debug(err.backtrace.join("\n"))
|
659
|
+
$error_message = "#{err}"
|
660
|
+
Ncurses.beep
|
661
|
+
return -1
|
662
|
+
end
|
663
|
+
return 0
|
664
|
+
end
|
665
|
+
# put focus on next field
|
666
|
+
# will cycle by default, unless navigation policy not :CYCLICAL
|
667
|
+
# in which case returns :NO_NEXT_FIELD.
|
668
|
+
def select_next_field
|
669
|
+
return if @widgets.nil? or @widgets.empty?
|
670
|
+
#$log.debug "insdie sele nxt field : #{@active_index} WL:#{@widgets.length}"
|
671
|
+
if @active_index.nil?
|
672
|
+
@active_index = -1
|
673
|
+
else
|
674
|
+
f = @widgets[@active_index]
|
675
|
+
begin
|
676
|
+
on_leave f
|
677
|
+
rescue => err
|
678
|
+
$log.debug "select_next_field: caught EXCEPTION #{err}"
|
679
|
+
$log.debug(err.backtrace.join("\n"))
|
680
|
+
$error_message = "#{err}"
|
681
|
+
Ncurses.beep
|
682
|
+
return
|
683
|
+
end
|
684
|
+
end
|
685
|
+
index = @active_index + 1
|
686
|
+
index.upto(@widgets.length-1) do |i|
|
687
|
+
f = @widgets[i]
|
688
|
+
if f.focusable
|
689
|
+
select_field i
|
690
|
+
return
|
691
|
+
end
|
692
|
+
end
|
693
|
+
#req_first_field
|
694
|
+
#$log.debug "insdie sele nxt field FAILED: #{@active_index} WL:#{@widgets.length}"
|
695
|
+
## added on 2008-12-14 18:27 so we can skip to another form/tab
|
696
|
+
if @navigation_policy == :CYCLICAL
|
697
|
+
@active_index = nil
|
698
|
+
# recursive call worked, but bombed if no focusable field!
|
699
|
+
#select_next_field
|
700
|
+
0.upto(index-1) do |i|
|
701
|
+
f = @widgets[i]
|
702
|
+
if f.focusable
|
703
|
+
select_field i
|
704
|
+
return
|
705
|
+
end
|
706
|
+
end
|
707
|
+
end
|
708
|
+
return :NO_NEXT_FIELD
|
709
|
+
end
|
710
|
+
##
|
711
|
+
# put focus on previous field
|
712
|
+
# will cycle by default, unless navigation policy not :CYCLICAL
|
713
|
+
# in which case returns :NO_PREV_FIELD.
|
714
|
+
def select_prev_field
|
715
|
+
return if @widgets.nil? or @widgets.empty?
|
716
|
+
#$log.debug "insdie sele prev field : #{@active_index} WL:#{@widgets.length}"
|
717
|
+
if @active_index.nil?
|
718
|
+
@active_index = @widgets.length
|
719
|
+
else
|
720
|
+
f = @widgets[@active_index]
|
721
|
+
begin
|
722
|
+
on_leave f
|
723
|
+
rescue => err
|
724
|
+
$log.debug " cauGHT EXCEPTION #{err}"
|
725
|
+
Ncurses.beep
|
726
|
+
return
|
727
|
+
end
|
728
|
+
end
|
729
|
+
|
730
|
+
index = @active_index - 1
|
731
|
+
(index).downto(0) do |i|
|
732
|
+
f = @widgets[i]
|
733
|
+
if f.focusable
|
734
|
+
select_field i
|
735
|
+
return
|
736
|
+
end
|
737
|
+
end
|
738
|
+
# $log.debug "insdie sele prev field FAILED: #{@active_index} WL:#{@widgets.length}"
|
739
|
+
## added on 2008-12-14 18:27 so we can skip to another form/tab
|
740
|
+
# 2009-01-08 12:24 no recursion, can be stack overflows if no focusable field
|
741
|
+
if @navigation_policy == :CYCLICAL
|
742
|
+
@active_index = nil # HACK !!!
|
743
|
+
#select_prev_field
|
744
|
+
total = @widgets.length-1
|
745
|
+
total.downto(index-1) do |i|
|
746
|
+
f = @widgets[i]
|
747
|
+
if f.focusable
|
748
|
+
select_field i
|
749
|
+
return
|
750
|
+
end
|
751
|
+
end
|
752
|
+
end
|
753
|
+
return :NO_PREV_FIELD
|
754
|
+
end
|
755
|
+
alias :req_next_field :select_next_field
|
756
|
+
alias :req_prev_field :select_prev_field
|
757
|
+
##
|
758
|
+
# move cursor by num columns
|
759
|
+
def addcol num
|
760
|
+
return if @col.nil? or @col == -1
|
761
|
+
@col += num
|
762
|
+
@window.wmove @row, @col
|
763
|
+
end
|
764
|
+
##
|
765
|
+
# move cursor by given rows and columns, can be negative.
|
766
|
+
def addrowcol row,col
|
767
|
+
return if @col.nil? or @col == -1
|
768
|
+
return if @row.nil? or @row == -1
|
769
|
+
@col += col
|
770
|
+
@row += row
|
771
|
+
@window.wmove @row, @col
|
772
|
+
end
|
773
|
+
##
|
774
|
+
# bind an action to a key, required if you create a button which has a hotkey
|
775
|
+
# or a field to be focussed on a key, or any other user defined action based on key
|
776
|
+
# e.g. bind_key ?\C-x, object, block
|
777
|
+
def bind_key keycode, *args, &blk
|
778
|
+
$log.debug "called bind_key BIND #{keycode} #{keycode_tos(keycode)} #{args} "
|
779
|
+
@key_handler ||= {}
|
780
|
+
@key_args ||= {}
|
781
|
+
@key_handler[keycode] = blk
|
782
|
+
@key_args[keycode] = args
|
783
|
+
end
|
784
|
+
|
785
|
+
# e.g. process_key ch, self
|
786
|
+
# returns UNHANDLED if no block for it
|
787
|
+
# after form handles basic keys, it gives unhandled key to current field, if current field returns
|
788
|
+
# unhandled, then it checks this map.
|
789
|
+
def process_key keycode, object
|
790
|
+
return :UNHANDLED if @key_handler.nil?
|
791
|
+
blk = @key_handler[keycode]
|
792
|
+
return :UNHANDLED if blk.nil?
|
793
|
+
$log.debug "called process_key #{object}, #{@key_args[keycode]}"
|
794
|
+
blk.call object, *@key_args[keycode]
|
795
|
+
0
|
796
|
+
end
|
797
|
+
## forms handle keys
|
798
|
+
# mainly traps tab and backtab to navigate between widgets.
|
799
|
+
# I know some widgets will want to use tab, e.g edit boxes for entering a tab
|
800
|
+
# or for completion.
|
801
|
+
def handle_key(ch)
|
802
|
+
case ch
|
803
|
+
when -1
|
804
|
+
return
|
805
|
+
else
|
806
|
+
field = get_current_field
|
807
|
+
handled = :UNHANDLED
|
808
|
+
handled = field.handle_key ch unless field.nil? # no field focussable
|
809
|
+
# some widgets like textarea and list handle up and down
|
810
|
+
if handled == :UNHANDLED or handled == -1 or field.nil?
|
811
|
+
case ch
|
812
|
+
when 9, ?\M-\C-i # tab and M-tab in case widget eats tab (such as Table)
|
813
|
+
ret = select_next_field
|
814
|
+
return ret if ret == :NO_NEXT_FIELD
|
815
|
+
# alt-shift-tab or backtab (in case Table eats backtab)
|
816
|
+
when 353, 481 ## backtab added 2008-12-14 18:41
|
817
|
+
ret = select_prev_field
|
818
|
+
return ret if ret == :NO_PREV_FIELD
|
819
|
+
when KEY_UP
|
820
|
+
select_prev_field
|
821
|
+
when KEY_DOWN
|
822
|
+
select_next_field
|
823
|
+
else
|
824
|
+
ret = process_key ch, self
|
825
|
+
return :UNHANDLED if ret == :UNHANDLED
|
826
|
+
end
|
827
|
+
end
|
828
|
+
end
|
829
|
+
$log.debug " form before repaint"
|
830
|
+
repaint
|
831
|
+
end
|
832
|
+
##
|
833
|
+
# test program to dump data onto log
|
834
|
+
# The problem I face is that since widget array contains everything that should be displayed
|
835
|
+
# I do not know what all the user wants - what are his data entry fields.
|
836
|
+
# A user could have disabled entry on some field after modification, so i can't use focusable
|
837
|
+
# or editable as filters. I just dump everything?
|
838
|
+
# What's more, currently getvalue has been used by paint to return what needs to be displayed -
|
839
|
+
# at least by label and button.
|
840
|
+
def dump_data
|
841
|
+
$log.debug " DUMPING DATA "
|
842
|
+
@widgets.each do |w|
|
843
|
+
# we need checkbox and radio button values
|
844
|
+
#next if w.is_a? RubyCurses::Button or w.is_a? RubyCurses::Label
|
845
|
+
next if w.is_a? RubyCurses::Label
|
846
|
+
next if !w.is_a? RubyCurses::Widget
|
847
|
+
if w.respond_to? :getvalue
|
848
|
+
$log.debug " #{w.name} #{w.getvalue}"
|
849
|
+
else
|
850
|
+
$log.debug " #{w.name} DOES NOT RESPOND TO getvalue"
|
851
|
+
end
|
852
|
+
end
|
853
|
+
$log.debug " END DUMPING DATA "
|
854
|
+
end
|
855
|
+
|
856
|
+
## ADD HERE FORM
|
857
|
+
end
|
858
|
+
|
859
|
+
##
|
860
|
+
# Text edit field
|
861
|
+
# To get value use getvalue()
|
862
|
+
# TODO - test text_variable
|
863
|
+
#
|
864
|
+
class Field < Widget
|
865
|
+
dsl_accessor :maxlen # maximum length allowed into field
|
866
|
+
attr_reader :buffer # actual buffer being used for storage
|
867
|
+
dsl_accessor :label # label of field
|
868
|
+
dsl_accessor :default # TODO use set_buffer for now
|
869
|
+
dsl_accessor :values # validate against provided list
|
870
|
+
dsl_accessor :valid_regex # validate against regular expression
|
871
|
+
|
872
|
+
dsl_accessor :chars_allowed # regex, what characters to allow, will ignore all else
|
873
|
+
dsl_accessor :display_length # how much to display
|
874
|
+
dsl_accessor :bgcolor # background color 'red' 'black' 'cyan' etc
|
875
|
+
dsl_accessor :color # foreground colors from Ncurses COLOR_xxxx
|
876
|
+
dsl_accessor :show # what charactr to show for each char entered (password field)
|
877
|
+
dsl_accessor :null_allowed # allow nulls, don't validate if null # added 2008-12-22 12:38
|
878
|
+
|
879
|
+
# any new widget that has editable should have modified also
|
880
|
+
dsl_accessor :editable # allow editing
|
881
|
+
|
882
|
+
attr_reader :form
|
883
|
+
attr_reader :handler # event handler
|
884
|
+
attr_reader :type # datatype of field, currently only sets chars_allowed
|
885
|
+
attr_reader :curpos # cursor position in buffer current
|
886
|
+
attr_accessor :datatype # crrently set during set_buffer
|
887
|
+
attr_reader :original_value # value on entering field
|
888
|
+
|
889
|
+
def initialize form, config={}, &block
|
890
|
+
@form = form
|
891
|
+
@buffer = String.new
|
892
|
+
#@type=config.fetch("type", :varchar)
|
893
|
+
@display_length = config.fetch("display_length", 20)
|
894
|
+
@maxlen=config.fetch("maxlen", @display_length)
|
895
|
+
@row = config.fetch("row", 0)
|
896
|
+
@col = config.fetch("col", 0)
|
897
|
+
@bgcolor = config.fetch("bgcolor", $def_bg_color)
|
898
|
+
@color = config.fetch("color", $def_fg_color)
|
899
|
+
@name = config.fetch("name", nil)
|
900
|
+
@editable = config.fetch("editable", true)
|
901
|
+
@focusable = config.fetch("focusable", true)
|
902
|
+
@handler = {}
|
903
|
+
@event_args = {} # arguments passed at time of binding, to use when firing event
|
904
|
+
init_vars
|
905
|
+
super
|
906
|
+
end
|
907
|
+
def init_vars
|
908
|
+
@pcol = 0 # needed for horiz scrolling
|
909
|
+
@curpos = 0 # current cursor position in buffer
|
910
|
+
@modified = false
|
911
|
+
end
|
912
|
+
def text_variable tv
|
913
|
+
@text_variable = tv
|
914
|
+
set_buffer tv.value
|
915
|
+
end
|
916
|
+
##
|
917
|
+
# define a datatype, currently only influences chars allowed
|
918
|
+
# integer and float. what about allowing a minus sign? XXX
|
919
|
+
def type dtype
|
920
|
+
case dtype.to_s.downcase
|
921
|
+
when 'integer'
|
922
|
+
@chars_allowed = /\d/ if @chars_allowed.nil?
|
923
|
+
when 'numeric'
|
924
|
+
@chars_allowed = /[\d\.]/ if @chars_allowed.nil?
|
925
|
+
when 'alpha'
|
926
|
+
@chars_allowed = /[a-zA-Z]/ if @chars_allowed.nil?
|
927
|
+
when 'alnum'
|
928
|
+
@chars_allowed = /[a-zA-Z0-9]/ if @chars_allowed.nil?
|
929
|
+
end
|
930
|
+
end
|
931
|
+
def putch char
|
932
|
+
return -1 if !@editable or @buffer.length >= @maxlen
|
933
|
+
if @chars_allowed != nil
|
934
|
+
return if char.match(@chars_allowed).nil?
|
935
|
+
end
|
936
|
+
@buffer.insert(@curpos, char)
|
937
|
+
@curpos += 1 if @curpos < @maxlen
|
938
|
+
@modified = true
|
939
|
+
$log.debug " FIELD FIRING CHANGE: #{char} at new #{@curpos}: bl:#{@buffer.length} buff:[#{@buffer}]"
|
940
|
+
fire_handler :CHANGE, self # 2008-12-09 14:51
|
941
|
+
0
|
942
|
+
end
|
943
|
+
|
944
|
+
##
|
945
|
+
# TODO : sending c>=0 allows control chars to go. Should be >= ?A i think.
|
946
|
+
def putc c
|
947
|
+
if c >= 0 and c <= 127
|
948
|
+
ret = putch c.chr
|
949
|
+
if ret == 0
|
950
|
+
if addcol(1) == -1 # if can't go forward, try scrolling
|
951
|
+
# scroll if exceeding display len but less than max len
|
952
|
+
if @curpos > @display_length and @curpos <= @maxlen
|
953
|
+
@pcol += 1 if @pcol < @display_length
|
954
|
+
end
|
955
|
+
end
|
956
|
+
set_modified
|
957
|
+
end
|
958
|
+
end
|
959
|
+
return -1
|
960
|
+
end
|
961
|
+
def delete_at index=@curpos
|
962
|
+
return -1 if !@editable
|
963
|
+
@buffer.slice!(index,1)
|
964
|
+
$log.debug " delete at #{index}: #{@buffer.length}: #{@buffer}"
|
965
|
+
@modified = true
|
966
|
+
fire_handler :CHANGE, self # 2008-12-09 14:51
|
967
|
+
end
|
968
|
+
##
|
969
|
+
# should this do a dup ??
|
970
|
+
def set_buffer value
|
971
|
+
@datatype = value.class
|
972
|
+
#$log.debug " FIELD DATA #{@datatype}"
|
973
|
+
@buffer = value.to_s
|
974
|
+
@curpos = 0
|
975
|
+
end
|
976
|
+
# converts back into original type
|
977
|
+
# changed to convert on 2009-01-06 23:39
|
978
|
+
def getvalue
|
979
|
+
dt = @datatype || String
|
980
|
+
case dt.to_s
|
981
|
+
when "String"
|
982
|
+
return @buffer
|
983
|
+
when "Fixnum"
|
984
|
+
return @buffer.to_i
|
985
|
+
when "Float"
|
986
|
+
return @buffer.to_f
|
987
|
+
else
|
988
|
+
return @buffer.to_s
|
989
|
+
end
|
990
|
+
end
|
991
|
+
|
992
|
+
def set_label label
|
993
|
+
@label = label
|
994
|
+
label.row @row if label.row == -1
|
995
|
+
label.col @col-(label.name.length+1) if label.col == -1
|
996
|
+
label.label_for(self)
|
997
|
+
end
|
998
|
+
def repaint
|
999
|
+
# $log.debug("FIELD: #{id}, #{zorder}, #{focusable}")
|
1000
|
+
printval = getvalue_for_paint().to_s # added 2009-01-06 23:27
|
1001
|
+
printval = show()*printval.length unless @show.nil?
|
1002
|
+
if !printval.nil?
|
1003
|
+
if printval.length > display_length # only show maxlen
|
1004
|
+
printval = printval[@pcol..@pcol+display_length-1]
|
1005
|
+
else
|
1006
|
+
printval = printval[@pcol..-1]
|
1007
|
+
end
|
1008
|
+
end
|
1009
|
+
#printval = printval[0..display_length-1] if printval.length > display_length
|
1010
|
+
if @bgcolor.is_a? String and @color.is_a? String
|
1011
|
+
acolor = ColorMap.get_color(@color, @bgcolor)
|
1012
|
+
else
|
1013
|
+
acolor = $datacolor
|
1014
|
+
end
|
1015
|
+
@form.window.printstring row, col, sprintf("%-*s", display_length, printval), acolor, @attr
|
1016
|
+
end
|
1017
|
+
def set_focusable(tf)
|
1018
|
+
@focusable = tf
|
1019
|
+
# @form.regenerate_focusables
|
1020
|
+
end
|
1021
|
+
|
1022
|
+
# field
|
1023
|
+
def handle_key ch
|
1024
|
+
case ch
|
1025
|
+
when KEY_LEFT
|
1026
|
+
cursor_backward
|
1027
|
+
when KEY_RIGHT
|
1028
|
+
cursor_forward
|
1029
|
+
when KEY_BACKSPACE, 127
|
1030
|
+
delete_prev_char if @editable
|
1031
|
+
#when KEY_UP
|
1032
|
+
# $log.debug " FIELD GOT KEY_UP, NOW IGNORING 2009-01-16 17:52 "
|
1033
|
+
#@form.select_prev_field # in a table this should not happen 2009-01-16 17:47
|
1034
|
+
# return :UNHANDLED
|
1035
|
+
#when KEY_DOWN
|
1036
|
+
# $log.debug " FIELD GOT KEY_DOWN, NOW IGNORING 2009-01-16 17:52 "
|
1037
|
+
#@form.select_next_field # in a table this should not happen 2009-01-16 17:47
|
1038
|
+
# return :UNHANDLED
|
1039
|
+
when KEY_ENTER, 10, 13
|
1040
|
+
if respond_to? :fire
|
1041
|
+
fire
|
1042
|
+
end
|
1043
|
+
when 330
|
1044
|
+
delete_curr_char if @editable
|
1045
|
+
when ?\C-a
|
1046
|
+
cursor_home
|
1047
|
+
when ?\C-e
|
1048
|
+
cursor_end
|
1049
|
+
when ?\C-k
|
1050
|
+
delete_eol if @editable
|
1051
|
+
when ?\C-u
|
1052
|
+
@buffer.insert @curpos, @delete_buffer unless @delete_buffer.nil?
|
1053
|
+
when 32..126
|
1054
|
+
#$log.debug("FIELD: ch #{ch} ,at #{@curpos}, buffer:[#{@buffer}] bl: #{@buffer.to_s.length}")
|
1055
|
+
putc ch
|
1056
|
+
when 27 # escape
|
1057
|
+
$log.debug " ADDED FIELD ESCAPE on 2009-01-18 12:27 XXX #{@original_value}"
|
1058
|
+
set_buffer @original_value
|
1059
|
+
else
|
1060
|
+
ret = super
|
1061
|
+
return ret
|
1062
|
+
end
|
1063
|
+
0 # 2008-12-16 23:05 without this -1 was going back so no repaint
|
1064
|
+
end
|
1065
|
+
##
|
1066
|
+
# position cursor at start of field
|
1067
|
+
def cursor_home
|
1068
|
+
set_form_col 0
|
1069
|
+
@pcol = 0
|
1070
|
+
end
|
1071
|
+
##
|
1072
|
+
# goto end of field, "end" is a keyword so could not use it.
|
1073
|
+
def cursor_end
|
1074
|
+
blen = @buffer.rstrip.length
|
1075
|
+
if blen < @display_length
|
1076
|
+
set_form_col blen
|
1077
|
+
else
|
1078
|
+
@pcol = blen-@display_length
|
1079
|
+
set_form_col @display_length-1
|
1080
|
+
end
|
1081
|
+
@curpos = blen # HACK XXX
|
1082
|
+
# $log.debug " crusor END cp:#{@curpos} pcol:#{@pcol} b.l:#{@buffer.length} d_l:#{@display_length} fc:#{@form.col}"
|
1083
|
+
#set_form_col @buffer.length
|
1084
|
+
end
|
1085
|
+
def delete_eol
|
1086
|
+
return -1 unless @editable
|
1087
|
+
pos = @curpos-1
|
1088
|
+
@delete_buffer = @buffer[@curpos..-1]
|
1089
|
+
# if pos is 0, pos-1 becomes -1, end of line!
|
1090
|
+
@buffer = pos == -1 ? "" : @buffer[0..pos]
|
1091
|
+
fire_handler :CHANGE, self # 2008-12-09 14:51
|
1092
|
+
return @delete_buffer
|
1093
|
+
end
|
1094
|
+
def cursor_forward
|
1095
|
+
if @curpos < @buffer.length
|
1096
|
+
if addcol(1)==-1 # go forward if you can, else scroll
|
1097
|
+
@pcol += 1 if @pcol < @display_length
|
1098
|
+
end
|
1099
|
+
@curpos += 1
|
1100
|
+
end
|
1101
|
+
# $log.debug " crusor FORWARD cp:#{@curpos} pcol:#{@pcol} b.l:#{@buffer.length} d_l:#{@display_length} fc:#{@form.col}"
|
1102
|
+
end
|
1103
|
+
def cursor_backward
|
1104
|
+
if @curpos > 0
|
1105
|
+
@curpos -= 1
|
1106
|
+
if @pcol > 0 and @form.col == @col + @col_offset
|
1107
|
+
@pcol -= 1
|
1108
|
+
end
|
1109
|
+
addcol -1
|
1110
|
+
elsif @pcol > 0 # added 2008-11-26 23:05
|
1111
|
+
@pcol -= 1
|
1112
|
+
end
|
1113
|
+
# $log.debug " crusor back cp:#{@curpos} pcol:#{@pcol} b.l:#{@buffer.length} d_l:#{@display_length} fc:#{@form.col}"
|
1114
|
+
=begin
|
1115
|
+
# this is perfect if not scrolling, but now needs changes
|
1116
|
+
if @curpos > 0
|
1117
|
+
@curpos -= 1
|
1118
|
+
addcol -1
|
1119
|
+
end
|
1120
|
+
=end
|
1121
|
+
end
|
1122
|
+
def delete_curr_char
|
1123
|
+
return -1 unless @editable
|
1124
|
+
delete_at
|
1125
|
+
set_modified
|
1126
|
+
end
|
1127
|
+
def delete_prev_char
|
1128
|
+
return -1 if !@editable
|
1129
|
+
return if @curpos <= 0
|
1130
|
+
@curpos -= 1 if @curpos > 0
|
1131
|
+
delete_at
|
1132
|
+
set_modified
|
1133
|
+
addcol -1
|
1134
|
+
end
|
1135
|
+
def addcol num
|
1136
|
+
if num < 0
|
1137
|
+
if @form.col <= @col + @col_offset
|
1138
|
+
# $log.debug " error trying to cursor back #{@form.col}"
|
1139
|
+
return -1
|
1140
|
+
end
|
1141
|
+
elsif num > 0
|
1142
|
+
if @form.col >= @col + @col_offset + @display_length
|
1143
|
+
# $log.debug " error trying to cursor forward #{@form.col}"
|
1144
|
+
return -1
|
1145
|
+
end
|
1146
|
+
end
|
1147
|
+
@form.addcol num
|
1148
|
+
end
|
1149
|
+
# upon leaving a field
|
1150
|
+
# returns false if value not valid as per values or valid_regex
|
1151
|
+
# 2008-12-22 12:40 if null_allowed, don't validate, but do fire_handlers
|
1152
|
+
def on_leave
|
1153
|
+
val = getvalue
|
1154
|
+
#$log.debug " FIELD ON LEAVE:#{val}. #{@values.inspect}"
|
1155
|
+
valid = true
|
1156
|
+
if val.to_s.empty? and @null_allowed
|
1157
|
+
$log.debug " empty and null allowed"
|
1158
|
+
else
|
1159
|
+
if !@values.nil?
|
1160
|
+
valid = @values.include? val
|
1161
|
+
raise FieldValidationException, "Field value (#{val}) not in values: #{@values.join(',')}" unless valid
|
1162
|
+
end
|
1163
|
+
if !@valid_regex.nil?
|
1164
|
+
valid = @valid_regex.match(val.to_s)
|
1165
|
+
raise FieldValidationException, "Field not matching regex #{@valid_regex}" unless valid
|
1166
|
+
end
|
1167
|
+
end
|
1168
|
+
# here is where we should set the forms modified to true - 2009-01-18 12:36 XXX
|
1169
|
+
if modified?
|
1170
|
+
set_modified true
|
1171
|
+
end
|
1172
|
+
super
|
1173
|
+
#return valid
|
1174
|
+
end
|
1175
|
+
## save original value on enter, so we can check for modified.
|
1176
|
+
# 2009-01-18 12:25
|
1177
|
+
def on_enter
|
1178
|
+
@original_value = getvalue.dup rescue getvalue
|
1179
|
+
super
|
1180
|
+
end
|
1181
|
+
##
|
1182
|
+
# overriding widget, check for value change
|
1183
|
+
# 2009-01-18 12:25
|
1184
|
+
def modified?
|
1185
|
+
getvalue() != @original_value
|
1186
|
+
end
|
1187
|
+
# ADD HERE FIELD
|
1188
|
+
end
|
1189
|
+
|
1190
|
+
##
|
1191
|
+
# Like Tk's TkVariable, a simple proxy that can be passed to a widget. The widget
|
1192
|
+
# will update the Variable. A variable can be used to link a field with a label or
|
1193
|
+
# some other widget.
|
1194
|
+
# This is the new version of Variable. Deleting old version on 2009-01-17 12:04
|
1195
|
+
class Variable
|
1196
|
+
|
1197
|
+
def initialize value=""
|
1198
|
+
@update_command = []
|
1199
|
+
@args = []
|
1200
|
+
@value = value
|
1201
|
+
@klass = value.class.to_s
|
1202
|
+
end
|
1203
|
+
def add_dependent obj
|
1204
|
+
$log.debug " ADDING DEPENDE #{obj}"
|
1205
|
+
@dependents ||= []
|
1206
|
+
@dependents << obj
|
1207
|
+
end
|
1208
|
+
##
|
1209
|
+
# install trigger to call whenever a value is updated
|
1210
|
+
def update_command *args, &block
|
1211
|
+
$log.debug "Variable: update command set #{args}"
|
1212
|
+
@update_command << block
|
1213
|
+
@args << args
|
1214
|
+
end
|
1215
|
+
##
|
1216
|
+
# value of the variable
|
1217
|
+
def get_value val=nil
|
1218
|
+
if @klass == 'String'
|
1219
|
+
return @value
|
1220
|
+
elsif @klass == 'Hash'
|
1221
|
+
return @value[val]
|
1222
|
+
elsif @klass == 'Array'
|
1223
|
+
return @value[val]
|
1224
|
+
else
|
1225
|
+
return @value
|
1226
|
+
end
|
1227
|
+
end
|
1228
|
+
##
|
1229
|
+
# update the value of this variable.
|
1230
|
+
# 2008-12-31 18:35 Added source so one can identify multiple sources that are updating.
|
1231
|
+
# Idea is that mutiple fields (e.g. checkboxes) can share one var and update a hash through it.
|
1232
|
+
# Source would contain some code or key relatin to each field.
|
1233
|
+
def set_value val, key=""
|
1234
|
+
oldval = @value
|
1235
|
+
if @klass == 'String'
|
1236
|
+
@value = val
|
1237
|
+
elsif @klass == 'Hash'
|
1238
|
+
$log.debug " Variable setting hash #{key} to #{val}"
|
1239
|
+
oldval = @value[key]
|
1240
|
+
@value[key]=val
|
1241
|
+
elsif @klass == 'Array'
|
1242
|
+
$log.debug " Variable setting array #{key} to #{val}"
|
1243
|
+
oldval = @value[key]
|
1244
|
+
@value[key]=val
|
1245
|
+
else
|
1246
|
+
oldval = @value
|
1247
|
+
@value = val
|
1248
|
+
end
|
1249
|
+
return if @update_command.nil?
|
1250
|
+
@update_command.each_with_index do |comm, ix|
|
1251
|
+
comm.call(self, *@args[ix]) unless comm.nil?
|
1252
|
+
end
|
1253
|
+
@dependents.each {|d| d.fire_property_change(d, oldval, val) } unless @dependents.nil?
|
1254
|
+
end
|
1255
|
+
##
|
1256
|
+
def value= (val)
|
1257
|
+
raise "Please use set_value for hash/array" if @klass=='Hash' or @klass=='Array'
|
1258
|
+
oldval = @value
|
1259
|
+
@value=val
|
1260
|
+
return if @update_command.nil?
|
1261
|
+
@update_command.each_with_index do |comm, ix|
|
1262
|
+
comm.call(self, *@args[ix]) unless comm.nil?
|
1263
|
+
end
|
1264
|
+
@dependents.each {|d| d.fire_property_change(d, oldval, val) } unless @dependents.nil?
|
1265
|
+
end
|
1266
|
+
def value
|
1267
|
+
raise "Please use set_value for hash/array: #{@klass}" if @klass=='Hash' #or @klass=='Array'
|
1268
|
+
@value
|
1269
|
+
end
|
1270
|
+
def inspect
|
1271
|
+
@value.inspect
|
1272
|
+
end
|
1273
|
+
def [](key)
|
1274
|
+
@value[key]
|
1275
|
+
end
|
1276
|
+
##
|
1277
|
+
# in order to run some method we don't yet support
|
1278
|
+
def source
|
1279
|
+
@value
|
1280
|
+
end
|
1281
|
+
def to_s
|
1282
|
+
inspect
|
1283
|
+
end
|
1284
|
+
end
|
1285
|
+
##
|
1286
|
+
# the preferred way of printing text on screen, esp if you want to modify it at run time.
|
1287
|
+
# Use display_length to ensure no spillage.
|
1288
|
+
class Label < Widget
|
1289
|
+
#dsl_accessor :label_for # related field or buddy
|
1290
|
+
dsl_accessor :mnemonic # keyboard focus is passed to buddy based on this key (ALT mask)
|
1291
|
+
# justify required a display length, esp if center.
|
1292
|
+
#dsl_accessor :justify # :right, :left, :center # added 2008-12-22 19:02
|
1293
|
+
dsl_property :justify # :right, :left, :center # added 2008-12-22 19:02
|
1294
|
+
dsl_property :display_length # please give this to ensure the we only print this much
|
1295
|
+
dsl_property :height # if you want a multiline label.
|
1296
|
+
|
1297
|
+
def initialize form, config={}, &block
|
1298
|
+
|
1299
|
+
@row = config.fetch("row",-1)
|
1300
|
+
@col = config.fetch("col",-1)
|
1301
|
+
@bgcolor = config.fetch("bgcolor", $def_bg_color)
|
1302
|
+
@color = config.fetch("color", $def_fg_color)
|
1303
|
+
@text = config.fetch("text", "NOTFOUND")
|
1304
|
+
@editable = false
|
1305
|
+
@focusable = false
|
1306
|
+
super
|
1307
|
+
@justify ||= :left
|
1308
|
+
@name ||= @text
|
1309
|
+
@repaint_required = true
|
1310
|
+
end
|
1311
|
+
def getvalue
|
1312
|
+
@text_variable && @text_variable.value || @text
|
1313
|
+
end
|
1314
|
+
def label_for field
|
1315
|
+
@label_for = field
|
1316
|
+
#$log.debug " label for: #{@label_for}"
|
1317
|
+
bind_hotkey unless @form.nil? # GRRR!
|
1318
|
+
end
|
1319
|
+
|
1320
|
+
##
|
1321
|
+
# for a button, fire it when label invoked without changing focus
|
1322
|
+
# for other widgets, attempt to change focus to that field
|
1323
|
+
def bind_hotkey
|
1324
|
+
if !@mnemonic.nil?
|
1325
|
+
ch = @mnemonic.downcase()[0] ## FIXME 1.9
|
1326
|
+
# meta key
|
1327
|
+
mch = ?\M-a + (ch - ?a)
|
1328
|
+
if @label_for.is_a? RubyCurses::Button and @label_for.respond_to? :fire
|
1329
|
+
@form.bind_key(mch, @label_for) { |_form, _butt| _butt.fire }
|
1330
|
+
else
|
1331
|
+
$log.debug " bind_hotkey label for: #{@label_for}"
|
1332
|
+
@form.bind_key(mch, @label_for) { |_form, _field| _field.focus }
|
1333
|
+
end
|
1334
|
+
end
|
1335
|
+
end
|
1336
|
+
|
1337
|
+
##
|
1338
|
+
# XXX need to move wrapping etc up and done once.
|
1339
|
+
def repaint
|
1340
|
+
return unless @repaint_required
|
1341
|
+
r,c = rowcol
|
1342
|
+
value = getvalue_for_paint
|
1343
|
+
lablist = []
|
1344
|
+
if @height && @height > 1
|
1345
|
+
lablist = wrap_text(value, @display_length).split("\n")
|
1346
|
+
else
|
1347
|
+
# ensure we do not exceed
|
1348
|
+
if !@display_length.nil?
|
1349
|
+
if value.length > @display_length
|
1350
|
+
value = value[0..@display_length-1]
|
1351
|
+
end
|
1352
|
+
end
|
1353
|
+
lablist << value
|
1354
|
+
end
|
1355
|
+
len = @display_length || value.length
|
1356
|
+
acolor = get_color $datacolor
|
1357
|
+
#$log.debug "label :#{@text}, #{value}, #{r}, #{c} col= #{@color}, #{@bgcolor} acolor #{acolor} j:#{@justify} dlL: #{@display_length} "
|
1358
|
+
firstrow = r
|
1359
|
+
_height = @height || 1
|
1360
|
+
str = @justify.to_sym == :right ? "%*s" : "%-*s" # added 2008-12-22 19:05
|
1361
|
+
# loop added for labels that are wrapped.
|
1362
|
+
# TODO clear separately since value can change in status like labels
|
1363
|
+
0.upto(_height-1) { |i|
|
1364
|
+
@form.window.printstring r+i, c, " " * len , acolor,@attr
|
1365
|
+
}
|
1366
|
+
lablist.each_with_index do |_value, ix|
|
1367
|
+
break if ix >= _height
|
1368
|
+
if @justify.to_sym == :center
|
1369
|
+
padding = (@display_length - _value.length)/2
|
1370
|
+
_value = " "*padding + _value + " "*padding # so its cleared if we change it midway
|
1371
|
+
end
|
1372
|
+
@form.window.printstring r, c, str % [len, _value], acolor,@attr
|
1373
|
+
r += 1
|
1374
|
+
end
|
1375
|
+
if !@mnemonic.nil?
|
1376
|
+
ulindex = value.index(@mnemonic) || value.index(@mnemonic.swapcase)
|
1377
|
+
@form.window.mvchgat(y=firstrow, x=c+ulindex, max=1, Ncurses::A_BOLD|Ncurses::A_UNDERLINE, acolor, nil)
|
1378
|
+
end
|
1379
|
+
#@form.window.mvchgat(y=r, x=c, max=len, Ncurses::A_NORMAL, color, nil)
|
1380
|
+
@repaint_required = false
|
1381
|
+
end
|
1382
|
+
# ADD HERE LABEL
|
1383
|
+
end
|
1384
|
+
##
|
1385
|
+
# action buttons
|
1386
|
+
# TODO: phasing out underline, and giving mnemonic and ampersand preference
|
1387
|
+
# - Action: may have to listen to Action property changes so enabled, name etc change can be reflected
|
1388
|
+
class Button < Widget
|
1389
|
+
dsl_accessor :surround_chars # characters to use to surround the button, def is square brackets
|
1390
|
+
dsl_accessor :mnemonic
|
1391
|
+
def initialize form, config={}, &block
|
1392
|
+
@focusable = true
|
1393
|
+
@editable = false
|
1394
|
+
#@command_block = nil
|
1395
|
+
@handler={} # event handler
|
1396
|
+
@event_args ||= {}
|
1397
|
+
super
|
1398
|
+
@bgcolor ||= $datacolor
|
1399
|
+
@color ||= $datacolor
|
1400
|
+
@surround_chars ||= ['[ ', ' ]']
|
1401
|
+
@col_offset = @surround_chars[0].length
|
1402
|
+
#@text = @name if @text.nil?
|
1403
|
+
#bind_hotkey # 2008-12-23 22:41 remarked
|
1404
|
+
end
|
1405
|
+
##
|
1406
|
+
# set button based on Action
|
1407
|
+
# 2009-01-21 19:59
|
1408
|
+
def action a
|
1409
|
+
text a.name
|
1410
|
+
mnemonic a.mnemonic unless a.mnemonic.nil?
|
1411
|
+
command { a.call }
|
1412
|
+
end
|
1413
|
+
##
|
1414
|
+
# sets text, checking for ampersand, uses that for hotkey and underlines
|
1415
|
+
def text(*val)
|
1416
|
+
if val.empty?
|
1417
|
+
return @text
|
1418
|
+
else
|
1419
|
+
s = val[0].dup
|
1420
|
+
s = s.to_s if !s.is_a? String # 2009-01-15 17:32
|
1421
|
+
if (( ix = s.index('&')) != nil)
|
1422
|
+
s.slice!(ix,1)
|
1423
|
+
@underline = ix unless @form.nil? # this setting a fake underline in messageboxes
|
1424
|
+
mnemonic s[ix,1]
|
1425
|
+
end
|
1426
|
+
@text = s
|
1427
|
+
end
|
1428
|
+
end
|
1429
|
+
##
|
1430
|
+
# FIXME this will not work in messageboxes since no form available
|
1431
|
+
def mnemonic char
|
1432
|
+
$log.error " #{self} COULD NOT SET MNEMONIC since form NIL" if @form.nil?
|
1433
|
+
return if @form.nil?
|
1434
|
+
@mnemonic = char
|
1435
|
+
ch = char.downcase()[0] ## XXX 1.9
|
1436
|
+
# meta key
|
1437
|
+
mch = ?\M-a + (ch - ?a)
|
1438
|
+
$log.debug " #{self} setting MNEMO to #{char} #{mch}"
|
1439
|
+
@form.bind_key(mch, self) { |_form, _butt| _butt.fire }
|
1440
|
+
end
|
1441
|
+
##
|
1442
|
+
# which index to use as underline.
|
1443
|
+
# Instead of using this to make a hotkey, I am thinking of giving this a new usage.
|
1444
|
+
# If you wish to override the underline?
|
1445
|
+
# @deprecated . use mnemonic or an ampersand in text.
|
1446
|
+
def OLDunderline ix
|
1447
|
+
_value = @text || getvalue # hack for Togglebutton FIXME
|
1448
|
+
raise "#{self}: underline requires text to be set " if _value.nil?
|
1449
|
+
mnemonic _value[ix]
|
1450
|
+
end
|
1451
|
+
# bind hotkey to form keys. added 2008-12-15 20:19
|
1452
|
+
# use ampersand in name or underline
|
1453
|
+
def bind_hotkey
|
1454
|
+
return if @underline.nil? or @form.nil?
|
1455
|
+
_value = @text || getvalue # hack for Togglebutton FIXME
|
1456
|
+
#_value = getvalue
|
1457
|
+
$log.debug " bind hot #{_value} #{@underline}"
|
1458
|
+
ch = _value[@underline,1].downcase()[0] ## XXX 1.9
|
1459
|
+
@mnemonic = _value[@underline,1]
|
1460
|
+
# meta key
|
1461
|
+
mch = ?\M-a + (ch - ?a)
|
1462
|
+
@form.bind_key(mch, self) { |_form, _butt| _butt.fire }
|
1463
|
+
end
|
1464
|
+
# 2009-01-17 01:48 removed so widgets can be called
|
1465
|
+
# def on_enter
|
1466
|
+
# $log.debug "ONENTER : #{@bgcolor} "
|
1467
|
+
# end
|
1468
|
+
# def on_leave
|
1469
|
+
# $log.debug "ONLEAVE : #{@bgcolor} "
|
1470
|
+
# end
|
1471
|
+
def getvalue
|
1472
|
+
@text_variable.nil? ? @text : @text_variable.get_value(@name)
|
1473
|
+
end
|
1474
|
+
|
1475
|
+
def getvalue_for_paint
|
1476
|
+
ret = getvalue
|
1477
|
+
@text_offset = @surround_chars[0].length
|
1478
|
+
@surround_chars[0] + ret + @surround_chars[1]
|
1479
|
+
end
|
1480
|
+
def repaint # button
|
1481
|
+
#$log.debug("BUTTon repaint : #{self} r:#{@row} c:#{@col} #{getvalue_for_paint}" )
|
1482
|
+
r,c = @row, @col #rowcol include offset for putting cursor
|
1483
|
+
@highlight_foreground ||= $reversecolor
|
1484
|
+
@highlight_background ||= 0
|
1485
|
+
bgcolor = @state==:HIGHLIGHTED ? @highlight_background : @bgcolor
|
1486
|
+
color = @state==:HIGHLIGHTED ? @highlight_foreground : @color
|
1487
|
+
if bgcolor.is_a? String and color.is_a? String
|
1488
|
+
color = ColorMap.get_color(color, bgcolor)
|
1489
|
+
end
|
1490
|
+
value = getvalue_for_paint
|
1491
|
+
#$log.debug("button repaint :#{self} r:#{r} c:#{c} col:#{color} bg #{bgcolor} v: #{value} ul #{@underline} mnem #{@mnemonic}")
|
1492
|
+
len = @display_length || value.length
|
1493
|
+
@form.window.printstring r, c, "%-*s" % [len, value], color, @attr
|
1494
|
+
# @form.window.mvchgat(y=r, x=c, max=len, Ncurses::A_NORMAL, bgcolor, nil)
|
1495
|
+
# in toggle buttons the underline can change as the text toggles
|
1496
|
+
if !@underline.nil? or !@mnemonic.nil?
|
1497
|
+
uline = @underline && (@underline + @text_offset) || value.index(@mnemonic) || value.index(@mnemonic.swapcase)
|
1498
|
+
@form.window.mvchgat(y=r, x=c+uline, max=1, Ncurses::A_BOLD|Ncurses::A_UNDERLINE, color, nil)
|
1499
|
+
end
|
1500
|
+
end
|
1501
|
+
## command of button (invoked on press, hotkey, space)
|
1502
|
+
# added args 2008-12-20 19:22
|
1503
|
+
def command *args, &block
|
1504
|
+
bind :PRESS, *args, &block
|
1505
|
+
$log.debug "#{text} bound PRESS"
|
1506
|
+
end
|
1507
|
+
## fires PRESS event of button
|
1508
|
+
def fire
|
1509
|
+
$log.debug "firing PRESS #{text}"
|
1510
|
+
fire_handler :PRESS, @form
|
1511
|
+
end
|
1512
|
+
# Button
|
1513
|
+
def handle_key ch
|
1514
|
+
case ch
|
1515
|
+
when KEY_LEFT, KEY_UP
|
1516
|
+
$log.debug " from 2009-01-16 18:18 buttons return UNHANDLED on UP DOWN LEFT RIGHT"
|
1517
|
+
return :UNHANDLED
|
1518
|
+
# @form.select_prev_field
|
1519
|
+
when KEY_RIGHT, KEY_DOWN
|
1520
|
+
$log.debug " from 2009-01-16 18:18 buttons return UNHANDLED on UP DOWN LEFT RIGHT"
|
1521
|
+
return :UNHANDLED
|
1522
|
+
# @form.select_next_field
|
1523
|
+
when KEY_ENTER, 10, 13, 32 # added space bar also
|
1524
|
+
if respond_to? :fire
|
1525
|
+
fire
|
1526
|
+
end
|
1527
|
+
else
|
1528
|
+
return :UNHANDLED
|
1529
|
+
end
|
1530
|
+
end
|
1531
|
+
# temporary method, shoud be a proper class
|
1532
|
+
def self.button_layout buttons, row, startcol=0, cols=Ncurses.COLS-1, gap=5
|
1533
|
+
col = startcol
|
1534
|
+
buttons.each_with_index do |b, ix|
|
1535
|
+
$log.debug " BUTTON #{b}: #{b.col} "
|
1536
|
+
b.row = row
|
1537
|
+
b.col col
|
1538
|
+
$log.debug " after BUTTON #{b}: #{b.col} "
|
1539
|
+
len = b.text.length + gap
|
1540
|
+
col += len
|
1541
|
+
end
|
1542
|
+
end
|
1543
|
+
end #BUTTON
|
1544
|
+
|
1545
|
+
##
|
1546
|
+
# an event fired when an item that can be selected is toggled/selected
|
1547
|
+
class ItemEvent
|
1548
|
+
# http://java.sun.com/javase/6/docs/api/java/awt/event/ItemEvent.html
|
1549
|
+
attr_reader :state # :SELECTED :DESELECTED
|
1550
|
+
attr_reader :item # the item pressed such as toggle button
|
1551
|
+
attr_reader :item_selectable # item originating event such as list or collection
|
1552
|
+
attr_reader :item_first # if from a list
|
1553
|
+
attr_reader :item_last #
|
1554
|
+
attr_reader :param_string # for debugging etc
|
1555
|
+
=begin
|
1556
|
+
def initialize item, item_selectable, state, item_first=-1, item_last=-1, paramstring=nil
|
1557
|
+
@item, @item_selectable, @state, @item_first, @item_last =
|
1558
|
+
item, item_selectable, state, item_first, item_last
|
1559
|
+
@param_string = "Item event fired: #{item}, #{state}"
|
1560
|
+
end
|
1561
|
+
=end
|
1562
|
+
# i think only one is needed per object, so create once only
|
1563
|
+
def initialize item, item_selectable
|
1564
|
+
@item, @item_selectable =
|
1565
|
+
item, item_selectable
|
1566
|
+
end
|
1567
|
+
def set state, item_first=-1, item_last=-1, param_string=nil
|
1568
|
+
@state, @item_first, @item_last, @param_string =
|
1569
|
+
state, item_first, item_last, param_string
|
1570
|
+
@param_string = "Item event fired: #{item}, #{state}" if param_string.nil?
|
1571
|
+
end
|
1572
|
+
end
|
1573
|
+
##
|
1574
|
+
# A button that may be switched off an on.
|
1575
|
+
# To be extended by RadioButton and checkbox.
|
1576
|
+
# TODO: add editable here nd prevent toggling if not so.
|
1577
|
+
class ToggleButton < Button
|
1578
|
+
dsl_accessor :onvalue, :offvalue
|
1579
|
+
dsl_accessor :value
|
1580
|
+
dsl_accessor :surround_chars
|
1581
|
+
dsl_accessor :variable # value linked to this variable which is a boolean
|
1582
|
+
dsl_accessor :display_length # 2009-01-06 00:10
|
1583
|
+
|
1584
|
+
# item_event
|
1585
|
+
def initialize form, config={}, &block
|
1586
|
+
super
|
1587
|
+
# no longer linked to text_variable, that was a misunderstanding
|
1588
|
+
@value ||= (@variable.nil? ? false : @variable.get_value(@name)==true)
|
1589
|
+
end
|
1590
|
+
def getvalue
|
1591
|
+
@value ? @onvalue : @offvalue
|
1592
|
+
end
|
1593
|
+
##
|
1594
|
+
# is the button on or off
|
1595
|
+
# added 2008-12-09 19:05
|
1596
|
+
def checked?
|
1597
|
+
@value
|
1598
|
+
end
|
1599
|
+
alias :selected? :checked?
|
1600
|
+
|
1601
|
+
def getvalue_for_paint
|
1602
|
+
buttontext = getvalue()
|
1603
|
+
@text_offset = @surround_chars[0].length
|
1604
|
+
@surround_chars[0] + buttontext + @surround_chars[1]
|
1605
|
+
end
|
1606
|
+
def handle_key ch
|
1607
|
+
if ch == 32
|
1608
|
+
toggle
|
1609
|
+
else
|
1610
|
+
super
|
1611
|
+
end
|
1612
|
+
end
|
1613
|
+
##
|
1614
|
+
# toggle the button value
|
1615
|
+
def toggle
|
1616
|
+
fire
|
1617
|
+
end
|
1618
|
+
def fire
|
1619
|
+
checked(!@value)
|
1620
|
+
# added ItemEvent on 2008-12-31 13:44
|
1621
|
+
@item_event = ItemEvent.new self, self if @item_event.nil?
|
1622
|
+
@item_event.set(@value ? :SELECTED : :DESELECTED)
|
1623
|
+
fire_handler :PRESS, @item_event # should the event itself be ITEM_EVENT
|
1624
|
+
# fire_handler :PRESS, @form
|
1625
|
+
# super
|
1626
|
+
end
|
1627
|
+
##
|
1628
|
+
# set the value to true or false
|
1629
|
+
# user may programmatically want to check or uncheck
|
1630
|
+
def checked tf
|
1631
|
+
@value = tf
|
1632
|
+
if !@variable.nil?
|
1633
|
+
if @value
|
1634
|
+
@variable.set_value((@onvalue || 1), @name)
|
1635
|
+
else
|
1636
|
+
@variable.set_value((@offvalue || 0), @name)
|
1637
|
+
end
|
1638
|
+
end
|
1639
|
+
# call fire of button class 2008-12-09 17:49
|
1640
|
+
end
|
1641
|
+
end # class
|
1642
|
+
##
|
1643
|
+
# A checkbox, may be selected or unselected
|
1644
|
+
# TODO hotkey should work here too.
|
1645
|
+
class CheckBox < ToggleButton
|
1646
|
+
dsl_accessor :align_right # the button will be on the right 2008-12-09 23:41
|
1647
|
+
# if a variable has been defined, off and on value will be set in it (default 0,1)
|
1648
|
+
def initialize form, config={}, &block
|
1649
|
+
@surround_chars = ['[', ']'] # 2008-12-23 23:16 added space in Button so overriding
|
1650
|
+
super
|
1651
|
+
end
|
1652
|
+
def getvalue
|
1653
|
+
@value
|
1654
|
+
end
|
1655
|
+
|
1656
|
+
def getvalue_for_paint
|
1657
|
+
buttontext = getvalue() ? "X" : " "
|
1658
|
+
dtext = @display_length.nil? ? @text : "%-*s" % [@display_length, @text]
|
1659
|
+
dtext = "" if @text.nil? # added 2009-01-13 00:41 since cbcellrenderer prints no text
|
1660
|
+
if @align_right
|
1661
|
+
@text_offset = 0
|
1662
|
+
@col_offset = dtext.length + @surround_chars[0].length + 1
|
1663
|
+
return "#{dtext} " + @surround_chars[0] + buttontext + @surround_chars[1]
|
1664
|
+
else
|
1665
|
+
pretext = @surround_chars[0] + buttontext + @surround_chars[1]
|
1666
|
+
@text_offset = pretext.length + 1
|
1667
|
+
@col_offset = @surround_chars[0].length
|
1668
|
+
#@surround_chars[0] + buttontext + @surround_chars[1] + " #{@text}"
|
1669
|
+
return pretext + " #{dtext}"
|
1670
|
+
end
|
1671
|
+
end
|
1672
|
+
end # class
|
1673
|
+
##
|
1674
|
+
# A selectable button that has a text value. It is based on a Variable that
|
1675
|
+
# is shared by other radio buttons. Only one is selected at a time, unlike checkbox
|
1676
|
+
# 2008-11-27 18:45 just made this inherited from Checkbox
|
1677
|
+
class RadioButton < ToggleButton
|
1678
|
+
dsl_accessor :align_right # the button will be on the right 2008-12-09 23:41
|
1679
|
+
# if a variable has been defined, off and on value will be set in it (default 0,1)
|
1680
|
+
def initialize form, config={}, &block
|
1681
|
+
@surround_chars = ['(', ')'] if @surround_chars.nil?
|
1682
|
+
super
|
1683
|
+
end
|
1684
|
+
# all radio buttons will return the value of the selected value, not the offered value
|
1685
|
+
def getvalue
|
1686
|
+
#@text_variable.value
|
1687
|
+
@variable.get_value @name
|
1688
|
+
end
|
1689
|
+
def getvalue_for_paint
|
1690
|
+
buttontext = getvalue() == @value ? "o" : " "
|
1691
|
+
dtext = @display_length.nil? ? text : "%-*s" % [@display_length, text]
|
1692
|
+
if @align_right
|
1693
|
+
@text_offset = 0
|
1694
|
+
@col_offset = dtext.length + @surround_chars[0].length + 1
|
1695
|
+
return "#{dtext} " + @surround_chars[0] + buttontext + @surround_chars[1]
|
1696
|
+
else
|
1697
|
+
pretext = @surround_chars[0] + buttontext + @surround_chars[1]
|
1698
|
+
@text_offset = pretext.length + 1
|
1699
|
+
@col_offset = @surround_chars[0].length
|
1700
|
+
return pretext + " #{dtext}"
|
1701
|
+
end
|
1702
|
+
end
|
1703
|
+
def toggle
|
1704
|
+
@variable.set_value @value, @name
|
1705
|
+
# call fire of button class 2008-12-09 17:49
|
1706
|
+
fire
|
1707
|
+
end
|
1708
|
+
# added for bindkeys since that calls fire, not toggle - XXX i don't like this
|
1709
|
+
def fire
|
1710
|
+
@variable.set_value @value,@name
|
1711
|
+
super
|
1712
|
+
end
|
1713
|
+
##
|
1714
|
+
# ideally this should not be used. But implemented for completeness.
|
1715
|
+
# it is recommended to toggle some other radio button than to uncheck this.
|
1716
|
+
def checked tf
|
1717
|
+
if tf
|
1718
|
+
toggle
|
1719
|
+
elsif !@variable.nil? and getvalue() != @value # XXX ???
|
1720
|
+
@variable.set_value "",""
|
1721
|
+
end
|
1722
|
+
end
|
1723
|
+
end # class radio
|
1724
|
+
|
1725
|
+
def self.startup
|
1726
|
+
VER::start_ncurses
|
1727
|
+
$log = Logger.new("view.log")
|
1728
|
+
$log.level = Logger::DEBUG
|
1729
|
+
end
|
1730
|
+
|
1731
|
+
end # module
|