class Canis::Table
Attributes
attr_reader :columns
Public Class Methods
# File lib/canis/core/widgets/table.rb, line 413 def initialize form = nil, config={}, &block # array of column info objects @chash = [] # chash should be an array which is basically the order of rows to be printed # it contains index, which is the offset of the row in the data @list # When printing we should loop through chash and get the index in data # # should be zero here, but then we won't get textpad correct @_header_adjustment = 0 @col_min_width = 3 self.extend DefaultListSelection super create_default_renderer unless @renderer # 2014-04-10 - 11:01 # NOTE listselection takes + and - for ask_select bind_key(?w, "next column") { self.next_column } bind_key(?b, "prev column") { self.prev_column } bind_key(?\M-\-, "contract column") { self.contract_column } bind_key(?\M-+, "expand column") { self.expand_column } bind_key(?=, "expand column to width") { self.expand_column_to_width } bind_key(?\M-=, "expand column to width") { self.expand_column_to_max_width } bind_key(?\C-s, "Save as") { self.save_as(nil) } #@list_selection_model ||= DefaultListSelectionModel.new self set_default_selection_model unless @list_selection_model end
Public Instance Methods
Find the next row that contains given string Overrides textpad since each line is an array NOTE does not go to next match within row NOTE: FIXME ensure_visible puts prow = current_index so in this case, the header overwrites the matched row. @return row and col offset of match, or nil @param String to find
@ deprecate since it does not get second match in line. textpad does
however, the offset textpad shows is wrong
# File lib/canis/core/widgets/table.rb, line 985 def OLDnext_match str _calculate_column_offsets unless @coffsets first = nil ## content can be string or Chunkline, so we had to write <tt>index</tt> for this. @list.each_with_index do |fields, ix| #col = line.index str #fields.each_with_index do |f, jx| #@chash.each_with_index do |c, jx| #next if c.hidden each_column do |c,jx| f = fields[c.index] # value can be numeric col = f.to_s.index str if col col += @coffsets[jx] first ||= [ ix, col ] if ix > @current_index return [ix, col] end end end end return first end
This calculates and stores the offset at which each column starts. Used when going to next column or doing a find for a string in the table. TODO store this inside the hash so it's not calculated again in renderer
# File lib/canis/core/widgets/table.rb, line 483 def _calculate_column_offsets @coffsets = [] total = 0 #@chash.each_pair { |i, c| #@chash.each_with_index { |c, i| #next if c.hidden each_column {|c,i| w = c.width @coffsets[i] = total c.offset = total # if you use prepare_format then use w+2 due to separator symbol total += w + 1 } end
size each column based on widths of this row of data. Only changed width if no width for that column
# File lib/canis/core/widgets/table.rb, line 649 def _init_model array # clear the column data -- this line should be called otherwise previous tables stuff will remain. @chash.clear array.each_with_index { |e,i| # if columns added later we could be overwriting the width c = get_column(i) c.width ||= 10 } # maintains index in current pointer and gives next or prev @column_pointer = Circular.new array.size()-1 end
add a row to the table
The name add will be removed soon, pls use << instead.
# File lib/canis/core/widgets/table.rb, line 798 def add array unless @list # columns were not added, this most likely is the title @list ||= [] _init_model array end @list << array fire_dimension_changed self end
TODO
# File lib/canis/core/widgets/table.rb, line 887 def add_column tc raise "to figure out add_column" _invalidate_width_cache end
# File lib/canis/core/widgets/table.rb, line 896 def calculate_column_width col, maxrows=99 ret = 3 ctr = 0 @list.each_with_index { |r, i| #next if i < @toprow # this is also a possibility, it checks visible rows break if ctr > maxrows ctr += 1 #next if r == :separator c = r[col] x = c.to_s.length ret = x if x > ret } ret end
clear the list completely
# File lib/canis/core/widgets/table.rb, line 825 def clear @selected_indices.clear super end
# File lib/canis/core/widgets/table.rb, line 1032 def clear_matches # clear previous match so all data can show again if @indices && @indices.count > 0 fire_dimension_changed init_vars end @indices = nil end
convenience method to set alignment of a column @param index of column @param align - :right (any other value is taken to be left)
# File lib/canis/core/widgets/table.rb, line 862 def column_align colindex, align get_column(colindex).align = align end
returns collection of ColumnInfo
objects
# File lib/canis/core/widgets/table.rb, line 460 def column_model @chash end
# File lib/canis/core/widgets/table.rb, line 855 def column_width colindex, width get_column(colindex).width = width _invalidate_width_cache end
getter and setter for columns 2014-04-10 - 13:49 @param [Array] columns to set as Array of Strings @return if no args, returns array of column names as Strings
NOTE Appends columns to array, so it must be set before data, and thus it should clear the list
# File lib/canis/core/widgets/table.rb, line 612 def columns(*val) if val.empty? # returns array of column names as Strings @list[0] else array = val[0] @_header_adjustment = 1 @list ||= [] @list.clear @list << array _init_model array # update the names in column model array.each_with_index { |n,i| c = get_column(i) #c.name = name ## 2018-05-19 - seems to be a bug c.name = n } self end end
Set column titles with given array of strings. NOTE: This is only required to be called if first row of file or content does not contain titles. In that case, this should be called before setting the data as the array passed is appended into the content array. @deprecated complicated, just use `columns()`
# File lib/canis/core/widgets/table.rb, line 640 def columns=(array) columns(array) self end
calculate pad width based on widths of columns
# File lib/canis/core/widgets/table.rb, line 465 def content_cols total = 0 #@chash.each_pair { |i, c| #@chash.each_with_index { |c, i| #next if c.hidden each_column {|c,i| w = c.width # if you use prepare_format then use w+2 due to separator symbol total += w + 1 } return total end
# File lib/canis/core/widgets/table.rb, line 581 def contract_column x = _convert_curpos_to_column w = get_column(x).width return if w <= @col_min_width column_width x, w-1 if w @coffsets = nil fire_dimension_changed end
set a default renderer
# File lib/canis/core/widgets/table.rb, line 949 def create_default_renderer r = DefaultTableRenderer.new self renderer(r) end
# File lib/canis/core/widgets/table.rb, line 940 def create_default_sorter raise "Data not sent in." unless @list @table_row_sorter = DefaultTableRowSorter.new @list end
delete a data row at index
NOTE : This does not adjust for header_adjustment. So zero will refer to the header if there is one.
This is to keep consistent with textpad which does not know of header_adjustment and uses the actual index. Usually, programmers will be dealing with +@current_index+
# File lib/canis/core/widgets/table.rb, line 816 def delete_at ix return unless @list raise ArgumentError, "Argument must be within 0 and #{@list.length}" if ix < 0 or ix >= @list.length fire_dimension_changed #@list.delete_at(ix + @_header_adjustment) @list.delete_at(ix) end
yields non-hidden columns (ColumnInfo
) and the offset/index This is the order in which columns are to be printed
# File lib/canis/core/widgets/table.rb, line 1054 def each_column @chash.each_with_index { |c, i| next if c.hidden yield c,i if block_given? } end
Ensure current row is visible, if not make it first row
This overrides textpad due to header_adjustment, otherwise during next_match, the header overrides the found row.
@param current_index (default if not given)
# File lib/canis/core/widgets/table.rb, line 1046 def ensure_visible row = @current_index unless is_visible? row @prow = @current_index - @_header_adjustment end end
estimate columns widths based on data in first 10 or so rows This will override any previous widths, so put custom widths after calling this.
# File lib/canis/core/widgets/table.rb, line 675 def estimate_column_widths each_column {|c,i| c.width = suggest_column_width(i) } self end
# File lib/canis/core/widgets/table.rb, line 556 def expand_column x = _convert_curpos_to_column w = get_column(x).width column_width x, w+1 if w @coffsets = nil fire_dimension_changed end
find the width of the longest item in the current columns and expand the width to that.
# File lib/canis/core/widgets/table.rb, line 576 def expand_column_to_max_width x = _convert_curpos_to_column w = calculate_column_width x expand_column_to_width w end
# File lib/canis/core/widgets/table.rb, line 563 def expand_column_to_width w=nil x = _convert_curpos_to_column unless w # expand to width of current cell s = @list[@current_index][x] w = s.to_s.length + 1 end column_width x, w @coffsets = nil fire_dimension_changed end
Takes the name of a file containing delimited data
and load it into the table.
This method will load and split the file into the table. @param name is the file name @param config is a hash containing:
- :separator - field separator, default is TAB - :columns - array of column names or true - first row is column names or false - no columns.
NOTE¶ ↑
if columns is not mentioned, then it defaults to false
Example¶ ↑
table = Table.new ... table.filename 'contacts.tsv', :separator => '|', :columns => true
# File lib/canis/core/widgets/table.rb, line 742 def filename name, _config = {} arr = File.open(name,"r").read.split("\n") lines = [] sep = _config[:separator] || _config[:delimiter] || '\t' arr.each { |l| lines << l.split(sep) } cc = _config[:columns] if cc.is_a? Array columns(cc) text(lines) elsif cc # cc is true, use first row as column names columns(lines[0]) text(lines[1..-1]) else # cc is false - no columns # XXX since columns() is not called, so chash is not cleared. _init_model lines[0] text(lines) end end
called when ENTER is pressed. Takes into account if user is on header_row
# File lib/canis/core/widgets/table.rb, line 961 def fire_action_event if header_row? if @table_row_sorter x = _convert_curpos_to_column c = @chash[x] # convert to index in data model since sorter only has data_model index = c.index @table_row_sorter.toggle_sort_order index @table_row_sorter.sort fire_dimension_changed end end super end
a column traversal has happened. FIXME needs to be looked into. is this consistent naming wise and are we using the correct object In old system it was TABLE_TRAVERSAL_EVENT
# File lib/canis/core/widgets/table.rb, line 551 def fire_column_event eve require 'canis/core/include/ractionevent' aev = TextActionEvent.new self, eve, get_column(@column_pointer.current_index), @column_pointer.current_index, @column_pointer.last_index fire_handler eve, aev end
retrieve the column info structure for the given offset. The offset pertains to the visible offset not actual offset in data model. These two differ when we move a column. @return ColumnInfo
object containing width align color bgcolor attrib hidden
# File lib/canis/core/widgets/table.rb, line 450 def get_column index return @chash[index] if @chash[index] # create a new entry since none present c = ColumnInfo.new c.index = index @chash[index] = c return c end
get the value at the cell at row and col @return String
# File lib/canis/core/widgets/table.rb, line 832 def get_value_at row,col actrow = row + @_header_adjustment @list[actrow, col] end
# File lib/canis/core/widgets/table.rb, line 599 def header_adjustment @_header_adjustment end
returns true if focus is on header_row
# File lib/canis/core/widgets/table.rb, line 954 def header_row? #@prow == 0 @prow == @current_index end
yields each column to caller method if yield returns true, collects index of row into array and returns the array @returns array of indices which can be empty Value yielded can be fixnum or date etc
# File lib/canis/core/widgets/table.rb, line 1013 def matching_indices raise "block required for matching_indices" unless block_given? @indices = [] ## content can be string or Chunkline, so we had to write <tt>index</tt> for this. @list.each_with_index do |fields, ix| flag = yield ix, fields if flag @indices << ix end end #$log.debug "XXX: INDICES found #{@indices}" if @indices.count > 0 fire_dimension_changed init_vars else @indices = nil end #return @indices end
size each column based on widths of this row of data.
# File lib/canis/core/widgets/table.rb, line 661 def model_row index array = @list[index] array.each_with_index { |c,i| # if columns added later we could be overwriting the width ch = get_column(i) ch.width = c.to_s.length + 2 } # maintains index in current pointer and gives next or prev @column_pointer = Circular.new array.size()-1 self end
should all this move into table column model or somepn move a column from offset ix to offset newix
# File lib/canis/core/widgets/table.rb, line 879 def move_column ix, newix acol = @chash.delete_at ix @chash.insert newix, acol _invalidate_width_cache #tmce = TableColumnModelEvent.new(ix, newix, self, :MOVE) #fire_handler :TABLE_COLUMN_MODEL_EVENT, tmce end
jump cursor to next column TODO : if cursor goes out of view, then pad should scroll right or left and down
# File lib/canis/core/widgets/table.rb, line 526 def next_column # TODO take care of multipliers _calculate_column_offsets unless @coffsets c = @column_pointer.next cp = @coffsets[c] #$log.debug " next_column #{c} , #{cp} " @curpos = cp if cp down() if c < @column_pointer.last_index fire_column_event :ENTER_COLUMN end
refresh pad onto window overrides super due to header_adjustment
and the header too
# File lib/canis/core/widgets/table.rb, line 913 def padrefresh top = @window.top left = @window.left sr = @startrow + top sc = @startcol + left # first do header always in first row # -- prefresh arguments are: # 1. pad # 2. pminrow # 3. pmincol (pad upper left) # 4, sminrow (screen upper left row) # 5, smincol # 6, smaxrow # 7, smaxcol # 2019-03-12 - fixed bug in table, only printed header if table on row 1 retval = FFI::NCurses.prefresh(@pad, @prow, @pcol, sr, sc, sr + 1, @cols + sc) $log.warn "XXX: PADREFRESH HEADER #{retval}, #{@prow}, #{@pcol}, #{sr}, #{sc}, #{1+sr}, #{@cols+sc}." if retval == -1 # retval = FFI::NCurses.prefresh(@pad,0,@pcol, sr , sc , 2 , @cols+ sc ); # now print rest of data # h is header_adjustment h = 1 retval = FFI::NCurses.prefresh(@pad,@prow + h,@pcol, sr + h , sc , @rows + sr , @cols+ sc ); $log.warn "XXX: PADREFRESH #{retval}, #{@prow}, #{@pcol}, #{sr}, #{sc}, #{@rows+sr}, #{@cols+sc}." if retval == -1 # padrefresh can fail if width is greater than NCurses.COLS end
jump cursor to previous column TODO : if cursor goes out of view, then pad should scroll right or left and down
# File lib/canis/core/widgets/table.rb, line 538 def prev_column # TODO take care of multipliers _calculate_column_offsets unless @coffsets c = @column_pointer.previous cp = @coffsets[c] #$log.debug " prev #{c} , #{cp} " @curpos = cp if cp up() if c > @column_pointer.last_index fire_column_event :ENTER_COLUMN end
print footer containing line and total, overriding textpad which prints column offset also This is called internally by +repaint()+ but can be overridden for more complex printing.
# File lib/canis/core/widgets/table.rb, line 1075 def print_foot return unless @print_footer ha = @_header_adjustment # ha takes into account whether there are headers or not footer = "#{@current_index+1-ha} of #{@list.length-ha} " @graphic.printstring( @row + @height -1 , @col+2, footer, @color_pair || $datacolor, @footer_attrib) @repaint_footer_required = false end
TODO
# File lib/canis/core/widgets/table.rb, line 892 def remove_column tc raise "to figure out add_column" _invalidate_width_cache end
calls the renderer for all rows of data giving them pad, lineno, and line data
# File lib/canis/core/widgets/table.rb, line 1061 def render_all if @indices && @indices.count > 0 @indices.each_with_index do |ix, jx| render @pad, jx, @list[ix] end else @list.each_with_index { |line, ix| # FFI::NCurses.mvwaddstr(@pad,ix, 0, @list[ix].to_s) render @pad, ix, line } end end
def method_missing(name, *args) @tp.send(name, *args) end
supply a custom renderer that implements +render()+ @see render
# File lib/canis/core/widgets/table.rb, line 596 def renderer r @renderer = r end
set column array and data array in one shot Erases any existing content
# File lib/canis/core/widgets/table.rb, line 719 def resultset columns, data @list = [] columns(columns) text(data) end
save the table as a file @param String
name of output file. If nil, user is prompted Currently, tabs are used as delimiter, but this could be based on input separator, or prompted.
# File lib/canis/core/widgets/table.rb, line 768 def save_as outfile _t = "(all rows)" if @selected_indices.size > 0 _t = "(selected rows)" end unless outfile outfile = get_string "Enter file name to save #{_t} as " return unless outfile end # if there is a selection, then write only selected rows l = nil if @selected_indices.size > 0 l = [] @list.each_with_index { |v,i| l << v if @selected_indices.include? i } else l = @list end File.open(outfile, 'w') {|f| l.each {|r| line = r.join "\t" f.puts line } } end
set the default selection model as the operational one
# File lib/canis/core/widgets/table.rb, line 441 def set_default_selection_model @list_selection_model = nil @list_selection_model = Canis::DefaultListSelectionModel.new self end
set value at the cell at row and col @param int row @param int col @param String
value @return self
# File lib/canis/core/widgets/table.rb, line 842 def set_value_at row,col,val actrow = row + @_header_adjustment @list[actrow , col] = val fire_row_changed actrow self end
calculates and returns a suggested columns width for given column based on data (first 10 rows) called by estimate_column_widths
in a loop
# File lib/canis/core/widgets/table.rb, line 684 def suggest_column_width col #ret = @cw[col] || 2 ret = get_column(col).width || 2 ctr = 0 @list.each_with_index { |r, i| #next if i < @toprow # this is also a possibility, it checks visible rows break if ctr > 10 ctr += 1 next if r == :separator c = r[col] x = c.to_s.length ret = x if x > ret } ret end
I am assuming the column has been set using columns=
Now only data is being sent in NOTE : calling set_content sends to TP's +text()+ which resets @list @param lines is an array or arrays
# File lib/canis/core/widgets/table.rb, line 706 def text lines, fmt=:none # maybe we can check this out # should we not erase data, will user keep one column and resetting data ? # set_content assumes data is gone. @list ||= [] # this would work if no columns @list.concat( lines) fire_dimension_changed self end
convert the row into something searchable so that offsets returned by index
are exactly what is seen on the screen.
# File lib/canis/core/widgets/table.rb, line 517 def to_searchable index if @renderer @renderer.to_searchable(@list[index]) else @list[index].to_s end end