canis 0.0.4

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