rbcurse 0.1.3 → 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. data/CHANGELOG +126 -0
  2. data/Manifest.txt +53 -20
  3. data/README.markdown +423 -0
  4. data/Rakefile +3 -1
  5. data/examples/keytest.rb +177 -0
  6. data/examples/mpad2.rb +156 -0
  7. data/examples/newtesttabp.rb +121 -0
  8. data/examples/rfe.rb +48 -10
  9. data/examples/rfe_renderer.rb +4 -4
  10. data/examples/rvimsplit.rb +376 -0
  11. data/examples/sqlc.rb +97 -106
  12. data/examples/sqlm.rb +446 -0
  13. data/examples/test1.rb +4 -4
  14. data/examples/test2.rb +12 -12
  15. data/examples/testchars.rb +140 -0
  16. data/examples/testkeypress.rb +9 -4
  17. data/examples/testmulticomp.rb +72 -0
  18. data/examples/testscroller.rb +136 -0
  19. data/examples/testscrolllb.rb +86 -0
  20. data/examples/testscrollp.rb +87 -0
  21. data/examples/testscrollta.rb +80 -0
  22. data/examples/testscrolltable.rb +166 -0
  23. data/examples/testsplit.rb +87 -0
  24. data/examples/testsplit2.rb +123 -0
  25. data/examples/testsplit3.rb +215 -0
  26. data/examples/testsplit3_1.rb +244 -0
  27. data/examples/testsplit3a.rb +215 -0
  28. data/examples/testsplit3b.rb +237 -0
  29. data/examples/testsplitta.rb +148 -0
  30. data/examples/testsplittv.rb +142 -0
  31. data/examples/testsplittvv.rb +144 -0
  32. data/examples/testtable.rb +1 -1
  33. data/examples/testtabp.rb +3 -2
  34. data/examples/testtestw.rb +69 -0
  35. data/examples/testtodo.rb +5 -3
  36. data/examples/testtpane.rb +203 -0
  37. data/examples/testtpane2.rb +145 -0
  38. data/examples/testtpanetable.rb +199 -0
  39. data/examples/viewtodo.rb +5 -3
  40. data/lib/rbcurse.rb +1 -1
  41. data/lib/rbcurse/celleditor.rb +2 -2
  42. data/lib/rbcurse/colormap.rb +5 -5
  43. data/lib/rbcurse/defaultlistselectionmodel.rb +3 -3
  44. data/lib/rbcurse/io.rb +663 -0
  45. data/lib/rbcurse/listeditable.rb +306 -0
  46. data/lib/rbcurse/listkeys.rb +15 -15
  47. data/lib/rbcurse/listscrollable.rb +168 -27
  48. data/lib/rbcurse/mapper.rb +35 -13
  49. data/lib/rbcurse/rchangeevent.rb +28 -0
  50. data/lib/rbcurse/rform.rb +845 -0
  51. data/lib/rbcurse/rlistbox.rb +144 -34
  52. data/lib/rbcurse/rmessagebox.rb +10 -5
  53. data/lib/rbcurse/rmulticontainer.rb +325 -0
  54. data/lib/rbcurse/rmultitextview.rb +306 -0
  55. data/lib/rbcurse/rscrollform.rb +369 -0
  56. data/lib/rbcurse/rscrollpane.rb +511 -0
  57. data/lib/rbcurse/rsplitpane.rb +820 -0
  58. data/lib/rbcurse/rtabbedpane.rb +737 -109
  59. data/lib/rbcurse/rtabbedwindow.rb +326 -0
  60. data/lib/rbcurse/rtable.rb +220 -64
  61. data/lib/rbcurse/rtextarea.rb +340 -181
  62. data/lib/rbcurse/rtextview.rb +237 -101
  63. data/lib/rbcurse/rviewport.rb +203 -0
  64. data/lib/rbcurse/rwidget.rb +919 -95
  65. data/lib/rbcurse/scrollable.rb +7 -7
  66. data/lib/rbcurse/selectable.rb +4 -4
  67. data/lib/rbcurse/table/tablecellrenderer.rb +3 -0
  68. data/lib/rbcurse/undomanager.rb +181 -0
  69. data/lib/rbcurse/vieditable.rb +100 -0
  70. data/lib/ver/window.rb +471 -21
  71. metadata +66 -22
  72. data/README.txt +0 -312
  73. data/examples/testd.db +0 -0
  74. data/examples/todocsv.csv +0 -28
@@ -1,13 +1,25 @@
1
1
  =begin
2
2
  * Name: tabbed pane: can have multiple forms overlapping.
3
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
4
6
  * Author: rkumar
5
7
 
6
8
  --------
7
- * Date: 2008-12-13 13:06
9
+ * Date: 2009-10-25 12:05
8
10
  * License:
9
11
  Same as Ruby's License (http://www.ruby-lang.org/LICENSE.txt)
10
12
 
13
+ * 2010-02-28 09:47 - major cleanup and rewrite.
14
+ - Allow adding of component (in addition to form)
15
+ - Ideally, even form should be created and managed itself, why should TP have to repaint it?
16
+
17
+ NOTE:
18
+ Tp now does not create a form by default, since awefun you may want to just put in one component.
19
+ Pls use tp.form(tab) to get a form associated with the tab.
20
+ 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.
21
+ #
22
+ # TODO : disable/hide tab ???
11
23
  =end
12
24
  require 'rubygems'
13
25
  require 'ncurses'
@@ -19,19 +31,21 @@ include RubyCurses
19
31
  module RubyCurses
20
32
  extend self
21
33
 
22
- # TODO : insert_tab, remove_tab, disable/hide tab
23
- # Hotkeys should be defined with ampersand, too.
24
- #
34
+ Event = Struct.new( :tab, :index, :event)
35
+
25
36
  # Multiple independent overlapping forms using the tabbed metaphor.
26
37
  class TabbedButton < RubyCurses::RadioButton
27
38
  def getvalue_for_paint
28
39
  @text
29
40
  end
41
+ def selected?
42
+ @variable.value == @value
43
+ end
30
44
  ##
31
45
  # highlight abd selected colors and attribs should perhaps be in a
