clevic 0.13.0.b3 → 0.13.0.b5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. data/History.txt +21 -0
  2. data/Manifest.txt +91 -85
  3. data/README.txt +33 -18
  4. data/Rakefile +2 -3
  5. data/TODO +8 -14
  6. data/bin/clevic +18 -20
  7. data/lib/clevic.rb +7 -1
  8. data/lib/clevic/action_builder.rb +4 -1
  9. data/lib/clevic/ar_methods.rb +72 -57
  10. data/lib/clevic/attribute_list.rb +4 -0
  11. data/lib/clevic/cache_table.rb +43 -69
  12. data/lib/clevic/dataset_roller.rb +22 -0
  13. data/lib/clevic/delegate.rb +11 -5
  14. data/lib/clevic/delegates/combo_delegate.rb +156 -0
  15. data/lib/clevic/delegates/distinct_delegate.rb +48 -0
  16. data/lib/clevic/delegates/relational_delegate.rb +59 -0
  17. data/lib/clevic/delegates/set_delegate.rb +31 -0
  18. data/lib/clevic/field.rb +39 -55
  19. data/lib/clevic/field_valuer.rb +23 -10
  20. data/lib/clevic/filter_command.rb +22 -36
  21. data/lib/clevic/framework.rb +37 -0
  22. data/lib/clevic/generic_format.rb +5 -1
  23. data/lib/clevic/many_field.rb +28 -3
  24. data/lib/clevic/model_builder.rb +27 -32
  25. data/lib/clevic/ordered_dataset.rb +45 -0
  26. data/lib/clevic/qt.rb +4 -1
  27. data/lib/clevic/qt/action_builder.rb +9 -1
  28. data/lib/clevic/qt/browser.rb +1 -1
  29. data/lib/clevic/qt/clipboard.rb +3 -3
  30. data/lib/clevic/qt/combo_delegate.rb +25 -89
  31. data/lib/clevic/qt/delegate.rb +25 -0
  32. data/lib/clevic/qt/distinct_delegate.rb +5 -23
  33. data/lib/clevic/qt/extensions.rb +8 -1
  34. data/lib/clevic/qt/qt_combo_box.rb +58 -0
  35. data/lib/clevic/qt/relational_delegate.rb +18 -58
  36. data/lib/clevic/qt/set_delegate.rb +4 -34
  37. data/lib/clevic/qt/simplest_delegate.rb +19 -0
  38. data/lib/clevic/qt/table_model.rb +10 -10
  39. data/lib/clevic/qt/table_view.rb +7 -23
  40. data/lib/clevic/qt/text_delegate.rb +2 -2
  41. data/lib/clevic/qt/ui/browser_ui.rb +1 -1
  42. data/lib/clevic/qt/ui/search_dialog_ui.rb +1 -1
  43. data/lib/clevic/rails_models_loaders.rb +13 -0
  44. data/lib/clevic/record.rb +2 -2
  45. data/lib/clevic/sampler.rb +85 -39
  46. data/lib/clevic/sequel_ar_adapter.rb +1 -28
  47. data/lib/clevic/sequel_clevic.rb +68 -0
  48. data/lib/clevic/sequel_meta.rb +1 -13
  49. data/lib/clevic/subclasses.rb +18 -0
  50. data/lib/clevic/swing.rb +2 -1
  51. data/lib/clevic/swing/action.rb +27 -3
  52. data/lib/clevic/swing/action_builder.rb +0 -2
  53. data/lib/clevic/swing/browser.rb +1 -10
  54. data/lib/clevic/swing/combo_delegate.rb +45 -133
  55. data/lib/clevic/swing/delegate.rb +2 -0
  56. data/lib/clevic/swing/distinct_delegate.rb +2 -14
  57. data/lib/clevic/swing/relational_delegate.rb +2 -20
  58. data/lib/clevic/swing/set_delegate.rb +13 -28
  59. data/lib/clevic/swing/table_view.rb +1 -1
  60. data/lib/clevic/table_model.rb +3 -4
  61. data/lib/clevic/table_searcher.rb +10 -31
  62. data/lib/clevic/table_view.rb +97 -43
  63. data/lib/clevic/ui/browser_ui.rb +133 -0
  64. data/lib/clevic/ui/search_dialog_ui.rb +106 -0
  65. data/lib/clevic/version.rb +2 -2
  66. data/models/accounts_models.rb +24 -21
  67. data/models/times_models.rb +34 -28
  68. data/models/times_psql_models.rb +9 -3
  69. data/models/times_sqlite_models.rb +24 -1
  70. data/sql/times_sqlite.sql +3 -3
  71. data/tasks/clevic.rake +2 -2
  72. data/test/test_cache_table.rb +9 -19
  73. data/test/test_table_searcher.rb +2 -5
  74. metadata +95 -91
  75. data/lib/clevic/order_attribute.rb +0 -63
  76. data/lib/clevic/qt/boolean_delegate.rb +0 -8
  77. data/lib/clevic/qt/delegates.rb +0 -1
  78. data/lib/clevic/qt/item_delegate.rb +0 -66
  79. data/lib/clevic/sql_dialects.rb +0 -33
  80. data/tasks/website.rake +0 -25
  81. data/test/test_order_attribute.rb +0 -62
  82. data/test/test_sql_dialects.rb +0 -77
