rbcurse-core 0.0.0

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 (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