32
46
  # structure, so user can override easily
33
47
  def repaint # tabbedbutton
34
- # $log.debug("BUTTon repaint : #{self.class()} r:#{@row} c:#{@col} #{getvalue_for_paint}" )
48
+ $log.debug("TabbedBUTTon repaint : #{self.class()} r:#{@row} c:#{@col} #{getvalue_for_paint}" )
35
49
  r,c = rowcol
36
50
  attribs = @attrs
37
51
  @highlight_foreground ||= $reversecolor
@@ -40,16 +54,24 @@ module RubyCurses
40
54
  _state = :SELECTED if @variable.value == @value
41
55
  case _state
42
56
  when :HIGHLIGHTED
57
+ $log.debug("TabbedBUTTon repaint : HIGHLIGHTED #{bgcolor}, #{color}, v: #{@value}" )
43
58
  bgcolor = @highlight_background
44
59
  color = @highlight_foreground
45
60
  bgcolor = @bgcolor
46
61
  color = @color
47
62
  attribs = Ncurses::A_BOLD
63
+ setrowcol r,c # show cursor on highlighted as we tab through
64
+ ## but when tabbing thru selected one, then selected one doesn't show cursor
48
65
  when :SELECTED
66
+ $log.debug("TabbedBUTTon repaint : SELECTED #{bgcolor}, #{color}")
49
67
  bgcolor = @bgcolor
50
68
  color = @color
51
69
  attribs = Ncurses::A_REVERSE
70
+ if @state == :HIGHLIGHTED
71
+ setrowcol r,c # show cursor on highlighted as we tab through
72
+ end
52
73
  else
74
+ $log.debug("TabbedBUTTon repaint : ELSE #{bgcolor}, #{color}")
53
75
  bgcolor = @bgcolor
54
76
  color = @color
55
77
  end
@@ -59,48 +81,234 @@ module RubyCurses
59
81
  color = ColorMap.get_color(color, bgcolor)
60
82
  end
61
83
  value = getvalue_for_paint
62
- # $log.debug("button repaint : r:#{r} c:#{c} col:#{color} bg #{bgcolor} v: #{value} ")
84
+ $log.debug("button repaint : r:#{r} #{@graphic.top} c:#{c} #{@graphic.left} color:#{color} bg #{bgcolor} v: #{value}, g: #{@graphic} ")
63
85
  len = @display_length || value.length
64
- @form.window.printstring r, c, "%-*s" % [len, value], color, attribs
86
+ # paint the tabs name in approp place with attribs
87
+ #@form.window.printstring r, c, "%-*s" % [len, value], color, attribs
88
+ #@graphic.printstring r+@graphic.top, c+@graphic.left, "%-*s" % [len, value], color, attribs
89
+ #@graphic.printstring r-@graphic.top, c-@graphic.left, "%-*s" % [len, value], color, attribs
90
+ @graphic.printstring r, c, "%-*s" % [len, value], color, attribs
91
+ @graphic.modified = true
65
92
  # @form.window.mvchgat(y=r, x=c, max=len, Ncurses::A_NORMAL, bgcolor, nil)
93
+ # underline for the top tab buttons.
66
94
  if @underline != nil
67
- # changed +1 to +0 on 2008-12-15 21:23 pls check.
68
- @form.window.mvchgat(y=r, x=c+@underline+0, max=1, Ncurses::A_BOLD|Ncurses::A_UNDERLINE, color, nil)
95
+ r -= @graphic.top # because of pad, remove if we go back to windows
96
+ c -= @graphic.left # because of pad, remove if we go back to windows
97
+ @graphic.mvchgat(y=r, x=c+@underline+0, max=1, Ncurses::A_BOLD|Ncurses::A_UNDERLINE, color, nil)
69
98
  end
70
99
  end
100
+ # trying to give the option so as we tab through buttons, the relevant tab opens
101
+ # but this is getting stuck on a tab and not going on
102
+ # fire() is causing the problem
103
+ # fire takes the focus into tab area so the next TAB goes back to first button
104
+ # due to current_tab = tab (so next key stroke goes to tab)
105
+ def on_enter
106
+ $log.debug " overridden on_enter of tabbedbutton #{@name} "
107
+ super
108
+ $log.debug " calling fire overridden on_enter of tabbedbutton"
109
+ fire if @display_tab_on_traversal
110
+ end
111
+ # In order to get tab display as we traverse buttons, we need to tamper with KEY_DOWN
112
+ # since that's the only way of getting down to selected tab in this case.
113
+ def handle_key ch
114
+ case ch
115
+ when KEY_DOWN
116
+ # form will not do a next_field, it will ignore this
117
+ return :NO_NEXT_FIELD
118
+ when KEY_RIGHT
119
+ @form.select_next_field
120
+ when KEY_LEFT
121
+ @form.select_prev_field
122
+ when KEY_ENTER, 10, 13, 32 # added space bar also
123
+ if respond_to? :fire
124
+ fire
125
+ end
126
+ else
127
+ # all thrse will be re-evaluated by form
128
+ return :UNHANDLED
129
+ end
130
+ end
131
+
71
132
  end
72
- class TabbedPane
73
- include DSL
74
- include EventHandler
75
- dsl_accessor :row, :col
76
- dsl_accessor :height, :width
133
+ ##
134
+ # extending Widget from 2009-10-08 18:45
135
+ # It should extend Widget so we can pop it in a form. In fact it should be in a form,
136
+ # we should not have tried to make it standalone like messagebox.
137
+ # This is the main TabbedPane widget that will be slung into a form
138
+ class TabbedPane < Widget
139
+ TAB_ROW_OFFSET = 3 # what row should tab start on (was 4 when printing subheader)
140
+ TAB_COL_OFFSET = 0 # what col should tab start on (to save space, flush on left)
77
141
  dsl_accessor :button_type # ok, ok_cancel, yes_no
78
142
  dsl_accessor :buttons # used if type :custom
79
143
  attr_reader :selected_index
