Class: Clevic::ComboDelegate

Inherits:
Delegate show all
Defined in:
lib/clevic/qt/combo_delegate.rb,
lib/clevic/swing/combo_delegate.rb,
lib/clevic/delegates/combo_delegate.rb

Overview

TODO this should be a module

Direct Known Subclasses

DistinctDelegate, RelationalDelegate, SetDelegate

Instance Attribute Summary collapse

Attributes inherited from Delegate

#entity, #field, #parent

Instance Method Summary collapse

Methods inherited from Delegate

#attribute, #editorEvent, #entity_class, #inspect, #native, #show_message

Methods included from FieldValuer

#attribute_value, #attribute_value=, #display_value, #edit_value, #edit_value=, #find_related, #raw_value, #text_value=, #tooltip, valuer, #valuer, #writer

Constructor Details

#initialize(field) ⇒ ComboDelegate

Returns a new instance of ComboDelegate.



58
59
60
61
# File 'lib/clevic/swing/combo_delegate.rb', line 58

def initialize( field )
  super
  @autocompleting = false
end

Instance Attribute Details

#editorObject (readonly)

Return the GUI component / widget that is displayed when editing. Usually this will be a combo box widget, but it can be a text editor in some cases. if editor is a combo it must support no_insert=( bool )



14
15
16
# File 'lib/clevic/delegates/combo_delegate.rb', line 14

def editor
  @editor
end

Instance Method Details

#allow_null?Boolean

TODO fetch this from the model definition

Returns:

  • (Boolean)


112
113
114
# File 'lib/clevic/delegates/combo_delegate.rb', line 112

def allow_null?
  true
end

#autocomplete(&block) ⇒ Object

make sure we don’t react to document change events while we’re doing autocompletion. Reentrant



174
175
176
177
178
179
# File 'lib/clevic/swing/combo_delegate.rb', line 174

def autocomplete( &block )
  @autocompleting = true
  yield
ensure
  @autocompleting = false
end

#combo_classObject



63
64
65
# File 'lib/clevic/swing/combo_delegate.rb', line 63

def combo_class
  ComboBox
end

#configure_editableObject

TODO kinda redundant because all combos must be editable to support prefix matching



28
29
30
# File 'lib/clevic/delegates/combo_delegate.rb', line 28

def configure_editable
  editor.editable = true
end

#configure_prefixObject

Some GUIs (Qt) can just set this. Swing can’t.



104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/clevic/swing/combo_delegate.rb', line 104

def configure_prefix
  # pick up events from editor
  # but only after all the other config, otherwise we get
  # events triggered by the setup, which isn't helpful.
  editor.editor.editor_component.document.add_document_listener do |event|
    # don't do anything if autocomplete manipulations are in progress
    unless @autocompleting || !editor.typing
      if event.type == javax.swing.event.DocumentEvent::EventType::REMOVE
        invoke_later do
          repopulate
        end
      else
        # only suggest on inserts and updates. Not on deletes.
        filter_prefix( editor.editor.item )
      end
    end
  end
end

#create_combo_box(*args) ⇒ Object



46
47
48
49
50
51
# File 'lib/clevic/qt/combo_delegate.rb', line 46

def create_combo_box( *args )
  Qt::ComboBox.new( parent ).tap do |combo|
    # all combos are editable so that prefix matching will work
    combo.editable = true
  end
end

#createEditor(parent_widget, style_option_view_item, model_index) ⇒ Object

Override the Qt method. Create a ComboBox widget and fill it with the possible values.



54
55
56
57
58
59
60
# File 'lib/clevic/qt/combo_delegate.rb', line 54

def createEditor( parent_widget, style_option_view_item, model_index )
  self.parent = parent_widget
  self.entity = model_index.entity
  init_component( parent_widget, style_option_view_item, model_index )
  editor.delegate = self
  editor
end

#display_for(model_value) ⇒ Object

Return a string to be shown to the user. model_value is an item stored in the combo box model.



18
19
20
# File 'lib/clevic/delegates/combo_delegate.rb', line 18

def display_for( model_value )
  field.transform_attribute( model_value )
end

#dump_editor_state(editor) ⇒ Object



23
24
25
26
27
28
29
30
31
32
33
34
35
# File 'lib/clevic/qt/combo_delegate.rb', line 23

def dump_editor_state( editor )
  if $options[:debug]
    puts "#{self.class.name}"
	puts "editor.completer.completion_count: #{editor.completer.completion_count}"
	puts "editor.completer.current_completion: #{editor.completer.current_completion}"
	puts "editor.find_text( editor.completer.current_completion ): #{editor.find_text( editor.completer.current_completion )}"
	puts "editor.current_text: #{editor.current_text}"
	puts "editor.count: #{editor.count}"
	puts "editor.completer.current_row: #{editor.completer.current_row}"
	puts "editor.item_data( editor.current_index ): #{editor.item_data( editor.current_index ).inspect}"
    puts
