rbcurse-core 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
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