rbcurse-core 0.0.0

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