rbhex-core 1.0.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 +20 -0
- data/CHANGELOG +2000 -0
- data/LICENSE +56 -0
- data/README.md +44 -0
- data/examples/abasiclist.rb +179 -0
- data/examples/alpmenu.rb +50 -0
- data/examples/app.sample +19 -0
- data/examples/atree.rb +100 -0
- data/examples/bline.rb +136 -0
- data/examples/common/file.rb +45 -0
- data/examples/data/README.markdown +9 -0
- data/examples/data/brew.txt +38 -0
- data/examples/data/color.2 +37 -0
- data/examples/data/gemlist.txt +60 -0
- data/examples/data/lotr.txt +12 -0
- data/examples/data/ports.txt +136 -0
- data/examples/data/table.txt +37 -0
- data/examples/data/tasks.csv +88 -0
- data/examples/data/tasks.txt +27 -0
- data/examples/data/todo.txt +10 -0
- data/examples/data/todo.txt.bak +10 -0
- data/examples/data/todocsv.csv +28 -0
- data/examples/data/unix1.txt +21 -0
- data/examples/data/unix2.txt +11 -0
- data/examples/dbdemo.rb +502 -0
- data/examples/dirtree.rb +94 -0
- data/examples/newtabbedwindow.rb +100 -0
- data/examples/newtesttabp.rb +92 -0
- data/examples/tabular.rb +146 -0
- data/examples/tasks.rb +178 -0
- data/examples/term2.rb +84 -0
- data/examples/testbuttons.rb +296 -0
- data/examples/testcombo.rb +102 -0
- data/examples/testfields.rb +195 -0
- data/examples/testkeypress.rb +72 -0
- data/examples/testlistbox.rb +170 -0
- data/examples/testmessagebox.rb +140 -0
- data/examples/testprogress.rb +116 -0
- data/examples/testree.rb +106 -0
- data/examples/testwsshortcuts.rb +66 -0
- data/examples/testwsshortcuts2.rb +128 -0
- data/lib/rbhex.rb +6 -0
- data/lib/rbhex/core/docs/index.txt +73 -0
- data/lib/rbhex/core/include/action.rb +80 -0
- data/lib/rbhex/core/include/actionmanager.rb +49 -0
- data/lib/rbhex/core/include/appmethods.rb +214 -0
- data/lib/rbhex/core/include/bordertitle.rb +48 -0
- data/lib/rbhex/core/include/chunk.rb +203 -0
- data/lib/rbhex/core/include/io.rb +553 -0
- data/lib/rbhex/core/include/listbindings.rb +74 -0
- data/lib/rbhex/core/include/listcellrenderer.rb +140 -0
- data/lib/rbhex/core/include/listeditable.rb +317 -0
- data/lib/rbhex/core/include/listscrollable.rb +663 -0
- data/lib/rbhex/core/include/listselectable.rb +271 -0
- data/lib/rbhex/core/include/multibuffer.rb +83 -0
- data/lib/rbhex/core/include/orderedhash.rb +77 -0
- data/lib/rbhex/core/include/ractionevent.rb +73 -0
- data/lib/rbhex/core/include/rchangeevent.rb +27 -0
- data/lib/rbhex/core/include/rhistory.rb +95 -0
- data/lib/rbhex/core/include/rinputdataevent.rb +47 -0
- data/lib/rbhex/core/include/vieditable.rb +172 -0
- data/lib/rbhex/core/include/widgetmenu.rb +66 -0
- data/lib/rbhex/core/system/colormap.rb +165 -0
- data/lib/rbhex/core/system/keyboard.rb +150 -0
- data/lib/rbhex/core/system/keydefs.rb +30 -0
- data/lib/rbhex/core/system/ncurses.rb +236 -0
- data/lib/rbhex/core/system/panel.rb +162 -0
- data/lib/rbhex/core/system/window.rb +913 -0
- data/lib/rbhex/core/util/ansiparser.rb +119 -0
- data/lib/rbhex/core/util/app.rb +1228 -0
- data/lib/rbhex/core/util/basestack.rb +410 -0
- data/lib/rbhex/core/util/bottomline.rb +1859 -0
- data/lib/rbhex/core/util/colorparser.rb +77 -0
- data/lib/rbhex/core/util/focusmanager.rb +31 -0
- data/lib/rbhex/core/util/padreader.rb +192 -0
- data/lib/rbhex/core/util/rcommandwindow.rb +604 -0
- data/lib/rbhex/core/util/rdialogs.rb +574 -0
- data/lib/rbhex/core/util/viewer.rb +149 -0
- data/lib/rbhex/core/util/widgetshortcuts.rb +506 -0
- data/lib/rbhex/core/version.rb +5 -0
- data/lib/rbhex/core/widgets/applicationheader.rb +103 -0
- data/lib/rbhex/core/widgets/box.rb +58 -0
- data/lib/rbhex/core/widgets/divider.rb +310 -0
- data/lib/rbhex/core/widgets/keylabelprinter.rb +194 -0
- data/lib/rbhex/core/widgets/rcombo.rb +253 -0
- data/lib/rbhex/core/widgets/rcontainer.rb +415 -0
- data/lib/rbhex/core/widgets/rlink.rb +30 -0
- data/lib/rbhex/core/widgets/rlist.rb +696 -0
- data/lib/rbhex/core/widgets/rmenu.rb +958 -0
- data/lib/rbhex/core/widgets/rmenulink.rb +22 -0
- data/lib/rbhex/core/widgets/rmessagebox.rb +387 -0
- data/lib/rbhex/core/widgets/rprogress.rb +118 -0
- data/lib/rbhex/core/widgets/rtabbedpane.rb +634 -0
- data/lib/rbhex/core/widgets/rtabbedwindow.rb +70 -0
- data/lib/rbhex/core/widgets/rtextarea.rb +960 -0
- data/lib/rbhex/core/widgets/rtextview.rb +739 -0
- data/lib/rbhex/core/widgets/rtree.rb +768 -0
- data/lib/rbhex/core/widgets/rwidget.rb +3277 -0
- data/lib/rbhex/core/widgets/scrollbar.rb +143 -0
- data/lib/rbhex/core/widgets/statusline.rb +113 -0
- data/lib/rbhex/core/widgets/tabular.rb +264 -0
- data/lib/rbhex/core/widgets/tabularwidget.rb +1142 -0
- data/lib/rbhex/core/widgets/textpad.rb +995 -0
- data/lib/rbhex/core/widgets/tree/treecellrenderer.rb +150 -0
- data/lib/rbhex/core/widgets/tree/treemodel.rb +428 -0
- data/rbhex-core.gemspec +32 -0
- metadata +172 -0
|
@@ -0,0 +1,3277 @@
|
|
|
1
|
+
=begin
|
|
2
|
+
* Name: rwidget: base class and then basic widgets like field, button and label
|
|
3
|
+
* Description
|
|
4
|
+
Some simple light widgets for creating ncurses applications. No reliance on ncurses
|
|
5
|
+
forms and fields.
|
|
6
|
+
I expect to pass through this world but once. Any good therefore that I can do,
|
|
7
|
+
or any kindness or ablities that I can show to any fellow creature, let me do it now.
|
|
8
|
+
Let me not defer it or neglect it, for I shall not pass this way again.
|
|
9
|
+
* Author: rkumar (arunachalesha)
|
|
10
|
+
* Date: 2008-11-19 12:49
|
|
11
|
+
* License: Same as Ruby's License (http://www.ruby-lang.org/LICENSE.txt)
|
|
12
|
+
* Last update: 2014-03-24 00:11
|
|
13
|
+
|
|
14
|
+
== CHANGES
|
|
15
|
+
* 2011-10-2 Added PropertyVetoException to rollback changes to property
|
|
16
|
+
* 2011-10-2 Returning self from dsl_accessor and dsl_property for chaining
|
|
17
|
+
* 2011-10-2 removing clutter of buffering, a lot of junk code removed too.
|
|
18
|
+
== TODO
|
|
19
|
+
- make some methods private/protected
|
|
20
|
+
- Add bottom bar also, perhaps allow it to be displayed on a key so it does not take
|
|
21
|
+
- Can key bindings be abstracted so they can be inherited /reused.
|
|
22
|
+
- some kind of CSS style sheet.
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
=end
|
|
26
|
+
require 'logger'
|
|
27
|
+
require 'rbhex/core/system/colormap'
|
|
28
|
+
require 'rbhex/core/include/orderedhash'
|
|
29
|
+
require 'rbhex/core/include/rinputdataevent' # for FIELD 2010-09-11 12:31
|
|
30
|
+
require 'rbhex/core/include/io'
|
|
31
|
+
require 'rbhex/core/system/keydefs'
|
|
32
|
+
|
|
33
|
+
# 2013-03-21 - 187compat removed ||
|
|
34
|
+
BOLD = FFI::NCurses::A_BOLD
|
|
35
|
+
REVERSE = FFI::NCurses::A_REVERSE
|
|
36
|
+
UNDERLINE = FFI::NCurses::A_UNDERLINE
|
|
37
|
+
NORMAL = FFI::NCurses::A_NORMAL
|
|
38
|
+
|
|
39
|
+
class Object
|
|
40
|
+
# thanks to terminal-table for this method
|
|
41
|
+
def yield_or_eval &block
|
|
42
|
+
return unless block
|
|
43
|
+
if block.arity > 0
|
|
44
|
+
yield self
|
|
45
|
+
else
|
|
46
|
+
self.instance_eval(&block)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
class Module
|
|
51
|
+
## others may not want this, sets config, so there's a duplicate hash
|
|
52
|
+
# also creates a attr_writer so you can use =.
|
|
53
|
+
# 2011-10-2 V1.3.1 Now returning self, so i can chain calls
|
|
54
|
+
def dsl_accessor(*symbols)
|
|
55
|
+
symbols.each { |sym|
|
|
56
|
+
class_eval %{
|
|
57
|
+
def #{sym}(*val)
|
|
58
|
+
if val.empty?
|
|
59
|
+
@#{sym}
|
|
60
|
+
else
|
|
61
|
+
@#{sym} = val.size == 1 ? val[0] : val
|
|
62
|
+
# i am itching to deprecate next line XXX
|
|
63
|
+
# @config["#{sym}"]=@#{sym} # commented out 2011-12-18 to simplify
|
|
64
|
+
self # 2011-10-2
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
# can the next bypass validations
|
|
68
|
+
attr_writer sym #2011-10-2
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
end
|
|
72
|
+
# Besides creating getters and setters, this also fires property change handler
|
|
73
|
+
# if the value changes, and after the object has been painted once.
|
|
74
|
+
# 2011-10-2 V1.3.1 Now returning self, so i can chain calls
|
|
75
|
+
def dsl_property(*symbols)
|
|
76
|
+
symbols.each { |sym|
|
|
77
|
+
class_eval %{
|
|
78
|
+
def #{sym}(*val)
|
|
79
|
+
if val.empty?
|
|
80
|
+
@#{sym}
|
|
81
|
+
else
|
|
82
|
+
oldvalue = @#{sym}
|
|
83
|
+
# @#{sym} = val.size == 1 ? val[0] : val
|
|
84
|
+
tmp = val.size == 1 ? val[0] : val
|
|
85
|
+
newvalue = tmp
|
|
86
|
+
# i am itching to deprecate config setting
|
|
87
|
+
if oldvalue.nil? || @_object_created.nil?
|
|
88
|
+
@#{sym} = tmp
|
|
89
|
+
# @config["#{sym}"]=@#{sym} # 2011-12-18
|
|
90
|
+
end
|
|
91
|
+
return(self) if oldvalue.nil? || @_object_created.nil?
|
|
92
|
+
|
|
93
|
+
if oldvalue != newvalue
|
|
94
|
+
# trying to reduce calls to fire, when object is being created
|
|
95
|
+
begin
|
|
96
|
+
@property_changed = true
|
|
97
|
+
fire_property_change("#{sym}", oldvalue, newvalue) if !oldvalue.nil?
|
|
98
|
+
@#{sym} = tmp
|
|
99
|
+
@config["#{sym}"]=@#{sym}
|
|
100
|
+
rescue PropertyVetoException
|
|
101
|
+
$log.warn "PropertyVetoException for #{sym}:" + oldvalue.to_s + "-> "+ newvalue.to_s
|
|
102
|
+
end
|
|
103
|
+
end # if old
|
|
104
|
+
self
|
|
105
|
+
end # if val
|
|
106
|
+
end # def
|
|
107
|
+
#attr_writer sym
|
|
108
|
+
def #{sym}=val
|
|
109
|
+
# TODO if Variable, take .value NEXT VERSION
|
|
110
|
+
#{sym}(val)
|
|
111
|
+
end
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# 2009-10-04 14:13 added RK after suggestion on http://www.ruby-forum.com/topic/196618#856703
|
|
119
|
+
# these are for 1.8 compatibility
|
|
120
|
+
unless "a"[0] == "a"
|
|
121
|
+
class Fixnum
|
|
122
|
+
def ord
|
|
123
|
+
self
|
|
124
|
+
end
|
|
125
|
+
## mostly for control and meta characters
|
|
126
|
+
def getbyte(n)
|
|
127
|
+
self
|
|
128
|
+
end
|
|
129
|
+
end unless "a"[0] == "a"
|
|
130
|
+
# 2013-03-21 - 187compat
|
|
131
|
+
class String
|
|
132
|
+
## mostly for control and meta characters
|
|
133
|
+
def getbyte(n)
|
|
134
|
+
self[n]
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
module RubyCurses
|
|
140
|
+
extend self
|
|
141
|
+
include ColorMap
|
|
142
|
+
class FieldValidationException < RuntimeError
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# The property change is not acceptable, undo it. e.g. test2.rb
|
|
146
|
+
# @param [String] text message
|
|
147
|
+
# @param [Event] PropertyChangeEvent object
|
|
148
|
+
# @since 1.4.0
|
|
149
|
+
class PropertyVetoException < RuntimeError
|
|
150
|
+
def initialize(string, event)
|
|
151
|
+
@string = string
|
|
152
|
+
@event = event
|
|
153
|
+
super(string)
|
|
154
|
+
end
|
|
155
|
+
attr_reader :string, :event
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
module Utils
|
|
159
|
+
## this is the numeric argument used to repeat and action by repeatm()
|
|
160
|
+
$multiplier = 0
|
|
161
|
+
|
|
162
|
+
# 2010-03-04 18:01
|
|
163
|
+
## this may come in handy for methods to know whether they are inside a batch action or not
|
|
164
|
+
# e.g. a single call of foo() may set a var, a repeated call of foo() may append to var
|
|
165
|
+
$inside_multiplier_action = true
|
|
166
|
+
|
|
167
|
+
# This has been put here since the API is not yet stable, and i
|
|
168
|
+
# don't want to have to change in many places. 2011-11-10
|
|
169
|
+
#
|
|
170
|
+
# Converts formatted text into chunkline objects.
|
|
171
|
+
#
|
|
172
|
+
# To print chunklines you may for each row:
|
|
173
|
+
# window.wmove row+height, col
|
|
174
|
+
# a = get_attrib @attrib
|
|
175
|
+
# window.show_colored_chunks content, color, a
|
|
176
|
+
#
|
|
177
|
+
# @param [color_parser] object or symbol :tmux, :ansi
|
|
178
|
+
# the color_parser implements parse_format, the symbols
|
|
179
|
+
# relate to default parsers provided.
|
|
180
|
+
# @param [String] string containing formatted text
|
|
181
|
+
def parse_formatted_text(color_parser, formatted_text)
|
|
182
|
+
require 'rbhex/core/include/chunk'
|
|
183
|
+
cp = Chunks::ColorParser.new color_parser
|
|
184
|
+
l = []
|
|
185
|
+
formatted_text.each { |e| l << cp.convert_to_chunk(e) }
|
|
186
|
+
return l
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
##
|
|
190
|
+
# wraps text given max length, puts newlines in it.
|
|
191
|
+
# it does not take into account existing newlines
|
|
192
|
+
# Some classes have @maxlen or display_length which may be passed as the second parameter
|
|
193
|
+
def wrap_text(txt, max )
|
|
194
|
+
txt.gsub(/(.{1,#{max}})( +|$\n?)|(.{1,#{max}})/,
|
|
195
|
+
"\\1\\3\n")
|
|
196
|
+
end
|
|
197
|
+
def clean_string! content
|
|
198
|
+
content.chomp! # don't display newline
|
|
199
|
+
content.gsub!(/[\t\n]/, ' ') # don't display tab
|
|
200
|
+
content.gsub!(/[^[:print:]]/, '') # don't display non print characters
|
|
201
|
+
content
|
|
202
|
+
end
|
|
203
|
+
# needs to move to a keystroke class
|
|
204
|
+
# please use these only for printing or debugging, not comparing
|
|
205
|
+
# I could soon return symbols instead 2010-09-07 14:14
|
|
206
|
+
def keycode_tos keycode
|
|
207
|
+
case keycode
|
|
208
|
+
when 33..126
|
|
209
|
+
return keycode.chr
|
|
210
|
+
when ?\C-a.getbyte(0) .. ?\C-z.getbyte(0)
|
|
211
|
+
return "C-" + (keycode + ?a.getbyte(0) -1).chr
|
|
212
|
+
when ?\M-A.getbyte(0)..?\M-z.getbyte(0)
|
|
213
|
+
return "M-"+ (keycode - 128).chr
|
|
214
|
+
when ?\M-\C-A.getbyte(0)..?\M-\C-Z.getbyte(0)
|
|
215
|
+
return "M-C-"+ (keycode - 32).chr
|
|
216
|
+
when ?\M-0.getbyte(0)..?\M-9.getbyte(0)
|
|
217
|
+
return "M-"+ (keycode-?\M-0.getbyte(0)).to_s
|
|
218
|
+
when 32
|
|
219
|
+
return "space" # changed to lowercase so consistent
|
|
220
|
+
when 27
|
|
221
|
+
return "esc" # changed to lowercase so consistent
|
|
222
|
+
when ?\C-].getbyte(0)
|
|
223
|
+
return "C-]"
|
|
224
|
+
when 258
|
|
225
|
+
return "down"
|
|
226
|
+
when 259
|
|
227
|
+
return "up"
|
|
228
|
+
when 260
|
|
229
|
+
return "left"
|
|
230
|
+
when 261
|
|
231
|
+
return "right"
|
|
232
|
+
when FFI::NCurses::KEY_F1..FFI::NCurses::KEY_F12
|
|
233
|
+
return "F"+ (keycode-264).to_s
|
|
234
|
+
when 330
|
|
235
|
+
return "delete"
|
|
236
|
+
when 127
|
|
237
|
+
return "bs"
|
|
238
|
+
when 353
|
|
239
|
+
return "btab"
|
|
240
|
+
when 481
|
|
241
|
+
return "M-S-tab"
|
|
242
|
+
when 393..402
|
|
243
|
+
return "M-F"+ (keycode-392).to_s
|
|
244
|
+
when 0
|
|
245
|
+
return "C-space"
|
|
246
|
+
when 160
|
|
247
|
+
return "M-space" # at least on OSX Leopard now (don't remember this working on PPC)
|
|
248
|
+
when C_LEFT
|
|
249
|
+
return "C-left"
|
|
250
|
+
when C_RIGHT
|
|
251
|
+
return "C-right"
|
|
252
|
+
when S_F9
|
|
253
|
+
return "S_F9"
|
|
254
|
+
else
|
|
255
|
+
others=[?\M--,?\M-+,?\M-=,?\M-',?\M-",?\M-;,?\M-:,?\M-\,, ?\M-.,?\M-<,?\M->,?\M-?,?\M-/,?\M-!]
|
|
256
|
+
others.collect! {|x| x.getbyte(0) } ## added 2009-10-04 14:25 for 1.9
|
|
257
|
+
s_others=%w[M-- M-+ M-= M-' M-" M-; M-: M-, M-. M-< M-> M-? M-/ M-!]
|
|
258
|
+
if others.include? keycode
|
|
259
|
+
index = others.index keycode
|
|
260
|
+
return s_others[index]
|
|
261
|
+
end
|
|
262
|
+
# all else failed
|
|
263
|
+
return keycode.to_s
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
# if passed a string in second or third param, will create a color
|
|
268
|
+
# and return, else it will return default color
|
|
269
|
+
# Use this in order to create a color pair with the colors
|
|
270
|
+
# provided, however, if user has not provided, use supplied
|
|
271
|
+
# default.
|
|
272
|
+
# @param [Fixnum] color_pair created by ncurses
|
|
273
|
+
# @param [Symbol] color name such as white black cyan magenta red green yellow
|
|
274
|
+
# @param [Symbol] bgcolor name such as white black cyan magenta red green yellow
|
|
275
|
+
# @example get_color $promptcolor, :white, :cyan
|
|
276
|
+
def get_color default=$datacolor, color=@color, bgcolor=@bgcolor
|
|
277
|
+
return default if color.nil? || bgcolor.nil?
|
|
278
|
+
raise ArgumentError, "Color not valid: #{color}: #{ColorMap.colors} " if !ColorMap.is_color? color
|
|
279
|
+
raise ArgumentError, "Bgolor not valid: #{bgcolor} : #{ColorMap.colors} " if !ColorMap.is_color? bgcolor
|
|
280
|
+
acolor = ColorMap.get_color(color, bgcolor)
|
|
281
|
+
return acolor
|
|
282
|
+
end
|
|
283
|
+
#
|
|
284
|
+
# convert a string to integer attribute
|
|
285
|
+
# FIXME: what if user wishes to OR two attribs, this will give error
|
|
286
|
+
# @param [String] e.g. reverse bold normal underline
|
|
287
|
+
# if a Fixnum is passed, it is returned as is assuming to be
|
|
288
|
+
# an attrib
|
|
289
|
+
def get_attrib str
|
|
290
|
+
return FFI::NCurses::A_NORMAL unless str
|
|
291
|
+
# next line allows us to do a one time conversion and keep the value
|
|
292
|
+
# in the same variable
|
|
293
|
+
if str.is_a? Fixnum
|
|
294
|
+
if [
|
|
295
|
+
FFI::NCurses::A_BOLD,
|
|
296
|
+
FFI::NCurses::A_REVERSE,
|
|
297
|
+
FFI::NCurses::A_NORMAL,
|
|
298
|
+
FFI::NCurses::A_UNDERLINE,
|
|
299
|
+
FFI::NCurses::A_STANDOUT,
|
|
300
|
+
FFI::NCurses::A_DIM,
|
|
301
|
+
FFI::NCurses::A_BOLD | FFI::NCurses::A_REVERSE,
|
|
302
|
+
FFI::NCurses::A_BOLD | FFI::NCurses::A_UNDERLINE,
|
|
303
|
+
FFI::NCurses::A_REVERSE | FFI::NCurses::A_UNDERLINE,
|
|
304
|
+
FFI::NCurses::A_BLINK
|
|
305
|
+
].include? str
|
|
306
|
+
return str
|
|
307
|
+
else
|
|
308
|
+
raise ArgumentError, "get_attrib got a wrong value: #{str} "
|
|
309
|
+
end
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
att = nil
|
|
314
|
+
str = str.downcase.to_sym if str.is_a? String
|
|
315
|
+
case str #.to_s.downcase
|
|
316
|
+
when :bold
|
|
317
|
+
att = FFI::NCurses::A_BOLD
|
|
318
|
+
when :reverse
|
|
319
|
+
att = FFI::NCurses::A_REVERSE
|
|
320
|
+
when :normal
|
|
321
|
+
att = FFI::NCurses::A_NORMAL
|
|
322
|
+
when :underline
|
|
323
|
+
att = FFI::NCurses::A_UNDERLINE
|
|
324
|
+
when :standout
|
|
325
|
+
att = FFI::NCurses::A_STANDOUT
|
|
326
|
+
when :bold_reverse
|
|
327
|
+
att = FFI::NCurses::A_BOLD | FFI::NCurses::A_REVERSE
|
|
328
|
+
when :bold_underline
|
|
329
|
+
att = FFI::NCurses::A_BOLD | FFI::NCurses::A_UNDERLINE
|
|
330
|
+
when :dim
|
|
331
|
+
att = FFI::NCurses::A_DIM
|
|
332
|
+
when :blink
|
|
333
|
+
att = FFI::NCurses::A_BLINK # unlikely to work
|
|
334
|
+
else
|
|
335
|
+
att = FFI::NCurses::A_NORMAL
|
|
336
|
+
end
|
|
337
|
+
return att
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
# returns last line of full screen, should it be current window ?
|
|
341
|
+
def last_line; FFI::NCurses.LINES-1; end
|
|
342
|
+
|
|
343
|
+
# Create a one line window typically at the bottom
|
|
344
|
+
# should we really put this here, too much clutter ?
|
|
345
|
+
def one_line_window at=last_line(), config={}, &blk
|
|
346
|
+
at ||= last_line()
|
|
347
|
+
at = FFI::NCurses.LINES-at if at < 0
|
|
348
|
+
VER::Window.new(1,0,at,0)
|
|
349
|
+
end
|
|
350
|
+
## repeats the given action based on how value of universal numerica argument
|
|
351
|
+
##+ set using the C-u key. Or in vim-mode using numeric keys
|
|
352
|
+
def repeatm
|
|
353
|
+
$inside_multiplier_action = true
|
|
354
|
+
_multiplier = ( ($multiplier.nil? || $multiplier == 0) ? 1 : $multiplier )
|
|
355
|
+
_multiplier.times { yield }
|
|
356
|
+
$multiplier = 0
|
|
357
|
+
$inside_multiplier_action = false
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
##
|
|
361
|
+
# bind an action to a key, required if you create a button which has a hotkey
|
|
362
|
+
# or a field to be focussed on a key, or any other user defined action based on key
|
|
363
|
+
# e.g. bind_key ?\C-x, object, block
|
|
364
|
+
# added 2009-01-06 19:13 since widgets need to handle keys properly
|
|
365
|
+
# 2010-02-24 12:43 trying to take in multiple key bindings, TODO unbind
|
|
366
|
+
# TODO add symbol so easy to map from config file or mapping file
|
|
367
|
+
def bind_key keycode, *args, &blk
|
|
368
|
+
#$log.debug " #{@name} bind_key received #{keycode} "
|
|
369
|
+
@key_handler ||= {}
|
|
370
|
+
#
|
|
371
|
+
# added on 2011-12-4 so we can pass a description for a key and print it
|
|
372
|
+
# The first argument may be a string, it will not be removed
|
|
373
|
+
# so existing programs will remain as is.
|
|
374
|
+
@key_label ||= {}
|
|
375
|
+
if args[0].is_a?(String) || args[0].is_a?(Symbol)
|
|
376
|
+
@key_label[keycode] = args[0]
|
|
377
|
+
else
|
|
378
|
+
@key_label[keycode] = :unknown
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
if !block_given?
|
|
382
|
+
blk = args.pop
|
|
383
|
+
raise "If block not passed, last arg should be a method symbol" if !blk.is_a? Symbol
|
|
384
|
+
#$log.debug " #{@name} bind_key received a symbol #{blk} "
|
|
385
|
+
end
|
|
386
|
+
case keycode
|
|
387
|
+
when String
|
|
388
|
+
# single assignment
|
|
389
|
+
keycode = keycode.getbyte(0) #if keycode.class==String ## 1.9 2009-10-05 19:40
|
|
390
|
+
#$log.debug " #{name} Widg String called bind_key BIND #{keycode}, #{keycode_tos(keycode)} "
|
|
391
|
+
#$log.debug " assigning #{keycode} " if $log.debug?
|
|
392
|
+
@key_handler[keycode] = blk
|
|
393
|
+
when Array
|
|
394
|
+
# double assignment
|
|
395
|
+
# for starters lets try with 2 keys only
|
|
396
|
+
raise "A one key array will not work. Pass without array" if keycode.size == 1
|
|
397
|
+
a0 = keycode[0]
|
|
398
|
+
a0 = keycode[0].getbyte(0) if keycode[0].class == String
|
|
399
|
+
a1 = keycode[1]
|
|
400
|
+
a1 = keycode[1].getbyte(0) if keycode[1].class == String
|
|
401
|
+
@key_handler[a0] ||= OrderedHash.new
|
|
402
|
+
#$log.debug " assigning #{keycode} , A0 #{a0} , A1 #{a1} " if $log.debug?
|
|
403
|
+
@key_handler[a0][a1] = blk
|
|
404
|
+
#$log.debug " XX assigning #{keycode} to key_handler " if $log.debug?
|
|
405
|
+
else
|
|
406
|
+
#$log.debug " assigning #{keycode} to key_handler " if $log.debug?
|
|
407
|
+
@key_handler[keycode] = blk
|
|
408
|
+
end
|
|
409
|
+
@key_args ||= {}
|
|
410
|
+
@key_args[keycode] = args
|
|
411
|
+
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
def define_prefix_command _name, config={} #_mapvar=nil, _prompt=nil
|
|
415
|
+
$rb_prefix_map ||= {}
|
|
416
|
+
_name = _name.to_sym unless _name.is_a? Symbol
|
|
417
|
+
$rb_prefix_map[_name] ||= {}
|
|
418
|
+
scope = config[:scope] || self
|
|
419
|
+
$rb_prefix_map[_name][:scope] = scope
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
# create a variable by name _name
|
|
423
|
+
# create a method by same name to use
|
|
424
|
+
# Don;t let this happen more than once
|
|
425
|
+
instance_eval %{
|
|
426
|
+
def #{_name.to_s} *args
|
|
427
|
+
#$log.debug "XXX: came inside #{_name} "
|
|
428
|
+
h = $rb_prefix_map["#{_name}".to_sym]
|
|
429
|
+
raise "No prefix_map named #{_name}, #{$rb_prefix_map.keys} " unless h
|
|
430
|
+
ch = @window.getchar
|
|
431
|
+
if ch
|
|
432
|
+
if ch == KEY_F1
|
|
433
|
+
text = ["Options are: "]
|
|
434
|
+
h.keys.each { |e| c = keycode_tos(e); text << c + " " + @descriptions[e] }
|
|
435
|
+
textdialog text, :title => "#{_name} key bindings"
|
|
436
|
+
return
|
|
437
|
+
end
|
|
438
|
+
res = h[ch]
|
|
439
|
+
if res.is_a? Proc
|
|
440
|
+
res.call
|
|
441
|
+
elsif res.is_a? Symbol
|
|
442
|
+
scope = h[:scope]
|
|
443
|
+
scope.send(res)
|
|
444
|
+
elsif res.nil?
|
|
445
|
+
Ncurses.beep
|
|
446
|
+
return :UNHANDLED
|
|
447
|
+
end
|
|
448
|
+
else
|
|
449
|
+
#@window.ungetch(ch)
|
|
450
|
+
:UNHANDLED
|
|
451
|
+
end
|
|
452
|
+
end
|
|
453
|
+
}
|
|
454
|
+
return _name
|
|
455
|
+
end
|
|
456
|
+
|
|
457
|
+
def define_key _symbol, _keycode, *args, &blk
|
|
458
|
+
#_symbol = @symbol
|
|
459
|
+
h = $rb_prefix_map[_symbol]
|
|
460
|
+
raise ArgumentError, "No such keymap #{_symbol} defined. Use define_prefix_command." unless h
|
|
461
|
+
_keycode = _keycode[0].getbyte(0) if _keycode[0].class == String
|
|
462
|
+
arg = args.shift
|
|
463
|
+
if arg.is_a? String
|
|
464
|
+
desc = arg
|
|
465
|
+
arg = args.shift
|
|
466
|
+
elsif arg.is_a? Symbol
|
|
467
|
+
# its a symbol
|
|
468
|
+
desc = arg.to_s
|
|
469
|
+
elsif arg.nil?
|
|
470
|
+
desc = "unknown"
|
|
471
|
+
else
|
|
472
|
+
raise ArgumentError, "Don't know how to handle #{arg.class} in PrefixManager"
|
|
473
|
+
end
|
|
474
|
+
# 2013-03-20 - 18:45 187compat gave error in 187 cannot convert string to int
|
|
475
|
+
#@descriptions ||= []
|
|
476
|
+
@descriptions ||= {}
|
|
477
|
+
@descriptions[_keycode] = desc
|
|
478
|
+
|
|
479
|
+
if !block_given?
|
|
480
|
+
blk = arg
|
|
481
|
+
end
|
|
482
|
+
h[_keycode] = blk
|
|
483
|
+
end
|
|
484
|
+
# define a key within a prefix key map such as C-x
|
|
485
|
+
def OLDdefine_key _symbol, _keycode, _method=nil, &blk
|
|
486
|
+
h = $rb_prefix_map[_symbol]
|
|
487
|
+
raise ArgumentError, "No such keymap #{_symbol} defined. Use define_prefix_command." unless h
|
|
488
|
+
_keycode = _keycode[0].getbyte(0) if _keycode[0].class == String
|
|
489
|
+
if !block_given?
|
|
490
|
+
blk = _method
|
|
491
|
+
end
|
|
492
|
+
h[_keycode] = blk
|
|
493
|
+
end
|
|
494
|
+
# Display key bindings for current widget and form in dialog
|
|
495
|
+
def print_key_bindings *args
|
|
496
|
+
f = get_current_field
|
|
497
|
+
#labels = [@key_label, f.key_label]
|
|
498
|
+
#labels = [@key_label]
|
|
499
|
+
#labels << f.key_label if f.key_label
|
|
500
|
+
labels = []
|
|
501
|
+
labels << (f.key_label || {}) #if f.key_label
|
|
502
|
+
labels << @key_label
|
|
503
|
+
arr = []
|
|
504
|
+
if get_current_field.help_text
|
|
505
|
+
arr << get_current_field.help_text
|
|
506
|
+
end
|
|
507
|
+
labels.each_with_index { |h, i|
|
|
508
|
+
case i
|
|
509
|
+
when 0
|
|
510
|
+
arr << " === Current widget bindings ==="
|
|
511
|
+
when 1
|
|
512
|
+
arr << " === Form bindings ==="
|
|
513
|
+
end
|
|
514
|
+
|
|
515
|
+
h.each_pair { |name, val|
|
|
516
|
+
if name.is_a? Fixnum
|
|
517
|
+
name = keycode_tos name
|
|
518
|
+
elsif name.is_a? String
|
|
519
|
+
name = keycode_tos(name.getbyte(0))
|
|
520
|
+
elsif name.is_a? Array
|
|
521
|
+
s = []
|
|
522
|
+
name.each { |e|
|
|
523
|
+
s << keycode_tos(e.getbyte(0))
|
|
524
|
+
}
|
|
525
|
+
name = s
|
|
526
|
+
else
|
|
527
|
+
#$log.debug "XXX: KEY #{name} #{name.class} "
|
|
528
|
+
end
|
|
529
|
+
arr << " %-30s %s" % [name ,val]
|
|
530
|
+
$log.debug "KEY: #{name} : #{val} "
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
textdialog arr, :title => "Key Bindings"
|
|
534
|
+
end
|
|
535
|
+
def bind_keys keycodes, *args, &blk
|
|
536
|
+
keycodes.each { |k| bind_key k, *args, &blk }
|
|
537
|
+
end
|
|
538
|
+
# e.g. process_key ch, self
|
|
539
|
+
# returns UNHANDLED if no block for it
|
|
540
|
+
# after form handles basic keys, it gives unhandled key to current field, if current field returns
|
|
541
|
+
# unhandled, then it checks this map.
|
|
542
|
+
# added 2009-01-06 19:13 since widgets need to handle keys properly
|
|
543
|
+
# added 2009-01-18 12:58 returns ret val of blk.call
|
|
544
|
+
# so that if block does not handle, the key can still be handled
|
|
545
|
+
# e.g. table last row, last col does not handle, so it will auto go to next field
|
|
546
|
+
# 2010-02-24 13:45 handles 2 key combinations, copied from Form, must be identical in logic
|
|
547
|
+
# except maybe for window pointer. TODO not tested
|
|
548
|
+
def _process_key keycode, object, window
|
|
549
|
+
return :UNHANDLED if @key_handler.nil?
|
|
550
|
+
blk = @key_handler[keycode]
|
|
551
|
+
$log.debug "XXX: _process key keycode #{keycode} #{blk.class}, #{self.class} "
|
|
552
|
+
return :UNHANDLED if blk.nil?
|
|
553
|
+
if blk.is_a? OrderedHash
|
|
554
|
+
#Ncurses::nodelay(window.get_window, bf = false)
|
|
555
|
+
# if you set nodelay in ncurses.rb then this will not
|
|
556
|
+
# wait for second key press, so you then must either make it blocking
|
|
557
|
+
# here, or set a wtimeout here.
|
|
558
|
+
#
|
|
559
|
+
# This is since i have removed timeout globally since resize was happeing
|
|
560
|
+
# after a keypress. maybe we can revert to timeout and not worry about resize so much
|
|
561
|
+
Ncurses::wtimeout(window.get_window, $ncurses_timeout || 500) # will wait a second on wgetch so we can get gg and qq
|
|
562
|
+
ch = window.getch
|
|
563
|
+
Ncurses::nowtimeout(window.get_window, true)
|
|
564
|
+
#Ncurses::nodelay(window.get_window, bf = true)
|
|
565
|
+
|
|
566
|
+
$log.debug " process_key: got #{keycode} , #{ch} "
|
|
567
|
+
# next line ignores function keys etc. C-x F1, thus commented 255 2012-01-11
|
|
568
|
+
if ch < 0 #|| ch > 255
|
|
569
|
+
return nil
|
|
570
|
+
end
|
|
571
|
+
#yn = ch.chr
|
|
572
|
+
blk1 = blk[ch]
|
|
573
|
+
# FIXME we are only returning the second key, what if form
|
|
574
|
+
# has mapped first and second combo. We should unget keycode and ch. 2011-12-23
|
|
575
|
+
# check this out first.
|
|
576
|
+
window.ungetch(ch) if blk1.nil? # trying 2011-09-27
|
|
577
|
+
return :UNHANDLED if blk1.nil? # changed nil to unhandled 2011-09-27
|
|
578
|
+
$log.debug " process_key: found block for #{keycode} , #{ch} "
|
|
579
|
+
blk = blk1
|
|
580
|
+
end
|
|
581
|
+
if blk.is_a? Symbol
|
|
582
|
+
if respond_to? blk
|
|
583
|
+
return send(blk, *@key_args[keycode])
|
|
584
|
+
else
|
|
585
|
+
## 2013-03-05 - 19:50 why the hell is there an alert here, nowhere else
|
|
586
|
+
alert "This ( #{self.class} ) does not respond to #{blk.to_s} "
|
|
587
|
+
# added 2013-03-05 - 19:50 so called can know
|
|
588
|
+
return :UNHANDLED
|
|
589
|
+
end
|
|
590
|
+
else
|
|
591
|
+
$log.debug "rwidget BLOCK called _process_key " if $log.debug?
|
|
592
|
+
return blk.call object, *@key_args[keycode]
|
|
593
|
+
end
|
|
594
|
+
#0
|
|
595
|
+
end
|
|
596
|
+
# view a file or array of strings
|
|
597
|
+
def view what, config={}, &block # :yields: textview for further configuration
|
|
598
|
+
require 'rbhex/core/util/viewer'
|
|
599
|
+
RubyCurses::Viewer.view what, config, &block
|
|
600
|
+
end
|
|
601
|
+
end # module
|
|
602
|
+
|
|
603
|
+
module EventHandler
|
|
604
|
+
##
|
|
605
|
+
# bind an event to a block, optional args will also be passed when calling
|
|
606
|
+
def bind event, *xargs, &blk
|
|
607
|
+
#$log.debug "#{self} called EventHandler BIND #{event}, args:#{xargs} "
|
|
608
|
+
if @_events
|
|
609
|
+
$log.warn "#{self.class} does not support this event: #{event}. #{@_events} " if !@_events.include? event
|
|
610
|
+
#raise ArgumentError, "#{self.class} does not support this event: #{event}. #{@_events} " if !@_events.include? event
|
|
611
|
+
else
|
|
612
|
+
# it can come here if bind in initial block, since widgets add to @_event after calling super
|
|
613
|
+
# maybe we can change that.
|
|
614
|
+
$log.warn "BIND #{self.class} (#{event}) XXXXX no events defined in @_events. Please do so to avoid bugs and debugging. This will become a fatal error soon."
|
|
615
|
+
end
|
|
616
|
+
@handler ||= {}
|
|
617
|
+
@event_args ||= {}
|
|
618
|
+
@handler[event] ||= []
|
|
619
|
+
@handler[event] << blk
|
|
620
|
+
@event_args[event] ||= []
|
|
621
|
+
@event_args[event] << xargs
|
|
622
|
+
end
|
|
623
|
+
alias :add_binding :bind # temporary, needs a proper name to point out that we are adding
|
|
624
|
+
|
|
625
|
+
# NOTE: Do we have a way of removing bindings
|
|
626
|
+
# # TODO check if event is valid. Classes need to define what valid event names are
|
|
627
|
+
|
|
628
|
+
##
|
|
629
|
+
# Fire all bindings for given event
|
|
630
|
+
# e.g. fire_handler :ENTER, self
|
|
631
|
+
# The first parameter passed to the calling block is either self, or some action event
|
|
632
|
+
# The second and beyond are any objects you passed when using `bind` or `command`.
|
|
633
|
+
# Exceptions are caught here itself, or else they prevent objects from updating, usually the error is
|
|
634
|
+
# in the block sent in by application, not our error.
|
|
635
|
+
# TODO: if an object throws a subclass of VetoException we should not catch it and throw it back for
|
|
636
|
+
# caller to catch and take care of, such as prevent LEAVE or update etc.
|
|
637
|
+
def fire_handler event, object
|
|
638
|
+
$log.debug "inside def fire_handler evt:#{event}, o: #{object.class}"
|
|
639
|
+
if !@handler.nil?
|
|
640
|
+
if @_events
|
|
641
|
+
raise ArgumentError, "#{self.class} does not support this event: #{event}. #{@_events} " if !@_events.include? event
|
|
642
|
+
else
|
|
643
|
+
$log.debug "bIND #{self.class} XXXXX TEMPO no events defined in @_events "
|
|
644
|
+
end
|
|
645
|
+
ablk = @handler[event]
|
|
646
|
+
if !ablk.nil?
|
|
647
|
+
aeve = @event_args[event]
|
|
648
|
+
ablk.each_with_index do |blk, ix|
|
|
649
|
+
#$log.debug "#{self} called EventHandler firehander #{@name}, #{event}, obj: #{object},args: #{aeve[ix]}"
|
|
650
|
+
$log.debug "#{self} called EventHandler firehander #{@name}, #{event}"
|
|
651
|
+
begin
|
|
652
|
+
blk.call object, *aeve[ix]
|
|
653
|
+
rescue FieldValidationException => fve
|
|
654
|
+
# added 2011-09-26 1.3.0 so a user raised exception on LEAVE
|
|
655
|
+
# keeps cursor in same field.
|
|
656
|
+
raise fve
|
|
657
|
+
rescue PropertyVetoException => pve
|
|
658
|
+
# added 2011-09-26 1.3.0 so a user raised exception on LEAVE
|
|
659
|
+
# keeps cursor in same field.
|
|
660
|
+
raise pve
|
|
661
|
+
rescue => ex
|
|
662
|
+
## some don't have name
|
|
663
|
+
#$log.error "======= Error ERROR in block event #{self}: #{name}, #{event}"
|
|
664
|
+
$log.error "======= Error ERROR in block event #{self}: #{event}"
|
|
665
|
+
$log.error ex
|
|
666
|
+
$log.error(ex.backtrace.join("\n"))
|
|
667
|
+
#$error_message = "#{ex}" # changed 2010
|
|
668
|
+
$error_message.value = "#{ex.to_s}"
|
|
669
|
+
Ncurses.beep
|
|
670
|
+
end
|
|
671
|
+
end
|
|
672
|
+
else
|
|
673
|
+
# there is no block for this key/event
|
|
674
|
+
# we must behave exactly as processkey
|
|
675
|
+
# NOTE this is too risky since then buttons and radio buttons
|
|
676
|
+
# that don't have any command don;t update,so removing 2011-12-2
|
|
677
|
+
#return :UNHANDLED
|
|
678
|
+
return :NO_BLOCK
|
|
679
|
+
end # if
|
|
680
|
+
else
|
|
681
|
+
# there is no handler
|
|
682
|
+
# I've done this since list traps ENTER but rarely uses it.
|
|
683
|
+
# For buttons default, we'd like to trap ENTER even when focus is elsewhere
|
|
684
|
+
# we must behave exactly as processkey
|
|
685
|
+
# NOTE this is too risky since then buttons and radio buttons
|
|
686
|
+
# that don't have any command don;t update,so removing 2011-12-2
|
|
687
|
+
#return :UNHANDLED
|
|
688
|
+
# If caller wants, can return UNHANDLED such as list and ENTER.
|
|
689
|
+
return :NO_BLOCK
|
|
690
|
+
end # if
|
|
691
|
+
end
|
|
692
|
+
## added on 2009-01-08 00:33
|
|
693
|
+
# goes with dsl_property
|
|
694
|
+
# Need to inform listeners - done 2010-02-25 23:09
|
|
695
|
+
# Can throw a FieldValidationException or PropertyVetoException
|
|
696
|
+
def fire_property_change text, oldvalue, newvalue
|
|
697
|
+
return if oldvalue.nil? || @_object_created.nil? # added 2010-09-16 so if called by methods it is still effective
|
|
698
|
+
$log.debug " FPC #{self}: #{text} #{oldvalue}, #{newvalue}"
|
|
699
|
+
if @pce.nil?
|
|
700
|
+
@pce = PropertyChangeEvent.new(self, text, oldvalue, newvalue)
|
|
701
|
+
else
|
|
702
|
+
@pce.set( self, text, oldvalue, newvalue)
|
|
703
|
+
end
|
|
704
|
+
fire_handler :PROPERTY_CHANGE, @pce
|
|
705
|
+
@repaint_required = true # this was a hack and shoudl go, someone wanted to set this so it would repaint (viewport line 99 fire_prop
|
|
706
|
+
repaint_all(true) # for repainting borders, headers etc 2011-09-28 V1.3.1
|
|
707
|
+
end
|
|
708
|
+
|
|
709
|
+
end # module eventh
|
|
710
|
+
|
|
711
|
+
module ConfigSetup
|
|
712
|
+
# private
|
|
713
|
+
def variable_set var, val
|
|
714
|
+
#nvar = "@#{var}"
|
|
715
|
+
send("#{var}", val) #rescue send("#{var}=", val) # 2009-01-08 01:30 BIG CHANGE calling methods too here.
|
|
716
|
+
# 2011-11-20 NOTE i don;t know why i commented off rescue, but i am wondering
|
|
717
|
+
# we should respect attr_accessor, to make it easy. if respond_to? :var=
|
|
718
|
+
# then set it, else call send. I'd ilke to phase out dsl_accessor.
|
|
719
|
+
#instance_variable_set(nvar, val) # we should not call this !!! bypassing
|
|
720
|
+
end
|
|
721
|
+
def configure(*val , &block)
|
|
722
|
+
case val.size
|
|
723
|
+
when 1
|
|
724
|
+
return @config[val[0]]
|
|
725
|
+
when 2
|
|
726
|
+
@config[val[0]] = val[1]
|
|
727
|
+
variable_set(val[0], val[1])
|
|
728
|
+
end
|
|
729
|
+
instance_eval &block if block_given?
|
|
730
|
+
end
|
|
731
|
+
##
|
|
732
|
+
# returns param from hash. Unused and untested.
|
|
733
|
+
def cget param
|
|
734
|
+
@config[param]
|
|
735
|
+
end
|
|
736
|
+
# this bypasses our methods and sets directly !
|
|
737
|
+
def config_setup aconfig
|
|
738
|
+
@config = aconfig
|
|
739
|
+
# this creates a problem in 1.9.2 since variable_set sets @config 2010-08-22 19:05 RK
|
|
740
|
+
#@config.each_pair { |k,v| variable_set(k,v) }
|
|
741
|
+
keys = @config.keys
|
|
742
|
+
keys.each do |e|
|
|
743
|
+
variable_set(e, @config[e])
|
|
744
|
+
end
|
|
745
|
+
end
|
|
746
|
+
end # module config
|
|
747
|
+
|
|
748
|
+
# Adding widget shortcuts here for non-App cases 2011-10-12 . MOVE these to widget shortcuts
|
|
749
|
+
#
|
|
750
|
+
# prints a status line at bottom where mode's statuses et can be reflected
|
|
751
|
+
def status_line config={}, &block
|
|
752
|
+
require 'rbhex/core/widgets/statusline'
|
|
753
|
+
sl = RubyCurses::StatusLine.new @form, config, &block
|
|
754
|
+
end
|
|
755
|
+
|
|
756
|
+
# add a standard application header
|
|
757
|
+
# == Example
|
|
758
|
+
# header = app_header "rbhex ", :text_center => "Browser Demo", :text_right =>"New Improved!",
|
|
759
|
+
# :color => :black, :bgcolor => :white, :attr => :bold
|
|
760
|
+
def app_header title, config={}, &block
|
|
761
|
+
require 'rbhex/core/widgets/applicationheader'
|
|
762
|
+
header = ApplicationHeader.new @form, title, config, &block
|
|
763
|
+
end
|
|
764
|
+
|
|
765
|
+
# prints pine-like key labels
|
|
766
|
+
def dock labels, config={}, &block
|
|
767
|
+
require 'rbhex/core/widgets/keylabelprinter'
|
|
768
|
+
klp = RubyCurses::KeyLabelPrinter.new @form, labels, config, &block
|
|
769
|
+
end
|
|
770
|
+
|
|
771
|
+
##
|
|
772
|
+
# Basic widget class superclass. Anything embedded in a form should
|
|
773
|
+
# extend this, if it wants to be repainted or wants focus. Otherwise.
|
|
774
|
+
# form will be unaware of it.
|
|
775
|
+
|
|
776
|
+
|
|
777
|
+
class Widget
|
|
778
|
+
require 'rbhex/core/include/action' # added 2012-01-3 for add_action
|
|
779
|
+
include EventHandler
|
|
780
|
+
include ConfigSetup
|
|
781
|
+
include RubyCurses::Utils
|
|
782
|
+
include Io # added 2010-03-06 13:05
|
|
783
|
+
# common interface for text related to a field, label, textview, button etc
|
|
784
|
+
dsl_property :text
|
|
785
|
+
|
|
786
|
+
# next 3 to be checked if used or not. Copied from TK.
|
|
787
|
+
dsl_property :select_foreground, :select_background # color init_pair
|
|
788
|
+
dsl_property :highlight_foreground, :highlight_background # color init_pair
|
|
789
|
+
dsl_property :disabled_foreground, :disabled_background # color init_pair
|
|
790
|
+
|
|
791
|
+
# FIXME is enabled used? is menu using it
|
|
792
|
+
dsl_accessor :focusable, :enabled # boolean
|
|
793
|
+
dsl_property :row, :col # location of object
|
|
794
|
+
dsl_property :color, :bgcolor # normal foreground and background
|
|
795
|
+
# moved to a method which calculates color 2011-11-12
|
|
796
|
+
#dsl_property :color_pair # instead of colors give just color_pair
|
|
797
|
+
dsl_property :attr # attribute bold, normal, reverse
|
|
798
|
+
dsl_accessor :name # name to refr to or recall object by_name
|
|
799
|
+
attr_accessor :id #, :zorder
|
|
800
|
+
attr_accessor :curpos # cursor position inside object - column, not row.
|
|
801
|
+
attr_reader :config # can be used for popping user objects too
|
|
802
|
+
attr_accessor :form # made accessor 2008-11-27 22:32 so menu can set
|
|
803
|
+
attr_accessor :state # normal, selected, highlighted
|
|
804
|
+
attr_reader :row_offset, :col_offset # where should the cursor be placed to start with
|
|
805
|
+
dsl_property :visible # boolean # 2008-12-09 11:29
|
|
806
|
+
#attr_accessor :modified # boolean, value modified or not (moved from field 2009-01-18 00:14 )
|
|
807
|
+
dsl_accessor :help_text # added 2009-01-22 17:41 can be used for status/tooltips
|
|
808
|
+
|
|
809
|
+
dsl_property :preferred_width # added 2009-10-28 13:40 for splitpanes and better resizing
|
|
810
|
+
dsl_property :preferred_height # added 2009-10-28 13:40 for splitpanes and better resizing
|
|
811
|
+
dsl_property :min_width # added 2009-10-28 13:40 for splitpanes and better resizing
|
|
812
|
+
dsl_property :min_height # added 2009-10-28 13:40 for splitpanes and better resizing
|
|
813
|
+
# widget also has height and width as a method
|
|
814
|
+
|
|
815
|
+
attr_accessor :_object_created # 2010-09-16 12:12 to prevent needless property change firing when object being set
|
|
816
|
+
|
|
817
|
+
#attr_accessor :frozen # true false
|
|
818
|
+
#attr_accessor :frozen_list # list of attribs that cannot be changed
|
|
819
|
+
## I think parent_form was not a good idea since i can't add parent widget offsets
|
|
820
|
+
##+ thus we should use parent_comp and push up.
|
|
821
|
+
attr_accessor :parent_component # added 2010-01-12 23:28 BUFFERED - to bubble up
|
|
822
|
+
# tired of getting the cursor wrong and guessing, i am now going to try to get absolute
|
|
823
|
+
# coordinates - 2010-02-07 20:17 this should be updated by parent.
|
|
824
|
+
#attr_accessor :ext_col_offset, :ext_row_offset # 2010-02-07 20:16 to get abs position for cursor rem 2011-09-29
|
|
825
|
+
attr_accessor :rows_panned # moved from form, how many rows scrolled.panned 2010-02-11 15:26
|
|
826
|
+
attr_accessor :cols_panned # moved from form, how many cols scrolled.panned 2010-02-11 15:26
|
|
827
|
+
|
|
828
|
+
# sometimes inside a container there's no way of knowing if an individual comp is in focus
|
|
829
|
+
# other than the explicitly set it and inquire . 2010-09-02 14:47 @since 1.1.5
|
|
830
|
+
# NOTE state takes care of this and is set by form
|
|
831
|
+
attr_accessor :focussed # is this widget in focus, so they may paint differently
|
|
832
|
+
|
|
833
|
+
dsl_accessor :height_pc, :width_pc # tryin out in stacks and flows 2011-11-23
|
|
834
|
+
|
|
835
|
+
attr_reader :key_label
|
|
836
|
+
|
|
837
|
+
def initialize aform, aconfig={}, &block
|
|
838
|
+
# I am trying to avoid passing the nil when you don't want to give a form.
|
|
839
|
+
# I hope this does not create new issues 2011-11-20
|
|
840
|
+
if aform.is_a? Hash
|
|
841
|
+
# presumable there's nothing coming in in hash, or else we will have to merge
|
|
842
|
+
aconfig = aform
|
|
843
|
+
@form = nil
|
|
844
|
+
else
|
|
845
|
+
#raise "got a #{aform.class} "
|
|
846
|
+
@form = aform
|
|
847
|
+
end
|
|
848
|
+
@row_offset ||= 0
|
|
849
|
+
@col_offset ||= 0
|
|
850
|
+
#@ext_row_offset = @ext_col_offset = 0 # 2010-02-07 20:18 # removed on 2011-09-29
|
|
851
|
+
@state = :NORMAL
|
|
852
|
+
#@attr = nil # 2011-11-5 i could be removing what's been entered since super is called
|
|
853
|
+
|
|
854
|
+
@handler = nil # we can avoid firing if nil
|
|
855
|
+
@event_args = {}
|
|
856
|
+
# These are standard events for most widgets which will be fired by
|
|
857
|
+
# Form. In the case of CHANGED, form fires if it's editable property is set, so
|
|
858
|
+
# it does not apply to all widgets.
|
|
859
|
+
@_events ||= []
|
|
860
|
+
@_events.push( *[:ENTER, :LEAVE, :CHANGED, :PROPERTY_CHANGE])
|
|
861
|
+
|
|
862
|
+
config_setup aconfig # @config.each_pair { |k,v| variable_set(k,v) }
|
|
863
|
+
#instance_eval &block if block_given?
|
|
864
|
+
if block_given?
|
|
865
|
+
if block.arity > 0
|
|
866
|
+
yield self
|
|
867
|
+
else
|
|
868
|
+
self.instance_eval(&block)
|
|
869
|
+
end
|
|
870
|
+
end
|
|
871
|
+
# 2010-09-20 13:12 moved down, so it does not create problems with other who want to set their
|
|
872
|
+
# own default
|
|
873
|
+
#@bgcolor ||= "black" # 0
|
|
874
|
+
#@color ||= "white" # $datacolor
|
|
875
|
+
set_form(@form) if @form
|
|
876
|
+
end
|
|
877
|
+
def init_vars
|
|
878
|
+
# just in case anyone does a super. Not putting anything here
|
|
879
|
+
# since i don't want anyone accidentally overriding
|
|
880
|
+
@buffer_modified = false
|
|
881
|
+
#@manages_cursor = false # form should manage it, I will pass row and col to it.
|
|
882
|
+
end
|
|
883
|
+
|
|
884
|
+
# modified
|
|
885
|
+
##
|
|
886
|
+
# typically read will be overridden to check if value changed from what it was on enter.
|
|
887
|
+
# getter and setter for modified (added 2009-01-18 12:31 )
|
|
888
|
+
def modified?
|
|
889
|
+
@modified
|
|
890
|
+
end
|
|
891
|
+
def set_modified tf=true
|
|
892
|
+
@modified = tf
|
|
893
|
+
@form.modified = true if tf
|
|
894
|
+
end
|
|
895
|
+
alias :modified :set_modified
|
|
896
|
+
##
|
|
897
|
+
# getter and setter for text_variable
|
|
898
|
+
def text_variable(*val)
|
|
899
|
+
if val.empty?
|
|
900
|
+
@text_variable
|
|
901
|
+
else
|
|
902
|
+
@text_variable = val[0]
|
|
903
|
+
$log.debug " GOING TO CALL ADD DELPENDENT #{self}"
|
|
904
|
+
@text_variable.add_dependent(self)
|
|
905
|
+
end
|
|
906
|
+
end
|
|
907
|
+
|
|
908
|
+
## got left out by mistake 2008-11-26 20:20
|
|
909
|
+
def on_enter
|
|
910
|
+
@state = :HIGHLIGHTED # duplicating since often these are inside containers
|
|
911
|
+
@focussed = true
|
|
912
|
+
if @handler && @handler.has_key?(:ENTER)
|
|
913
|
+
fire_handler :ENTER, self
|
|
914
|
+
end
|
|
915
|
+
end
|
|
916
|
+
## got left out by mistake 2008-11-26 20:20
|
|
917
|
+
def on_leave
|
|
918
|
+
@state = :NORMAL # duplicating since often these are inside containers
|
|
919
|
+
@focussed = false
|
|
920
|
+
if @handler && @handler.has_key?(:LEAVE)
|
|
921
|
+
fire_handler :LEAVE, self
|
|
922
|
+
end
|
|
923
|
+
end
|
|
924
|
+
##
|
|
925
|
+
# @return row and col of a widget where painting data actually starts
|
|
926
|
+
# row and col is where a widget starts. offsets usually take into account borders.
|
|
927
|
+
# the offsets typically are where the cursor should be positioned inside, upon on_enter.
|
|
928
|
+
def rowcol
|
|
929
|
+
# $log.debug "widgte rowcol : #{@row+@row_offset}, #{@col+@col_offset}"
|
|
930
|
+
return @row+@row_offset, @col+@col_offset
|
|
931
|
+
end
|
|
932
|
+
## return the value of the widget.
|
|
933
|
+
# In cases where selection is possible, should return selected value/s
|
|
934
|
+
def getvalue
|
|
935
|
+
@text_variable && @text_variable.value || @text
|
|
936
|
+
end
|
|
937
|
+
##
|
|
938
|
+
# Am making a separate method since often value for print differs from actual value
|
|
939
|
+
def getvalue_for_paint
|
|
940
|
+
getvalue
|
|
941
|
+
end
|
|
942
|
+
##
|
|
943
|
+
# default repaint method. Called by form for all widgets.
|
|
944
|
+
# widget does not have display_length.
|
|
945
|
+
def repaint
|
|
946
|
+
r,c = rowcol
|
|
947
|
+
@bgcolor ||= $def_bg_color # moved down 2011-11-5
|
|
948
|
+
@color ||= $def_fg_color
|
|
949
|
+
$log.debug("widget repaint : r:#{r} c:#{c} col:#{@color}" )
|
|
950
|
+
value = getvalue_for_paint
|
|
951
|
+
len = @display_length || value.length
|
|
952
|
+
acolor = @color_pair || get_color($datacolor, @color, @bgcolor)
|
|
953
|
+
@graphic.printstring r, c, "%-*s" % [len, value], acolor, @attr
|
|
954
|
+
# next line should be in same color but only have @att so we can change att is nec
|
|
955
|
+
#@form.window.mvchgat(y=r, x=c, max=len, Ncurses::A_NORMAL, @bgcolor, nil)
|
|
956
|
+
#@buffer_modified = true # required for form to call buffer_to_screen CLEANUP
|
|
957
|
+
end
|
|
958
|
+
|
|
959
|
+
def destroy
|
|
960
|
+
$log.debug "DESTROY : widget #{@name} "
|
|
961
|
+
panel = @window.panel
|
|
962
|
+
Ncurses::Panel.del_panel(panel.pointer) if !panel.nil?
|
|
963
|
+
@window.delwin if !@window.nil?
|
|
964
|
+
end
|
|
965
|
+
# in those cases where we create widget without a form, and later give it to
|
|
966
|
+
# some other program which sets the form. Dirty, we should perhaps create widgets
|
|
967
|
+
# without forms, and add explicitly.
|
|
968
|
+
def set_form form
|
|
969
|
+
raise "Form is nil in set_form" if form.nil?
|
|
970
|
+
@form = form
|
|
971
|
+
@id = form.add_widget(self) if !form.nil? and form.respond_to? :add_widget
|
|
972
|
+
# 2009-10-29 15:04 use form.window, unless buffer created
|
|
973
|
+
# should not use form.window so explicitly everywhere.
|
|
974
|
+
# added 2009-12-27 20:05 BUFFERED in case child object needs a form.
|
|
975
|
+
# We don;t wish to overwrite the graphic object
|
|
976
|
+
if @graphic.nil?
|
|
977
|
+
#$log.debug " setting graphic to form window for #{self.class}, #{form} "
|
|
978
|
+
@graphic = form.window unless form.nil? # use screen for writing, not buffer
|
|
979
|
+
end
|
|
980
|
+
end
|
|
981
|
+
# puts cursor on correct row.
|
|
982
|
+
def set_form_row
|
|
983
|
+
# @form.row = @row + 1 + @winrow
|
|
984
|
+
#@form.row = @row + 1
|
|
985
|
+
r, c = rowcol
|
|
986
|
+
$log.warn " empty set_form_row in widget #{self} r = #{r} , c = #{c} "
|
|
987
|
+
#raise "trying to set 0, maybe called repaint before container has set value" if row <= 0
|
|
988
|
+
setrowcol row, nil
|
|
989
|
+
end
|
|
990
|
+
# set cursor on correct column, widget
|
|
991
|
+
# Ideally, this should be overriden, as it is not likely to be correct.
|
|
992
|
+
# NOTE: this is okay for some widgets but NOT for containers
|
|
993
|
+
# that will call their own components SFR and SFC
|
|
994
|
+
def set_form_col col1=@curpos
|
|
995
|
+
@curpos = col1 || 0 # 2010-01-14 21:02
|
|
996
|
+
#@form.col = @col + @col_offset + @curpos
|
|
997
|
+
c = @col + @col_offset + @curpos
|
|
998
|
+
$log.warn " #{@name} empty set_form_col #{c}, curpos #{@curpos} , #{@col} + #{@col_offset} #{@form} "
|
|
999
|
+
setrowcol nil, c
|
|
1000
|
+
end
|
|
1001
|
+
def hide
|
|
1002
|
+
@visible = false
|
|
1003
|
+
end
|
|
1004
|
+
def show
|
|
1005
|
+
@visible = true
|
|
1006
|
+
end
|
|
1007
|
+
def remove
|
|
1008
|
+
@form.remove_widget(self)
|
|
1009
|
+
end
|
|
1010
|
+
# is this required can we remove
|
|
1011
|
+
def move row, col
|
|
1012
|
+
@row = row
|
|
1013
|
+
@col = col
|
|
1014
|
+
end
|
|
1015
|
+
##
|
|
1016
|
+
# moves focus to this field
|
|
1017
|
+
# we must look into running on_leave of previous field
|
|
1018
|
+
def focus
|
|
1019
|
+
return if !@focusable
|
|
1020
|
+
if @form.validate_field != -1
|
|
1021
|
+
@form.select_field @id
|
|
1022
|
+
end
|
|
1023
|
+
end
|
|
1024
|
+
##
|
|
1025
|
+
# remove a binding that you don't want
|
|
1026
|
+
def unbind_key keycode
|
|
1027
|
+
@key_args.delete keycode unless @key_args.nil?
|
|
1028
|
+
@key_handler.delete keycode unless @key_handler.nil?
|
|
1029
|
+
end
|
|
1030
|
+
|
|
1031
|
+
# e.g. process_key ch, self
|
|
1032
|
+
# returns UNHANDLED if no block for it
|
|
1033
|
+
# after form handles basic keys, it gives unhandled key to current field, if current field returns
|
|
1034
|
+
# unhandled, then it checks this map.
|
|
1035
|
+
def process_key keycode, object
|
|
1036
|
+
return _process_key keycode, object, @graphic
|
|
1037
|
+
end
|
|
1038
|
+
##
|
|
1039
|
+
# to be added at end of handle_key of widgets so instlalled actions can be checked
|
|
1040
|
+
def handle_key(ch)
|
|
1041
|
+
ret = process_key ch, self
|
|
1042
|
+
return :UNHANDLED if ret == :UNHANDLED
|
|
1043
|
+
0
|
|
1044
|
+
end
|
|
1045
|
+
# @since 0.1.3
|
|
1046
|
+
def get_preferred_size
|
|
1047
|
+
return @preferred_height, @preferred_width
|
|
1048
|
+
end
|
|
1049
|
+
|
|
1050
|
+
|
|
1051
|
+
##
|
|
1052
|
+
# Inform the system that the buffer has been modified
|
|
1053
|
+
# and should be blitted over the screen or copied to parent.
|
|
1054
|
+
def set_buffer_modified(tf=true)
|
|
1055
|
+
@buffer_modified = tf
|
|
1056
|
+
end
|
|
1057
|
+
|
|
1058
|
+
|
|
1059
|
+
|
|
1060
|
+
##
|
|
1061
|
+
# getter and setter for width - 2009-10-29 22:45
|
|
1062
|
+
# Using dsl_property style
|
|
1063
|
+
#
|
|
1064
|
+
# @param [val, nil] value to set
|
|
1065
|
+
# @return [val] earlier value if nil param
|
|
1066
|
+
# @since 0.1.3
|
|
1067
|
+
#
|
|
1068
|
+
def width(*val)
|
|
1069
|
+
#$log.debug " inside width() #{val}"
|
|
1070
|
+
if val.empty?
|
|
1071
|
+
return @width
|
|
1072
|
+
else
|
|
1073
|
+
#$log.debug " inside width()"
|
|
1074
|
+
oldvalue = @width || 0 # is this default okay, else later nil cries
|
|
1075
|
+
#@width = val.size == 1 ? val[0] : val
|
|
1076
|
+
@width = val[0]
|
|
1077
|
+
newvalue = @width
|
|
1078
|
+
@config["width"]=@width
|
|
1079
|
+
if oldvalue != newvalue
|
|
1080
|
+
@property_changed = true
|
|
1081
|
+
fire_property_change(:width, oldvalue, newvalue)
|
|
1082
|
+
repaint_all(true) # added 2010-01-08 18:51 so widgets can redraw everything.
|
|
1083
|
+
end
|
|
1084
|
+
#if is_double_buffered? and newvalue != oldvalue # removed on 2011-09-29
|
|
1085
|
+
#$log.debug " #{@name} w calling resize of screen buffer with #{newvalue}. WARNING: does not change buffering_params"
|
|
1086
|
+
#@screen_buffer.resize(0, newvalue)
|
|
1087
|
+
#end
|
|
1088
|
+
end
|
|
1089
|
+
end
|
|
1090
|
+
def width=val
|
|
1091
|
+
width(val)
|
|
1092
|
+
end
|
|
1093
|
+
##
|
|
1094
|
+
# getter and setter for height - 2009-10-30 12:25
|
|
1095
|
+
# Using dsl_property style
|
|
1096
|
+
# SO WE've finally succumbed and added height to widget
|
|
1097
|
+
# @param [val, nil] height to set
|
|
1098
|
+
# @return [val] earlier height if nil param
|
|
1099
|
+
# @since 0.1.3
|
|
1100
|
+
#
|
|
1101
|
+
def height(*val)
|
|
1102
|
+
#$log.debug " inside height() #{val[0]}"
|
|
1103
|
+
if val.empty?
|
|
1104
|
+
return @height
|
|
1105
|
+
else
|
|
1106
|
+
#$log.debug " inside #{@name} height()"
|
|
1107
|
+
oldvalue = @height || 0 # is this default okay, else later nil cries
|
|
1108
|
+
@height = val.size == 1 ? val[0] : val
|
|
1109
|
+
newvalue = @height
|
|
1110
|
+
@config[:height]=@height
|
|
1111
|
+
if oldvalue != newvalue
|
|
1112
|
+
@property_changed = true
|
|
1113
|
+
fire_property_change(:height, oldvalue, newvalue)
|
|
1114
|
+
repaint_all true
|
|
1115
|
+
end
|
|
1116
|
+
end
|
|
1117
|
+
end
|
|
1118
|
+
def height=val
|
|
1119
|
+
height(val)
|
|
1120
|
+
end
|
|
1121
|
+
# to give simple access to other components, (eg, parent) to tell a comp to either
|
|
1122
|
+
# paint its data, or to paint all - borders, headers, footers due to a big change (ht/width)
|
|
1123
|
+
def repaint_required(tf=true)
|
|
1124
|
+
@repaint_required = tf
|
|
1125
|
+
end
|
|
1126
|
+
def repaint_all(tf=true)
|
|
1127
|
+
@repaint_all = tf
|
|
1128
|
+
@repaint_required = tf
|
|
1129
|
+
end
|
|
1130
|
+
|
|
1131
|
+
##
|
|
1132
|
+
# When an enclosing component creates a pad (buffer) and the child component
|
|
1133
|
+
#+ should write onto the same pad, then the enclosing component should override
|
|
1134
|
+
#+ the default graphic of child. This applies mainly to editor components in
|
|
1135
|
+
#+ listboxes and tables.
|
|
1136
|
+
# @param graphic graphic object to use for writing contents
|
|
1137
|
+
# @see prepare_editor in rlistbox.
|
|
1138
|
+
# added 2010-01-05 15:25
|
|
1139
|
+
def override_graphic gr
|
|
1140
|
+
@graphic = gr
|
|
1141
|
+
end
|
|
1142
|
+
|
|
1143
|
+
## passing a cursor up and adding col and row offsets
|
|
1144
|
+
## Added 2010-01-13 13:27 I am checking this out.
|
|
1145
|
+
## I would rather pass the value down and store it than do this recursive call
|
|
1146
|
+
##+ for each cursor display
|
|
1147
|
+
# @see Form#setrowcol
|
|
1148
|
+
def setformrowcol r, c
|
|
1149
|
+
@form.row = r unless r.nil?
|
|
1150
|
+
@form.col = c unless c.nil?
|
|
1151
|
+
# this is stupid, going through this route i was losing windows top and left
|
|
1152
|
+
# And this could get repeated if there are mult objects.
|
|
1153
|
+
if !@parent_component.nil? and @parent_component != self
|
|
1154
|
+
r+= @parent_component.form.window.top unless r.nil?
|
|
1155
|
+
c+= @parent_component.form.window.left unless c.nil?
|
|
1156
|
+
$log.debug " (#{@name}) calling parents setformrowcol #{r}, #{c} pa: #{@parent_component.name} self: #{name}, #{self.class}, poff #{@parent_component.row_offset}, #{@parent_component.col_offset}, top:#{@form.window.left} left:#{@form.window.left} "
|
|
1157
|
+
@parent_component.setformrowcol r, c
|
|
1158
|
+
else
|
|
1159
|
+
# no more parents, now set form
|
|
1160
|
+
$log.debug " name NO MORE parents setting #{r}, #{c} in #{@form} "
|
|
1161
|
+
@form.setrowcol r, c
|
|
1162
|
+
end
|
|
1163
|
+
end
|
|
1164
|
+
## widget: i am putting one extra level of indirection so i can switch here
|
|
1165
|
+
# between form#setrowcol and setformrowcol, since i am not convinced either
|
|
1166
|
+
# are giving the accurate result. i am not sure what the issue is.
|
|
1167
|
+
def setrowcol r, c
|
|
1168
|
+
# 2010-02-07 21:32 is this where i should add ext_offsets
|
|
1169
|
+
#$log.debug " #{@name} w.setrowcol #{r} + #{@ext_row_offset}, #{c} + #{@ext_col_offset} "
|
|
1170
|
+
# commented off 2010-02-15 18:22
|
|
1171
|
+
#r += @ext_row_offset unless r.nil?
|
|
1172
|
+
#c += @ext_col_offset unless c.nil?
|
|
1173
|
+
if @form
|
|
1174
|
+
@form.setrowcol r, c
|
|
1175
|
+
#elsif @parent_component
|
|
1176
|
+
else
|
|
1177
|
+
raise "Parent component not defined for #{self}, #{self.class} " unless @parent_component
|
|
1178
|
+
@parent_component.setrowcol r, c
|
|
1179
|
+
end
|
|
1180
|
+
#setformrowcol r,c
|
|
1181
|
+
end
|
|
1182
|
+
|
|
1183
|
+
# I was removing this altogether but vimsplit needs this, or masterdetail gives form and window
|
|
1184
|
+
# to vimsplit. So i 've removed everything but the form and window setting. 2011-09-29 SETBUFF
|
|
1185
|
+
# move from TextView
|
|
1186
|
+
# parameters relating to buffering - new 2010-02-12 12:09 RFED16
|
|
1187
|
+
# I am merging so i can call multiple times
|
|
1188
|
+
# WARNING NOTE : this does not set Pad's top and left since Pad may not be created yet, or at all
|
|
1189
|
+
def set_buffering params
|
|
1190
|
+
|
|
1191
|
+
@target_window ||= params[:target_window]
|
|
1192
|
+
@form = params[:form] unless @form
|
|
1193
|
+
if @graphic.nil?
|
|
1194
|
+
@graphic = @target_window
|
|
1195
|
+
end
|
|
1196
|
+
end
|
|
1197
|
+
|
|
1198
|
+
def event_list
|
|
1199
|
+
return @@events if defined? @@events
|
|
1200
|
+
nil
|
|
1201
|
+
end
|
|
1202
|
+
# 2011-11-12 trying to make color setting a bit sane
|
|
1203
|
+
# You may set as a color_pair using get_color which gives a fixnum
|
|
1204
|
+
# or you may give 2 color symbols so i can update color, bgcolor and colorpair in one shot
|
|
1205
|
+
# if one of them is nil, i just use the existing value
|
|
1206
|
+
def color_pair(*val)
|
|
1207
|
+
if val.empty?
|
|
1208
|
+
return @color_pair
|
|
1209
|
+
end
|
|
1210
|
+
|
|
1211
|
+
oldvalue = @color_pair
|
|
1212
|
+
case val.size
|
|
1213
|
+
when 1
|
|
1214
|
+
raise ArgumentError, "Expecting fixnum for color_pair." unless val[0].is_a? Fixnum
|
|
1215
|
+
@color_pair = val[0]
|
|
1216
|
+
@color, @bgcolor = ColorMap.get_colors_for_pair @color_pair
|
|
1217
|
+
when 2
|
|
1218
|
+
@color = val.first if val.first
|
|
1219
|
+
@bgcolor = val.last if val.last
|
|
1220
|
+
@color_pair = get_color $datacolor, @color, @bgcolor
|
|
1221
|
+
end
|
|
1222
|
+
if oldvalue != @color_pair
|
|
1223
|
+
fire_property_change(:color_pair, oldvalue, @color_pair)
|
|
1224
|
+
@property_changed = true
|
|
1225
|
+
repaint_all true
|
|
1226
|
+
end
|
|
1227
|
+
self
|
|
1228
|
+
end
|
|
1229
|
+
# a general method for all widgets to override with their favorite or most meaninful event
|
|
1230
|
+
# Ideally this is where the block in the constructor should land up.
|
|
1231
|
+
# @since 1.5.0 2011-11-21
|
|
1232
|
+
def command *args, &block
|
|
1233
|
+
if @_events.include? :PRESS
|
|
1234
|
+
bind :PRESS, *args, &block
|
|
1235
|
+
else
|
|
1236
|
+
bind :CHANGED, *args, &block
|
|
1237
|
+
end
|
|
1238
|
+
end
|
|
1239
|
+
# return an object of actionmanager class, creating if required
|
|
1240
|
+
# Widgets and apps may add_action and show_menu using the same
|
|
1241
|
+
def action_manager
|
|
1242
|
+
require 'rbhex/core/include/actionmanager'
|
|
1243
|
+
@action_manager ||= ActionManager.new
|
|
1244
|
+
end
|
|
1245
|
+
#
|
|
1246
|
+
## ADD HERE WIDGET
|
|
1247
|
+
end
|
|
1248
|
+
|
|
1249
|
+
##
|
|
1250
|
+
#
|
|
1251
|
+
# TODO: we don't have an event for when form is entered and exited.
|
|
1252
|
+
# Current ENTER and LEAVE are for when any widgt is entered, so a common event can be put for all widgets
|
|
1253
|
+
# in one place.
|
|
1254
|
+
class Form
|
|
1255
|
+
include EventHandler
|
|
1256
|
+
include RubyCurses::Utils
|
|
1257
|
+
attr_reader :value # ???
|
|
1258
|
+
|
|
1259
|
+
# array of widgets
|
|
1260
|
+
attr_reader :widgets
|
|
1261
|
+
|
|
1262
|
+
# related window used for printing
|
|
1263
|
+
attr_accessor :window
|
|
1264
|
+
|
|
1265
|
+
# cursor row and col
|
|
1266
|
+
attr_accessor :row, :col
|
|
1267
|
+
# attr_accessor :color
|
|
1268
|
+
# attr_accessor :bgcolor
|
|
1269
|
+
|
|
1270
|
+
# has the form been modified
|
|
1271
|
+
attr_accessor :modified
|
|
1272
|
+
|
|
1273
|
+
# index of active widget
|
|
1274
|
+
attr_accessor :active_index
|
|
1275
|
+
|
|
1276
|
+
# hash containing widgets by name for retrieval
|
|
1277
|
+
# Useful if one widget refers to second before second created.
|
|
1278
|
+
attr_reader :by_name
|
|
1279
|
+
|
|
1280
|
+
# associated menubar
|
|
1281
|
+
attr_reader :menu_bar
|
|
1282
|
+
|
|
1283
|
+
attr_accessor :navigation_policy # :CYCLICAL will cycle around. Needed to move to other tabs
|
|
1284
|
+
## i need some way to move the cursor by telling the main form what the coords are
|
|
1285
|
+
##+ perhaps this will work
|
|
1286
|
+
attr_accessor :parent_form # added 2009-12-28 23:01 BUFFERED - to bubble up row col changes
|
|
1287
|
+
|
|
1288
|
+
# how many rows the component is panning embedded widget by
|
|
1289
|
+
attr_accessor :rows_panned # HACK added 2009-12-30 16:01 BUFFERED USED ??? CLEANUP XXX
|
|
1290
|
+
# how many cols the component is panning embedded widget by
|
|
1291
|
+
attr_accessor :cols_panned # HACK added 2009-12-30 16:01 BUFFERED USED ??? CLEANUP XXX
|
|
1292
|
+
|
|
1293
|
+
## next 2 added since tabbedpanes offset needs to be accounted by form inside it.
|
|
1294
|
+
# NOTE: if you set a form inside another set parent_form in addition to these 2.
|
|
1295
|
+
attr_accessor :add_cols # 2010-01-26 20:23 additional columns due to being placed in some container
|
|
1296
|
+
attr_accessor :add_rows # 2010-01-26 20:23 additional columns due to being placed in some container
|
|
1297
|
+
|
|
1298
|
+
# name given to form for debugging
|
|
1299
|
+
attr_accessor :name # for debugging 2010-02-02 20:12
|
|
1300
|
+
|
|
1301
|
+
def initialize win, &block
|
|
1302
|
+
@window = win
|
|
1303
|
+
@widgets = []
|
|
1304
|
+
@by_name = {}
|
|
1305
|
+
@active_index = -1
|
|
1306
|
+
@row = @col = -1
|
|
1307
|
+
@add_cols = @add_rows = 0 # 2010-01-26 20:28 CLEANUP
|
|
1308
|
+
@handler = {}
|
|
1309
|
+
@modified = false
|
|
1310
|
+
@focusable = true
|
|
1311
|
+
@navigation_policy ||= :CYCLICAL
|
|
1312
|
+
@_events = [:ENTER, :LEAVE, :RESIZE]
|
|
1313
|
+
instance_eval &block if block_given?
|
|
1314
|
+
## I need some counter so a widget knows it has been panned and can send a correct
|
|
1315
|
+
##+ cursor coordinate to system.
|
|
1316
|
+
@rows_panned = @cols_panned = 0 # how many rows were panned, typically at a higher level
|
|
1317
|
+
@_firsttime = true; # added on 2010-01-02 19:21 to prevent scrolling crash !
|
|
1318
|
+
@name ||= ""
|
|
1319
|
+
|
|
1320
|
+
# related to emacs kill ring concept for copy-paste
|
|
1321
|
+
|
|
1322
|
+
$kill_ring ||= [] # 2010-03-09 22:42 so textarea and others can copy and paste emacs EMACS
|
|
1323
|
+
$kill_ring_pointer = 0 # needs to be incremented with each append, moved with yank-pop
|
|
1324
|
+
$append_next_kill = false
|
|
1325
|
+
$kill_last_pop_size = 0 # size of last pop which has to be cleared
|
|
1326
|
+
|
|
1327
|
+
$last_key = 0 # last key pressed @since 1.1.5 (not used yet)
|
|
1328
|
+
$current_key = 0 # curr key pressed @since 1.1.5 (so some containers can behave based on whether
|
|
1329
|
+
# user tabbed in, or backtabbed in (rmultisplit)
|
|
1330
|
+
|
|
1331
|
+
# for storing error message
|
|
1332
|
+
$error_message ||= Variable.new ""
|
|
1333
|
+
|
|
1334
|
+
# what kind of key-bindings do you want, :vim or :emacs
|
|
1335
|
+
$key_map ||= :vim ## :emacs or :vim, keys to be defined accordingly. TODO
|
|
1336
|
+
|
|
1337
|
+
bind_key(KEY_F1, 'help') { hm = help_manager(); hm.display_help }
|
|
1338
|
+
end
|
|
1339
|
+
##
|
|
1340
|
+
# set this menubar as the form's menu bar.
|
|
1341
|
+
# also bind the toggle_key for popping up.
|
|
1342
|
+
# Should this not be at application level ?
|
|
1343
|
+
def set_menu_bar mb
|
|
1344
|
+
@menu_bar = mb
|
|
1345
|
+
add_widget mb
|
|
1346
|
+
mb.toggle_key ||= Ncurses.KEY_F2
|
|
1347
|
+
if !mb.toggle_key.nil?
|
|
1348
|
+
ch = mb.toggle_key
|
|
1349
|
+
bind_key(ch, 'Menu Bar') do |_form|
|
|
1350
|
+
if !@menu_bar.nil?
|
|
1351
|
+
@menu_bar.toggle
|
|
1352
|
+
@menu_bar.handle_keys
|
|
1353
|
+
end
|
|
1354
|
+
end
|
|
1355
|
+
end
|
|
1356
|
+
end
|
|
1357
|
+
##
|
|
1358
|
+
# Add given widget to widget list and returns an incremental id.
|
|
1359
|
+
# Adding to widgets, results in it being painted, and focussed.
|
|
1360
|
+
# removing a widget and adding can give the same ID's, however at this point we are not
|
|
1361
|
+
# really using ID. But need to use an incremental int in future.
|
|
1362
|
+
def add_widget widget
|
|
1363
|
+
# this help to access widget by a name
|
|
1364
|
+
if widget.respond_to? :name and !widget.name.nil?
|
|
1365
|
+
##$log.debug "NAME #{self} adding a widget #{@widgets.length} .. #{widget.name} "
|
|
1366
|
+
@by_name[widget.name] = widget
|
|
1367
|
+
end
|
|
1368
|
+
|
|
1369
|
+
|
|
1370
|
+
#$log.debug " #{self} adding a widget #{@widgets.length} .. #{widget} "
|
|
1371
|
+
@widgets << widget
|
|
1372
|
+
return @widgets.length-1
|
|
1373
|
+
end
|
|
1374
|
+
alias :add :add_widget
|
|
1375
|
+
|
|
1376
|
+
# remove a widget
|
|
1377
|
+
# added 2008-12-09 12:18
|
|
1378
|
+
def remove_widget widget
|
|
1379
|
+
if widget.respond_to? :name and !widget.name.nil?
|
|
1380
|
+
@by_name.delete(widget.name)
|
|
1381
|
+
end
|
|
1382
|
+
@widgets.delete widget
|
|
1383
|
+
end
|
|
1384
|
+
# form repaint
|
|
1385
|
+
# to be called at some interval, such as after each keypress.
|
|
1386
|
+
def repaint
|
|
1387
|
+
$log.debug " form repaint:#{self}, #{@name} , r #{@row} c #{@col} " if $log.debug?
|
|
1388
|
+
@widgets.each do |f|
|
|
1389
|
+
next if f.visible == false # added 2008-12-09 12:17
|
|
1390
|
+
#$log.debug "XXX: FORM CALLING REPAINT OF WIDGET #{f} IN LOOP"
|
|
1391
|
+
#raise "Row or col nil #{f.row} #{f.col} for #{f}, #{f.name} " if f.row.nil? || f.col.nil?
|
|
1392
|
+
f.repaint
|
|
1393
|
+
f._object_created = true # added 2010-09-16 13:02 now prop handlers can be fired
|
|
1394
|
+
end
|
|
1395
|
+
# this can bomb if someone sets row. We need a better way!
|
|
1396
|
+
if @row == -1 and @_firsttime == true
|
|
1397
|
+
#set_field_cursor 0
|
|
1398
|
+
# this part caused an endless loop on 2010-01-02 19:20 when scrollpane scrolled up
|
|
1399
|
+
#$log.debug "form repaint calling select field 0 SHOULD HAPPEN FIRST TIME ONLY"
|
|
1400
|
+
select_first_field
|
|
1401
|
+
@_firsttime = false
|
|
1402
|
+
end
|
|
1403
|
+
setpos
|
|
1404
|
+
# XXX this creates a problem if window is a pad
|
|
1405
|
+
# although this does show cursor movement etc.
|
|
1406
|
+
### @window.wrefresh
|
|
1407
|
+
if @window.window_type == :WINDOW
|
|
1408
|
+
#$log.debug " formrepaint #{@name} calling window.wrefresh #{@window} "
|
|
1409
|
+
@window.wrefresh
|
|
1410
|
+
Ncurses::Panel.update_panels ## added 2010-11-05 00:30 to see if clears the stdscr problems
|
|
1411
|
+
else
|
|
1412
|
+
$log.warn " XXX formrepaint #{@name} no refresh called 2011-09-19 #{@window} "
|
|
1413
|
+
end
|
|
1414
|
+
end
|
|
1415
|
+
##
|
|
1416
|
+
# move cursor to where the fields row and col are
|
|
1417
|
+
# private
|
|
1418
|
+
def setpos r=@row, c=@col
|
|
1419
|
+
$log.debug "setpos : (#{self.name}) #{r} #{c} XXX"
|
|
1420
|
+
## adding just in case things are going out of bounds of a parent and no cursor to be shown
|
|
1421
|
+
return if r.nil? or c.nil? # added 2009-12-29 23:28 BUFFERED
|
|
1422
|
+
return if r<0 or c<0 # added 2010-01-02 18:49 stack too deep coming if goes above screen
|
|
1423
|
+
@window.wmove r,c
|
|
1424
|
+
end
|
|
1425
|
+
# @return [Widget, nil] current field, nil if no focusable field
|
|
1426
|
+
def get_current_field
|
|
1427
|
+
select_next_field if @active_index == -1
|
|
1428
|
+
return nil if @active_index.nil? # for forms that have no focusable field 2009-01-08 12:22
|
|
1429
|
+
@widgets[@active_index]
|
|
1430
|
+
end
|
|
1431
|
+
# take focus to first focussable field
|
|
1432
|
+
# we shoud not send to select_next. have a separate method to avoid bugs.
|
|
1433
|
+
# but check current_field, in case called from anotehr field TODO FIXME
|
|
1434
|
+
def select_first_field
|
|
1435
|
+
# this results in on_leave of last field being executed when form starts.
|
|
1436
|
+
#@active_index = -1 # FIXME HACK
|
|
1437
|
+
#select_next_field
|
|
1438
|
+
ix = index_of_first_focusable_field()
|
|
1439
|
+
return unless ix # no focussable field
|
|
1440
|
+
|
|
1441
|
+
# if the user is on a field other than current then fire on_leave
|
|
1442
|
+
if @active_index.nil? || @active_index < 0
|
|
1443
|
+
elsif @active_index != ix
|
|
1444
|
+
f = @widgets[@active_index]
|
|
1445
|
+
begin
|
|
1446
|
+
#$log.debug " select first field, calling on_leave of #{f} #{@active_index} "
|
|
1447
|
+
on_leave f
|
|
1448
|
+
rescue => err
|
|
1449
|
+
$log.error " Caught EXCEPTION req_first_field on_leave #{err}"
|
|
1450
|
+
Ncurses.beep
|
|
1451
|
+
#$error_message = "#{err}"
|
|
1452
|
+
$error_message.value = "#{err}"
|
|
1453
|
+
return
|
|
1454
|
+
end
|
|
1455
|
+
end
|
|
1456
|
+
select_field ix
|
|
1457
|
+
end
|
|
1458
|
+
# please do not use req_ i will deprecate it soon.
|
|
1459
|
+
alias :req_first_field :select_first_field
|
|
1460
|
+
# return the offset of first field that takes focus
|
|
1461
|
+
def index_of_first_focusable_field
|
|
1462
|
+
@widgets.each_with_index do |f, i|
|
|
1463
|
+
if focusable?(f)
|
|
1464
|
+
#select_field i
|
|
1465
|
+
return i
|
|
1466
|
+
end
|
|
1467
|
+
end
|
|
1468
|
+
nil
|
|
1469
|
+
end
|
|
1470
|
+
# take focus to last field on form
|
|
1471
|
+
def select_last_field
|
|
1472
|
+
@active_index = nil
|
|
1473
|
+
select_prev_field
|
|
1474
|
+
end
|
|
1475
|
+
|
|
1476
|
+
# please do not use req_ i will deprecate it soon.
|
|
1477
|
+
alias :req_last_field :select_last_field
|
|
1478
|
+
|
|
1479
|
+
## do not override
|
|
1480
|
+
# form's trigger, fired when any widget loses focus
|
|
1481
|
+
# This wont get called in editor components in tables, since they are formless
|
|
1482
|
+
def on_leave f
|
|
1483
|
+
return if f.nil? || !f.focusable # added focusable, else label was firing
|
|
1484
|
+
f.state = :NORMAL
|
|
1485
|
+
# on leaving update text_variable if defined. Should happen on modified only
|
|
1486
|
+
# should this not be f.text_var ... f.buffer ? 2008-11-25 18:58
|
|
1487
|
+
#f.text_variable.value = f.buffer if !f.text_variable.nil? # 2008-12-20 23:36
|
|
1488
|
+
f.on_leave if f.respond_to? :on_leave
|
|
1489
|
+
fire_handler :LEAVE, f
|
|
1490
|
+
## to test XXX in combo boxes the box may not be editable by be modified by selection.
|
|
1491
|
+
if f.respond_to? :editable and f.modified?
|
|
1492
|
+
$log.debug " Form about to fire CHANGED for #{f} "
|
|
1493
|
+
f.fire_handler(:CHANGED, f)
|
|
1494
|
+
end
|
|
1495
|
+
end
|
|
1496
|
+
# form calls on_enter of each object.
|
|
1497
|
+
# However, if a multicomponent calls on_enter of a widget, this code will
|
|
1498
|
+
# not be triggered. The highlighted part
|
|
1499
|
+
def on_enter f
|
|
1500
|
+
return if f.nil? || !f.focusable # added focusable, else label was firing 2010-09
|
|
1501
|
+
|
|
1502
|
+
f.state = :HIGHLIGHTED
|
|
1503
|
+
# If the widget has a color defined for focussed, set repaint
|
|
1504
|
+
# otherwise it will not be repainted unless user edits !
|
|
1505
|
+
if f.highlight_background || f.highlight_foreground
|
|
1506
|
+
f.repaint_required true
|
|
1507
|
+
end
|
|
1508
|
+
|
|
1509
|
+
f.modified false
|
|
1510
|
+
#f.set_modified false
|
|
1511
|
+
f.on_enter if f.respond_to? :on_enter
|
|
1512
|
+
fire_handler :ENTER, f
|
|
1513
|
+
end
|
|
1514
|
+
## is a field focusable
|
|
1515
|
+
# Added a method here, so forms can extend this to avoid focussing on off-screen components
|
|
1516
|
+
def focusable?(f)
|
|
1517
|
+
return f.focusable
|
|
1518
|
+
end
|
|
1519
|
+
##
|
|
1520
|
+
# puts focus on the given field/widget index
|
|
1521
|
+
# XXX if called externally will not run a on_leave of previous field
|
|
1522
|
+
def select_field ix0
|
|
1523
|
+
return if @widgets.nil? or @widgets.empty? or !focusable?(@widgets[ix0])
|
|
1524
|
+
#$log.debug "inside select_field : #{ix0} ai #{@active_index}"
|
|
1525
|
+
f = @widgets[ix0]
|
|
1526
|
+
if focusable?(f)
|
|
1527
|
+
@active_index = ix0
|
|
1528
|
+
@row, @col = f.rowcol
|
|
1529
|
+
#$log.debug " WMOVE insdie sele nxt field : ROW #{@row} COL #{@col} "
|
|
1530
|
+
on_enter f
|
|
1531
|
+
@window.wmove @row, @col # added RK FFI 2011-09-7 = setpos
|
|
1532
|
+
|
|
1533
|
+
f.set_form_row # added 2011-10-5 so when embedded in another form it can get the cursor
|
|
1534
|
+
f.set_form_col # this can wreak havoc in containers, unless overridden
|
|
1535
|
+
|
|
1536
|
+
# next line in field changes cursor position after setting form_col
|
|
1537
|
+
# resulting in a bug. 2011-11-25
|
|
1538
|
+
# maybe it is in containers or tabbed panes and multi-containers
|
|
1539
|
+
# where previous objects col is still shown. we cannot do this after
|
|
1540
|
+
# setformcol
|
|
1541
|
+
#f.curpos = 0 # why was this, okay is it because of prev obj's cursor ?
|
|
1542
|
+
repaint
|
|
1543
|
+
@window.refresh
|
|
1544
|
+
else
|
|
1545
|
+
$log.debug "inside select field ENABLED FALSE : act #{@active_index} ix0 #{ix0}"
|
|
1546
|
+
end
|
|
1547
|
+
end
|
|
1548
|
+
##
|
|
1549
|
+
# run validate_field on a field, usually whatevers current
|
|
1550
|
+
# before transferring control
|
|
1551
|
+
# We should try to automate this so developer does not have to remember to call it.
|
|
1552
|
+
# # @param field object
|
|
1553
|
+
# @return [0, -1] for success or failure
|
|
1554
|
+
# NOTE : catches exception and sets $error_message, check if -1
|
|
1555
|
+
def validate_field f=@widgets[@active_index]
|
|
1556
|
+
begin
|
|
1557
|
+
on_leave f
|
|
1558
|
+
rescue => err
|
|
1559
|
+
$log.error "form: validate_field caught EXCEPTION #{err}"
|
|
1560
|
+
$log.error(err.backtrace.join("\n"))
|
|
1561
|
+
# $error_message = "#{err}" # changed 2010
|
|
1562
|
+
$error_message.value = "#{err}"
|
|
1563
|
+
Ncurses.beep
|
|
1564
|
+
return -1
|
|
1565
|
+
end
|
|
1566
|
+
return 0
|
|
1567
|
+
end
|
|
1568
|
+
# put focus on next field
|
|
1569
|
+
# will cycle by default, unless navigation policy not :CYCLICAL
|
|
1570
|
+
# in which case returns :NO_NEXT_FIELD.
|
|
1571
|
+
# FIXME: in the beginning it comes in as -1 and does an on_leave of last field
|
|
1572
|
+
def select_next_field
|
|
1573
|
+
return :UNHANDLED if @widgets.nil? || @widgets.empty?
|
|
1574
|
+
#$log.debug "insdie sele nxt field : #{@active_index} WL:#{@widgets.length}"
|
|
1575
|
+
if @active_index.nil? || @active_index == -1 # needs to be tested out A LOT
|
|
1576
|
+
@active_index = -1
|
|
1577
|
+
else
|
|
1578
|
+
f = @widgets[@active_index]
|
|
1579
|
+
begin
|
|
1580
|
+
on_leave f
|
|
1581
|
+
rescue FieldValidationException => err # added 2011-10-2 v1.3.1 so we can rollback
|
|
1582
|
+
$log.error "select_next_field: caught EXCEPTION #{err}"
|
|
1583
|
+
$error_message.value = "#{err}"
|
|
1584
|
+
raise err
|
|
1585
|
+
rescue => err
|
|
1586
|
+
$log.error "select_next_field: caught EXCEPTION #{err}"
|
|
1587
|
+
$log.error(err.backtrace.join("\n"))
|
|
1588
|
+
# $error_message = "#{err}" # changed 2010
|
|
1589
|
+
$error_message.value = "#{err}"
|
|
1590
|
+
Ncurses.beep
|
|
1591
|
+
return 0
|
|
1592
|
+
end
|
|
1593
|
+
end
|
|
1594
|
+
index = @active_index + 1
|
|
1595
|
+
index.upto(@widgets.length-1) do |i|
|
|
1596
|
+
f = @widgets[i]
|
|
1597
|
+
#$log.debug "insdie sele nxt field : i #{i} #{index} WL:#{@widgets.length}, field #{f}"
|
|
1598
|
+
if focusable?(f)
|
|
1599
|
+
select_field i
|
|
1600
|
+
return 0
|
|
1601
|
+
end
|
|
1602
|
+
end
|
|
1603
|
+
#req_first_field
|
|
1604
|
+
#$log.debug "insdie sele nxt field FAILED: #{@active_index} WL:#{@widgets.length}"
|
|
1605
|
+
## added on 2008-12-14 18:27 so we can skip to another form/tab
|
|
1606
|
+
if @navigation_policy == :CYCLICAL
|
|
1607
|
+
@active_index = nil
|
|
1608
|
+
# recursive call worked, but bombed if no focusable field!
|
|
1609
|
+
#select_next_field
|
|
1610
|
+
0.upto(index-1) do |i|
|
|
1611
|
+
f = @widgets[i]
|
|
1612
|
+
if focusable?(f)
|
|
1613
|
+
select_field i
|
|
1614
|
+
return 0
|
|
1615
|
+
end
|
|
1616
|
+
end
|
|
1617
|
+
end
|
|
1618
|
+
$log.debug "inside sele nxt field : NO NEXT #{@active_index} WL:#{@widgets.length}"
|
|
1619
|
+
return :NO_NEXT_FIELD
|
|
1620
|
+
end
|
|
1621
|
+
##
|
|
1622
|
+
# put focus on previous field
|
|
1623
|
+
# will cycle by default, unless navigation policy not :CYCLICAL
|
|
1624
|
+
# in which case returns :NO_PREV_FIELD.
|
|
1625
|
+
# @return [nil, :NO_PREV_FIELD] nil if cyclical and it finds a field
|
|
1626
|
+
# if not cyclical, and no more fields then :NO_PREV_FIELD
|
|
1627
|
+
def select_prev_field
|
|
1628
|
+
return :UNHANDLED if @widgets.nil? or @widgets.empty?
|
|
1629
|
+
#$log.debug "insdie sele prev field : #{@active_index} WL:#{@widgets.length}"
|
|
1630
|
+
if @active_index.nil?
|
|
1631
|
+
@active_index = @widgets.length
|
|
1632
|
+
else
|
|
1633
|
+
f = @widgets[@active_index]
|
|
1634
|
+
begin
|
|
1635
|
+
on_leave f
|
|
1636
|
+
rescue => err
|
|
1637
|
+
$log.error " Caught EXCEPTION #{err}"
|
|
1638
|
+
Ncurses.beep
|
|
1639
|
+
# $error_message = "#{err}" # changed 2010
|
|
1640
|
+
$error_message.value = "#{err}"
|
|
1641
|
+
return
|
|
1642
|
+
end
|
|
1643
|
+
end
|
|
1644
|
+
|
|
1645
|
+
index = @active_index - 1
|
|
1646
|
+
(index).downto(0) do |i|
|
|
1647
|
+
f = @widgets[i]
|
|
1648
|
+
if focusable?(f)
|
|
1649
|
+
select_field i
|
|
1650
|
+
return
|
|
1651
|
+
end
|
|
1652
|
+
end
|
|
1653
|
+
|
|
1654
|
+
## added on 2008-12-14 18:27 so we can skip to another form/tab
|
|
1655
|
+
# 2009-01-08 12:24 no recursion, can be stack overflows if no focusable field
|
|
1656
|
+
if @navigation_policy == :CYCLICAL
|
|
1657
|
+
@active_index = nil # HACK !!!
|
|
1658
|
+
#select_prev_field
|
|
1659
|
+
total = @widgets.length-1
|
|
1660
|
+
total.downto(index-1) do |i|
|
|
1661
|
+
f = @widgets[i]
|
|
1662
|
+
if focusable?(f)
|
|
1663
|
+
select_field i
|
|
1664
|
+
return
|
|
1665
|
+
end
|
|
1666
|
+
end
|
|
1667
|
+
end
|
|
1668
|
+
return :NO_PREV_FIELD
|
|
1669
|
+
end
|
|
1670
|
+
alias :req_next_field :select_next_field
|
|
1671
|
+
alias :req_prev_field :select_prev_field
|
|
1672
|
+
##
|
|
1673
|
+
# move cursor by num columns. Form
|
|
1674
|
+
def addcol num
|
|
1675
|
+
return if @col.nil? || @col == -1
|
|
1676
|
+
@col += num
|
|
1677
|
+
@window.wmove @row, @col
|
|
1678
|
+
## 2010-01-30 23:45 exchange calling parent with calling this forms setrow
|
|
1679
|
+
# since in tabbedpane with table i am not gietting this forms offset.
|
|
1680
|
+
setrowcol nil, col
|
|
1681
|
+
end
|
|
1682
|
+
##
|
|
1683
|
+
# move cursor by given rows and columns, can be negative.
|
|
1684
|
+
# 2010-01-30 23:47 FIXME, if this is called we should call setrowcol like in addcol
|
|
1685
|
+
def addrowcol row,col
|
|
1686
|
+
return if @col.nil? or @col == -1 # contradicts comment on top
|
|
1687
|
+
return if @row.nil? or @row == -1
|
|
1688
|
+
@col += col
|
|
1689
|
+
@row += row
|
|
1690
|
+
@window.wmove @row, @col
|
|
1691
|
+
# added on 2010-01-05 22:26 so component widgets like scrollpane can get the cursor
|
|
1692
|
+
if !@parent_form.nil? and @parent_form != @form
|
|
1693
|
+
$log.debug " #{@name} addrowcol calling parents setrowcol #{row}, #{col} "
|
|
1694
|
+
@parent_form.setrowcol row, col
|
|
1695
|
+
end
|
|
1696
|
+
end
|
|
1697
|
+
|
|
1698
|
+
## Form
|
|
1699
|
+
# New attempt at setting cursor using absolute coordinates
|
|
1700
|
+
# Also, trying NOT to go up. let this pad or window print cursor.
|
|
1701
|
+
def setrowcol r, c
|
|
1702
|
+
@row = r unless r.nil?
|
|
1703
|
+
@col = c unless c.nil?
|
|
1704
|
+
r += @add_rows unless r.nil? # 2010-01-26 20:31
|
|
1705
|
+
c += @add_cols unless c.nil? # 2010-01-26 20:31
|
|
1706
|
+
$log.debug " addcols #{@add_cols} addrow #{@add_rows} : #{self} r = #{r} , c = #{c}, parent: #{@parent_form} "
|
|
1707
|
+
if !@parent_form.nil? and @parent_form != self
|
|
1708
|
+
$log.debug " (#{@name}) addrow calling parents setrowcol #{r}, #{c} : pare: #{@parent_form}; self: #{self}, #{self.class} "
|
|
1709
|
+
#r += @parent_form.window.top unless r.nil?
|
|
1710
|
+
#c += @parent_form.window.left unless c.nil?
|
|
1711
|
+
@parent_form.setrowcol r, c
|
|
1712
|
+
end
|
|
1713
|
+
end
|
|
1714
|
+
##
|
|
1715
|
+
|
|
1716
|
+
# e.g. process_key ch, self
|
|
1717
|
+
# returns UNHANDLED if no block for it
|
|
1718
|
+
# after form handles basic keys, it gives unhandled key to current field, if current field returns
|
|
1719
|
+
# unhandled, then it checks this map.
|
|
1720
|
+
# Please update widget with any changes here. TODO: match regexes as in mapper
|
|
1721
|
+
|
|
1722
|
+
def process_key keycode, object
|
|
1723
|
+
return _process_key keycode, object, @window
|
|
1724
|
+
end
|
|
1725
|
+
|
|
1726
|
+
# Defines how user can give numeric args to a command even in edit mode
|
|
1727
|
+
# User either presses universal_argument (C-u) which generates a series of 4 16 64.
|
|
1728
|
+
# Or he presses C-u and then types some numbers. Followed by the action.
|
|
1729
|
+
# @returns [0, :UNHANDLED] :UNHANDLED implies that last keystroke is still to evaluated
|
|
1730
|
+
# by system. ) implies only numeric args were obtained. This method updates $multiplier
|
|
1731
|
+
|
|
1732
|
+
def universal_argument
|
|
1733
|
+
$multiplier = ( ($multiplier.nil? || $multiplier == 0) ? 4 : $multiplier *= 4)
|
|
1734
|
+
$log.debug " inside UNIV MULT0: #{$multiplier} "
|
|
1735
|
+
# See if user enters numerics. If so discard existing varaible and take only
|
|
1736
|
+
#+ entered values
|
|
1737
|
+
_m = 0
|
|
1738
|
+
while true
|
|
1739
|
+
ch = @window.getchar()
|
|
1740
|
+
case ch
|
|
1741
|
+
when -1
|
|
1742
|
+
next
|
|
1743
|
+
when ?0.getbyte(0)..?9.getbyte(0)
|
|
1744
|
+
_m *= 10 ; _m += (ch-48)
|
|
1745
|
+
$multiplier = _m
|
|
1746
|
+
$log.debug " inside UNIV MULT #{$multiplier} "
|
|
1747
|
+
when ?\C-u.getbyte(0)
|
|
1748
|
+
if _m == 0
|
|
1749
|
+
# user is incrementally hitting C-u
|
|
1750
|
+
$multiplier *= 4
|
|
1751
|
+
else
|
|
1752
|
+
# user is terminating some numbers so he can enter a numeric command next
|
|
1753
|
+
return 0
|
|
1754
|
+
end
|
|
1755
|
+
else
|
|
1756
|
+
$log.debug " inside UNIV MULT else got #{ch} "
|
|
1757
|
+
# here is some other key that is the function key to be repeated. we must honor this
|
|
1758
|
+
# and ensure it goes to the right widget
|
|
1759
|
+
return ch
|
|
1760
|
+
#return :UNHANDLED
|
|
1761
|
+
end
|
|
1762
|
+
end
|
|
1763
|
+
return 0
|
|
1764
|
+
end
|
|
1765
|
+
|
|
1766
|
+
def digit_argument ch
|
|
1767
|
+
$multiplier = ch - ?\M-0.getbyte(0)
|
|
1768
|
+
$log.debug " inside UNIV MULT 0 #{$multiplier} "
|
|
1769
|
+
# See if user enters numerics. If so discard existing varaible and take only
|
|
1770
|
+
#+ entered values
|
|
1771
|
+
_m = $multiplier
|
|
1772
|
+
while true
|
|
1773
|
+
ch = @window.getchar()
|
|
1774
|
+
case ch
|
|
1775
|
+
when -1
|
|
1776
|
+
next
|
|
1777
|
+
when ?0.getbyte(0)..?9.getbyte(0)
|
|
1778
|
+
_m *= 10 ; _m += (ch-48)
|
|
1779
|
+
$multiplier = _m
|
|
1780
|
+
$log.debug " inside UNIV MULT 1 #{$multiplier} "
|
|
1781
|
+
when ?\M-0.getbyte(0)..?\M-9.getbyte(0)
|
|
1782
|
+
_m *= 10 ; _m += (ch-?\M-0.getbyte(0))
|
|
1783
|
+
$multiplier = _m
|
|
1784
|
+
$log.debug " inside UNIV MULT 2 #{$multiplier} "
|
|
1785
|
+
else
|
|
1786
|
+
$log.debug " inside UNIV MULT else got #{ch} "
|
|
1787
|
+
# here is some other key that is the function key to be repeated. we must honor this
|
|
1788
|
+
# and ensure it goes to the right widget
|
|
1789
|
+
return ch
|
|
1790
|
+
#return :UNHANDLED
|
|
1791
|
+
end
|
|
1792
|
+
end
|
|
1793
|
+
return 0
|
|
1794
|
+
end
|
|
1795
|
+
#
|
|
1796
|
+
# These mappings will only trigger if the current field
|
|
1797
|
+
# does not use them.
|
|
1798
|
+
#
|
|
1799
|
+
def map_keys
|
|
1800
|
+
return if @keys_mapped
|
|
1801
|
+
bind_keys([?\M-?,?\?], 'show field help') {
|
|
1802
|
+
#if get_current_field.help_text
|
|
1803
|
+
#textdialog(get_current_field.help_text, 'title' => 'Help Text', :bgcolor => 'green', :color => :white)
|
|
1804
|
+
#else
|
|
1805
|
+
print_key_bindings
|
|
1806
|
+
#end
|
|
1807
|
+
}
|
|
1808
|
+
bind_key(FFI::NCurses::KEY_F9, "Print keys", :print_key_bindings) # show bindings, tentative on F9
|
|
1809
|
+
bind_key(?\M-:, 'show menu') {
|
|
1810
|
+
fld = get_current_field
|
|
1811
|
+
am = fld.action_manager()
|
|
1812
|
+
#fld.init_menu
|
|
1813
|
+
am.show_actions
|
|
1814
|
+
}
|
|
1815
|
+
@keys_mapped = true
|
|
1816
|
+
end
|
|
1817
|
+
|
|
1818
|
+
## forms handle keys
|
|
1819
|
+
# mainly traps tab and backtab to navigate between widgets.
|
|
1820
|
+
# I know some widgets will want to use tab, e.g edit boxes for entering a tab
|
|
1821
|
+
# or for completion.
|
|
1822
|
+
# @throws FieldValidationException
|
|
1823
|
+
# NOTE : please rescue exceptions when you use this in your main loop and alert() user
|
|
1824
|
+
#
|
|
1825
|
+
def handle_key(ch)
|
|
1826
|
+
map_keys unless @keys_mapped
|
|
1827
|
+
handled = :UNHANDLED # 2011-10-4
|
|
1828
|
+
if ch == ?\C-u.getbyte(0)
|
|
1829
|
+
ret = universal_argument
|
|
1830
|
+
$log.debug "C-u FORM set MULT to #{$multiplier}, ret = #{ret} "
|
|
1831
|
+
return 0 if ret == 0
|
|
1832
|
+
ch = ret # unhandled char
|
|
1833
|
+
elsif ch >= ?\M-1.getbyte(0) && ch <= ?\M-9.getbyte(0)
|
|
1834
|
+
if $catch_alt_digits # emacs EMACS
|
|
1835
|
+
ret = digit_argument ch
|
|
1836
|
+
$log.debug " FORM set MULT DA to #{$multiplier}, ret = #{ret} "
|
|
1837
|
+
return 0 if ret == 0 # don't see this happening
|
|
1838
|
+
ch = ret # unhandled char
|
|
1839
|
+
end
|
|
1840
|
+
end
|
|
1841
|
+
|
|
1842
|
+
$current_key = ch
|
|
1843
|
+
case ch
|
|
1844
|
+
when -1
|
|
1845
|
+
return
|
|
1846
|
+
#when Ncurses::KEY_RESIZE # SIGWINCH
|
|
1847
|
+
when FFI::NCurses::KEY_RESIZE # SIGWINCH # FFI
|
|
1848
|
+
lines = Ncurses.LINES
|
|
1849
|
+
cols = Ncurses.COLS
|
|
1850
|
+
x = Ncurses.stdscr.getmaxy
|
|
1851
|
+
y = Ncurses.stdscr.getmaxx
|
|
1852
|
+
$log.debug " form RESIZE HK #{ch} #{self}, #{@name}, #{ch}, x #{x} y #{y} "
|
|
1853
|
+
#alert "SIGWINCH WE NEED TO RECALC AND REPAINT resize #{lines}, #{cols}: #{x}, #{y} "
|
|
1854
|
+
Ncurses.endwin
|
|
1855
|
+
@window.wrefresh
|
|
1856
|
+
@widgets.each { |e| e.repaint_all(true) } # trying out
|
|
1857
|
+
## added RESIZE on 2012-01-5
|
|
1858
|
+
## stuff that relies on last line such as statusline dock etc will need to be redrawn.
|
|
1859
|
+
fire_handler :RESIZE, self
|
|
1860
|
+
else
|
|
1861
|
+
field = get_current_field
|
|
1862
|
+
if $log.debug?
|
|
1863
|
+
keycode = keycode_tos(ch)
|
|
1864
|
+
#$log.debug " form HK #{ch} #{self}, #{@name}, #{keycode}, field: giving to: #{field}, #{field.name} " if field
|
|
1865
|
+
end
|
|
1866
|
+
handled = :UNHANDLED
|
|
1867
|
+
handled = field.handle_key ch unless field.nil? # no field focussable
|
|
1868
|
+
$log.debug "handled inside Form #{ch} from #{field} got #{handled} "
|
|
1869
|
+
# some widgets like textarea and list handle up and down
|
|
1870
|
+
if handled == :UNHANDLED or handled == -1 or field.nil?
|
|
1871
|
+
case ch
|
|
1872
|
+
when KEY_TAB, ?\M-\C-i.getbyte(0) # tab and M-tab in case widget eats tab (such as Table)
|
|
1873
|
+
ret = select_next_field
|
|
1874
|
+
return ret if ret == :NO_NEXT_FIELD
|
|
1875
|
+
# alt-shift-tab or backtab (in case Table eats backtab)
|
|
1876
|
+
when FFI::NCurses::KEY_BTAB, 481 ## backtab added 2008-12-14 18:41
|
|
1877
|
+
ret = select_prev_field
|
|
1878
|
+
return ret if ret == :NO_PREV_FIELD
|
|
1879
|
+
when FFI::NCurses::KEY_UP
|
|
1880
|
+
ret = select_prev_field
|
|
1881
|
+
return ret if ret == :NO_PREV_FIELD
|
|
1882
|
+
when FFI::NCurses::KEY_DOWN
|
|
1883
|
+
ret = select_next_field
|
|
1884
|
+
return ret if ret == :NO_NEXT_FIELD
|
|
1885
|
+
else
|
|
1886
|
+
#$log.debug " before calling process_key in form #{ch} " if $log.debug?
|
|
1887
|
+
ret = process_key ch, self
|
|
1888
|
+
$log.debug "FORM process_key #{ch} got ret #{ret} in #{self} "
|
|
1889
|
+
return :UNHANDLED if ret == :UNHANDLED
|
|
1890
|
+
end
|
|
1891
|
+
elsif handled == :NO_NEXT_FIELD || handled == :NO_PREV_FIELD # 2011-10-4
|
|
1892
|
+
return handled
|
|
1893
|
+
end
|
|
1894
|
+
end
|
|
1895
|
+
$log.debug " form before repaint #{self} , #{@name}, ret #{ret}"
|
|
1896
|
+
repaint
|
|
1897
|
+
$last_key = ch
|
|
1898
|
+
ret || 0 # 2011-10-17
|
|
1899
|
+
end
|
|
1900
|
+
##
|
|
1901
|
+
# test program to dump data onto log
|
|
1902
|
+
# The problem I face is that since widget array contains everything that should be displayed
|
|
1903
|
+
# I do not know what all the user wants - what are his data entry fields.
|
|
1904
|
+
# A user could have disabled entry on some field after modification, so i can't use focusable
|
|
1905
|
+
# or editable as filters. I just dump everything?
|
|
1906
|
+
# What's more, currently getvalue has been used by paint to return what needs to be displayed -
|
|
1907
|
+
# at least by label and button.
|
|
1908
|
+
def DEPRECATED_dump_data # CLEAN
|
|
1909
|
+
$log.debug "DEPRECATED DUMPING DATA "
|
|
1910
|
+
@widgets.each do |w|
|
|
1911
|
+
# we need checkbox and radio button values
|
|
1912
|
+
#next if w.is_a? RubyCurses::Button or w.is_a? RubyCurses::Label
|
|
1913
|
+
next if w.is_a? RubyCurses::Label
|
|
1914
|
+
next if !w.is_a? RubyCurses::Widget
|
|
1915
|
+
if w.respond_to? :getvalue
|
|
1916
|
+
$log.debug " #{w.name} #{w.getvalue}"
|
|
1917
|
+
else
|
|
1918
|
+
$log.debug " #{w.name} DOES NOT RESPOND TO getvalue"
|
|
1919
|
+
end
|
|
1920
|
+
end
|
|
1921
|
+
$log.debug " END DUMPING DATA "
|
|
1922
|
+
end
|
|
1923
|
+
##
|
|
1924
|
+
# trying out for splitpane and others who have a sub-form. TabbedPane uses
|
|
1925
|
+
def set_parent_buffer b
|
|
1926
|
+
@parent_buffer = b
|
|
1927
|
+
end
|
|
1928
|
+
# 2010-02-07 14:50 to aid in debugging and comparing log files.
|
|
1929
|
+
def to_s; @name || self; end
|
|
1930
|
+
|
|
1931
|
+
# NOTE: very experimental, use at risk, can change location or be deprec
|
|
1932
|
+
# place given widget below given one, or last added one
|
|
1933
|
+
# Does not check for availability or overlap
|
|
1934
|
+
def place_below me, other=nil
|
|
1935
|
+
$log.warn "WARN deprecated form place_below"
|
|
1936
|
+
w = widgets
|
|
1937
|
+
if other.nil?
|
|
1938
|
+
other = w[-1]
|
|
1939
|
+
# if user calls this after placing this field
|
|
1940
|
+
other = w[-2] if other == me
|
|
1941
|
+
end
|
|
1942
|
+
if other.height.nil? || other.height == 0
|
|
1943
|
+
h = 1
|
|
1944
|
+
else
|
|
1945
|
+
h = other.height
|
|
1946
|
+
end
|
|
1947
|
+
me.row = other.row + h
|
|
1948
|
+
me.col = other.col
|
|
1949
|
+
me
|
|
1950
|
+
end
|
|
1951
|
+
# NOTE: very experimental, use at risk, can change location or be deprec
|
|
1952
|
+
# return location to place next widget (below)
|
|
1953
|
+
# Does not check for availability or overlap
|
|
1954
|
+
def next_position
|
|
1955
|
+
$log.warn "WARN deprecated form next_position"
|
|
1956
|
+
w = widgets.last
|
|
1957
|
+
if w.height.nil? || w.height == 0
|
|
1958
|
+
h = 1
|
|
1959
|
+
else
|
|
1960
|
+
h = w.height
|
|
1961
|
+
end
|
|
1962
|
+
row = w.row + h
|
|
1963
|
+
col = w.col
|
|
1964
|
+
return row, col
|
|
1965
|
+
end
|
|
1966
|
+
#
|
|
1967
|
+
# returns in instance of help_manager with which one may install help_text and call help.
|
|
1968
|
+
# user apps will only supply help_text, form would already have mapped F1 to help.
|
|
1969
|
+
def help_manager
|
|
1970
|
+
@help_manager ||= HelpManager.new self
|
|
1971
|
+
end
|
|
1972
|
+
|
|
1973
|
+
## ADD HERE FORM
|
|
1974
|
+
end
|
|
1975
|
+
|
|
1976
|
+
|
|
1977
|
+
class HelpManager
|
|
1978
|
+
def initialize form, config={}, &block
|
|
1979
|
+
@form = form
|
|
1980
|
+
#super
|
|
1981
|
+
#instance_eval &block if block_given?
|
|
1982
|
+
end
|
|
1983
|
+
def help_text text=nil
|
|
1984
|
+
if text
|
|
1985
|
+
@help_text = text
|
|
1986
|
+
end
|
|
1987
|
+
return @help_text
|
|
1988
|
+
end
|
|
1989
|
+
def help_text=(text); help_text(text); end
|
|
1990
|
+
def display_help
|
|
1991
|
+
filename = File.dirname(__FILE__) + "/../docs/index.txt"
|
|
1992
|
+
# defarr contains default help
|
|
1993
|
+
if File.exists?(filename)
|
|
1994
|
+
defarr = File.open(filename,'r').readlines
|
|
1995
|
+
else
|
|
1996
|
+
arr = []
|
|
1997
|
+
arr << " NO HELP SPECIFIED FOR APP "
|
|
1998
|
+
arr << " "
|
|
1999
|
+
arr << " --- General help --- "
|
|
2000
|
+
arr << " F10 - exit application "
|
|
2001
|
+
arr << " Alt-x - select commands "
|
|
2002
|
+
arr << " : (or M-:) - select commands "
|
|
2003
|
+
arr << " ? (or M-?) - current widget key bindings "
|
|
2004
|
+
arr << " "
|
|
2005
|
+
defarr = arr
|
|
2006
|
+
end
|
|
2007
|
+
defhelp = true
|
|
2008
|
+
if @help_text
|
|
2009
|
+
defhelp = false
|
|
2010
|
+
arr = @help_text
|
|
2011
|
+
else
|
|
2012
|
+
arr = defarr
|
|
2013
|
+
end
|
|
2014
|
+
case arr
|
|
2015
|
+
when String
|
|
2016
|
+
arr = arr.split("\n")
|
|
2017
|
+
when Array
|
|
2018
|
+
end
|
|
2019
|
+
#w = arr.max_by(&:length).length
|
|
2020
|
+
h = FFI::NCurses.LINES - 4
|
|
2021
|
+
w = FFI::NCurses.COLS - 10
|
|
2022
|
+
|
|
2023
|
+
require 'rbhex/core/util/viewer'
|
|
2024
|
+
RubyCurses::Viewer.view(arr, :layout => [2, 4, h, w],:close_key => KEY_F10, :title => "[ Help ]", :print_footer => true) do |t|
|
|
2025
|
+
# you may configure textview further here.
|
|
2026
|
+
#t.suppress_borders true
|
|
2027
|
+
#t.color = :black
|
|
2028
|
+
#t.bgcolor = :white
|
|
2029
|
+
# or
|
|
2030
|
+
#t.attr = :reverse
|
|
2031
|
+
|
|
2032
|
+
# help was provided, so default help is provided in second buffer
|
|
2033
|
+
unless defhelp
|
|
2034
|
+
t.add_content defarr, :title => ' General Help '
|
|
2035
|
+
end
|
|
2036
|
+
end
|
|
2037
|
+
end
|
|
2038
|
+
end # class
|
|
2039
|
+
## Created and sent to all listeners whenever a property is changed
|
|
2040
|
+
# @see fire_property_change
|
|
2041
|
+
# @see fire_handler
|
|
2042
|
+
# @since 1.0.5 added 2010-02-25 23:06
|
|
2043
|
+
class PropertyChangeEvent
|
|
2044
|
+
attr_accessor :source, :property_name, :oldvalue, :newvalue
|
|
2045
|
+
def initialize source, property_name, oldvalue, newvalue
|
|
2046
|
+
set source, property_name, oldvalue, newvalue
|
|
2047
|
+
end
|
|
2048
|
+
def set source, property_name, oldvalue, newvalue
|
|
2049
|
+
@source, @property_name, @oldvalue, @newvalue =
|
|
2050
|
+
source, property_name, oldvalue, newvalue
|
|
2051
|
+
end
|
|
2052
|
+
def to_s
|
|
2053
|
+
"PROPERTY_CHANGE name: #{property_name}, oldval: #{@oldvalue}, newvalue: #{@newvalue}, source: #{@source}"
|
|
2054
|
+
end
|
|
2055
|
+
def inspect
|
|
2056
|
+
to_s
|
|
2057
|
+
end
|
|
2058
|
+
end
|
|
2059
|
+
|
|
2060
|
+
##
|
|
2061
|
+
# Text edit field
|
|
2062
|
+
# NOTE: To get value use getvalue()
|
|
2063
|
+
# TODO - test text_variable
|
|
2064
|
+
# TODO: some methods should return self, so chaining can be done. Not sure if the return value of the
|
|
2065
|
+
# fire_handler is being checked.
|
|
2066
|
+
# NOTE: i have just added repain_required check in Field before repaint
|
|
2067
|
+
# this may mean in some places field does not paint. repaint_require will have to be set
|
|
2068
|
+
# to true in those cases. this was since field was overriding a popup window that was not modal.
|
|
2069
|
+
#
|
|
2070
|
+
class Field < Widget
|
|
2071
|
+
dsl_accessor :maxlen # maximum length allowed into field
|
|
2072
|
+
attr_reader :buffer # actual buffer being used for storage
|
|
2073
|
+
#
|
|
2074
|
+
# this was unused earlier. Unlike set_label which creates a separate label
|
|
2075
|
+
# object, this stores a label and prints it before the string. This is less
|
|
2076
|
+
# customizable, however, in some cases when a field is attached to some container
|
|
2077
|
+
# the label gets left out. This labels is gauranteed to print to the left of the field
|
|
2078
|
+
#
|
|
2079
|
+
dsl_accessor :label # label of field Unused earlier, now will print
|
|
2080
|
+
dsl_property :label_color_pair # label of field Unused earlier, now will print
|
|
2081
|
+
dsl_property :label_attr # label of field Unused earlier, now will print
|
|
2082
|
+
#dsl_accessor :default # now alias of text 2011-11-5
|
|
2083
|
+
dsl_accessor :values # validate against provided list
|
|
2084
|
+
dsl_accessor :valid_regex # validate against regular expression
|
|
2085
|
+
dsl_accessor :valid_range # validate against numeric range # 2011-09-29 V1.3.1
|
|
2086
|
+
|
|
2087
|
+
dsl_accessor :chars_allowed # regex, what characters to allow, will ignore all else
|
|
2088
|
+
dsl_accessor :display_length # how much to display
|
|
2089
|
+
dsl_accessor :show # what charactr to show for each char entered (password field)
|
|
2090
|
+
dsl_accessor :null_allowed # allow nulls, don't validate if null # added 2008-12-22 12:38
|
|
2091
|
+
|
|
2092
|
+
# any new widget that has editable should have modified also
|
|
2093
|
+
dsl_accessor :editable # allow editing
|
|
2094
|
+
|
|
2095
|
+
attr_reader :form
|
|
2096
|
+
attr_reader :handler # event handler
|
|
2097
|
+
attr_reader :type # datatype of field, currently only sets chars_allowed
|
|
2098
|
+
#attr_reader :curpos # cursor position in buffer current, in WIDGET
|
|
2099
|
+
attr_accessor :datatype # crrently set during set_buffer
|
|
2100
|
+
attr_reader :original_value # value on entering field
|
|
2101
|
+
attr_accessor :overwrite_mode # true or false INSERT OVERWRITE MODE
|
|
2102
|
+
|
|
2103
|
+
attr_reader :field_col # column on which field is printed
|
|
2104
|
+
# required due to labels. Is updated after printing
|
|
2105
|
+
# # so can be nil if accessed early 2011-12-8
|
|
2106
|
+
# For consistency, now width equates to display_length
|
|
2107
|
+
alias :width :display_length
|
|
2108
|
+
alias :width= :display_length=
|
|
2109
|
+
|
|
2110
|
+
def initialize form=nil, config={}, &block
|
|
2111
|
+
@form = form
|
|
2112
|
+
@buffer = String.new
|
|
2113
|
+
#@type=config.fetch("type", :varchar)
|
|
2114
|
+
@row = 0
|
|
2115
|
+
@col = 0
|
|
2116
|
+
#@bgcolor = $def_bg_color
|
|
2117
|
+
#@color = $def_fg_color
|
|
2118
|
+
@editable = true
|
|
2119
|
+
@focusable = true
|
|
2120
|
+
@event_args = {} # arguments passed at time of binding, to use when firing event
|
|
2121
|
+
map_keys
|
|
2122
|
+
init_vars
|
|
2123
|
+
@_events ||= []
|
|
2124
|
+
@_events.push(:CHANGE)
|
|
2125
|
+
super
|
|
2126
|
+
@display_length ||= 20
|
|
2127
|
+
@maxlen ||= @display_length
|
|
2128
|
+
end
|
|
2129
|
+
def init_vars
|
|
2130
|
+
@pcol = 0 # needed for horiz scrolling
|
|
2131
|
+
@curpos = 0 # current cursor position in buffer
|
|
2132
|
+
# this is the index where characters are put or deleted
|
|
2133
|
+
# # when user edits
|
|
2134
|
+
@modified = false
|
|
2135
|
+
@repaint_required = true
|
|
2136
|
+
end
|
|
2137
|
+
|
|
2138
|
+
#
|
|
2139
|
+
# Set Variable as value.
|
|
2140
|
+
# This allows using Field as a proxy
|
|
2141
|
+
# @param [Variable] variable containing text value
|
|
2142
|
+
#
|
|
2143
|
+
def text_variable tv
|
|
2144
|
+
@text_variable = tv
|
|
2145
|
+
set_buffer tv.value
|
|
2146
|
+
end
|
|
2147
|
+
##
|
|
2148
|
+
# define a datatype, currently only influences chars allowed
|
|
2149
|
+
# integer and float. what about allowing a minus sign?
|
|
2150
|
+
def type dtype
|
|
2151
|
+
return if @chars_allowed # disallow changing
|
|
2152
|
+
dtype = dtype.to_s.downcase.to_sym if dtype.is_a? String
|
|
2153
|
+
case dtype # missing to_sym would have always failed due to to_s 2011-09-30 1.3.1
|
|
2154
|
+
when :integer
|
|
2155
|
+
@chars_allowed = /\d/
|
|
2156
|
+
when :numeric, :float
|
|
2157
|
+
@chars_allowed = /[\d\.]/
|
|
2158
|
+
when :alpha
|
|
2159
|
+
@chars_allowed = /[a-zA-Z]/
|
|
2160
|
+
when :alnum
|
|
2161
|
+
@chars_allowed = /[a-zA-Z0-9]/
|
|
2162
|
+
else
|
|
2163
|
+
raise ArgumentError, "Field type: invalid datatype specified. Use :integer, :numeric, :float, :alpha, :alnum "
|
|
2164
|
+
end
|
|
2165
|
+
end
|
|
2166
|
+
|
|
2167
|
+
#
|
|
2168
|
+
# add a char to field, and validate
|
|
2169
|
+
# NOTE: this should return self for chaining operations and throw an exception
|
|
2170
|
+
# if disabled or exceeding size
|
|
2171
|
+
# @param [char] a character to add
|
|
2172
|
+
# @return [Fixnum] 0 if okay, -1 if not editable or exceeding length
|
|
2173
|
+
def putch char
|
|
2174
|
+
return -1 if !@editable
|
|
2175
|
+
return -1 if !@overwrite_mode && (@buffer.length >= @maxlen)
|
|
2176
|
+
if @chars_allowed != nil
|
|
2177
|
+
return if char.match(@chars_allowed).nil?
|
|
2178
|
+
end
|
|
2179
|
+
# added insert or overwrite mode 2010-03-17 20:11
|
|
2180
|
+
oldchar = nil
|
|
2181
|
+
if @overwrite_mode
|
|
2182
|
+
oldchar = @buffer[@curpos]
|
|
2183
|
+
@buffer[@curpos] = char
|
|
2184
|
+
else
|
|
2185
|
+
@buffer.insert(@curpos, char)
|
|
2186
|
+
end
|
|
2187
|
+
oldcurpos = @curpos
|
|
2188
|
+
$log.warn "XXX: FIELD CURPOS #{@curpos} "
|
|
2189
|
+
@curpos += 1 if @curpos < @maxlen
|
|
2190
|
+
@modified = true
|
|
2191
|
+
#$log.debug " FIELD FIRING CHANGE: #{char} at new #{@curpos}: bl:#{@buffer.length} buff:[#{@buffer}]"
|
|
2192
|
+
# i have no way of knowing what change happened and what char was added deleted or changed
|
|
2193
|
+
#fire_handler :CHANGE, self # 2008-12-09 14:51
|
|
2194
|
+
if @overwrite_mode
|
|
2195
|
+
fire_handler :CHANGE, InputDataEvent.new(oldcurpos,@curpos, self, :DELETE, 0, oldchar) # 2010-09-11 12:43
|
|
2196
|
+
end
|
|
2197
|
+
fire_handler :CHANGE, InputDataEvent.new(oldcurpos,@curpos, self, :INSERT, 0, char) # 2010-09-11 12:43
|
|
2198
|
+
0
|
|
2199
|
+
end
|
|
2200
|
+
|
|
2201
|
+
##
|
|
2202
|
+
# TODO : sending c>=0 allows control chars to go. Should be >= ?A i think.
|
|
2203
|
+
def putc c
|
|
2204
|
+
if c >= 0 and c <= 127
|
|
2205
|
+
ret = putch c.chr
|
|
2206
|
+
if ret == 0
|
|
2207
|
+
if addcol(1) == -1 # if can't go forward, try scrolling
|
|
2208
|
+
# scroll if exceeding display len but less than max len
|
|
2209
|
+
if @curpos > @display_length && @curpos <= @maxlen
|
|
2210
|
+
@pcol += 1 if @pcol < @display_length
|
|
2211
|
+
end
|
|
2212
|
+
end
|
|
2213
|
+
set_modified
|
|
2214
|
+
return 0 # 2010-09-11 12:59 else would always return -1
|
|
2215
|
+
end
|
|
2216
|
+
end
|
|
2217
|
+
return -1
|
|
2218
|
+
end
|
|
2219
|
+
def delete_at index=@curpos
|
|
2220
|
+
return -1 if !@editable
|
|
2221
|
+
char = @buffer.slice!(index,1)
|
|
2222
|
+
#$log.debug " delete at #{index}: #{@buffer.length}: #{@buffer}"
|
|
2223
|
+
@modified = true
|
|
2224
|
+
#fire_handler :CHANGE, self # 2008-12-09 14:51
|
|
2225
|
+
fire_handler :CHANGE, InputDataEvent.new(@curpos,@curpos, self, :DELETE, 0, char) # 2010-09-11 13:01
|
|
2226
|
+
end
|
|
2227
|
+
#
|
|
2228
|
+
# silently restores value without firing handlers, use if exception and you want old value
|
|
2229
|
+
# @since 1.4.0 2011-10-2
|
|
2230
|
+
def restore_original_value
|
|
2231
|
+
@buffer = @original_value.dup
|
|
2232
|
+
#@curpos = 0 # this would require restting setformcol
|
|
2233
|
+
@repaint_required = true
|
|
2234
|
+
end
|
|
2235
|
+
##
|
|
2236
|
+
# should this do a dup ?? YES
|
|
2237
|
+
# set value of Field
|
|
2238
|
+
# fires CHANGE handler
|
|
2239
|
+
def set_buffer value
|
|
2240
|
+
@repaint_required = true
|
|
2241
|
+
@datatype = value.class
|
|
2242
|
+
#$log.debug " FIELD DATA #{@datatype}"
|
|
2243
|
+
@delete_buffer = @buffer.dup
|
|
2244
|
+
@buffer = value.to_s.dup
|
|
2245
|
+
# don't allow setting of value greater than maxlen
|
|
2246
|
+
@buffer = @buffer[0,@maxlen] if @maxlen && @buffer.length > @maxlen
|
|
2247
|
+
@curpos = 0
|
|
2248
|
+
# hope @delete_buffer is not overwritten
|
|
2249
|
+
fire_handler :CHANGE, InputDataEvent.new(@curpos,@curpos, self, :DELETE, 0, @delete_buffer) # 2010-09-11 13:01
|
|
2250
|
+
fire_handler :CHANGE, InputDataEvent.new(@curpos,@curpos, self, :INSERT, 0, @buffer) # 2010-09-11 13:01
|
|
2251
|
+
self # 2011-10-2
|
|
2252
|
+
end
|
|
2253
|
+
# converts back into original type
|
|
2254
|
+
# changed to convert on 2009-01-06 23:39
|
|
2255
|
+
def getvalue
|
|
2256
|
+
dt = @datatype || String
|
|
2257
|
+
case dt.to_s
|
|
2258
|
+
when "String"
|
|
2259
|
+
return @buffer
|
|
2260
|
+
when "Fixnum"
|
|
2261
|
+
return @buffer.to_i
|
|
2262
|
+
when "Float"
|
|
2263
|
+
return @buffer.to_f
|
|
2264
|
+
else
|
|
2265
|
+
return @buffer.to_s
|
|
2266
|
+
end
|
|
2267
|
+
end
|
|
2268
|
+
|
|
2269
|
+
# create a label linked to this field
|
|
2270
|
+
# Typically one passes a Label, but now we can pass just a String, a label
|
|
2271
|
+
# is created
|
|
2272
|
+
# NOTE: 2011-10-20 when field attached to some container, label won't be attached
|
|
2273
|
+
# @param [Label, String] label object to be associated with this field
|
|
2274
|
+
# FIXME this may not work since i have disabled -1, now i do not set row and col 2011-11-5
|
|
2275
|
+
def set_label label
|
|
2276
|
+
# added case for user just using a string
|
|
2277
|
+
case label
|
|
2278
|
+
when String
|
|
2279
|
+
# what if no form at this point
|
|
2280
|
+
@label_unattached = true unless @form
|
|
2281
|
+
label = Label.new @form, {:text => label}
|
|
2282
|
+
end
|
|
2283
|
+
@label = label
|
|
2284
|
+
# in the case of app it won't be set yet FIXME
|
|
2285
|
+
# So app sets label to 0 and t his won't trigger
|
|
2286
|
+
# can this be delayed to when paint happens XXX
|
|
2287
|
+
if @row
|
|
2288
|
+
position_label
|
|
2289
|
+
else
|
|
2290
|
+
@label_unplaced = true
|
|
2291
|
+
end
|
|
2292
|
+
label
|
|
2293
|
+
end
|
|
2294
|
+
# FIXME this may not work since i have disabled -1, now i do not set row and col
|
|
2295
|
+
def position_label
|
|
2296
|
+
$log.debug "XXX: LABEL row #{@label.row}, #{@label.col} "
|
|
2297
|
+
@label.row @row unless @label.row #if @label.row == -1
|
|
2298
|
+
@label.col @col-(@label.name.length+1) unless @label.col #if @label.col == -1
|
|
2299
|
+
@label.label_for(self) # this line got deleted when we redid stuff !
|
|
2300
|
+
$log.debug " XXX: LABEL row #{@label.row}, #{@label.col} "
|
|
2301
|
+
end
|
|
2302
|
+
|
|
2303
|
+
## Note that some older widgets like Field repaint every time the form.repaint
|
|
2304
|
+
##+ is called, whether updated or not. I can't remember why this is, but
|
|
2305
|
+
##+ currently I've not implemented events with these widgets. 2010-01-03 15:00
|
|
2306
|
+
|
|
2307
|
+
def repaint
|
|
2308
|
+
return unless @repaint_required # 2010-11-20 13:13 its writing over a window i think TESTING
|
|
2309
|
+
if @label_unattached
|
|
2310
|
+
alert "came here unattachd"
|
|
2311
|
+
@label.set_form(@form)
|
|
2312
|
+
end
|
|
2313
|
+
if @label_unplaced
|
|
2314
|
+
alert "came here unplaced"
|
|
2315
|
+
position_label
|
|
2316
|
+
end
|
|
2317
|
+
@bgcolor ||= $def_bg_color
|
|
2318
|
+
@color ||= $def_fg_color
|
|
2319
|
+
$log.debug("repaint FIELD: #{id}, #{name}, #{row} #{col},pcol:#{@pcol}, #{focusable} st: #{@state} ")
|
|
2320
|
+
#return if display_length <= 0 # added 2009-02-17 00:17 sometimes editor comp has 0 and that
|
|
2321
|
+
# becomes negative below, no because editing still happens
|
|
2322
|
+
@display_length = 1 if display_length == 0
|
|
2323
|
+
printval = getvalue_for_paint().to_s # added 2009-01-06 23:27
|
|
2324
|
+
printval = show()*printval.length unless @show.nil?
|
|
2325
|
+
if !printval.nil?
|
|
2326
|
+
if printval.length > display_length # only show maxlen
|
|
2327
|
+
printval = printval[@pcol..@pcol+display_length-1]
|
|
2328
|
+
else
|
|
2329
|
+
printval = printval[@pcol..-1]
|
|
2330
|
+
end
|
|
2331
|
+
end
|
|
2332
|
+
|
|
2333
|
+
acolor = @color_pair || get_color($datacolor, @color, @bgcolor)
|
|
2334
|
+
if @state == :HIGHLIGHTED
|
|
2335
|
+
_bgcolor = @highlight_background || @bgcolor
|
|
2336
|
+
_color = @highlight_foreground || @color
|
|
2337
|
+
acolor = get_color(acolor, _color, _bgcolor)
|
|
2338
|
+
end
|
|
2339
|
+
@graphic = @form.window if @graphic.nil? ## cell editor listbox hack
|
|
2340
|
+
#$log.debug " Field g:#{@graphic}. r,c,displen:#{@row}, #{@col}, #{@display_length} c:#{@color} bg:#{@bgcolor} a:#{@attr} :#{@name} "
|
|
2341
|
+
r = row
|
|
2342
|
+
c = col
|
|
2343
|
+
if label.is_a? String
|
|
2344
|
+
lcolor = @label_color_pair || $datacolor # this should be the same color as window bg XXX
|
|
2345
|
+
lattr = @label_attr || NORMAL
|
|
2346
|
+
@graphic.printstring row, col, label, lcolor, lattr
|
|
2347
|
+
c += label.length + 2
|
|
2348
|
+
@col_offset = c-@col # required so cursor lands in right place
|
|
2349
|
+
end
|
|
2350
|
+
@graphic.printstring r, c, sprintf("%-*s", display_length, printval), acolor, @attr
|
|
2351
|
+
@field_col = c
|
|
2352
|
+
@repaint_required = false
|
|
2353
|
+
end
|
|
2354
|
+
def set_focusable(tf)
|
|
2355
|
+
@focusable = tf
|
|
2356
|
+
end
|
|
2357
|
+
def map_keys
|
|
2358
|
+
return if @keys_mapped
|
|
2359
|
+
bind_key(FFI::NCurses::KEY_LEFT, :cursor_backward )
|
|
2360
|
+
bind_key(FFI::NCurses::KEY_RIGHT, :cursor_forward )
|
|
2361
|
+
bind_key(FFI::NCurses::KEY_BACKSPACE, :delete_prev_char )
|
|
2362
|
+
bind_key(127, :delete_prev_char )
|
|
2363
|
+
bind_key(330, :delete_curr_char )
|
|
2364
|
+
bind_key(?\C-a, :cursor_home )
|
|
2365
|
+
bind_key(?\C-e, :cursor_end )
|
|
2366
|
+
bind_key(?\C-k, :delete_eol )
|
|
2367
|
+
bind_key(?\C-_, :undo_delete_eol )
|
|
2368
|
+
#bind_key(27){ set_buffer @original_value }
|
|
2369
|
+
bind_key(?\C-g, 'revert'){ set_buffer @original_value } # 2011-09-29 V1.3.1 ESC did not work
|
|
2370
|
+
@keys_mapped = true
|
|
2371
|
+
end
|
|
2372
|
+
|
|
2373
|
+
# field
|
|
2374
|
+
#
|
|
2375
|
+
def handle_key ch
|
|
2376
|
+
@repaint_required = true
|
|
2377
|
+
#map_keys unless @keys_mapped # moved to init
|
|
2378
|
+
case ch
|
|
2379
|
+
when 32..126
|
|
2380
|
+
#$log.debug("FIELD: ch #{ch} ,at #{@curpos}, buffer:[#{@buffer}] bl: #{@buffer.to_s.length}")
|
|
2381
|
+
putc ch
|
|
2382
|
+
when 27 # cannot bind it
|
|
2383
|
+
set_buffer @original_value
|
|
2384
|
+
else
|
|
2385
|
+
ret = super
|
|
2386
|
+
return ret
|
|
2387
|
+
end
|
|
2388
|
+
0 # 2008-12-16 23:05 without this -1 was going back so no repaint
|
|
2389
|
+
end
|
|
2390
|
+
# does an undo on delete_eol, not a real undo
|
|
2391
|
+
def undo_delete_eol
|
|
2392
|
+
return if @delete_buffer.nil?
|
|
2393
|
+
#oldvalue = @buffer
|
|
2394
|
+
@buffer.insert @curpos, @delete_buffer
|
|
2395
|
+
fire_handler :CHANGE, InputDataEvent.new(@curpos,@curpos+@delete_buffer.length, self, :INSERT, 0, @delete_buffer) # 2010-09-11 13:01
|
|
2396
|
+
end
|
|
2397
|
+
##
|
|
2398
|
+
# position cursor at start of field
|
|
2399
|
+
def cursor_home
|
|
2400
|
+
@curpos = 0
|
|
2401
|
+
@pcol = 0
|
|
2402
|
+
set_form_col 0
|
|
2403
|
+
end
|
|
2404
|
+
##
|
|
2405
|
+
# goto end of field, "end" is a keyword so could not use it.
|
|
2406
|
+
def cursor_end
|
|
2407
|
+
blen = @buffer.rstrip.length
|
|
2408
|
+
if blen < @display_length
|
|
2409
|
+
set_form_col blen
|
|
2410
|
+
else
|
|
2411
|
+
# there is a problem here FIXME.
|
|
2412
|
+
@pcol = blen-@display_length
|
|
2413
|
+
#set_form_col @display_length-1
|
|
2414
|
+
set_form_col blen
|
|
2415
|
+
end
|
|
2416
|
+
@curpos = blen # this is position in array where editing or motion is to happen regardless of what you see
|
|
2417
|
+
# regardless of pcol (panning)
|
|
2418
|
+
# $log.debug " crusor END cp:#{@curpos} pcol:#{@pcol} b.l:#{@buffer.length} d_l:#{@display_length} fc:#{@form.col}"
|
|
2419
|
+
#set_form_col @buffer.length
|
|
2420
|
+
end
|
|
2421
|
+
# sets the visual cursor on the window at correct place
|
|
2422
|
+
# added here since we need to account for pcol. 2011-12-7
|
|
2423
|
+
# NOTE be careful of curpos - pcol being less than 0
|
|
2424
|
+
def set_form_col col1=@curpos
|
|
2425
|
+
@curpos = col1 || 0 # NOTE we set the index of cursor here
|
|
2426
|
+
c = @col + @col_offset + @curpos - @pcol
|
|
2427
|
+
min = @col + @col_offset
|
|
2428
|
+
max = min + @display_length
|
|
2429
|
+
c = min if c < min
|
|
2430
|
+
c = max if c > max
|
|
2431
|
+
$log.debug " #{@name} FIELD set_form_col #{c}, curpos #{@curpos} , #{@col} + #{@col_offset} pcol:#{@pcol} "
|
|
2432
|
+
setrowcol nil, c
|
|
2433
|
+
end
|
|
2434
|
+
def delete_eol
|
|
2435
|
+
return -1 unless @editable
|
|
2436
|
+
pos = @curpos-1
|
|
2437
|
+
@delete_buffer = @buffer[@curpos..-1]
|
|
2438
|
+
# if pos is 0, pos-1 becomes -1, end of line!
|
|
2439
|
+
@buffer = pos == -1 ? "" : @buffer[0..pos]
|
|
2440
|
+
#fire_handler :CHANGE, self # 2008-12-09 14:51
|
|
2441
|
+
fire_handler :CHANGE, InputDataEvent.new(@curpos,@curpos+@delete_buffer.length, self, :DELETE, 0, @delete_buffer) # 2010-09-11 13:01
|
|
2442
|
+
return @delete_buffer
|
|
2443
|
+
end
|
|
2444
|
+
def cursor_forward
|
|
2445
|
+
if @curpos < @buffer.length
|
|
2446
|
+
if addcol(1)==-1 # go forward if you can, else scroll
|
|
2447
|
+
@pcol += 1 if @pcol < @display_length
|
|
2448
|
+
end
|
|
2449
|
+
@curpos += 1
|
|
2450
|
+
end
|
|
2451
|
+
# $log.debug " crusor FORWARD cp:#{@curpos} pcol:#{@pcol} b.l:#{@buffer.length} d_l:#{@display_length} fc:#{@form.col}"
|
|
2452
|
+
end
|
|
2453
|
+
def cursor_backward
|
|
2454
|
+
if @curpos > 0
|
|
2455
|
+
@curpos -= 1
|
|
2456
|
+
if @pcol > 0 and @form.col == @col + @col_offset
|
|
2457
|
+
@pcol -= 1
|
|
2458
|
+
end
|
|
2459
|
+
addcol -1
|
|
2460
|
+
elsif @pcol > 0 # added 2008-11-26 23:05
|
|
2461
|
+
@pcol -= 1
|
|
2462
|
+
end
|
|
2463
|
+
# $log.debug " crusor back cp:#{@curpos} pcol:#{@pcol} b.l:#{@buffer.length} d_l:#{@display_length} fc:#{@form.col}"
|
|
2464
|
+
=begin
|
|
2465
|
+
# this is perfect if not scrolling, but now needs changes
|
|
2466
|
+
if @curpos > 0
|
|
2467
|
+
@curpos -= 1
|
|
2468
|
+
addcol -1
|
|
2469
|
+
end
|
|
2470
|
+
=end
|
|
2471
|
+
end
|
|
2472
|
+
def delete_curr_char
|
|
2473
|
+
return -1 unless @editable
|
|
2474
|
+
delete_at
|
|
2475
|
+
set_modified
|
|
2476
|
+
end
|
|
2477
|
+
def delete_prev_char
|
|
2478
|
+
return -1 if !@editable
|
|
2479
|
+
return if @curpos <= 0
|
|
2480
|
+
# if we've panned, then unpan, and don't move cursor back
|
|
2481
|
+
# Otherwise, adjust cursor (move cursor back as we delete)
|
|
2482
|
+
adjust = true
|
|
2483
|
+
if @pcol > 0
|
|
2484
|
+
@pcol -= 1
|
|
2485
|
+
adjust = false
|
|
2486
|
+
end
|
|
2487
|
+
@curpos -= 1 if @curpos > 0
|
|
2488
|
+
delete_at
|
|
2489
|
+
addcol -1 if adjust # move visual cursor back
|
|
2490
|
+
set_modified
|
|
2491
|
+
end
|
|
2492
|
+
## add a column to cursor position. Field
|
|
2493
|
+
def addcol num
|
|
2494
|
+
if num < 0
|
|
2495
|
+
if @form.col <= @col + @col_offset
|
|
2496
|
+
# $log.debug " error trying to cursor back #{@form.col}"
|
|
2497
|
+
return -1
|
|
2498
|
+
end
|
|
2499
|
+
elsif num > 0
|
|
2500
|
+
if @form.col >= @col + @col_offset + @display_length
|
|
2501
|
+
# $log.debug " error trying to cursor forward #{@form.col}"
|
|
2502
|
+
return -1
|
|
2503
|
+
end
|
|
2504
|
+
end
|
|
2505
|
+
@form.addcol num
|
|
2506
|
+
end
|
|
2507
|
+
# upon leaving a field
|
|
2508
|
+
# returns false if value not valid as per values or valid_regex
|
|
2509
|
+
# 2008-12-22 12:40 if null_allowed, don't validate, but do fire_handlers
|
|
2510
|
+
def on_leave
|
|
2511
|
+
val = getvalue
|
|
2512
|
+
#$log.debug " FIELD ON LEAVE:#{val}. #{@values.inspect}"
|
|
2513
|
+
valid = true
|
|
2514
|
+
if val.to_s.empty? && @null_allowed
|
|
2515
|
+
#$log.debug " empty and null allowed"
|
|
2516
|
+
else
|
|
2517
|
+
if !@values.nil?
|
|
2518
|
+
valid = @values.include? val
|
|
2519
|
+
raise FieldValidationException, "Field value (#{val}) not in values: #{@values.join(',')}" unless valid
|
|
2520
|
+
end
|
|
2521
|
+
if !@valid_regex.nil?
|
|
2522
|
+
valid = @valid_regex.match(val.to_s)
|
|
2523
|
+
raise FieldValidationException, "Field not matching regex #{@valid_regex}" unless valid
|
|
2524
|
+
end
|
|
2525
|
+
# added valid_range for numerics 2011-09-29
|
|
2526
|
+
if !@valid_range.nil?
|
|
2527
|
+
valid = @valid_range.include?(val.to_i)
|
|
2528
|
+
raise FieldValidationException, "Field not matching range #{@valid_range}" unless valid
|
|
2529
|
+
end
|
|
2530
|
+
end
|
|
2531
|
+
# here is where we should set the forms modified to true - 2009-01
|
|
2532
|
+
if modified?
|
|
2533
|
+
set_modified true
|
|
2534
|
+
end
|
|
2535
|
+
# if super fails we would have still set modified to true
|
|
2536
|
+
super
|
|
2537
|
+
#return valid
|
|
2538
|
+
end
|
|
2539
|
+
## save original value on enter, so we can check for modified.
|
|
2540
|
+
# 2009-01-18 12:25
|
|
2541
|
+
# 2011-10-9 I have changed to take @buffer since getvalue returns a datatype
|
|
2542
|
+
# and this causes a crash in set_original on cursor forward.
|
|
2543
|
+
def on_enter
|
|
2544
|
+
#@original_value = getvalue.dup rescue getvalue
|
|
2545
|
+
@original_value = @buffer.dup # getvalue.dup rescue getvalue
|
|
2546
|
+
super
|
|
2547
|
+
end
|
|
2548
|
+
##
|
|
2549
|
+
# overriding widget, check for value change
|
|
2550
|
+
# 2009-01-18 12:25
|
|
2551
|
+
def modified?
|
|
2552
|
+
getvalue() != @original_value
|
|
2553
|
+
end
|
|
2554
|
+
#
|
|
2555
|
+
# Use this to set a default text to the field. This does not imply that if the field is left
|
|
2556
|
+
# blank, this value will be used. It only provides this value for editing when field is shown.
|
|
2557
|
+
# @since 1.2.0
|
|
2558
|
+
def text(*val)
|
|
2559
|
+
if val.empty?
|
|
2560
|
+
return getvalue()
|
|
2561
|
+
else
|
|
2562
|
+
return unless val # added 2010-11-17 20:11, dup will fail on nil
|
|
2563
|
+
return unless val[0]
|
|
2564
|
+
# 2013-04-20 - 19:02 dup failing on fixnum, set_buffer does a dup
|
|
2565
|
+
# so maybe i can do without it here
|
|
2566
|
+
#s = val[0].dup
|
|
2567
|
+
s = val[0]
|
|
2568
|
+
set_buffer(s)
|
|
2569
|
+
end
|
|
2570
|
+
end
|
|
2571
|
+
alias :default :text
|
|
2572
|
+
def text=(val)
|
|
2573
|
+
return unless val # added 2010-11-17 20:11, dup will fail on nil
|
|
2574
|
+
set_buffer(val.dup)
|
|
2575
|
+
end
|
|
2576
|
+
# ADD HERE FIELD
|
|
2577
|
+
end
|
|
2578
|
+
|
|
2579
|
+
##
|
|
2580
|
+
# Like Tk's TkVariable, a simple proxy that can be passed to a widget. The widget
|
|
2581
|
+
# will update the Variable. A variable can be used to link a field with a label or
|
|
2582
|
+
# some other widget.
|
|
2583
|
+
# This is the new version of Variable. Deleting old version on 2009-01-17 12:04
|
|
2584
|
+
|
|
2585
|
+
class Variable
|
|
2586
|
+
|
|
2587
|
+
def initialize value=""
|
|
2588
|
+
@update_command = []
|
|
2589
|
+
@args = []
|
|
2590
|
+
@value = value
|
|
2591
|
+
@klass = value.class.to_s
|
|
2592
|
+
end
|
|
2593
|
+
|
|
2594
|
+
##
|
|
2595
|
+
# This is to ensure that change handlers for all dependent objects are called
|
|
2596
|
+
# so they are updated. This is called from text_variable property of some widgets. If you
|
|
2597
|
+
# use one text_variable across objects, all will be updated auto. User does not need to call.
|
|
2598
|
+
# @ private
|
|
2599
|
+
def add_dependent obj
|
|
2600
|
+
$log.debug " ADDING DEPENDE #{obj}"
|
|
2601
|
+
@dependents ||= []
|
|
2602
|
+
@dependents << obj
|
|
2603
|
+
end
|
|
2604
|
+
##
|
|
2605
|
+
# install trigger to call whenever a value is updated
|
|
2606
|
+
# @public called by user components
|
|
2607
|
+
def update_command *args, &block
|
|
2608
|
+
$log.debug "Variable: update command set " # #{args}"
|
|
2609
|
+
@update_command << block
|
|
2610
|
+
@args << args
|
|
2611
|
+
end
|
|
2612
|
+
alias :command :update_command
|
|
2613
|
+
##
|
|
2614
|
+
# value of the variable
|
|
2615
|
+
def get_value val=nil
|
|
2616
|
+
if @klass == 'String'
|
|
2617
|
+
return @value
|
|
2618
|
+
elsif @klass == 'Hash'
|
|
2619
|
+
return @value[val]
|
|
2620
|
+
elsif @klass == 'Array'
|
|
2621
|
+
return @value[val]
|
|
2622
|
+
else
|
|
2623
|
+
return @value
|
|
2624
|
+
end
|
|
2625
|
+
end
|
|
2626
|
+
##
|
|
2627
|
+
# update the value of this variable.
|
|
2628
|
+
# 2008-12-31 18:35 Added source so one can identify multiple sources that are updating.
|
|
2629
|
+
# Idea is that mutiple fields (e.g. checkboxes) can share one var and update a hash through it.
|
|
2630
|
+
# Source would contain some code or key relatin to each field.
|
|
2631
|
+
def set_value val, key=""
|
|
2632
|
+
oldval = @value
|
|
2633
|
+
if @klass == 'String'
|
|
2634
|
+
@value = val
|
|
2635
|
+
elsif @klass == 'Hash'
|
|
2636
|
+
$log.debug " Variable setting hash #{key} to #{val}"
|
|
2637
|
+
oldval = @value[key]
|
|
2638
|
+
@value[key]=val
|
|
2639
|
+
elsif @klass == 'Array'
|
|
2640
|
+
$log.debug " Variable setting array #{key} to #{val}"
|
|
2641
|
+
oldval = @value[key]
|
|
2642
|
+
@value[key]=val
|
|
2643
|
+
else
|
|
2644
|
+
oldval = @value
|
|
2645
|
+
@value = val
|
|
2646
|
+
end
|
|
2647
|
+
return if @update_command.nil?
|
|
2648
|
+
@update_command.each_with_index do |comm, ix|
|
|
2649
|
+
comm.call(self, *@args[ix]) unless comm.nil?
|
|
2650
|
+
end
|
|
2651
|
+
@dependents.each {|d| d.fire_property_change(d, oldval, val) } unless @dependents.nil?
|
|
2652
|
+
end
|
|
2653
|
+
##
|
|
2654
|
+
def value= (val)
|
|
2655
|
+
raise "Please use set_value for hash/array" if @klass=='Hash' or @klass=='Array'
|
|
2656
|
+
oldval = @value
|
|
2657
|
+
@value=val
|
|
2658
|
+
return if @update_command.nil?
|
|
2659
|
+
@update_command.each_with_index do |comm, ix|
|
|
2660
|
+
comm.call(self, *@args[ix]) unless comm.nil?
|
|
2661
|
+
end
|
|
2662
|
+
@dependents.each {|d| d.fire_property_change(d, oldval, val) } unless @dependents.nil?
|
|
2663
|
+
end
|
|
2664
|
+
def value
|
|
2665
|
+
raise "Please use set_value for hash/array: #{@klass}" if @klass=='Hash' #or @klass=='Array'
|
|
2666
|
+
@value
|
|
2667
|
+
end
|
|
2668
|
+
def inspect
|
|
2669
|
+
@value.inspect
|
|
2670
|
+
end
|
|
2671
|
+
def [](key)
|
|
2672
|
+
@value[key]
|
|
2673
|
+
end
|
|
2674
|
+
##
|
|
2675
|
+
# in order to run some method we don't yet support
|
|
2676
|
+
def source
|
|
2677
|
+
@value
|
|
2678
|
+
end
|
|
2679
|
+
def to_s
|
|
2680
|
+
inspect
|
|
2681
|
+
end
|
|
2682
|
+
end
|
|
2683
|
+
##
|
|
2684
|
+
# The preferred way of printing text on screen, esp if you want to modify it at run time.
|
|
2685
|
+
# Use display_length to ensure no spillage.
|
|
2686
|
+
# This can use text or text_variable for setting and getting data (inh from Widget).
|
|
2687
|
+
# 2011-11-12 making it simpler, and single line only. The original multiline label
|
|
2688
|
+
# has moved to extras/multilinelabel.rb
|
|
2689
|
+
#
|
|
2690
|
+
class Label < Widget
|
|
2691
|
+
dsl_accessor :mnemonic # keyboard focus is passed to buddy based on this key (ALT mask)
|
|
2692
|
+
|
|
2693
|
+
# justify required a display length, esp if center.
|
|
2694
|
+
dsl_property :justify #:right, :left, :center
|
|
2695
|
+
dsl_property :display_length #please give this to ensure the we only print this much
|
|
2696
|
+
#dsl_property :height #if you want a multiline label. already added to widget
|
|
2697
|
+
# for consistency with others 2011-11-5
|
|
2698
|
+
alias :width :display_length
|
|
2699
|
+
alias :width= :display_length=
|
|
2700
|
+
|
|
2701
|
+
def initialize form, config={}, &block
|
|
2702
|
+
|
|
2703
|
+
# this crap was used in position_label, find another way. where is it used ?
|
|
2704
|
+
#@row = config.fetch("row",-1) # why on earth this monstrosity ? 2011-11-5
|
|
2705
|
+
#@col = config.fetch("col",-1)
|
|
2706
|
+
#@bgcolor = config.fetch("bgcolor", $def_bg_color)
|
|
2707
|
+
#@color = config.fetch("color", $def_fg_color)
|
|
2708
|
+
@text = config.fetch(:text, "NOTFOUND")
|
|
2709
|
+
@editable = false
|
|
2710
|
+
@focusable = false
|
|
2711
|
+
super
|
|
2712
|
+
@justify ||= :left
|
|
2713
|
+
@name ||= @text
|
|
2714
|
+
@repaint_required = true
|
|
2715
|
+
end
|
|
2716
|
+
#
|
|
2717
|
+
# get the value for the label
|
|
2718
|
+
def getvalue
|
|
2719
|
+
@text_variable && @text_variable.value || @text
|
|
2720
|
+
end
|
|
2721
|
+
def label_for field
|
|
2722
|
+
@label_for = field
|
|
2723
|
+
#$log.debug " label for: #{@label_for}"
|
|
2724
|
+
if @form
|
|
2725
|
+
bind_hotkey
|
|
2726
|
+
else
|
|
2727
|
+
@when_form ||= []
|
|
2728
|
+
@when_form << lambda { bind_hotkey }
|
|
2729
|
+
end
|
|
2730
|
+
end
|
|
2731
|
+
|
|
2732
|
+
##
|
|
2733
|
+
# for a button, fire it when label invoked without changing focus
|
|
2734
|
+
# for other widgets, attempt to change focus to that field
|
|
2735
|
+
def bind_hotkey
|
|
2736
|
+
if @mnemonic
|
|
2737
|
+
ch = @mnemonic.downcase()[0].ord ## 1.9 DONE
|
|
2738
|
+
# meta key
|
|
2739
|
+
mch = ?\M-a.getbyte(0) + (ch - ?a.getbyte(0)) ## 1.9
|
|
2740
|
+
if (@label_for.is_a? RubyCurses::Button ) && (@label_for.respond_to? :fire)
|
|
2741
|
+
@form.bind_key(mch, "hotkey for button #{@label_for.text} ") { |_form, _butt| @label_for.fire }
|
|
2742
|
+
else
|
|
2743
|
+
$log.debug " bind_hotkey label for: #{@label_for}"
|
|
2744
|
+
@form.bind_key(mch, "hotkey for label #{text} ") { |_form, _field| @label_for.focus }
|
|
2745
|
+
end
|
|
2746
|
+
end
|
|
2747
|
+
end
|
|
2748
|
+
|
|
2749
|
+
##
|
|
2750
|
+
# label's repaint - I am removing wrapping and Array stuff and making it simple 2011-11-12
|
|
2751
|
+
def repaint
|
|
2752
|
+
return unless @repaint_required
|
|
2753
|
+
raise "Label row or col nil #{@row} , #{@col}, #{@text} " if @row.nil? || @col.nil?
|
|
2754
|
+
r,c = rowcol
|
|
2755
|
+
|
|
2756
|
+
@bgcolor ||= $def_bg_color
|
|
2757
|
+
@color ||= $def_fg_color
|
|
2758
|
+
# value often nil so putting blank, but usually some application error
|
|
2759
|
+
value = getvalue_for_paint || ""
|
|
2760
|
+
|
|
2761
|
+
if value.is_a? Array
|
|
2762
|
+
value = value.join " "
|
|
2763
|
+
end
|
|
2764
|
+
# ensure we do not exceed
|
|
2765
|
+
if @display_length
|
|
2766
|
+
if value.length > @display_length
|
|
2767
|
+
value = value[0..@display_length-1]
|
|
2768
|
+
end
|
|
2769
|
+
end
|
|
2770
|
+
len = @display_length || value.length
|
|
2771
|
+
#acolor = get_color $datacolor
|
|
2772
|
+
# the user could have set color_pair, use that, else determine color
|
|
2773
|
+
# This implies that if he sets cp, then changing col and bg won't have an effect !
|
|
2774
|
+
# A general routine that only changes color will not work here.
|
|
2775
|
+
acolor = @color_pair || get_color($datacolor, @color, @bgcolor)
|
|
2776
|
+
#$log.debug "label :#{@text}, #{value}, r #{r}, c #{c} col= #{@color}, #{@bgcolor} acolor #{acolor} j:#{@justify} dlL: #{@display_length} "
|
|
2777
|
+
str = @justify.to_sym == :right ? "%*s" : "%-*s" # added 2008-12-22 19:05
|
|
2778
|
+
|
|
2779
|
+
@graphic ||= @form.window
|
|
2780
|
+
# clear the area
|
|
2781
|
+
@graphic.printstring r, c, " " * len , acolor, @attr
|
|
2782
|
+
if @justify.to_sym == :center
|
|
2783
|
+
padding = (@display_length - value.length)/2
|
|
2784
|
+
value = " "*padding + value + " "*padding # so its cleared if we change it midway
|
|
2785
|
+
end
|
|
2786
|
+
@graphic.printstring r, c, str % [len, value], acolor, @attr
|
|
2787
|
+
if @mnemonic
|
|
2788
|
+
ulindex = value.index(@mnemonic) || value.index(@mnemonic.swapcase)
|
|
2789
|
+
@graphic.mvchgat(y=r, x=c+ulindex, max=1, Ncurses::A_BOLD|Ncurses::A_UNDERLINE, acolor, nil)
|
|
2790
|
+
end
|
|
2791
|
+
@repaint_required = false
|
|
2792
|
+
end
|
|
2793
|
+
# Added 2011-10-22 to prevent some naive components from putting focus here.
|
|
2794
|
+
def on_enter
|
|
2795
|
+
raise "Cannot enter Label"
|
|
2796
|
+
end
|
|
2797
|
+
def on_leave
|
|
2798
|
+
raise "Cannot leave Label"
|
|
2799
|
+
end
|
|
2800
|
+
# ADD HERE LABEL
|
|
2801
|
+
end
|
|
2802
|
+
##
|
|
2803
|
+
# action buttons
|
|
2804
|
+
# NOTE: When firing event, an ActionEvent will be passed as the first parameter, followed by anything
|
|
2805
|
+
# you may have passed when binding, or calling the command() method.
|
|
2806
|
+
# - Action: may have to listen to Action property changes so enabled, name etc change can be reflected
|
|
2807
|
+
# 2011-11-26 : define button as default, so it can show differently and also fire on ENTER
|
|
2808
|
+
# trying out behavior change. space to fire current button, ENTER for default button which has
|
|
2809
|
+
# > Name < look.
|
|
2810
|
+
class Button < Widget
|
|
2811
|
+
dsl_accessor :surround_chars # characters to use to surround the button, def is square brackets
|
|
2812
|
+
dsl_accessor :mnemonic
|
|
2813
|
+
# boolean for whether this is the default button. Careful, don't do this for more than 1
|
|
2814
|
+
def initialize form, config={}, &block
|
|
2815
|
+
require 'rbhex/core/include/ractionevent'
|
|
2816
|
+
@focusable = true
|
|
2817
|
+
@editable = false
|
|
2818
|
+
# hotkey denotes we should bind the key itself not alt-key (for menulinks)
|
|
2819
|
+
@hotkey = config.delete(:hotkey)
|
|
2820
|
+
$log.debug "XXX: HOTKEY #{@hotkey} "
|
|
2821
|
+
@handler={} # event handler
|
|
2822
|
+
@event_args ||= {}
|
|
2823
|
+
@_events ||= []
|
|
2824
|
+
@_events.push :PRESS
|
|
2825
|
+
@default_chars = ['> ', ' <']
|
|
2826
|
+
super
|
|
2827
|
+
|
|
2828
|
+
|
|
2829
|
+
@surround_chars ||= ['[ ', ' ]']
|
|
2830
|
+
@col_offset = @surround_chars[0].length
|
|
2831
|
+
@text_offset = 0
|
|
2832
|
+
end
|
|
2833
|
+
##
|
|
2834
|
+
# set button based on Action
|
|
2835
|
+
# 2009-01-21 19:59
|
|
2836
|
+
def action a
|
|
2837
|
+
text a.name
|
|
2838
|
+
mnemonic a.mnemonic unless a.mnemonic.nil?
|
|
2839
|
+
command { a.call }
|
|
2840
|
+
end
|
|
2841
|
+
##
|
|
2842
|
+
# button: sets text, checking for ampersand, uses that for hotkey and underlines
|
|
2843
|
+
def text(*val)
|
|
2844
|
+
if val.empty?
|
|
2845
|
+
return @text
|
|
2846
|
+
else
|
|
2847
|
+
s = val[0].dup
|
|
2848
|
+
s = s.to_s if !s.is_a? String # 2009-01-15 17:32
|
|
2849
|
+
if (( ix = s.index('&')) != nil)
|
|
2850
|
+
s.slice!(ix,1)
|
|
2851
|
+
# 2011-10-20 NOTE XXX I have removed form check since bindkey is called conditionally
|
|
2852
|
+
@underline = ix #unless @form.nil? # this setting a fake underline in messageboxes
|
|
2853
|
+
@text = s # mnemo needs this for setting description
|
|
2854
|
+
mnemonic s[ix,1]
|
|
2855
|
+
end
|
|
2856
|
+
@text = s
|
|
2857
|
+
end
|
|
2858
|
+
return self # added 2014-03-23 - 22:59 so that we can chain methods
|
|
2859
|
+
end
|
|
2860
|
+
|
|
2861
|
+
##
|
|
2862
|
+
# FIXME this will not work in messageboxes since no form available
|
|
2863
|
+
# if already set mnemonic, then unbind_key, ??
|
|
2864
|
+
# NOTE: Some buttons like checkbox directly call mnemonic, so if they have no form
|
|
2865
|
+
# then this processing does not happen
|
|
2866
|
+
|
|
2867
|
+
def mnemonic char=nil
|
|
2868
|
+
return @mnemonic unless char # added 2011-11-24 so caller can get mne
|
|
2869
|
+
|
|
2870
|
+
$log.error "ERROR WARN #{self} COULD NOT SET MNEMONIC since form NIL" if @form.nil?
|
|
2871
|
+
unless @form
|
|
2872
|
+
@when_form ||= []
|
|
2873
|
+
@when_form << lambda { mnemonic char }
|
|
2874
|
+
return self # added 2014-03-23 - 22:59 so that we can chain methods
|
|
2875
|
+
end
|
|
2876
|
+
#return if @form.nil?
|
|
2877
|
+
@mnemonic = char
|
|
2878
|
+
ch = char.downcase()[0].ord ## 1.9
|
|
2879
|
+
# meta key
|
|
2880
|
+
ch = ?\M-a.getbyte(0) + (ch - ?a.getbyte(0)) unless @hotkey
|
|
2881
|
+
$log.debug " #{self} setting MNEMO to #{char} #{ch}, #{@hotkey} "
|
|
2882
|
+
_t = self.text || self.name || "Unnamed #{self.class} "
|
|
2883
|
+
@form.bind_key(ch, "hotkey for button #{_t} ") { |_form, _butt| self.fire }
|
|
2884
|
+
return self # added 2015-03-23 - 22:59 so that we can chain methods
|
|
2885
|
+
end
|
|
2886
|
+
|
|
2887
|
+
##
|
|
2888
|
+
# bind hotkey to form keys. added 2008-12-15 20:19
|
|
2889
|
+
# use ampersand in name or underline
|
|
2890
|
+
def bind_hotkey
|
|
2891
|
+
if @form.nil?
|
|
2892
|
+
if @underline
|
|
2893
|
+
@when_form ||= []
|
|
2894
|
+
@when_form << lambda { bind_hotkey }
|
|
2895
|
+
end
|
|
2896
|
+
return
|
|
2897
|
+
end
|
|
2898
|
+
_value = @text || getvalue # hack for Togglebutton FIXME
|
|
2899
|
+
#_value = getvalue
|
|
2900
|
+
$log.debug " bind hot #{_value} #{@underline}"
|
|
2901
|
+
ch = _value[@underline,1].downcase()[0].ord ## 1.9 2009-10-05 18:55 TOTEST
|
|
2902
|
+
@mnemonic = _value[@underline,1]
|
|
2903
|
+
# meta key
|
|
2904
|
+
mch = ?\M-a.getbyte(0) + (ch - ?a.getbyte(0))
|
|
2905
|
+
@form.bind_key(mch, "hotkey for button #{self.text}" ) { |_form, _butt| self.fire }
|
|
2906
|
+
end
|
|
2907
|
+
def default_button tf=nil
|
|
2908
|
+
return @default_button unless tf
|
|
2909
|
+
raise ArgumentError, "default button must be true or false" if ![false,true].include? tf
|
|
2910
|
+
$log.debug "XXX: BUTTON DEFAULT setting to true : #{tf} "
|
|
2911
|
+
@default_button = tf
|
|
2912
|
+
if tf
|
|
2913
|
+
@surround_chars = @default_chars
|
|
2914
|
+
@form.bind_key(13, "fire #{self.text} ") { |_form, _butt| self.fire }
|
|
2915
|
+
else
|
|
2916
|
+
# i have no way of reversing the above
|
|
2917
|
+
end
|
|
2918
|
+
end
|
|
2919
|
+
|
|
2920
|
+
def getvalue
|
|
2921
|
+
@text_variable.nil? ? @text : @text_variable.get_value(@name)
|
|
2922
|
+
end
|
|
2923
|
+
|
|
2924
|
+
# ensure text has been passed or action
|
|
2925
|
+
def getvalue_for_paint
|
|
2926
|
+
ret = getvalue
|
|
2927
|
+
@text_offset = @surround_chars[0].length
|
|
2928
|
+
@surround_chars[0] + ret + @surround_chars[1]
|
|
2929
|
+
end
|
|
2930
|
+
def repaint # button
|
|
2931
|
+
if @form
|
|
2932
|
+
if @when_form
|
|
2933
|
+
$log.debug "XXX:WHEN calling when_forms commands"
|
|
2934
|
+
@when_form.each { |c| c.call() }
|
|
2935
|
+
@when_form = nil
|
|
2936
|
+
end
|
|
2937
|
+
end
|
|
2938
|
+
|
|
2939
|
+
@bgcolor ||= $def_bg_color
|
|
2940
|
+
@color ||= $def_fg_color
|
|
2941
|
+
$log.debug("BUTTON repaint : #{self} r:#{@row} c:#{@col} , #{@color} , #{@bgcolor} , #{getvalue_for_paint}" )
|
|
2942
|
+
r,c = @row, @col #rowcol include offset for putting cursor
|
|
2943
|
+
# NOTE: please override both (if using a string), or else it won't work
|
|
2944
|
+
@highlight_foreground ||= $reversecolor
|
|
2945
|
+
@highlight_background ||= 0
|
|
2946
|
+
_bgcolor = @bgcolor
|
|
2947
|
+
_color = @color
|
|
2948
|
+
if @state == :HIGHLIGHTED
|
|
2949
|
+
_bgcolor = @state==:HIGHLIGHTED ? @highlight_background : @bgcolor
|
|
2950
|
+
_color = @state==:HIGHLIGHTED ? @highlight_foreground : @color
|
|
2951
|
+
elsif selected? # only for certain buttons lie toggle and radio
|
|
2952
|
+
_bgcolor = @selected_background || @bgcolor
|
|
2953
|
+
_color = @selected_foreground || @color
|
|
2954
|
+
end
|
|
2955
|
+
$log.debug "XXX: button #{text} STATE is #{@state} color #{_color} , bg: #{_bgcolor} "
|
|
2956
|
+
if _bgcolor.is_a?( Fixnum) && _color.is_a?( Fixnum)
|
|
2957
|
+
else
|
|
2958
|
+
_color = get_color($datacolor, _color, _bgcolor)
|
|
2959
|
+
end
|
|
2960
|
+
value = getvalue_for_paint
|
|
2961
|
+
$log.debug("button repaint :#{self} r:#{r} c:#{c} col:#{_color} bg #{_bgcolor} v: #{value} ul #{@underline} mnem #{@mnemonic} datacolor #{$datacolor} ")
|
|
2962
|
+
len = @display_length || value.length
|
|
2963
|
+
@graphic = @form.window if @graphic.nil? ## cell editor listbox hack
|
|
2964
|
+
@graphic.printstring r, c, "%-*s" % [len, value], _color, @attr
|
|
2965
|
+
# @form.window.mvchgat(y=r, x=c, max=len, Ncurses::A_NORMAL, bgcolor, nil)
|
|
2966
|
+
# in toggle buttons the underline can change as the text toggles
|
|
2967
|
+
if @underline || @mnemonic
|
|
2968
|
+
uline = @underline && (@underline + @text_offset) || value.index(@mnemonic) ||
|
|
2969
|
+
value.index(@mnemonic.swapcase)
|
|
2970
|
+
# if the char is not found don't print it
|
|
2971
|
+
if uline
|
|
2972
|
+
y=r #-@graphic.top
|
|
2973
|
+
x=c+uline #-@graphic.left
|
|
2974
|
+
if @graphic.window_type == :PAD
|
|
2975
|
+
x -= @graphic.left
|
|
2976
|
+
y -= @graphic.top
|
|
2977
|
+
end
|
|
2978
|
+
#
|
|
2979
|
+
# NOTE: often values go below zero since root windows are defined
|
|
2980
|
+
# with 0 w and h, and then i might use that value for calcaluting
|
|
2981
|
+
#
|
|
2982
|
+
$log.error "XXX button underline location error #{x} , #{y} " if x < 0 or c < 0
|
|
2983
|
+
raise " #{r} #{c} #{uline} button underline location error x:#{x} , y:#{y}. left #{@graphic.left} top:#{@graphic.top} " if x < 0 or c < 0
|
|
2984
|
+
@graphic.mvchgat(y, x, max=1, Ncurses::A_BOLD|Ncurses::A_UNDERLINE, _color, nil)
|
|
2985
|
+
end
|
|
2986
|
+
end
|
|
2987
|
+
end
|
|
2988
|
+
|
|
2989
|
+
## command of button (invoked on press, hotkey, space)
|
|
2990
|
+
# added args 2008-12-20 19:22
|
|
2991
|
+
def command *args, &block
|
|
2992
|
+
bind :PRESS, *args, &block
|
|
2993
|
+
$log.debug "#{text} bound PRESS"
|
|
2994
|
+
end
|
|
2995
|
+
## fires PRESS event of button
|
|
2996
|
+
def fire
|
|
2997
|
+
$log.debug "firing PRESS #{text}"
|
|
2998
|
+
# why the .... am i passing form ? Pass a ActionEvent with source, text() and getvalue()
|
|
2999
|
+
#fire_handler :PRESS, @form changed on 2010-09-12 19:22
|
|
3000
|
+
fire_handler :PRESS, ActionEvent.new(self, :PRESS, text)
|
|
3001
|
+
end
|
|
3002
|
+
# for campatibility with all buttons, will apply to radio buttons mostly
|
|
3003
|
+
def selected?; false; end
|
|
3004
|
+
|
|
3005
|
+
# Button
|
|
3006
|
+
def handle_key ch
|
|
3007
|
+
case ch
|
|
3008
|
+
when FFI::NCurses::KEY_LEFT, FFI::NCurses::KEY_UP
|
|
3009
|
+
return :UNHANDLED
|
|
3010
|
+
# @form.select_prev_field
|
|
3011
|
+
when FFI::NCurses::KEY_RIGHT, FFI::NCurses::KEY_DOWN
|
|
3012
|
+
return :UNHANDLED
|
|
3013
|
+
# @form.select_next_field
|
|
3014
|
+
when FFI::NCurses::KEY_ENTER, 10, 13, 32 # added space bar also
|
|
3015
|
+
# I am really confused about this. Default button really confuses things in some
|
|
3016
|
+
# situations, but is great if you are not on the buttons.
|
|
3017
|
+
# shall we keep ENTER for default button
|
|
3018
|
+
#when 32 # added space bar also
|
|
3019
|
+
if respond_to? :fire
|
|
3020
|
+
fire
|
|
3021
|
+
end
|
|
3022
|
+
else
|
|
3023
|
+
if $key_map == :vim
|
|
3024
|
+
case ch
|
|
3025
|
+
when ?j.getbyte(0)
|
|
3026
|
+
@form.window.ungetch(KEY_DOWN)
|
|
3027
|
+
return 0
|
|
3028
|
+
when ?k.getbyte(0)
|
|
3029
|
+
@form.window.ungetch(KEY_UP)
|
|
3030
|
+
return 0
|
|
3031
|
+
end
|
|
3032
|
+
|
|
3033
|
+
end
|
|
3034
|
+
return :UNHANDLED
|
|
3035
|
+
end
|
|
3036
|
+
end
|
|
3037
|
+
|
|
3038
|
+
# temporary method, shoud be a proper class
|
|
3039
|
+
def self.button_layout buttons, row, startcol=0, cols=Ncurses.COLS-1, gap=5
|
|
3040
|
+
col = startcol
|
|
3041
|
+
buttons.each_with_index do |b, ix|
|
|
3042
|
+
$log.debug " BUTTON #{b}: #{b.col} "
|
|
3043
|
+
b.row = row
|
|
3044
|
+
b.col col
|
|
3045
|
+
$log.debug " after BUTTON #{b}: #{b.col} "
|
|
3046
|
+
len = b.text.length + gap
|
|
3047
|
+
col += len
|
|
3048
|
+
end
|
|
3049
|
+
end
|
|
3050
|
+
end #BUTTON
|
|
3051
|
+
|
|
3052
|
+
##
|
|
3053
|
+
# an event fired when an item that can be selected is toggled/selected
|
|
3054
|
+
class ItemEvent
|
|
3055
|
+
# http://java.sun.com/javase/6/docs/api/java/awt/event/ItemEvent.html
|
|
3056
|
+
attr_reader :state # :SELECTED :DESELECTED
|
|
3057
|
+
attr_reader :item # the item pressed such as toggle button
|
|
3058
|
+
attr_reader :item_selectable # item originating event such as list or collection
|
|
3059
|
+
attr_reader :item_first # if from a list
|
|
3060
|
+
attr_reader :item_last #
|
|
3061
|
+
attr_reader :param_string # for debugging etc
|
|
3062
|
+
=begin
|
|
3063
|
+
def initialize item, item_selectable, state, item_first=-1, item_last=-1, paramstring=nil
|
|
3064
|
+
@item, @item_selectable, @state, @item_first, @item_last =
|
|
3065
|
+
item, item_selectable, state, item_first, item_last
|
|
3066
|
+
@param_string = "Item event fired: #{item}, #{state}"
|
|
3067
|
+
end
|
|
3068
|
+
=end
|
|
3069
|
+
# i think only one is needed per object, so create once only
|
|
3070
|
+
def initialize item, item_selectable
|
|
3071
|
+
@item, @item_selectable =
|
|
3072
|
+
item, item_selectable
|
|
3073
|
+
end
|
|
3074
|
+
def set state, item_first=-1, item_last=-1, param_string=nil
|
|
3075
|
+
@state, @item_first, @item_last, @param_string =
|
|
3076
|
+
state, item_first, item_last, param_string
|
|
3077
|
+
@param_string = "Item event fired: #{item}, #{state}" if param_string.nil?
|
|
3078
|
+
end
|
|
3079
|
+
end
|
|
3080
|
+
##
|
|
3081
|
+
# A button that may be switched off an on.
|
|
3082
|
+
# To be extended by RadioButton and checkbox.
|
|
3083
|
+
# TODO: add editable here nd prevent toggling if not so.
|
|
3084
|
+
class ToggleButton < Button
|
|
3085
|
+
dsl_accessor :onvalue, :offvalue
|
|
3086
|
+
dsl_accessor :value
|
|
3087
|
+
dsl_accessor :surround_chars
|
|
3088
|
+
dsl_accessor :variable # value linked to this variable which is a boolean
|
|
3089
|
+
dsl_accessor :display_length # 2009-01-06 00:10
|
|
3090
|
+
# background to use when selected, if not set then default
|
|
3091
|
+
dsl_accessor :selected_background
|
|
3092
|
+
dsl_accessor :selected_foreground
|
|
3093
|
+
|
|
3094
|
+
# For consistency, now width equates to display_length
|
|
3095
|
+
alias :width :display_length
|
|
3096
|
+
alias :width= :display_length=
|
|
3097
|
+
|
|
3098
|
+
# item_event
|
|
3099
|
+
def initialize form, config={}, &block
|
|
3100
|
+
super
|
|
3101
|
+
|
|
3102
|
+
@value ||= (@variable.nil? ? false : @variable.get_value(@name)==true)
|
|
3103
|
+
end
|
|
3104
|
+
def getvalue
|
|
3105
|
+
@value ? @onvalue : @offvalue
|
|
3106
|
+
end
|
|
3107
|
+
# added for some standardization 2010-09-07 20:28
|
|
3108
|
+
# alias :text :getvalue # NEXT VERSION
|
|
3109
|
+
# change existing text to label
|
|
3110
|
+
##
|
|
3111
|
+
# is the button on or off
|
|
3112
|
+
# added 2008-12-09 19:05
|
|
3113
|
+
def checked?
|
|
3114
|
+
@value
|
|
3115
|
+
end
|
|
3116
|
+
alias :selected? :checked?
|
|
3117
|
+
|
|
3118
|
+
def getvalue_for_paint
|
|
3119
|
+
unless @display_length
|
|
3120
|
+
if @onvalue && @offvalue
|
|
3121
|
+
@display_length = [ @onvalue.length, @offvalue.length ].max
|
|
3122
|
+
end
|
|
3123
|
+
end
|
|
3124
|
+
buttontext = getvalue().center(@display_length)
|
|
3125
|
+
@text_offset = @surround_chars[0].length
|
|
3126
|
+
@surround_chars[0] + buttontext + @surround_chars[1]
|
|
3127
|
+
end
|
|
3128
|
+
|
|
3129
|
+
# toggle button handle key
|
|
3130
|
+
# @param [int] key received
|
|
3131
|
+
#
|
|
3132
|
+
def handle_key ch
|
|
3133
|
+
if ch == 32
|
|
3134
|
+
toggle
|
|
3135
|
+
else
|
|
3136
|
+
super
|
|
3137
|
+
end
|
|
3138
|
+
end
|
|
3139
|
+
|
|
3140
|
+
##
|
|
3141
|
+
# toggle the button value
|
|
3142
|
+
def toggle
|
|
3143
|
+
fire
|
|
3144
|
+
end
|
|
3145
|
+
|
|
3146
|
+
# called on :PRESS event
|
|
3147
|
+
# caller should check state of itemevent passed to block
|
|
3148
|
+
def fire
|
|
3149
|
+
checked(!@value)
|
|
3150
|
+
# added ItemEvent on 2008-12-31 13:44
|
|
3151
|
+
@item_event = ItemEvent.new self, self if @item_event.nil?
|
|
3152
|
+
@item_event.set(@value ? :SELECTED : :DESELECTED)
|
|
3153
|
+
fire_handler :PRESS, @item_event # should the event itself be ITEM_EVENT
|
|
3154
|
+
# fire_handler :PRESS, @form
|
|
3155
|
+
# super
|
|
3156
|
+
end
|
|
3157
|
+
##
|
|
3158
|
+
# set the value to true or false
|
|
3159
|
+
# user may programmatically want to check or uncheck
|
|
3160
|
+
def checked tf
|
|
3161
|
+
@value = tf
|
|
3162
|
+
if !@variable.nil?
|
|
3163
|
+
if @value
|
|
3164
|
+
@variable.set_value((@onvalue || 1), @name)
|
|
3165
|
+
else
|
|
3166
|
+
@variable.set_value((@offvalue || 0), @name)
|
|
3167
|
+
end
|
|
3168
|
+
end
|
|
3169
|
+
# call fire of button class 2008-12-09 17:49
|
|
3170
|
+
end
|
|
3171
|
+
end # class
|
|
3172
|
+
|
|
3173
|
+
##
|
|
3174
|
+
# A checkbox, may be selected or unselected
|
|
3175
|
+
#
|
|
3176
|
+
class CheckBox < ToggleButton
|
|
3177
|
+
dsl_accessor :align_right # the button will be on the right 2008-12-09 23:41
|
|
3178
|
+
# if a variable has been defined, off and on value will be set in it (default 0,1)
|
|
3179
|
+
def initialize form, config={}, &block
|
|
3180
|
+
@surround_chars = ['[', ']'] # 2008-12-23 23:16 added space in Button so overriding
|
|
3181
|
+
super
|
|
3182
|
+
end
|
|
3183
|
+
def getvalue
|
|
3184
|
+
@value
|
|
3185
|
+
end
|
|
3186
|
+
|
|
3187
|
+
def getvalue_for_paint
|
|
3188
|
+
buttontext = getvalue() ? "X" : " "
|
|
3189
|
+
dtext = @display_length.nil? ? @text : "%-*s" % [@display_length, @text]
|
|
3190
|
+
dtext = "" if @text.nil? # added 2009-01-13 00:41 since cbcellrenderer prints no text
|
|
3191
|
+
if @align_right
|
|
3192
|
+
@text_offset = 0
|
|
3193
|
+
@col_offset = dtext.length + @surround_chars[0].length + 1
|
|
3194
|
+
return "#{dtext} " + @surround_chars[0] + buttontext + @surround_chars[1]
|
|
3195
|
+
else
|
|
3196
|
+
pretext = @surround_chars[0] + buttontext + @surround_chars[1]
|
|
3197
|
+
@text_offset = pretext.length + 1
|
|
3198
|
+
@col_offset = @surround_chars[0].length
|
|
3199
|
+
#@surround_chars[0] + buttontext + @surround_chars[1] + " #{@text}"
|
|
3200
|
+
return pretext + " #{dtext}"
|
|
3201
|
+
end
|
|
3202
|
+
end
|
|
3203
|
+
end # class
|
|
3204
|
+
|
|
3205
|
+
##
|
|
3206
|
+
# A selectable button that has a text value. It is based on a Variable that
|
|
3207
|
+
# is shared by other radio buttons. Only one is selected at a time, unlike checkbox
|
|
3208
|
+
# 2008-11-27 18:45 just made this inherited from Checkbox
|
|
3209
|
+
|
|
3210
|
+
class RadioButton < ToggleButton
|
|
3211
|
+
dsl_accessor :align_right # the button will be on the right 2008-12-09 23:41
|
|
3212
|
+
# if a variable has been defined, off and on value will be set in it (default 0,1)
|
|
3213
|
+
def initialize form, config={}, &block
|
|
3214
|
+
@surround_chars = ['(', ')'] if @surround_chars.nil?
|
|
3215
|
+
super
|
|
3216
|
+
$log.warn "XXX: FIXME Please set 'value' for radiobutton. If you don't know, try setting it to 'text'" unless @value
|
|
3217
|
+
# I am setting value of value here if not set 2011-10-21
|
|
3218
|
+
@value ||= @text
|
|
3219
|
+
## trying with off since i can't do conventional style construction
|
|
3220
|
+
#raise "A single Variable must be set for a group of Radio Buttons for this to work." unless @variable
|
|
3221
|
+
end
|
|
3222
|
+
|
|
3223
|
+
# all radio buttons will return the value of the selected value, not the offered value
|
|
3224
|
+
def getvalue
|
|
3225
|
+
#@text_variable.value
|
|
3226
|
+
@variable.get_value @name
|
|
3227
|
+
end
|
|
3228
|
+
|
|
3229
|
+
def getvalue_for_paint
|
|
3230
|
+
buttontext = getvalue() == @value ? "o" : " "
|
|
3231
|
+
dtext = @display_length.nil? ? text : "%-*s" % [@display_length, text]
|
|
3232
|
+
if @align_right
|
|
3233
|
+
@text_offset = 0
|
|
3234
|
+
@col_offset = dtext.length + @surround_chars[0].length + 1
|
|
3235
|
+
return "#{dtext} " + @surround_chars[0] + buttontext + @surround_chars[1]
|
|
3236
|
+
else
|
|
3237
|
+
pretext = @surround_chars[0] + buttontext + @surround_chars[1]
|
|
3238
|
+
@text_offset = pretext.length + 1
|
|
3239
|
+
@col_offset = @surround_chars[0].length
|
|
3240
|
+
return pretext + " #{dtext}"
|
|
3241
|
+
end
|
|
3242
|
+
end
|
|
3243
|
+
|
|
3244
|
+
def toggle
|
|
3245
|
+
@variable.set_value @value, @name
|
|
3246
|
+
# call fire of button class 2008-12-09 17:49
|
|
3247
|
+
fire
|
|
3248
|
+
end
|
|
3249
|
+
|
|
3250
|
+
# added for bindkeys since that calls fire, not toggle - XXX i don't like this
|
|
3251
|
+
def fire
|
|
3252
|
+
@variable.set_value @value,@name
|
|
3253
|
+
super
|
|
3254
|
+
end
|
|
3255
|
+
|
|
3256
|
+
##
|
|
3257
|
+
# ideally this should not be used. But implemented for completeness.
|
|
3258
|
+
# it is recommended to toggle some other radio button than to uncheck this.
|
|
3259
|
+
def checked tf
|
|
3260
|
+
if tf
|
|
3261
|
+
toggle
|
|
3262
|
+
elsif !@variable.nil? and getvalue() != @value # XXX ???
|
|
3263
|
+
@variable.set_value "",""
|
|
3264
|
+
end
|
|
3265
|
+
end
|
|
3266
|
+
end # class radio
|
|
3267
|
+
|
|
3268
|
+
def self.startup
|
|
3269
|
+
VER::start_ncurses
|
|
3270
|
+
path = File.join(ENV["LOGDIR"] || "./" ,"rbc13.log")
|
|
3271
|
+
file = File.open(path, File::WRONLY|File::TRUNC|File::CREAT)
|
|
3272
|
+
$log = Logger.new(path)
|
|
3273
|
+
$log.level = Logger::DEBUG
|
|
3274
|
+
end
|
|
3275
|
+
|
|
3276
|
+
end # module
|
|
3277
|
+
include RubyCurses::Utils
|