clevic 0.8.0 → 0.11.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. data/History.txt +9 -0
  2. data/Manifest.txt +13 -10
  3. data/README.txt +6 -9
  4. data/Rakefile +35 -24
  5. data/TODO +29 -17
  6. data/bin/clevic +84 -37
  7. data/config/hoe.rb +7 -3
  8. data/lib/clevic.rb +2 -4
  9. data/lib/clevic/browser.rb +37 -49
  10. data/lib/clevic/cache_table.rb +55 -165
  11. data/lib/clevic/db_options.rb +32 -21
  12. data/lib/clevic/default_view.rb +66 -0
  13. data/lib/clevic/delegates.rb +51 -67
  14. data/lib/clevic/dirty.rb +101 -0
  15. data/lib/clevic/extensions.rb +24 -38
  16. data/lib/clevic/field.rb +400 -99
  17. data/lib/clevic/item_delegate.rb +32 -33
  18. data/lib/clevic/model_builder.rb +315 -148
  19. data/lib/clevic/order_attribute.rb +53 -0
  20. data/lib/clevic/record.rb +57 -57
  21. data/lib/clevic/search_dialog.rb +71 -67
  22. data/lib/clevic/sql_dialects.rb +33 -0
  23. data/lib/clevic/table_model.rb +73 -120
  24. data/lib/clevic/table_searcher.rb +165 -0
  25. data/lib/clevic/table_view.rb +140 -100
  26. data/lib/clevic/ui/.gitignore +1 -0
  27. data/lib/clevic/ui/browser_ui.rb +55 -56
  28. data/lib/clevic/ui/search_dialog_ui.rb +50 -51
  29. data/lib/clevic/version.rb +2 -2
  30. data/lib/clevic/view.rb +89 -0
  31. data/models/accounts_models.rb +12 -9
  32. data/models/minimal_models.rb +4 -2
  33. data/models/times_models.rb +41 -25
  34. data/models/times_sqlite_models.rb +1 -145
  35. data/models/values_models.rb +15 -16
  36. data/test/test_cache_table.rb +138 -0
  37. data/test/test_helper.rb +131 -0
  38. data/test/test_model_index_extensions.rb +22 -0
  39. data/test/test_order_attribute.rb +62 -0
  40. data/test/test_sql_dialects.rb +77 -0
  41. data/test/test_table_searcher.rb +188 -0
  42. metadata +36 -20
  43. data/bin/import-times +0 -128
  44. data/config/jamis.rb +0 -589
  45. data/env.sh +0 -1
  46. data/lib/active_record/dirty.rb +0 -87
  47. data/lib/clevic/field_builder.rb +0 -42
  48. data/website/index.html +0 -170
  49. data/website/index.txt +0 -17
  50. data/website/screenshot.png +0 -0
  51. data/website/stylesheets/screen.css +0 -131
  52. 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
@@ -1,70 +1,70 @@
1
- require 'active_record.rb'
2
- require 'active_record/dirty.rb'
1
+ require 'clevic/view.rb'
2
+ require 'clevic/default_view.rb'
3
3
 
4
4
  module Clevic
5
5
 
6
- module Default
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 define_ui_block; nil; end
9
-
10
- def post_default_ui_block
11
- @post_default_ui_block
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 post_default_ui( &block )
15
- @post_default_ui_block = block
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
- end
22
- end
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
- # The base class for all Clevic model and UI definitions.
35
- # minimal definition is like this
36
- # class User < Clevic::Record; end
37
- # Record automatically keeps track of the order
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
 
@@ -2,76 +2,80 @@ require 'Qt4'
2
2
  require 'clevic/ui/search_dialog_ui.rb'
3
3
  require 'qtext/flags.rb'
4
4
 
5
- class SearchDialog
6
- include QtFlags
7
- attr_reader :match_flags, :layout
8
-
9
- def initialize
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
- # remember previous searches
56
- if search_combo.find_text( search_combo.current_text ) == -1
57
- search_combo.add_item( search_combo.current_text )
11
+ def initialize
12
+ @layout = Ui_SearchDialog.new
13
+ @dialog = Qt::Dialog.new
14
+ @layout.setupUi( @dialog )
58
15
  end
59
16
 
60
- #~ Qt::MatchExactly 0 Performs QVariant-based matching.
61
- #~ Qt::MatchFixedString 8 Performs string-based matching. String-based comparisons are case-insensitive unless the MatchCaseSensitive flag is also specified.
62
- #~ Qt::MatchContains 1 The search term is contained in the item.
63
- #~ Qt::MatchStartsWith 2 The search term matches the start of the item.
64
- #~ Qt::MatchEndsWith 3 The search term matches the end of the item.
65
- #~ Qt::MatchCaseSensitive 16 The search is case sensitive.
66
- #~ Qt::MatchRegExp 4 Performs string-based matching using a regular expression as the search term.
67
- #~ Qt::MatchWildcard 5 Performs string-based matching using a string with wildcards as the search term.
68
- #~ 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.
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
@@ -10,20 +10,9 @@ require 'clevic/model_column'
10
10
  module Clevic
11
11
 
12
12
  =begin rdoc
13
- This table model allows an ActiveRecord or ActiveResource to be used as a
14
- basis for a Qt::AbstractTableModel for viewing in a Qt::TableView.
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 << model_class.new
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 = model_class.columns_hash[attributes[column].to_s]
104
+ meta = entity_class.columns_hash[attributes[column].to_s]
135
105
  if meta.nil?
136
- meta = model_class.columns_hash[ "#{attributes[column]}_id" ]
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 << model_class.new
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
- dots.size
167
+ fields.size
196
168
  end
197
169
 
170
+ # Not looked up or aliased properly by Qt bindings
198
171
  def column_count
199
- dots.size
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 !collection[section].errors.empty?
245
+ when !item.errors.empty?
268
246
  Qt::Color.new( 'orange' )
269
- when collection[section].changed?
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, qt_edit_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 when we don't need to
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
- begin
294
- value = index.gui_value
295
- unless value.nil?
296
- index.field.do_format( value )
297
- end
298
- rescue Exception => e
299
- puts e.backtrace
300
- end
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
- # these are just here to make debug output quieter
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 == model_class.primary_key.to_sym
355
+ return false if index.attribute == entity_class.primary_key.to_sym
361
356
 
362
- if ( index.column < 0 || index.column >= dots.size )
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.gui_value =
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 == :date && value =~ %r{^(\d{1,2})[ /-]?(\w{3})$}
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 == :date && value =~ %r{^(\d{1,2})$}
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 == :date && value =~ %r{^(\d{2})[ /-](\w{3})[ /-](\d{2})$}
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
- # get the search value parameter, in SQL format
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