clevic 0.8.0 → 0.11.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
@@ -0,0 +1,53 @@
|
|
1
|
+
=begin rdoc
|
2
|
+
Store the SQL order_by attributes with ascending and descending values
|
3
|
+
=end
|
4
|
+
class OrderAttribute
|
5
|
+
attr_reader :direction, :attribute
|
6
|
+
|
7
|
+
def initialize( entity_class, sql_order_fragment )
|
8
|
+
@entity_class = entity_class
|
9
|
+
if sql_order_fragment =~ /^(.*?\.)?(.*?) *asc$/i
|
10
|
+
@direction = :asc
|
11
|
+
@attribute = $2
|
12
|
+
elsif sql_order_fragment =~ /^(.*?\.)?(.*?) *desc$/i
|
13
|
+
@direction = :desc
|
14
|
+
@attribute = $2
|
15
|
+
else
|
16
|
+
@direction = :asc
|
17
|
+
@attribute = sql_order_fragment
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# return ORDER BY field name
|
22
|
+
def to_s
|
23
|
+
@string ||= attribute
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_sym
|
27
|
+
@sym ||= attribute.to_sym
|
28
|
+
end
|
29
|
+
|
30
|
+
# return 'field ASC' or 'field DESC', depending
|
31
|
+
def to_sql
|
32
|
+
"#{@entity_class.table_name}.#{attribute} #{direction.to_s}"
|
33
|
+
end
|
34
|
+
|
35
|
+
def reverse( direction )
|
36
|
+
case direction
|
37
|
+
when :asc; :desc
|
38
|
+
when :desc; :asc
|
39
|
+
else; raise "unknown direction #{direction}"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# return the opposite ASC or DESC from to_sql
|
44
|
+
def to_reverse_sql
|
45
|
+
"#{@entity_class.table_name}.#{attribute} #{reverse(direction).to_s}"
|
46
|
+
end
|
47
|
+
|
48
|
+
def ==( other )
|
49
|
+
@entity_class == other.instance_eval( '@entity_class' ) and
|
50
|
+
self.direction == other.direction and
|
51
|
+
self.attribute == other.attribute
|
52
|
+
end
|
53
|
+
end
|
data/lib/clevic/record.rb
CHANGED
@@ -1,70 +1,70 @@
|
|
1
|
-
require '
|
2
|
-
require '
|
1
|
+
require 'clevic/view.rb'
|
2
|
+
require 'clevic/default_view.rb'
|
3
3
|
|
4
4
|
module Clevic
|
5
5
|
|
6
|
-
|
6
|
+
# include this in ActiveRecord::Base instances to
|
7
|
+
# get embedded view definitions. See ModelBuilder.
|
8
|
+
#
|
9
|
+
# A Clevic::Default#{model}View class will be created. If
|
10
|
+
# a define_ui block is not specified in the entity class,
|
11
|
+
# a default UI will be created.
|
12
|
+
module Record
|
13
|
+
# TODO not sure if these are necessary here anymore?
|
14
|
+
def self.db_options=( db_options )
|
15
|
+
@db_options = db_options
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.db_options
|
19
|
+
@db_options
|
20
|
+
end
|
21
|
+
|
7
22
|
module ClassMethods
|
8
|
-
def
|
9
|
-
|
10
|
-
|
11
|
-
|
23
|
+
def default_view_class_name
|
24
|
+
"::Clevic::Default#{name.gsub('::','')}View"
|
25
|
+
end
|
26
|
+
|
27
|
+
#TODO will have to fix modules here
|
28
|
+
def create_view_class
|
29
|
+
# create the default view class
|
30
|
+
# Don't use Class.new because even if you assign
|
31
|
+
# the result to a contstant, there are still anonymous classes
|
32
|
+
# hanging around, which gives weird results with Clevic::View.subclasses.
|
33
|
+
st,line = <<-EOF, __LINE__
|
34
|
+
class #{default_view_class_name} < Clevic::DefaultView
|
35
|
+
entity_class #{name}
|
36
|
+
end
|
37
|
+
EOF
|
38
|
+
eval st, nil, __FILE__, line
|
39
|
+
|
40
|
+
# keep track of the order in which views are
|
41
|
+
# defined, so that can be used as the default ordering
|
42
|
+
# of the views.
|
43
|
+
Clevic::View.order << default_view_class_name.constantize
|
12
44
|
end
|
13
45
|
|
14
|
-
def
|
15
|
-
@
|
46
|
+
def default_view_class
|
47
|
+
@default_view_class ||= eval default_view_class_name
|
16
48
|
end
|
49
|
+
|
50
|
+
# Need to defer the execution of the view definition block
|
51
|
+
# until related models have been defined.
|
52
|
+
def define_ui( &block )
|
53
|
+
default_view_class.define_ui_block( &block )
|
54
|
+
end
|
55
|
+
|
17
56
|
end
|
18
57
|
|
19
|
-
def self.included(base)
|
20
|
-
base.extend(ClassMethods)
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
end
|
25
|
-
|
26
|
-
module ActiveRecord
|
27
|
-
class Base
|
28
|
-
include Clevic::Default
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
module Clevic
|
58
|
+
def self.included( base )
|
59
|
+
base.extend( ClassMethods )
|
60
|
+
|
61
|
+
# create the default view class
|
62
|
+
base.create_view_class
|
33
63
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
# in which models are defined, so that tabs can
|
39
|
-
# be constructed in that order.
|
40
|
-
class Record < ActiveRecord::Base
|
41
|
-
include ActiveRecord::Dirty
|
42
|
-
self.abstract_class = true
|
43
|
-
@@subclass_order = []
|
44
|
-
|
45
|
-
def self.define_ui_block
|
46
|
-
@define_ui_block
|
47
|
-
end
|
48
|
-
|
49
|
-
# keep track of the order in which subclasses are
|
50
|
-
# defined, so that can be used as the default ordering
|
51
|
-
# of the views.
|
52
|
-
def self.inherited( subclass )
|
53
|
-
@@subclass_order << subclass
|
54
|
-
super
|
55
|
-
end
|
56
|
-
|
57
|
-
def self.models
|
58
|
-
@@subclass_order
|
59
|
-
end
|
60
|
-
|
61
|
-
def self.models=( array )
|
62
|
-
@@subclass_order = array
|
63
|
-
end
|
64
|
-
|
65
|
-
# use this to define UI blocks using the ModelBuilder DSL
|
66
|
-
def self.define_ui( &block )
|
67
|
-
@define_ui_block = block
|
64
|
+
# DbOptions instance
|
65
|
+
db_options = nil
|
66
|
+
found = ObjectSpace.each_object( Clevic::DbOptions ){|x| db_options = x}
|
67
|
+
@db_options = db_options
|
68
68
|
end
|
69
69
|
end
|
70
70
|
|
data/lib/clevic/search_dialog.rb
CHANGED
@@ -2,76 +2,80 @@ require 'Qt4'
|
|
2
2
|
require 'clevic/ui/search_dialog_ui.rb'
|
3
3
|
require 'qtext/flags.rb'
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
@layout = Ui_SearchDialog.new
|
11
|
-
@dialog = Qt::Dialog.new
|
12
|
-
@layout.setupUi( @dialog )
|
13
|
-
end
|
14
|
-
|
15
|
-
def from_start?
|
16
|
-
layout.from_start.value
|
17
|
-
end
|
18
|
-
|
19
|
-
def from_start=( value )
|
20
|
-
layout.from_start.value = value
|
21
|
-
end
|
22
|
-
|
23
|
-
def regex?
|
24
|
-
layout.regex.value
|
25
|
-
end
|
26
|
-
|
27
|
-
def whole_words?
|
28
|
-
layout.whole_words.value
|
29
|
-
end
|
30
|
-
|
31
|
-
def search_combo
|
32
|
-
layout.search_combo
|
33
|
-
end
|
34
|
-
|
35
|
-
def forwards?
|
36
|
-
@layout.forwards.checked?
|
37
|
-
end
|
38
|
-
|
39
|
-
def backwards?
|
40
|
-
@layout.backwards.checked?
|
41
|
-
end
|
42
|
-
|
43
|
-
# return either :backwards or :forwards
|
44
|
-
def direction
|
45
|
-
return :forwards if forwards?
|
46
|
-
return :backwards if backwards?
|
47
|
-
raise "direction not known"
|
48
|
-
end
|
49
|
-
|
50
|
-
def exec( text = '' )
|
51
|
-
search_combo.edit_text = text.to_s
|
52
|
-
search_combo.set_focus
|
53
|
-
retval = @dialog.exec
|
5
|
+
module Clevic
|
6
|
+
|
7
|
+
class SearchDialog
|
8
|
+
include QtFlags
|
9
|
+
attr_reader :match_flags, :layout
|
54
10
|
|
55
|
-
|
56
|
-
|
57
|
-
|
11
|
+
def initialize
|
12
|
+
@layout = Ui_SearchDialog.new
|
13
|
+
@dialog = Qt::Dialog.new
|
14
|
+
@layout.setupUi( @dialog )
|
58
15
|
end
|
59
16
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
17
|
+
def from_start?
|
18
|
+
layout.from_start.value
|
19
|
+
end
|
20
|
+
|
21
|
+
def from_start=( value )
|
22
|
+
layout.from_start.value = value
|
23
|
+
end
|
24
|
+
|
25
|
+
def regex?
|
26
|
+
layout.regex.value
|
27
|
+
end
|
28
|
+
|
29
|
+
def whole_words?
|
30
|
+
layout.whole_words.value
|
31
|
+
end
|
32
|
+
|
33
|
+
def search_combo
|
34
|
+
layout.search_combo
|
35
|
+
end
|
36
|
+
|
37
|
+
def forwards?
|
38
|
+
layout.forwards.checked?
|
39
|
+
end
|
40
|
+
|
41
|
+
def backwards?
|
42
|
+
layout.backwards.checked?
|
43
|
+
end
|
44
|
+
|
45
|
+
# return either :backwards or :forwards
|
46
|
+
def direction
|
47
|
+
return :forwards if forwards?
|
48
|
+
return :backwards if backwards?
|
49
|
+
raise "direction not known"
|
50
|
+
end
|
51
|
+
|
52
|
+
def exec( text = '' )
|
53
|
+
search_combo.edit_text = text.to_s
|
54
|
+
search_combo.set_focus
|
55
|
+
retval = @dialog.exec
|
56
|
+
|
57
|
+
# remember previous searches
|
58
|
+
if search_combo.find_text( search_combo.current_text ) == -1
|
59
|
+
search_combo.add_item( search_combo.current_text )
|
60
|
+
end
|
61
|
+
|
62
|
+
#~ Qt::MatchExactly 0 Performs QVariant-based matching.
|
63
|
+
#~ Qt::MatchFixedString 8 Performs string-based matching. String-based comparisons are case-insensitive unless the MatchCaseSensitive flag is also specified.
|
64
|
+
#~ Qt::MatchContains 1 The search term is contained in the item.
|
65
|
+
#~ Qt::MatchStartsWith 2 The search term matches the start of the item.
|
66
|
+
#~ Qt::MatchEndsWith 3 The search term matches the end of the item.
|
67
|
+
#~ Qt::MatchCaseSensitive 16 The search is case sensitive.
|
68
|
+
#~ Qt::MatchRegExp 4 Performs string-based matching using a regular expression as the search term.
|
69
|
+
#~ Qt::MatchWildcard 5 Performs string-based matching using a string with wildcards as the search term.
|
70
|
+
#~ Qt::MatchWrap 32 Perform a search that wraps around, so that when the search reaches the last item in the model, it begins again at the first item and continues until all items have been examined.
|
71
|
+
|
72
|
+
retval
|
73
|
+
end
|
74
|
+
|
75
|
+
def search_text
|
76
|
+
search_combo.current_text
|
77
|
+
end
|
69
78
|
|
70
|
-
retval
|
71
|
-
end
|
72
|
-
|
73
|
-
def search_text
|
74
|
-
search_combo.current_text
|
75
79
|
end
|
76
|
-
|
80
|
+
|
77
81
|
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Clevic
|
2
|
+
# Provide some SQL dialect differences that aren't in ActiveRecord. Including
|
3
|
+
# class must respond to entity_class.
|
4
|
+
module SqlDialects
|
5
|
+
def adapter_name
|
6
|
+
connection.adapter_name
|
7
|
+
end
|
8
|
+
|
9
|
+
def connection
|
10
|
+
entity_class.connection
|
11
|
+
end
|
12
|
+
|
13
|
+
# return a string containing the correct
|
14
|
+
# boolean value depending on the DB adapter
|
15
|
+
# because Postgres wants real true and false in complex statements, not 't' and 'f'
|
16
|
+
def sql_boolean( value )
|
17
|
+
case adapter_name
|
18
|
+
when 'PostgreSQL'
|
19
|
+
value ? 'true' : 'false'
|
20
|
+
else
|
21
|
+
value ? connection.quoted_true : connection.quoted_false
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# return a case-insensitive like operator
|
26
|
+
def like_operator
|
27
|
+
case adapter_name
|
28
|
+
when 'PostgreSQL'; 'ilike'
|
29
|
+
else; 'like'
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/lib/clevic/table_model.rb
CHANGED
@@ -10,20 +10,9 @@ require 'clevic/model_column'
|
|
10
10
|
module Clevic
|
11
11
|
|
12
12
|
=begin rdoc
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
* labels are the headings in the table view
|
17
|
-
|
18
|
-
* dots are the dotted attribute paths that specify how to get values from
|
19
|
-
the underlying ActiveRecord model
|
20
|
-
|
21
|
-
* attribute_paths is a collection of attribute symbols. It comes from
|
22
|
-
dots, and is split on /\./
|
23
|
-
|
24
|
-
* attributes are the first-level of the dots
|
25
|
-
|
26
|
-
* collection is the set of ActiveRecord model objects (also called entities)
|
13
|
+
An instance of Clevic::TableModel is constructed by Clevic::ModelBuilder from the
|
14
|
+
UI definition in a Clevic::View, or from the default Clevic::View created by
|
15
|
+
including the Clevic::Record module in a ActiveRecord::Base subclass.
|
27
16
|
=end
|
28
17
|
class TableModel < Qt::AbstractTableModel
|
29
18
|
include QtFlags
|
@@ -31,9 +20,6 @@ class TableModel < Qt::AbstractTableModel
|
|
31
20
|
# the CacheTable of Clevic::Record or ActiveRecord::Base objects
|
32
21
|
attr_reader :collection
|
33
22
|
|
34
|
-
# the actual class for the collection objects
|
35
|
-
attr_accessor :model_class
|
36
|
-
|
37
23
|
# the collection of Clevic::Field objects
|
38
24
|
attr_reader :fields
|
39
25
|
|
@@ -44,11 +30,15 @@ class TableModel < Qt::AbstractTableModel
|
|
44
30
|
attr_accessor :auto_new
|
45
31
|
def auto_new?; auto_new; end
|
46
32
|
|
33
|
+
attr_accessor :entity_view
|
34
|
+
|
35
|
+
def entity_class
|
36
|
+
entity_view.entity_class
|
37
|
+
end
|
38
|
+
|
47
39
|
signals(
|
48
40
|
# index where error occurred, value, message
|
49
|
-
'data_error(QModelIndex,QVariant,QString)'
|
50
|
-
# top_left, bottom_right
|
51
|
-
'dataChanged(const QModelIndex&,const QModelIndex&)'
|
41
|
+
'data_error(QModelIndex,QVariant,QString)'
|
52
42
|
)
|
53
43
|
|
54
44
|
def initialize( parent = nil )
|
@@ -59,16 +49,10 @@ class TableModel < Qt::AbstractTableModel
|
|
59
49
|
def fields=( arr )
|
60
50
|
@fields = arr
|
61
51
|
|
62
|
-
#reset these
|
52
|
+
# reset these
|
63
53
|
@metadatas = []
|
64
|
-
@dots = nil
|
65
54
|
@labels = nil
|
66
55
|
@attributes = nil
|
67
|
-
@attribute_paths = nil
|
68
|
-
end
|
69
|
-
|
70
|
-
def dots
|
71
|
-
@dots ||= fields.map {|x| x.column }
|
72
56
|
end
|
73
57
|
|
74
58
|
def labels
|
@@ -79,15 +63,11 @@ class TableModel < Qt::AbstractTableModel
|
|
79
63
|
@attributes ||= fields.map {|x| x.attribute }
|
80
64
|
end
|
81
65
|
|
82
|
-
def attribute_paths
|
83
|
-
@attribute_paths ||= fields.map {|x| x.attribute_path }
|
84
|
-
end
|
85
|
-
|
86
66
|
def collection=( arr )
|
87
67
|
@collection = arr
|
88
68
|
# fill in an empty record for data entry
|
89
69
|
if collection.size == 0 && auto_new?
|
90
|
-
collection <<
|
70
|
+
collection << entity_class.new
|
91
71
|
end
|
92
72
|
end
|
93
73
|
|
@@ -115,25 +95,15 @@ class TableModel < Qt::AbstractTableModel
|
|
115
95
|
[]
|
116
96
|
end
|
117
97
|
|
118
|
-
#~ def build_dots( dots, attrs, prefix="" )
|
119
|
-
#~ attrs.inject( dots ) do |cols, a|
|
120
|
-
#~ if a[1].respond_to? :attributes
|
121
|
-
#~ build_keys(cols, a[1].attributes, prefix + a[0] + ".")
|
122
|
-
#~ else
|
123
|
-
#~ cols << prefix + a[0]
|
124
|
-
#~ end
|
125
|
-
#~ end
|
126
|
-
#~ end
|
127
|
-
|
128
98
|
# cache metadata (ActiveRecord#column_for_attribute) because it's not going
|
129
99
|
# to change over the lifetime of the table
|
130
100
|
# if the column is an attribute, create a ModelColumn
|
131
101
|
# TODO use ActiveRecord::Base.reflections instead
|
132
102
|
def metadata( column )
|
133
103
|
if @metadatas[column].nil?
|
134
|
-
meta =
|
104
|
+
meta = entity_class.columns_hash[attributes[column].to_s]
|
135
105
|
if meta.nil?
|
136
|
-
meta =
|
106
|
+
meta = entity_class.columns_hash[ "#{attributes[column]}_id" ]
|
137
107
|
if meta.nil?
|
138
108
|
return nil
|
139
109
|
else
|
@@ -149,20 +119,21 @@ class TableModel < Qt::AbstractTableModel
|
|
149
119
|
def add_new_item
|
150
120
|
# 1 new row
|
151
121
|
begin_insert_rows( Qt::ModelIndex.invalid, row_count, row_count )
|
152
|
-
collection <<
|
122
|
+
collection << entity_class.new
|
153
123
|
end_insert_rows
|
154
124
|
end
|
155
125
|
|
156
126
|
# rows is a collection of integers specifying row indices to remove
|
157
|
-
# TODO call begin_remove and end_remove around the whole block
|
158
127
|
def remove_rows( rows )
|
159
128
|
# delete from the end to avoid holes affecting the indexing
|
160
|
-
rows.sort.reverse.each do |index|
|
129
|
+
rows.uniq.sort.reverse.each do |index|
|
161
130
|
# remove the item from the collection
|
131
|
+
# NOTE call this within each iteration because
|
132
|
+
# the rows array may be non-contiguous
|
162
133
|
begin_remove_rows( Qt::ModelIndex.invalid, index, index )
|
163
134
|
removed = collection.delete_at( index )
|
164
135
|
end_remove_rows
|
165
|
-
# destroy the db object, and its table row
|
136
|
+
# destroy the db object, and its associated table row
|
166
137
|
removed.destroy
|
167
138
|
end
|
168
139
|
end
|
@@ -187,26 +158,32 @@ class TableModel < Qt::AbstractTableModel
|
|
187
158
|
collection.size
|
188
159
|
end
|
189
160
|
|
161
|
+
# Not looked up or aliased properly by Qt bindings
|
190
162
|
def row_count
|
191
163
|
collection.size
|
192
164
|
end
|
193
165
|
|
194
166
|
def columnCount( parent = nil )
|
195
|
-
|
167
|
+
fields.size
|
196
168
|
end
|
197
169
|
|
170
|
+
# Not looked up or aliased properly by Qt bindings
|
198
171
|
def column_count
|
199
|
-
|
172
|
+
fields.size
|
200
173
|
end
|
201
174
|
|
202
175
|
def flags( model_index )
|
203
176
|
retval = super
|
177
|
+
|
178
|
+
# sometimes this actually happens.
|
179
|
+
# TODO probably a bug in the combo editor exit code
|
180
|
+
return retval if model_index.column >= columnCount
|
181
|
+
|
204
182
|
# TODO don't return IsEditable if the model is read-only
|
205
183
|
if model_index.metadata.type == :boolean
|
206
184
|
retval = item_boolean_flags
|
207
185
|
end
|
208
186
|
|
209
|
-
# read-only
|
210
187
|
unless model_index.field.read_only? || model_index.entity.readonly? || read_only?
|
211
188
|
retval |= qt_item_is_editable.to_i
|
212
189
|
end
|
@@ -263,10 +240,11 @@ class TableModel < Qt::AbstractTableModel
|
|
263
240
|
|
264
241
|
when qt_background_role
|
265
242
|
if orientation == Qt::Vertical
|
243
|
+
item = collection[section]
|
266
244
|
case
|
267
|
-
when !
|
245
|
+
when !item.errors.empty?
|
268
246
|
Qt::Color.new( 'orange' )
|
269
|
-
when
|
247
|
+
when item.changed?
|
270
248
|
Qt::Color.new( 'yellow' )
|
271
249
|
end
|
272
250
|
end
|
@@ -281,23 +259,25 @@ class TableModel < Qt::AbstractTableModel
|
|
281
259
|
|
282
260
|
# Provide data to UI.
|
283
261
|
def data( index, role = qt_display_role )
|
284
|
-
#~ puts "data for index: #{index.inspect} and role: #{const_as_string role}"
|
262
|
+
#~ puts "data for index: #{index.inspect}, field #{index.field.attribute.inspect} and role: #{const_as_string role}"
|
285
263
|
begin
|
286
264
|
retval =
|
287
265
|
case role
|
288
|
-
when qt_display_role
|
266
|
+
when qt_display_role
|
289
267
|
# boolean values generally don't have text next to them in this context
|
290
|
-
# check explicitly to avoid fetching the entity from
|
291
|
-
# the model's collection
|
268
|
+
# check this explicitly to avoid fetching the entity from
|
269
|
+
# the model's collection (and maybe db) when we
|
270
|
+
# definitely don't need to
|
292
271
|
unless index.metadata.type == :boolean
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
272
|
+
value = index.gui_value
|
273
|
+
index.field.do_format( value ) unless value.nil?
|
274
|
+
end
|
275
|
+
|
276
|
+
when qt_edit_role
|
277
|
+
# see comment for qt_display_role
|
278
|
+
unless index.metadata.type == :boolean
|
279
|
+
value = index.gui_value
|
280
|
+
index.field.do_edit_format( value ) unless value.nil?
|
301
281
|
end
|
302
282
|
|
303
283
|
when qt_checkstate_role
|
@@ -306,22 +286,30 @@ class TableModel < Qt::AbstractTableModel
|
|
306
286
|
end
|
307
287
|
|
308
288
|
when qt_text_alignment_role
|
309
|
-
index.field.alignment
|
289
|
+
case index.field.alignment
|
290
|
+
when :left; qt_alignleft
|
291
|
+
when :right; qt_alignright
|
292
|
+
when :centre; qt_aligncenter
|
293
|
+
when :justified; qt_alignjustified
|
294
|
+
end
|
310
295
|
|
311
|
-
#
|
296
|
+
# just here to make debug output quieter
|
312
297
|
when qt_size_hint_role;
|
313
298
|
|
314
299
|
# show field with a red background if there's an error
|
315
300
|
when qt_background_role
|
316
|
-
Qt::Color.new( 'red' ) if index.has_errors?
|
317
|
-
|
301
|
+
index.field.background_for( index.entity ) || Qt::Color.new( 'red' ) if index.has_errors?
|
302
|
+
|
318
303
|
when qt_font_role;
|
304
|
+
|
319
305
|
when qt_foreground_role
|
306
|
+
index.field.foreground_for( index.entity ) ||
|
320
307
|
if index.field.read_only? || index.entity.readonly? || read_only?
|
321
308
|
Qt::Color.new( 'dimgray' )
|
322
309
|
end
|
323
|
-
|
310
|
+
|
324
311
|
when qt_decoration_role;
|
312
|
+
index.field.decoration_for( index.entity )
|
325
313
|
|
326
314
|
when qt_tooltip_role
|
327
315
|
case
|
@@ -336,6 +324,9 @@ class TableModel < Qt::AbstractTableModel
|
|
336
324
|
# read-only field
|
337
325
|
when index.field.read_only?
|
338
326
|
'Read-only'
|
327
|
+
|
328
|
+
else
|
329
|
+
index.field.tooltip_for( index.entity )
|
339
330
|
end
|
340
331
|
else
|
341
332
|
puts "data index: #{index}, role: #{const_as_string(role)}" if $options[:debug]
|
@@ -343,23 +334,27 @@ class TableModel < Qt::AbstractTableModel
|
|
343
334
|
end
|
344
335
|
|
345
336
|
# return a variant
|
337
|
+
#~ puts "retval: #{retval.inspect}"
|
346
338
|
retval.to_variant
|
347
339
|
rescue Exception => e
|
340
|
+
puts "#{index.inspect} #{value.inspect} #{index.entity.inspect} for and role: #{const_as_string role}"
|
341
|
+
puts e.message
|
348
342
|
puts e.backtrace
|
349
|
-
puts "#{index.inspect} #{value.inspect} #{index.entity.inspect} #{e.message}"
|
350
343
|
nil.to_variant
|
351
344
|
end
|
352
345
|
end
|
353
346
|
|
354
347
|
# data sent from UI
|
348
|
+
# return true if conversion from variant was successful,
|
349
|
+
# or false if something went wrong.
|
355
350
|
def setData( index, variant, role = qt_edit_role )
|
356
351
|
if index.valid?
|
357
352
|
case role
|
358
353
|
when qt_edit_role
|
359
354
|
# Don't allow the primary key to be changed
|
360
|
-
return false if index.attribute ==
|
355
|
+
return false if index.attribute == entity_class.primary_key.to_sym
|
361
356
|
|
362
|
-
if ( index.column < 0 || index.column >=
|
357
|
+
if ( index.column < 0 || index.column >= column_count )
|
363
358
|
raise "invalid column #{index.column}"
|
364
359
|
end
|
365
360
|
|
@@ -369,7 +364,7 @@ class TableModel < Qt::AbstractTableModel
|
|
369
364
|
# translate the value from the ui to something that
|
370
365
|
# the AR model will understand
|
371
366
|
begin
|
372
|
-
index.
|
367
|
+
index.attribute_value =
|
373
368
|
case
|
374
369
|
when value.class.name == 'Qt::Date'
|
375
370
|
Date.new( value.year, value.month, value.day )
|
@@ -382,19 +377,19 @@ class TableModel < Qt::AbstractTableModel
|
|
382
377
|
# TODO need to be cleverer about which year to use
|
383
378
|
# for when you're entering 16dec and you're in the next
|
384
379
|
# year
|
385
|
-
when type
|
380
|
+
when [:date,:datetime].include?( type ) && value =~ %r{^(\d{1,2})[ /-]?(\w{3})$}
|
386
381
|
Date.parse( "#$1 #$2 #{Time.now.year.to_s}" )
|
387
382
|
|
388
383
|
# if a digit only is entered, fetch month and year from
|
389
384
|
# previous row
|
390
|
-
when type
|
385
|
+
when [:date,:datetime].include?( type ) && value =~ %r{^(\d{1,2})$}
|
391
386
|
previous_entity = collection[index.row - 1]
|
392
387
|
# year,month,day
|
393
388
|
Date.new( previous_entity.date.year, previous_entity.date.month, $1.to_i )
|
394
389
|
|
395
390
|
# this one is mostly to fix date strings that have come
|
396
391
|
# out of the db and been formatted
|
397
|
-
when type
|
392
|
+
when [:date,:datetime].include?( type ) && value =~ %r{^(\d{2})[ /-](\w{3})[ /-](\d{2})$}
|
398
393
|
Date.parse( "#$1 #$2 20#$3" )
|
399
394
|
|
400
395
|
# allow lots of flexibility in entering times
|
@@ -452,51 +447,9 @@ class TableModel < Qt::AbstractTableModel
|
|
452
447
|
end
|
453
448
|
end
|
454
449
|
|
455
|
-
def like_operator
|
456
|
-
case model_class.connection.adapter_name
|
457
|
-
when 'PostgreSQL'; 'ilike'
|
458
|
-
else; 'like'
|
459
|
-
end
|
460
|
-
end
|
461
|
-
|
462
450
|
# return a set of indexes that match the search criteria
|
463
|
-
# TODO this implementation is very un-ruby.
|
464
451
|
def search( start_index, search_criteria )
|
465
|
-
|
466
|
-
search_value =
|
467
|
-
if search_criteria.whole_words?
|
468
|
-
"% #{search_criteria.search_text} %"
|
469
|
-
else
|
470
|
-
"%#{search_criteria.search_text}%"
|
471
|
-
end
|
472
|
-
|
473
|
-
# build up the ordering conditions
|
474
|
-
bits = collection.build_sql_find( start_index.entity, search_criteria.direction )
|
475
|
-
|
476
|
-
# do the conditions for the search value
|
477
|
-
conditions =
|
478
|
-
if start_index.field.is_association?
|
479
|
-
# for related tables
|
480
|
-
# TODO this will only work with a path value with no dots
|
481
|
-
"#{start_index.field.path} #{like_operator} :search_value"
|
482
|
-
else
|
483
|
-
# for this table
|
484
|
-
"#{model_class.connection.quote_column_name( start_index.field_name )} #{like_operator} :search_value"
|
485
|
-
end
|
486
|
-
|
487
|
-
# add ordering conditions
|
488
|
-
conditions += ( " and " + bits[:sql] ) unless search_criteria.from_start?
|
489
|
-
|
490
|
-
params = { :search_value => search_value }
|
491
|
-
params.merge!( bits[:params] ) unless search_criteria.from_start?
|
492
|
-
|
493
|
-
# find the first match
|
494
|
-
entity = model_class.find(
|
495
|
-
:first,
|
496
|
-
:conditions => [ conditions, params ],
|
497
|
-
:order => search_criteria.direction == :forwards ? collection.order : collection.reverse_order,
|
498
|
-
:joins => ( start_index.field.meta.name if start_index.field.is_association? )
|
499
|
-
)
|
452
|
+
entity = collection.search( start_index.field, search_criteria, start_index.entity )
|
500
453
|
|
501
454
|
# return matched indexes
|
502
455
|
if entity != nil
|