canis 0.0.4

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