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