80
- def initialize win, aconfig={}, &block
81
- @parent = win
144
+ attr_reader :current_tab
145
+ attr_reader :window
146
+ def initialize form, aconfig={}, &block
147
+ super
148
+ @parent = form
149
+ @parentwin = form.window
150
+ @visible = true
151
+ @focusable= true
82
152
  @tabs ||= []
83
153
  @forms ||= []
84
- @bgcolor ||= "black" # 0
85
- @color ||= "white" # $datacolor
86
154
  @attr = nil
87
155
  @current_form = nil
88
156
  @current_tab = nil
89
157
  @config = aconfig
90
- @config.each_pair { |k,v| variable_set(k,v) }
91
- instance_eval &block if block_given?
158
+ @col_offset = 2; @row_offset = 1 # added 2010-01-10 22:54
159
+ @recreate_buttons = true
160
+ install_keys
161
+ end
162
+ def install_keys
163
+ @form.bind_key([?d, ?d]) { ix = highlighted_tab_index; repeatm { remove_tab(ix) } }
164
+ @form.bind_key(?u) { undelete_tab; }
165
+ @form.bind_key(?p) { paste_tab 0; } # paste before or at position
166
+ @form.bind_key(?P) { paste_tab 1; } # paste deleted tab after this one
167
+ @form.bind_key([?c, ?w]) { change_label }
168
+ @form.bind_key(?C) { change_label }
92
169
  end
93
170
  ##
171
+ # This is a public, user called method for appending a new tab
172
+ # This will be called several times for one TP.
94
173
  # when adding tabs, you may use ampersand in text to create hotkey
95
- def add_tab text, aconfig={}, &block
96
- #create a button here and block is taken care of in button's instance
97
- #or push this for later creation.
98
- @tabs << Tab.new(text, aconfig, &block)
99
- tab = @tabs.last
100
- @forms << create_tab_form(tab)
101
- tab.form = @forms.last
174
+ # XXX adding a tab later does not influence buttons array,
175
+ def add_tab text, component = nil, aconfig={}, &block
176
+ index = @tabs.size
177
+ tab = insert_tab text, component, index, aconfig, &block
178
+ return tab
179
+ end
180
+ alias :add :add_tab
181
+ ## insert a component at given index
182
+ # index cannnot be greater than size of tab count
183
+ def insert_tab text, component, index, aconfig={}, &block
184
+ $log.debug " TAB insert #{text} at #{index} "
185
+ @tabs[index] = Tab.new(text, self, aconfig, &block)
186
+ tab = @tabs[index]
187
+ tab.component = component unless component.nil?
188
+ tab.index = index # so i can undelete !!!
189
+ fire_event tab, index, :INSERT
190
+ @recreate_buttons = true
102
191
  return tab
103
192
  end
193
+ ## remove given tab based on index
194
+ # This does not unbind the key mapping, FIXME
195
+ # Currently, can be invoked by 'dd' over highlighted button
196
+ # XXX can append to deleted_tabs, then on insert or paste insert with splat.
197
+ def remove_tab index
198
+ @recreate_buttons = true
199
+ $log.debug " inside remove_tab with #{index}, #{@tabs.size} "
200
+ @deleted_tab = @tabs.delete_at(index) unless @tabs.size < index
201
+ # note this is the index it was at.
202
+ fire_event @deleted_tab, index, :DELETE
203
+ end
204
+ ##
205
+ # Move this fun stuff to a util class. TODO
206
+ # If tab deleted accidentally, undelete it
207
+ # Okay, i just can stop myself from having a little fun
208
+ def undelete_tab
209
+ return unless @deleted_tab
210
+ @recreate_buttons = true
211
+ @tabs.insert(@deleted_tab.index, @deleted_tab)
212
+ fire_event @deleted_tab, @deleted_tab.index, :INSERT
213
+ @deleted_tab = nil
214
+ $log.debug " undelete over #{@tabs.size} "
215
+ end
216
+ def paste_tab pos
217
+ return unless @deleted_tab
218
+ ix = highlighted_tab_index
219
+ return if ix == -1
220
+ @recreate_buttons = true
221
+ @deleted_tab.index = ix + pos
222
+ @tabs.insert(@deleted_tab.index, @deleted_tab)
223
+ fire_event @deleted_tab, @deleted_tab.index, :INSERT
224
+ @deleted_tab = nil
225
+ $log.debug " paste over #{@tabs.size} #{ix} + #{pos} "
226
+ end
227
+
228
+ ##
229
+ # prompts for a new label for a tab - taking care of mnemonics if ampersand present
230
+ # Currently, mapped to 'C' and 'cw' when cursor is on a label
231
+ # Perhaps some of this kind of utility stuff needs to go into a util class.
232
+ #
233
+ def change_label
234
+ ix = highlighted_tab_index
235
+ return if ix < 0
236
+ prompt = "Enter new label: "
237
+ label = @buttons[ix].text
238
+ config = {}
239
+ config[:default] = label.dup
240
+ maxlen = 10
241
+ ret, str = rbgetstr(@graphic, $error_message_row, $error_message_col, prompt, maxlen, config)
242
+ if ret == 0 and str != "" and str != label
243
+ @tabs[ix].text = str
244
+ @buttons[ix].text(str)
245
+ @recreate_buttons = true
246
+ end
247
+ end
248
+ ##
249
+ # returns the index of the tab cursor is on (not the one that is selected)
250
+ # @return [0..] index, or -1 if some error
251
+ def highlighted_tab_index
252
+ @form.widgets.each_with_index{ |w, ix|
253
+ return ix if w.state == :HIGHLIGHTED
254
+ }
255
+ return -1
256
+ end
257
+ def selected_tab_index
258
+ @form.widgets.each_with_index{ |w, ix|
259
+ return ix if w.selected?
260
+ }
261
+ return -1
262
+ end
263
+ ## remove all tabs
264
+ def remove_all
265
+ if !@buttons.empty?
266
+ @buttons.each {|e| @form.remove_widget(e) }
267
+ end
268
+ @buttons = []
269
+ @tabs = []
270
+ @recreate_buttons = true
271
+ end
272
+
273
+ ## return a form for use by program - if you want to put multiple items
274
+ # Otherwise just use add_component
275
+ # private - can't use externally
276
+ def configure_component component
277
+ #component.set_form @parent <<--- definitely NOT
278
+ component.form = @parent
279
+ component.rows_panned = component.cols_panned = 0
280
+ component.parent_component = self # added 2010-02-27 so offsets can go down ?
281
+ component.should_create_buffer = true
282
+ component.row = @row + TAB_ROW_OFFSET # 2
283
+ component.col = @col + TAB_COL_OFFSET
284
+
285
+ # current_form likely to be nil XXX
286
+ scr_top = component.row # for Pad, if Pad passed as in SplitPane
287
+ scr_left = component.col # for Pad, if Pad passed as in SplitPane
288
+ ho = TAB_ROW_OFFSET + 2 # 5
289
+ 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)
290
+ # if left nil, then we expand the comp
291
+ component.height ||= @height - (ho - 1) # 1 keeps lower border inside by 1
292
+ component.width ||= @width - 0 # 0 keeps it flush on right border
293
+
294
+
295
+ end
296
+ ## create a form for tab, if multiple components are to be placed inside tab.
297
+ # Tabbedpane has no control over placement and width etc of what's inside a form
298
+ def form tab
299
+ if tab.form.nil?
300
+ @forms << create_tab_form(tab)
301
+ tab.form = @forms.last
302
+ end
303
+ return tab.form
304
+ end
305
+
306
+ ## returns the index of the current / selected tab
307
+ ## @returns 0.. index of selected tab
308
+ def selected_tab_index
309
+ @tabs.index(@current_tab)
310
+ end
311
+
104
312
  # private