data/lib/clevic.rb CHANGED
@@ -6,8 +6,14 @@ module Clevic
6
6
  end
7
7
  end
8
8
 
9
- require 'clevic/sequel_ar_adapter.rb'
9
+ # TODO should this really be here?
10
+ # There are other inflection gems.
11
+ # JRuby-1.5.2 raises exception if this require has a .rb on the
12
+ require 'active_support/inflector'
13
+
14
+ require 'clevic/framework'
10
15
  require 'clevic/sequel_length_validation.rb'
11
16
  require 'clevic/record.rb'
12
17
  require 'clevic/view.rb'
13
18
  require 'clevic/sequel_meta.rb'
19
+ require 'clevic/sequel_clevic.rb'
@@ -84,7 +84,7 @@ module ActionBuilder
84
84
  # Create and return a list of actions. The actions are grouped together,
85
85
  # ie live together on the menu with a separator between groups.
86
86
  # A method called "#{group_name}_actions" will be added to self, which will return the
87
- # set of Qt::Action instances created in the block.
87
+ # set of framework-specific Action instances created in the block.
88
88
  def list( group_name, &block )
89
89
  @group_name = group_name
90
90
  group_names << group_name
@@ -160,6 +160,9 @@ protected
160
160
  unless instance_methods.include?( :action_triggered )
161
161
  def action_triggered( &someblock )
162
162
  yield
163
+ rescue
164
+ puts $!.message
165
+ puts $!.backtrace
163
166
  end
164
167
  end
165
168
 
@@ -2,6 +2,71 @@ require 'active_support'
2
2
  require 'active_support/core_ext/array/extract_options.rb'
3
3
 
4
4
  module Sequel
