rbcurse-core 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. data/README.md +69 -0
  2. data/VERSION +1 -0
  3. data/examples/abasiclist.rb +151 -0
  4. data/examples/alpmenu.rb +46 -0
  5. data/examples/app.sample +17 -0
  6. data/examples/atree.rb +100 -0
  7. data/examples/common/file.rb +45 -0
  8. data/examples/data/README.markdown +9 -0
  9. data/examples/data/brew.txt +38 -0
  10. data/examples/data/color.2 +37 -0
  11. data/examples/data/gemlist.txt +60 -0
  12. data/examples/data/lotr.txt +12 -0
  13. data/examples/data/ports.txt +136 -0
  14. data/examples/data/table.txt +37 -0
  15. data/examples/data/tasks.csv +88 -0
  16. data/examples/data/tasks.txt +27 -0
  17. data/examples/data/todo.txt +10 -0
  18. data/examples/data/todocsv.csv +28 -0
  19. data/examples/data/unix1.txt +21 -0
  20. data/examples/data/unix2.txt +11 -0
  21. data/examples/dbdemo.rb +487 -0
  22. data/examples/dirtree.rb +90 -0
  23. data/examples/newtabbedwindow.rb +100 -0
  24. data/examples/newtesttabp.rb +92 -0
  25. data/examples/tabular.rb +132 -0
  26. data/examples/tasks.rb +167 -0
  27. data/examples/term2.rb +83 -0
  28. data/examples/testkeypress.rb +72 -0
  29. data/examples/testlistbox.rb +158 -0
  30. data/examples/testmessagebox.rb +140 -0
  31. data/examples/testree.rb +106 -0
  32. data/examples/testwsshortcuts.rb +66 -0
  33. data/examples/testwsshortcuts2.rb +127 -0
  34. data/lib/rbcurse.rb +8 -0
  35. data/lib/rbcurse/core/docs/index.txt +73 -0
  36. data/lib/rbcurse/core/include/action.rb +40 -0
  37. data/lib/rbcurse/core/include/appmethods.rb +112 -0
  38. data/lib/rbcurse/core/include/bordertitle.rb +41 -0
  39. data/lib/rbcurse/core/include/chunk.rb +182 -0
  40. data/lib/rbcurse/core/include/io.rb +953 -0
  41. data/lib/rbcurse/core/include/listcellrenderer.rb +140 -0
  42. data/lib/rbcurse/core/include/listeditable.rb +317 -0
  43. data/lib/rbcurse/core/include/listscrollable.rb +590 -0
  44. data/lib/rbcurse/core/include/listselectable.rb +264 -0
  45. data/lib/rbcurse/core/include/multibuffer.rb +83 -0
  46. data/lib/rbcurse/core/include/orderedhash.rb +77 -0
  47. data/lib/rbcurse/core/include/ractionevent.rb +67 -0
  48. data/lib/rbcurse/core/include/rchangeevent.rb +27 -0
  49. data/lib/rbcurse/core/include/rhistory.rb +62 -0
  50. data/lib/rbcurse/core/include/rinputdataevent.rb +47 -0
  51. data/lib/rbcurse/core/include/vieditable.rb +170 -0
  52. data/lib/rbcurse/core/system/colormap.rb +163 -0
  53. data/lib/rbcurse/core/system/keyboard.rb +150 -0
  54. data/lib/rbcurse/core/system/keydefs.rb +30 -0
  55. data/lib/rbcurse/core/system/ncurses.rb +218 -0
  56. data/lib/rbcurse/core/system/panel.rb +162 -0
  57. data/lib/rbcurse/core/system/window.rb +901 -0
  58. data/lib/rbcurse/core/util/ansiparser.rb +117 -0
  59. data/lib/rbcurse/core/util/app.rb +1235 -0
  60. data/lib/rbcurse/core/util/basestack.rb +407 -0
  61. data/lib/rbcurse/core/util/bottomline.rb +1850 -0
  62. data/lib/rbcurse/core/util/colorparser.rb +71 -0
  63. data/lib/rbcurse/core/util/focusmanager.rb +31 -0
  64. data/lib/rbcurse/core/util/padreader.rb +189 -0
  65. data/lib/rbcurse/core/util/rcommandwindow.rb +587 -0
  66. data/lib/rbcurse/core/util/rdialogs.rb +619 -0
  67. data/lib/rbcurse/core/util/viewer.rb +149 -0
  68. data/lib/rbcurse/core/util/widgetshortcuts.rb +505 -0
  69. data/lib/rbcurse/core/widgets/applicationheader.rb +102 -0
  70. data/lib/rbcurse/core/widgets/box.rb +58 -0
  71. data/lib/rbcurse/core/widgets/divider.rb +310 -0
  72. data/lib/rbcurse/core/widgets/keylabelprinter.rb +178 -0
  73. data/lib/rbcurse/core/widgets/rcombo.rb +238 -0
  74. data/lib/rbcurse/core/widgets/rcontainer.rb +415 -0
  75. data/lib/rbcurse/core/widgets/rlink.rb +30 -0
  76. data/lib/rbcurse/core/widgets/rlist.rb +723 -0
  77. data/lib/rbcurse/core/widgets/rmenu.rb +939 -0
  78. data/lib/rbcurse/core/widgets/rmenulink.rb +22 -0
  79. data/lib/rbcurse/core/widgets/rmessagebox.rb +373 -0
  80. data/lib/rbcurse/core/widgets/rprogress.rb +118 -0
  81. data/lib/rbcurse/core/widgets/rtabbedpane.rb +615 -0
  82. data/lib/rbcurse/core/widgets/rtabbedwindow.rb +68 -0
  83. data/lib/rbcurse/core/widgets/rtextarea.rb +920 -0
  84. data/lib/rbcurse/core/widgets/rtextview.rb +780 -0
  85. data/lib/rbcurse/core/widgets/rtree.rb +787 -0
  86. data/lib/rbcurse/core/widgets/rwidget.rb +3040 -0
  87. data/lib/rbcurse/core/widgets/scrollbar.rb +143 -0
  88. data/lib/rbcurse/core/widgets/statusline.rb +94 -0
  89. data/lib/rbcurse/core/widgets/tabular.rb +264 -0
  90. data/lib/rbcurse/core/widgets/tabularwidget.rb +1211 -0
  91. data/lib/rbcurse/core/widgets/textpad.rb +516 -0
  92. data/lib/rbcurse/core/widgets/tree/treecellrenderer.rb +150 -0
  93. data/lib/rbcurse/core/widgets/tree/treemodel.rb +428 -0
  94. metadata +156 -0
