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