rbhex-core 1.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 (108) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +20 -0
  3. data/CHANGELOG +2000 -0
  4. data/LICENSE +56 -0
  5. data/README.md +44 -0
  6. data/examples/abasiclist.rb +179 -0
  7. data/examples/alpmenu.rb +50 -0
  8. data/examples/app.sample +19 -0
  9. data/examples/atree.rb +100 -0
  10. data/examples/bline.rb +136 -0
  11. data/examples/common/file.rb +45 -0
  12. data/examples/data/README.markdown +9 -0
  13. data/examples/data/brew.txt +38 -0
  14. data/examples/data/color.2 +37 -0
  15. data/examples/data/gemlist.txt +60 -0
  16. data/examples/data/lotr.txt +12 -0
  17. data/examples/data/ports.txt +136 -0
  18. data/examples/data/table.txt +37 -0
  19. data/examples/data/tasks.csv +88 -0
  20. data/examples/data/tasks.txt +27 -0
  21. data/examples/data/todo.txt +10 -0
  22. data/examples/data/todo.txt.bak +10 -0
  23. data/examples/data/todocsv.csv +28 -0
  24. data/examples/data/unix1.txt +21 -0
  25. data/examples/data/unix2.txt +11 -0
  26. data/examples/dbdemo.rb +502 -0
  27. data/examples/dirtree.rb +94 -0
  28. data/examples/newtabbedwindow.rb +100 -0
  29. data/examples/newtesttabp.rb +92 -0
  30. data/examples/tabular.rb +146 -0
  31. data/examples/tasks.rb +178 -0
  32. data/examples/term2.rb +84 -0
  33. data/examples/testbuttons.rb +296 -0
  34. data/examples/testcombo.rb +102 -0
  35. data/examples/testfields.rb +195 -0
  36. data/examples/testkeypress.rb +72 -0
  37. data/examples/testlistbox.rb +170 -0
  38. data/examples/testmessagebox.rb +140 -0
  39. data/examples/testprogress.rb +116 -0
  40. data/examples/testree.rb +106 -0
  41. data/examples/testwsshortcuts.rb +66 -0
  42. data/examples/testwsshortcuts2.rb +128 -0
  43. data/lib/rbhex.rb +6 -0
  44. data/lib/rbhex/core/docs/index.txt +73 -0
  45. data/lib/rbhex/core/include/action.rb +80 -0
  46. data/lib/rbhex/core/include/actionmanager.rb +49 -0
  47. data/lib/rbhex/core/include/appmethods.rb +214 -0
  48. data/lib/rbhex/core/include/bordertitle.rb +48 -0
  49. data/lib/rbhex/core/include/chunk.rb +203 -0
  50. data/lib/rbhex/core/include/io.rb +553 -0
  51. data/lib/rbhex/core/include/listbindings.rb +74 -0
  52. data/lib/rbhex/core/include/listcellrenderer.rb +140 -0
  53. data/lib/rbhex/core/include/listeditable.rb +317 -0
  54. data/lib/rbhex/core/include/listscrollable.rb +663 -0
  55. data/lib/rbhex/core/include/listselectable.rb +271 -0
  56. data/lib/rbhex/core/include/multibuffer.rb +83 -0
  57. data/lib/rbhex/core/include/orderedhash.rb +77 -0
  58. data/lib/rbhex/core/include/ractionevent.rb +73 -0
  59. data/lib/rbhex/core/include/rchangeevent.rb +27 -0
  60. data/lib/rbhex/core/include/rhistory.rb +95 -0
  61. data/lib/rbhex/core/include/rinputdataevent.rb +47 -0
  62. data/lib/rbhex/core/include/vieditable.rb +172 -0
  63. data/lib/rbhex/core/include/widgetmenu.rb +66 -0
  64. data/lib/rbhex/core/system/colormap.rb +165 -0
  65. data/lib/rbhex/core/system/keyboard.rb +150 -0
  66. data/lib/rbhex/core/system/keydefs.rb +30 -0
  67. data/lib/rbhex/core/system/ncurses.rb +236 -0
  68. data/lib/rbhex/core/system/panel.rb +162 -0
  69. data/lib/rbhex/core/system/window.rb +913 -0
  70. data/lib/rbhex/core/util/ansiparser.rb +119 -0
  71. data/lib/rbhex/core/util/app.rb +1228 -0
  72. data/lib/rbhex/core/util/basestack.rb +410 -0
  73. data/lib/rbhex/core/util/bottomline.rb +1859 -0
  74. data/lib/rbhex/core/util/colorparser.rb +77 -0
  75. data/lib/rbhex/core/util/focusmanager.rb +31 -0
  76. data/lib/rbhex/core/util/padreader.rb +192 -0
  77. data/lib/rbhex/core/util/rcommandwindow.rb +604 -0
  78. data/lib/rbhex/core/util/rdialogs.rb +574 -0
  79. data/lib/rbhex/core/util/viewer.rb +149 -0
  80. data/lib/rbhex/core/util/widgetshortcuts.rb +506 -0
  81. data/lib/rbhex/core/version.rb +5 -0
  82. data/lib/rbhex/core/widgets/applicationheader.rb +103 -0
  83. data/lib/rbhex/core/widgets/box.rb +58 -0
  84. data/lib/rbhex/core/widgets/divider.rb +310 -0
  85. data/lib/rbhex/core/widgets/keylabelprinter.rb +194 -0
  86. data/lib/rbhex/core/widgets/rcombo.rb +253 -0
  87. data/lib/rbhex/core/widgets/rcontainer.rb +415 -0
  88. data/lib/rbhex/core/widgets/rlink.rb +30 -0
  89. data/lib/rbhex/core/widgets/rlist.rb +696 -0
  90. data/lib/rbhex/core/widgets/rmenu.rb +958 -0
  91. data/lib/rbhex/core/widgets/rmenulink.rb +22 -0
  92. data/lib/rbhex/core/widgets/rmessagebox.rb +387 -0
  93. data/lib/rbhex/core/widgets/rprogress.rb +118 -0
  94. data/lib/rbhex/core/widgets/rtabbedpane.rb +634 -0
  95. data/lib/rbhex/core/widgets/rtabbedwindow.rb +70 -0
  96. data/lib/rbhex/core/widgets/rtextarea.rb +960 -0
  97. data/lib/rbhex/core/widgets/rtextview.rb +739 -0
  98. data/lib/rbhex/core/widgets/rtree.rb +768 -0
  99. data/lib/rbhex/core/widgets/rwidget.rb +3277 -0
  100. data/lib/rbhex/core/widgets/scrollbar.rb +143 -0
  101. data/lib/rbhex/core/widgets/statusline.rb +113 -0
  102. data/lib/rbhex/core/widgets/tabular.rb +264 -0
  103. data/lib/rbhex/core/widgets/tabularwidget.rb +1142 -0
  104. data/lib/rbhex/core/widgets/textpad.rb +995 -0
  105. data/lib/rbhex/core/widgets/tree/treecellrenderer.rb +150 -0
  106. data/lib/rbhex/core/widgets/tree/treemodel.rb +428 -0
  107. data/rbhex-core.gemspec +32 -0
  108. metadata +172 -0
