rbhex-core 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (108) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +20 -0
  3. data/CHANGELOG +2000 -0
  4. data/LICENSE +56 -0
  5. data/README.md +44 -0
  6. data/examples/abasiclist.rb +179 -0
  7. data/examples/alpmenu.rb +50 -0
  8. data/examples/app.sample +19 -0
  9. data/examples/atree.rb +100 -0
  10. data/examples/bline.rb +136 -0
  11. data/examples/common/file.rb +45 -0
  12. data/examples/data/README.markdown +9 -0
  13. data/examples/data/brew.txt +38 -0
  14. data/examples/data/color.2 +37 -0
  15. data/examples/data/gemlist.txt +60 -0
  16. data/examples/data/lotr.txt +12 -0
  17. data/examples/data/ports.txt +136 -0
  18. data/examples/data/table.txt +37 -0
  19. data/examples/data/tasks.csv +88 -0
  20. data/examples/data/tasks.txt +27 -0
  21. data/examples/data/todo.txt +10 -0
  22. data/examples/data/todo.txt.bak +10 -0
  23. data/examples/data/todocsv.csv +28 -0
  24. data/examples/data/unix1.txt +21 -0
  25. data/examples/data/unix2.txt +11 -0
  26. data/examples/dbdemo.rb +502 -0
  27. data/examples/dirtree.rb +94 -0
  28. data/examples/newtabbedwindow.rb +100 -0
  29. data/examples/newtesttabp.rb +92 -0
  30. data/examples/tabular.rb +146 -0
  31. data/examples/tasks.rb +178 -0
  32. data/examples/term2.rb +84 -0
  33. data/examples/testbuttons.rb +296 -0
  34. data/examples/testcombo.rb +102 -0
  35. data/examples/testfields.rb +195 -0
  36. data/examples/testkeypress.rb +72 -0
  37. data/examples/testlistbox.rb +170 -0
  38. data/examples/testmessagebox.rb +140 -0
  39. data/examples/testprogress.rb +116 -0
  40. data/examples/testree.rb +106 -0
  41. data/examples/testwsshortcuts.rb +66 -0
  42. data/examples/testwsshortcuts2.rb +128 -0
  43. data/lib/rbhex.rb +6 -0
  44. data/lib/rbhex/core/docs/index.txt +73 -0
  45. data/lib/rbhex/core/include/action.rb +80 -0
  46. data/lib/rbhex/core/include/actionmanager.rb +49 -0
  47. data/lib/rbhex/core/include/appmethods.rb +214 -0
  48. data/lib/rbhex/core/include/bordertitle.rb +48 -0
  49. data/lib/rbhex/core/include/chunk.rb +203 -0
  50. data/lib/rbhex/core/include/io.rb +553 -0
  51. data/lib/rbhex/core/include/listbindings.rb +74 -0
  52. data/lib/rbhex/core/include/listcellrenderer.rb +140 -0
  53. data/lib/rbhex/core/include/listeditable.rb +317 -0
  54. data/lib/rbhex/core/include/listscrollable.rb +663 -0
  55. data/lib/rbhex/core/include/listselectable.rb +271 -0
  56. data/lib/rbhex/core/include/multibuffer.rb +83 -0
  57. data/lib/rbhex/core/include/orderedhash.rb +77 -0
  58. data/lib/rbhex/core/include/ractionevent.rb +73 -0
  59. data/lib/rbhex/core/include/rchangeevent.rb +27 -0
  60. data/lib/rbhex/core/include/rhistory.rb +95 -0
  61. data/lib/rbhex/core/include/rinputdataevent.rb +47 -0
  62. data/lib/rbhex/core/include/vieditable.rb +172 -0
  63. data/lib/rbhex/core/include/widgetmenu.rb +66 -0
  64. data/lib/rbhex/core/system/colormap.rb +165 -0
  65. data/lib/rbhex/core/system/keyboard.rb +150 -0
  66. data/lib/rbhex/core/system/keydefs.rb +30 -0
  67. data/lib/rbhex/core/system/ncurses.rb +236 -0
  68. data/lib/rbhex/core/system/panel.rb +162 -0
  69. data/lib/rbhex/core/system/window.rb +913 -0
  70. data/lib/rbhex/core/util/ansiparser.rb +119 -0
  71. data/lib/rbhex/core/util/app.rb +1228 -0
  72. data/lib/rbhex/core/util/basestack.rb +410 -0
  73. data/lib/rbhex/core/util/bottomline.rb +1859 -0
  74. data/lib/rbhex/core/util/colorparser.rb +77 -0
  75. data/lib/rbhex/core/util/focusmanager.rb +31 -0
  76. data/lib/rbhex/core/util/padreader.rb +192 -0
  77. data/lib/rbhex/core/util/rcommandwindow.rb +604 -0
  78. data/lib/rbhex/core/util/rdialogs.rb +574 -0
  79. data/lib/rbhex/core/util/viewer.rb +149 -0
  80. data/lib/rbhex/core/util/widgetshortcuts.rb +506 -0
  81. data/lib/rbhex/core/version.rb +5 -0
  82. data/lib/rbhex/core/widgets/applicationheader.rb +103 -0
  83. data/lib/rbhex/core/widgets/box.rb +58 -0
  84. data/lib/rbhex/core/widgets/divider.rb +310 -0
  85. data/lib/rbhex/core/widgets/keylabelprinter.rb +194 -0
  86. data/lib/rbhex/core/widgets/rcombo.rb +253 -0
  87. data/lib/rbhex/core/widgets/rcontainer.rb +415 -0
  88. data/lib/rbhex/core/widgets/rlink.rb +30 -0
  89. data/lib/rbhex/core/widgets/rlist.rb +696 -0
  90. data/lib/rbhex/core/widgets/rmenu.rb +958 -0
  91. data/lib/rbhex/core/widgets/rmenulink.rb +22 -0
  92. data/lib/rbhex/core/widgets/rmessagebox.rb +387 -0
  93. data/lib/rbhex/core/widgets/rprogress.rb +118 -0
  94. data/lib/rbhex/core/widgets/rtabbedpane.rb +634 -0
  95. data/lib/rbhex/core/widgets/rtabbedwindow.rb +70 -0
  96. data/lib/rbhex/core/widgets/rtextarea.rb +960 -0
  97. data/lib/rbhex/core/widgets/rtextview.rb +739 -0
  98. data/lib/rbhex/core/widgets/rtree.rb +768 -0
  99. data/lib/rbhex/core/widgets/rwidget.rb +3277 -0
  100. data/lib/rbhex/core/widgets/scrollbar.rb +143 -0
  101. data/lib/rbhex/core/widgets/statusline.rb +113 -0
  102. data/lib/rbhex/core/widgets/tabular.rb +264 -0
  103. data/lib/rbhex/core/widgets/tabularwidget.rb +1142 -0
  104. data/lib/rbhex/core/widgets/textpad.rb +995 -0
  105. data/lib/rbhex/core/widgets/tree/treecellrenderer.rb +150 -0
  106. data/lib/rbhex/core/widgets/tree/treemodel.rb +428 -0
  107. data/rbhex-core.gemspec +32 -0
  108. 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