Class: Glimmer::LibUI::ControlProxy::TableProxy

Inherits:
Glimmer::LibUI::ControlProxy show all
Includes:
FiddleConsumer
Defined in:
lib/glimmer/libui/control_proxy/table_proxy.rb

Overview

Proxy for LibUI table objects

Follows the Proxy Design Pattern

Constant Summary collapse

CUSTOM_LISTENER_NAMES =
['on_changed', 'on_edited']
DEFAULT_COLUMN_SORT_BLOCK =
lambda do |table_cell_row, column, table_proxy|
  if table_cell_row.is_a?(Array)
    value = table_cell_row[column]
  else
    attribute = table_proxy.column_attributes[column]
    value = table_cell_row.send(attribute)
  end
  if value.is_a?(Array)
    # This is needed to not crash on sorting an unsortable array
    value = value.map do |element|
      case element
      when true
        1
      when false
        0
      else
        element
      end
    end
  end
  value
end

Constants inherited from Glimmer::LibUI::ControlProxy

BOOLEAN_PROPERTIES, KEYWORD_ALIASES, STRING_PROPERTIES, TransformProxy

Instance Attribute Summary collapse

Attributes inherited from Glimmer::LibUI::ControlProxy

#args, #block, #content_added, #custom_control, #keyword, #libui, #options, #parent_custom_control, #parent_proxy, #slot

Instance Method Summary collapse

Methods included from FiddleConsumer

#fiddle_closure_block_caller

Methods inherited from Glimmer::LibUI::ControlProxy

#append_properties, #append_property, #bind_content, #can_handle_listener?, constant_symbol, #content, control_proxies, control_proxy_class, create, #custom_listener_name_aliases, #custom_listener_names, #default_destroy, #deregister_all_custom_listeners, #deregister_custom_listeners, descendant_keyword_constant_map, #destroy_child, #enabled, exists?, #handle_custom_listener, #has_custom_listener?, image_proxies, keyword, #libui_api_keyword, #listeners, #listeners_for, main_window_proxy, map_descendant_keyword_constants_for, menu_proxies, #method_missing, new_control, #notify_custom_listeners, reset_descendant_keyword_constant_map, #respond_to?, #respond_to_libui?, #send_to_libui, #visible, #window_proxy

Methods included from DataBindable

#data_bind, #data_binding_model_attribute_observer_registrations

Methods included from Parent

#children

Constructor Details

#initialize(keyword, parent, args, &block) ⇒ TableProxy

Returns a new instance of TableProxy.



65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/glimmer/libui/control_proxy/table_proxy.rb', line 65

def initialize(keyword, parent, args, &block)
  @keyword = keyword
  @parent_proxy = parent
  @args = args
  @block = block
  @enabled = true
  @columns = []
  @cell_rows = []
  @last_cell_rows = nil
  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

Dynamic Method Handling

This class handles dynamic methods through the method_missing method in the class Glimmer::LibUI::ControlProxy

Instance Attribute Details

#columnsObject (readonly)

Returns the value of attribute columns.



63
64
65
# File 'lib/glimmer/libui/control_proxy/table_proxy.rb', line 63

def columns
  @columns
end

#modelObject (readonly)

Returns the value of attribute model.



63
64
65
# File 'lib/glimmer/libui/control_proxy/table_proxy.rb', line 63

def model
  @model
end

#model_handlerObject (readonly)

Returns the value of attribute model_handler.



63
64
65
# File 'lib/glimmer/libui/control_proxy/table_proxy.rb', line 63

def model_handler
  @model_handler
end

#table_paramsObject (readonly)

Returns the value of attribute table_params.



63
64
65
# File 'lib/glimmer/libui/control_proxy/table_proxy.rb', line 63

def table_params
  @table_params
end

Instance Method Details

#array_deep_dup(array_or_object) ⇒ Object



274
275
276
277
278
279
280
281
282
# File 'lib/glimmer/libui/control_proxy/table_proxy.rb', line 274

def array_deep_dup(array_or_object)
  if array_or_object.is_a?(Array)
    array_or_object.map do |element|
      array_deep_dup(element)
    end
  else
    array_or_object.dup
  end
end

#cell_rows(rows = nil) ⇒ Object Also known as: cell_rows=, set_cell_rows