5
+ class Dataset
6
+ # Basically, we're translating from AR's hash options
7
+ # to Sequel's method algebra, and returning the resulting
8
+ # dataset.
9
+ def translate( options )
10
+ # recursively send key-value pairs to self
11
+ # and return the result
12
+ options.inject( self ) do |dataset, (key, value)|
13
+ case key
14
+ when :limit; dataset.limit( value, nil )
15
+ when :offset
16
+ # workaround for Sequel's refusal to do offset without limit
17
+ # not sure we need :all for >= 3.13.0
18
+ dataset.limit( options[:limit] || :all, value )
19
+
20
+ when :order
21
+ orders = value.split(/, */ ).map do |x|
22
+ case x
23
+ when /^(\w+) +(asc|desc)$/i
24
+ $1.to_sym.send( $2 )
25
+
26
+ when /^\w+$/i
27
+ x.to_sym
28
+
29
+ else
30
+ x.lit
31
+
32
+ end
33
+ end
34
+ dataset.order( *orders )
35
+
36
+ when :conditions
37
+ # this translation is not adequate for all use cases of the AR api
38
+ # specifically where value contains a SQL expression
39
+ unless value.nil?
40
+ possible_literal =
41
+ if value.is_a?( String )
42
+ value.lit
43
+ else
44
+ value
45
+ end
46
+
47
+ dataset.filter( possible_literal )
48
+ end
49
+
50
+ when :include
51
+ # this is the class to join
52
+ joined_class = eval( model.reflections[value][:class_name] )
53
+ dataset.join_table(
54
+ :inner,
55
+ joined_class,
56
+ joined_class.primary_key => model.reflections[value][:key]
57
+ ).select( model.table_name.* )
58
+
59
+ else
60
+ raise "#{key} not implemented"
61
+ # make sure at least it's unchanged, in case options is empty
62
+ end || dataset
63
+ end
64
+
65
+ rescue Exception => e
66
+ raise RuntimeError, "#{self} #{options.inspect} #{e.message}", caller(0)
67
+ end
68
+ end
69
+
5
70
  module Plugins
6
71
  module ArMethods
7
72
  # plugin :ar_methods calls this.
@@ -18,60 +83,10 @@ module Sequel
18
83
  # Copy the necessary class instance variables to the subclass.
19
84
  def inherited(subclass)
20
85
  super
21
- #~ store = @cache_store
22
- #~ ttl = @cache_ttl
23
- #~ cache_ignore_exceptions = @cache_ignore_exceptions
24
- #~ subclass.instance_eval do
25
- #~ @cache_store = store
26
- #~ @cache_ttl = ttl
27
- #~ @cache_ignore_exceptions = cache_ignore_exceptions
28
- #~ end
29
86
  end
30
87
 
31
- def lit_if_string( arg )
32
- if arg.is_a?( String )
33
- arg.lit
34
- else
35
- arg
36
- end
37
- end
38
-
39
- # Basically, we're translating from AR's hash options
40
- # to Sequel's method algebra, and returning the resulting
41
- # dataset.
42
88
  def translate( options )
43
- options.inject( dataset ) do |dataset, (key, value)|
44
- case key
45
- when :limit; dataset.limit( value, nil )
46
- when :offset
47
- # workaround for Sequel's refusal to do offset without limit
48
- dataset.limit( options[:limit] || :all, value )
49
-
50
- when :order
51
- dataset.order( lit_if_string( value ) )
52
-
53
- when :conditions
54
- # this is most likely not adequate for all use cases
55
- # of the AR api
56
- dataset.filter( lit_if_string( value ) ) unless value.nil?
57
-
58
- when :include
59
- # this is the class to joing
60
- joined_class = eval( reflections[value][:class_name] )
61
- dataset.join_table(
62
- :inner,
63
- joined_class,
64
- joined_class.primary_key => reflections[value][:key]
65
- ).select( table_name.* )
66
-
67
- else
68
- raise "#{key} not implemented"
69
- # make sure at least it's unchanged
70
- end || dataset
71
- end
72
-
73
- rescue Exception => e
74
- raise RuntimeError, "#{self.name} #{options.inspect} #{e.message}", caller(0)
89
+ dataset.translate( options )
75
90
  end
76
91
 
77
92
  def find_ar( *args )
@@ -82,19 +97,19 @@ module Sequel
82
97
 
83
98
  case args.first
84
99
  when :first
85
- translate(options).first
100
+ dataset.translate(options).first
86
101
 
87
102
  when :last
88
- translate(options).last
103
+ dataset.translate(options).last
89
104
 
90
105
  when :all
