class Glimmer::LibUI::ControlProxy::TableProxy

Proxy for LibUI table objects

Follows the Proxy Design Pattern

Constants

CUSTOM_LISTENER_NAMES
CellValue
ColumnType
Model
NumColumns
NumRows
RowBackgroundColorModelColumn
SetCellValue

Attributes

column_attributes[R]
columns[R]
model[R]
model_handler[R]
table_params[R]

Public Class Methods

new(keyword, parent, args, &block) click to toggle source
# File lib/glimmer/libui/control_proxy/table_proxy.rb, line 43
def initialize(keyword, parent, args, &block)
  @keyword = keyword
  @parent_proxy = parent
  @args = args
  @block = block
  @enabled = true
  @columns = []
  @cell_rows = []
  @last_cell_rows = []
  register_cell_rows_observer
  window_proxy.on_destroy do
    # the following unless condition is an exceptional condition stumbled upon that fails freeing the table model
    ::LibUI.free_table_model(@model) unless @destroyed && parent_proxy.is_a?(Box)
  end
end

Public Instance Methods

array_deep_clone(array_or_object) click to toggle source
# File lib/glimmer/libui/control_proxy/table_proxy.rb, line 151
def array_deep_clone(array_or_object)
  if array_or_object.is_a?(Array)
    array_or_object.map do |element|
      array_deep_clone(element)
    end
  else
    array_or_object.clone
  end
end
cell_rows(rows = nil) click to toggle source
# File lib/glimmer/libui/control_proxy/table_proxy.rb, line 85
def cell_rows(rows = nil)
  if rows.nil?
    @cell_rows
  else
    if rows != @cell_rows
      @cell_rows = rows
      @cell_rows = @cell_rows.to_a if @cell_rows.is_a?(Enumerator)
    end
    @cell_rows
  end
end
Also aliased as: cell_rows=, set_cell_rows
cell_rows=(rows = nil)
Alias for: cell_rows
data_bind_read(property, model_binding) click to toggle source
# File lib/glimmer/libui/control_proxy/table_proxy.rb, line 121
def data_bind_read(property, model_binding)
  if model_binding.binding_options[:column_attributes].is_a?(Array)
    @column_attributes = model_binding.binding_options[:column_attributes]
  else
    column_attribute_mapping = model_binding.binding_options[:column_attributes].is_a?(Hash) ? model_binding.binding_options[:column_attributes] : {}
    @column_attributes = columns.select {|column| column.is_a?(Column)}.map(&:name).map {|column_name| column_attribute_mapping[column_name] || column_name.underscore}
  end
  model_attribute_observer = model_attribute_observer_registration = nil
  model_attribute_observer = Glimmer::DataBinding::Observer.proc do
    new_value = model_binding.evaluate_property
    new_value = new_value.to_a if new_value.is_a?(Enumerator)
    if model_binding.binding_options[:column_attributes] || (!new_value.empty? && !new_value.first.is_a?(Array))
      @model_attribute_array_observer_registration&.deregister
      @model_attribute_array_observer_registration = model_attribute_observer.observe(new_value, @column_attributes, ignore_frozen: true, attribute_writer_type: [:attribute=, :set_attribute])
      model_attribute_observer.add_dependent(model_attribute_observer_registration => @model_attribute_array_observer_registration)
    end
    # TODO look if multiple notifications are happening as a result of observing array and observing model binding
    send("#{property}=", new_value) unless @last_cell_rows == new_value
  end
  model_attribute_observer_registration = model_attribute_observer.observe(model_binding, attribute_writer_type: [:attribute=, :set_attribute])
  model_attribute_observer.call # initial update
  data_binding_model_attribute_observer_registrations << model_attribute_observer_registration
  model_attribute_observer
end
data_bind_write(property, model_binding) click to toggle source
# File lib/glimmer/libui/control_proxy/table_proxy.rb, line 146
def data_bind_write(property, model_binding)
  # TODO ensure writing is happening to models if rows are not arrays
  handle_listener('on_edited') { model_binding.call(cell_rows) } if property == 'cell_rows'