115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/glimmer/libui/control_proxy/table_proxy.rb', line 115

def cell_rows(rows = nil)
  if rows.nil?
    @cell_rows
  else
    if !rows.equal?(@cell_rows)
      @cell_rows = rows
      # TODO instead of clearing cached cell rows, consider amending cached cell rows for better performance (to avoid regenerating them)
      clear_cached_cell_rows
      if @cell_rows.is_a?(Enumerator)
        @cell_rows.rewind
        @future_last_cell_rows = array_deep_dup(@cell_rows) # must be done after rewinding
      end
    end
    @cell_rows
  end
end

#column_attributesObject



232
233
234
# File 'lib/glimmer/libui/control_proxy/table_proxy.rb', line 232

def column_attributes
  @column_attributes ||= columns.select {|column| column.is_a?(Column)}.map(&:name).map(&:underscore)
end

#column_proxiesObject



290
291
292
# File 'lib/glimmer/libui/control_proxy/table_proxy.rb', line 290

def column_proxies
  @columns&.select {|c| c.is_a?(Column)}
end

#compound_column_index_for(expanded_column_index) ⇒ Object



284
285
286
287
288
# File 'lib/glimmer/libui/control_proxy/table_proxy.rb', line 284

def compound_column_index_for(expanded_column_index)
  compound_column = @columns.find { |compound_column| compound_column.respond_to?(:column_index) && compound_column.column_index == expanded_column_index }
  compound_columns = @columns.select { |compound_column| compound_column.is_a?(Column) }
  compound_columns.index(compound_column)
end

#data_bind_read(property, model_binding) ⇒ Object