91
- translate(options).all
106
+ dataset.translate(options).all
92
107
 
93
108
  else
94
109
  if args.size == 1
95
- translate(options).filter( :id.qualify( table_name ) => args.first ).first
110
+ dataset.translate(options).filter( :id.qualify( table_name ) => args.first ).first
96
111
  else
97
- translate(options).filter( :id.qualify( table_name ) => args ).all
112
+ dataset.translate(options).filter( :id.qualify( table_name ) => args ).all
98
113
  end
99
114
  end
100
115
  end
@@ -103,7 +118,7 @@ module Sequel
103
118
  options = args.extract_options!
104
119
  attribute = args.first
105
120
 
106
- dataset = translate( options )
121
+ dataset = dataset.translate( options )
107
122
 
108
123
  unless attribute.nil?
109
124
  dataset = dataset.select( attribute )
@@ -1,4 +1,8 @@
1
1
  module Clevic
2
+
3
+ # Provide a list of entries for a distinct field, ordered
4
+ # by either value order, or frequency order.
5
+ # TODO move this into the common DistinctDelegate class.
2
6
  class AttributeList
3
7
  def initialize( entity_class, attribute, attribute_value, find_options )
4
8
  @entity_class = entity_class
@@ -1,7 +1,9 @@
1
- require 'clevic/table_searcher.rb'
2
- require 'clevic/order_attribute.rb'
1
+ require 'clevic/table_searcher'
2
+ require 'clevic/ordered_dataset'
3
3
  require 'bsearch'
4
4
 
5
+ module Clevic
6
+
5
7
  =begin rdoc
6
8
  Fetch rows from the db on demand, rather than all up front.
7
9
 
@@ -18,76 +20,34 @@ TODO drop rows when they haven't been accessed for a while
18
20
  TODO how to handle a quickly-changing underlying table? invalidate cache
19
21
  for each call?
20
22
 
21
- TODO use Sequel instead of order_attributes
23
+ TODO figure out how to handle situations where the character set ordering
24
+ in the db and in Ruby are different.
22
25
  =end
23
26
  class CacheTable < Array
27
+
28
+ include OrderedDataset
29
+
24
30
  # the number of records loaded in one call to the db
25
31
  attr_accessor :preload_count
26
- attr_reader :find_options, :entity_class
32
+ attr_reader :entity_class
27
33
 
28
- def initialize( entity_class, find_options = {} )
29
- @preload_count = 20
30
- # must be before sanitise_options
34
+ def initialize( entity_class, dataset = nil )
35
+ @preload_count = 30
31
36
  @entity_class = entity_class
32
- # must be before anything that uses options
33
- @find_options = (find_options || {}).clone
34
- sanitise_options!
37
+ # defined in OrderAttributes
38
+ self.dataset = dataset || entity_class.dataset
35
39
 
36
40
  # size the array and fill it with nils. They'll be filled
37
41
  # in by the [] operator
38
- @row_count = sql_count
39
- super( @row_count )
42
+ super( sql_count )
40
43
  end
41
44
 
42
45
  # The count of the records according to the db, which may be different to
43
46
  # the records in the cache
44
47
  def sql_count
45
- entity_class.adaptor.count( find_options.reject{|k,v| k == :order} )
48
+ dataset.count
46
49
  end
47
50
 