@@ -0,0 +1,178 @@
1
+ require 'rbcurse/core/widgets/rwidget'
2
+ #include Ncurses # FFI 2011-09-8
3
+ include RubyCurses
4
+ module RubyCurses
5
+ #
6
+ # This paints labels for various keys at the bottom of the screen, in 2 rows.
7
+ # This is based on alpines last 2 rows. Modes are supported so that the
8
+ # labels change as you enter a widget.
9
+ # For an example, see dbdemo.rb or rfe.rb
10
+ # NOTE: applications using 'App' use a shortcut "dock" to create this.
11
+ #
12
+ # The most minimal keylabel to print one label in first row, and none in second is:
13
+ # [["F1", "Help"], nil]
14
+ # To print 2 labels, one over the other:
15
+ # [["F1", "Help"], ["F10", "Quit"]]
16
+ #
17
+ class KeyLabelPrinter < Widget
18
+ attr_reader :key_labels
19
+ # the current mode (labels are based on mode, changing the mode, changes the labels
20
+ # displayed)
21
+ dsl_property :mode
22
+ # set the color of the labels, overriding the defaults
23
+ dsl_accessor :footer_color_pair
24
+ # set the color of the mnemonic, overriding the defaults
25
+ dsl_accessor :footer_mnemonic_color_pair
26
+
27
+ def initialize form, key_labels, config={}, &block
28
+
29
+ case key_labels
30
+ when Hash
31
+ raise "KeyLabelPrinter: KeyLabels cannot be a hash, Array of key labels required. Perhaps you did not pass labels"
32
+ when Array
33
+ else
34
+ raise "KeyLabelPrinter: Array of key labels required. Perhaps you did not pass labels"
35
+ end
36
+ super form, config, &block
37
+ @mode ||= :normal
38
+ #@key_labels = key_labels
39
+ @key_hash = {}
40
+ @key_hash[@mode] = key_labels
41
+ @editable = false
42
+ @focusable = false
43
+ @cols ||= Ncurses.COLS-1
44
+ @row ||= Ncurses.LINES-2
45
+ @col ||= 0
46
+ @repaint_required = true
47
+ @footer_color_pair ||= $bottomcolor
48
+ @footer_mnemonic_color_pair ||= $reversecolor #2
49
+ end
50
+ def key_labels mode=@mode
51
+ @key_hash[mode]
52
+ end
53
+ # returns the keys as printed. these may or may not help
54
+ # in validation depedign on what you passed as zeroth index
55
+ def get_current_keys
56
+ a = []
57
+ @key_hash[@mode].each do |arr|
58
+ a << arr[0] unless arr.nil?
59
+ end
60
+ return a
61
+ end
62
+ def getvalue
63
+ @key_hash
64
+ end
65
+ def set_key_labels _key_labels, mode=:normal
66
+ @key_hash[mode] = _key_labels
67
+ end
68
+
69
+ ##
70
+ # XXX need to move wrapping etc up and done once.
71
+ def repaint
72
+ return unless @repaint_required
73
+ r,c = rowcol
74
+ arr = key_labels()
75
+ print_key_labels(arr, mode=@mode)
76
+ @repaint_required = false
77
+ end
78
+ # ?? does not use mode, i think key_labels is unused. a hash is now used 2011-10-11 XXX FIXME
79
+ # WARNING, i have not tested this after changing it.
80
+ def append_key_label key, label, mode=@mode
81
+ #@key_labels << [key, label] if !@key_labels.include? [key, label]
82
+ @key_hash[mode] << [key, label] if !@key_hash[mode].include? [key, label]
83
+ @repaint_required = true
84
+ end
85
+ def print_key_labels(arr = key_labels(), mode=@mode)
86
+ #return if !@show_key_labels # XXX
87
+ @win ||= @form.window
88
+ $log.debug "XXX: PKL #{arr.length}, #{arr}"
89
+ @padding = @cols / (arr.length/2)
90
+ posx = 0
91
+ even = []
92
+ odd = []
93
+ arr.each_index { |i|
94
+ if i % 2 == 0
95
+ #arr[i+1] = ['',''] if arr[i+1].nil?
96
+ nextarr = arr[i+1] || ['', '']
97
+ keyw = [arr[i][0].length, nextarr[0].length].max
98
+ labelw = [arr[i][1].length, nextarr[1].length].max
99
+
100
+ even << [ sprintf("%*s", keyw, arr[i][0]), sprintf("%-*s", labelw, arr[i][1]) ]
101
+ odd << [ sprintf("%*s", keyw, nextarr[0]), sprintf("%-*s", labelw, nextarr[1]) ]
102
+ #$log.debug("loop even: #{even.inspect}")
103
+ else
104
+ end
105
+ }
106
+ #$log.debug("even: #{even.inspect}")
107
+ #$log.debug("odd : #{odd.inspect}")
108
+ #posy = @barrow-1
109
+ posy = @row
110
+ print_key_labels_row(posy, posx, even)
111
+ posy = @row+1
112
+ print_key_labels_row(posy, posx, odd)
113
+ # uncommented next line after ffi-ncurses else not showing till key press FFI 2011-09-17
114
+ @win.wrefresh # needed else secod row not shown after askchoice XXX
115
+ end
116
+ def print_key_labels_row(posy, posx, arr)
117
+ # FIXME: this logic of padding needs to take into account
118
+ # width of window
119
+ padding = 8
120
+ padding = 4 if arr.length > 5
121
+ padding = 2 if arr.length > 7
122
+ padding = 0 if arr.length > 9
123
+ #padding = @padding # XXX 2008-11-13 23:01
124
+ my_form_win = @win
125
+ @win.printstring(posy,0, "%-*s" % [@cols," "], @footer_color_pair, @attr)
126
+ arr.each do |kl|
127
+ key = kl[0]
128
+ lab = kl[1]
129
+ if key !="" # don't print that white blank space for fillers
130
+ color_pair= @footer_mnemonic_color_pair # $reversecolor #2
131
+ x = posx + (key.length - key.strip.length)
132
+ my_form_win.attron(Ncurses.COLOR_PAIR(color_pair))
133
+ my_form_win.mvprintw(posy, x, "%s" % kl[0].strip );
134
+ my_form_win.attroff(Ncurses.COLOR_PAIR(color_pair))
135
+ end
136
+ color_pair=@footer_color_pair
137
+ posx = posx + kl[0].length
138
+ my_form_win.attron(Ncurses.COLOR_PAIR(color_pair))
139
+
140
+ #lab = sprintf(" %s %*s" , kl[1], padding, " ");
141
+ lab = sprintf(" %s %s" , kl[1], " "*padding);
142
+ my_form_win.mvprintw(posy, posx, lab)
143
+ my_form_win.attroff(Ncurses.COLOR_PAIR(color_pair))
144
+ posx = posx + lab.length
145
+ end
146
+ end
147
+ ##
148
+ # updates existing label with a new one.
149
+ # @return true if updated, else false
150
+ # @example update "C-x", "C-x", "Disable"
151
+ def update_application_key_label(display_code, new_display_code, text)
152
+ @repaint_required = true
153
+ labels = key_labels()
154
+ raise "labels are nil !!!" unless labels
155
+ labels.each_index do |ix|
156
+ lab = labels[ix]
157
+ next if lab.nil?
158
+ if lab[0] == display_code
159
+ labels[ix] = [new_display_code , text]
160
+ $log.debug("updated #{labels[ix]}")
161
+ return true
162
+ end
163
+ end
164
+ return false
165
+ end
166
+ alias :update :update_application_key_label
167
+ ##
168
+ # inserts an application label at given index
169
+ # to add the key, use create_datakeys to add bindings
170
+ # remember to call restore_application_key_labels after updating/inserting
171
+ def insert_application_key_label(index, display_code, text)
172
+ @repaint_required = true
173
+ labels = key_labels()
174
+ labels.insert(index, [display_code , text] )
175
+ end
176
+ # ADD HERE KEYLABEL
177
+ end
178
+ end
@@ -0,0 +1,238 @@
1
+ # ----------------------------------------------------------------------------- #
2
+ # File: rcombo.rb
3
+ # Description: Non-editable combo box.
4
+ # Make it dead-simple to use.
5
+ # This is a simpler version of the original ComboBox which allowed
6
+ # editing and used rlistbox. This simpler class is meant for the rbcurse
7
+ # core package and will only depend on a core class if at all.
8
+ # Author: rkumar http://github.com/rkumar/rbcurse/
9
+ # Date: 2011-11-11 - 21:42
10
+ # License: Same as Ruby's License (http://www.ruby-lang.org/LICENSE.txt)
11
+ # Last update: use ,,L
12
+ # ----------------------------------------------------------------------------- #
13
+ #
14
+ require 'rbcurse'
15
+
16
+ include RubyCurses
17
+ module RubyCurses
18
+ extend self
19
+
20
+ # the quick approach would be to use field, and just add a popup.
21
+ # Or since we are not editing, we could use a Label and a popup
22
+ # Or just display a label and a popup without using anything else.
23
+ #
24
+
25
+ class ComboBox < Field
26
+ include RubyCurses::EventHandler
27
+ dsl_accessor :list_config
28
+
29
+ attr_accessor :current_index
30
+ # the symbol you want to use for combos
31
+ attr_accessor :COMBO_SYMBOL
32
+ attr_accessor :show_symbol # show that funny symbol after a combo to signify its a combo
33
+ dsl_accessor :arrow_key_policy # :IGNORE :NEXT_ROW :POPUP
34
+
35
+ def initialize form, config={}, &block
36
+ @arrow_key_policy = :ignore
37
+ @editable = false
38
+ @COMBO_SYMBOL = "v".ord # trying this out
39
+ @current_index = 0
40
+ super
41
+ # added if check since it was overriding set_buffer in creation. 2009-01-18 00:03
42
+ set_buffer @list[@current_index].dup if @buffer.nil? or @buffer.empty?
43
+ init_vars
44
+ @_events.push(*[:CHANGE, :ENTER_ROW, :LEAVE_ROW])
45
+ end
46
+ def init_vars
47
+ super
48
+ @show_symbol = true if @show_symbol.nil? # if set to false don't touch
49
+ #@show_symbol = false if @label # 2011-11-13
50
+ @COMBO_SYMBOL ||= FFI::NCurses::ACS_DARROW #GEQUAL
51
+ bind_key(KEY_UP) { previous_row }
52
+ bind_key(KEY_DOWN) { next_row }
53
+ end
54
+ def selected_item
55
+ @list[@current_index]
56
+ end
57
+ def selected_index
58
+ @current_index
59
+ end
60
+
61
+ ##
62
+ # convert given list to datamodel
63
+ def list alist=nil
64
+ return @list if alist.nil?
65
+ #@list = RubyCurses::ListDataModel.new(alist)
66
+ @list = alist
67
+ end
68
+ ##
69
+ # combo edit box key handling
70
+ # removed UP and DOWN and bound it, so it can be unbound
71
+ def handle_key(ch)
72
+ @current_index ||= 0
73
+ # added 2009-01-18 22:44 no point moving horiz or passing up to Field if not edit
74
+ if !@editable
75
+ if ch == KEY_LEFT or ch == KEY_RIGHT
76
+ return :UNHANDLED
77
+ end
78
+ end
79
+ case @arrow_key_policy
80
+ when :ignore
81
+ if ch == KEY_DOWN or ch == KEY_UP
82
+ return :UNHANDLED
83
+ end
84
+ when :popup
85
+ if ch == KEY_DOWN or ch == KEY_UP
86
+ popup
87
+ end
88
+ end
89
+ case ch
90
+ #when KEY_UP # show previous value
91
+ # previous_row
92
+ #when KEY_DOWN # show previous value
93
+ # next_row
94
+ # adding spacebar to popup combo, as in microemacs 2010-10-01 13:21
95
+ when 32, KEY_DOWN+ META_KEY # alt down
96
+ popup # pop up the popup
97
+ else
98
+ super
99
+ end
100
+ end
101
+ def DEPprevious_row
102
+ @current_index -= 1 if @current_index > 0
103
+ set_buffer @list[@current_index].dup
104
+ set_modified(true) ## ??? not required
105
+ fire_handler :ENTER_ROW, self
106
+ @list.on_enter_row self
107
+ end
108
+ def DEPnext_row
109
+ @current_index += 1 if @current_index < @list.length()-1
110
+ set_buffer @list[@current_index].dup
111
+ set_modified(true) ## ??? not required
112
+ fire_handler :ENTER_ROW, self
113
+ @list.on_enter_row self
114
+ end
115
+ ##
116
+ # calls a popup list
117
+ # TODO: should not be positioned so that it goes off edge
118
+ # user's customizations of list should be passed in
119
+ # The dup of listconfig is due to a tricky feature/bug.
120
+ # I try to keep the config hash and instance variables in synch. So
121
+ # this config hash is sent to popuplist which updates its row col and
122
+ # next time we pop up the popup row and col are zero.
123
+ #
124
+ #
125
+ # added dup in PRESS since editing edit field mods this
126
+ # on pressing ENTER, value set back and current_index updated
127
+ def popup
128
+ @list_config ||= {}
129
+ @list_config[:row] ||= @row
130
+ @list_config[:col] ||= @col
131
+ @list_config[:relative_to] ||= self
132
+ # this does not allow us to bind to events in the list
133
+ index = popuplist @list, @list_config
134
+ if index
135
+ set_buffer @list[index].dup
136
+ set_modified(true) if @current_index != index
137
+ @current_index = index
138
+ end
139
+ end
140
+ def OLDpopup
141
+ listconfig = (@list_config && @list_config.dup) || {}
142
+ dm = @list
143
+ # current item in edit box will be focussed when list pops up
144
+ #$log.debug "XXX POPUP: #{dm.selected_index} = #{@current_index}, value #{@buffer}"
145
+ # we are having some problms when using this in a list. it retains earlier value
146
+ _index = dm.index @buffer
147
+ dm.selected_index = _index # @current_index
148
+ poprow = @row+0 # one row below the edit box
149
+ popcol = @col
150
+ dlength = @display_length
151
+ f = self
152
+ @popup = RubyCurses::PopupList.new do
153
+ row poprow
154
+ col popcol
155
+ width dlength
156
+ list_data_model dm
157
+ list_selection_mode 'single'
158
+ relative_to f
159
+ list_config listconfig
160
+ bind(:PRESS) do |index|
161
+ f.set_buffer dm[index].dup
162
+ f.set_modified(true) if f.current_index != index
163
+ f.current_index = index
164
+ end
165
+ end
166
+ end
167
+
168
+ # Field putc advances cursor when it gives a char so we override this
169
+ def putc c
170
+ if c >= 0 and c <= 127
171
+ ret = putch c.chr
172
+ if ret == 0
173
+ addcol 1 if @editable
174
+ set_modified
175
+ end
176
+ end
177
+ return -1 # always ??? XXX
178
+ end
179
+ ##
180
+ # field does not give char to non-editable fields so we override
181
+ def putch char
182
+ @current_index ||= 0
183
+ if @editable
184
+ super
185
+ return 0
186
+ else
187
+ match = next_match(char)
188
+ set_buffer match unless match.nil?
189
+ fire_handler :ENTER_ROW, self
190
+ end
191
+ @modified = true
192
+ fire_handler :CHANGE, self # 2008-12-09 14:51 ???
193
+ 0
194
+ end
195
+ ##
196
+ # the sets the next match in the edit field
197
+ def next_match char
198
+ start = @current_index
199
+ start.upto(@list.length-1) do |ix|
200
+ if @list[ix][0,1].casecmp(char) == 0
201
+ return @list[ix] unless @list[ix] == @buffer
202
+ end
203
+ @current_index += 1
204
+ end
205
+ ## could not find, start from zero
206
+ @current_index = 0
207
+ start = [@list.length()-1, start].min
208
+ 0.upto(start) do |ix|
209
+ if @list[ix][0,1].casecmp(char) == 0
210
+ return @list[ix] unless @list[ix] == @buffer
211
+ end
212
+ @current_index += 1
213
+ end
214
+ @current_index = [@list.length()-1, @current_index].min
215
+ return nil
216
+ end
217
+ ##
218
+ # on leaving the listbox, update the combo/datamodel.
219
+ # we are using methods of the datamodel. Updating our list will have
220
+ # no effect on the list, and wont trigger events.
221
+ # Do not override.
222
+ def on_leave
223
+ fire_handler :LEAVE, self
224
+ end
225
+
226
+ def repaint
227
+ super
228
+ c = @col + @display_length
229
+ if @show_symbol # 2009-01-11 18:47
230
+ # i have changed c +1 to c, since we have no right to print beyond display_length
231
+ @form.window.mvwaddch @row, c, @COMBO_SYMBOL # Ncurses::ACS_GEQUAL
232
+ @form.window.mvchgat(y=@row, x=c, max=1, Ncurses::A_REVERSE|Ncurses::A_UNDERLINE, $datacolor, nil)
233
+ end
234
+ end
235
+
236
+ end # class ComboBox
237
+
238
+ end # module
@@ -0,0 +1,415 @@
1
+ =begin
2
+ * Name: A container that manages components placed in it but
3
+ is not a form. Thus it can be safely placed as a widget
4
+ without all the complicatinos of a form embedded inside another.
5
+ NOTE: Still experimental
6
+ * Description
7
+ * Author: rkumar (http://github.com/rkumar/rbcurse/)
8
+ * Date: 21.10.11 - 00:29
9
+ * License: Same as Ruby's License (http://www.ruby-lang.org/LICENSE.txt)
10
+
11
+ * Last update: 23.10.11 - 00:29
12
+ == CHANGES
13
+ Focusables so we don't focus on label
14
+ == TODO
15
+ How to put blank lines in stack - use a blank label
16
+
17
+ - The contaomers and multis need to do their own on_enter and on_leave
18
+ management, they cannot rely on some other container doing it.
19
+ We can only rely on handle_key being called. HK should determine
20
+ whether any set_form row etc needs to be done.
21
+ - Should have its own stack and flow
22
+ =end
23
+
24
+ require 'rbcurse'
25
+
26
+ include RubyCurses
27
+ module RubyCurses
28
+ extend self
29
+
30
+ # This is an attempt at having a container which can contain multiple
31
+ # widgets without being a form itself. Having forms within forms
32
+ # complicates code too much, esp cursor positioning. e.g. tabbedpane
33
+
34
+ class Container < Widget
35
+
36
+ dsl_accessor :suppress_borders #to_print_borders
37
+ dsl_accessor :border_attrib, :border_color
38
+ dsl_accessor :title #set this on top
39
+ dsl_accessor :title_attrib #bold, reverse, normal
40
+ # should container stack objects ignoring users row col
41
+ # this is esp needed since App sets row and col which is too early
42
+ # This is now the default value, till i can redo things
43
+ #dsl_accessor :stack
44
+ dsl_accessor :positioning # absolute, relative, stack
45
+ attr_reader :current_component
46
+
47
+ def initialize form=nil, config={}, &block
48
+ @suppress_borders = false
49
+ @row_offset = @col_offset = 1
50
+ @_events ||= []
51
+ @stack = true
52
+ @positioning = :stack
53
+ super
54
+ @focusable = true
55
+ @editable = false
56
+ @components = [] # all components
57
+ @focusables = [] # focusable components, makes checks easier
58
+
59
+ init_vars
60
+ end
61
+ def init_vars
62
+ @repaint_required = true
63
+ @row_offset = @col_offset = 0 if @suppress_borders # FIXME supposed to use this !!
64
+
65
+ @internal_width = 2
66
+ @internal_width = 1 if @suppress_borders
67
+ @name ||= "AContainer"
68
+ @first_time = true
69
+
70
+ end
71
+
72
+ # NOTE: since we are handling the traversal, we delink the object from any
73
+ # form's widgets array that might have been added. Whenever a form is available,
74
+ # we set it (without adding widget to it) so it can print using the form's window.
75
+ #
76
+ # @param [Widget] to add
77
+ def add *items
78
+ items.each do |c|
79
+ raise ArgumentError, "Nil component passed to add" unless c
80
+ if c.is_a? Widget
81
+ if c.form && c.form != @form
82
+ $log.debug " removing widget VIMSPLIT #{c.class} wr: #{c.row} row:#{@row} ht:#{@height} "
83
+ c.form.remove_widget c
84
+ c.form = nil
85
+ # or should i just stack them myself and screw what you've asked for
86
+ end
87
+ # take it out of form's control. We will control it.
88
+ if c.form
89
+ c.form.remove_widget c
90
+ end
91
+ # shoot, what if at this point the container does not have a form
92
+ attach_form c if @form
93
+ end
94
+ # most likely if you have created both container and widgets
95
+ # inside app, it would have given row after container
96
+
97
+ @components << c
98
+ if c.focusable
99
+ @focusables << c
100
+ @current_component ||= c # only the first else cursor falls on last on enter
101
+ end
102
+
103
+ end # items each
104
+ self
105
+ end
106
+
107
+ # When we get a form, we silently attach it to this object, without the form
108
+ # knowing. We don't want form managing this object.
109
+ def attach_form c
110
+ c.form = @form
111
+ c.override_graphic @graphic
112
+ c.parent_component = self
113
+ end
114
+ alias :add_widget :add
115
+ def widgets; @components; end
116
+ # what of by_name
117
+
118
+
119
+ # correct coordinates of comp esp if App has stacked them after this
120
+ # container
121
+ # It is best to use the simple stack feature. The rest could change at any time
122
+ # and is quite arbitrary. Some folks may set absolute locations if container
123
+ # is directly on a form, others may set relative locations if it is inside a
124
+ # tabbed pane or other container. Thus, stacks are best
125
+ def correct_component c
126
+ raise "Form is still not set in Container" unless @form
127
+ attach_form(c) unless c.form
128
+ @last_row ||= @row + 1
129
+ inset = 2
130
+ # 2011-10-20 current default behaviour is to stack
131
+ if @positioning == :stack
132
+ c.row = @last_row
133
+ c.col = @col + inset
134
+
135
+ # do not advance row, save col for next row
136
+ @last_row += 1
137
+ elsif @positioning == :relative # UNTESTED NOTE
138
+ if (c.row || 0) <= 0
139
+ $log.warn "c.row in CONTAINER is #{c.row} "
140
+ c.row = @last_row
141
+ @last_row += 1
142
+ elsif c.row > @row + @height -1
143
+ $log.warn "c.row in CONTAINER exceeds container. #{c.row} "
144
+ c.row -= @height - @row_offset
145
+ else
146
+ # this is where it should come
147
+ c.row += @row + @row_offset
148
+ @last_row = c.row + 1
149
+ end
150
+ if (c.col || 0) <= 0
151
+ c.col = @col + inset + @col_offset
152
+ elsif c.col > @col + @width -1
153
+ c.col -= @width
154
+ elsif c.col == @col
155
+ c.col += @col_offset + inset
156
+ else #f c.col < @col
157
+ c.col += @col+@col_offset
158
+ end
159
+ $log.debug "XXX: CORRECT #{c.name} r:#{c.row} c:#{c.col} "
160
+ end
161
+ @first_time = false
162
+ end
163
+ def check_component c
164
+ raise "row is less than container #{c.row} #{@row} " if c.row <= @row
165
+ raise "col is less than container #{c.col} #{@col} " if c.col <= @col
166
+ end
167
+
168
+ public
169
+ # repaint object
170
+ # called by Form, and sometimes parent component (if not form).
171
+ def repaint
172
+ my_win = @form ? @form.window : @target_window
173
+ @graphic = my_win unless @graphic
174
+ raise " #{@name} NO GRAPHIC set as yet CONTAINER paint " unless @graphic
175
+ @components.each { |e| correct_component e } if @first_time
176
+ #@components.each { |e| check_component e } # seeme one if printing out
177
+
178
+ #return unless @repaint_required
179
+
180
+ # if some major change has happened then repaint everything
181
+ if @repaint_required
182
+ $log.debug " VIM repaint graphic #{@graphic} "
183
+ print_borders unless @suppress_borders # do this once only, unless everything changes
184
+ @components.each { |e| e.repaint_all(true); e.repaint }
185
+ else
186
+ @components.each { |e| e.repaint }
187
+ end # if repaint_required
188
+
189
+ @repaint_required = false
190
+ end
191
+
192
+ private
193
+ def print_borders
194
+ width = @width
195
+ height = @height-1 # 2010-01-04 15:30 BUFFERED HEIGHT
196
+ window = @graphic # 2010-01-04 12:37 BUFFERED
197
+ startcol = @col
198
+ startrow = @row
199
+ @color_pair = get_color($datacolor)
200
+ #$log.debug "rlistb #{name}: window.print_border #{startrow}, #{startcol} , h:#{height}, w:#{width} , @color_pair, @attr "
201
+ window.print_border startrow, startcol, height, width, @color_pair, @attr
202
+ print_title
203
+ end
204
+ def print_title
205
+ $log.debug "CONTAINER PRINTING TITLE at #{row} #{col} "
206
+ @graphic.printstring( @row, @col+(@width-@title.length)/2, @title, @color_pair, @title_attrib) unless @title.nil?
207
+ end
208
+
209
+ public
210
+ # called by parent or form, otherwise its private
211
+ def handle_key ch
212
+ $log.debug " CONTAINER handle_key #{ch} "
213
+ return if @components.empty?
214
+ _multiplier = ($multiplier == 0 ? 1 : $multiplier )
215
+
216
+ # should this go here 2011-10-19
217
+ unless @_entered
218
+ $log.warn "XXX WARN: calling ON_ENTER since in this situation it was not called"
219
+ on_enter
220
+ end
221
+ if ch == KEY_TAB
222
+ $log.debug "CONTAINER GOTO NEXT TAB"
223
+ return goto_next_component
224
+ elsif ch == KEY_BTAB
225
+ return goto_prev_component
226
+ end
227
+ comp = @current_component
228
+ $log.debug " CONTAINER handle_key #{ch}: #{comp}"
229
+ if comp
230
+ ret = comp.handle_key(ch)
231
+ $log.debug " CONTAINER handle_key#{ch}: #{comp} returned #{ret} "
232
+ if ret != :UNHANDLED
233
+ comp.repaint # NOTE: if we don;t do this, then it won't get repainted. I will have to repaint ALL
234
+ # in repaint of this.
235
+ return ret
236
+ end
237
+ $log.debug "XXX CONTAINER key unhandled by comp #{comp.name} "
238
+ else
239
+ $log.warn "XXX CONTAINER key unhandled NULL comp"
240
+ end
241
+ case ch
242
+ when ?\C-c.getbyte(0)
243
+ $multiplier = 0
244
+ return 0
245
+ when ?0.getbyte(0)..?9.getbyte(0)
246
+ $log.debug " VIM coming here to set multiplier #{$multiplier} "
247
+ $multiplier *= 10 ; $multiplier += (ch-48)
248
+ return 0
249
+ end
250
+ ret = process_key ch, self
251
+ # allow user to map left and right if he wants
252
+ if ret == :UNHANDLED
253
+ case ch
254
+ when KEY_UP
255
+ # form will pick this up and do needful
256
+ return goto_prev_component #unless on_first_component?
257
+ when KEY_LEFT
258
+ # if i don't check for first component, key will go back to form,
259
+ # but not be processes. so focussed remain here, but be false.
260
+ # In case of returnign an unhandled TAB, on_leave will happen and cursor will move to
261
+ # previous component outside of this.
262
+ return goto_prev_component unless on_first_component?
263
+ when KEY_RIGHT
264
+ return goto_next_component #unless on_last_component?
265
+ when KEY_DOWN
266
+ return goto_next_component #unless on_last_component?
267
+ else
268
+ @_entered = false
269
+ return :UNHANDLED
270
+ end
271
+ end
272
+
273
+ $multiplier = 0
274
+ return 0
275
+ end
276
+ # Actually we should only go to current component if it accepted
277
+ # a key stroke. if user tabbed thru it, then no point going back to
278
+ # it. Go to first or last depending on TAB or BACKTAB otherwise.
279
+ # NOTE: if user comes in using DOWN or UP, last traversed component will get the focus
280
+ #
281
+ def on_enter
282
+ # if BTAB, the last comp XXX they must be focusable FIXME
283
+ if $current_key == KEY_BTAB || $current_key == KEY_UP
284
+ @current_component = @focusables.last
285
+ else
286
+ @current_component = @focusables.first
287
+ end
288
+ return unless @current_component
289
+ $log.debug " CONTAINER came to ON_ENTER #{@current_component} "
290
+ set_form_row
291
+ @_entered = true
292
+ end
293
+ # we cannot be sure that this will be called especially if this is embedded
294
+ # inside some other component
295
+ def on_leave
296
+ @_entered = false
297
+ super
298
+ end
299
+ def goto_next_component
300
+ if @current_component != nil
301
+ leave_current_component
302
+ if on_last_component?
303
+ #@_entered = false
304
+ return :UNHANDLED
305
+ end
306
+ @current_index = @focusables.index(@current_component)
307
+ index = @current_index + 1
308
+ f = @focusables[index]
309
+ if f
310
+ @current_index = index
311
+ @current_component = f
312
+ return set_form_row
313
+ end
314
+ end
315
+ @_entered = false
316
+ return :UNHANDLED
317
+ end
318
+ def goto_prev_component
319
+ if @current_component != nil
320
+ leave_current_component
321
+ if on_first_component?
322
+ @_entered = false
323
+ return :UNHANDLED
324
+ end
325
+ @current_index = @focusables.index(@current_component)
326
+ index = @current_index -= 1
327
+ f = @focusables[index]
328
+ if f
329
+ @current_index = index
330
+ @current_component = f
331
+ return set_form_row
332
+ end
333
+ end
334
+ return :UNHANDLED
335
+ end
336
+ # private
337
+ # XXX why are we calling 3 methods in a row, why not OE manages these 3
338
+ # There's double calling going on.
339
+ def set_form_row
340
+ return :UNHANDLED if @current_component.nil?
341
+ cc = @current_component
342
+ $log.debug "CONT #{@name} set_form_row calling sfr for #{cc.name}, r #{cc.row} c: #{cc.col} "
343
+ $log.debug " CONTAINER on enter sfr #{@current_component.name} #{@current_component} "
344
+
345
+ # bug caught here. we were printing a field before it had been set, so it printed out
346
+ @components.each { |e| correct_component e } if @first_time
347
+ @current_component.on_enter
348
+ @current_component.set_form_row # why was this missing in vimsplit. is it
349
+ $log.debug "CONT2 #{@name} set_form_row calling sfr for #{cc.name}, r #{cc.row} c: #{cc.col} "
350
+ # that on_enter does a set_form_row
351
+ @current_component.set_form_col # XXX
352
+ @current_component.repaint # OMG this could happen before we've set row and col
353
+ # XXX compo should do set_form_row and col if it has that
354
+ end
355
+ #
356
+ def set_form_col
357
+ return if @current_component.nil?
358
+ $log.debug " #{@name} CONTAINER EMPTY set_form_col calling sfc for #{@current_component.name} "
359
+ # already called from above.
360
+ #@current_component.set_form_col
361
+ end
362
+ # leave the component we are on.
363
+ # This should be followed by all containers, so that the on_leave action
364
+ # of earlier comp can be displayed, such as dimming components selections
365
+ def leave_current_component
366
+ @current_component.on_leave
367
+ # NOTE this is required, since repaint will just not happen otherwise
368
+ # Some components are erroneously repainting all, after setting this to true so it is
369
+ # working there.
370
+ @current_component.repaint_required true
371
+ $log.debug " after on_leave RCONT XXX #{@current_component.focussed} #{@current_component.name}"
372
+ @current_component.repaint
373
+ end
374
+
375
+ # is focus on first component FIXME check for focusable
376
+ def on_first_component?
377
+ @current_component == @focusables.first
378
+ end
379
+ # is focus on last component FIXME check for focusable
380
+ def on_last_component?
381
+ @current_component == @focusables.last
382
+ end
383
+ # set focus on given component
384
+ # Sometimes you have the handle to component, and you want to move focus to it
385
+ def goto_component comp
386
+ return if comp == @current_component
387
+ leave_current_component
388
+ @current_component = comp
389
+ set_form_row
390
+ end
391
+
392
+ # ADD HERE ABOVe
393
+ end # class
394
+ end # module
395
+
396
+ if __FILE__ == $PROGRAM_NAME
397
+ require 'rbcurse/core/util/app'
398
+ App.new do
399
+ f1 = field "name", :maxlen => 20, :display_length => 20, :bgcolor => :white,
400
+ :color => :black, :text => "abc", :label => " Name: ", :label_color_pair => @datacolor
401
+ f2 = field "email", :display_length => 20, :bgcolor => :white,
402
+ :color => :blue, :text => "me@google.com", :label => "Email: ", :label_color_pair => @datacolor
403
+ f3 = radio :group => :grp, :text => "red", :value => "RED", :color => :red
404
+ f4 = radio :group => :grp, :text => "blue", :value => "BLUE", :color => :blue
405
+ f5 = radio :group => :grp, :text => "green", :value => "GREEN", :color => :green
406
+ stack :margin_top => 2, :margin => 2 do
407
+ r = container :row => 1, :col => 2, :width => 80, :height => 20, :title => "A container"
408
+ r.add(f1)
409
+ r.add(f2)
410
+ r.add(f3,f4,f5)
411
+ sl = status_line
412
+ end # stack
413
+
414
+ end # app
415
+ end # if