rbcurse 1.1.5 → 1.2.0.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (123) hide show
  1. data/CHANGELOG +45 -0
  2. data/Makefile +1 -1
  3. data/Manifest.txt +91 -0
  4. data/NOTES +349 -2
  5. data/README.markdown +12 -0
  6. data/VERSION +1 -1
  7. data/examples/abasiclist.rb +25 -0
  8. data/examples/alpmenu.rb +42 -0
  9. data/examples/app.rb +883 -0
  10. data/examples/appcombo.rb +17 -0
  11. data/examples/appdirtree.rb +73 -0
  12. data/examples/appemail.rb +164 -0
  13. data/examples/appemaillb.rb +308 -0
  14. data/examples/appgcompose.rb +303 -0
  15. data/examples/appgmail.rb +951 -0
  16. data/examples/atree.rb +56 -0
  17. data/examples/dirtree.rb +78 -0
  18. data/examples/focusmanager.rb +31 -0
  19. data/examples/imap.rb +48 -0
  20. data/examples/menu1.rb +79 -0
  21. data/examples/multispl.rb +86 -0
  22. data/examples/rfe.rb +3 -4
  23. data/examples/rmail.rb +188 -0
  24. data/examples/s.rb +10 -0
  25. data/examples/scrollbar.rb +104 -0
  26. data/examples/splitp.rb +56 -0
  27. data/examples/table1.rb +30 -0
  28. data/examples/term.rb +48 -0
  29. data/examples/term2.rb +54 -0
  30. data/examples/test1.rb +4 -2
  31. data/examples/test2.rb +9 -9
  32. data/examples/testapp.rb +44 -0
  33. data/examples/testapp2.rb +51 -0
  34. data/examples/testcombo.rb +2 -2
  35. data/examples/testgmail.rb +46 -0
  36. data/examples/testlistbox.rb +0 -1
  37. data/examples/testmultispl.rb +199 -0
  38. data/examples/testree.rb +127 -0
  39. data/examples/testscroller.rb +0 -1
  40. data/examples/testscrolllb.rb +1 -1
  41. data/examples/testscrollp.rb +2 -1
  42. data/examples/testscrollta.rb +1 -1
  43. data/examples/testscrolltable.rb +1 -2
  44. data/examples/testsplit.rb +1 -1
  45. data/examples/testsplit2.rb +1 -1
  46. data/examples/testsplit3.rb +1 -1
  47. data/examples/testsplit3_1.rb +1 -1
  48. data/examples/testsplit3a.rb +1 -1
  49. data/examples/testsplit3b.rb +1 -1
  50. data/examples/testsplitta.rb +1 -1
  51. data/examples/testsplittv.rb +1 -1
  52. data/examples/testsplittvv.rb +1 -1
  53. data/examples/testtodo.rb +491 -488
  54. data/examples/testvimsplit.rb +111 -0
  55. data/examples/todo.db +0 -0
  56. data/examples/todocsv.csv +28 -0
  57. data/examples/viewtodo.rb +408 -403
  58. data/lib/rbcurse/action.rb +1 -0
  59. data/lib/rbcurse/app.rb +1294 -0
  60. data/lib/rbcurse/applicationheader.rb +7 -2
  61. data/lib/rbcurse/checkboxcellrenderer.rb +0 -12
  62. data/lib/rbcurse/colormap.rb +34 -8
  63. data/lib/rbcurse/comboboxcellrenderer.rb +0 -11
  64. data/lib/rbcurse/defaultlistselectionmodel.rb +23 -7
  65. data/lib/rbcurse/extras/bottomline.rb +1681 -0
  66. data/lib/rbcurse/extras/directorylist.rb +445 -0
  67. data/lib/rbcurse/extras/directorytree.rb +69 -0
  68. data/lib/rbcurse/extras/divider.rb +310 -0
  69. data/lib/rbcurse/extras/focusmanager.rb +31 -0
  70. data/lib/rbcurse/extras/listselectable.rb +222 -0
  71. data/lib/rbcurse/extras/masterdetail.rb +164 -0
  72. data/lib/rbcurse/extras/menutree.rb +63 -0
  73. data/lib/rbcurse/extras/rlink.rb +27 -0
  74. data/lib/rbcurse/extras/rmenulink.rb +21 -0
  75. data/lib/rbcurse/extras/scrollbar.rb +134 -0
  76. data/lib/rbcurse/extras/stdscrwindow.rb +247 -0
  77. data/lib/rbcurse/extras/tabular.rb +258 -0
  78. data/lib/rbcurse/extras/tabularwidget.rb +1070 -0
  79. data/lib/rbcurse/extras/viewer.rb +106 -0
  80. data/lib/rbcurse/io.rb +137 -80
  81. data/lib/rbcurse/keylabelprinter.rb +4 -0
  82. data/lib/rbcurse/listcellrenderer.rb +91 -59
  83. data/lib/rbcurse/listscrollable.rb +93 -95
  84. data/lib/rbcurse/listselectable.rb +60 -7
  85. data/lib/rbcurse/ractionevent.rb +67 -0
  86. data/lib/rbcurse/rbasiclistbox.rb +688 -0
  87. data/lib/rbcurse/rcombo.rb +5 -5
  88. data/lib/rbcurse/rcommandwindow.rb +555 -0
  89. data/lib/rbcurse/rinputdataevent.rb +12 -0
  90. data/lib/rbcurse/rlistbox.rb +305 -124
  91. data/lib/rbcurse/rmenu.rb +99 -46
  92. data/lib/rbcurse/rmessagebox.rb +13 -6
  93. data/lib/rbcurse/rmulticontainer.rb +54 -93
  94. data/lib/rbcurse/rmultisplit.rb +731 -0
  95. data/lib/rbcurse/rmultitextview.rb +3 -2
  96. data/lib/rbcurse/rpopupmenu.rb +0 -1
  97. data/lib/rbcurse/rprogress.rb +117 -0
  98. data/lib/rbcurse/rscrollpane.rb +2 -1
  99. data/lib/rbcurse/rsplitpane.rb +94 -20
  100. data/lib/rbcurse/rsplitpane2.rb +1009 -0
  101. data/lib/rbcurse/rtabbedpane.rb +3 -2
  102. data/lib/rbcurse/rtabbedwindow.rb +0 -1
  103. data/lib/rbcurse/rtable.rb +92 -64
  104. data/lib/rbcurse/rtextarea.rb +91 -57
  105. data/lib/rbcurse/rtextview.rb +223 -70
  106. data/lib/rbcurse/rtree.rb +723 -0
  107. data/lib/rbcurse/rviewport.rb +2 -1
  108. data/lib/rbcurse/rvimsplit.rb +768 -0
  109. data/lib/rbcurse/rwidget.rb +524 -325
  110. data/lib/rbcurse/table/tablecellrenderer.rb +1 -1
  111. data/lib/rbcurse/table/tabledatecellrenderer.rb +0 -1
  112. data/lib/rbcurse/tree/treecellrenderer.rb +137 -0
  113. data/lib/rbcurse/tree/treemodel.rb +428 -0
  114. data/lib/rbcurse/vieditable.rb +14 -13
  115. data/lib/ver/ncurses.rb +6 -0
  116. data/lib/ver/window.rb +67 -32
  117. metadata +99 -23
  118. data/bin/rbcurse +0 -0
  119. data/examples/rvimsplit.rb +0 -376
  120. data/examples/todo.rb +0 -1
  121. data/lib/rbcurse/rform.rb +0 -845
  122. data/lib/rbcurse/selectable.rb +0 -94
  123. data/rbcurse.gemspec +0 -188
