rbcurse 1.4.1 → 1.5.0.pre1

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