end
end

#empty_set?Boolean

return true if this field has no data (needs_combo? is false) and is at the same time restricted (ie needs data from somewhere else)

Returns:

  • (Boolean)


135
136
137
# File 'lib/clevic/delegates/combo_delegate.rb', line 135

def empty_set?
  !needs_combo? && restricted?
end

#filter_prefix(prefix) ⇒ Object

www.drdobbs.com/184404457 for autocompletion steps



182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
# File 'lib/clevic/swing/combo_delegate.rb', line 182

def filter_prefix( prefix )
  # search for matching item in the UI display_for for the items in the combo model
  candidate = population.map{|item| display_for( item ) }.select {|x| x =~ /^#{prefix}/i }.first
  unless candidate.nil?
    first_not_of = candidate.match( /^#{prefix}/i ).offset(0).last
    invoke_later do
      autocomplete do
        # set the shortlist, and the text editor value
        repopulate prefix, candidate

        # set the suggestion selection
        editor.editor.editor_component.with do |text_edit|
          # highlight the suggested match, and leave caret
          # at the beginning of the suggested text
          text_edit.caret_position = candidate.length
          text_edit.move_caret_position( first_not_of )
        end
      end
    end
  end
end

#framework_setup(*args) ⇒ Object



66
67
68
69
# File 'lib/clevic/qt/combo_delegate.rb', line 66

def framework_setup( *args )
  # don't need to do anything here
  # might need to once prefix-matching is implemented
end

#full_editObject

open the combo box, just like if F4 was pressed big trouble here with JComboBox firing an comboEdited action (probably) on focusGained which causes the popup to be hidden again



38
39
40
# File 'lib/clevic/qt/combo_delegate.rb', line 38

def full_edit
  editor.show_popup if is_combo?( editor )
end

#getListCellRendererComponent(jlist, value, index, selected, cell_has_focus) ⇒ Object

return the component to render the values in the list we just transform the value, and pass it to the pre-existing renderer for the combo.



90
91
92
# File 'lib/clevic/swing/combo_delegate.rb', line 90

def getListCellRendererComponent(jlist, value, index, selected, cell_has_focus)
  @original_renderer.getListCellRendererComponent(jlist, display_for( value ), index, selected, cell_has_focus)
end

#hint_string(hint) ⇒ Object

Convert Qt

constants from the integer value to a string value.

TODO this really shouldn’t be here. qtext, or extensions.rb



15
16
17
18
19
20
21
# File 'lib/clevic/qt/combo_delegate.rb', line 15

def hint_string( hint )
  hs = String.new
  Qt::AbstractItemDelegate.constants.each do |x|
    hs = x if eval( "Qt::AbstractItemDelegate::#{x}.to_i" ) == hint.to_i
  end
  hs
end

#if_empty_messageObject

if this delegate has an empty set, return the message, otherwise return nil.



145
146
147
# File 'lib/clevic/delegates/combo_delegate.rb', line 145

def if_empty_message
  empty_set_message if empty_set?
end

#init_component(*args) ⇒ Object

Create a GUI widget and fill it with the possible values. *args will be passed as-is to framework_setup NOTE RIght now it’s the framework’s responsibility to call

self.entity = some_entity

There must be a good way to check that though.



58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/clevic/delegates/combo_delegate.rb', line 58

def init_component( *args )
  if needs_combo?
    @editor = create_combo_box( *args )
    @editor.delegate = self

    # add all entries from population
    population.each do |item|
      editor << item
    end

    # create a nil entry if necessary
    if allow_null? && !editor.include?( nil )
      editor << nil
    end

    # don't allow inserts if the delegate is restricted
    editor.no_insert = restricted?

    # set the correct value in the list
    editor.selected_item = entity.nil? ? nil : attribute_value

    # set up prefix matching when typing in the editor
    configure_prefix

    framework_setup( *args )
  else
    @editor =
    if restricted?
      show_message( empty_set_message )
      nil
    else
      # there is no data yet for the combo, and it's
      # not restricted, so just edit with a simple text field.
      line_editor( edit_value )
    end
  end
  editor
end

#is_combo?Boolean

Returns:

  • (Boolean)


42
43
44
# File 'lib/clevic/qt/combo_delegate.rb', line 42

def is_combo?( editor )
  editor.is_a?( Qt::ComboBox )
end

#line_editor(value) ⇒ Object

return a new text editor. This is for distinct_delegate when there are no other values to choose from. TODO move into distinct_delegate then?



97
98
99
# File 'lib/clevic/swing/combo_delegate.rb', line 97

def line_editor( edit_value )
  @line_editor ||= Qt::LineEdit.new( edit_value, parent )
end

#minimal_editObject

show only the text editor part, not the drop-down



221
222
223
# File 'lib/clevic/swing/combo_delegate.rb', line 221

def minimal_edit
  editor.hide_popup if is_combo?
end

#needs_pre_selection?Boolean

the cell must be selected before the edit can be clicked

Returns:

  • (Boolean)


68
69
70
# File 'lib/clevic/swing/combo_delegate.rb', line 68

def needs_pre_selection?
  true
end

#repopulate(prefix = nil, text = nil) ⇒ Object

Recreate the model and fill it with anything in population that matches the prefix first, followed by anything in the population that doesn’t match the prefix. Then set the editor text value to either text, or to the previous value. Order is important: if the text is set first it’s overridden when the model is populated.



152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/clevic/swing/combo_delegate.rb', line 152

def repopulate( prefix = nil, text = nil )
  autocomplete do
    # save text and popup
    save_item = editor.editor.item
    dropdown_visible = editor.popup_visible?

    # repopulate based on the prefix
    prefix ||= editor.editor.item
    editor.model = editor.model.class.new
    # split set into things to display at the top, and things to display further down
    matching, non_matching = population.partition{ |item| display_for( item ) =~ /^#{prefix}/i }
    matching.each {|item| editor << item}
    non_matching.each {|item| editor << item}

    # restore text and popup
    editor.editor.item = text || save_item
    editor.popup_visible = dropdown_visible
  end
end

#restricted?Boolean

returns true if the editor allows values outside of a predefined range, false otherwise.

Returns:

  • (Boolean)


107
108
109
# File 'lib/clevic/delegates/combo_delegate.rb', line 107

def restricted?
  false
end

#setEditorData(editor, model_index) ⇒ Object

Override the Qt method to send data to the editor from the model.



82
83
84
85
86
87
88
89
# File 'lib/clevic/qt/combo_delegate.rb', line 82

def setEditorData( editor, model_index )
  if is_combo?( editor )
    editor.current_index = editor.find_data( model_index.attribute_value.to_variant )
    editor.line_edit.select_all if editor.editable
  else
    editor.text = model_index.edit_value
  end
end

#setModelData(editor, abstract_item_model, model_index) ⇒ Object

Send the data from the editor to the model. The data will be translated by translate_from_editor_text,



105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/clevic/qt/combo_delegate.rb', line 105

def setModelData( editor, abstract_item_model, model_index )
  if is_combo?( editor )
    dump_editor_state( editor )
    value =
    if editor.completer.current_row == -1
      # item doesn't exist in the list, add it if not restricted
      editor.current_text unless restricted?
    elsif editor.completer.completion_count == editor.count
      # selection from drop down. if it's empty, we want a nil
      editor.current_text
    else
      # there is a matching completion, so use it
      editor.completer.current_completion
    end

    if value != nil
      model_index.attribute_value = translate_from_editor_text( editor, value )
    end

  else
    model_index.attribute_value = editor.text
  end
  abstract_item_model.data_changed( model_index )
end

#translate_from_editor_text(editor, text) ⇒ Object

This translates the text from the editor into something that is stored in an underlying model. Intended to be overridden by subclasses.



93
94
95
96
97
98
99
100
101
# File 'lib/clevic/qt/combo_delegate.rb', line 93

def translate_from_editor_text( editor, text )
  index = editor.find_text( text )

  if index == -1
    text unless restricted?
  else
    editor.item_data( index ).value
  end
end

#updateEditorGeometry(editor, style_option_view_item, model_index) ⇒ Object

Override the Qt::ItemDelegate method.



72
73
74
75
76
77
78
79
# File 'lib/clevic/qt/combo_delegate.rb', line 72

def updateEditorGeometry( editor, style_option_view_item, model_index )
  rect = style_option_view_item.rect

  # ask the editor for how much space it wants, and set the editor
  # to that size when it displays in the table
  rect.set_width( [editor.size_hint.width,rect.width].max ) if is_combo?( editor )
  editor.set_geometry( rect )
end

#valueObject



231
232
233
234
235
236
237
238
239
240
241
242
243
# File 'lib/clevic/swing/combo_delegate.rb', line 231

def value
  # editor could be either a combo or a line (DistinctDelegate with no values yet)
  if is_combo?
    if restricted?
      editor.selected_item
    else
      editor.editor.item
    end
  else
    puts "#{__FILE__}:#{__LINE__}:line item value: #{editor.text}. Take this away when it's printed."
    editor.text
  end
end