end
destroy() click to toggle source
Calls superclass method Glimmer::LibUI::ControlProxy#destroy
# File lib/glimmer/libui/control_proxy/table_proxy.rb, line 78
def destroy
  super
  # TODO consider replacing unobserve with observer_registration.deregister
  @cell_rows_observer&.unobserve(self, :cell_rows, recursive: true, ignore_frozen: true, attribute_writer_type: [:attribute=, :set_attribute])
  @destroyed = true
end
editable(value = nil) click to toggle source
# File lib/glimmer/libui/control_proxy/table_proxy.rb, line 110
def editable(value = nil)
  if value.nil?
    @editable
  else
    @editable = !!value
  end
end
Also aliased as: editable=, set_editable, editable?
editable=(value = nil)
Alias for: editable
editable?(value = nil)
Alias for: editable
expand(cell_rows) click to toggle source
# File lib/glimmer/libui/control_proxy/table_proxy.rb, line 103
def expand(cell_rows)
  cell_rows.to_a.map do |row|
    row = @column_attributes.map {|attribute| row.send(attribute) } if @column_attributes&.any? && !row.is_a?(Array)
    row.flatten(1)
  end
end
expanded_cell_rows() click to toggle source
# File lib/glimmer/libui/control_proxy/table_proxy.rb, line 99
def expanded_cell_rows
  expand(cell_rows)
end
post_add_content() click to toggle source
# File lib/glimmer/libui/control_proxy/table_proxy.rb, line 59
def post_add_content
  build_control unless @content_added
  super
end
post_initialize_child(child) click to toggle source
# File lib/glimmer/libui/control_proxy/table_proxy.rb, line 64
def post_initialize_child(child)
  @columns << child
  # add extra complementary columns (:text, :color) if it is a dual/triple column (i.e. ImageTextColumnProxy or CheckboxTextColumnProxy
  case child
  when Column::ImageTextColumnProxy, Column::CheckboxTextColumnProxy
    @columns << :text
  when Column::TextColorColumnProxy
    @columns << :color
  when Column::CheckboxTextColorColumnProxy, Column::ImageTextColorColumnProxy
    @columns << :text
    @columns << :color
  end
end
set_cell_rows(rows = nil)
Alias for: cell_rows
set_editable(value = nil)
Alias for: editable

Private Instance Methods

apply_windows_fix() click to toggle source
# File lib/glimmer/libui/control_proxy/table_proxy.rb, line 333
def apply_windows_fix
  Glimmer::LibUI.queue_main do
    new_row = @columns&.select {|column| column.is_a?(Column)}&.map {|column| column.class.default_value}
    if new_row
      @cell_rows << new_row
      @cell_rows.pop
    end
  end
