rbcurse 1.5.0 → 1.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. data/Makefile +21 -0
  2. data/Manifest.txt +6 -0
  3. data/README.markdown +6 -4
  4. data/TODO +372 -0
  5. data/TODO2.txt +121 -0
  6. data/VERSION +1 -1
  7. data/examples/README.txt +67 -0
  8. data/examples/abasiclist.rb +33 -0
  9. data/examples/alpmenu.rb +42 -0
  10. data/examples/app.rb +859 -0
  11. data/examples/app.sample +17 -0
  12. data/examples/appdirtree.rb +74 -0
  13. data/examples/appemail.rb +191 -0
  14. data/examples/appemaillb.rb +308 -0
  15. data/examples/appgcompose.rb +315 -0
  16. data/examples/atree.rb +64 -0
  17. data/examples/common/file.rb +40 -0
  18. data/examples/common/rmail.rb +257 -0
  19. data/examples/data.txt +683 -0
  20. data/examples/data/README.markdown +9 -0
  21. data/examples/data/brew.txt +38 -0
  22. data/examples/data/color.2 +37 -0
  23. data/examples/data/gemlist.txt +60 -0
  24. data/examples/data/lotr.txt +12 -0
  25. data/examples/data/ports.txt +136 -0
  26. data/examples/data/tasks.txt +27 -0
  27. data/examples/data/todocsv.csv +28 -0
  28. data/examples/data/unix1.txt +21 -0
  29. data/examples/data/unix2.txt +11 -0
  30. data/examples/dbdemo.rb +495 -0
  31. data/examples/deprecated/appgmail.rb +952 -0
  32. data/examples/deprecated/splitp.rb +56 -0
  33. data/examples/deprecated/testscrolllb.rb +86 -0
  34. data/examples/deprecated/testscrollp.rb +88 -0
  35. data/examples/deprecated/testscrollta.rb +80 -0
  36. data/examples/deprecated/testscrolltable.rb +165 -0
  37. data/examples/deprecated/testsplit.rb +87 -0
  38. data/examples/deprecated/testsplit2.rb +123 -0
  39. data/examples/deprecated/testsplit3.rb +215 -0
  40. data/examples/deprecated/testsplit3_1.rb +244 -0
  41. data/examples/deprecated/testsplit3a.rb +215 -0
  42. data/examples/deprecated/testsplit3b.rb +237 -0
  43. data/examples/deprecated/testsplitta.rb +148 -0
  44. data/examples/deprecated/testsplittv.rb +142 -0
  45. data/examples/deprecated/testsplittvv.rb +144 -0
  46. data/examples/deprecated/testtpane.rb +215 -0
  47. data/examples/deprecated/testtpane2.rb +145 -0
  48. data/examples/deprecated/testtpanetable.rb +203 -0
  49. data/examples/dirtree.rb +88 -0
  50. data/examples/experimental/resultsetdemo.rb +280 -0
  51. data/examples/experimental/testmform.rb +35 -0
  52. data/examples/experimental/testscroller.rb +117 -0
  53. data/examples/experimental/teststackflow.rb +111 -0
  54. data/examples/menu1.rb +112 -0
  55. data/examples/multispl.rb +86 -0
  56. data/examples/newmessagebox.rb +131 -0
  57. data/examples/newtabbedwindow.rb +100 -0
  58. data/examples/newtesttabp.rb +121 -0
  59. data/examples/qdfilechooser.rb +68 -0
  60. data/examples/rfe.rb +1239 -0
  61. data/examples/rfe_renderer.rb +121 -0
  62. data/examples/sqlc.rb +454 -0
  63. data/examples/sqlm.rb +437 -0
  64. data/examples/sqlt.rb +408 -0
  65. data/examples/status.txt +68 -0
  66. data/examples/table1.rb +24 -0
  67. data/examples/term2.rb +84 -0
  68. data/examples/test1.rb +239 -0
  69. data/examples/test2.rb +674 -0
  70. data/examples/testapp.rb +44 -0
  71. data/examples/testapp2.rb +58 -0
  72. data/examples/testchars.rb +137 -0
  73. data/examples/testcombo.rb +91 -0
  74. data/examples/testkeypress.rb +66 -0
  75. data/examples/testlistbox.rb +113 -0
  76. data/examples/testmenu.rb +101 -0
  77. data/examples/testmulticomp.rb +70 -0
  78. data/examples/testmulticontainer.rb +94 -0
  79. data/examples/testmultispl.rb +199 -0
  80. data/examples/testree.rb +106 -0
  81. data/examples/testtable.rb +264 -0
  82. data/examples/testtabp.rb +107 -0
  83. data/examples/testtodo.rb +584 -0
  84. data/examples/testvimsplit.rb +112 -0
  85. data/examples/testwsshortcuts.rb +64 -0
  86. data/examples/testwsshortcuts2.rb +126 -0
  87. data/examples/todo.db +0 -0
  88. data/examples/todo.yml +191 -0
  89. data/examples/viewtodo.rb +574 -0
  90. data/lib/rbcurse/deprecated/README.markdown +12 -0
  91. data/lib/rbcurse/deprecated/rpad.rb +375 -0
  92. data/lib/rbcurse/deprecated/rscrollpane.rb +512 -0
  93. data/lib/rbcurse/deprecated/rsplitpane.rb +894 -0
  94. data/lib/rbcurse/deprecated/rsplitpane2.rb +1009 -0
  95. data/lib/rbcurse/deprecated/rviewport.rb +204 -0
  96. data/lib/rbcurse/deprecated/widgets/mapper.rb +130 -0
  97. data/lib/rbcurse/deprecated/widgets/rmessagebox.rb +348 -0
  98. data/lib/rbcurse/deprecated/widgets/rtabbedpane.rb +1158 -0
  99. data/lib/rbcurse/deprecated/widgets/rtabbedwindow.rb +167 -0
  100. data/lib/rbcurse/deprecated/widgets/scrollable.rb +301 -0
  101. data/lib/rbcurse/deprecated/widgets/stdscrwindow.rb +309 -0
  102. data/lib/ver/keyboard2.rb +170 -0
  103. data/test/test_rbcurse.rb +0 -0
  104. metadata +131 -9