105
313
  def variable_set var, val
106
314
  var = "@#{var}"
@@ -117,128 +325,240 @@ module RubyCurses
117
325
  end
118
326
  instance_eval &block if block_given?
119
327
  end
328
+ ## this is a really wierd repaint method.
329
+ # First time it creates the TP window/form which contains the buttons.
330
+ # In future calls it really doesn't do anything.
331
+ # Deos it have nothing to paint, no borders to redraw, no repaint_required ???
120
332
  def repaint
333
+ $log.debug " tabbedpane repaint "
121
334
  @window || create_window
335
+ _recreate_buttons if @recreate_buttons
336
+ $log.debug " tabbedpane repaint #{@window.name} "
122
337
  @window.show
338
+ #x set_buffer_modified()
123
339
  end
124
340
  def show
125
341
  repaint
126
342
  end
127
- def create_window
128
- # first create the main top window with the tab buttons on it.
129
- @layout = { :height => @height, :width => @width, :top => @row, :left => @col }
130
- @window = VER::Window.new(@layout)
131
- @form = RubyCurses::Form.new @window
132
- @form.navigation_policy = :NON_CYCLICAL
133
- @current_form = @form
134
- @window.bkgd(Ncurses.COLOR_PAIR($datacolor));
135
- @window.box( 0, 0);
136
- @window.wrefresh
137
- Ncurses::Panel.update_panels
138
- col = 1
343
+ ## recreate all buttons
344
+ # We call this if even one is added : adv is we can space out accordinagly if the numbers increase
345
+ # We could also expand the pad here.
346
+ # Test it out with removing tabs to.
347
+ # XXX have to remove buttons from the form
348
+ def _recreate_buttons
349
+ $log.debug " inside recreate_buttons: #{@tabs.size} "
350
+ r = @row
351
+ col = @col + 1
352
+ @buttons ||= []
353
+ if !@buttons.empty?
354
+ @buttons.each {|e| @form.remove_widget(e) }
355
+ end
139
356
  @buttons = []
357
+ button_gap = 4
358
+ # the next line necessitates a clear on the pad
359
+ # button_gap = 1 if @tabs.size > 6 # quick dirty fix, we need something that checks fit
360
+ # we may also need to truncate text to fit
361
+
362
+ @buttonpad.wclear
140
363
  ## create a button for each tab
141
- $tabradio = Variable.new
364
+ $tabradio = Variable.new # so we know which is highlighted
142
365
  @tabs.each do |tab|
143
366
  text = tab.text
367
+ $log.debug " TABS EACH #{text} "
144
368
  @buttons << RubyCurses::TabbedButton.new(@form) do
145
369
  variable $tabradio
146
370
  text text
147
371
  name text
148
372
  value text
149
- row 1
373
+ row r + 1
150
374
  col col
151
375
  end
152
- col += text.length+4
153
- # @forms << create_tab_form(tab)
154
- # form = @forms.last
376
+ col += text.length + button_gap
377
+ # if col exceeds pad_w then we need to expand pad
378
+ # but here we don't know that a pad is being used
379
+ $log.debug " button col #{col} "
155
380
  form = tab.form
