clevic 0.12.0 → 0.13.0.b1
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +10 -0
- data/Manifest.txt +209 -30
- data/README.txt +16 -20
- data/Rakefile +8 -8
- data/TODO +6 -7
- data/bin/clevic +12 -73
- data/lib/clevic/action_builder.rb +168 -0
- data/lib/clevic/ar_methods.rb +120 -0
- data/lib/clevic/attribute_list.rb +56 -0
- data/lib/clevic/cache_table.rb +60 -37
- data/lib/clevic/default_view.rb +3 -16
- data/lib/clevic/delegate.rb +46 -0
- data/lib/clevic/emitter.rb +38 -0
- data/lib/clevic/extensions.rb +61 -114
- data/lib/clevic/field.rb +159 -228
- data/lib/clevic/field_valuer.rb +165 -0
- data/lib/clevic/filter_command.rb +2 -6
- data/lib/clevic/generic_format.rb +52 -0
- data/lib/clevic/{ui → icons}/icon.png +0 -0
- data/lib/clevic/many_field.rb +7 -0
- data/lib/clevic/model_builder.rb +234 -146
- data/lib/clevic/model_column.rb +61 -13
- data/lib/clevic/order_attribute.rb +10 -0
- data/lib/clevic/qt.rb +35 -0
- data/lib/clevic/qt/action_builder.rb +47 -0
- data/lib/clevic/qt/boolean_delegate.rb +8 -0
- data/lib/clevic/{browser.rb → qt/browser.rb} +35 -14
- data/lib/clevic/qt/clipboard.rb +35 -0
- data/lib/clevic/qt/combo_delegate.rb +198 -0
- data/lib/clevic/qt/delegates.rb +1 -0
- data/lib/clevic/qt/distinct_delegate.rb +35 -0
- data/lib/clevic/qt/extensions.rb +52 -0
- data/lib/clevic/qt/field.rb +18 -0
- data/lib/clevic/{item_delegate.rb → qt/item_delegate.rb} +8 -4
- data/lib/clevic/qt/relational_delegate.rb +87 -0
- data/lib/clevic/{search_dialog.rb → qt/search_dialog.rb} +1 -11
- data/lib/clevic/qt/set_delegate.rb +44 -0
- data/lib/clevic/qt/table_model.rb +331 -0
- data/lib/clevic/qt/table_view.rb +344 -0
- data/lib/clevic/qt/text_area_delegate.rb +8 -0
- data/lib/clevic/{text_delegate.rb → qt/text_delegate.rb} +6 -4
- data/lib/clevic/{ui → qt/ui}/.gitignore +0 -0
- data/lib/clevic/{ui → qt/ui}/browser.ui +0 -0
- data/lib/clevic/{ui → qt/ui}/search_dialog.ui +0 -0
- data/lib/clevic/rails_models_loaders.rb +56 -0
- data/lib/clevic/record.rb +2 -17
- data/lib/clevic/sampler.rb +81 -0
- data/lib/clevic/sequel_ar_adapter.rb +215 -0
- data/lib/clevic/sequel_length_validation.rb +23 -0
- data/lib/clevic/sequel_meta.rb +65 -0
- data/lib/clevic/sequel_naked.rb +30 -0
- data/lib/clevic/swing.rb +38 -0
- data/lib/clevic/swing/action.rb +125 -0
- data/lib/clevic/swing/action_builder.rb +47 -0
- data/lib/clevic/swing/boolean_delegate.rb +26 -0
- data/lib/clevic/swing/browser.rb +282 -0
- data/lib/clevic/swing/cell_editor.rb +95 -0
- data/lib/clevic/swing/cell_renderer.rb +44 -0
- data/lib/clevic/swing/clipboard.rb +135 -0
- data/lib/clevic/swing/combo_delegate.rb +336 -0
- data/lib/clevic/swing/confirm_dialog.rb +57 -0
- data/lib/clevic/swing/delegate.rb +40 -0
- data/lib/clevic/swing/distinct_delegate.rb +30 -0
- data/lib/clevic/swing/extensions.rb +274 -0
- data/lib/clevic/swing/field.rb +35 -0
- data/lib/clevic/swing/relational_delegate.rb +48 -0
- data/lib/clevic/swing/row_header.rb +210 -0
- data/lib/clevic/swing/search_dialog.rb +230 -0
- data/lib/clevic/swing/selection_model.rb +90 -0
- data/lib/clevic/swing/set_delegate.rb +41 -0
- data/lib/clevic/swing/swing_table_index.rb +43 -0
- data/lib/clevic/swing/table_model.rb +200 -0
- data/lib/clevic/swing/table_view.rb +385 -0
- data/lib/clevic/swing/table_view_focus.rb +47 -0
- data/lib/clevic/swing/tag_delegate.rb +127 -0
- data/lib/clevic/swing/tag_editor.rb +101 -0
- data/lib/clevic/swing/text_area_delegate.rb +46 -0
- data/lib/clevic/swing/text_delegate.rb +31 -0
- data/lib/clevic/swing/ui/build.xml +74 -0
- data/lib/clevic/swing/ui/dist/README.TXT +33 -0
- data/lib/clevic/swing/ui/dist/lib/swing-layout-1.0.3.jar +0 -0
- data/lib/clevic/swing/ui/manifest.mf +3 -0
- data/lib/clevic/swing/ui/nbproject/build-impl.xml +731 -0
- data/lib/clevic/swing/ui/nbproject/genfiles.properties +8 -0
- data/lib/clevic/swing/ui/nbproject/private/config.properties +0 -0
- data/lib/clevic/swing/ui/nbproject/private/private.properties +6 -0
- data/lib/clevic/swing/ui/nbproject/private/private.xml +4 -0
- data/lib/clevic/swing/ui/nbproject/project.properties +70 -0
- data/lib/clevic/swing/ui/nbproject/project.xml +14 -0
- data/lib/clevic/swing/ui/src/SearchDialog.form +158 -0
- data/lib/clevic/swing/ui/src/SearchDialog.java +163 -0
- data/lib/clevic/swing/ui/src/TagEditor.form +106 -0
- data/lib/clevic/swing/ui/src/TagEditor.java +108 -0
- data/lib/clevic/swing/ui/src/resources/SearchDialog.properties +0 -0
- data/lib/clevic/table_index.rb +100 -0
- data/lib/clevic/table_model.rb +54 -425
- data/lib/clevic/table_searcher.rb +113 -116
- data/lib/clevic/table_view.rb +171 -399
- data/lib/clevic/table_view_paste.rb +199 -0
- data/lib/clevic/version.rb +3 -2
- data/lib/clevic/view.rb +94 -43
- data/models/accounts_models.rb +13 -13
- data/models/minimal_models.rb +5 -9
- data/models/times_models.rb +19 -14
- data/models/times_psql_models.rb +10 -0
- data/models/times_sqlite_models.rb +1 -8
- data/models/values_models.rb +2 -8
- data/tasks/clevic.rake +1 -1
- data/tasks/rdoc.rake +1 -5
- data/tasks/website.rake +1 -1
- data/test/test_cache_table.rb +15 -29
- data/test/test_helper.rb +14 -83
- data/test/test_order_attribute.rb +1 -1
- data/test/test_table_model.rb +0 -21
- data/test/test_table_searcher.rb +67 -61
- metadata +262 -78
- data/lib/clevic.rb +0 -4
- data/lib/clevic/db_options.rb +0 -112
- data/lib/clevic/delegates.rb +0 -386
data/lib/clevic/default_view.rb
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
module Clevic
|
2
2
|
|
3
3
|
# A subclass of Clevic::DefaultView is created by Clevic::Record
|
4
|
-
# when the latter is included in
|
4
|
+
# when the latter is included in a Sequel::Model
|
5
|
+
# subclass.
|
5
6
|
#
|
6
7
|
# The Clevic::DefaultView subclass knows how to:
|
7
|
-
# - build a fairly sensible UI from the the
|
8
|
+
# - build a fairly sensible UI from the the ORM model metadata.
|
8
9
|
# - create a UI definition using a class method called define_ui.
|
9
10
|
#
|
10
11
|
# See Clevic::ModelBuilder for an example.
|
@@ -17,20 +18,6 @@ module Clevic
|
|
17
18
|
end
|
18
19
|
end
|
19
20
|
|
20
|
-
def self.define_ui_block( &block )
|
21
|
-
@define_ui_block ||= block
|
22
|
-
end
|
23
|
-
|
24
|
-
def define_ui
|
25
|
-
if self.class.define_ui_block.nil?
|
26
|
-
# use the define_ui from Clevic::View to build a default UI
|
27
|
-
super
|
28
|
-
else
|
29
|
-
# use the provided block
|
30
|
-
model_builder( &self.class.define_ui_block )
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
21
|
def title
|
35
22
|
@title ||= entity_class.name.demodulize.tableize.humanize
|
36
23
|
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'clevic/field_valuer.rb'
|
2
|
+
|
3
|
+
module Clevic
|
4
|
+
|
5
|
+
# This has both a field and an entity, so it's a perfect candidate
|
6
|
+
# for including FieldValuer, which it does.
|
7
|
+
class Delegate
|
8
|
+
include FieldValuer
|
9
|
+
|
10
|
+
def initialize( field )
|
11
|
+
@field = field
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_accessor :entity, :parent
|
15
|
+
|
16
|
+
attr_reader :field
|
17
|
+
def attribute
|
18
|
+
field.attribute
|
19
|
+
end
|
20
|
+
|
21
|
+
def entity_class
|
22
|
+
field.entity_class
|
23
|
+
end
|
24
|
+
|
25
|
+
def find_options
|
26
|
+
field.find_options
|
27
|
+
end
|
28
|
+
|
29
|
+
def is_combo?
|
30
|
+
false
|
31
|
+
end
|
32
|
+
|
33
|
+
# change the visual state of the editor to the biggest / most
|
34
|
+
# space-consuming it can be. This grew out of combo boxes having
|
35
|
+
# a drop-down that can show or hide.
|
36
|
+
def full_edit
|
37
|
+
end
|
38
|
+
|
39
|
+
# change the visual state of the editor to the smallest / least
|
40
|
+
# space-consuming it can be. This grew out of combo boxes having
|
41
|
+
# a drop-down that can show or hide.
|
42
|
+
def minimal_edit
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module Clevic
|
4
|
+
# This is a quickie emitter for Swing mostly. Qt has this built in.
|
5
|
+
module Emitter
|
6
|
+
def self.included( base )
|
7
|
+
base.extend( ClassMethods )
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
def emitter( emitter_name )
|
12
|
+
line, st = __LINE__, <<-EOF
|
13
|
+
def #{emitter_name}_listeners
|
14
|
+
@#{emitter_name}_listeners ||= Set.new
|
15
|
+
end
|
16
|
+
|
17
|
+
# If msg is provided, yield to stored block.
|
18
|
+
# If block is provided, store it for later.
|
19
|
+
def emit_#{emitter_name}( *args, ¬ifier_block )
|
20
|
+
if block_given?
|
21
|
+
#{emitter_name}_listeners << notifier_block
|
22
|
+
else
|
23
|
+
puts "emit_#{emitter_name} called with " + args.inspect
|
24
|
+
#{emitter_name}_listeners.each do |notify|
|
25
|
+
notify.call( *args )
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def remove_#{emitter_name}( ¬ifier_block )
|
31
|
+
#{emitter_name}_listeners.delete( notifier_block )
|
32
|
+
end
|
33
|
+
EOF
|
34
|
+
class_eval st, __FILE__, line + 1
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/lib/clevic/extensions.rb
CHANGED
@@ -1,8 +1,5 @@
|
|
1
1
|
# extensions specific to clevic
|
2
2
|
|
3
|
-
require 'qtext/flags.rb'
|
4
|
-
require 'qtext/hash_collector.rb'
|
5
|
-
|
6
3
|
class Object
|
7
4
|
# recursively calls each entry in path_ary
|
8
5
|
# will return nil if any entry in path_ary
|
@@ -12,128 +9,78 @@ class Object
|
|
12
9
|
value.nil? ? nil : value.send( att )
|
13
10
|
end
|
14
11
|
end
|
12
|
+
|
13
|
+
# pass self to the block and return the results of the block.
|
14
|
+
def with( &block )
|
15
|
+
yield( self )
|
16
|
+
end
|
15
17
|
end
|
16
18
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
true
|
26
|
-
else
|
27
|
-
false
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
def self.attribute_names
|
32
|
-
( column_names + reflections.keys.map {|sym| sym.to_s} ).sort
|
19
|
+
class String
|
20
|
+
# just grab the character code of the last character in the string
|
21
|
+
# TODO this won't work in unicode or utf-8
|
22
|
+
def to_char
|
23
|
+
if RUBY_VERSION <= '1.8.6'
|
24
|
+
self[0]
|
25
|
+
else
|
26
|
+
bytes.first
|
33
27
|
end
|
34
28
|
end
|
35
29
|
end
|
36
30
|
|
37
|
-
|
38
|
-
|
31
|
+
class Array
|
32
|
+
def sparse_hash
|
33
|
+
Hash[ *(first..last).map do |index|
|
34
|
+
[index, include?( index ) ]
|
35
|
+
end.flatten ]
|
36
|
+
end
|
39
37
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
# overridden in EntryDelegate subclasses
|
44
|
-
def full_edit
|
38
|
+
def sparse
|
39
|
+
(first..last).map do |index|
|
40
|
+
index if include?( index )
|
45
41
|
end
|
46
42
|
end
|
47
43
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
metadata: #{metadata.inspect}
|
68
|
-
EOF
|
69
|
-
else
|
70
|
-
'invalid'
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
# return the attribute of the underlying entity corresponding
|
75
|
-
# to the column of this index
|
76
|
-
def attribute
|
77
|
-
model.attributes[column]
|
78
|
-
end
|
79
|
-
|
80
|
-
# fetch the value of the attribute, without following
|
81
|
-
# the full path. This will return a related entity for
|
82
|
-
# belongs_to or has_one relationships, or a plain value
|
83
|
-
# for model attributes
|
84
|
-
def attribute_value
|
85
|
-
entity.send( attribute )
|
86
|
-
end
|
87
|
-
|
88
|
-
# set the value of the attribute, without following the
|
89
|
-
# full path.
|
90
|
-
# TODO remove need to constantly recalculate the attribute writer
|
91
|
-
def attribute_value=( obj )
|
92
|
-
entity.send( "#{attribute.to_s}=", obj )
|
93
|
-
end
|
94
|
-
|
95
|
-
# returns the ActiveRecord column_for_attribute
|
96
|
-
def metadata
|
97
|
-
# use the optimised version
|
98
|
-
model.metadata( column )
|
99
|
-
end
|
100
|
-
|
101
|
-
# return the table's field name. For associations, this would
|
102
|
-
# be suffixed with _id
|
103
|
-
def field_name
|
104
|
-
metadata.name
|
105
|
-
end
|
106
|
-
|
107
|
-
# return the value of the field, it may be the _id value
|
108
|
-
def field_value
|
109
|
-
entity.send( field_name )
|
110
|
-
end
|
111
|
-
|
112
|
-
# the underlying entity
|
113
|
-
def entity
|
114
|
-
return nil if model.nil?
|
115
|
-
@entity ||= model.collection[row]
|
116
|
-
end
|
117
|
-
|
118
|
-
attr_writer :entity
|
119
|
-
|
120
|
-
# return true if validation failed for this indexes field
|
121
|
-
def has_errors?
|
122
|
-
# virtual fields don't have metadata
|
123
|
-
if metadata.nil?
|
124
|
-
false
|
125
|
-
else
|
126
|
-
entity.errors.invalid?( field_name.to_sym )
|
127
|
-
end
|
128
|
-
end
|
129
|
-
|
130
|
-
# return a collection of errors. Unlike AR, this
|
131
|
-
# will always return an array that will have zero, one
|
132
|
-
# or many elements.
|
133
|
-
def errors
|
134
|
-
[ entity.errors[field_name.to_sym] ].flatten
|
44
|
+
def section
|
45
|
+
return [] if empty?
|
46
|
+
rv = [first]
|
47
|
+
self[1..-1].each_with_index do |next_value, index|
|
48
|
+
break if rv.last.succ != next_value
|
49
|
+
rv << next_value
|
50
|
+
end
|
51
|
+
rv
|
52
|
+
end
|
53
|
+
|
54
|
+
# group by ascending values
|
55
|
+
def group
|
56
|
+
parts = []
|
57
|
+
next_section = section
|
58
|
+
if next_section.empty?
|
59
|
+
parts
|
60
|
+
else
|
61
|
+
parts << section
|
62
|
+
parts + self[section.size..-1].group
|
135
63
|
end
|
136
|
-
|
137
64
|
end
|
65
|
+
|
66
|
+
def range
|
67
|
+
first..last
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def Range
|
72
|
+
def distance
|
73
|
+
last - first
|
74
|
+
end
|
75
|
+
end
|
138
76
|
|
77
|
+
# workaround for the date freeze issue, if it exists
|
78
|
+
begin
|
79
|
+
Date.new.freeze.to_s
|
80
|
+
rescue TypeError
|
81
|
+
class Date
|
82
|
+
def freeze
|
83
|
+
self
|
84
|
+
end
|
85
|
+
end
|
139
86
|
end
|
data/lib/clevic/field.rb
CHANGED
@@ -1,12 +1,17 @@
|
|
1
|
-
require 'gather
|
1
|
+
require 'gather'
|
2
|
+
require 'clevic/sampler.rb'
|
3
|
+
require 'clevic/generic_format.rb'
|
2
4
|
|
3
5
|
module Clevic
|
4
6
|
|
5
7
|
=begin rdoc
|
6
8
|
This defines a field in the UI, and how it hooks up to a field in the DB.
|
7
9
|
|
8
|
-
|
9
|
-
set with either an assignment or by passing a parameter.
|
10
|
+
Many attributes are DSL-style accessors, where the value can be
|
11
|
+
set with either an assignment or by passing a parameter. Unfortunately
|
12
|
+
rdoc seems to have lost the ability to display these nicely. Anyway, here's
|
13
|
+
an example
|
14
|
+
|
10
15
|
property :ixnay
|
11
16
|
|
12
17
|
will allow
|
@@ -14,12 +19,17 @@ will allow
|
|
14
19
|
# reader
|
15
20
|
instance.ixnay
|
16
21
|
|
17
|
-
#writer
|
22
|
+
# writer
|
18
23
|
instance.ixnay = 'nix, baby'
|
19
24
|
|
20
|
-
#writer
|
25
|
+
# writer
|
21
26
|
instance.ixnay 'nix baby'
|
22
27
|
|
28
|
+
# store the block for later
|
29
|
+
instance.ixnay do |*args|
|
30
|
+
# block stuff here
|
31
|
+
end
|
32
|
+
|
23
33
|
Generally properties are for options that can be passed to the field creation
|
24
34
|
method in ModelBuilder, whereas ruby attributes are for the internal workings.
|
25
35
|
|
@@ -27,14 +37,22 @@ method in ModelBuilder, whereas ruby attributes are for the internal workings.
|
|
27
37
|
TODO decide whether value_for type methods take an entity and do_something methods
|
28
38
|
take a value.
|
29
39
|
|
40
|
+
TODO the xxx_for methods are in here because their return values don't change
|
41
|
+
by entity. Well, maybe sometimes they do. Anyway, need to find a better location
|
42
|
+
for these and a better caching strategy.
|
43
|
+
|
30
44
|
TODO this class is a bit confused about whether it handles metadata or record data, or both.
|
31
45
|
|
32
|
-
TODO meta needs to handle virtual fields better.
|
46
|
+
TODO meta needs to handle virtual fields better.
|
33
47
|
=end
|
34
48
|
class Field
|
35
49
|
# For defining properties
|
36
50
|
include Gather
|
37
51
|
|
52
|
+
# for formatting values
|
53
|
+
include GenericFormat
|
54
|
+
|
55
|
+
##
|
38
56
|
# The value to be displayed after being optionally format-ed
|
39
57
|
#
|
40
58
|
# Takes a String, a Symbol, or a Proc.
|
@@ -50,21 +68,21 @@ class Field
|
|
50
68
|
# Defaults to nil, in other words the value of the attribute for this field.
|
51
69
|
property :display
|
52
70
|
|
71
|
+
##
|
53
72
|
# The label to be displayed in the column headings. Defaults to the humanised field name.
|
54
73
|
property :label
|
55
74
|
|
56
|
-
|
57
|
-
# TODO not used anymore?
|
58
|
-
property :class_name
|
59
|
-
|
75
|
+
##
|
60
76
|
# One of the alignment specifiers - :left, :centre, :right or :justified.
|
61
77
|
# Defaults to right for numeric fields, centre for boolean, and left for
|
62
78
|
# other values.
|
63
79
|
property :alignment
|
64
80
|
|
81
|
+
##
|
65
82
|
# something to do with the icon that Qt displays. Not implemented yet.
|
66
83
|
property :decoration
|
67
84
|
|
85
|
+
##
|
68
86
|
# This defines how to format the value returned by :display. It takes a string or a Proc.
|
69
87
|
# Generally the string is something
|
70
88
|
# that can be understood by strftime (for time and date fields) or understood
|
@@ -72,15 +90,18 @@ class Field
|
|
72
90
|
# the current entity. There are sensible defaults for common field types.
|
73
91
|
property :format
|
74
92
|
|
93
|
+
##
|
75
94
|
# This is just like format, except that it's used to format the value just
|
76
95
|
# before it's edited. A good use of this is to display dates with a 2-digit year
|
77
96
|
# but edit them with a 4 digit year.
|
78
97
|
# Defaults to a sensible value for some fields, for others it will default to the value of :format.
|
79
98
|
property :edit_format
|
80
99
|
|
100
|
+
##
|
81
101
|
# Whether the field is currently visible or not.
|
82
102
|
property :visible
|
83
103
|
|
104
|
+
##
|
84
105
|
# Sample is used if the programmer wishes to provide a value (that will be converted
|
85
106
|
# using to_s) that can be used
|
86
107
|
# as the basis for calculating the width of the field. By default this will be
|
@@ -89,9 +110,11 @@ class Field
|
|
89
110
|
# have the option to override that if we wish.
|
90
111
|
property :sample
|
91
112
|
|
113
|
+
##
|
92
114
|
# Takes a boolean. Set the field to read-only.
|
93
115
|
property :read_only
|
94
116
|
|
117
|
+
##
|
95
118
|
# The foreground and background colors.
|
96
119
|
# Can take a Proc, a string, or a symbol.
|
97
120
|
# - A Proc is called with an entity
|
@@ -102,44 +125,54 @@ class Field
|
|
102
125
|
# http://www.w3.org/TR/SVG/types.html#ColorKeywords.
|
103
126
|
property :foreground, :background
|
104
127
|
|
128
|
+
##
|
105
129
|
# Can take a Proc, a string, or a symbol.
|
106
130
|
# - A Proc is called with an entity
|
107
131
|
# - A String is treated as a constant
|
108
132
|
# - A symbol is treated as a method to be call on an entity
|
109
133
|
property :tooltip
|
110
134
|
|
135
|
+
##
|
111
136
|
# An Enumerable of allowed values for restricted fields. If each yields
|
112
137
|
# two values (like it does for a Hash), the
|
113
138
|
# first will be stored in the db, and the second displayed in the UI.
|
114
139
|
# If it's a proc, it must return an Enumerable as above.
|
115
140
|
property :set
|
116
141
|
|
142
|
+
##
|
117
143
|
# When this is true, only the values in the combo may be entered.
|
118
144
|
# Otherwise the text-entry part of the combo can be used to enter
|
119
145
|
# non-listed values. Default is true if a set is explicitly specified.
|
120
146
|
# Otherwise depends on the field type.
|
121
147
|
property :restricted
|
122
148
|
|
149
|
+
##
|
123
150
|
# Only for the distinct field type. The values will be sorted either with the
|
124
|
-
# most used values first (:frequency => true) or in
|
151
|
+
# most used values first (:frequency => true) or in
|
152
|
+
# alphabetical order (:description => true).
|
125
153
|
property :frequency, :description
|
126
154
|
|
155
|
+
##
|
127
156
|
# Default value for this field for new records.
|
128
157
|
# Can be a Proc or a value. A value will just be
|
129
158
|
# set, a proc will be executed with the entity as a parameter.
|
130
159
|
property :default
|
131
160
|
|
132
|
-
|
133
|
-
#
|
161
|
+
##
|
162
|
+
# The property used for finding the field, ie by TableModel#field_column.
|
163
|
+
# Defaults to the attribute.
|
134
164
|
property :id
|
135
165
|
|
136
|
-
|
137
|
-
#
|
166
|
+
##
|
167
|
+
# Called when the data in this field changes.
|
168
|
+
# Either a proc( clevic_view, table_view, model_index ) or a symbol
|
169
|
+
# for a method( view, model_index ) on the Clevic::View object.
|
138
170
|
property :notify_data_changed
|
139
171
|
|
140
|
-
# properties for ActiveRecord options
|
141
|
-
# There are actually from ActiveRecord::Base.VALID_FIND_OPTIONS, but it's protected
|
142
|
-
#
|
172
|
+
# The list of properties for ActiveRecord options.
|
173
|
+
# There are actually from ActiveRecord::Base.VALID_FIND_OPTIONS, but it's protected.
|
174
|
+
# Each element becomes a property.
|
175
|
+
# TODO remove these? That will destroy the migration path.
|
143
176
|
AR_FIND_OPTIONS = [ :conditions, :include, :joins, :limit, :offset, :order, :select, :readonly, :group, :from, :lock ]
|
144
177
|
AR_FIND_OPTIONS.each{|x| property x}
|
145
178
|
|
@@ -155,7 +188,8 @@ class Field
|
|
155
188
|
end
|
156
189
|
end
|
157
190
|
|
158
|
-
# The UI delegate class for the field.
|
191
|
+
# The UI delegate class for the field. The delegate class knows how to create a UI
|
192
|
+
# for this field using whatever GUI toolkit is selected
|
159
193
|
attr_accessor :delegate
|
160
194
|
|
161
195
|
# The attribute on the AR entity that forms the basis for this field.
|
@@ -165,13 +199,13 @@ class Field
|
|
165
199
|
# would normally have an _id suffix for relationships.
|
166
200
|
attr_accessor :attribute
|
167
201
|
|
168
|
-
# The
|
202
|
+
# The Object Relational Model this field uses to get data from.
|
169
203
|
attr_reader :entity_class
|
170
204
|
|
171
205
|
# Create a new Field object that displays the contents of a database field in
|
172
206
|
# the UI using the given parameters.
|
173
207
|
# - attribute is the symbol for the attribute on the entity_class.
|
174
|
-
# - entity_class is the
|
208
|
+
# - entity_class is the Object Relational Model which this Field talks to.
|
175
209
|
# - options is a hash of writable attributes in Field, which can be any of the properties defined in this class.
|
176
210
|
def initialize( attribute, entity_class, options, &block )
|
177
211
|
# sanity checking
|
@@ -179,11 +213,7 @@ class Field
|
|
179
213
|
raise "attribute #{attribute.inspect} must be a symbol"
|
180
214
|
end
|
181
215
|
|
182
|
-
unless entity_class.
|
183
|
-
raise "entity_class must be a descendant of ActiveRecord::Base"
|
184
|
-
end
|
185
|
-
|
186
|
-
unless entity_class.has_attribute?( attribute ) or entity_class.instance_methods.include?( attribute.to_s )
|
216
|
+
unless ( entity_class.is_a?( Clevic.base_entity_class ) and entity_class.has_attribute?( attribute ) ) or entity_class.instance_methods.include?( attribute.to_s )
|
187
217
|
msg = <<EOF
|
188
218
|
#{attribute} not found in #{entity_class.name}. Possibilities are:
|
189
219
|
#{entity_class.attribute_names.join("\n")}
|
@@ -193,7 +223,7 @@ EOF
|
|
193
223
|
|
194
224
|
# instance variables
|
195
225
|
@attribute = attribute
|
196
|
-
# default to attribute
|
226
|
+
# default to attribute, can be overwritten later
|
197
227
|
@id = attribute
|
198
228
|
@entity_class = entity_class
|
199
229
|
@visible = true
|
@@ -210,9 +240,40 @@ EOF
|
|
210
240
|
default_format!
|
211
241
|
default_edit_format!
|
212
242
|
default_alignment!
|
243
|
+
default_display! if association?
|
244
|
+
end
|
245
|
+
|
246
|
+
# x_to_many fields are by definition collections of other entities
|
247
|
+
def many( &block )
|
248
|
+
if block
|
249
|
+
many_view( &block )
|
250
|
+
else
|
251
|
+
many_view do |mb|
|
252
|
+
# TODO should fetch this from one of the field definitions
|
253
|
+
mb.plain related_attribute
|
254
|
+
end
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
def many_builder
|
259
|
+
@many_view.builder
|
213
260
|
end
|
214
261
|
|
215
|
-
|
262
|
+
def many_fields
|
263
|
+
many_builder.fields
|
264
|
+
end
|
265
|
+
|
266
|
+
# return an instance of Clevic::View that represents the many items
|
267
|
+
# for this field
|
268
|
+
def many_view( &block )
|
269
|
+
@many_view ||= View.new( :entity_class => related_class, &block )
|
270
|
+
end
|
271
|
+
|
272
|
+
# The model object (eg TableModel) this field is part of.
|
273
|
+
# Set to TableModel by ModelBuilder#build
|
274
|
+
attr_accessor :model
|
275
|
+
|
276
|
+
# Return the attribute value for the given Object Relational Model instance, or nil
|
216
277
|
# if entity is nil. Will call transform_attribute.
|
217
278
|
def value_for( entity )
|
218
279
|
begin
|
@@ -224,7 +285,7 @@ EOF
|
|
224
285
|
end
|
225
286
|
end
|
226
287
|
|
227
|
-
# Apply display
|
288
|
+
# Apply the value of the display property to the given
|
228
289
|
# attribute value. Otherwise just return the
|
229
290
|
# attribute_value itself.
|
230
291
|
def transform_attribute( attribute_value )
|
@@ -245,44 +306,20 @@ EOF
|
|
245
306
|
end
|
246
307
|
|
247
308
|
# return true if this is a field for a related table, false otherwise.
|
248
|
-
def
|
249
|
-
meta.
|
309
|
+
def association?
|
310
|
+
meta.andand.association?
|
250
311
|
end
|
251
312
|
|
252
|
-
#
|
253
|
-
# If display is nil, the value is calculated, so we need
|
254
|
-
# to check the value. Otherwise use the field metadata.
|
255
|
-
# Cache the result for the first non-nil value.
|
256
|
-
def is_date_time?( value )
|
257
|
-
if value.nil?
|
258
|
-
false
|
259
|
-
else
|
260
|
-
@is_date_time ||=
|
261
|
-
if display.nil?
|
262
|
-
[:time, :date, :datetime, :timestamp].include?( meta.type )
|
263
|
-
else
|
264
|
-
# it's a virtual field, so we need to use the value
|
265
|
-
value.is_a?( Date ) || value.is_a?( Time )
|
266
|
-
end
|
267
|
-
end
|
268
|
-
end
|
269
|
-
|
270
|
-
# return ActiveRecord::Base.columns_hash[attribute]
|
271
|
-
# in other words an ActiveRecord::ConnectionAdapters::Column object,
|
272
|
-
# or an ActiveRecord::Reflection::AssociationReflection object
|
313
|
+
# ModelColumn object
|
273
314
|
def meta
|
274
|
-
|
315
|
+
entity_class.meta[attribute]
|
275
316
|
end
|
276
317
|
|
277
318
|
# return the type of this attribute. Usually one of :string, :integer, :float
|
278
|
-
# or some entity class
|
319
|
+
# or some entity class
|
320
|
+
# TODO remove
|
279
321
|
def attribute_type
|
280
|
-
|
281
|
-
if meta.kind_of?( ActiveRecord::Reflection::MacroReflection )
|
282
|
-
meta.klass
|
283
|
-
else
|
284
|
-
meta.type
|
285
|
-
end
|
322
|
+
meta.type
|
286
323
|
end
|
287
324
|
|
288
325
|
# return true if this field can be used in a filter
|
@@ -292,21 +329,18 @@ EOF
|
|
292
329
|
!meta.nil?
|
293
330
|
end
|
294
331
|
|
295
|
-
# Return the name of the database field for this Field, quoted for the dbms.
|
296
|
-
def quoted_field
|
297
|
-
quote_field( meta.name )
|
298
|
-
end
|
299
|
-
|
300
|
-
# Quote the given string as a field name for SQL.
|
301
|
-
def quote_field( field_name )
|
302
|
-
@entity_class.connection.quote_column_name( field_name )
|
303
|
-
end
|
304
|
-
|
305
332
|
# return the result of the attribute + the path
|
306
333
|
def column
|
307
334
|
[attribute.to_s, path].compact.join('.')
|
308
335
|
end
|
309
336
|
|
337
|
+
# return the class object of a related class if this is a relational
|
338
|
+
# field, otherwise nil
|
339
|
+
def related_class
|
340
|
+
return nil unless entity_class.meta.has_key?( attribute )
|
341
|
+
@related_class ||= eval( entity_class.meta[attribute].class_name || attribute.to_s.classify )
|
342
|
+
end
|
343
|
+
|
310
344
|
# return an array of the various attribute parts
|
311
345
|
def attribute_path
|
312
346
|
pieces = [ attribute.to_s ]
|
@@ -319,77 +353,40 @@ EOF
|
|
319
353
|
@read_only || false
|
320
354
|
end
|
321
355
|
|
322
|
-
#
|
323
|
-
# If format is a proc, pass value to it.
|
324
|
-
def do_generic_format( format, value )
|
325
|
-
begin
|
326
|
-
unless format.nil?
|
327
|
-
if format.is_a? Proc
|
328
|
-
format.call( value )
|
329
|
-
else
|
330
|
-
if is_date_time?( value )
|
331
|
-
value.strftime( format )
|
332
|
-
else
|
333
|
-
format % value
|
334
|
-
end
|
335
|
-
end
|
336
|
-
else
|
337
|
-
value
|
338
|
-
end
|
339
|
-
rescue Exception => e
|
340
|
-
puts "format: #{format.inspect}"
|
341
|
-
puts "value.class: #{value.class.inspect}"
|
342
|
-
puts "value: #{value.inspect}"
|
343
|
-
puts e.message
|
344
|
-
puts e.backtrace
|
345
|
-
nil
|
346
|
-
end
|
347
|
-
end
|
348
|
-
|
356
|
+
# Called by Clevic::Model to format the display value.
|
349
357
|
def do_format( value )
|
350
358
|
do_generic_format( format, value )
|
351
359
|
end
|
352
360
|
|
361
|
+
# Called by Clevic::Model to format the edit value.
|
353
362
|
def do_edit_format( value )
|
354
363
|
do_generic_format( edit_format, value )
|
355
364
|
end
|
356
365
|
|
357
|
-
#
|
366
|
+
# Return a sample for the field which can be used to size the UI field widget.
|
358
367
|
def sample( *args )
|
359
368
|
if !args.empty?
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
case meta.type
|
367
|
-
# max width of 40 chars
|
368
|
-
when :string, :text
|
369
|
-
string_sample( 'n'*40 )
|
370
|
-
|
371
|
-
when :date, :time, :datetime, :timestamp
|
372
|
-
date_time_sample
|
373
|
-
|
374
|
-
when :numeric, :decimal, :integer, :float
|
375
|
-
numeric_sample
|
376
|
-
|
377
|
-
# TODO return a width, or something like that
|
378
|
-
when :boolean; 'W'
|
379
|
-
|
380
|
-
when ActiveRecord::Reflection::AssociationReflection.class
|
381
|
-
related_sample
|
382
|
-
|
369
|
+
@sample = args.first
|
370
|
+
self
|
371
|
+
else
|
372
|
+
if @sample.nil?
|
373
|
+
if meta.type == :boolean
|
374
|
+
@sample = self.label
|
383
375
|
else
|
384
|
-
|
376
|
+
begin
|
377
|
+
@sample ||= Sampler.new( entity_class, attribute, display ) do |value|
|
378
|
+
do_format( value )
|
379
|
+
end.compute
|
380
|
+
rescue
|
381
|
+
puts $!
|
382
|
+
ensure
|
383
|
+
# if we don't know how to figure it out from the data, just return the label size
|
384
|
+
@sample ||= self.label
|
385
|
+
end
|
386
|
+
end
|
385
387
|
end
|
386
|
-
|
387
|
-
#~ if $options && $options[:debug]
|
388
|
-
#~ puts "@sample for #{@entity_class.name}.#{attribute} #{meta.type}: #{@sample.inspect}"
|
389
|
-
#~ end
|
388
|
+
@sample
|
390
389
|
end
|
391
|
-
# if we don't know how to figure it out from the data, just return the label size
|
392
|
-
@sample || self.label
|
393
390
|
end
|
394
391
|
|
395
392
|
# Called by Clevic::TableModel to get the tooltip value
|
@@ -401,17 +398,6 @@ EOF
|
|
401
398
|
def decoration_for( entity )
|
402
399
|
nil
|
403
400
|
end
|
404
|
-
|
405
|
-
# Convert something that responds to to_s to a Qt::Color,
|
406
|
-
# or just return the argument if it's already a Qt::Color
|
407
|
-
def string_or_color( s_or_c )
|
408
|
-
case s_or_c
|
409
|
-
when Qt::Color
|
410
|
-
s_or_c
|
411
|
-
else
|
412
|
-
Qt::Color.new( s_or_c.to_s )
|
413
|
-
end
|
414
|
-
end
|
415
401
|
|
416
402
|
# Called by Clevic::TableModel to get the foreground color value
|
417
403
|
def foreground_for( entity )
|
@@ -423,6 +409,8 @@ EOF
|
|
423
409
|
cache_value_for( :background, entity ) {|x| string_or_color(x)}
|
424
410
|
end
|
425
411
|
|
412
|
+
# called when a new entity object is created to set default values
|
413
|
+
# specified by the default property.
|
426
414
|
def set_default_for( entity )
|
427
415
|
begin
|
428
416
|
entity[attribute] =
|
@@ -438,6 +426,7 @@ EOF
|
|
438
426
|
end
|
439
427
|
end
|
440
428
|
|
429
|
+
# fetch the permitted set of values for a restricted field.
|
441
430
|
def set_for( entity )
|
442
431
|
case set
|
443
432
|
when Proc
|
@@ -453,6 +442,10 @@ EOF
|
|
453
442
|
end
|
454
443
|
end
|
455
444
|
|
445
|
+
def inspect
|
446
|
+
"#<Clevic::Field id=#{id.inspect}>"
|
447
|
+
end
|
448
|
+
|
456
449
|
protected
|
457
450
|
|
458
451
|
# call the conversion_block with the value, or just return the
|
@@ -481,111 +474,49 @@ protected
|
|
481
474
|
end
|
482
475
|
end
|
483
476
|
|
477
|
+
# the label if it's not defined. Based on the attribute.
|
484
478
|
def default_label!
|
485
479
|
@label ||= attribute.to_s.humanize
|
486
480
|
end
|
487
481
|
|
482
|
+
# sensible display format defaults if they're not defined.
|
488
483
|
def default_format!
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
when :decimal, :float; "%.2f"
|
496
|
-
end
|
484
|
+
@format ||=
|
485
|
+
case meta.type
|
486
|
+
when :time; '%H:%M'
|
487
|
+
when :date; '%d-%h-%y'
|
488
|
+
when :datetime; '%d-%h-%y %H:%M:%S'
|
489
|
+
when :decimal, :float; "%.2f"
|
497
490
|
end
|
498
|
-
@format
|
499
491
|
end
|
500
492
|
|
493
|
+
# sensible edit format defaults if they're not defined.
|
501
494
|
def default_edit_format!
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
end || default_format!
|
508
|
-
end
|
509
|
-
@edit_format
|
495
|
+
@edit_format ||=
|
496
|
+
case meta.type
|
497
|
+
when :date; '%d-%h-%Y'
|
498
|
+
when :datetime; '%d-%h-%Y %H:%M:%S'
|
499
|
+
end || default_format!
|
510
500
|
end
|
511
501
|
|
502
|
+
# sensible alignment defaults if they're not defined.
|
512
503
|
def default_alignment!
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
end
|
504
|
+
@alignment ||=
|
505
|
+
case meta.type
|
506
|
+
when :decimal, :integer, :float; :right
|
507
|
+
when :boolean; :centre
|
508
|
+
else :left
|
519
509
|
end
|
520
510
|
end
|
521
511
|
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
end
|
529
|
-
end
|
530
|
-
|
531
|
-
def string_sample( max_sample = nil, entity_class = @entity_class, field_name = meta.name )
|
532
|
-
statement = <<-EOF
|
533
|
-
select distinct #{quote_field field_name}
|
534
|
-
from #{entity_class.table_name}
|
535
|
-
where
|
536
|
-
length( #{quote_field field_name} ) = (
|
537
|
-
select max( length( #{quote_field field_name} ) )
|
538
|
-
from #{entity_class.table_name}
|
539
|
-
)
|
540
|
-
EOF
|
541
|
-
result_set = @entity_class.connection.execute statement
|
542
|
-
unless result_set.entries.size == 0
|
543
|
-
row = result_set[0]
|
544
|
-
result =
|
545
|
-
case row
|
546
|
-
when Array
|
547
|
-
row[0]
|
548
|
-
when Hash
|
549
|
-
row.values[0]
|
550
|
-
end
|
551
|
-
|
552
|
-
if max_sample.nil?
|
553
|
-
result
|
554
|
-
else
|
555
|
-
result.length < max_sample.length ? result : max_sample
|
556
|
-
end
|
557
|
-
end
|
512
|
+
# try to find a sensible display method
|
513
|
+
def default_display!
|
514
|
+
candidates = %W{#{entity_class.name.downcase} name title username to_s}
|
515
|
+
@display ||= candidates.find do |m|
|
516
|
+
related_class.column_names.include?( m ) || related_class.instance_methods.include?( m )
|
517
|
+
end || raise( "Can't find one of #{candidates.inspect} in #{related_class.name}" )
|
558
518
|
end
|
559
|
-
|
560
|
-
def date_time_sample
|
561
|
-
result_set = @entity_class.find_by_sql <<-EOF
|
562
|
-
select #{quoted_field}
|
563
|
-
from #{@entity_class.table_name}
|
564
|
-
where #{quoted_field} is not null
|
565
|
-
limit 1
|
566
|
-
EOF
|
567
|
-
format_result( result_set )
|
568
|
-
end
|
569
|
-
|
570
|
-
def numeric_sample
|
571
|
-
# TODO Use precision from metadata, not for integers
|
572
|
-
# returns nil for floats. So it's probably not useful
|
573
|
-
#~ puts "meta.precision: #{meta.precision.inspect}"
|
574
|
-
result_set = @entity_class.find_by_sql <<-EOF
|
575
|
-
select max( #{quoted_field} )
|
576
|
-
from #{@entity_class.table_name}
|
577
|
-
EOF
|
578
|
-
format_result( result_set )
|
579
|
-
end
|
580
|
-
|
581
|
-
def related_sample
|
582
|
-
# TODO this isn't really the right way to do this
|
583
|
-
return nil if meta.nil?
|
584
|
-
if meta.klass.attribute_names.include?( attribute_path[1].to_s )
|
585
|
-
string_sample( nil, meta.klass, attribute_path[1] )
|
586
|
-
end
|
587
|
-
end
|
588
|
-
|
519
|
+
|
589
520
|
end
|
590
521
|
|
591
522
|
end
|