@@ -0,0 +1,1158 @@
1
+ =begin
2
+ * Name: tabbed pane: can have multiple forms overlapping.
3
+ * Description:
4
+ * A tabbed pane, mostly based (iirc) on the Terminal Preferences in OSX PPC 10.5.x
5
+ * Starting a new version using pads 2009-10-25 12:05
6
+
7
+ 2011-10-18 : removed ScrollForm since it would not print if window was not at 0,0 top left.
8
+
9
+
10
+ * Author: rkumar
11
+
12
+ --------
13
+ * Date: 2009-10-25 12:05
14
+ * License:
15
+ Same as Ruby's License (http://www.ruby-lang.org/LICENSE.txt)
16
+
17
+ * 2010-02-28 09:47 - major cleanup and rewrite.
18
+ - Allow adding of component (in addition to form)
19
+ - Ideally, even form should be created and managed itself, why should TP have to repaint it?
20
+
21
+ NOTE:
22
+ Tp now does not create a form by default, since awefun you may want to just put in one component.
23
+ Pls use tp.form(tab) to get a form associated with the tab.
24
+ You may add as many tabs as you wish. To scroll tabs, traverse into the tab form and use the usual scroll keys M-l and M-h to scroll left and right.
25
+ #
26
+ # TODO : disable/hide tab ???
27
+ =end
28
+ require 'rbcurse'
29
+ require 'ver/rpad'
30
+ #require 'rbcurse/experimental/widgets/rscrollform' # tried, shows in all cases teh buttons but never gets control
31
+
32
+ KEY_TAB ||= 9
33
+ KEY_BTAB ||= 353
34
+ #include Ncurses # FFI 2011-09-8
35
+ include RubyCurses
36
+ module RubyCurses
37
+ extend self
38
+
39
+ Event = Struct.new( :tab, :index, :event)
40
+
41
+ # Multiple independent overlapping forms using the tabbed metaphor.
42
+ class TabbedButton < RubyCurses::RadioButton
43
+ attr_accessor :display_tab_on_traversal
44
+ def getvalue_for_paint
45
+ @text
46
+ end
47
+ def selected?
48
+ @variable.value == @value
49
+ end
50
+ ##
51
+ # highlight abd selected colors and attribs should perhaps be in a
52
+ # structure, so user can override easily
53
+ def repaint # tabbedbutton
54
+ $log.debug("TabbedBUTTon repaint : #{self.class()} fn:#{@form.name} r:#{@row} c:#{@col} #{getvalue_for_paint} gt #{@form.window.top} gl #{@form.window.left}" )
55
+ r,c = rowcol
56
+ attribs = @attrs
57
+ @highlight_foreground ||= $reversecolor
58
+ @highlight_background ||= $reversecolor # 0
59
+ _state = @state
60
+ _state = :SELECTED if @variable.value == @value
61
+ color = $datacolor
62
+ case _state
63
+ when :HIGHLIGHTED
64
+ $log.debug("TabbedBUTTon repaint : HIGHLIGHTED #{bgcolor}, #{color}, v: #{@value}" )
65
+ bgcolor = @highlight_background
66
+ color = @highlight_foreground
67
+ bgcolor = @bgcolor
68
+ color = :red #@color
69
+ attribs = Ncurses::A_BOLD
70
+ setrowcol r,c # show cursor on highlighted as we tab through
71
+ ## but when tabbing thru selected one, then selected one doesn't show cursor
72
+ when :SELECTED
73
+ $log.debug("TabbedBUTTon repaint : SELECTED #{bgcolor}, #{color}")
74
+ bgcolor = @bgcolor
75
+ color = @color
76
+ attribs = Ncurses::A_REVERSE
77
+ if @state == :HIGHLIGHTED
78
+ setrowcol r,c # show cursor on highlighted as we tab through
79
+ end
80
+ else
81
+ $log.debug("TabbedBUTTon repaint : ELSE #{bgcolor}, #{color}")
82
+ bgcolor = @bgcolor
83
+ color = @color
84
+ end
85
+ #bgcolor = @state==:HIGHLIGHTED ? @highlight_background : @bgcolor
86
+ #color = @state==:HIGHLIGHTED ? @highlight_foreground : @color
87
+ if bgcolor.is_a? String and color.is_a? String
88
+ color = ColorMap.get_color(color, bgcolor)
89
+ end
90
+ value = getvalue_for_paint
91
+ $log.debug("button repaint : r:#{r} #{@graphic.top} c:#{c} #{@graphic.left} color:#{color} bg #{bgcolor} v: #{value}, g: #{@graphic} ")
92
+ len = @display_length || value.length
93
+ # paint the tabs name in approp place with attribs
94
+ #@form.window.printstring r, c, "%-*s" % [len, value], color, attribs
95
+ #@graphic.printstring r+@graphic.top, c+@graphic.left, "%-*s" % [len, value], color, attribs
96
+ #@graphic.printstring r-@graphic.top, c-@graphic.left, "%-*s" % [len, value], color, attribs
97
+
98
+ ro = @graphic.top
99
+ co = @graphic.left
100
+ ro = 0
101
+ co = 0
102
+ # NOTE after removing scrollform I've replaced check of graphic with 0, Note if we revert
103
+ if _state == :HIGHLIGHTED
104
+ color = $datacolor
105
+ @graphic.printstring r+ro, c-1+co, ">", color, @attrs unless c-1 < 0 #@graphic.left
106
+ #@graphic.printstring r, c+len+1, "<", color, @attrs
107
+ else
108
+ color = $datacolor
109
+ @graphic.printstring r+ro, c-1+co, " ", color, @attrs unless c-1 < 0 #@graphic.left
110
+ #@graphic.printstring r, c+len+1, " ", color, @attrs
111
+ end
112
+ @graphic.printstring r+ro, c+co, "%-*s" % [len, value], color, attribs
113
+ @graphic.modified = true
114
+ # @form.window.mvchgat(y=r, x=c, max=len, Ncurses::A_NORMAL, bgcolor, nil)
115
+ # underline for the top tab buttons.
116
+ if @underline != nil
117
+ # 2011-10-17 cmmented
118
+ r -= @graphic.top # because of pad, remove if we go back to windows
119
+ c -= @graphic.left # because of pad, remove if we go back to windows
120
+ @graphic.mvchgat(y=r, x=c+@underline+0, max=1, Ncurses::A_BOLD|Ncurses::A_UNDERLINE, color, nil)
121
+ end
122
+ end
123
+ # trying to give the option so as we tab through buttons, the relevant tab opens
124
+ # but this is getting stuck on a tab and not going on
125
+ # fire() is causing the problem
126
+ # fire takes the focus into tab area so the next TAB goes back to first button
127
+ # due to current_tab = tab (so next key stroke goes to tab)
128
+ def on_enter
129
+ $log.debug " overridden on_enter of tabbedbutton #{@name}, foc #{@focussed} "
130
+ super
131
+ #@state = :HIGHLIGHTED if @focussed
132
+ $log.debug " overridden on_enter of tabbedbutton #{@name} state #{@state}, foc #{@focussed} "
133
+ $log.debug " calling fire overridden on_enter of tabbedbutton" if @display_tab_on_traversal
134
+ fire if @display_tab_on_traversal
135
+ end
136
+ # In order to get tab display as we traverse buttons, we need to tamper with KEY_DOWN
137
+ # since that's the only way of getting down to selected tab in this case.
138
+ def handle_key ch # tabbed button
139
+ case ch
140
+ when KEY_DOWN
141
+ # form will not do a next_field, it will ignore this
142
+ @state = :NORMAL # 2011-10-5
143
+ @form.repaint
144
+ return :NO_NEXT_FIELD
145
+ when KEY_RIGHT
146
+ ret = @form.select_next_field
147
+ ret = @form.select_first_field if ret == :NO_NEXT_FIELD
148
+ return ret
149
+ when KEY_LEFT
150
+ ret = @form.select_prev_field
151
+ ret = @form.select_last_field if ret == :NO_PREV_FIELD
152
+ return ret
153
+ when KEY_ENTER, 10, 13, 32 # added space bar also
154
+ if respond_to? :fire
155
+ fire
156
+ end
157
+ else
158
+ # all thrse will be re-evaluated by form
159
+ return :UNHANDLED
160
+ end
161
+ end
162
+ 0
163
+ end
164
+ ##
165
+ # extending Widget from 2009-10-08 18:45
166
+ # It should extend Widget so we can pop it in a form. In fact it should be in a form,
167
+ # we should not have tried to make it standalone like messagebox.
168
+ # This is the main TabbedPane widget that will be slung into a form
169
+ class TabbedPane < Widget
170
+ TAB_ROW_OFFSET = 3 # what row should tab start on (was 4 when printing subheader)
171
+ TAB_COL_OFFSET = 0 # what col should tab start on (to save space, flush on left)
172
+ dsl_accessor :button_type # ok, ok_cancel, yes_no
173
+ dsl_accessor :buttons # used if type :custom
174
+
175
+ # set to true if you want tabs to show as you traverse the tab buttons
176
+ dsl_accessor :display_tab_on_traversal
177
+
178
+ # creates a scrollable form so many buttons can be placed
179
+ # NOTE: this only works in a root window, I can't figure out why.
180
+ dsl_accessor :scrolling_allowed
181
+
182
+ attr_reader :selected_index
183
+ attr_reader :current_tab
184
+ attr_reader :window
185
+ def initialize form, aconfig={}, &block
186
+ @scrolling_allowed = false
187
+ super
188
+ @parent = form
189
+ @parentwin = form.window
190
+ @visible = true
191
+ @focusable= true
192
+ @tabs ||= []
193
+ @forms ||= []
194
+ @attr = nil
195
+ @current_form = nil
196
+ @current_tab = nil
197
+ @config = aconfig
198
+ @col_offset = 2; @row_offset = 1 # added 2010-01-10 22:54
199
+ @recreate_buttons = true
200
+ install_keys
201
+ @_events.push(*[:OPEN, :INSERT, :DELETE])
202
+ @on_main_form = true # 2011-10-4 we need to know this i think
203
+ end
204
+ def install_keys
205
+ @form.bind_key([?d, ?d]) { ix = highlighted_tab_index; repeatm { remove_tab(ix) } }
206
+ @form.bind_key(?u) { undelete_tab; }
207
+ @form.bind_key(?p) { paste_tab 0; } # paste before or at position
208
+ @form.bind_key(?P) { paste_tab 1; } # paste deleted tab after this one
209
+ @form.bind_key([?c, ?w]) { change_label }
210
+ @form.bind_key(?C) { change_label }
211
+ end
212
+ ##
213
+ # This is a public, user called method for appending a new tab
214
+ # This will be called several times for one TP.
215
+ # when adding tabs, you may use ampersand in text to create hotkey
216
+ # XXX adding a tab later does not influence buttons array,
217
+ def add_tab text, component = nil, aconfig={}, &block
218
+ index = @tabs.size
219
+ tab = insert_tab text, component, index, aconfig, &block
220
+ return tab
221
+ end
222
+ alias :add :add_tab
223
+
224
+ ## insert a component at given index
225
+ # index cannnot be greater than size of tab count
226
+ def insert_tab text, component, index, aconfig={}, &block
227
+ $log.debug " TAB insert #{text} at #{index} "
228
+ @tabs[index] = Tab.new(text, self, aconfig, &block)
229
+ tab = @tabs[index]
230
+
231
+ # trying out setting current tab when created 2011-10-4
232
+ # This situation happens when someone externally is setting data
233
+ # in a tab and calling its repaint. When user presses DOWN from button
234
+ # it was crashing. Old tab is the tab you see below. current_tab takes on
235
+ # either form or old_tab to distinguish whether we are on top buttons or
236
+ # inside a tab.
237
+ @old_tab = tab
238
+
239
+ tform = form(tab) # this could go inside Tab constructor now 2011-10-4
240
+ #tab.component = component unless component.nil? # changed on 2011-10-3 CLEAN
241
+ #component.form = tform unless component.nil? # changed on 2011-10-3 CLEAN
242
+ component.set_form( tform) unless component.nil? # changed on 2011-10-3
243
+ configure_component component unless component.nil?
244
+ tab.index = index # so i can undelete !!!
245
+ fire_event tab, index, :INSERT
246
+ @recreate_buttons = true
247
+ return tab
248
+ end
249
+ ## remove given tab based on index
250
+ # This does not unbind the key mapping, FIXME
251
+ # Currently, can be invoked by 'dd' over highlighted button
252
+ # XXX can append to deleted_tabs, then on insert or paste insert with splat.
253
+ def remove_tab index
254
+ @recreate_buttons = true
255
+ $log.debug " inside remove_tab with #{index}, #{@tabs.size} "
256
+ @deleted_tab = @tabs.delete_at(index) unless @tabs.size < index
257
+ # note this is the index it was at.
258
+ fire_event @deleted_tab, index, :DELETE
259
+ end
260
+ ##
261
+ # Move this fun stuff to a util class. TODO
262
+ # If tab deleted accidentally, undelete it
263
+ # Okay, i just can stop myself from having a little fun
264
+ def undelete_tab
265
+ return unless @deleted_tab
266
+ @recreate_buttons = true
267
+ @tabs.insert(@deleted_tab.index, @deleted_tab)
268
+ fire_event @deleted_tab, @deleted_tab.index, :INSERT
269
+ @deleted_tab = nil
270
+ $log.debug " undelete over #{@tabs.size} "
271
+ end
272
+ def paste_tab pos
273
+ return unless @deleted_tab
274
+ ix = highlighted_tab_index
275
+ return if ix == -1
276
+ @recreate_buttons = true
277
+ @deleted_tab.index = ix + pos
278
+ @tabs.insert(@deleted_tab.index, @deleted_tab)
279
+ fire_event @deleted_tab, @deleted_tab.index, :INSERT
280
+ @deleted_tab = nil
281
+ $log.debug " paste over #{@tabs.size} #{ix} + #{pos} "
282
+ end
283
+
284
+ ##
285
+ # prompts for a new label for a tab - taking care of mnemonics if ampersand present
286
+ # Currently, mapped to 'C' and 'cw' when cursor is on a label
287
+ # Perhaps some of this kind of utility stuff needs to go into a util class.
288
+ #
289
+ def change_label
290
+ ix = highlighted_tab_index
291
+ return if ix < 0
292
+ prompt = "Enter new label: "
293
+ label = @buttons[ix].text
294
+ config = {}
295
+ config[:default] = label.dup
296
+ maxlen = 10
297
+ ret, str = rbgetstr(@graphic, $error_message_row, $error_message_col, prompt, maxlen, config)
298
+ if ret == 0 and str != "" and str != label
299
+ @tabs[ix].text = str
300
+ @buttons[ix].text(str)
301
+ @recreate_buttons = true
302
+ end
303
+ end
304
+ ##
305
+ # returns the index of the tab cursor is on (not the one that is selected)
306
+ # @return [0..] index, or -1 if some error
307
+ def highlighted_tab_index
308
+ @form.widgets.each_with_index{ |w, ix|
309
+ return ix if w.state == :HIGHLIGHTED
310
+ }
311
+ return -1
312
+ end
313
+ def selected_tab_index
314
+ @form.widgets.each_with_index{ |w, ix|
315
+ return ix if w.selected?
316
+ }
317
+ return -1
318
+ end
319
+ ## remove all tabs
320
+ def remove_all
321
+ if !@buttons.empty?
322
+ @buttons.each {|e| @form.remove_widget(e) }
323
+ end
324
+ @buttons = []
325
+ @tabs = []
326
+ @recreate_buttons = true
327
+ end
328
+
329
+ ## return a form for use by program - if you want to put multiple items
330
+ # Otherwise just use add_component
331
+ # private - can't use externally
332
+ def configure_component component
333
+ #component.set_form @parent <<--- definitely NOT
334
+ #component.form = @parent # changed on 2011-10-2
335
+ component.rows_panned = component.cols_panned = 0
336
+ component.parent_component = self # added 2010-02-27 so offsets can go down ?
337
+
338
+ $log.debug "XXX: TABBED #{@row} #{@col} #{@height} #{@width} "
339
+ component.row ||= 0 # 2011-10-3 @row + TAB_ROW_OFFSET # 2
340
+ component.col ||= 0 #@col + TAB_COL_OFFSET
341
+ component.width ||= @width #@col + TAB_COL_OFFSET
342
+ component.height ||= @height - 2 #@col + TAB_COL_OFFSET
343
+ $log.debug "XXX: TABBED #{component.row} #{component.col} #{component.height} #{component.width} "
344
+
345
+ # current_form likely to be nil XXX
346
+ scr_top = component.row # for Pad, if Pad passed as in SplitPane
347
+ scr_left = component.col # for Pad, if Pad passed as in SplitPane
348
+ ho = TAB_ROW_OFFSET + 2 # 5
349
+ component.set_buffering(:target_window => @target_window || @parentwin, :form => @current_form, :bottom => @height-ho, :right => @width-2, :screen_top => scr_top, :screen_left => scr_left)
350
+ # if left nil, then we expand the comp
351
+ component.height ||= @height - (ho - 1) # 1 keeps lower border inside by 1
352
+ component.width ||= @width - 0 # 0 keeps it flush on right border
353
+
354
+
355
+ end
356
+ ## create a form for tab, if multiple components are to be placed inside tab.
357
+ # Tabbedpane has no control over placement and width etc of what's inside a form
358
+ def form tab
359
+ if !tab.has_form?
360
+ @forms << create_tab_form(tab)
361
+ tab.form = @forms.last
362
+ end
363
+ return tab.form
364
+ end
365
+
366
+ ## returns the index of the current / selected tab
367
+ ## @returns 0.. index of selected tab
368
+ def selected_tab_index
369
+ @tabs.index(@current_tab)
370
+ end
371
+
372
+ # private
373
+ def variable_set var, val
374
+ var = "@#{var}"
375
+ instance_variable_set(var, val)
376
+ end
377
+ # private
378
+ def configure(*val , &block)
379
+ case val.size
380
+ when 1
381
+ return @config[val[0]]
382
+ when 2
383
+ @config[val[0]] = val[1]
384
+ variable_set(val[0], val[1])
385
+ end
386
+ instance_eval &block if block_given?
387
+ end
388
+ ## this is a really wierd repaint method.
389
+ # First time it creates the TP window/form which contains the buttons.
390
+ # In future calls it really doesn't do anything.
391
+ # Deos it have nothing to paint, no borders to redraw, no repaint_required ???
392
+ def repaint
393
+ $log.debug " tabbedpane repaint "
394
+ @window || create_window
395
+ _recreate_buttons if @recreate_buttons
396
+ $log.debug " tabbedpane repaint #{@window.name} "
397
+ @window.show
398
+ @window.wrefresh # trying out FFI 2011-09-19 since form not being refreshed
399
+ #x set_buffer_modified()
400
+ end
401
+ def show
402
+ repaint
403
+ end
404
+ ## recreate all buttons
405
+ # We call this if even one is added : adv is we can space out accordinagly if the numbers increase
406
+ # We could also expand the pad here.
407
+ # Test it out with removing tabs to.
408
+ # XXX have to remove buttons from the form
409
+ def _recreate_buttons
410
+ $log.debug " inside recreate_buttons: #{@tabs.size} "
411
+ r = @row
412
+ col = @col + 1
413
+ @buttons ||= []
414
+ #
415
+ # NOTE: since we remove buttons, setting any events on them is useless !
416
+ #
417
+ if !@buttons.empty?
418
+ @buttons.each {|e| @form.remove_widget(e) }
419
+ end
420
+ @buttons = []
421
+ button_gap = 4
422
+ # the next line necessitates a clear on the pad
423
+ # button_gap = 1 if @tabs.size > 6 # quick dirty fix, we need something that checks fit
424
+ # we may also need to truncate text to fit
425
+
426
+ @buttonpad.wclear if @buttonpad # 2011-10-18
427
+ ## create a button for each tab
428
+ $tabradio = Variable.new # so we know which is highlighted
429
+ # 2011-10-17 seems objects on this form do not get windows offset
430
+ wco = @window.left
431
+ wro = @window.top
432
+ @tabs.each do |tab|
433
+ text = tab.text
434
+ $log.debug " TABS EACH #{text}, #{wro} #{wco} "
435
+ @buttons << RubyCurses::TabbedButton.new(@form) do
436
+ variable $tabradio
437
+ text text
438
+ name text
439
+ value text
440
+ row r + 1 #+ wro
441
+ col col #+ wco
442
+ end
443
+ col += text.length + button_gap
444
+ # if col exceeds pad_w then we need to expand pad
445
+ # but here we don't know that a pad is being used
446
+ $log.debug " button col #{col} "
447
+ # form = tab.form # changed 2011 2011-09-26
448
+ form = form(tab)
449
+ form.set_parent_buffer(@window) if form
450
+
451
+ b = @buttons.last
452
+ b.display_tab_on_traversal = @display_tab_on_traversal # 2011-10-4
453
+ tab._button(b) # too late, user needs this when tab is created FIXME
454
+ b.command(b, @form) {
455
+ $log.debug " calling tab.repaint,button_form_repaint from button press #{b.name} #{b.state} "
456
+ # form.rep essentially sees that buttons get correct attributes
457
+ # when triggering M-<char>. This button should get highlighted.
458
+ tab.repaint
459
+
460
+ # the on_leave of current button does not get fired, so it gets
461
+ # left in a HIGHLIGHTED state, so two can show highlighted at the same time 2011-10-5
462
+ #
463
+ @form.widgets.each { |tb| @form.on_leave(tb) if tb.state == :HIGHLIGHTED }
464
+
465
+ button_form_repaint #( b.state == :HIGHLIGHTED )
466
+ if @display_tab_on_traversal
467
+ # set as old tab so ONLY on going down this becomes current_tab
468
+ @old_tab = tab
469
+ else
470
+ # next line means next key is IMMED taken by the tab not main form
471
+ @current_tab = tab
472
+ end
473
+ $log.debug "TAB : form #{tab.form}, #{tab.form.widgets.first} "
474
+ c = tab.form.widgets.first
475
+ c.set_form_row; c.set_form_col
476
+ fire_event tab, tab.index, :OPEN
477
+ }
478
+ end
479
+ @recreate_buttons = false
480
+ # make the buttons visible now, not after next handle_key
481
+ @form.repaint
482
+ end
483
+ ## This form is for the tabbed buttons on top
484
+ def create_window
485
+ set_buffer_modified() # required still ??
486
+ # first create the main top window with the tab buttons on it.
487
+ $log.debug " TPane create_buff Top #{@row}, Left #{@col} H #{@height} W #{@width} "
488
+ #$log.debug " parentwin #{@parentwin.left} #{@parentwin.top} "
489
+
490
+ r = @row
491
+ c = @col
492
+ # NOTE: I had to remove ScrollForm since it would not work if
493
+ # window was not root window. This means I cannot have more buttons
494
+ # than fit on screen.
495
+
496
+
497
+ # tried out proper ScrollForm, it shows the buttons but does not take
498
+ # cursor there or show cursor there at all.
499
+
500
+ scrolling = @scrolling_allowed
501
+ if scrolling
502
+ @form = ScrollForm.new(@parentwin)
503
+ # 2011-10-18 trying to see why buttons won't print if window is not 0,0
504
+ #@form.parent_form = @parent # 2011-10-18 trying out why buttons not coming at correct place
505
+ #@form.add_cols = @parent.window.left
506
+ #@form.add_rows = @parent.window.top
507
+ offset = 1
508
+ @form.set_layout(1, @width, @row+offset, @col+offset)
509
+ @form.display_h = 1
510
+ @form.display_w = @width-3
511
+ @buttonpad = @form.create_pad
512
+ @buttonpad.name = "Window::TPTOPPAD" # 2010-02-02 20:01
513
+
514
+ else
515
+ @form = Form.new(@parentwin)
516
+ end
517
+
518
+
519
+ ## We will use the parent window, and not a pad. We will write absolute coordinates.
520
+ @window = @parentwin
521
+ color = $datacolor
522
+ # border around button bar. should this not be in scrollform as a border ? XXX
523
+ @window.print_border @row, @col, 2, @width, color #, Ncurses::A_REVERSE
524
+ @form.name = "Form::TPTOPFORM"
525
+ $log.debug("TP WINDOW TOP ? PAD MAIN FORM W:#{@window.name}, F:#{@form.name} ")
526
+ @form.parent_form = @parent ## 2010-01-21 15:55 TRYING OUT BUFFERED
527
+ @form.navigation_policy = :NON_CYCLICAL
528
+
529
+ Ncurses::Panel.update_panels
530
+ _recreate_buttons
531
+
532
+ @old_tab = @tabs.first
533
+ @old_tab.repaint if @old_tab
534
+ button_form_repaint true
535
+ @window.wrefresh ## ADDED 2009-11-02 23:29
536
+ #@buttons.first().fire unless @buttons.empty? # make the first form active to start with.
537
+ #@current_tab = nil # 2011-10-3 otherwise keys go to this form in the beginning
538
+ @current_tab = @form # 2011-10-4 buttons form should be first
539
+ end
540
+ #
541
+ # Handle placing control in first or last button.
542
+ # R
543
+ def on_enter
544
+ if $current_key == KEY_BTAB
545
+ c = @form.widgets.count-1
546
+ @form.select_field c
547
+ else
548
+ @form.select_field 0
549
+ end
550
+ end
551
+ def button_form_repaint flag = true
552
+ $log.debug " INSIDE button_form_repaint #{flag} "
553
+ if flag
554
+ # repaint form and refresh pad
555
+ @form.repaint
556
+ else
557
+ # only refresh pad
558
+ # - 2011-09-19 I don't think this is called, prolly give an error
559
+ @form.prefresh
560
+ end
561
+ end
562
+
563
+ ##
564
+ # This creates a form for the tab, in case we wish to put many components in it.
565
+ # Else just pass single components in add_tab.
566
+ # @params tab tab just created for which a form is required
567
+ # @return form - a pad based form
568
+ def create_tab_form tab
569
+
570
+ mtop = 0
571
+ mleft = 0
572
+ bottom_offset = 2 # 0 will overwrite bottom line, 1 will make another line for inner form
573
+ layout = { :height => @height-(mtop+bottom_offset), :width => @width, :top => mtop, :left => mleft }
574
+ window = VER::Pad.create_with_layout(layout)
575
+
576
+ form = RubyCurses::Form.new window
577
+
578
+ form.parent_form = @parent
579
+ form.add_cols = @col + 0
580
+ form.add_rows = @row + 2
581
+
582
+
583
+ form.navigation_policy = :NON_CYCLICAL
584
+ window.bkgd(Ncurses.COLOR_PAIR($datacolor));
585
+ window.box(0, 0);
586
+ window.mvwaddch(0, 0, Ncurses::ACS_LTEE) # beautify the corner 2010-02-06 19:35
587
+ window.mvwaddch(0, @width-1, Ncurses::ACS_RTEE)
588
+ window.mvwaddch(layout[:height]-1, 0, Ncurses::ACS_LTEE) # beautify the corner 2010-02-06 19:35
589
+ window.mvwaddch(layout[:height]-1, @width-1, Ncurses::ACS_RTEE)
590
+
591
+ ## this prints the tab name on top left
592
+ window.mvprintw(1,1, tab.text.tr('&', '')) if @print_subheader
593
+ window.name = "Tab::TAB-#{tab.text}"
594
+ form.name = "Form::TAB-#{tab.text}"
595
+ return form
596
+ end
597
+ ##
598
+ # added 2009-10-08 19:39 so it can be placed in a form
599
+ # @form is the top button form
600
+ # XXX stop this nonsense about current_form and current_tab
601
+ # TP should only be concerned with tabs. what happens inside is none of its business
602
+ def handle_key(ch) # tabbed pane TP
603
+
604
+
605
+ if @current_tab == @form # on main form
606
+ return :UNHANDLED if ch == ?\M-\C-i.getbyte(0) # alt-tab to exit
607
+ ret = @form.handle_key ch
608
+ $log.debug "TP HNDLE KEY got ret #{ret}, ch #{ch} "
609
+ ret ||= :UNHANDLED
610
+ if ret == :UNHANDLED
611
+ $log.warn "unhandled key in TP main #{ch} "
612
+ case ch
613
+ when KEY_RIGHT, KEY_LEFT, KEY_TAB, KEY_BTAB
614
+ when KEY_UP
615
+ when KEY_DOWN
616
+ when KEY_TAB
617
+ when KEY_BTAB
618
+ end
619
+ elsif ret == :NO_NEXT_FIELD
620
+ case ch
621
+ when KEY_TAB
622
+ return :UNHANDLED
623
+ when KEY_RIGHT, KEY_DOWN, ?j.getbyte(0)
624
+
625
+ @current_tab = @old_tab
626
+ if @current_tab
627
+ @current_tab.set_focus :FIRST
628
+ else
629
+ alert "Need to press enter on button"
630
+ if @current_tab.nil? ##or @current_tab.is_a? Form
631
+ w = @form.widgets.first
632
+ w.fire if w.respond_to? :fire
633
+ end
634
+ end
635
+ return 0
636
+ when KEY_UP
637
+ when KEY_BTAB
638
+ end
639
+ return ret
640
+ elsif ret == :NO_PREV_FIELD
641
+ case ch
642
+ when KEY_LEFT, KEY_BTAB, ?k.getbyte(0)
643
+ $log.debug "LEFT BTAB when no previous field"
644
+ return :UNHANDLED
645
+ end
646
+ return ret
647
+ end
648
+ return 0
649
+ end
650
+
651
+ $log.debug " handle_key in tabbed pane got : #{ch}, #{@current_tab}, f: #{@form} "
652
+ @current_tab ||= @form # first we cycle buttons
653
+ $log.debug " handle_key in tabbed pane got : #{ch}, giving to #{@current_tab} "
654
+ # needs to go to component
655
+ ret = @current_tab.handle_key(ch)
656
+ $log.debug " -- form.handle_key in tabbed pane got ret : #{ret} , #{@current_tab} , #{ch} "
657
+
658
+ # components will usually return UNHANDLED for a tab or btab
659
+ # We need to convert it so the main form can use it
660
+ if @current_tab != @form
661
+ if ret == :UNHANDLED
662
+ if ch == KEY_TAB #or ch == KEY_DOWN
663
+ ret = :NO_NEXT_FIELD
664
+ elsif ch == KEY_BTAB or ch == ?k.getbyte(0) #or ch == KEY_UP # btab
665
+ ret = :NO_PREV_FIELD
666
+ end
667
+ end
668
+ else
669
+ # key down pressed in top form, go to tab
670
+ if ch == KEY_DOWN
671
+ ret = :NO_NEXT_FIELD
672
+ end
673
+ end
674
+
675
+ case ret
676
+ when :NO_NEXT_FIELD
677
+ #alert "came to no nex field" # CLEAN
678
+ if @current_tab != @form
679
+ #alert "case 1 no next field req first" # changed on 2011-10-2 # 2011-10-04 16:12:34
680
+ ## if no next field on a subform go to first button of main form
681
+ @old_tab = @current_tab
682
+ @current_tab = @form
683
+ @form.req_first_field
684
+
685
+ else
686
+ # on top button panel - no more buttons, go to tabs first field
687
+ if @old_tab # in case of empty tabbed pane old_tab was nil
688
+ #alert "case 2 no next field set focus" # CLEANUP # 2011-10-04 16:12:46
689
+ @current_tab = @old_tab
690
+ @current_tab.set_focus :FIRST
691
+ end
692
+ end
693
+ when :NO_PREV_FIELD
694
+ if @current_tab != @form
695
+ $log.debug "TP 1 no prev field - going to last button "
696
+ @old_tab = @current_tab
697
+ @current_tab = @form
698
+ @form.req_last_field
699
+ else
700
+ # on top button panel - no prev buttons, go to tabs last field
701
+ if @old_tab # in case of one tab
702
+ @current_tab = @old_tab
703
+ @current_tab.set_focus :LAST
704
+ end
705
+ end
706
+ when :UNHANDLED
707
+ $log.debug " unhandled in tabbed pane #{ch}"
708
+ ret = @form.process_key ch, self # field
709
+
710
+ return ret if ret == :UNHANDLED
711
+ end
712
+ if @buttonpad && @buttonpad.modified
713
+ button_form_repaint
714
+ end
715
+ end
716
+ ##
717
+ # ensure that the pads are being destroyed, although we've not found a way.
718
+ def destroy
719
+ @window.destroy
720
+ @forms.each { |f| w = f.window; w.destroy unless w.nil? }
721
+ end
722
+ def create_buttons
723
+ case @button_type.to_s.downcase
724
+ when "ok"
725
+ make_buttons ["&OK"]
726
+ when "ok_cancel" #, "input", "list", "field_list"
727
+ make_buttons %w[&OK &Cancel]
728
+ when "yes_no"
729
+ make_buttons %w[&Yes &No]
730
+ when "yes_no_cancel"
731
+ make_buttons ["&Yes", "&No", "&Cancel"]
732
+ when "custom"
733
+ raise "Blank list of buttons passed to custom" if @buttons.nil? or @buttons.size == 0
734
+ make_buttons @buttons
735
+ else
736
+ $log.debug "No buttontype passed for creating tabbedpane. Using default (OK)"
737
+ make_buttons ["&OK"]
738
+ end
739
+ end
740
+ def make_buttons names
741
+ $log.debug "XXX: came to TP make lower buttons FORM #{@form.name} "
742
+ total = names.inject(0) {|total, item| total + item.length + 4}
743
+ bcol = center_column total
744
+
745
+ brow = @layout[:height]-2 # check for < 0
746
+ button_ct=0
747
+ names.each_with_index do |bname, ix|
748
+ text = bname
749
+ #underline = @underlines[ix] if !@underlines.nil?
750
+
751
+ button = Button.new @form do
752
+ text text
753
+ name bname
754
+ row brow
755
+ col bcol
756
+ #underline underline
757
+ highlight_background $reversecolor
758
+ color $datacolor
759
+ bgcolor $datacolor
760
+ end
761
+ index = button_ct
762
+ button.command { |form| @selected_index = index; @stop = true; $log.debug "Pressed Button #{bname}";}
763
+ button_ct += 1
764
+ bcol += text.length+6
765
+ end
766
+ end
767
+ def center_column textlen
768
+ width = @layout[:width] # check for 0 XXX
769
+ return (width-textlen)/2
770
+ end
771
+ def fire_event tab, index, event
772
+ # experimenting with structs, earlier we've used classes
773
+ if @tabbedpane_event.nil?
774
+ @tabbedpane_event = Event.new
775
+ end
776
+ @tabbedpane_event.tab = tab
777
+ @tabbedpane_event.index = index
778
+ @tabbedpane_event.event = event
779
+ fire_handler event, @tabbedpane_event
780
+ end
781
+
782
+ ##
783
+ # nested class tab
784
+ # A user created tab, with its own form
785
+ class Tab
786
+ attr_accessor :text
787
+ attr_reader :config
788
+ attr_reader :component # 2011-10-4 DEPRECATED
789
+ #attr_accessor :form
790
+ attr_accessor :parent_component
791
+ attr_accessor :index
792
+ attr_accessor :button # so you can set an event on it 2011-10-4
793
+ def initialize text, parent_component, aconfig={}, &block
794
+ @text = text
795
+ @config = aconfig
796
+ @parent_component = parent_component
797
+ @config.each_pair { |k,v| variable_set(k,v) }
798
+ instance_eval &block if block_given?
799
+ end
800
+ ## add a single component to the tab
801
+ # Calling this a second time will overwrite the existing component
802
+ # @deprecated since 2011-10-4 1.3.1
803
+ def component=(component)
804
+ @form.add_widget component
805
+ raise "Component cannot be null" unless component
806
+ raise "Component already associated with a form. Do not pass form in constructor." unless component.form.nil?
807
+ $log.debug " calling configure component "
808
+ @parent_component.configure_component component
809
+ #@component = component # changed on 2011-10-3
810
+ end
811
+ # @deprecated since 2011-10-4 1.3.1
812
+ def remove_component
813
+ @form.widgets.pop # 2011-10-4
814
+ #@component = nil
815
+ end
816
+ # private
817
+ def variable_set var, val
818
+ var = "@#{var}"
819
+ instance_variable_set(var, val)
820
+ end
821
+ # tab should handle key instead of TP.
822
+ # Pass to component or form
823
+ def handle_key ch # Tab
824
+ #kh = @component || @form 2011-10-4
825
+ kh = @form
826
+ $log.debug "DEBUG : handle_key Tab giving key to ( #{kh} ) "
827
+ ret = kh.handle_key(ch)
828
+ $log.debug "DEBUG : handle_key Tab ( #{kh} )got ret #{ret} "
829
+ # forms seem to returning a nil when the pad has been updated. We need to copy it
830
+ ret ||= 0
831
+ if ret == :UNHANDLED
832
+ $log.debug "tab handle_key returns UNHANDLED" if $log.debug?
833
+ #alert "Inside unhandled case, tab's for did not handle key"
834
+ #@component.repaint if @component
835
+ #@form.window.refresh
836
+ #$log.debug "DEBUG calling display form(false) from handle_key XXXX" if @form
837
+ #display_form false if @form # this caused others to go blank 2011-09-26
838
+ elsif ret != :UNHANDLED # FFI trying out, since forms with components not displaying changes
839
+ display_form false if @form # required for multi field forms, not single ones
840
+ end
841
+ # XXX i need to call repaint of compoent if updated !!
842
+ return ret
843
+ end
844
+ def repaint # Tab
845
+ if @form
846
+ $log.debug "DEBUG calling display form(true) from repaint XXX" if $log.debug?
847
+ display_form
848
+ else
849
+ # pls use tp.form(tab) to get form explicity.
850
+ # It could come here if tab precreated and user is yet to assign a component.
851
+ # or has removed component and not yet set a new one.
852
+ $log.error "Got neither component nor form."
853
+ $log.error "Programmer error. A change in Tabbedpane requires you to create form explicitly using form = tpane.form(tab) syntax"
854
+ end
855
+ end
856
+ ## Set focus on a component or form field when a user has tabbed off the last or first button
857
+ def set_focus first_last
858
+ if !@form.nil?
859
+ # move to first field of existing form
860
+ #@current_form = @current_tab.form # 2010-02-27 20:22
861
+ $log.debug " calling display form(true) from handle_key NO_NEXT_FIELD: #{first_last} "
862
+ first_last == :FIRST ? @form.req_first_field : @form.req_last_field
863
+ display_form
864
+ if @form.widgets.count == 1 # not working trying to get cursor to show in first form.
865
+ component = @form.widgets.first
866
+ component.set_form_row
867
+ component.set_form_col
868
+ end
869
+ else
870
+ raise "Form nil. Component cannot be directly added any longer. Pls use form"
871
+ end
872
+ end
873
+ # On a tabbed button press, this will display the relevant form
874
+ # On why I am directyl calling copywin and not using copy_pad_to_win etc
875
+ #+ those require setting top and left. However, while printing a pad, top and left are reduced and so
876
+ #+ must be absolute r and c. But inside TP, objects have a relative coord. So the print functions
877
+ #+ were failing silently, and i was wondering why nothing was printing.
878
+ # XXX move this tab in tab.repaint and let tab decide based on component or form
879
+ # if component then pad = component.get_buffer
880
+ def display_form flag = true
881
+ return if @form.nil?
882
+ form = @form
883
+ if form.is_a? RubyCurses::Form # tempo XXX since there are components
884
+ pad = form.window
885
+ else
886
+ return
887
+ end
888
+ pc = @parent_component
889
+ form.repaint if flag # added 2009-11-03 23:27 paint widgets in inside form
890
+ $log.debug " TP display form(#{flag}) before pad copy: #{pad.name}, set_backing: #{form}: #{form.name} parent: #{@parent_component} : #{pc.row} , #{pc.col}. #{pc.height} , #{pc.width}: repaint flag #{flag} "
891
+ ret = -1
892
+ pminr = pminc = 0
893
+ r = pc.row + 2
894
+ c = pc.col + 0
895
+ border_width = 0
896
+ maxr = pc.height() - 3
897
+ maxc = pc.width() - 1
898
+ $log.debug " ret = pad.copywin(pc.window.get_window, #{pminr}, #{pminc}, #{r}, #{c}, r+ #{maxr} - border_width, c+ #{maxc} -border_width,0). W:#{pc.window}, #{pc.window.get_window} "
899
+ ret = pad.copywin(pc.window.get_window, pminr, pminc, r, c, r+maxr-border_width, c+maxc-border_width,0)
900
+ $log.debug " display form after pad copy #{ret}. #{form.name} "
901
+ #pad.mvhline( 0,0, Ncurses::ACS_HLINE, pad.width-1)
902
+ pad.mvwaddch(0, 0, Ncurses::ACS_LTEE) # beautify the corner 2010-02-06 19:35
903
+ pad.mvwaddch(0, pad.width-1, Ncurses::ACS_RTEE)
904
+ end
905
+
906
+ # 2011-09-19 @since 1.3.0 changed so that calling form throws an exception
907
+ # if it's nil. Please use Tabbedpane's form() to get a form. This throws an exception
908
+ # so i can catch old programs that use the incorrect method
909
+ def form(*val)
910
+ if val.empty?
911
+ return @form if @form
912
+ raise "Form is nil. You may be using deprecated method. Use @tp.form, not @tab.form"
913
+ else
914
+ #raise ArgumentError "form should be ... " if val[0] ...
915
+ oldvalue = @form
916
+ @form = val[0]
917
+ end
918
+ self
919
+ end
920
+ # used by TP to set form
921
+ def form=(val)
922
+ @form = val
923
+ end
924
+ # used by TP to check form, since the other methods throws an exception
925
+ def has_form?
926
+ !@form.nil?
927
+ end
928
+
929
+ # @private avoid external use
930
+ def _button(b)
931
+ @button = b
932
+ end
933
+ end # class Tab
934
+
935
+ end # class Tabbedpane
936
+
937
+ ## An extension of Form that only displays and focuses on visible widgets
938
+ # This is minimal, and is being expanded upon as a separate class in rscrollform.rb
939
+ #
940
+ class ScrollForm < RubyCurses::Form
941
+ attr_accessor :pmincol # advance / scroll columns
942
+ attr_accessor :pminrow # advance / scroll rows (vertically)
943
+ attr_accessor :display_w
944
+ attr_accessor :display_h
945
+ attr_accessor :scroll_ctr
946
+ attr_reader :orig_top, :orig_left
947
+ attr_reader :window
948
+ attr_accessor :name
949
+ attr_reader :cols_panned, :rows_panned
950
+ def initialize win, &block
951
+ @target_window = win
952
+ super
953
+ @pminrow = @pmincol = 0
954
+ @scroll_ctr = 2
955
+ @cols_panned = @rows_panned = 0
956
+ end
957
+
958
+ def set_layout(h, w, t, l)
959
+ @pad_h = h
960
+ @pad_w = w
961
+ @top = t
962
+ @left = l
963
+ @top += @target_window.top
964
+ @left += @target_window.left
965
+ @orig_top = @top
966
+ @orig_left = @left
967
+ end
968
+ def create_pad
969
+ r = @top
970
+ c = @left
971
+ layout = { :height => @pad_h, :width => @pad_w, :top => r, :left => c }
972
+ @window = VER::Pad.create_with_layout(layout)
973
+
974
+ @window.name = "Pad::ScrollPad"
975
+ @name = "Form::ScrollForm"
976
+ return @window
977
+ end
978
+
979
+ ## ScrollForm handle key, scrolling
980
+ def handle_key ch
981
+ #alert("SCROLLFORM #{ch} , ai: #{@active_index} , #{get_current_field.name} ")
982
+ $log.debug " inside ScrollForm handlekey #{ch} "
983
+ # do the scrolling thing here top left prow and pcol of pad to be done
984
+ # # XXX TODO check whether we can scroll before incrementing esp cols_panned etc
985
+ case ch
986
+ when ?\M-l.getbyte(0)
987
+ return false if !validate_scroll_col(@pmincol + @scroll_ctr)
988
+ @pmincol += @scroll_ctr # some check is required or we'll crash
989
+ @cols_panned -= @scroll_ctr
990
+ $log.debug " handled ch M-l in ScrollForm"
991
+ @window.modified = true
992
+ return 0
993
+ when ?\M-h.getbyte(0)
994
+ return false if !validate_scroll_col(@pmincol - @scroll_ctr)
995
+ @pmincol -= @scroll_ctr # some check is required or we'll crash
996
+ @cols_panned += @scroll_ctr
997
+ $log.debug " handled ch M-h in ScrollForm"
998
+ @window.modified = true
999
+ return 0
1000
+ when ?\M-n.getbyte(0)
1001
+ return false if !validate_scroll_row(@pminrow + @scroll_ctr)
1002
+ @pminrow += @scroll_ctr # some check is required or we'll crash
1003
+ @rows_panned -= @scroll_ctr
1004
+ @window.modified = true
1005
+ return 0
1006
+ when ?\M-p.getbyte(0)
1007
+ return false if !validate_scroll_row(@pminrow - @scroll_ctr)
1008
+ @pminrow -= @scroll_ctr # some check is required or we'll crash
1009
+ @rows_panned += @scroll_ctr
1010
+ @window.modified = true
1011
+ return 0
1012
+ end
1013
+ case ch
1014
+ when ?j.getbyte(0)
1015
+ ch = KEY_DOWN
1016
+ when ?k.getbyte(0)
1017
+ ch = KEY_BTAB
1018
+ @window.ungetch(KEY_BTAB)
1019
+ return 0
1020
+ when ?h.getbyte(0)
1021
+ ch = KEY_LEFT
1022
+ when ?l.getbyte(0)
1023
+ ch = KEY_RIGHT
1024
+ end
1025
+
1026
+ super
1027
+ end
1028
+ def repaint
1029
+ $log.debug " scrollForm repaint calling parent"
1030
+ super
1031
+ prefresh
1032
+ @window.modified = false
1033
+ end
1034
+ def prefresh
1035
+ ## reduce so we don't go off in top+h and top+w
1036
+ $log.debug " start ret = @buttonpad.prefresh( #{@pminrow} , #{@pmincol} , #{@top} , #{@left} , top + #{@display_h} left + #{@display_w} ) "
1037
+ if @pminrow + @display_h > @orig_top + @pad_h
1038
+ $log.debug " if #{@pminrow} + #{@display_h} > #{@orig_top} +#{@pad_h} "
1039
+ $log.debug " ERROR 1 "
1040
+ #return -1
1041
+ end
1042
+ if @pmincol + @display_w > @orig_left + @pad_w
1043
+ $log.debug " if #{@pmincol} + #{@display_w} > #{@orig_left} +#{@pad_w} "
1044
+ $log.debug " ERROR 2 "
1045
+ return -1
1046
+ end
1047
+ # actually if there is a change in the screen, we may still need to allow update
1048
+ # but ensure that size does not exceed
1049
+ if @top + @display_h > @orig_top + @pad_h
1050
+ $log.debug " if #{@top} + #{@display_h} > #{@orig_top} +#{@pad_h} "
1051
+ $log.debug " ERROR 3 "
1052
+ return -1
1053
+ end
1054
+ if @left + @display_w > @orig_left + @pad_w
1055
+ $log.debug " if #{@left} + #{@display_w} > #{@orig_left} +#{@pad_w} "
1056
+ $log.debug " ERROR 4 "
1057
+ return -1
1058
+ end
1059
+ # maybe we should use copywin to copy onto @target_window
1060
+ $log.debug " ret = @buttonpad.prefresh( #{@pminrow} , #{@pmincol} , #{@top} , #{@left} , #{@top} + #{@display_h}, #{@left} + #{@display_w} ) "
1061
+ omit = 0
1062
+ # this works but if want to avoid copying border
1063
+ ret = @window.prefresh(@pminrow, @pmincol, @top, @left, @top + @display_h , @left + @display_w)
1064
+
1065
+ $log.debug " ret = #{ret} "
1066
+ # need to refresh the form after repaint over
1067
+ end
1068
+ def validate_scroll_row minrow
1069
+ return false if minrow < 0
1070
+ if minrow + @display_h > @orig_top + @pad_h
1071
+ $log.debug " if #{minrow} + #{@display_h} > #{@orig_top} +#{@pad_h} "
1072
+ $log.debug " ERROR 1 "
1073
+ return false
1074
+ end
1075
+ return true
1076
+ end
1077
+ def validate_scroll_col mincol
1078
+ return false if mincol < 0
1079
+ if mincol + @display_w > @orig_left + @pad_w
1080
+ $log.debug " if #{mincol} + #{@display_w} > #{@orig_left} +#{@pad_w} "
1081
+ $log.debug " ERROR 2 "
1082
+ return false
1083
+ end
1084
+ return true
1085
+ end
1086
+ # when tabbing through buttons, we need to account for all that panning/scrolling goin' on
1087
+ # Either of r or c can be nil (usually one will be)
1088
+ def setrowcol r, c
1089
+ # aha ! here's where i can check whether the cursor is falling off the viewable area
1090
+ if c
1091
+ if c+@cols_panned < @orig_left
1092
+ # this essentially means this widget (button) is not in view, its off to the left
1093
+ $log.debug " setrowcol OVERRIDE #{c} #{@cols_panned} < #{@orig_left} "
1094
+ $log.debug " aborting settrow col for now"
1095
+ return
1096
+ end
1097
+ if c+@cols_panned > @orig_left + @display_w
1098
+ # this essentially means this button is not in view, its off to the right
1099
+ $log.debug " setrowcol OVERRIDE #{c} #{@cols_panned} > #{@orig_left} + #{@display_w} "
1100
+ $log.debug " aborting settrow col for now"
1101
+ return
1102
+ end
1103
+ end # if c
1104
+ rr = r.nil? ? nil : r+@rows_panned
1105
+ cc = c.nil? ? nil : c+@cols_panned
1106
+ super rr, cc
1107
+ end
1108
+ def add_widget w
1109
+ super
1110
+ #$log.debug " inside add_widget #{w.name} pad w #{@pad_w} #{w.col} "
1111
+ if w.col >= @pad_w
1112
+ @pad_w += 10 # XXX currently just a guess value, we need length and maybe some extra
1113
+ @window.wresize(@pad_h, @pad_w)
1114
+ end
1115
+ end
1116
+ ## Is a component visible, typically used to prevent traversal into the field
1117
+ # @returns [true, false] false if components has scrolled off
1118
+ def visible? component
1119
+ r, c = component.rowcol
1120
+ return false if c+@cols_panned < @orig_left
1121
+ return false if c+@cols_panned > @orig_left + @display_w
1122
+ # XXX TODO for rows UNTESTED for rows
1123
+ return false if r + @rows_panned < @orig_top
1124
+ return false if r + @rows_panned > @orig_top + @display_h
1125
+
1126
+ return true
1127
+ end
1128
+ # returns index of first visible component. Currently using column index
1129
+ # I am doing this for horizontal scrolling presently
1130
+ # @return [index, -1] -1 if none visible, else index/offset
1131
+ def first_visible_component_index
1132
+ @widgets.each_with_index do |w, ix|
1133
+ return ix if visible?(w)
1134
+ end
1135
+ return -1
1136
+ end
1137
+ def last_visible_component_index
1138
+ ret = -1
1139
+ @widgets.each_with_index do |w, ix|
1140
+ $log.debug " reverse last vis #{ix} , #{w} : #{visible?(w)} "
1141
+ ret = ix if visible?(w)
1142
+ end
1143
+ return ret
1144
+ end
1145
+ def req_first_field
1146
+ select_field(first_visible_component_index)
1147
+ end
1148
+ def req_last_field
1149
+ select_field(last_visible_component_index)
1150
+ end
1151
+ def focusable?(w)
1152
+ w.focusable and visible?(w)
1153
+ end
1154
+
1155
+ end # class ScrollF
1156
+
1157
+
1158
+ end # module