rbcurse-core 0.0.0

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