class Canis::Table

Attributes

table_row_sorter[RW]

attr_reader :columns

Public Class Methods

new(form = nil, config={}) click to toggle source
Calls superclass method
# 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

<<(array)
Alias for: add
OLDnext_match(str) click to toggle source
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
_calculate_column_offsets() click to toggle source

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
_init_model(array) click to toggle source

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(array) click to toggle source
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
Also aliased as: <<
add_column(tc) click to toggle source

TODO

# File lib/canis/core/widgets/table.rb, line 887
def add_column tc
  raise "to figure out add_column"
  _invalidate_width_cache
end
calculate_column_width(col, maxrows=99) click to toggle source
# 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() click to toggle source

clear the list completely

Calls superclass method
# File lib/canis/core/widgets/table.rb, line 825
def clear
  @selected_indices.clear
  super
end
clear_matches() click to toggle source
# 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
column_align(colindex, align) click to toggle source

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
column_hidden(colindex, hidden) click to toggle source

convenience method to hide or unhide a column Provided since column offsets need to be recalculated in the case of a width change or visibility change

# File lib/canis/core/widgets/table.rb, line 868
def column_hidden colindex, hidden
  get_column(colindex).hidden = hidden
  _invalidate_width_cache
end
column_model() click to toggle source

returns collection of ColumnInfo objects

# File lib/canis/core/widgets/table.rb, line 460
def column_model
  @chash
end
column_width(colindex, width) click to toggle source
# File lib/canis/core/widgets/table.rb, line 855
def column_width colindex, width
  get_column(colindex).width = width
  _invalidate_width_cache
end
columns(*val) click to toggle source

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
columns=(array) click to toggle source

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
Also aliased as: headings=
content_cols() click to toggle source

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
contract_column() click to toggle source
# 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
create_default_renderer() click to toggle source

set a default renderer

# File lib/canis/core/widgets/table.rb, line 949
def create_default_renderer
  r = DefaultTableRenderer.new self
  renderer(r)
end
create_default_sorter() click to toggle source
# 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_at(ix) click to toggle source

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
each_column() { |c,i| ... } click to toggle source

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_visible(row = @current_index) click to toggle source

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_column_widths() click to toggle source

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
expand_column() click to toggle source
# 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
expand_column_to_max_width() click to toggle source

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
expand_column_to_width(w=nil) click to toggle source
# 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
filename(name, _config = {}) click to toggle source

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
Also aliased as: load
fire_action_event() click to toggle source

called when ENTER is pressed. Takes into account if user is on header_row

Calls superclass method
# 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
fire_column_event(eve) click to toggle source

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
get_column(index) click to toggle source

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_value_at(row,col) click to toggle source

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
header_adjustment() click to toggle source
# File lib/canis/core/widgets/table.rb, line 599
def header_adjustment
  @_header_adjustment
end
header_row?() click to toggle source

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
headings=(array)
Alias for: columns=
load(name, _config = {})
Alias for: filename
matching_indices() { |ix, fields| ... } click to toggle source

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
model_row(index) click to toggle source

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
move_column(ix, newix) click to toggle source

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
next_column() click to toggle source

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
padrefresh() click to toggle source

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
prev_column() click to toggle source

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_foot() click to toggle source

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.

remove_column(tc) click to toggle source

TODO

# File lib/canis/core/widgets/table.rb, line 892
def remove_column tc
  raise "to figure out add_column"
  _invalidate_width_cache
end
render_all() click to toggle source

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
renderer(r) click to toggle source

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
resultset(columns, data) click to toggle source

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_as(outfile) click to toggle source

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_default_selection_model() click to toggle source

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(row,col,val) click to toggle source

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
suggest_column_width(col) click to toggle source

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
text(lines, fmt=:none) click to toggle source

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
to_searchable(index) click to toggle source

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