48
- # Return the set of OrderAttribute objects for this collection.
49
- # If no order attributes are specified, the primary key will be used.
50
- # TODO what about compound primary keys?
51
- def order_attributes
52
- # This is sorted in @find_options[:order], so use that for the search
53
- if @order_attributes.nil?
54
- @order_attributes = find_options[:order].to_s.split( /, */ ).map{|x| OrderAttribute.new(@entity_class, x)}
55
-
56
- # add the primary key if nothing is specified
57
- # because we need an ordering of some kind otherwise
58
- # index_for_entity will not work
59
- unless @order_attributes.any? {|x| x.attribute.to_s == entity_class.primary_key.to_s }
60
- @order_attributes << OrderAttribute.new( entity_class, entity_class.primary_key )
61
- end
62
- end
63
- @order_attributes
64
- end
65
-
66
- # add an id to options[:order] if it's not in there
67
- # make sure options[:conditions] uses db values rather than objects
68
- # ie convert { :debit => DebitObject<...> } to { :debit_id => 5 }
69
- def sanitise_options!
70
- # make sure we have a string here, even if it's blank
71
- # value would be a ,-separated list of order by fields (expressions?)
72
- find_options[:order] ||= ''
73
-
74
- # recreate the options[:order] entry to include default
75
- find_options[:order] = order_attributes.map{|x| x.to_sql}.join(',')
76
-
77
- # make sure objects are converted to ids
78
- if find_options.has_key?( :conditions ) && find_options[:conditions].is_a?( Hash )
79
- conditions = find_options[:conditions].map do |key,value|
80
- metadata = entity_class.meta[key]
81
- if metadata.association?
82
- [metadata.key, value.send( value.primary_key )]
83
- else
84
- [key,value]
85
- end
86
- end
87
- find_options[:conditions] = Hash[ *conditions.flatten ]
88
- end
89
- end
90
-
91
51
  # Execute the block with the specified preload_count,
92
52
  # and restore the existing one when done.
93
53
  # Return the value of the block
@@ -104,14 +64,14 @@ class CacheTable < Array
104
64
  # hits on the db
105
65
  def fetch_entity( index )
106
66
  # calculate negative indices for the SQL offset
107
- offset = index < 0 ? index + @row_count : index
67
+ offset = index < 0 ? index + sql_count : index
108
68
 
109
69
  # fetch self.preload_count records
110
- records = entity_class.adaptor.find( :all, find_options.merge( :offset => offset, :limit => preload_count ) )
70
+ records = dataset.limit( preload_count, offset )
111
71
  records.each_with_index {|x,i| self[i+index] = x if !cached_at?( i+index )}
112
72
 
113
73
  # return the first one
114
- records[0]
74
+ records.first
115
75
  end
116
76
 
117
77
  # return the entity at the given index. Fetch it from the
@@ -120,11 +80,25 @@ class CacheTable < Array
120
80
  super( index ) || fetch_entity( index )
121
81
  end
122
82
 
123
- # make a new instance that has the attributes of this one, but an empty
124
- # data set. pass in ActiveRecord options to filter.
125
- def renew( args = nil )
126
- clear
127
- self.class.new( entity_class, args || find_options )
83
+ # Make a new instance based on the current dataset.
84
+ # Unless new_dataset is specified, pass the dataset
85
+ # to the block, and use the return
86
+ # value from the block as the new dataset.
87
+ #
88
+ # This is so that filter of datasets can be based on the
89
+ # existing one, but it's easy to go back to previous data
90
+ # sets if necessary.
91
+ # TODO write tests for both cases.
92
+ def renew( new_dataset = nil, &block )
93
+ if new_dataset && block_given?
94
+ raise "Passing a new dataset and a modification block doesn't make sense."
95
+ end
96
+
97
+ if block_given?
98
+ self.class.new( entity_class, block.call( dataset ) )
99
+ else
100
+ self.class.new( entity_class, new_dataset || dataset )
101
+ end
128
102
  end
129
103
 
130
104
  # key is what we're searching for. candidate
@@ -165,14 +139,12 @@ class CacheTable < Array
165
139
  bsearch do |candidate|
166
140
  # find using all sort attributes
167
141
  order_attributes.inject(0) do |result,attribute|
168
- # value from the block should be in [-1,0,1],
142
+ # result from the block should be in [-1,0,1],
169
143
  # similar to candidate <=> entity
144
+ key, direction = attribute
170
145
  if result == 0