156
- form.window = @window if form.window.nil? ## XXX
157
- panel = form.window.panel
158
- @buttons.last.command { Ncurses::Panel.top_panel(panel)
159
- Ncurses::Panel.update_panels();
160
- Ncurses.doupdate();
161
- form.repaint
162
- @current_form = form
163
- @current_tab = form
381
+ form.set_parent_buffer(@window) if form
382
+
383
+ b = @buttons.last
384
+ b.command(b) {
385
+ $log.debug " calling display form from button press #{b.name} #{b.state} "
386
+ # form.rep essentially sees that buttons get correct attributes
387
+ # when triggering M-<char>. This button should get highlighted.
388
+ tab.repaint
389
+ button_form_repaint #( b.state == :HIGHLIGHTED )
390
+ if @display_tab_on_traversal
391
+ # set as old tab so ONLY on going down this becomes current_tab
392
+ @old_tab = tab
393
+ else
394
+ # next line means next key is IMMED taken by the tab not main form
395
+ @current_tab = tab
396
+ end
397
+ fire_event tab, tab.index, :OPEN
164
398
  }
165
-
166
399
  end
167
- create_buttons
400
+ @recreate_buttons = false
401
+ # make the buttons visible now, not after next handle_key
168
402
  @form.repaint
169
403
  end
170
- def display_form form
171
- panel = form.window.panel
172
- Ncurses::Panel.top_panel(panel)
173
- Ncurses::Panel.update_panels();
174
- Ncurses.doupdate();
175
- form.repaint
404
+ ## This form is for the tabbed buttons on top
405
+ def create_window
406
+ set_buffer_modified() # required still ??
407
+ # first create the main top window with the tab buttons on it.
408
+ $log.debug " TPane create_buff Top #{@row}, Left #{@col} H #{@height} W #{@width} "
409
+ #$log.debug " parentwin #{@parentwin.left} #{@parentwin.top} "
410
+
411
+ r = @row
412
+ c = @col
413
+ @form = ScrollForm.new(@parentwin)
414
+ @form.set_layout(1, @width, @row+1, @col+1)
415
+ @form.display_h = 1
416
+ @form.display_w = @width-3
417
+ @buttonpad = @form.create_pad
418
+
419
+
420
+ ## We will use the parent window, and not a pad. We will write absolute coordinates.
421
+ @window = @parentwin
422
+ color = $datacolor
423
+ # border around button bar. should this not be in scrollform as a border ? XXX
424
+ @window.print_border @row, @col, 2, @width, color #, Ncurses::A_REVERSE
425
+ @buttonpad.name = "Window::TPTOPPAD" # 2010-02-02 20:01
426
+ @form.name = "Form::TPTOPFORM"
427
+ $log.debug("TP WINDOW TOP ? PAD MAIN FORM W:#{@window.name}, F:#{@form.name} ")
428
+ @form.parent_form = @parent ## 2010-01-21 15:55 TRYING OUT BUFFERED
429
+ @form.navigation_policy = :NON_CYCLICAL
430
+ #xx @current_form = @form
431
+ #xx color = $datacolor
432
+ #xx @window.print_border @row, @col, @height-1, @width, color #, Ncurses::A_REVERSE
433
+
434
+ Ncurses::Panel.update_panels
435
+ _recreate_buttons
436
+
437
+ button_form_repaint true
438
+ @window.wrefresh ## ADDED 2009-11-02 23:29
439
+ @old_tab = @tabs.first
440
+ @buttons.first().fire unless @buttons.empty? # make the first form active to start with.
441
+ end
442
+ def button_form_repaint flag = true
443
+ $log.debug " INSIDE button_form_repaint #{flag} "
444
+ #if flag
445
+ #@form.repaint if flag # This paints the outer form not inner
446
+ #@buttonpad.mvwaddch(2, 0, Ncurses::ACS_LTEE) # beautify the corner 2010-02-06 19:35
447
+ #@buttonpad.mvwaddch(2, @width-1, Ncurses::ACS_RTEE)
448
+ #end
449
+ #ret = @buttonpad.prefresh(0,0, @row+0, @col+0, @row+@height, @col+@width)
450
+ #$log.debug " prefresh error buttonpad 2 " if ret < 0
451
+ #@buttonpad.modified = false
452
+ if flag
453
+ # repaint form and refresh pad
454
+ @form.repaint
455
+ else
456
+ # only refresh pad
457
+ @form.prefresh
458
+ end
176
459
  end
460
+ ##
461
+ # This creates a form for the tab, in case we wish to put many components in it.
462
+ # Else just pass single components in add_tab.
463
+ # @params tab tab just created for which a form is required
464
+ # @return form - a pad based form
177
465
  def create_tab_form tab
178
- layout = { :height => @height-2, :width => @width, :top => @row+2, :left => @col }
179
- window = VER::Window.new(layout)
466
+ mtop = 0
467
+ mleft = 0
468
+ bottom_offset = 2 # 0 will overwrite bottom line, 1 will make another line for inner form
469
+ layout = { :height => @height-(mtop+bottom_offset), :width => @width, :top => mtop, :left => mleft }
470
+ # create a pad but it must behave like a window at all times 2009-10-25 12:25
471
+ window = VER::Pad.create_with_layout(layout)
472
+
180
473
  form = RubyCurses::Form.new window
474
+ $log.debug " pad created in TP create_tab_form: #{window.name} , form #{form.name} "
475
+ $log.debug " hwtl: #{layout[:height]} #{layout[:width]} #{layout[:top]} #{layout[:left]} "
476
+ ## added 2010-01-21 15:46 to pass cursor up
477
+ form.parent_form = @parent
181
478
  form.navigation_policy = :NON_CYCLICAL
182
479
  window.bkgd(Ncurses.COLOR_PAIR($datacolor));
183
480
  window.box( 0, 0);
184
- window.mvprintw(1,1, tab.text.tr('&', ''))
185
- ##window.wrefresh
186
- ##Ncurses::Panel.update_panels
481
+ window.mvwaddch(0, 0, Ncurses::ACS_LTEE) # beautify the corner 2010-02-06 19:35
482
+ window.mvwaddch(0, @width-1, Ncurses::ACS_RTEE)
483
+
484
+ # XXX TODO this wastes space we should ditch it.
485
+ ## this prints the tab name on top left
486
+ window.mvprintw(1,1, tab.text.tr('&', '')) if @print_subheader
487
+ window.name = "Tab::TAB-#{tab.text}" # 2010-02-02 19:59
488
+ form.name = "Form::TAB-#{tab.text}" # 2010-02-02 19:59
489
+ form.add_cols=@col
490
+ form.add_rows=@row
187
491
  return form
188
492
  end
189
- def handle_keys
190
- begin
191
- while (( ch=@window.getchar()) != 999)
192
- if ch == ?\C-q
193
- @selected_index = -1 # this signifies cancel by ?C-q
194
- @stop = true
195
- return
196
- end
197
- return if @stop
198
- @current_form ||= @form
199
- ret = @current_form.handle_key(ch)
493
+ ##
494
+ # added 2009-10-08 19:39 so it can be placed in a form
495
+ # @form is the top button form
496
+ # XXX stop this nonsense about current_form and current_tab
497
+ # TP should only be concerned with tabs. what happens inside is none of its business
498
+ def handle_key(ch)
499
+ @current_tab ||= @form # first we cycle buttons
500
+ $log.debug " handle_key in tabbed pane got : #{ch}"
501
+ # needs to go to component
502
+ ret = @current_tab.handle_key(ch)
503
+ $log.debug " -- form.handle_key in tabbed pane got ret : #{ret} , #{@current_tab} , #{ch} "
504
+
505
+ # components will usually return UNHANDLED for a tab or btab
506
+ # We need to convert it so the main form can use it
507
+ if @current_tab != @form
508
+ if ret == :UNHANDLED
509
+ if ch == 9 #or ch == KEY_DOWN
510
+ ret = :NO_NEXT_FIELD
511
+ elsif ch == 353 #or ch == KEY_UP # btab
512
+ ret = :NO_PREV_FIELD
513
+ end
514
+ end
515
+ else
516
+ # key down pressed in top form, go to tab
517
+ if ch == KEY_DOWN
518
+ ret = :NO_NEXT_FIELD
519
+ end
520
+ end
521
+
200
522
  case ret
201
523
  when :NO_NEXT_FIELD
202
- if @current_form != @form
203
- @current_form = @form
204
- #@current_form.select_field -1
205
- @current_form.req_first_field
206
- #ret = @current_form.handle_key(ch)
524
+ if @current_tab != @form
525
+ ## if no next field on a subform go to first button of main form
526
+ @old_tab = @current_tab
527
+ @current_tab = @form
528
+ @form.req_first_field
529
+ #@form.select_next_field
207
530
  else
208
- if !@current_tab.nil?
209
- @current_form = @current_tab
210
- display_form @current_form
211
- @current_form.req_first_field
212
- #@current_form.select_field -1
213
- #ret = @current_form.handle_key(ch)
531
+ # on top button panel - no more buttons, go to tabs first field
532
+ if @old_tab # in case of empty tabbed pane old_tab was nil
533
+ @current_tab = @old_tab
534
+ @current_tab.set_focus :FIRST
214
535
  end
215
536
  end
216
537
  when :NO_PREV_FIELD
217
- if @current_form != @form
218
- $log.debug " 1 no prev field - going to button "
219
- @current_form = @form
220
- @current_form.req_last_field
538
+ if @current_tab != @form
539
+ $log.debug "TP 1 no prev field - going to last button "
540
+ @old_tab = @current_tab
541
+ @current_tab = @form
542
+ @form.req_last_field
221
543
  else
222
- if !@current_tab.nil?
223
- @current_form = @current_tab
224
- display_form @current_form
225
- @current_form.req_last_field
544
+ # on top button panel - no prev buttons, go to tabs last field
545
+ if @old_tab # in case of one tab
546
+ @current_tab = @old_tab
547
+ @current_tab.set_focus :LAST
226
548
  end
227
549
  end
228
550
  when :UNHANDLED
229
551
  $log.debug " unhandled in tabbed pane #{ch}"
230
552
  ret = @form.process_key ch, self # field
231
- @form.repaint
232
- #return :UNHANDLED if ret == :UNHANDLED
553
+
554
+ return ret if ret == :UNHANDLED
555
+ end
556
+ if @buttonpad.modified
557
+ button_form_repaint
233
558
  end
234
- return if @stop
235
- @current_form.window.wrefresh
236
- @window.refresh
237
- end
238
- ensure
239
- destroy
240
- end
241
559
  end
560
+ ##
561
+ # ensure that the pads are being destroyed, although we've not found a way.
242
562
  def destroy
243
563
  @window.destroy
244
564
  @forms.each { |f| w = f.window; w.destroy unless w.nil? }
@@ -291,31 +611,339 @@ module RubyCurses
291
611
  width = @layout[:width]
292
612
  return (width-textlen)/2
293
613
  end
614
+ def fire_event tab, index, event
615
+ # experimenting with structs, earlier we've used classes
616
+ if @tabbedpane_event.nil?
617
+ @tabbedpane_event = Event.new
618
+ end
619
+ @tabbedpane_event.tab = tab
620
+ @tabbedpane_event.index = index
621
+ @tabbedpane_event.event = event
622
+ fire_handler event, @tabbedpane_event
623
+ end
294
624
 
295
625
  ##
296
626
  # nested class tab
627
+ # A user created tab, with its own form
297
628
  class Tab
298
- attr_reader :text
629
+ attr_accessor :text
299
630
  attr_reader :config
631
+ attr_reader :component
300
632
  attr_accessor :form
301
- def initialize text, aconfig={}, &block
633
+ attr_accessor :parent_component
634
+ attr_accessor :index
635
+ def initialize text, parent_component, aconfig={}, &block
302
636
  @text = text
303
637
  @config = aconfig
638
+ @parent_component = parent_component
304
639
  @config.each_pair { |k,v| variable_set(k,v) }
305
640
  instance_eval &block if block_given?
306
641
  end
642
+ ## add a single component to the tab
643
+ # Calling this a second time will overwrite the existing component
644
+ def component=(component)
645
+ raise "Component cannot be null" unless component
646
+ raise "Component already associated with a form. Do not pass form in constructor." unless component.form.nil?
647
+ $log.debug " calling configure component "
648
+ @parent_component.configure_component component
649
+ @component = component
650
+ end
651
+ def remove_component
652
+ @component = nil
653
+ end
307
654
  # private
308
655
  def variable_set var, val
309
656
  var = "@#{var}"
310
657
  instance_variable_set(var, val)
311
658
  end
312
- def repaint
313
-
314
-
659
+ # tab should handle key instead of TP.
660
+ # Pass to component or form
661
+ def handle_key ch
662
+ kh = @component || @form
663
+ ret = kh.handle_key(ch)
664
+ # forms seem to returning a nil when the pad has been updated. We need to copy it
665
+ ret ||= 0
666
+ if ret == 0
667
+ @component.repaint if @component
668
+ display_form false if @form
669
+ end
670
+ # XXX i need to call repaint of compoent if updated !!
671
+ return ret
672
+ end
673
+ def repaint # Tab
674
+ if @form
675
+ display_form
676
+ elsif @component
677
+ # we ask the component to paint its buffer only, no actual repainting
678
+ redraw
679
+ else
680
+ # pls use tp.form(tab) to get form explicity.
681
+ # It could come here if tab precreated and user is yet to assign a component.
682
+ # or has removed component and not yet set a new one.
683
+ $log.error "Got neither component nor form."
684
+ $log.error "Programmer error. A change in Tabbedpane requires you to create form explicitly using form = tpane.form(tab) syntax"
685
+ end
686
+ end
687
+ # force a redraw of a component when tabs changed
688
+ def redraw
689
+ # this kinda stuff should be inside widget or some util class
690
+ c = @component
691
+ if c.is_double_buffered?
692
+ c.set_buffer_modified
693
+ c.buffer_to_window
694
+ else
695
+ # force a repaint, if not buffered object e.g scrollpane.
696
+ $log.debug " TP: forcing repaint of non-buffered object #{c} "
697
+ c.repaint_all
698
+ c.repaint
699
+ end
315
700
  end
701
+ ## Set focus on a component or form field when a user has tabbed off the last or first button
702
+ def set_focus first_last
703
+ if !@form.nil?
704
+ # move to first field of existing form
705
+ #@current_form = @current_tab.form # 2010-02-27 20:22
706
+ $log.debug " calling display form from handle_key NO_NEXT_FIELD: #{first_last} "
707
+ first_last == :FIRST ? @form.req_first_field : @form.req_last_field
708
+ display_form
709
+ else
710
+ # move to component
711
+ #@current_form = @current_tab.component # temp HACK 2010-02-27 23:24
712
+ @component.set_form_row
713
+ @component.set_form_col
714
+ end
715
+ end
716
+ # On a tabbed button press, this will display the relevant form
717
+ # On why I am directyl calling copywin and not using copy_pad_to_win etc
718
+ #+ those require setting top and left. However, while printing a pad, top and left are reduced and so
719
+ #+ must be absolute r and c. But inside TP, objects have a relative coord. So the print functions
720
+ #+ were failing silently, and i was wondering why nothing was printing.
721
+ # XXX move this tab in tab.repaint and let tab decide based on component or form
722
+ # if component then pad = component.get_buffer
723
+ def display_form flag = true
724
+ return if @form.nil?
725
+ form = @form
726
+ if form.is_a? RubyCurses::Form # tempo XXX since there are components
727
+ pad = form.window
728
+ else
729
+ return
730
+ pad = form.get_buffer() # component
731
+ end
732
+ pc = @parent_component
733
+ form.repaint if flag # added 2009-11-03 23:27 paint widgets in inside form
734
+ $log.debug " TP display form before pad copy: #{pad.name}, set_backing: #{form}: #{form.name} parent: #{@parent_component} : #{pc.row} , #{pc.col}. #{pc.height} , #{pc.width}: repaint flag #{flag} "
735
+ ret = -1
736
+ pminr = pminc = 0
737
+ r = pc.row + 2
738
+ c = pc.col + 0
739
+ border_width = 0
740
+ maxr = pc.height() - 3
741
+ maxc = pc.width() - 1
742
+ $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} "
743
+ ret = pad.copywin(pc.window.get_window, pminr, pminc, r, c, r+maxr-border_width, c+maxc-border_width,0)
744
+ $log.debug " display form after pad copy #{ret}. #{form.name} "
316
745
  end