@@ -0,0 +1,194 @@
1
+ require 'rbhex/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
+ #attr_accessor :row_relative # lets only advertise this when we've tested it out
27
+
28
+ def initialize form, key_labels, config={}, &block
29
+
30
+ @name = "dock"
31
+ case key_labels
32
+ when Hash
33
+ raise "KeyLabelPrinter: KeyLabels cannot be a hash, Array of key labels required. Perhaps you did not pass labels"
34
+ when Array
35
+ else
36
+ raise "KeyLabelPrinter: Array of key labels required. Perhaps you did not pass labels"
37
+ end
38
+ super form, config, &block
39
+ @mode ||= :normal
40
+ #@key_labels = key_labels
41
+ @key_hash = {}
42
+ @key_hash[@mode] = key_labels
43
+ @editable = false
44
+ @focusable = false
45
+ unless @row
46
+ @row_relative = -2
47
+ @row = Ncurses.LINES + @row_relative
48
+ end
49
+ @col ||= 0
50
+ # if negativ row passed we store as relative to bottom, so we can maintain that.
51
+ if @row < 0
52
+ @row_relative = @row
53
+ @row = Ncurses.LINES - @row
54
+ else
55
+ @row_relative = (Ncurses.LINES - @row) * -1
56
+ end
57
+ @cols ||= Ncurses.COLS-1
58
+ @repaint_required = true
59
+ @footer_color_pair ||= $bottomcolor
60
+ @footer_mnemonic_color_pair ||= $reversecolor #2
61
+ end
62
+ def key_labels mode=@mode
63
+ @key_hash[mode]
64
+ end
65
+ # returns the keys as printed. these may or may not help
66
+ # in validation depedign on what you passed as zeroth index
67
+ def get_current_keys
68
+ a = []
69
+ @key_hash[@mode].each do |arr|
70
+ a << arr[0] unless arr.nil?
71
+ end
72
+ return a
73
+ end
74
+ def getvalue
75
+ @key_hash
76
+ end
77
+ def set_key_labels _key_labels, mode=:normal
78
+ @key_hash[mode] = _key_labels
79
+ end
80
+
81
+ ##
82
+ # XXX need to move wrapping etc up and done once.
83
+ def repaint
84
+ return unless @repaint_required
85
+ r,c = rowcol
86
+ # this should only happen if there's a change in window
87
+ if @row_relative
88
+ @row = Ncurses.LINES+@row_relative
89
+ end
90
+ arr = key_labels()
91
+ print_key_labels(arr, mode=@mode)
92
+ @repaint_required = false
93
+ end
94
+ # ?? does not use mode, i think key_labels is unused. a hash is now used 2011-10-11 XXX FIXME
95
+ # WARNING, i have not tested this after changing it.
96
+ def append_key_label key, label, mode=@mode
97
+ #@key_labels << [key, label] if !@key_labels.include? [key, label]
98
+ @key_hash[mode] << [key, label] if !@key_hash[mode].include? [key, label]
99
+ @repaint_required = true
100
+ end
101
+ def print_key_labels(arr = key_labels(), mode=@mode)
102
+ #return if !@show_key_labels # XXX
103
+ @win ||= @form.window
104
+ #$log.debug "XXX: PKL #{arr.length}, #{arr}"
105
+ @padding = @cols / (arr.length/2)
106
+ posx = 0
107
+ even = []
108
+ odd = []
109
+ arr.each_index { |i|
110
+ if i % 2 == 0
111
+ #arr[i+1] = ['',''] if arr[i+1].nil?
112
+ nextarr = arr[i+1] || ['', '']
113
+ keyw = [arr[i][0].length, nextarr[0].length].max
114
+ labelw = [arr[i][1].length, nextarr[1].length].max
115
+
116
+ even << [ sprintf("%*s", keyw, arr[i][0]), sprintf("%-*s", labelw, arr[i][1]) ]
117
+ odd << [ sprintf("%*s", keyw, nextarr[0]), sprintf("%-*s", labelw, nextarr[1]) ]
118
+ #$log.debug("loop even: #{even.inspect}")
119
+ else
120
+ end
121
+ }
122
+ #$log.debug("even: #{even.inspect}")
123
+ #$log.debug("odd : #{odd.inspect}")
124
+ #posy = @barrow-1
125
+ posy = @row
126
+ print_key_labels_row(posy, posx, even)
127
+ posy = @row+1
128
+ print_key_labels_row(posy, posx, odd)
129
+ # uncommented next line after ffi-ncurses else not showing till key press FFI 2011-09-17
130
+ @win.wrefresh # needed else secod row not shown after askchoice XXX
131
+ end
132
+ def print_key_labels_row(posy, posx, arr)
133
+ # FIXME: this logic of padding needs to take into account
134
+ # width of window
135
+ padding = 8
136
+ padding = 4 if arr.length > 5
137
+ padding = 2 if arr.length > 7
138
+ padding = 0 if arr.length > 9
139
+ #padding = @padding # XXX 2008-11-13 23:01
140
+ my_form_win = @win
141
+ @win.printstring(posy,0, "%-*s" % [@cols," "], @footer_color_pair, @attr)
142
+ arr.each do |kl|
143
+ key = kl[0]
144
+ lab = kl[1]
145
+ if key !="" # don't print that white blank space for fillers
146
+ color_pair= @footer_mnemonic_color_pair # $reversecolor #2
147
+ x = posx + (key.length - key.strip.length)
148
+ my_form_win.attron(Ncurses.COLOR_PAIR(color_pair))
149
+ my_form_win.mvprintw(posy, x, "%s" % kl[0].strip );
150
+ my_form_win.attroff(Ncurses.COLOR_PAIR(color_pair))
151
+ end
152
+ color_pair=@footer_color_pair
153
+ posx = posx + kl[0].length
154
+ my_form_win.attron(Ncurses.COLOR_PAIR(color_pair))
155
+
156
+ #lab = sprintf(" %s %*s" , kl[1], padding, " ");
157
+ lab = sprintf(" %s %s" , kl[1], " "*padding);
158
+ my_form_win.mvprintw(posy, posx, lab)
159
+ my_form_win.attroff(Ncurses.COLOR_PAIR(color_pair))
160
+ posx = posx + lab.length
161
+ end
162
+ end
163
+ ##
164
+ # updates existing label with a new one.
165
+ # @return true if updated, else false
166
+ # @example update "C-x", "C-x", "Disable"
167
+ def update_application_key_label(display_code, new_display_code, text)
168
+ @repaint_required = true
169
+ labels = key_labels()
170
+ raise "labels are nil !!!" unless labels
171
+ labels.each_index do |ix|
172
+ lab = labels[ix]
173
+ next if lab.nil?
174
+ if lab[0] == display_code
175
+ labels[ix] = [new_display_code , text]
176
+ $log.debug("updated #{labels[ix]}")
177
+ return true
178
+ end
179
+ end
180
+ return false
181
+ end
182
+ alias :update :update_application_key_label
183
+ ##
184
+ # inserts an application label at given index
185
+ # to add the key, use create_datakeys to add bindings
186
+ # remember to call restore_application_key_labels after updating/inserting
187
+ def insert_application_key_label(index, display_code, text)
188
+ @repaint_required = true
189
+ labels = key_labels()
190
+ labels.insert(index, [display_code , text] )
191
+ end
192
+ # ADD HERE KEYLABEL
193
+ end
194
+ end
@@ -0,0 +1,253 @@
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 rbhex
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: 2014-03-24 16:42
12
+ # ----------------------------------------------------------------------------- #
13
+ #
14
+ require 'rbhex'
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
+ # Thre is an undocumented variable +display_length+ which is the size of the label
24
+ # This is used to position the combo symbol and the popup. This can be calculated
25
+ # based on the label. 2014-03-24 - 16:42
26
+
27
+ class ComboBox < Field
28
+ include RubyCurses::EventHandler
29
+ dsl_accessor :list_config
30
+
31
+ attr_accessor :current_index
32
+ # the symbol you want to use for combos
33
+ attr_accessor :COMBO_SYMBOL
34
+ attr_accessor :show_symbol # show that funny symbol after a combo to signify its a combo
35
+ dsl_accessor :arrow_key_policy # :IGNORE :NEXT_ROW :POPUP
36
+
37
+ def initialize form, config={}, &block
38
+ @arrow_key_policy = :ignore
39
+ @editable = false
40
+ #@COMBO_SYMBOL = "v".ord # trying this out
41
+ # thanks hramrach for fix
42
+ if RUBY_VERSION < "1.9" then
43
+ @COMBO_SYMBOL = "v"[0] # trying this out
44
+ else
45
+ @COMBO_SYMBOL = "v".ord # trying this out
46
+ end
47
+ @current_index = 0
48
+ super
49
+ ## this was getting overridden this making the combo editable 2014-03-24 - 16:24
50
+ @editable = false
51
+ # added if check since it was overriding set_buffer in creation. 2009-01-18 00:03
52
+ set_buffer @list[@current_index].dup if @buffer.nil? or @buffer.empty?
53
+ init_vars
54
+ @_events.push(*[:CHANGE, :ENTER_ROW, :LEAVE_ROW])
55
+ end
56
+ def init_vars
57
+ super
58
+ @show_symbol = true if @show_symbol.nil? # if set to false don't touch
59
+ #@show_symbol = false if @label # 2011-11-13
60
+ @COMBO_SYMBOL ||= FFI::NCurses::ACS_DARROW #GEQUAL
61
+
62
+ # next 2 lines giving an error in newtabbedwindow.rb example since the methods
63
+ # have been deprecated.
64
+ #bind_key(KEY_UP) { previous_row }
65
+ #bind_key(KEY_DOWN) { next_row }
66
+ end
67
+ def selected_item
68
+ @list[@current_index]
69
+ end
70
+ def selected_index
71
+ @current_index
72
+ end
73
+
74
+ ##
75
+ # convert given list to datamodel
76
+ def list alist=nil
77
+ return @list if alist.nil?
78
+ #@list = RubyCurses::ListDataModel.new(alist)
79
+ @list = alist
80
+ end
81
+ ##
82
+ # combo edit box key handling
83
+ # removed UP and DOWN and bound it, so it can be unbound
84
+ def handle_key(ch)
85
+ @current_index ||= 0
86
+ # added 2009-01-18 22:44 no point moving horiz or passing up to Field if not edit
87
+ if !@editable
88
+ if ch == KEY_LEFT or ch == KEY_RIGHT
89
+ return :UNHANDLED
90
+ end
91
+ end
92
+ case @arrow_key_policy
93
+ when :ignore
94
+ if ch == KEY_DOWN or ch == KEY_UP
95
+ return :UNHANDLED
96
+ end
97
+ when :popup
98
+ if ch == KEY_DOWN or ch == KEY_UP
99
+ popup
100
+ end
101
+ end
102
+ case ch
103
+ #when KEY_UP # show previous value
104
+ # previous_row
105
+ #when KEY_DOWN # show previous value
106
+ # next_row
107
+ # adding spacebar to popup combo, as in microemacs 2010-10-01 13:21
108
+ when 32, KEY_DOWN+ META_KEY # alt down
109
+ popup # pop up the popup
110
+ else
111
+ super
112
+ end
113
+ end
114
+ def DEPprevious_row
115
+ @current_index -= 1 if @current_index > 0
116
+ set_buffer @list[@current_index].dup
117
+ set_modified(true) ## ??? not required
118
+ fire_handler :ENTER_ROW, self
119
+ @list.on_enter_row self
120
+ end
121
+ def DEPnext_row
122
+ @current_index += 1 if @current_index < @list.length()-1
123
+ set_buffer @list[@current_index].dup
124
+ set_modified(true) ## ??? not required
125
+ fire_handler :ENTER_ROW, self
126
+ @list.on_enter_row self
127
+ end
128
+ ##
129
+ # calls a popup list
130
+ # TODO: should not be positioned so that it goes off edge
131
+ # user's customizations of list should be passed in
132
+ # The dup of listconfig is due to a tricky feature/bug.
133
+ # I try to keep the config hash and instance variables in synch. So
134
+ # this config hash is sent to popuplist which updates its row col and
135
+ # next time we pop up the popup row and col are zero.
136
+ #
137
+ #
138
+ # added dup in PRESS since editing edit field mods this
139
+ # on pressing ENTER, value set back and current_index updated
140
+ def popup
141
+ @list_config ||= {}
142
+ @list_config[:row] ||= @row
143
+ #@list_config[:col] ||= @col
144
+ @list_config[:col] ||= @col + @display_length
145
+ @list_config[:relative_to] ||= self
146
+ # this does not allow us to bind to events in the list
147
+ index = popuplist @list, @list_config
148
+ if index
149
+ set_buffer @list[index].dup
150
+ set_modified(true) if @current_index != index
151
+ @current_index = index
152
+ end
153
+ end
154
+ def OLDpopup
155
+ listconfig = (@list_config && @list_config.dup) || {}
156
+ dm = @list
157
+ # current item in edit box will be focussed when list pops up
158
+ #$log.debug "XXX POPUP: #{dm.selected_index} = #{@current_index}, value #{@buffer}"
159
+ # we are having some problms when using this in a list. it retains earlier value
160
+ _index = dm.index @buffer
161
+ dm.selected_index = _index # @current_index
162
+ poprow = @row+0 # one row below the edit box
163
+ popcol = @col
164
+ dlength = @display_length
165
+ f = self
166
+ @popup = RubyCurses::PopupList.new do
167
+ row poprow
168
+ col popcol
169
+ width dlength
170
+ list_data_model dm
171
+ list_selection_mode 'single'
172
+ relative_to f
173
+ list_config listconfig
174
+ bind(:PRESS) do |index|
175
+ f.set_buffer dm[index].dup
176
+ f.set_modified(true) if f.current_index != index
177
+ f.current_index = index
178
+ end
179
+ end
180
+ end
181
+
182
+ # Field putc advances cursor when it gives a char so we override this
183
+ def putc c
184
+ if c >= 0 and c <= 127
185
+ ret = putch c.chr
186
+ if ret == 0
187
+ addcol 1 if @editable
188
+ set_modified
189
+ end
190
+ end
191
+ return -1 # always ??? XXX
192
+ end
193
+ ##
194
+ # field does not give char to non-editable fields so we override
195
+ def putch char
196
+ @current_index ||= 0
197
+ if @editable
198
+ raise "how is it editable here in combo"
199
+ super
200
+ return 0
201
+ else
202
+ match = next_match(char)
203
+ set_buffer match unless match.nil?
204
+ fire_handler :ENTER_ROW, self
205
+ end
206
+ @modified = true
207
+ fire_handler :CHANGE, self # 2008-12-09 14:51 ???
208
+ 0
209
+ end
210
+ ##
211
+ # the sets the next match in the edit field
212
+ def next_match char
213
+ start = @current_index
214
+ start.upto(@list.length-1) do |ix|
215
+ if @list[ix][0,1].casecmp(char) == 0
216
+ return @list[ix] unless @list[ix] == @buffer
217
+ end
218
+ @current_index += 1
219
+ end
220
+ ## could not find, start from zero
221
+ @current_index = 0
222
+ start = [@list.length()-1, start].min
223
+ 0.upto(start) do |ix|
224
+ if @list[ix][0,1].casecmp(char) == 0
225
+ return @list[ix] unless @list[ix] == @buffer
226
+ end
227
+ @current_index += 1
228
+ end
229
+ @current_index = [@list.length()-1, @current_index].min
230
+ return nil
231
+ end
232
+ ##
233
+ # on leaving the listbox, update the combo/datamodel.
234
+ # we are using methods of the datamodel. Updating our list will have
235
+ # no effect on the list, and wont trigger events.
236
+ # Do not override.
237
+ def on_leave
238
+ fire_handler :LEAVE, self
239
+ end
240
+
241
+ def repaint
242
+ super
243
+ c = @col + @display_length
244
+ if @show_symbol # 2009-01-11 18:47
245
+ # i have changed c +1 to c, since we have no right to print beyond display_length
246
+ @form.window.mvwaddch @row, c, @COMBO_SYMBOL # Ncurses::ACS_GEQUAL
247
+ @form.window.mvchgat(y=@row, x=c, max=1, Ncurses::A_REVERSE|Ncurses::A_UNDERLINE, $datacolor, nil)
248
+ end
249
+ end
250
+
251
+ end # class ComboBox
252
+
253
+ 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 'rbhex'
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 'rbhex/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