171
- # they're equal, so compare attribute values
172
- method = attribute.attribute.to_sym
173
-
174
146
  # compare taking ordering direction into account
175
- retval = compare( entity.send( method ), candidate.send( method ), attribute.to_i )
147
+ retval = compare( entity.send( key ), candidate.send( key ), direction )
176
148
 
177
149
  # exit now because we have a difference
178
150
  next( retval ) if retval != 0
@@ -189,6 +161,8 @@ class CacheTable < Array
189
161
  end
190
162
  end
191
163
 
164
+ end
165
+
192
166
  # This is part of Array in case the programmer wants to use
193
167
  # a simple array instead of a CacheTable.
194
168
  class Array
@@ -0,0 +1,22 @@
1
+ module Clevic
2
+
3
+ # Tricky, this. Keeps passing on the dataset and
4
+ # lets it build up, but keeps the result.
5
+ # Used in the UI block to make a nice syntax for specifying the dataset.
6
+ class DatasetRoller
7
+ def initialize( original_dataset )
8
+ @rolling_dataset = original_dataset
9
+ end
10
+
11
+ def dataset
12
+ @rolling_dataset
13
+ end
14
+
15
+ def method_missing(meth, *args, &block)
16
+ @rolling_dataset = @rolling_dataset.send( meth, *args, &block )
17
+ self
18
+ end
19
+ end
20
+
21
+
22
+ end
@@ -8,12 +8,21 @@ class Delegate
8
8
  include FieldValuer
9
9
 
10
10
  def initialize( field )
11
+ super()
11
12
  @field = field
12
13
  end
13
14
 
14
- attr_accessor :entity, :parent
15
+ # This is the ORM entity instance for which this delegate
16
+ # is editing a single field. It needs to be the entire entity
17
+ # so we can set the edited field value on it.
18
+ attr_accessor :entity
15
19
 
20
+ # The parent widget of this delegate / this delegate's widget
21
+ attr_accessor :parent
22
+
23
+ # the Clevic::Field instance which this delegate edits.
16
24
  attr_reader :field
25
+
17
26
  def attribute
18
27
  field.attribute
19
28
  end
@@ -22,10 +31,7 @@ class Delegate
22
31
  field.entity_class
23
32
  end
24
33
 
25
- def find_options
26
- field.find_options
27
- end
28
-
34
+ # assume this is not a combo delegate. That will come later.
29
35
  def is_combo?
30
36
  false
31
37
  end