end
build_control() click to toggle source
# File lib/glimmer/libui/control_proxy/table_proxy.rb, line 163
def build_control
  @model_handler = ::LibUI::FFI::TableModelHandler.malloc
  @model_handler.NumColumns   = fiddle_closure_block_caller(4) { @columns.map {|c| c.is_a?(DualColumn) ? 2 : (c.is_a?(TripleColumn) ? 3 : 1)}.sum }
  @model_handler.ColumnType   = fiddle_closure_block_caller(4, [1, 1, 4]) do |_, _, column|
    # TODO consider refactoring to use Glimmer::LibUI.enum_symbols(:table_value_type)
    case @columns[column]
    when Column::TextColumnProxy, Column::ButtonColumnProxy, Column::TextColorColumnProxy, :text
      0
    when Column::ImageColumnProxy, Column::ImageTextColumnProxy, Column::ImageTextColorColumnProxy
      1
    when Column::CheckboxColumnProxy, Column::CheckboxTextColumnProxy, Column::CheckboxTextColorColumnProxy, Column::ProgressBarColumnProxy
      2
    when Column::BackgroundColorColumnProxy, :color
      3
    end
  end
  @model_handler.NumRows      = fiddle_closure_block_caller(4) do
    # Note: there is a double-delete bug in Windows when performing table_model_row_deleted, which requires pre-adding and extra empty row
    cell_rows.count + (OS.windows? ? 1 : 0)
  end
  @model_handler.CellValue    = fiddle_closure_block_caller(1, [1, 1, 4, 4]) do |_, _, row, column|
    the_cell_rows = expanded_cell_rows
    case @columns[column]
    when Column::TextColumnProxy, Column::ButtonColumnProxy, Column::TextColorColumnProxy, :text
      ::LibUI.new_table_value_string((expanded_cell_rows[row] && expanded_cell_rows[row][column]).to_s)
    when Column::ImageColumnProxy, Column::ImageTextColumnProxy, Column::ImageTextColorColumnProxy
      if OS.windows? && row == cell_rows.count
        img = Glimmer::LibUI::ICON
      else
        img = expanded_cell_rows[row][column]
      end
      img = ControlProxy::ImageProxy.create('image', nil, img) if img.is_a?(Array)
      img = ControlProxy::ImageProxy.create('image', nil, [img]) if img.is_a?(String)
      img = img.respond_to?(:libui) ? img.libui : img
      ::LibUI.new_table_value_image(img)
    when Column::CheckboxColumnProxy, Column::CheckboxTextColumnProxy, Column::CheckboxTextColorColumnProxy
      ::LibUI.new_table_value_int(((expanded_cell_rows[row] && (expanded_cell_rows[row][column] == 1 || expanded_cell_rows[row][column].to_s.strip.downcase == 'true' ? 1 : 0))) || 0)
    when Column::ProgressBarColumnProxy
      value = (expanded_cell_rows[row] && expanded_cell_rows[row][column]).to_i
      expanded_last_last_cell_rows = expand(@last_last_cell_rows)
      old_value = (expanded_last_last_cell_rows[row] && expanded_last_last_cell_rows[row][column]).to_i
      if OS.windows? && old_value == -1 && value >= 0
        Glimmer::Config.logger.error('Switching a progress bar value from -1 to a positive value is not supported on Windows')
        cell_rows[row][column] = -1
        ::LibUI.new_table_value_int(old_value)
      else
        ::LibUI.new_table_value_int((expanded_cell_rows[row] && expanded_cell_rows[row][column]).to_i)
      end
    when Column::BackgroundColorColumnProxy
      background_color = Glimmer::LibUI.interpret_color(expanded_cell_rows[row] && expanded_cell_rows[row][column]) || {r: 255, g: 255, b: 255}
      ::LibUI.new_table_value_color(background_color[:r] / 255.0, background_color[:g] / 255.0, background_color[:b] / 255.0, background_color[:a] || 1.0)
    when :color
      color = Glimmer::LibUI.interpret_color(expanded_cell_rows[row] && expanded_cell_rows[row][column]) || {r: 0, g: 0, b: 0}
      ::LibUI.new_table_value_color(color[:r] / 255.0, color[:g] / 255.0, color[:b] / 255.0, color[:a] || 1.0)
    end
  end
  @model_handler.SetCellValue = fiddle_closure_block_caller(0, [1, 1, 4, 4, 1]) do |_, _, row, column, val|
    case @columns[column]
    when Column::TextColumnProxy
      column = @columns[column].index
      @cell_rows[row] ||= []
      if @cell_rows[row].is_a?(Array)
        @cell_rows[row][column] = ::LibUI.table_value_string(val).to_s
      else
        attribute = @column_attributes[column]
        @cell_rows[row].send("#{attribute}=", ::LibUI.table_value_string(val).to_s)
      end
    when Column::TextColorColumnProxy
      column = @columns[column].index
      @cell_rows[row] ||= []
      if @cell_rows[row].is_a?(Array)
        @cell_rows[row][column] ||= []
        @cell_rows[row][column][0] = ::LibUI.table_value_string(val).to_s
      else
        attribute = @column_attributes[column]
        @cell_rows[row].send("#{attribute}=", []) unless @cell_rows[row].send(attribute)
        @cell_rows[row].send(attribute)[0] = ::LibUI.table_value_string(val).to_s
      end
    when :text
      column = @columns[column - 1].index
      @cell_rows[row] ||= []
      if @cell_rows[row].is_a?(Array)
        @cell_rows[row][column] ||= []
        @cell_rows[row][column][1] = ::LibUI.table_value_string(val).to_s
      else
        attribute = @column_attributes[column]
        @cell_rows[row].send("#{attribute}=", []) unless @cell_rows[row].send(attribute)
        @cell_rows[row].send(attribute)[1] = ::LibUI.table_value_string(val).to_s
      end
    when Column::ButtonColumnProxy
      @columns[column].notify_custom_listeners('on_clicked', row)
    when Column::CheckboxColumnProxy
      column = @columns[column].index
      @cell_rows[row] ||= []
      if @cell_rows[row].is_a?(Array)
        @cell_rows[row][column] = ::LibUI.table_value_int(val).to_i == 1
      else
        attribute = @column_attributes[column]
        @cell_rows[row].send("#{attribute}=", ::LibUI.table_value_int(val).to_i == 1)
      end
    when Column::CheckboxTextColumnProxy
      column = @columns[column].index
      @cell_rows[row] ||= []
      if @cell_rows[row].is_a?(Array)
        @cell_rows[row][column] ||= []
        @cell_rows[row][column][0] = ::LibUI.table_value_int(val).to_i == 1
      else
        attribute = @column_attributes[column]
        @cell_rows[row].send("#{attribute}=", []) unless @cell_rows[row].send(attribute)
        @cell_rows[row].send(attribute)[0] = ::LibUI.table_value_int(val).to_i == 1
      end
    end
    notify_custom_listeners('on_edited', row, @cell_rows[row])
  end
  
  @model = ::LibUI.new_table_model(@model_handler)
  
  # figure out and reserve column indices for columns
  @columns.each { |column| column.respond_to?(:column_index) && column.column_index }
  
  @table_params = ::LibUI::FFI::TableParams.malloc
  @table_params.Model = @model
  @table_params.RowBackgroundColorModelColumn = @columns.find {|column| column.is_a?(Column::BackgroundColorColumnProxy)}&.column_index || -1
  
  @libui = ControlProxy.new_control(@keyword, [@table_params])
  @libui.tap do
    @columns.each {|column| column.respond_to?(:build_control, true) && column.send(:build_control) }
  end

  if !@applied_windows_fix && OS.windows?
    @applied_windows_fix = true
    apply_windows_fix
  end
