clevic 0.8.0 → 0.11.1
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.
- data/History.txt +9 -0
- data/Manifest.txt +13 -10
- data/README.txt +6 -9
- data/Rakefile +35 -24
- data/TODO +29 -17
- data/bin/clevic +84 -37
- data/config/hoe.rb +7 -3
- data/lib/clevic.rb +2 -4
- data/lib/clevic/browser.rb +37 -49
- data/lib/clevic/cache_table.rb +55 -165
- data/lib/clevic/db_options.rb +32 -21
- data/lib/clevic/default_view.rb +66 -0
- data/lib/clevic/delegates.rb +51 -67
- data/lib/clevic/dirty.rb +101 -0
- data/lib/clevic/extensions.rb +24 -38
- data/lib/clevic/field.rb +400 -99
- data/lib/clevic/item_delegate.rb +32 -33
- data/lib/clevic/model_builder.rb +315 -148
- data/lib/clevic/order_attribute.rb +53 -0
- data/lib/clevic/record.rb +57 -57
- data/lib/clevic/search_dialog.rb +71 -67
- data/lib/clevic/sql_dialects.rb +33 -0
- data/lib/clevic/table_model.rb +73 -120
- data/lib/clevic/table_searcher.rb +165 -0
- data/lib/clevic/table_view.rb +140 -100
- data/lib/clevic/ui/.gitignore +1 -0
- data/lib/clevic/ui/browser_ui.rb +55 -56
- data/lib/clevic/ui/search_dialog_ui.rb +50 -51
- data/lib/clevic/version.rb +2 -2
- data/lib/clevic/view.rb +89 -0
- data/models/accounts_models.rb +12 -9
- data/models/minimal_models.rb +4 -2
- data/models/times_models.rb +41 -25
- data/models/times_sqlite_models.rb +1 -145
- data/models/values_models.rb +15 -16
- data/test/test_cache_table.rb +138 -0
- data/test/test_helper.rb +131 -0
- data/test/test_model_index_extensions.rb +22 -0
- data/test/test_order_attribute.rb +62 -0
- data/test/test_sql_dialects.rb +77 -0
- data/test/test_table_searcher.rb +188 -0
- metadata +36 -20
- data/bin/import-times +0 -128
- data/config/jamis.rb +0 -589
- data/env.sh +0 -1
- data/lib/active_record/dirty.rb +0 -87
- data/lib/clevic/field_builder.rb +0 -42
- data/website/index.html +0 -170
- data/website/index.txt +0 -17
- data/website/screenshot.png +0 -0
- data/website/stylesheets/screen.css +0 -131
- data/website/template.html.erb +0 -41
data/lib/clevic/extensions.rb
CHANGED
@@ -1,20 +1,23 @@
|
|
1
1
|
# extensions specific to clevic
|
2
2
|
|
3
3
|
require 'qtext/flags.rb'
|
4
|
+
require 'qtext/hash_collector.rb'
|
5
|
+
|
6
|
+
class Object
|
7
|
+
# recursively calls each entry in path_ary
|
8
|
+
# will return nil if any entry in path_ary
|
9
|
+
# results in a nil value.
|
10
|
+
def evaluate_path( path_ary )
|
11
|
+
path_ary.inject( self ) do |value, att|
|
12
|
+
value.nil? ? nil : value.send( att )
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
4
16
|
|
5
17
|
module ActiveRecord
|
6
18
|
class Base
|
7
|
-
#
|
8
|
-
|
9
|
-
path_ary.inject( self ) do |value, att|
|
10
|
-
if value.nil?
|
11
|
-
nil
|
12
|
-
else
|
13
|
-
value.send( att )
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
19
|
+
# checks to see if attribute_sym is either in the column
|
20
|
+
# name list, or in the set of reflections.
|
18
21
|
def self.has_attribute?( attribute_sym )
|
19
22
|
if column_names.include?( attribute_sym.to_s )
|
20
23
|
true
|
@@ -56,23 +59,16 @@ module Qt
|
|
56
59
|
@field ||= model.field_for_index( self )
|
57
60
|
end
|
58
61
|
|
59
|
-
# set the value returned from the gui, as whatever the underlying
|
60
|
-
# entity wants it to be
|
61
|
-
# TODO this will break for more than 2 objects in a path
|
62
|
-
def gui_value=( obj )
|
63
|
-
entity.send( "#{model.dots[column]}=", obj )
|
64
|
-
end
|
65
|
-
|
66
62
|
def dump
|
63
|
+
if valid?
|
67
64
|
<<-EOF
|
68
|
-
|
69
|
-
|
70
|
-
dotted_path: #{dotted_path.inspect}
|
71
|
-
attribute_path: #{attribute_path.inspect}
|
72
|
-
attribute: #{attribute.inspect}
|
73
|
-
attribute_value: #{attribute_value.inspect}
|
65
|
+
field: #{field_name} => #{field_value}
|
66
|
+
attribute: #{attribute.inspect} => #{attribute_value.inspect}
|
74
67
|
metadata: #{metadata.inspect}
|
75
68
|
EOF
|
69
|
+
else
|
70
|
+
'invalid'
|
71
|
+
end
|
76
72
|
end
|
77
73
|
|
78
74
|
# return the attribute of the underlying entity corresponding
|
@@ -90,22 +86,12 @@ module Qt
|
|
90
86
|
end
|
91
87
|
|
92
88
|
# set the value of the attribute, without following the
|
93
|
-
# full path
|
89
|
+
# full path.
|
90
|
+
# TODO remove need to constantly recalculate the attribute writer
|
94
91
|
def attribute_value=( obj )
|
95
92
|
entity.send( "#{attribute.to_s}=", obj )
|
96
93
|
end
|
97
94
|
|
98
|
-
# the dotted attribute path, same as a 'column' in the model
|
99
|
-
def dotted_path
|
100
|
-
model.dots[column]
|
101
|
-
end
|
102
|
-
|
103
|
-
# return an array of path elements from dotted_path
|
104
|
-
def attribute_path
|
105
|
-
return nil if model.nil?
|
106
|
-
model.attribute_paths[column]
|
107
|
-
end
|
108
|
-
|
109
95
|
# returns the ActiveRecord column_for_attribute
|
110
96
|
def metadata
|
111
97
|
# use the optimised version
|
@@ -118,7 +104,7 @@ module Qt
|
|
118
104
|
metadata.name
|
119
105
|
end
|
120
106
|
|
121
|
-
# return the value of the field, it the _id value
|
107
|
+
# return the value of the field, it may be the _id value
|
122
108
|
def field_value
|
123
109
|
entity.send( field_name )
|
124
110
|
end
|
@@ -126,7 +112,6 @@ module Qt
|
|
126
112
|
# the underlying entity
|
127
113
|
def entity
|
128
114
|
return nil if model.nil?
|
129
|
-
#~ puts "fetching entity from collection for xy=(#{row},#{column})" if @entity.nil?
|
130
115
|
@entity ||= model.collection[row]
|
131
116
|
end
|
132
117
|
|
@@ -148,6 +133,7 @@ module Qt
|
|
148
133
|
def errors
|
149
134
|
[ entity.errors[field_name.to_sym] ].flatten
|
150
135
|
end
|
136
|
+
|
151
137
|
end
|
152
138
|
|
153
139
|
end
|
data/lib/clevic/field.rb
CHANGED
@@ -1,90 +1,224 @@
|
|
1
|
-
require '
|
2
|
-
|
3
|
-
require 'clevic/field_builder.rb'
|
1
|
+
require 'gather.rb'
|
4
2
|
|
5
3
|
module Clevic
|
6
4
|
|
7
5
|
=begin rdoc
|
8
6
|
This defines a field in the UI, and how it hooks up to a field in the DB.
|
7
|
+
|
8
|
+
Attributes marked PROPERTY are DSL-style accessors, where the value can be
|
9
|
+
set with either an assignment or by passing a parameter. For example
|
10
|
+
property :ixnay
|
11
|
+
|
12
|
+
will allow
|
13
|
+
|
14
|
+
# reader
|
15
|
+
instance.ixnay
|
16
|
+
|
17
|
+
#writer
|
18
|
+
instance.ixnay = 'nix, baby'
|
19
|
+
|
20
|
+
#writer
|
21
|
+
instance.ixnay 'nix baby'
|
22
|
+
|
23
|
+
Generally properties are for options that can be passed to the field creation
|
24
|
+
method in ModelBuilder, whereas ruby attributes are for the internal workings.
|
25
|
+
|
26
|
+
#--
|
27
|
+
TODO decide whether value_for type methods take an entity and do_something methods
|
28
|
+
take a value.
|
29
|
+
|
30
|
+
TODO this class is a bit confused about whether it handles metadata or record data, or both.
|
31
|
+
|
32
|
+
TODO meta needs to handle virtual fields better. Also is_date_time?
|
9
33
|
=end
|
10
34
|
class Field
|
11
|
-
|
35
|
+
# For defining properties
|
36
|
+
include Gather
|
37
|
+
|
38
|
+
# The value to be displayed after being optionally format-ed
|
39
|
+
#
|
40
|
+
# Takes a String, a Symbol, or a Proc.
|
41
|
+
#
|
42
|
+
# A String will be a dot-separated path of attributes starting on the object returned by attribute.
|
43
|
+
# Paths longer than 1 element haven't been tested much.
|
44
|
+
#
|
45
|
+
# A Symbol refers to a method to be called on the current entity
|
46
|
+
#
|
47
|
+
# A Proc will be passed the current entity. This can be used to display 'virtual'
|
48
|
+
# fields from related tables, or calculated fields.
|
49
|
+
#
|
50
|
+
# Defaults to nil, in other words the value of the attribute for this field.
|
51
|
+
property :display
|
52
|
+
|
53
|
+
# The label to be displayed in the column headings. Defaults to the humanised field name.
|
54
|
+
property :label
|
55
|
+
|
56
|
+
# For relational fields, this is the class_name for the related AR entity.
|
57
|
+
# TODO not used anymore?
|
58
|
+
property :class_name
|
59
|
+
|
60
|
+
# One of the alignment specifiers - :left, :centre, :right or :justified.
|
61
|
+
# Defaults to right for numeric fields, centre for boolean, and left for
|
62
|
+
# other values.
|
63
|
+
property :alignment
|
64
|
+
|
65
|
+
# something to do with the icon that Qt displays. Not implemented yet.
|
66
|
+
property :decoration
|
67
|
+
|
68
|
+
# This defines how to format the value returned by :display. It takes a string or a Proc.
|
69
|
+
# Generally the string is something
|
70
|
+
# that can be understood by strftime (for time and date fields) or understood
|
71
|
+
# by % (for everything else). It can also be a Proc that has one parameter -
|
72
|
+
# the current entity. There are sensible defaults for common field types.
|
73
|
+
property :format
|
74
|
+
|
75
|
+
# This is just like format, except that it's used to format the value just
|
76
|
+
# before it's edited. A good use of this is to display dates with a 2-digit year
|
77
|
+
# but edit them with a 4 digit year.
|
78
|
+
# Defaults to a sensible value for some fields, for others it will default to the value of :format.
|
79
|
+
property :edit_format
|
80
|
+
|
81
|
+
# Whether the field is currently visible or not.
|
82
|
+
property :visible
|
83
|
+
|
84
|
+
# Sample is used if the programmer wishes to provide a value (that will be converted
|
85
|
+
# using to_s) that can be used
|
86
|
+
# as the basis for calculating the width of the field. By default this will be
|
87
|
+
# calculated from the database, but this may be an expensive operation, and
|
88
|
+
# doesn't always work properly. So we
|
89
|
+
# have the option to override that if we wish.
|
90
|
+
property :sample
|
91
|
+
|
92
|
+
# Takes a boolean. Set the field to read-only.
|
93
|
+
property :read_only
|
94
|
+
|
95
|
+
# The foreground and background colors.
|
96
|
+
# Can take a Proc, a string, or a symbol.
|
97
|
+
# - A Proc is called with an entity
|
98
|
+
# - A String is treated as a constant which may be one of the string constants understood by Qt::Color
|
99
|
+
# - A symbol is treated as a method to be call on an entity
|
100
|
+
#
|
101
|
+
# The result can be a Qt::Color, or one of the strings in
|
102
|
+
# http://www.w3.org/TR/SVG/types.html#ColorKeywords.
|
103
|
+
property :foreground, :background
|
104
|
+
|
105
|
+
# Can take a Proc, a string, or a symbol.
|
106
|
+
# - A Proc is called with an entity
|
107
|
+
# - A String is treated as a constant
|
108
|
+
# - A symbol is treated as a method to be call on an entity
|
109
|
+
property :tooltip
|
110
|
+
|
111
|
+
# The set of allowed values for restricted fields. If it's a hash, the
|
112
|
+
# keys will be stored in the db, and the values displayed in the UI.
|
113
|
+
property :set
|
114
|
+
|
115
|
+
# Only for the distinct field type. The values will be sorted either with the
|
116
|
+
# most used values first (:frequency => true) or in alphabetical order (:description => true).
|
117
|
+
property :frequency, :description
|
118
|
+
|
119
|
+
# Not implemented. Default value for this field for new records. Not sure how to populate it though.
|
120
|
+
property :default
|
121
|
+
|
122
|
+
# properties for ActiveRecord options
|
123
|
+
# There are actually from ActiveRecord::Base.VALID_FIND_OPTIONS, but it's protected
|
124
|
+
# each element becomes a property.
|
125
|
+
AR_FIND_OPTIONS = [ :conditions, :include, :joins, :limit, :offset, :order, :select, :readonly, :group, :from, :lock ]
|
126
|
+
AR_FIND_OPTIONS.each{|x| property x}
|
127
|
+
|
128
|
+
# Return a list of find options and their values, but only
|
129
|
+
# if the values are not nil
|
130
|
+
def find_options
|
131
|
+
AR_FIND_OPTIONS.inject(Hash.new) do |ha,x|
|
132
|
+
option_value = self.send(x)
|
133
|
+
unless option_value.nil?
|
134
|
+
ha[x] = option_value
|
135
|
+
end
|
136
|
+
ha
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
# The UI delegate class for the field. In Qt, this is a subclass of AbstractItemDelegate.
|
141
|
+
attr_accessor :delegate
|
12
142
|
|
13
|
-
|
14
|
-
|
15
|
-
|
143
|
+
# The attribute on the AR entity that forms the basis for this field.
|
144
|
+
# Accessing the returned attribute (using send, or the [] method on an entity)
|
145
|
+
# will give a simple value, or another AR entity in the case of relational fields.
|
146
|
+
# In other words, this is *not* the same as the name of the field in the DB, which
|
147
|
+
# would normally have an _id suffix for relationships.
|
148
|
+
attr_accessor :attribute
|
16
149
|
|
17
|
-
|
150
|
+
# The ActiveRecord::Base subclass this field uses to get data from.
|
151
|
+
attr_reader :entity_class
|
18
152
|
|
19
|
-
#
|
20
|
-
|
153
|
+
# Create a new Field object that displays the contents of a database field in
|
154
|
+
# the UI using the given parameters.
|
155
|
+
# - attribute is the symbol for the attribute on the entity_class.
|
156
|
+
# - entity_class is the ActiveRecord::Base subclass which this Field talks to.
|
157
|
+
# - options is a hash of writable attributes in Field, which can be any of the properties defined in this class.
|
158
|
+
def initialize( attribute, entity_class, options, &block )
|
21
159
|
# sanity checking
|
22
|
-
unless
|
160
|
+
unless attribute.is_a?( Symbol )
|
161
|
+
raise "attribute #{attribute.inspect} must be a symbol"
|
162
|
+
end
|
163
|
+
|
164
|
+
unless entity_class.ancestors.include?( ActiveRecord::Base )
|
165
|
+
raise "entity_class must be a descendant of ActiveRecord::Base"
|
166
|
+
end
|
167
|
+
|
168
|
+
unless entity_class.has_attribute?( attribute ) or entity_class.instance_methods.include?( attribute.to_s )
|
23
169
|
msg = <<EOF
|
24
|
-
#{attribute} not found in #{
|
25
|
-
#{
|
170
|
+
#{attribute} not found in #{entity_class.name}. Possibilities are:
|
171
|
+
#{entity_class.attribute_names.join("\n")}
|
26
172
|
EOF
|
27
173
|
raise msg
|
28
174
|
end
|
29
175
|
|
30
|
-
#
|
176
|
+
# instance variables
|
31
177
|
@attribute = attribute
|
32
|
-
@
|
178
|
+
@entity_class = entity_class
|
33
179
|
@visible = true
|
34
180
|
|
35
|
-
|
36
|
-
|
37
|
-
end
|
181
|
+
# initialise
|
182
|
+
@value_cache = {}
|
38
183
|
|
39
|
-
#
|
40
|
-
|
41
|
-
@path_block = options[:display]
|
42
|
-
else
|
43
|
-
@path = options[:display]
|
44
|
-
end
|
184
|
+
# handle options
|
185
|
+
gather( options, &block )
|
45
186
|
|
46
|
-
#
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
when :time; @format = '%H:%M'
|
53
|
-
when :date; @format = '%d-%h-%y'
|
54
|
-
when :datetime; @format = '%d-%h-%y %H:%M:%S'
|
55
|
-
when :decimal, :float; @format = "%.2f"
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
# default alignments
|
60
|
-
if @alignment.nil?
|
61
|
-
@alignment =
|
62
|
-
case meta.type
|
63
|
-
when :decimal, :integer, :float; qt_alignright
|
64
|
-
when :boolean; qt_aligncenter
|
65
|
-
end
|
66
|
-
end
|
187
|
+
# set various sensible defaults. They're not lazy accessors because
|
188
|
+
# they might stay nil, and we don't want to keep evaluating them.
|
189
|
+
default_label!
|
190
|
+
default_format!
|
191
|
+
default_edit_format!
|
192
|
+
default_alignment!
|
67
193
|
end
|
68
194
|
|
69
|
-
# Return the attribute value for the given entity,
|
70
|
-
#
|
195
|
+
# Return the attribute value for the given ActiveRecord entity, or nil
|
196
|
+
# if entity is nil. Will call transform_attribute.
|
71
197
|
def value_for( entity )
|
72
|
-
|
73
|
-
|
198
|
+
begin
|
199
|
+
return nil if entity.nil?
|
200
|
+
transform_attribute( entity.send( attribute ) )
|
201
|
+
rescue Exception => e
|
202
|
+
puts "error for #{entity}.#{entity.send( attribute ).inspect} in value_for: #{e.message}"
|
203
|
+
puts e.backtrace
|
204
|
+
end
|
74
205
|
end
|
75
206
|
|
76
|
-
#
|
77
|
-
# attribute value. Otherwise just return
|
78
|
-
# attribute_value itself
|
207
|
+
# Apply display, to the given
|
208
|
+
# attribute value. Otherwise just return the
|
209
|
+
# attribute_value itself.
|
79
210
|
def transform_attribute( attribute_value )
|
80
211
|
return nil if attribute_value.nil?
|
81
|
-
case
|
82
|
-
when
|
83
|
-
|
84
|
-
|
85
|
-
when !path.nil?
|
86
|
-
attribute_value.evaluate_path( path.split( /\./ ) )
|
212
|
+
case display
|
213
|
+
when Proc
|
214
|
+
display.call( attribute_value )
|
87
215
|
|
216
|
+
when String
|
217
|
+
attribute_value.evaluate_path( display.split( '.' ) )
|
218
|
+
|
219
|
+
when Symbol
|
220
|
+
attribute_value.send( display )
|
221
|
+
|
88
222
|
else
|
89
223
|
attribute_value
|
90
224
|
end
|
@@ -95,29 +229,57 @@ EOF
|
|
95
229
|
meta.type == ActiveRecord::Reflection::AssociationReflection
|
96
230
|
end
|
97
231
|
|
98
|
-
#
|
99
|
-
#
|
100
|
-
|
101
|
-
|
232
|
+
# Return true if the field is a date, a time or a datetime.
|
233
|
+
# If display is nil, the value is calculated, so we need
|
234
|
+
# to check the value. Otherwise use the field metadata.
|
235
|
+
# Cache the result for the first non-nil value.
|
236
|
+
def is_date_time?( value )
|
237
|
+
if value.nil?
|
238
|
+
false
|
239
|
+
else
|
240
|
+
@is_date_time ||=
|
241
|
+
if display.nil?
|
242
|
+
[:time, :date, :datetime].include?( meta.type )
|
243
|
+
else
|
244
|
+
# it's a virtual field, so we need to use the value
|
245
|
+
value.is_a?( Date ) || value.is_a?( Time )
|
246
|
+
end
|
247
|
+
end
|
102
248
|
end
|
103
249
|
|
104
250
|
# return ActiveRecord::Base.columns_hash[attribute]
|
105
251
|
# in other words an ActiveRecord::ConnectionAdapters::Column object,
|
106
252
|
# or an ActiveRecord::Reflection::AssociationReflection object
|
107
253
|
def meta
|
108
|
-
@
|
254
|
+
@meta ||= @entity_class.columns_hash[attribute.to_s] || @entity_class.reflections[attribute]
|
255
|
+
end
|
256
|
+
|
257
|
+
# return the type of this attribute. Usually one of :string, :integer, :float
|
258
|
+
# or some entity class (ActiveRecord::Base subclass)
|
259
|
+
def attribute_type
|
260
|
+
@attribute_type ||=
|
261
|
+
if meta.kind_of?( ActiveRecord::Reflection::MacroReflection )
|
262
|
+
meta.klass
|
263
|
+
else
|
264
|
+
meta.type
|
265
|
+
end
|
109
266
|
end
|
110
267
|
|
111
268
|
# return true if this field can be used in a filter
|
112
269
|
# virtual fields (ie those that don't exist in this field's
|
113
|
-
# table) can't be
|
270
|
+
# table) can't be used to filter on.
|
114
271
|
def filterable?
|
115
272
|
!meta.nil?
|
116
273
|
end
|
117
274
|
|
118
|
-
#
|
275
|
+
# Return the name of the database field for this Field, quoted for the dbms.
|
119
276
|
def quoted_field
|
120
|
-
|
277
|
+
quote_field( meta.name )
|
278
|
+
end
|
279
|
+
|
280
|
+
# Quote the given string as a field name for SQL.
|
281
|
+
def quote_field( field_name )
|
282
|
+
@entity_class.connection.quote_column_name( field_name )
|
121
283
|
end
|
122
284
|
|
123
285
|
# return the result of the attribute + the path
|
@@ -128,30 +290,57 @@ EOF
|
|
128
290
|
# return an array of the various attribute parts
|
129
291
|
def attribute_path
|
130
292
|
pieces = [ attribute.to_s ]
|
131
|
-
pieces.concat(
|
293
|
+
pieces.concat( display.to_s.split( '.' ) ) unless display.is_a? Proc
|
132
294
|
pieces.map{|x| x.to_sym}
|
133
295
|
end
|
134
296
|
|
135
|
-
#
|
297
|
+
# Return true if the field is read-only. Defaults to false.
|
136
298
|
def read_only?
|
137
299
|
@read_only || false
|
138
300
|
end
|
139
301
|
|
140
|
-
# format
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
302
|
+
# apply format to value. Use strftime for date_time types, or % for everything else.
|
303
|
+
# If format is a proc, pass value to it.
|
304
|
+
def do_generic_format( format, value )
|
305
|
+
begin
|
306
|
+
unless format.nil?
|
307
|
+
if format.is_a? Proc
|
308
|
+
format.call( value )
|
309
|
+
else
|
310
|
+
if is_date_time?( value )
|
311
|
+
value.strftime( format )
|
312
|
+
else
|
313
|
+
format % value
|
314
|
+
end
|
315
|
+
end
|
145
316
|
else
|
146
|
-
|
317
|
+
value
|
147
318
|
end
|
148
|
-
|
149
|
-
|
319
|
+
rescue Exception => e
|
320
|
+
puts "format: #{format.inspect}"
|
321
|
+
puts "value.class: #{value.class.inspect}"
|
322
|
+
puts "value: #{value.inspect}"
|
323
|
+
puts e.message
|
324
|
+
puts e.backtrace
|
325
|
+
nil
|
150
326
|
end
|
151
327
|
end
|
152
328
|
|
153
|
-
|
154
|
-
|
329
|
+
def do_format( value )
|
330
|
+
do_generic_format( format, value )
|
331
|
+
end
|
332
|
+
|
333
|
+
def do_edit_format( value )
|
334
|
+
do_generic_format( edit_format, value )
|
335
|
+
end
|
336
|
+
|
337
|
+
# return a sample for the field which can be used to size the UI field widget
|
338
|
+
def sample( *args )
|
339
|
+
if !args.empty?
|
340
|
+
self.sample = *args
|
341
|
+
return
|
342
|
+
end
|
343
|
+
|
155
344
|
if @sample.nil?
|
156
345
|
self.sample =
|
157
346
|
case meta.type
|
@@ -168,44 +357,148 @@ EOF
|
|
168
357
|
# TODO return a width, or something like that
|
169
358
|
when :boolean; 'W'
|
170
359
|
|
171
|
-
when ActiveRecord::Reflection::AssociationReflection
|
172
|
-
|
360
|
+
when ActiveRecord::Reflection::AssociationReflection.class
|
361
|
+
related_sample
|
173
362
|
|
174
363
|
else
|
175
|
-
puts "#{@
|
364
|
+
puts "#{@entity_class.name}.#{attribute} is a #{meta.type.inspect}"
|
176
365
|
end
|
177
366
|
|
178
|
-
if $options[:debug]
|
179
|
-
puts "@sample for #{@
|
180
|
-
end
|
367
|
+
#~ if $options && $options[:debug]
|
368
|
+
#~ puts "@sample for #{@entity_class.name}.#{attribute} #{meta.type}: #{@sample.inspect}"
|
369
|
+
#~ end
|
181
370
|
end
|
182
371
|
# if we don't know how to figure it out from the data, just return the label size
|
183
372
|
@sample || self.label
|
184
373
|
end
|
374
|
+
|
375
|
+
# Called by Clevic::TableModel to get the tooltip value
|
376
|
+
def tooltip_for( entity )
|
377
|
+
cache_value_for( :background, entity )
|
378
|
+
end
|
379
|
+
|
380
|
+
# TODO Doesn't do anything useful yet.
|
381
|
+
def decoration_for( entity )
|
382
|
+
nil
|
383
|
+
end
|
384
|
+
|
385
|
+
# Convert something that responds to to_s to a Qt::Color,
|
386
|
+
# or just return the argument if it's already a Qt::Color
|
387
|
+
def string_or_color( s_or_c )
|
388
|
+
case s_or_c
|
389
|
+
when Qt::Color
|
390
|
+
s_or_c
|
391
|
+
else
|
392
|
+
Qt::Color.new( s_or_c.to_s )
|
393
|
+
end
|
394
|
+
end
|
395
|
+
|
396
|
+
# Called by Clevic::TableModel to get the foreground color value
|
397
|
+
def foreground_for( entity )
|
398
|
+
cache_value_for( :foreground, entity ) {|x| string_or_color(x)}
|
399
|
+
end
|
400
|
+
|
401
|
+
# Called by Clevic::TableModel to get the background color value
|
402
|
+
def background_for( entity )
|
403
|
+
cache_value_for( :background, entity ) {|x| string_or_color(x)}
|
404
|
+
end
|
405
|
+
|
406
|
+
protected
|
407
|
+
|
408
|
+
# call the conversion_block with the value, or just return the
|
409
|
+
# value if conversion_block is nil
|
410
|
+
def convert_or_identity( value, &conversion_block )
|
411
|
+
if conversion_block.nil?
|
412
|
+
value
|
413
|
+
else
|
414
|
+
conversion_block.call( value )
|
415
|
+
end
|
416
|
+
end
|
417
|
+
|
418
|
+
# symbol is the property name to fetch a value for.
|
419
|
+
# It can be a Proc, a symbol, or a value responding to to_s.
|
420
|
+
# In all cases, conversion block will be called
|
421
|
+
# conversion_block takes the value expected back from the property
|
422
|
+
# and converts it to something that Qt will understand. Mostly
|
423
|
+
# this applies to non-strings, ie colors for foreground and background,
|
424
|
+
# and an icon resource for decoration - that kind of thing.
|
425
|
+
def cache_value_for( symbol, entity, &conversion_block )
|
426
|
+
value = send( symbol )
|
427
|
+
case value
|
428
|
+
when Proc; convert_or_identity( value.call( entity ), &conversion_block ) unless entity.nil?
|
429
|
+
when Symbol; convert_or_identity( entity.send( value ), &conversion_block ) unless entity.nil?
|
430
|
+
else; @value_cache[symbol] ||=convert_or_identity( value, &conversion_block )
|
431
|
+
end
|
432
|
+
end
|
433
|
+
|
434
|
+
def default_label!
|
435
|
+
@label ||= attribute.to_s.humanize
|
436
|
+
end
|
437
|
+
|
438
|
+
def default_format!
|
439
|
+
if @format.nil?
|
440
|
+
@format =
|
441
|
+
case meta.type
|
442
|
+
when :time; '%H:%M'
|
443
|
+
when :date; '%d-%h-%y'
|
444
|
+
when :datetime; '%d-%h-%y %H:%M:%S'
|
445
|
+
when :decimal, :float; "%.2f"
|
446
|
+
end
|
447
|
+
end
|
448
|
+
@format
|
449
|
+
end
|
450
|
+
|
451
|
+
def default_edit_format!
|
452
|
+
if @edit_format.nil?
|
453
|
+
@edit_format =
|
454
|
+
case meta.type
|
455
|
+
when :date; '%d-%h-%Y'
|
456
|
+
when :datetime; '%d-%h-%Y %H:%M:%S'
|
457
|
+
end || default_format!
|
458
|
+
end
|
459
|
+
@edit_format
|
460
|
+
end
|
461
|
+
|
462
|
+
def default_alignment!
|
463
|
+
if @alignment.nil?
|
464
|
+
@alignment =
|
465
|
+
case meta.type
|
466
|
+
when :decimal, :integer, :float; :right
|
467
|
+
when :boolean; :centre
|
468
|
+
end
|
469
|
+
end
|
470
|
+
end
|
185
471
|
|
186
472
|
private
|
187
473
|
|
188
474
|
def format_result( result_set )
|
189
475
|
unless result_set.size == 0
|
190
476
|
obj = result_set[0][attribute]
|
191
|
-
unless obj.nil?
|
192
|
-
do_format( obj )
|
193
|
-
end
|
477
|
+
do_format( obj ) unless obj.nil?
|
194
478
|
end
|
195
479
|
end
|
196
480
|
|
197
|
-
def string_sample( max_sample = nil )
|
198
|
-
|
199
|
-
select distinct #{
|
200
|
-
from #{
|
481
|
+
def string_sample( max_sample = nil, entity_class = @entity_class, field_name = meta.name )
|
482
|
+
statement = <<-EOF
|
483
|
+
select distinct #{quote_field field_name}
|
484
|
+
from #{entity_class.table_name}
|
201
485
|
where
|
202
|
-
length( #{
|
203
|
-
select max( length( #{
|
204
|
-
from #{
|
486
|
+
length( #{quote_field field_name} ) = (
|
487
|
+
select max( length( #{quote_field field_name} ) )
|
488
|
+
from #{entity_class.table_name}
|
205
489
|
)
|
206
490
|
EOF
|
491
|
+
result_set = @entity_class.connection.execute statement
|
207
492
|
unless result_set.entries.size == 0
|
208
|
-
|
493
|
+
row = result_set[0]
|
494
|
+
result =
|
495
|
+
case row
|
496
|
+
when Array
|
497
|
+
row[0]
|
498
|
+
when Hash
|
499
|
+
row.values[0]
|
500
|
+
end
|
501
|
+
|
209
502
|
if max_sample.nil?
|
210
503
|
result
|
211
504
|
else
|
@@ -215,9 +508,9 @@ private
|
|
215
508
|
end
|
216
509
|
|
217
510
|
def date_time_sample
|
218
|
-
result_set = @
|
511
|
+
result_set = @entity_class.find_by_sql <<-EOF
|
219
512
|
select #{quoted_field}
|
220
|
-
from #{@
|
513
|
+
from #{@entity_class.table_name}
|
221
514
|
where #{quoted_field} is not null
|
222
515
|
limit 1
|
223
516
|
EOF
|
@@ -228,13 +521,21 @@ private
|
|
228
521
|
# TODO Use precision from metadata, not for integers
|
229
522
|
# returns nil for floats. So it's probably not useful
|
230
523
|
#~ puts "meta.precision: #{meta.precision.inspect}"
|
231
|
-
result_set = @
|
524
|
+
result_set = @entity_class.find_by_sql <<-EOF
|
232
525
|
select max( #{quoted_field} )
|
233
|
-
from #{@
|
526
|
+
from #{@entity_class.table_name}
|
234
527
|
EOF
|
235
528
|
format_result( result_set )
|
236
529
|
end
|
237
530
|
|
531
|
+
def related_sample
|
532
|
+
# TODO this isn't really the right way to do this
|
533
|
+
return nil if meta.nil?
|
534
|
+
if meta.klass.attribute_names.include?( attribute_path[1].to_s )
|
535
|
+
string_sample( nil, meta.klass, attribute_path[1] )
|
536
|
+
end
|
537
|
+
end
|
538
|
+
|
238
539
|
end
|
239
540
|
|
240
541
|
end
|