317
746
 
747
+ end # class Tab
748
+
318
749
  end # class Tabbedpane
319
750
 
751
+ ## An extension of Form that only displays and focuses on visible widgets
752
+ # This is minimal, and is being expanded upon as a separate class in rscrollform.rb
753
+ #
754
+ class ScrollForm < RubyCurses::Form
755
+ attr_accessor :pmincol # advance / scroll columns
756
+ attr_accessor :pminrow # advance / scroll rows (vertically)
757
+ attr_accessor :display_w
758
+ attr_accessor :display_h
759
+ attr_accessor :scroll_ctr
760
+ attr_reader :orig_top, :orig_left
761
+ attr_reader :window
762
+ attr_accessor :name
763
+ attr_reader :cols_panned, :rows_panned
764
+ def initialize win, &block
765
+ @target_window = win
766
+ super
767
+ @pminrow = @pmincol = 0
768
+ @scroll_ctr = 2
769
+ @cols_panned = @rows_panned = 0
770
+ end
771
+ def set_layout(h, w, t, l)
772
+ @pad_h = h
773
+ @pad_w = w
774
+ @top = @orig_top = t
775
+ @left = @orig_left = l
776
+ end
777
+ def create_pad
778
+ r = @top
779
+ c = @left
780
+ layout = { :height => @pad_h, :width => @pad_w, :top => r, :left => c }
781
+ @window = VER::Pad.create_with_layout(layout)
782
+ #@window = @parentwin
783
+ #@form = RubyCurses::Form.new @buttonpad
784
+ @window.name = "Pad::ScrollPad" # 2010-02-02 20:01
785
+ @name = "Form::ScrollForm"
786
+ return @window
787
+ end
788
+ ## ScrollForm handle key, scrolling
789
+ def handle_key ch
790
+ $log.debug " inside ScrollForm handlekey #{ch} "
791
+ # do the scrolling thing here top left prow and pcol of pad to be done
792
+ # # XXX TODO check whether we can scroll before incrementing esp cols_panned etc
793
+ case ch
794
+ when ?\M-l.getbyte(0)
795
+ return false if !validate_scroll_col(@pmincol + @scroll_ctr)
796
+ @pmincol += @scroll_ctr # some check is required or we'll crash
797
+ @cols_panned -= @scroll_ctr
798
+ $log.debug " handled ch M-l in ScrollForm"
799
+ @window.modified = true
800
+ return 0
801
+ when ?\M-h.getbyte(0)
802
+ return false if !validate_scroll_col(@pmincol - @scroll_ctr)
803
+ @pmincol -= @scroll_ctr # some check is required or we'll crash
804
+ @cols_panned += @scroll_ctr
805
+ $log.debug " handled ch M-h in ScrollForm"
806
+ @window.modified = true
807
+ return 0
808
+ when ?\M-n.getbyte(0)
809
+ return false if !validate_scroll_row(@pminrow + @scroll_ctr)
810
+ @pminrow += @scroll_ctr # some check is required or we'll crash
811
+ @rows_panned -= @scroll_ctr
812
+ @window.modified = true
813
+ return 0
814
+ when ?\M-p.getbyte(0)
815
+ return false if !validate_scroll_row(@pminrow - @scroll_ctr)
816
+ @pminrow -= @scroll_ctr # some check is required or we'll crash
817
+ @rows_panned += @scroll_ctr
818
+ @window.modified = true
819
+ return 0
820
+ end
821
+
822
+ super
823
+ end
824
+ def repaint
825
+ $log.debug " scrollForm repaint calling parent"
826
+ super
827
+ prefresh
828
+ @window.modified = false
829
+ end
830
+ def prefresh
831
+ ## reduce so we don't go off in top+h and top+w
832
+ $log.debug " start ret = @buttonpad.prefresh( #{@pminrow} , #{@pmincol} , #{@top} , #{@left} , top + #{@display_h} left + #{@display_w} ) "
833
+ if @pminrow + @display_h > @orig_top + @pad_h
834
+ $log.debug " if #{@pminrow} + #{@display_h} > #{@orig_top} +#{@pad_h} "
835
+ $log.debug " ERROR 1 "
836
+ #return -1
837
+ end
838
+ if @pmincol + @display_w > @orig_left + @pad_w
839
+ $log.debug " if #{@pmincol} + #{@display_w} > #{@orig_left} +#{@pad_w} "
840
+ $log.debug " ERROR 2 "
841
+ return -1
842
+ end
843
+ # actually if there is a change in the screen, we may still need to allow update
844
+ # but ensure that size does not exceed
845
+ if @top + @display_h > @orig_top + @pad_h
846
+ $log.debug " if #{@top} + #{@display_h} > #{@orig_top} +#{@pad_h} "
847
+ $log.debug " ERROR 3 "
848
+ return -1
849
+ end
850
+ if @left + @display_w > @orig_left + @pad_w
851
+ $log.debug " if #{@left} + #{@display_w} > #{@orig_left} +#{@pad_w} "
852
+ $log.debug " ERROR 4 "
853
+ return -1
854
+ end
855
+ # maybe we should use copywin to copy onto @target_window
856
+ $log.debug " ret = @buttonpad.prefresh( #{@pminrow} , #{@pmincol} , #{@top} , #{@left} , #{@top} + #{@display_h}, #{@left} + #{@display_w} ) "
857
+ omit = 0
858
+ # this works but if want to avoid copying border
859
+ ret = @window.prefresh(@pminrow, @pmincol, @top, @left, @top + @display_h , @left + @display_w)
860
+
861
+ $log.debug " ret = #{ret} "
862
+ # need to refresh the form after repaint over
863
+ end
864
+ def validate_scroll_row minrow
865
+ return false if minrow < 0
866
+ if minrow + @display_h > @orig_top + @pad_h
867
+ $log.debug " if #{minrow} + #{@display_h} > #{@orig_top} +#{@pad_h} "
868
+ $log.debug " ERROR 1 "
869
+ return false
870
+ end
871
+ return true
872
+ end
873
+ def validate_scroll_col mincol
874
+ return false if mincol < 0
875
+ if mincol + @display_w > @orig_left + @pad_w
876
+ $log.debug " if #{mincol} + #{@display_w} > #{@orig_left} +#{@pad_w} "
877
+ $log.debug " ERROR 2 "
878
+ return false
879
+ end
880
+ return true
881
+ end
882
+ # when tabbing through buttons, we need to account for all that panning/scrolling goin' on
883
+ def setrowcol r, c
884
+ # aha ! here's where i can check whether the cursor is falling off the viewable area
885
+ if c+@cols_panned < @orig_left
886
+ # this essentially means this widget (button) is not in view, its off to the left
887
+ $log.debug " setrowcol OVERRIDE #{c} #{@cols_panned} < #{@orig_left} "
888
+ $log.debug " aborting settrow col for now"
889
+ return
890
+ end
891
+ if c+@cols_panned > @orig_left + @display_w
892
+ # this essentially means this button is not in view, its off to the right
893
+ $log.debug " setrowcol OVERRIDE #{c} #{@cols_panned} > #{@orig_left} + #{@display_w} "
894
+ $log.debug " aborting settrow col for now"
895
+ return
896
+ end
897
+ super r+@rows_panned, c+@cols_panned
898
+ end
899
+ def add_widget w
900
+ super
901
+ #$log.debug " inside add_widget #{w.name} pad w #{@pad_w} #{w.col} "
902
+ if w.col >= @pad_w
903
+ @pad_w += 10 # XXX currently just a guess value, we need length and maybe some extra
904
+ @window.wresize(@pad_h, @pad_w)
905
+ end
906
+ end
907
+ ## Is a component visible, typically used to prevent traversal into the field
908
+ # @returns [true, false] false if components has scrolled off
909
+ def visible? component
910
+ r, c = component.rowcol
911
+ return false if c+@cols_panned < @orig_left
912
+ return false if c+@cols_panned > @orig_left + @display_w
913
+ # XXX TODO for rows UNTESTED for rows
914
+ return false if r + @rows_panned < @orig_top
915
+ return false if r + @rows_panned > @orig_top + @display_h
916
+
917
+ return true
918
+ end
919
+ # returns index of first visible component. Currently using column index
920
+ # I am doing this for horizontal scrolling presently
921
+ # @return [index, -1] -1 if none visible, else index/offset
922
+ def first_visible_component_index
923
+ @widgets.each_with_index do |w, ix|
924
+ return ix if visible?(w)
925
+ end
926
+ return -1
927
+ end
928
+ def last_visible_component_index
929
+ ret = -1
930
+ @widgets.each_with_index do |w, ix|
931
+ $log.debug " reverse last vis #{ix} , #{w} : #{visible?(w)} "
932
+ ret = ix if visible?(w)
933
+ end
934
+ return ret
935
+ end
936
+ def req_first_field
937
+ select_field(first_visible_component_index)
938
+ end
939
+ def req_last_field
940
+ select_field(last_visible_component_index)
941
+ end
942
+ def focusable?(w)
943
+ w.focusable and visible?(w)
944
+ end
945
+
946
+ end # class ScrollF
947
+
320
948
 
321
949
  end # module