clevic 0.13.0.b3 → 0.13.0.b5
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +21 -0
- data/Manifest.txt +91 -85
- data/README.txt +33 -18
- data/Rakefile +2 -3
- data/TODO +8 -14
- data/bin/clevic +18 -20
- data/lib/clevic.rb +7 -1
- data/lib/clevic/action_builder.rb +4 -1
- data/lib/clevic/ar_methods.rb +72 -57
- data/lib/clevic/attribute_list.rb +4 -0
- data/lib/clevic/cache_table.rb +43 -69
- data/lib/clevic/dataset_roller.rb +22 -0
- data/lib/clevic/delegate.rb +11 -5
- data/lib/clevic/delegates/combo_delegate.rb +156 -0
- data/lib/clevic/delegates/distinct_delegate.rb +48 -0
- data/lib/clevic/delegates/relational_delegate.rb +59 -0
- data/lib/clevic/delegates/set_delegate.rb +31 -0
- data/lib/clevic/field.rb +39 -55
- data/lib/clevic/field_valuer.rb +23 -10
- data/lib/clevic/filter_command.rb +22 -36
- data/lib/clevic/framework.rb +37 -0
- data/lib/clevic/generic_format.rb +5 -1
- data/lib/clevic/many_field.rb +28 -3
- data/lib/clevic/model_builder.rb +27 -32
- data/lib/clevic/ordered_dataset.rb +45 -0
- data/lib/clevic/qt.rb +4 -1
- data/lib/clevic/qt/action_builder.rb +9 -1
- data/lib/clevic/qt/browser.rb +1 -1
- data/lib/clevic/qt/clipboard.rb +3 -3
- data/lib/clevic/qt/combo_delegate.rb +25 -89
- data/lib/clevic/qt/delegate.rb +25 -0
- data/lib/clevic/qt/distinct_delegate.rb +5 -23
- data/lib/clevic/qt/extensions.rb +8 -1
- data/lib/clevic/qt/qt_combo_box.rb +58 -0
- data/lib/clevic/qt/relational_delegate.rb +18 -58
- data/lib/clevic/qt/set_delegate.rb +4 -34
- data/lib/clevic/qt/simplest_delegate.rb +19 -0
- data/lib/clevic/qt/table_model.rb +10 -10
- data/lib/clevic/qt/table_view.rb +7 -23
- data/lib/clevic/qt/text_delegate.rb +2 -2
- data/lib/clevic/qt/ui/browser_ui.rb +1 -1
- data/lib/clevic/qt/ui/search_dialog_ui.rb +1 -1
- data/lib/clevic/rails_models_loaders.rb +13 -0
- data/lib/clevic/record.rb +2 -2
- data/lib/clevic/sampler.rb +85 -39
- data/lib/clevic/sequel_ar_adapter.rb +1 -28
- data/lib/clevic/sequel_clevic.rb +68 -0
- data/lib/clevic/sequel_meta.rb +1 -13
- data/lib/clevic/subclasses.rb +18 -0
- data/lib/clevic/swing.rb +2 -1
- data/lib/clevic/swing/action.rb +27 -3
- data/lib/clevic/swing/action_builder.rb +0 -2
- data/lib/clevic/swing/browser.rb +1 -10
- data/lib/clevic/swing/combo_delegate.rb +45 -133
- data/lib/clevic/swing/delegate.rb +2 -0
- data/lib/clevic/swing/distinct_delegate.rb +2 -14
- data/lib/clevic/swing/relational_delegate.rb +2 -20
- data/lib/clevic/swing/set_delegate.rb +13 -28
- data/lib/clevic/swing/table_view.rb +1 -1
- data/lib/clevic/table_model.rb +3 -4
- data/lib/clevic/table_searcher.rb +10 -31
- data/lib/clevic/table_view.rb +97 -43
- data/lib/clevic/ui/browser_ui.rb +133 -0
- data/lib/clevic/ui/search_dialog_ui.rb +106 -0
- data/lib/clevic/version.rb +2 -2
- data/models/accounts_models.rb +24 -21
- data/models/times_models.rb +34 -28
- data/models/times_psql_models.rb +9 -3
- data/models/times_sqlite_models.rb +24 -1
- data/sql/times_sqlite.sql +3 -3
- data/tasks/clevic.rake +2 -2
- data/test/test_cache_table.rb +9 -19
- data/test/test_table_searcher.rb +2 -5
- metadata +95 -91
- data/lib/clevic/order_attribute.rb +0 -63
- data/lib/clevic/qt/boolean_delegate.rb +0 -8
- data/lib/clevic/qt/delegates.rb +0 -1
- data/lib/clevic/qt/item_delegate.rb +0 -66
- data/lib/clevic/sql_dialects.rb +0 -33
- data/tasks/website.rake +0 -25
- data/test/test_order_attribute.rb +0 -62
- 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
|
-
|
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
|
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
|
|
data/lib/clevic/ar_methods.rb
CHANGED
@@ -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
|
-
|
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
|
data/lib/clevic/cache_table.rb
CHANGED
@@ -1,7 +1,9 @@
|
|
1
|
-
require 'clevic/table_searcher
|
2
|
-
require 'clevic/
|
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
|
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 :
|
32
|
+
attr_reader :entity_class
|
27
33
|
|
28
|
-
def initialize( entity_class,
|
29
|
-
@preload_count =
|
30
|
-
# must be before sanitise_options
|
34
|
+
def initialize( entity_class, dataset = nil )
|
35
|
+
@preload_count = 30
|
31
36
|
@entity_class = entity_class
|
32
|
-
#
|
33
|
-
|
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
|
-
|
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
|
-
|
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 +
|
67
|
+
offset = index < 0 ? index + sql_count : index
|
108
68
|
|
109
69
|
# fetch self.preload_count records
|
110
|
-
records =
|
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
|
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
|
-
#
|
124
|
-
#
|
125
|
-
|
126
|
-
|
127
|
-
|
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
|
-
#
|
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(
|
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
|
data/lib/clevic/delegate.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|