rbcurse-core 0.0.0

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