rbhex-core 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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