236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
# File 'lib/glimmer/libui/control_proxy/table_proxy.rb', line 236

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
    if !new_value.is_a?(Enumerator) &&
      (
        model_binding.binding_options[:column_attributes] ||
        (!new_value.nil? && (!new_value.is_a?(String) || !new_value.empty?) && (!new_value.is_a?(Array) || !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
    last_cell_rows = @last_cell_rows || [] # initialize with empty array of first time loading table (only time when @last_cell_rows is nil)
    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) ⇒ Object



265
266
267
268
269
270
271
272
# File 'lib/glimmer/libui/control_proxy/table_proxy.rb', line 265

def data_bind_write(property, model_binding)
  case property
  when 'cell_rows'
    handle_listener('on_edited') { model_binding.call(cell_rows) }
  when 'selection'
    handle_listener('on_selection_changed') { model_binding.call(selection) }
  end
end

#destroyObject



108
109
110
111
112
113
# File 'lib/glimmer/libui/control_proxy/table_proxy.rb', line 108

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) ⇒ Object Also known as: editable=, set_editable, editable?



151
152
153
154
155
156
157
# File 'lib/glimmer/libui/control_proxy/table_proxy.rb', line 151

def editable(value = nil)
  if value.nil?
    @editable
  else
    @editable = !!value
  end
end

#expand_cell_row(cell_row) ⇒ Object



141
142
143
144
145
# File 'lib/glimmer/libui/control_proxy/table_proxy.rb', line 141

def expand_cell_row(cell_row)
  return cell_row if cell_row.nil?
  cell_row = expand_cell_row_from_model(cell_row) if !cell_row.is_a?(Array) && column_attributes.any?
  cell_row.flatten(1)
end

#expand_cell_row_from_model(cell_row) ⇒ Object



147
148
149
# File 'lib/glimmer/libui/control_proxy/table_proxy.rb', line 147

def expand_cell_row_from_model(cell_row)
  column_attributes.to_a.map {|attribute| cell_row.send(attribute) }
end

#expand_cell_rows(cell_rows = nil) ⇒ Object Also known as: expanded_cell_rows



134
135
136
137
138
# File 'lib/glimmer/libui/control_proxy/table_proxy.rb', line 134

def expand_cell_rows(cell_rows = nil)
  cell_rows ||= self.cell_rows
  cell_rows ||= []
  cell_rows.map { |cell_row| expand_cell_row(cell_row) }
end

#handle_listener(listener_name, &listener) ⇒ Object



294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
# File 'lib/glimmer/libui/control_proxy/table_proxy.rb', line 294

def handle_listener(listener_name, &listener)
  # if content has been added, then you can register listeners immediately (without accumulation)
  if CUSTOM_LISTENER_NAMES.include?(listener_name) || @content_added
    actual_listener = listener
    case listener_name
    when 'on_selection_changed'
      actual_listener = Proc.new do |myself, *args|
        added_selection = selection.is_a?(Array) ? (selection - @old_selection.to_a) : selection
        removed_selection = selection.is_a?(Array) ? (@old_selection.to_a - selection) : @old_selection
        listener.call(myself, selection, added_selection, removed_selection)
      end
    end
    super(listener_name, &actual_listener)
  else
    # if content is not added yet, then accumulate listeners to register later when table content is closed
    @table_listeners ||= []
    @table_listeners << [listener_name, listener]
  end
end

#header_visibleObject Also known as: header_visible?



203
204
205
206
207
208
# File 'lib/glimmer/libui/control_proxy/table_proxy.rb', line 203

def header_visible
  return @header_visible if !@content_added
  
  result = ::LibUI.table_header_visible(@libui)
  LibUI.integer_to_boolean(result)
end

#header_visible=(value) ⇒ Object Also known as: set_header_visible



211
212
213
214
215
216
217
218
# File 'lib/glimmer/libui/control_proxy/table_proxy.rb', line 211

def header_visible=(value)
  @header_visible = value
  return @header_visible if !@content_added
  return if value.nil?
  
  value = LibUI.boolean_to_integer(value)
  ::LibUI.table_header_set_visible(@libui, value)
end

#post_add_contentObject



81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/glimmer/libui/control_proxy/table_proxy.rb', line 81

def post_add_content
  build_control if !@content_added && @libui.nil?
  super
  # TODO consider automatically detecting what properties/listeners accumulated dynamically to avoid hardcoding code below
  register_listeners
  register_column_listeners
  configure_selection_mode
  configure_selection
  configure_header_visible
  configure_column_sort_indicators
  configure_sorting
end

#post_initialize_child(child) ⇒ Object



94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/glimmer/libui/control_proxy/table_proxy.rb', line 94

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

#selectionObject



173
174
175
176
177
178
179
180
181
182
183
184
# File 'lib/glimmer/libui/control_proxy/table_proxy.rb', line 173

def selection
  return @selection if !@content_added
  
  tsp = super
  ts = ::LibUI::FFI::TableSelection.new(tsp)
  if ts.NumRows > 0
    selection_array = ts.Rows[0, Fiddle::SIZEOF_INT * ts.NumRows].unpack("i*")
    selection_mode == ::LibUI::TableSelectionModeZeroOrMany ? selection_array : selection_array.first
  end
ensure
  ::LibUI.free_table_selection(tsp)
end

#selection=(*value) ⇒ Object Also known as: set_selection



186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
# File 'lib/glimmer/libui/control_proxy/table_proxy.rb', line 186

def selection=(*value)
  value = value.first if value.size == 1
  @selection = value
  return @selection if !@content_added
  return if @selection.nil?

  ts = ::LibUI::FFI::TableSelection.malloc
  ts.NumRows = @selection.is_a?(Array) ? @selection.size : 1
  ts.Rows = [@selection].flatten.pack('i*')
  super(ts)
  # TODO figure out why ensure block is not working (perhaps libui auto-frees that resource upon setting selection)
  # Delete following code if not needed.
#         ensure
#           ::LibUI.free_table_selection(ts)
end

#selection_mode(value = nil) ⇒ Object Also known as: selection_mode=, set_selection_mode



162
163
164
165
166
167
168
169
# File 'lib/glimmer/libui/control_proxy/table_proxy.rb', line 162

def selection_mode(value = nil)
  if value.nil?
    @selection_mode
  else
    value = LibUI.enum_symbol_to_value(:table_selection_mode, value)
    @selection_mode = value
  end
end

#sortableObject Also known as: sortable?



221
222
223
224
# File 'lib/glimmer/libui/control_proxy/table_proxy.rb', line 221

def sortable
  @sortable = true if @sortable.nil?
  @sortable
end

#sortable=(value) ⇒ Object Also known as: set_sortable



227
228
229
# File 'lib/glimmer/libui/control_proxy/table_proxy.rb', line 227

def sortable=(value)
  @sortable = value
end