end
next_column_index() click to toggle source
# File lib/glimmer/libui/control_proxy/table_proxy.rb, line 298
def next_column_index
  @next_column_index ||= -1
  @next_column_index += 1
end
register_cell_rows_observer() click to toggle source
# File lib/glimmer/libui/control_proxy/table_proxy.rb, line 303
def register_cell_rows_observer
  @cell_rows_observer = Glimmer::DataBinding::Observer.proc do |new_cell_rows|
    if @cell_rows.size < @last_cell_rows.size && @last_cell_rows.include_all?(*@cell_rows)
      @last_cell_rows.array_diff_indexes(@cell_rows).reverse.each do |row|
        ::LibUI.table_model_row_deleted(model, row) if model && row
        notify_custom_listeners('on_changed', row, :deleted, @last_cell_rows[row])
      end
    elsif @cell_rows.size > @last_cell_rows.size && @cell_rows.include_all?(*@last_cell_rows)
      @cell_rows.array_diff_indexes(@last_cell_rows).each do |row|
        ::LibUI.table_model_row_inserted(model, row) if model && row
        notify_custom_listeners('on_changed', row, :inserted, @cell_rows[row])
      end
    else
      @cell_rows.each_with_index do |new_row_data, row|
        if new_row_data != @last_cell_rows[row]
          ::LibUI.table_model_row_changed(model, row) if model && row
          notify_custom_listeners('on_changed', row, :changed, @cell_rows[row])
        end
      end
    end
    @last_last_cell_rows = array_deep_clone(@last_cell_rows)
    @last_cell_rows = array_deep_clone(@cell_rows)
    if !@applied_windows_fix_on_first_cell_rows_update && OS.windows?
      @applied_windows_fix_on_first_cell_rows_update = true
      apply_windows_fix
    end
  end
  @cell_rows_observer_registration = @cell_rows_observer.observe(self, :cell_rows, recursive: true, ignore_frozen: true, attribute_writer_type: [:attribute=, :set_attribute])
end