@@ -0,0 +1,258 @@
1
+ #!/usr/bin/env ruby -w
2
+ =begin
3
+ * Name : A Quick take on tabular data. Readonly.
4
+ * Description : To show tabular data inside a control, rather than going by the huge
5
+ Table object, I want to create a simple, minimal table data generator.
6
+ This will be thrown into a TextView for the user to navigate, select
7
+ etc.
8
+ I would use this applications where the tabular data is fairly fixed
9
+ not where i want the user to select columns, move them, expand etc.
10
+ * :
11
+ * Author : rkumar
12
+ * Date :
13
+ * License :
14
+ Same as Ruby's License (http://www.ruby-lang.org/LICENSE.txt)
15
+
16
+ =end
17
+
18
+ #
19
+ # A simple tabular data generator. Given table data in arrays and a column heading row in arrays, it
20
+ # quickely generates tabular data. It only takes left and right alignment of columns into account.
21
+ # You may specify individual column widths. Else it will take the widths of the column names you supply
22
+ # in the startup array. You are encouraged to supply column widths.
23
+ # If no columns are specified, and no widths are given, it take the widths of the first row
24
+ # as a model to determine column widths.
25
+ #
26
+ module RubyCurses
27
+
28
+ class Tabular
29
+
30
+ def yield_or_eval &block
31
+ return unless block
32
+ if block.arity > 0
33
+ yield self
34
+ else
35
+ self.instance_eval(&block)
36
+ end
37
+ end
38
+ # stores column info internally
39
+ class ColumnInfo < Struct.new(:name, :w, :align)
40
+ end
41
+ # an array of column titles
42
+ attr_reader :columns
43
+ # boolean, does user want lines numbered
44
+ attr_accessor :numbering
45
+ # x is the + character used a field delim in separators
46
+ # y is the field delim used in data rows, default is pipe or bar
47
+ attr_accessor :x, :y
48
+
49
+ # takes first optional argument as array of column names
50
+ # second optional argument as array of data arrays
51
+ # @yield self
52
+ #
53
+ def initialize cols=nil, *args, &block
54
+ @chash = {}
55
+ @cw = {}
56
+ @calign = {}
57
+ @separ = @columns = @numbering = nil
58
+ @y = '|'
59
+ @x = '+'
60
+ self.columns = cols if cols
61
+ if !args.empty?
62
+ puts "ARGS after shift #{args} "
63
+ if !args.empty?
64
+ self.data = args
65
+ end
66
+ end
67
+ yield_or_eval(&block) if block_given?
68
+ end
69
+ #
70
+ # set columns names
71
+ # @param [Array<String>] column names, preferably padded out to width for column
72
+ def columns=(array)
73
+ @columns = array
74
+ @columns.each_with_index { |c,i|
75
+ @chash[i] = ColumnInfo.new(c, c.to_s.length)
76
+ @cw[i] ||= c.to_s.length
77
+ @calign[i] ||= :left
78
+ }
79
+ end
80
+ alias :headings= :columns=
81
+ #
82
+ # set data as an array of arrays
83
+ # @param [Array<Array>] data as array of arrays
84
+ def data=(list)
85
+ puts "got data: #{list.size} "
86
+ puts list
87
+ @list = list
88
+ end
89
+
90
+ # add a row of data
91
+ # @param [Array] an array containing entries for each column
92
+ def add array
93
+ @list ||= []
94
+ @list << array
95
+ end
96
+ alias :<< :add
97
+ alias :add_row :add
98
+
99
+ # set width of a given column
100
+ # @param [Number] column offset, starting 0
101
+ # @param [Number] width
102
+ def column_width colindex, width
103
+ @cw[colindex] ||= width
104
+ if @chash[colindex].nil?
105
+ @chash[colindex] = ColumnInfo.new("", width)
106
+ else
107
+ @chash[colindex].w = width
108
+ end
109
+ @chash
110
+ end
111
+
112
+ # set alignment of given column offset
113
+ # @param [Number] column offset, starting 0
114
+ # @param [Symbol] :left, :right
115
+ def align_column colindex, lrc
116
+ raise ArgumentError, "wrong alignment value sent" if ![:right, :left, :center].include? lrc
117
+ @calign[colindex] ||= lrc
118
+ if @chash[colindex].nil?
119
+ @chash[colindex] = ColumnInfo.new("", nil, lrc)
120
+ else
121
+ @chash[colindex].align = lrc
122
+ end
123
+ @chash
124
+ end
125
+
126
+ #
127
+ # Now returns an array with formatted data
128
+ # @return [Array<String>] array of formatted data
129
+ def render
130
+ buffer = []
131
+ _guess_col_widths
132
+ rows = @list.size.to_s.length
133
+ @rows = rows
134
+ _prepare_format
135
+
136
+ str = ""
137
+ if @numbering
138
+ str = " "*(rows+1)+@y
139
+ end
140
+ str << @fmstr % @columns
141
+ buffer << str
142
+ #puts "-" * str.length
143
+ buffer << separator
144
+ if @list
145
+ if @numbering
146
+ @fmstr = "%#{rows}d "+ @y + @fmstr
147
+ end
148
+ #@list.each { |e| puts e.join(@y) }
149
+ count = 0
150
+ @list.each_with_index { |r,i|
151
+ value = convert_value_to_text r, count
152
+ buffer << value
153
+ count += 1
154
+ }
155
+ end
156
+ buffer
157
+ end
158
+ def convert_value_to_text r, count
159
+ if r == :separator
160
+ return separator
161
+ end
162
+ if @numbering
163
+ r.insert 0, count+1
164
+ end
165
+ return @fmstr % r;
166
+ end
167
+ # use this for printing out on terminal
168
+ # @example
169
+ # puts t.to_s
170
+ def to_s
171
+ render().join "\n"
172
+ end
173
+ def add_separator
174
+ @list << :separator
175
+ end
176
+ def separator
177
+ return @separ if @separ
178
+ str = ""
179
+ if @numbering
180
+ str = "-"*(@rows+1)+@x
181
+ end
182
+ @cw.each_pair { |k,v| str << "-" * (v+1) + @x }
183
+ @separ = str.chop
184
+ end
185
+ private
186
+ def _guess_col_widths #:nodoc:
187
+ @list.each_with_index { |r, i|
188
+ break if i > 10
189
+ next if r == :separator
190
+ r.each_with_index { |c, j|
191
+ x = c.to_s.length
192
+ if @cw[j].nil?
193
+ @cw[j] = x
194
+ else
195
+ @cw[j] = x if x > @cw[j]
196
+ end
197
+ }
198
+ }
199
+ end
200
+ def _prepare_format #:nodoc:
201
+ @fmtstr = nil
202
+ fmt = []
203
+ @cw.each_with_index { |c, i|
204
+ w = @cw[i]
205
+ case @calign[i]
206
+ when :right
207
+ fmt << "%#{w}s "
208
+ else
209
+ fmt << "%-#{w}s "
210
+ end
211
+ }
212
+ @fmstr = fmt.join(@y)
213
+ end
214
+ end
215
+ end
216
+
217
+ if __FILE__ == $PROGRAM_NAME
218
+ include RubyCurses
219
+ t = Tabular.new(['a', 'b'], [1, 2], [3, 4])
220
+ puts t.to_s
221
+ puts
222
+ t = Tabular.new([" Name ", " Number ", " Email "])
223
+ t.add %w{ rahul 32 r@ruby.org }
224
+ t << %w{ _why 133 j@gnu.org }
225
+ t << %w{ Jane 1331 jane@gnu.org }
226
+ t.align_column 1, :right
227
+ puts t.to_s
228
+ puts
229
+
230
+ s = Tabular.new do |b|
231
+ b.columns = %w{ country continent text }
232
+ b << ["india","asia","a warm country" ]
233
+ b << ["japan","asia","a cool country" ]
234
+ b << ["russia","europe","a hot country" ]
235
+ b.column_width 2, 30
236
+ end
237
+ puts s.to_s
238
+ puts
239
+ puts "::::"
240
+ puts
241
+ s = Tabular.new do |b|
242
+ b.columns = %w{ place continent text }
243
+ b << ["india","asia","a warm country" ]
244
+ b << ["japan","asia","a cool country" ]
245
+ b << ["russia","europe","a hot country" ]
246
+ b << ["sydney","australia","a dry country" ]
247
+ b << ["canberra","australia","a dry country" ]
248
+ b << ["ross island","antarctica","a dry country" ]
249
+ b << ["mount terror","antarctica","a windy country" ]
250
+ b << ["mt erebus","antarctica","a cold place" ]
251
+ b << ["siberia","russia","an icy city" ]
252
+ b << ["new york","USA","a fun place" ]
253
+ b.column_width 0, 12
254
+ b.column_width 1, 12
255
+ b.numbering = true
256
+ end
257
+ puts s.to_s
258
+ end
@@ -0,0 +1,1070 @@
1
+ =begin
2
+ * Name: TabularWidget
3
+ * Description A widget based on Tabular
4
+ * Author: rk (arunachalesha)
5
+ * file created 2010-09-28 23:37
6
+ FIXME:
7
+
8
+ TODO
9
+ * guess_c : have some config : NEVER, FIRST_TIME, EACH_TIME
10
+ if user has specified widths then we don't wanna guess. guess_size 20, ALL.
11
+ * move columns
12
+ * hide columns - importnat since with sorting we may need to store an identifier which
13
+ should not be displayed
14
+ x data truncation based on col wid TODO
15
+ * TODO: search -- how is it working, but curpos is wrong.
16
+ * allow resize of column inside column header
17
+ * Now that we allow header to get focus, we should allow it to handle
18
+ keys, but its not an object like it was in rtable ! AARGH !
19
+ * NOTE: header could become an object in near future, but then why did we break
20
+ away from rtable ?
21
+ * TODO FIXME : after converting to convert_value_to_text and truncation etc, numbering is broken
22
+ * we are checking widths of columsn and we have added a column, so columns widths refer to wrong col
23
+ TODO : tabbing with w to take care of hidden columns and numbering. FIXME
24
+ TODO: we forgot about selection altogether. we need multiple select !!! as in gmail
25
+ subject list.
26
+ --------
27
+ * License:
28
+ Same as Ruby's License (http://www.ruby-lang.org/LICENSE.txt)
29
+
30
+ =end
31
+ require 'rbcurse'
32
+ require 'rbcurse/listscrollable'
33
+ require 'rbcurse/extras/tabular'
34
+ require 'rbcurse/extras/listselectable'
35
+
36
+ #include RubyCurses
37
+ module RubyCurses
38
+ extend self
39
+ # used when firing a column resize, so calling application can perhaps
40
+ # resize other columns.
41
+ class ColumnResizeEvent < Struct.new(:source, :index, :type); end
42
+
43
+ ##
44
+ # A viewable read only, scrollable table. This is supposed to be a
45
+ # +minimal+, and (hopefully) fast version of Table (@see rtable.rb).
46
+ class TabularWidget < Widget
47
+
48
+
49
+ include ListScrollable
50
+ include NewListSelectable
51
+ dsl_accessor :title # set this on top
52
+ dsl_accessor :title_attrib # bold, reverse, normal
53
+ dsl_accessor :footer_attrib # bold, reverse, normal
54
+ dsl_accessor :list # the array of arrays of data to be sent by user XXX RISKY bypasses set_content
55
+ dsl_accessor :maxlen # max len to be displayed
56
+ attr_reader :toprow # the toprow in the view (offsets are 0)
57
+ attr_reader :winrow # the row in the viewport/window
58
+ # painting the footer does slow down cursor painting slightly if one is moving cursor fast
59
+ dsl_accessor :print_footer
60
+ dsl_accessor :suppress_borders
61
+ attr_accessor :current_index
62
+ dsl_accessor :border_attrib, :border_color # color pair for border
63
+ dsl_accessor :header_attrib, :header_fgcolor, :header_bgcolor # 2010-10-15 13:21
64
+
65
+ # boolean, whether lines should be cleaned (if containing tabs/newlines etc)
66
+ dsl_accessor :sanitization_required
67
+ # boolean, whether column widths should be estimated based on data. If you want this,
68
+ # set to true each time you do a set_content
69
+ dsl_accessor :estimate_column_widths
70
+ # boolean, whether lines should be numbered
71
+ attr_accessor :numbering
72
+ # default or custom sorter
73
+ attr_reader :table_row_sorter
74
+
75
+ # @group select related
76
+ dsl_accessor :selection_mode
77
+ dsl_accessor :selected_color, :selected_bgcolor, :selected_attr
78
+
79
+ dsl_property :show_selector # boolean
80
+ dsl_property :row_selected_symbol
81
+ dsl_property :row_unselected_symbol
82
+ attr_accessor :selected_index # should we use only indices ??
83
+ # index of selected rows, if multiple selection asked for
84
+ attr_reader :selected_indices
85
+ attr_reader :_header_adjustment # we need to adjust when using current_index !!! UGH
86
+ # @endgroup select related
87
+
88
+ def initialize form = nil, config={}, &block
89
+ @focusable = true
90
+ @editable = false
91
+ @sanitization_required = true
92
+ @estimate_column_widths = true
93
+ @row = 0
94
+ @col = 0
95
+ @cw = {} # column widths keyed on column index - why not array ??
96
+ @pw = [] # preferred column widths 2010-10-20 12:58
97
+ @calign = {} # columns aligns values, on column index
98
+ @coffsets = {}
99
+ @suppress_borders = false
100
+ @row_offset = @col_offset = 1
101
+ @chash = {}
102
+ # this should have index of displayed column
103
+ # so user can reorder columns
104
+ #@column_position = [] # TODO
105
+ @separ = @columns = @numbering = nil
106
+ @y = '|'
107
+ @x = '+'
108
+ @list = []
109
+ @_header_adjustment = 0
110
+ @show_focus = false # don't highlight row under focus TODO
111
+ @selection_mode = :multiple # default is multiple, anything else given becomes single
112
+ @row_selected_symbol = '*'
113
+ @show_selector = true
114
+ super
115
+ # ideally this should have been 2 to take care of borders, but that would break
116
+ # too much stuff !
117
+ @win = @graphic
118
+
119
+ @_events.push :CHANGE # thru vieditable
120
+ @_events << :PRESS # new, in case we want to use this for lists and allow ENTER
121
+ @_events << :ENTER_ROW # new, should be there in listscrollable ??
122
+ @_events << :COLUMN_RESIZE_EVENT
123
+ install_keys # << almost jnuk now, clean off TODO
124
+ init_vars
125
+ map_keys
126
+ end
127
+ def init_vars #:nodoc:
128
+ @curpos = @pcol = @toprow = @current_index = 0
129
+ @repaint_all=true
130
+ @repaint_required=true
131
+
132
+ @row_offset = @col_offset = 0 if @suppress_borders == true
133
+ @internal_width = 2
134
+ @internal_width = 0 if @suppress_borders
135
+ # added 2010-02-11 15:11 RFED16 so we don't need a form.
136
+ @win_left = 0
137
+ @win_top = 0
138
+ @current_column = 0
139
+ # currently i scroll right only if current line is longer than display width, i should use
140
+ # longest line on screen.
141
+ @longest_line = 0 # the longest line printed on this page, used to determine if scrolling shd work
142
+ list_init_vars
143
+
144
+ end
145
+ def map_keys
146
+ # remove bindings from here. we call repeatedly
147
+ bind_key([?g,?g]){ goto_start } # mapping double keys like vim
148
+ bind_key([?',?']){ goto_last_position } # vim , goto last row position (not column)
149
+ bind_key(?/, :ask_search) # XXX TESTME
150
+ bind_key(?n, :find_more) # XXX TESTME
151
+ bind_key([?\C-x, ?>], :scroll_right)
152
+ bind_key([?\C-x, ?<], :scroll_left)
153
+ #bind_key(?r) { getstr("Enter a word: ") }
154
+ bind_key(?m, :disp_menu) # enhance this or cut it out - how can app leverage this. TODO
155
+ bind_key(?w, :next_column)
156
+ bind_key(?b, :previous_column)
157
+ list_bindings
158
+ end
159
+ def columns=(array)
160
+ @_header_adjustment = 1
161
+ @columns = array
162
+ @columns.each_with_index { |c,i|
163
+ @cw[i] ||= c.to_s.length
164
+ @calign[i] ||= :left
165
+ }
166
+ # maintains index in current pointer and gives next or prev
167
+ @column_pointer = Circular.new @columns.size()-1
168
+ end
169
+ alias :headings= :columns=
170
+ ##
171
+ # send in a list
172
+ #
173
+ # @param [Array / Tabular] data to be displayed
174
+ def set_content list
175
+ if list.is_a? RubyCurses::Tabular
176
+ @list = list
177
+ elsif list.is_a? Array
178
+ @list = list
179
+ else
180
+ raise "set_content expects Array not #{list.class}"
181
+ end
182
+ if @table_row_sorter
183
+ @table_row_sorter.model=@list
184
+ else
185
+ @table_row_sorter = TableRowSorter.new @list
186
+ end
187
+ @current_index = @_header_adjustment
188
+ @toprow = 0
189
+ @second_time = false # so that reestimation of column_widths
190
+ @repaint_required = true
191
+ @recalc_required = true
192
+ end
193
+ # add a row of data
194
+ # @param [Array] an array containing entries for each column
195
+ def add array
196
+ @list ||= []
197
+ @list << array
198
+ @repaint_required = true
199
+ @recalc_required = true
200
+ end
201
+ alias :<< :add
202
+ alias :add_row :add
203
+ alias :append :add
204
+ def remove_all
205
+ @list = []
206
+ init_vars
207
+ end
208
+ def delete_at off0
209
+ ret=@list.delete_at off0
210
+ return ret
211
+ end
212
+ def []=(off0, data)
213
+ @list[off0] = data
214
+ end
215
+ def [](off0)
216
+ @list[off0]
217
+ end
218
+ def insert off0, *data
219
+ @list.insert off0, *data
220
+ end
221
+ # TODO more methods like in listbox so interchangeable, delete_at etc
222
+ def column_width colindex, width
223
+ return if width < 0
224
+ raise ArgumentError, "wrong width value sent: #{width} " if width.nil? || !width.is_a?(Fixnum) || width < 0
225
+ #@cw[colindex] = width
226
+ @pw[colindex] = width # XXXXX
227
+ get_column(colindex).width = width
228
+ @repaint_required = true
229
+ @recalc_required = true
230
+ end
231
+
232
+ # set alignment of given column offset
233
+ # @param [Number] column offset, starting 0
234
+ # @param [Symbol] :left, :right
235
+ def column_align colindex, lrc
236
+ raise ArgumentError, "wrong alignment value sent" if ![:right, :left, :center].include? lrc
237
+ @calign[colindex] = lrc
238
+ get_column(colindex).align = lrc
239
+ @repaint_required = true
240
+ #@recalc_required = true
241
+ end
242
+ # Set a column to hidden TODO we are not actually doing that
243
+ def column_hidden colindex, tf=true
244
+ #raise ArgumentError, "wrong alignment value sent" if ![:right, :left, :center].include? lrc
245
+ get_column(colindex).hidden = tf
246
+ @repaint_required = true
247
+ @recalc_required = true
248
+ end
249
+ def move_column
250
+
251
+ end
252
+ def expand_column
253
+ end
254
+ def contract_column
255
+ end
256
+ ## display this row number on top
257
+ # programmataically indicate a row to be top row
258
+ def top_row(*val)
259
+ if val.empty?
260
+ @toprow
261
+ else
262
+ @toprow = val[0] || 0
263
+ end
264
+ @repaint_required = true
265
+ end
266
+ ## ---- for listscrollable ---- ##
267
+ def scrollatrow #:nodoc:
268
+ # TODO account for headers
269
+ if @suppress_borders
270
+ @height - @_header_adjustment
271
+ else
272
+ @height - (2 + @_header_adjustment)
273
+ end
274
+ end
275
+ def row_count
276
+ #@list.length
277
+ get_content().length + @_header_adjustment
278
+ end
279
+ ##
280
+ # returns row of first match of given regex (or nil if not found)
281
+ def find_first_match regex #:nodoc:
282
+ @list.each_with_index do |row, ix|
283
+ return ix if !row.match(regex).nil?
284
+ end
285
+ return nil
286
+ end
287
+ ## returns the position where cursor was to be positioned by default
288
+ # It may no longer work like that.
289
+ def rowcol #:nodoc:
290
+ return @row+@row_offset, @col+@col_offset
291
+ end
292
+ ## print a border
293
+ ## Note that print_border clears the area too, so should be used sparingly.
294
+ def print_borders #:nodoc:
295
+ raise "#{self.class} needs width" unless @width
296
+ raise "#{self.class} needs height" unless @height
297
+
298
+ $log.debug " #{@name} print_borders, #{@graphic.name} "
299
+
300
+ bordercolor = @border_color || $datacolor
301
+ borderatt = @border_attrib || Ncurses::A_NORMAL
302
+ @graphic.print_border @row, @col, @height-1, @width, bordercolor, borderatt
303
+ print_title
304
+ end
305
+ def print_title #:nodoc:
306
+ raise "#{self.class} needs width" unless @width
307
+ $log.debug " print_title #{@row}, #{@col}, #{@width} "
308
+ @graphic.printstring( @row, @col+(@width-@title.length)/2, @title, $datacolor, @title_attrib) unless @title.nil?
309
+ end
310
+ def print_foot #:nodoc:
311
+ @footer_attrib ||= Ncurses::A_REVERSE
312
+ footer = "R: #{@current_index+1}, C: #{@curpos+@pcol}, #{@list.length} lines "
313
+ #$log.debug " print_foot calling printstring with #{@row} + #{@height} -1, #{@col}+2"
314
+ @graphic.printstring( @row + @height -1 , @col+2, footer, $datacolor, @footer_attrib)
315
+ @repaint_footer_required = false # 2010-01-23 22:55
316
+ end
317
+ ### FOR scrollable ###
318
+ def get_content
319
+ @list
320
+ #[:columns, :separator, *@list]
321
+ #[:columns, *@list]
322
+ end
323
+ def get_window #:nodoc:
324
+ @graphic
325
+ end
326
+
327
+ def repaint # Tabularwidget :nodoc:
328
+ if @screen_buffer.nil?
329
+ safe_create_buffer
330
+ @screen_buffer.name = "Pad::TABW_PAD_#{@name}" unless @screen_buffer.nil?
331
+ $log.debug " tabularwid creates pad #{@screen_buffer} #{@name}"
332
+ end
333
+
334
+ #return unless @repaint_required # 2010-02-12 19:08 TRYING - won't let footer print for col move
335
+ paint if @repaint_required
336
+ # raise "TV 175 graphic nil " unless @graphic
337
+ print_foot if @print_footer && @repaint_footer_required
338
+ buffer_to_window
339
+ end
340
+ def getvalue
341
+ @list
342
+ end
343
+ # returns value of current row.
344
+ # NOTE: you may need to adjust it with _header_adjustment - actually you can't
345
+ # this may give wrong row -- depends what you want.
346
+ def current_value
347
+ @list[@current_index-@_header_adjustment] # XXX added header_adju 2010-11-01 11:14
348
+ end
349
+ def real_index
350
+ @current_index-@_header_adjustment # XXX added header_adju 2010-11-06 19:38
351
+ end
352
+ # Tabularwidget
353
+ def handle_key ch #:nodoc:
354
+ if header_row?
355
+ ret = header_handle_key ch
356
+ return ret unless ret == :UNHANDLED
357
+ end
358
+ case ch
359
+ when ?\C-d.getbyte(0) #, 32
360
+ scroll_forward
361
+ when ?\C-b.getbyte(0)
362
+ scroll_backward
363
+ when ?\C-[.getbyte(0), ?t.getbyte(0)
364
+ goto_start #start of buffer # cursor_start
365
+ when ?\C-].getbyte(0), ?G.getbyte(0)
366
+ goto_end # end / bottom cursor_end
367
+ when KEY_UP, ?k.getbyte(0)
368
+ #select_prev_row
369
+ ret = up
370
+ check_curpos
371
+
372
+ when KEY_DOWN, ?j.getbyte(0)
373
+ ret = down
374
+ check_curpos
375
+ when KEY_LEFT, ?h.getbyte(0)
376
+ cursor_backward
377
+ when KEY_RIGHT, ?l.getbyte(0)
378
+ cursor_forward
379
+ when KEY_BACKSPACE, KEY_BSPACE, KEY_DELETE
380
+ cursor_backward
381
+ when ?\C-a.getbyte(0) #, ?0.getbyte(0)
382
+ # take care of data that exceeds maxlen by scrolling and placing cursor at start
383
+ @repaint_required = true if @pcol > 0 # tried other things but did not work
384
+ set_form_col 0
385
+ @pcol = 0
386
+ when ?\C-e.getbyte(0), ?$.getbyte(0)
387
+ # take care of data that exceeds maxlen by scrolling and placing cursor at end
388
+ # This use to actually pan the screen to actual end of line, but now somewhere
389
+ # it only goes to end of visible screen, set_form probably does a sanity check
390
+ blen = @buffer.rstrip.length
391
+ set_form_col blen
392
+ # search related
393
+ when @KEY_ASK_FIND
394
+ ask_search
395
+ when @KEY_FIND_MORE
396
+ find_more
397
+ when 10, 13, KEY_ENTER
398
+ #fire_handler :PRESS, self
399
+ fire_action_event
400
+ when ?0.getbyte(0)..?9.getbyte(0)
401
+ # FIXME the assumption here was that if numbers are being entered then a 0 is a number
402
+ # not a beg-of-line command.
403
+ # However, after introducing universal_argument, we can enters numbers using C-u and then press another
404
+ # C-u to stop. In that case a 0 should act as a command, even though multiplier has been set
405
+ if ch == ?0.getbyte(0) and $multiplier == 0
406
+ # copy of C-a - start of line
407
+ @repaint_required = true if @pcol > 0 # tried other things but did not work
408
+ set_form_col 0
409
+ @pcol = 0
410
+ return 0
411
+ end
412
+ # storing digits entered so we can multiply motion actions
413
+ $multiplier *= 10 ; $multiplier += (ch-48)
414
+ return 0
415
+ #when ?\C-u.getbyte(0)
416
+ ## multiplier. Series is 4 16 64
417
+ #@multiplier = (@multiplier == 0 ? 4 : @multiplier *= 4)
418
+ #return 0
419
+ when ?\M-l.getbyte(0) # just added 2010-03-05 not perfect
420
+ scroll_right # scroll data horizontally
421
+ when ?\M-h.getbyte(0)
422
+ scroll_left
423
+ when ?\C-c.getbyte(0)
424
+ $multiplier = 0
425
+ return 0
426
+ else
427
+ # check for bindings, these cannot override above keys since placed at end
428
+ begin
429
+ ret = process_key ch, self
430
+ rescue => err
431
+ $error_message = err
432
+ @form.window.print_error_message
433
+ $log.error " Tabularwidget ERROR #{err} "
434
+ $log.debug(err.backtrace.join("\n"))
435
+ # XXX caller app has no idea error occurred so can't do anything !
436
+ end
437
+ return :UNHANDLED if ret == :UNHANDLED
438
+ end
439
+ $multiplier = 0 # you must reset if you've handled a key. if unhandled, don't reset since parent could use
440
+ set_form_row
441
+ return 0 # added 2010-01-12 22:17 else down arrow was going into next field
442
+ end
443
+ #
444
+ # allow header to handle keys
445
+ # NOTE: header could become an object in near future
446
+ # We are calling a resize event and passing column index but do we really
447
+ # have a column object that user can access and do something with ?? XXX
448
+ #
449
+ def header_handle_key ch #:nodoc:
450
+ # TODO pressing = should revert to calculated size ?
451
+ col = _convert_curpos_to_column
452
+ #width = @cw[col]
453
+ width = @pw[col] || @cw[col]
454
+ case ch
455
+ when ?-.getbyte(0)
456
+ column_width col, width-1
457
+ # if this event has not been used in a sample it could change in near future
458
+ e = ColumnResizeEvent.new self, col, :DECREASE
459
+ fire_handler :COLUMN_RESIZE_EVENT, e
460
+ # can fire_hander so user can resize another column
461
+ return 0
462
+ when ?\+.getbyte(0)
463
+ column_width col, width+1
464
+ # if this event has not been used in a sample it could change in near future
465
+ e = ColumnResizeEvent.new self, col, :INCREASE
466
+ return 0
467
+ end
468
+ return :UNHANDLED
469
+ end
470
+ # newly added to check curpos when moving up or down
471
+ def check_curpos #:nodoc:
472
+ # if the cursor is ahead of data in this row then move it back
473
+ # i don't think this is required
474
+ return
475
+ if @pcol+@curpos > @buffer.length
476
+ addcol((@pcol+@buffer.length-@curpos)+1)
477
+ @curpos = @buffer.length
478
+ maxlen = (@maxlen || @width-@internal_width)
479
+
480
+ # even this row is gt maxlen, i.e., scrolled right
481
+ if @curpos > maxlen
482
+ @pcol = @curpos - maxlen
483
+ @curpos = maxlen-1
484
+ else
485
+ # this row is within maxlen, make scroll 0
486
+ @pcol=0
487
+ end
488
+ set_form_col
489
+ end
490
+ end
491
+ # set cursor on correct column tview
492
+ def set_form_col col1=@curpos #:nodoc:
493
+ @cols_panned ||= 0
494
+ @pad_offset ||= 0 # added 2010-02-11 21:54 since padded widgets get an offset.
495
+ @curpos = col1
496
+ maxlen = @maxlen || @width-@internal_width
497
+ #@curpos = maxlen if @curpos > maxlen
498
+ if @curpos > maxlen
499
+ @pcol = @curpos - maxlen
500
+ @curpos = maxlen - 1
501
+ @repaint_required = true # this is required so C-e can pan screen
502
+ else
503
+ @pcol = 0
504
+ end
505
+ # the rest only determines cursor placement
506
+ win_col = 0 # 2010-02-07 23:19 new cursor stuff
507
+ col2 = win_col + @col + @col_offset + @curpos + @cols_panned + @pad_offset
508
+ #$log.debug "TV SFC #{@name} setting c to #{col2} #{win_col} #{@col} #{@col_offset} #{@curpos} "
509
+ #@form.setrowcol @form.row, col
510
+ setrowcol nil, col2
511
+ @repaint_footer_required = true
512
+ end
513
+ def cursor_forward #:nodoc:
514
+ maxlen = @maxlen || @width-@internal_width
515
+ repeatm {
516
+ if @curpos < @width and @curpos < maxlen-1 # else it will do out of box
517
+ @curpos += 1
518
+ addcol 1
519
+ else
520
+ @pcol += 1 if @pcol <= @buffer.length
521
+ end
522
+ }
523
+ set_form_col
524
+ #@repaint_required = true
525
+ @repaint_footer_required = true # 2010-01-23 22:41
526
+ end
527
+ def addcol num #:nodoc:
528
+ #@repaint_required = true
529
+ @repaint_footer_required = true # 2010-01-23 22:41
530
+ if @form
531
+ @form.addcol num
532
+ else
533
+ @parent_component.form.addcol num
534
+ end
535
+ end
536
+ def addrowcol row,col #:nodoc:
537
+ #@repaint_required = true
538
+ @repaint_footer_required = true # 2010-01-23 22:41
539
+ if @form
540
+ @form.addrowcol row, col
541
+ else
542
+ @parent_component.form.addrowcol num
543
+ end
544
+ end
545
+ def cursor_backward #:nodoc:
546
+ repeatm {
547
+ if @curpos > 0
548
+ @curpos -= 1
549
+ set_form_col
550
+ #addcol -1
551
+ elsif @pcol > 0
552
+ @pcol -= 1
553
+ end
554
+ }
555
+ #@repaint_required = true
556
+ @repaint_footer_required = true # 2010-01-23 22:41
557
+ end
558
+
559
+ ## NOTE: earlier print_border was called only once in constructor, but when
560
+ ##+ a window is resized, and destroyed, then this was never called again, so the
561
+ ##+ border would not be seen in splitpane unless the width coincided exactly with
562
+ ##+ what is calculated in divider_location.
563
+ def paint #:nodoc:
564
+ my_win = nil
565
+ if @form
566
+ my_win = @form.window
567
+ else
568
+ my_win = @target_window
569
+ end
570
+ @graphic = my_win unless @graphic
571
+ @win_left = my_win.left
572
+ @win_top = my_win.top
573
+ #_guess_col_widths
574
+ tm = get_content
575
+ @left_margin ||= @row_selected_symbol.length
576
+ @width ||= @preferred_width
577
+ @height ||= [tm.length+3, 10].min
578
+ _prepare_format
579
+
580
+ print_borders if (@suppress_borders == false && @repaint_all) # do this once only, unless everything changes
581
+ rc = tm.length
582
+ _estimate_column_widths if rc > 0
583
+ _maxlen = @maxlen || @width-@internal_width
584
+ $log.debug " #{@name} Tabularwidget repaint width is #{@width}, height is #{@height} , maxlen #{maxlen}/ #{@maxlen}, #{@graphic.name} roff #{@row_offset} coff #{@col_offset}"
585
+ tr = @toprow
586
+ acolor = get_color $datacolor
587
+ h = scrollatrow()
588
+ r,c = rowcol
589
+ print_header
590
+ r += @_header_adjustment # for column header
591
+ @longest_line = @width #maxlen
592
+ 0.upto(h - @_header_adjustment) do |hh|
593
+ crow = tr+hh
594
+ if crow < rc
595
+ #focussed = @current_index == crow ? true : false
596
+ content = tm[crow]
597
+
598
+ columnrow = false
599
+ if content == :columns
600
+ columnrow = true
601
+ end
602
+
603
+ value = convert_value_to_text content, crow
604
+
605
+ @buffer = value if crow == @current_index
606
+ # next call modified string. you may wanna dup the string.
607
+ # rlistbox does
608
+ sanitize value if @sanitization_required
609
+ truncate value
610
+ ## set the selector symbol if requested
611
+ paint_selector crow, r+hh, c, acolor, @attr
612
+
613
+ #@graphic.printstring r+hh, c, "%-*s" % [@width-@internal_width,value], acolor, @attr
614
+ #print_data_row( r+hh, c, "%-*s" % [@width-@internal_width,value], acolor, @attr)
615
+ print_data_row( r+hh, c+@left_margin, @width-@internal_width-@left_margin, value, acolor, @attr)
616
+
617
+ else
618
+ # clear rows
619
+ @graphic.printstring r+hh, c, " " * (@width-@internal_width-@left_margin), acolor,@attr
620
+ end
621
+ end
622
+ @repaint_required = false
623
+ @repaint_footer_required = true
624
+ @buffer_modified = true # required by form to call buffer_to_screen
625
+ @repaint_all = false
626
+
627
+ end
628
+
629
+ # print data rows
630
+ def print_data_row r, c, len, value, color, attr
631
+ @graphic.printstring r, c, "%-*s" % [len,value], color, attr
632
+ end
633
+
634
+ # print header row
635
+ # allows user to override
636
+ def print_header_row r, c, len, value, color, attr
637
+ #acolor = $promptcolor
638
+ @graphic.printstring r, c, "%-*s" % [len ,value], color, attr
639
+ end
640
+ def separator
641
+ #return @separ if @separ
642
+ str = ""
643
+ if @numbering
644
+ rows = @list.size.to_s.length
645
+ str = "-"*(rows+1)+@x
646
+ end
647
+ @cw.each_pair { |k,v| str << "-" * (v+1) + @x }
648
+ @separ = str.chop
649
+ end
650
+ # prints the column headers
651
+ # Uses +convert_value_to_text+ and +print_header_row+
652
+ def print_header
653
+ r,c = rowcol
654
+ value = convert_value_to_text :columns, 0
655
+ len = @width - @internal_width
656
+ truncate value # else it can later suddenly exceed line
657
+ @header_color_pair ||= get_color $promptcolor, @header_fgcolor, @header_bgcolor
658
+ @header_attrib ||= @attr
659
+ print_header_row r, c, len, value, @header_color_pair, @header_attrib
660
+ end
661
+ # convert data object to a formatted string for print
662
+ # NOTE: useful for overriding and doing custom formatting
663
+ # @param [Array] array of column data, mostly +String+
664
+ # Can also be :columns or :separator
665
+ # @param [Fixnum] index of row in data
666
+ def convert_value_to_text r, count
667
+ if r == :separator
668
+ return separator
669
+ elsif r == :columns
670
+ return "??" unless @columns # column was requested but not supplied
671
+ # FIXME putting entire header into this, take care of hidden
672
+ r = []
673
+ @columns.each_with_index { |e, i| r << e unless get_column(i).hidden }
674
+ return @headerfmtstr % r if @numbering
675
+ end
676
+ str = ""
677
+
678
+ if @numbering
679
+ #r = r.dup
680
+ #r.insert 0, count+1
681
+ # TODO get the width
682
+ str << "%*d |"% [2, count + 1]
683
+ end
684
+ # unroll r, get width and align
685
+ # This is to truncate column to requested width
686
+ fmta = []
687
+ r.each_with_index { |e, i|
688
+ next if get_column(i).hidden == true
689
+ #w = @pw[i] || @cw[i] # XXX
690
+ #$log.debug "WIDTH XXX #{i} w= #{w} , #{@pw[i]}, #{@cw[i]} :: #{e} " if $log.debug?
691
+ w = @cw[i]
692
+ l = e.to_s.length
693
+ fmt = "%-#{w}s "
694
+ if l > w
695
+ fmt = "%.#{w}s "
696
+ else
697
+ # ack we don;t need to recalc this we can pull out of hash FIXME
698
+ case @calign[i]
699
+ when :right
700
+ fmt = "%#{w}s "
701
+ else
702
+ fmt = "%-#{w}s "
703
+ end
704
+ end
705
+ str << fmt % e
706
+ fmta << fmt
707
+ }
708
+ #fmstr = fmta.join(@y)
709
+ #return fmstr % r; # FIXME hidden column still goes int
710
+ return str
711
+ end
712
+ # perhaps we can delete this since it does not respect @pw
713
+ # @deprecated (see _estimate_column_widths)
714
+ def _guess_col_widths #:nodoc:
715
+ return if @second_time
716
+ @second_time = true if @list.size > 0
717
+ @list.each_with_index { |r, i|
718
+ break if i > 10
719
+ next if r == :separator
720
+ r.each_with_index { |c, j|
721
+ x = c.to_s.length
722
+ if @cw[j].nil?
723
+ @cw[j] = x
724
+ else
725
+ @cw[j] = x if x > @cw[j]
726
+ end
727
+ }
728
+ }
729
+ #sum = @cw.values.inject(0) { |mem, var| mem + var }
730
+ #$log.debug " SUM is #{sum} "
731
+ total = 0
732
+ @cw.each_pair { |name, val| total += val }
733
+ @preferred_width = total + (@cw.size() *2)
734
+ @preferred_width += 4 if @numbering # FIXME this 4 is rough
735
+ end
736
+ def _estimate_column_widths #:nodoc:
737
+ return unless @estimate_column_widths
738
+ @estimate_column_widths = false # XXX testing why its failing in gmail
739
+ @columns.each_with_index { |c, i|
740
+ if @pw[i]
741
+ @cw[i] = @pw[i]
742
+ else
743
+ @cw[i] = calculate_column_width(i)
744
+ end
745
+ }
746
+ total = 0
747
+ @cw.each_pair { |name, val| total += val }
748
+ @preferred_width = total + (@cw.size() *2)
749
+ @preferred_width += 4 if @numbering # FIXME this 4 is rough
750
+ end
751
+ # if user has not specified preferred_width for a column
752
+ # then we can calculate the same based on data
753
+ def calculate_column_width col
754
+ ret = @cw[col] || 2
755
+ ctr = 0
756
+ @list.each_with_index { |r, i|
757
+ #next if i < @toprow # this is also a possibility, it checks visible rows
758
+ break if ctr > 10
759
+ ctr += 1
760
+ next if r == :separator
761
+ c = r[col]
762
+ x = c.to_s.length
763
+ ret = x if x > ret
764
+ }
765
+ ret
766
+ end
767
+ def _prepare_format #:nodoc:
768
+ @fmtstr = nil
769
+ fmt = []
770
+ total = 0
771
+ @cw.each_with_index { |c, i|
772
+ next if get_column(i).hidden == true # added 2010-10-28 19:08
773
+ w = @cw[i]
774
+ @coffsets[i] = total
775
+ total += w + 2
776
+
777
+ case @calign[i]
778
+ when :right
779
+ fmt << "%#{w}s "
780
+ else
781
+ fmt << "%-#{w}s "
782
+ end
783
+ }
784
+ @fmstr = fmt.join(@y)
785
+ if @numbering
786
+ @rows ||= @list.size.to_s.length
787
+ @headerfmtstr = " "*(@rows+1)+@y + @fmstr
788
+ @fmstr = "%#{@rows}d "+ @y + @fmstr
789
+ @coffsets.each_pair { |name, val| @coffsets[name] = val + @rows + 2 }
790
+ end
791
+ #$log.debug " FMT : #{@fmstr} "
792
+ #alert "format: #{@fmstr} "
793
+ end
794
+ ## this is just a test of prompting user for a string
795
+ #+ as an alternative to the dialog.
796
+ def getstr prompt, maxlen=10 #:nodoc:
797
+ tabc = Proc.new {|str| Dir.glob(str +"*") }
798
+ config={}; config[:tab_completion] = tabc
799
+ config[:default] = "default"
800
+ $log.debug " inside getstr before call "
801
+ ret, str = rbgetstr(@form.window, @row+@height-1, @col+1, prompt, maxlen, config)
802
+ $log.debug " rbgetstr returned #{ret} , #{str} "
803
+ return "" if ret != 0
804
+ return str
805
+ end
806
+ # this is just a test of the simple "most" menu
807
+ def disp_menu #:nodoc:
808
+ $error_message_row ||= 23 # FIXME
809
+ $error_message_col ||= 1 # FIXME
810
+ menu = PromptMenu.new self
811
+ menu.add( menu.create_mitem( 's', "Goto start ", "Going to start", Proc.new { goto_start} ))
812
+ menu.add(menu.create_mitem( 'r', "scroll right", "I have scrolled ", :scroll_right ))
813
+ menu.add(menu.create_mitem( 'l', "scroll left", "I have scrolled ", :scroll_left ))
814
+ item = menu.create_mitem( 'm', "submenu", "submenu options" )
815
+ menu1 = PromptMenu.new( self, "Submenu Options")
816
+ menu1.add(menu1.create_mitem( 's', "CASE sensitive", "Ignoring Case in search" ))
817
+ menu1.add(menu1.create_mitem( 't', "goto last position", "moved to previous position", Proc.new { goto_last_position} ))
818
+ item.action = menu1
819
+ menu.add(item)
820
+ # how do i know what's available. the application or window should know where to place
821
+ #menu.display @form.window, 23, 1, $datacolor #, menu
822
+ menu.display @form.window, $error_message_row, $error_message_col, $datacolor #, menu
823
+ end
824
+ ##
825
+ # dynamically load a module and execute init method.
826
+ # Hopefully, we can get behavior like this such as vieditable or multibuffers
827
+ def load_module requirename, includename
828
+ require "rbcurse/#{requirename}"
829
+ extend Object.const_get("#{includename}")
830
+ send("#{requirename}_init") #if respond_to? "#{includename}_init"
831
+ end
832
+
833
+ # returns true if cursor is on header row
834
+ def header_row?
835
+ return false if @columns.nil?
836
+ 1 == @row + (@current_index-@toprow)
837
+ end
838
+ # on pressing ENTER we send user some info, the calling program
839
+ # would bind :PRESS
840
+ # Added a call to sort, should i still call PRESS
841
+ # or just do a sort in here and not call PRESS ???
842
+ #--
843
+ # FIXME we can create this once and reuse
844
+ #++
845
+ def fire_action_event
846
+ require 'rbcurse/ractionevent'
847
+ # the header event must only be used if columns passed
848
+ if header_row?
849
+ # TODO we need to fire correct even for header row, including
850
+ #alert "you are on header row: #{@columns[x]} curpos: #{@curpos}, x:#{x} "
851
+ #aev = TextActionEvent.new self, :PRESS, @columns[x], x, @curpos
852
+ x = _convert_curpos_to_column
853
+ @table_row_sorter.toggle_sort_order x
854
+ @table_row_sorter.sort
855
+ @repaint_required = true
856
+ aev = TextActionEvent.new self, :PRESS,:header, x, @curpos
857
+ else
858
+ # please check this again current_value due to _header_adjustment XXX test
859
+ aev = TextActionEvent.new self, :PRESS, current_value(), @current_index, @curpos
860
+ end
861
+ fire_handler :PRESS, aev
862
+ end
863
+ # Convert current cursor position to a table column
864
+ # calculate column based on curpos since user may not have
865
+ # user w and b keys (:next_column)
866
+ # @return [Fixnum] column index base 0
867
+ def _convert_curpos_to_column #:nodoc:
868
+ x = 0
869
+ @coffsets.each_pair { |e,i|
870
+ if @curpos < i
871
+ break
872
+ else
873
+ x += 1
874
+ end
875
+ }
876
+ x -= 1 # since we start offsets with 0, so first auto becoming 1
877
+ return x
878
+ end
879
+ def on_enter
880
+ # so cursor positioned on correct row
881
+ set_form_row
882
+ super
883
+ end
884
+ # called by listscrollable, used by scrollbar ENTER_ROW
885
+ def on_enter_row arow
886
+ fire_handler :ENTER_ROW, self
887
+ @repaint_required = true
888
+ end
889
+ # move cursor to next column
890
+ # FIXME need to account for hidden columns and numbering
891
+ def next_column
892
+ c = @column_pointer.next
893
+ cp = @coffsets[c]
894
+ #$log.debug " next_column #{c} , #{cp} "
895
+ @curpos = cp if cp
896
+ next_row() if c < @column_pointer.last_index
897
+ #addcol cp
898
+ set_form_col
899
+ end
900
+ def previous_column
901
+ c = @column_pointer.previous
902
+ cp = @coffsets[c]
903
+ #$log.debug " prev_column #{c} , #{cp} "
904
+ @curpos = cp if cp
905
+ previous_row() if c > @column_pointer.last_index
906
+ #addcol cp FIXME
907
+ set_form_col
908
+ end
909
+ private
910
+ def get_column index #:nodoc:
911
+ return @chash[index] if @chash.has_key? index
912
+ @chash[index] = ColumnInfo.new
913
+ end
914
+
915
+ # Some supporting classes
916
+
917
+ # This is our default table row sorter.
918
+ # It does a multiple sort and allows for reverse sort also.
919
+ # It's a pretty simple sorter and uses sort, not sort_by.
920
+ # Improvements welcome.
921
+ # Usage: provide model in constructor or using model method
922
+ # Call toggle_sort_order(column_index)
923
+ # Call sort.
924
+ # Currently, this sorts the provided model in-place. Future versions
925
+ # may maintain a copy, or use a table that provides a mapping of model to result.
926
+ # # TODO check if column_sortable
927
+ class TableRowSorter
928
+ attr_reader :sort_keys
929
+ def initialize model=nil
930
+ self.model = model
931
+ @columns_sort = []
932
+ @sort_keys = nil
933
+ end
934
+ def model=(model)
935
+ @model = model
936
+ @sort_keys = nil
937
+ end
938
+ def sortable colindex, tf
939
+ @columns_sort[colindex] = tf
940
+ end
941
+ def sortable? colindex
942
+ return false if @columns_sort[colindex]==false
943
+ return true
944
+ end
945
+ # should to_s be used for this column
946
+ def use_to_s colindex
947
+ return true # TODO
948
+ end
949
+ # sorts the model based on sort keys and reverse flags
950
+ # @sort_keys contains indices to sort on
951
+ # @reverse_flags is an array of booleans, true for reverse, nil or false for ascending
952
+ def sort
953
+ return unless @model
954
+ return if @sort_keys.empty?
955
+ @model.sort!{|x,y|
956
+ res = 0
957
+ @sort_keys.each { |ee|
958
+ e = ee.abs-1 # since we had offsetted by 1 earlier
959
+ abse = e.abs
960
+ if ee < 0
961
+ res = y[abse] <=> x[abse]
962
+ else
963
+ res = x[e] <=> y[e]
964
+ end
965
+ break if res != 0
966
+ }
967
+ res
968
+ }
969
+ end
970
+ # toggle the sort order if given column offset is primary sort key
971
+ # Otherwise, insert as primary sort key, ascending.
972
+ def toggle_sort_order index
973
+ index += 1 # increase by 1, since 0 won't multiple by -1
974
+ # internally, reverse sort is maintained by multiplying number by -1
975
+ @sort_keys ||= []
976
+ if @sort_keys.first && index == @sort_keys.first.abs
977
+ @sort_keys[0] *= -1
978
+ else
979
+ @sort_keys.delete index # in case its already there
980
+ @sort_keys.delete(index*-1) # in case its already there
981
+ @sort_keys.unshift index
982
+ # TODO delete beyond a max, default 3
983
+ end
984
+ end
985
+ def set_sort_keys list
986
+ @sort_keys = list
987
+ end
988
+ end #class
989
+ # what about is_resizable XXX
990
+ class ColumnInfo < Struct.new(:name, :width, :align, :hidden)
991
+ end
992
+
993
+ # a structure that maintains position and gives
994
+ # next and previous taking max index into account.
995
+ # it also circles. Can be used for traversing next component
996
+ # in a form, or container, or columns in a table.
997
+ class Circular < Struct.new(:max_index, :current_index)
998
+ attr_reader :last_index
999
+ attr_reader :current_index
1000
+ def initialize m, c=0
1001
+ raise "max index cannot be nil" unless m
1002
+ @max_index = m
1003
+ @current_index = c
1004
+ @last_index = c
1005
+ end
1006
+ def next
1007
+ @last_index = @current_index
1008
+ if @current_index + 1 > @max_index
1009
+ @current_index = 0
1010
+ else
1011
+ @current_index += 1
1012
+ end
1013
+ end
1014
+ def previous
1015
+ @last_index = @current_index
1016
+ if @current_index - 1 < 0
1017
+ @current_index = @max_index
1018
+ else
1019
+ @current_index -= 1
1020
+ end
1021
+ end
1022
+ def is_last?
1023
+ @current_index == @max_index
1024
+ end
1025
+ end
1026
+
1027
+ end # class tabluarw
1028
+
1029
+ end # modul
1030
+ if __FILE__ == $PROGRAM_NAME
1031
+
1032
+ require 'rbcurse/app'
1033
+ App.new do
1034
+ t = TabularWidget.new @form, :row => 2, :col => 2, :height => 20, :width => 30
1035
+ t.columns = ["Name ", "Age ", " Email "]
1036
+ t.add %w{ rahul 33 r@ruby.org }
1037
+ t << %w{ _why 133 j@gnu.org }
1038
+ t << ["jane", "1331", "jane@gnu.org" ]
1039
+ t.column_align 1, :right
1040
+ s = TabularWidget.new @form, :row => 2, :col =>32 do |b|
1041
+ b.columns = %w{ country continent text }
1042
+ b << ["india","asia","a warm country" ]
1043
+ b << ["japan","asia","a cool country" ]
1044
+ b << ["russia","europe","a hot country" ]
1045
+ #b.column_width 2, 30
1046
+ end
1047
+ s = TabularWidget.new @form , :row => 12, :col => 32 do |b|
1048
+ b.columns = %w{ place continent text }
1049
+ b << ["india","asia","a warm country" ]
1050
+ b << ["japan","asia","a cool country" ]
1051
+ b << ["russia","europe","a hot country" ]
1052
+ b << ["sydney","australia","a dry country" ]
1053
+ b << ["canberra","australia","a dry country" ]
1054
+ b << ["ross island","antarctica","a dry country" ]
1055
+ b << ["mount terror","antarctica","a windy country" ]
1056
+ b << ["mt erebus","antarctica","a cold place" ]
1057
+ b << ["siberia","russia","an icy city" ]
1058
+ b << ["new york","USA","a fun place" ]
1059
+ b.column_width 0, 12
1060
+ b.column_width 1, 12
1061
+ b.column_hidden 1, true
1062
+ b.numbering = true ## FIXME BROKEN
1063
+ end
1064
+ require 'rbcurse/extras/scrollbar'
1065
+ sb = Scrollbar.new @form, :parent => s
1066
+ #t.column_align 1, :right
1067
+ #puts t.to_s
1068
+ #puts
1069
+ end
1070
+ end