@@ -0,0 +1,156 @@
1
+ require 'andand'
2
+
3
+ module Clevic
4
+
5
+ =begin rdoc
6
+ Base class for other delegates using Combo boxes.
7
+ =end
8
+ # TODO this should be a module
9
+ class ComboDelegate
10
+ # Return the GUI component / widget that is displayed when editing.
11
+ # Usually this will be a combo box widget, but it can be a text editor
12
+ # in some cases.
13
+ # if editor is a combo it must support no_insert=( bool )
14
+ attr_reader :editor
15
+
16
+ # Return a string to be shown to the user.
17
+ # model_value is an item stored in the combo box model.
18
+ def display_for( model_value )
19
+ field.transform_attribute( model_value )
20
+ end
21
+
22
+ # Some GUIs (Qt) can just set this. Swing can't.
23
+ def configure_prefix
24
+ end
25
+
26
+ # TODO kinda redundant because all combos must be editable
27
+ # to support prefix matching
28
+ def configure_editable
29
+ editor.editable = true
30
+ end
31
+
32
+ # this will create the actual combo box widget
33
+ framework_responsibility :create_combo_box
34
+
35
+ # framework-specific code goes in here
36
+ # it's called right at the end of init_component, once all the other setup
37
+ # is done. Mainly it's so that event handlers can be attached
38
+ # to the combo box without having to deal with events triggered
39
+ # by setup code.
40
+ framework_responsibility :framework_setup
41
+
42
+ # This is called by the combo box to convert an item
43
+ # to something that the combo can insert into
44
+ # itself. Usually this will be a display value
45
+ # and a storage value.
46
+ framework_responsibility :item_to_editor
47
+
48
+ # This is called by the combo box when it needs to convert a
49
+ # storage value to an item, which is something that the delegate
50
+ # will understand.
51
+ framework_responsibility :editor_to_item
52
+
53
+ # Create a GUI widget and fill it with the possible values.
54
+ # *args will be passed as-is to framework_setup
55
+ # NOTE RIght now it's the framework's responsibility to call
56
+ # self.entity = some_entity
57
+ # There must be a good way to check that though.
58
+ def init_component( *args )
59
+ if needs_combo?
60
+ @editor = create_combo_box( *args )
61
+ @editor.delegate = self
62
+
63
+ # add all entries from population
64
+ population.each do |item|
65
+ editor << item
66
+ end
67
+
68
+ # create a nil entry if necessary
69
+ if allow_null? && !editor.include?( nil )
70
+ editor << nil
71
+ end
72
+
73
+ # don't allow inserts if the delegate is restricted
74
+ editor.no_insert = restricted?
75
+
76
+ # set the correct value in the list
77
+ editor.selected_item = entity.nil? ? nil : attribute_value
78
+
79
+ # set up prefix matching when typing in the editor
80
+ configure_prefix
81
+
82
+ framework_setup( *args )
83
+ else
84
+ @editor =
85
+ if restricted?
86
+ show_message( empty_set_message )
87
+ nil
88
+ else
89
+ # there is no data yet for the combo, and it's
90
+ # not restricted, so just edit with a simple text field.
91
+ line_editor( edit_value )
92
+ end
93
+ end
94
+ editor
95
+ end
96
+
97
+ # open the combo box, just like if F4 was pressed
98
+ framework_responsibility :full_edit
99
+
100
+ # show only the text editor part, not the drop-down
101
+ def minimal_edit
102
+ editor.hide_popup if is_combo?
103
+ end
104
+
105
+ # returns true if the editor allows values outside of a predefined
106
+ # range, false otherwise.
107
+ def restricted?
108
+ false
109
+ end
110
+
111
+ # TODO fetch this from the model definition
112
+ def allow_null?
113
+ true
114
+ end
115
+
116
+ # Subclasses should override this to prove a list of
117
+ # values to be used by the combo box. Values could
118
+ # be pretty much anything, depending on the delegate.
119
+ # For example, a RelationalDelegate will have a collection
120
+ # of entity objects, most other delegates will have collections
121
+ # of strings.
122
+ subclass_responsibility :population
123
+
124
+ # Return true if this delegate needs a combo, false otherwise
125
+ # ie if there are no values yet and it's not restricted, then a
126
+ # full combo doesn't make sense
127
+ subclass_responsibility :needs_combo?
128
+
129
+ # return true if this delegate has/needs a combo widget
130
+ # or false if it's a plain text field.
131
+ framework_responsibility :is_combo?
132
+
133
+ # return true if this field has no data (needs_combo? is false)
134
+ # and is at the same time restricted (ie needs data from somewhere else)
135
+ def empty_set?
136
+ !needs_combo? && restricted?
137
+ end
138
+
139
+ # the message to display if the set is empty, and
140
+ # the delegate is restricted to a predefined set.
141
+ subclass_responsibility :empty_set_message
142
+
143
+ # if this delegate has an empty set, return the message, otherwise
144
+ # return nil.
145
+ def if_empty_message
146
+ empty_set_message if empty_set?
147
+ end
148
+
149
+ # the value represented by the combo, ie either
150
+ # the current attribute_value of the field
151
+ # this combo is editing, or an object that could
152
+ # be a new attribute_value
153
+ framework_responsibility :value
154
+ end
155
+
156
+ end