clevic 0.13.0.b9 → 0.13.0.b10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (85) hide show
  1. data/History.txt +3 -0
  2. data/lib/clevic/action_builder.rb +16 -16
  3. data/lib/clevic/ar_methods.rb +22 -22
  4. data/lib/clevic/attribute_list.rb +5 -5
  5. data/lib/clevic/cache_table.rb +18 -18
  6. data/lib/clevic/dataset_roller.rb +3 -3
  7. data/lib/clevic/default_view.rb +4 -4
  8. data/lib/clevic/delegate.rb +8 -8
  9. data/lib/clevic/delegates/combo_delegate.rb +22 -22
  10. data/lib/clevic/delegates/distinct_delegate.rb +5 -5
  11. data/lib/clevic/delegates/relational_delegate.rb +6 -6
  12. data/lib/clevic/delegates/set_delegate.rb +3 -3
  13. data/lib/clevic/dirty.rb +1 -1
  14. data/lib/clevic/emitter.rb +3 -3
  15. data/lib/clevic/extensions.rb +4 -4
  16. data/lib/clevic/field.rb +68 -68
  17. data/lib/clevic/field_valuer.rb +26 -26
  18. data/lib/clevic/filter_command.rb +6 -6
  19. data/lib/clevic/framework.rb +3 -3
  20. data/lib/clevic/generic_format.rb +2 -2
  21. data/lib/clevic/many_field.rb +3 -3
  22. data/lib/clevic/model_builder.rb +88 -84
  23. data/lib/clevic/model_column.rb +13 -13
  24. data/lib/clevic/ordered_dataset.rb +7 -7
  25. data/lib/clevic/qt/action_builder.rb +3 -3
  26. data/lib/clevic/qt/browser.rb +28 -28
  27. data/lib/clevic/qt/clipboard.rb +5 -5
  28. data/lib/clevic/qt/combo_delegate.rb +12 -12
  29. data/lib/clevic/qt/distinct_delegate.rb +1 -1
  30. data/lib/clevic/qt/extensions.rb +4 -4
  31. data/lib/clevic/qt/qt_combo_box.rb +7 -7
  32. data/lib/clevic/qt/relational_delegate.rb +5 -5
  33. data/lib/clevic/qt/search_dialog.rb +15 -15
  34. data/lib/clevic/qt/simplest_delegate.rb +2 -2
  35. data/lib/clevic/qt/table_model.rb +46 -46
  36. data/lib/clevic/qt/table_view.rb +48 -48
  37. data/lib/clevic/qt/text_delegate.rb +9 -9
  38. data/lib/clevic/rails_models_loaders.rb +3 -3
  39. data/lib/clevic/record.rb +8 -8
  40. data/lib/clevic/sampler.rb +6 -6
  41. data/lib/clevic/sequel_ar_adapter.rb +22 -22
  42. data/lib/clevic/sequel_clevic.rb +10 -10
  43. data/lib/clevic/sequel_meta.rb +5 -5
  44. data/lib/clevic/sequel_naked.rb +4 -4
  45. data/lib/clevic/swing/action.rb +20 -20
  46. data/lib/clevic/swing/action_builder.rb +2 -2
  47. data/lib/clevic/swing/boolean_delegate.rb +3 -3
  48. data/lib/clevic/swing/browser.rb +37 -37
  49. data/lib/clevic/swing/cell_editor.rb +13 -13
  50. data/lib/clevic/swing/cell_renderer.rb +7 -7
  51. data/lib/clevic/swing/clipboard.rb +19 -19
  52. data/lib/clevic/swing/combo_delegate.rb +26 -26
  53. data/lib/clevic/swing/confirm_dialog.rb +7 -7
  54. data/lib/clevic/swing/delegate.rb +4 -4
  55. data/lib/clevic/swing/extensions.rb +24 -24
  56. data/lib/clevic/swing/field.rb +1 -1
  57. data/lib/clevic/swing/relational_delegate.rb +2 -2
  58. data/lib/clevic/swing/row_header.rb +32 -32
  59. data/lib/clevic/swing/search_dialog.rb +31 -31
  60. data/lib/clevic/swing/selection_model.rb +12 -12
  61. data/lib/clevic/swing/swing_table_index.rb +6 -6
  62. data/lib/clevic/swing/table_model.rb +30 -30
  63. data/lib/clevic/swing/table_view.rb +54 -54
  64. data/lib/clevic/swing/table_view_focus.rb +4 -4
  65. data/lib/clevic/swing/tag_delegate.rb +14 -14
  66. data/lib/clevic/swing/tag_editor.rb +4 -4
  67. data/lib/clevic/swing/text_area_delegate.rb +6 -6
  68. data/lib/clevic/swing/text_delegate.rb +4 -4
  69. data/lib/clevic/table_index.rb +14 -14
  70. data/lib/clevic/table_model.rb +30 -30
  71. data/lib/clevic/table_searcher.rb +19 -19
  72. data/lib/clevic/table_view.rb +92 -92
  73. data/lib/clevic/table_view_paste.rb +19 -19
  74. data/lib/clevic/version.rb +1 -1
  75. data/lib/clevic/view.rb +22 -22
  76. data/models/accounts_models.rb +10 -10
  77. data/models/examples.rb +2 -2
  78. data/models/times_models.rb +32 -32
  79. data/models/values_models.rb +2 -2
  80. data/test/test_cache_table.rb +15 -15
  81. data/test/test_helper.rb +7 -7
  82. data/test/test_model_index_extensions.rb +6 -6
  83. data/test/test_table_model.rb +6 -6
  84. data/test/test_table_searcher.rb +25 -25
  85. metadata +33 -35
@@ -8,14 +8,14 @@ class TagEditor < javax.swing.JComponent
8
8
  def initialize( field )
9
9
  super()
10
10
  @field = field
11
-
11
+
12
12
  create_fields
13
13
  init_layout
14
14
  end
15
-
15
+
16
16
  attr_reader :field
17
17
  attr_reader :entity
18
-
18
+
19
19
  # Hopefully called by the editor framework
20
20
  # might be init_component
21
21
  def configureEditor( editor, entity )
@@ -34,7 +34,7 @@ class TagEditor < javax.swing.JComponent
34
34
  @add_button = javax.swing.JButton.new
35
35
  @remove_button = javax.swing.JButton.new
36
36
  end
37
-
37
+
38
38
  def init_layout
39
39
  # This is mostly cut-n-pasted from the Netbeans Java sources. So don't tweak it.
40
40
  text_field.setName("text_field");
@@ -12,7 +12,7 @@ module Clevic
12
12
  super( newrect )
13
13
  end
14
14
  end
15
-
15
+
16
16
  class TextAreaDelegate < Delegate
17
17
  # TODO check that Ctrl-VK_ENTER stops editing
18
18
  def init_component( cell_editor )
@@ -21,23 +21,23 @@ module Clevic
21
21
  text_component.rows = ( edit_value.andand.count( "\n" ) || 0 ) + 2
22
22
  text_component.select_all
23
23
  end
24
-
24
+
25
25
  def editor
26
26
  @editor ||= EditorScrollPane.new( text_component, javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS, javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS )
27
27
  end
28
-
28
+
29
29
  def text_component
30
30
  @text_component ||= javax.swing.JTextArea.new
31
31
  end
32
-
32
+
33
33
  def value
34
34
  text_component.text
35
35
  end
36
-
36
+
37
37
  def minimal_edit
38
38
  text_component.select_all
39
39
  end
40
-
40
+
41
41
  def needs_pre_selection?
42
42
  true
43
43
  end
@@ -8,21 +8,21 @@ module Clevic
8
8
  editor.text = edit_value
9
9
  editor.select_all
10
10
  end
11
-
11
+
12
12
  def editor
13
13
  @editor ||= javax.swing.JTextField.new.tap do |e|
14
14
  e.horizontal_alignment = field.swing_alignment
15
15
  end
16
16
  end
17
-
17
+
18
18
  def value
19
19
  editor.text
20
20
  end
21
-
21
+
22
22
  def minimal_edit
23
23
  editor.select_all
24
24
  end
25
-
25
+
26
26
  def needs_pre_selection?
27
27
  true
28
28
  end
@@ -4,12 +4,12 @@ module Clevic
4
4
  # to be included in something that responds to model, row, column
5
5
  module TableIndex
6
6
  include FieldValuer
7
-
7
+
8
8
  # return the Clevic::Field for this index
9
9
  def field
10
10
  @field ||= model.field_for_index( self )
11
11
  end
12
-
12
+
13
13
  def dump
14
14
  if valid?
15
15
  <<-EOF
@@ -21,35 +21,35 @@ module Clevic
21
21
  'invalid'
22
22
  end
23
23
  end
24
-
24
+
25
25
  def prev
26
26
  choppy( :row => row - 1 )
27
27
  end
28
-
28
+
29
29
  # return the attribute of the underlying entity corresponding
30
30
  # to the column of this index
31
31
  def attribute
32
32
  model.attributes[column]
33
33
  end
34
-
34
+
35
35
  # returns the list of ModelColumn metadata
36
36
  def meta
37
37
  # use the optimised version
38
38
  # TODO just use the model version instead?
39
39
  field.meta
40
40
  end
41
-
41
+
42
42
  # return the table's field name. For associations, this would
43
43
  # be suffixed with _id
44
44
  def field_name
45
45
  meta.name
46
46
  end
47
-
47
+
48
48
  # return the value of the field, it may be the _id value
49
49
  def field_value
50
50
  entity.send( field_name )
51
51
  end
52
-
52
+
53
53
  # the underlying entity
54
54
  # TODO caching doesn't help with Qt because ModelIndex objects are
55
55
  # extremely short-lived. Not sure about swing.
@@ -57,9 +57,9 @@ module Clevic
57
57
  return nil if model.nil?
58
58
  model.collection[row]
59
59
  end
60
-
60
+
61
61
  attr_writer :entity
62
-
62
+
63
63
  # return true if validation failed for this indexes field
64
64
  def has_errors?
65
65
  # virtual fields don't have metadata
@@ -69,14 +69,14 @@ module Clevic
69
69
  entity.errors.invalid?( field_name.to_sym )
70
70
  end
71
71
  end
72
-
72
+
73
73
  # return a collection of errors. Unlike AR, this
74
74
  # will always return an array that will have zero, one
75
75
  # or many elements.
76
76
  def errors
77
77
  [ entity.errors[field_name.to_sym] ].flatten
78
78
  end
79
-
79
+
80
80
  # sort by row, then column
81
81
  def <=>( other )
82
82
  row_comp = self.row <=> other.row
@@ -86,11 +86,11 @@ module Clevic
86
86
  row_comp
87
87
  end
88
88
  end
89
-
89
+
90
90
  def inspect
91
91
  "#<TableIndex (#{row},#{column}) '#{raw_value rescue "no raw value: #{$!.message}"}'>"
92
92
  end
93
-
93
+
94
94
  # return a string (row,column)
95
95
  # suitable for displaying to users, ie 1-based not 0-based
96
96
  def rc
@@ -15,10 +15,10 @@ class TableModel
15
15
  # the CacheTable of Clevic::Record or Sequel::Model objects
16
16
  attr_reader :collection
17
17
  alias_method :cache_table, :collection
18
-
18
+
19
19
  # the collection of Clevic::Field objects
20
20
  attr_reader :fields
21
-
21
+
22
22
  attr_accessor :read_only
23
23
  def read_only?; read_only; end
24
24
 
@@ -26,69 +26,69 @@ class TableModel
26
26
  attr_accessor :auto_new
27
27
  def auto_new?; auto_new; end
28
28
  def auto_new?; auto_new; end
29
-
29
+
30
30
  attr_accessor :entity_view
31
31
  attr_accessor :builder
32
-
32
+
33
33
  # If somre or all the entities in the collection is related to a single entity
34
34
  # somewhere else, this is it. Not sure if this is the right
35
35
  # way to do it, but try anyway.
36
36
  attr_accessor :one
37
-
37
+
38
38
  def entity_class
39
39
  entity_view.entity_class
40
40
  end
41
-
41
+
42
42
  def fields=( arr )
43
43
  @fields = arr
44
-
44
+
45
45
  # reset these
46
46
  @labels = nil
47
47
  @attributes = nil
48
48
  end
49
-
49
+
50
50
  # field is a symbol or string referring to a column.
51
51
  # returns the index of that field.
52
52
  def field_column( field )
53
53
  fields.each_with_index {|x,i| return i if x.id == field.to_sym }
54
54
  end
55
-
55
+
56
56
  def field_for_index( model_index )
57
57
  fields[model_index.column]
58
58
  end
59
-
59
+
60
60
  def labels
61
61
  @labels ||= fields.map {|x| x.label }
62
62
  end
63
-
63
+
64
64
  def attributes
65
65
  @attributes ||= fields.map {|x| x.attribute }
66
66
  end
67
-
67
+
68
68
  def collection=( arr )
69
69
  @collection = arr
70
70
  # fill in an empty record for data entry
71
71
  add_new_item if collection.empty? && auto_new?
72
72
  end
73
-
73
+
74
74
  # add a new item, and set defaults from the Clevic::View
75
75
  # add_new_item_start and add_new_item_end are provided by
76
76
  # the GUI framework-specific class
77
77
  def add_new_item
78
78
  add_new_item_start
79
-
79
+
80
80
  entity = entity_class.new
81
-
81
+
82
82
  # set default values without triggering changed
83
83
  fields.each do |f|
84
84
  f.set_default_for( entity ) unless f.default.nil?
85
85
  end
86
-
86
+
87
87
  collection << entity
88
-
88
+
89
89
  add_new_item_end
90
90
  end
91
-
91
+
92
92
  # rows is a collection of integers specifying row indices to remove
93
93
  def remove_rows( rows )
94
94
  # don't delete rows twice
@@ -99,25 +99,25 @@ class TableModel
99
99
  removals = rows_in_order.map do |index|
100
100
  collection[index]
101
101
  end
102
-
102
+
103
103
  remove_notify( rows_in_order ) do
104
104
  removals.each do |to_remove, index|
105
105
  # destroy the db object, and its associated table row
106
106
  to_remove.destroy unless to_remove.nil? || to_remove.new?
107
107
  end
108
108
  end
109
-
109
+
110
110
  # because it's too bloody complicated to figure out which items
111
111
  # were deleted and then remove them from the collection. And there
112
112
  # most likely isn't a big hit for doing this, unless there's a lot
113
113
  # of cached calcuation in the entities.
114
114
  self.collection = collection.renew
115
-
115
+
116
116
  # create a new row if auto_new is on
117
117
  # should really be in a signal handler
118
118
  add_new_item if collection.empty? && auto_new?
119
119
  end
120
-
120
+
121
121
  # save the model at the given index, if it's dirty
122
122
  # TODO Sequel uses modified?
123
123
  def save( index )
@@ -141,7 +141,7 @@ class TableModel
141
141
  puts $!.backtrace
142
142
  emit_data_error( index, nil, $!.message )
143
143
  end
144
-
144
+
145
145
  def reload_data( dataset = nil, &dataset_block )
146
146
  # renew cache. All records will be dropped and reloaded.
147
147
  self.collection = self.cache_table.renew( dataset, &dataset_block )
@@ -159,7 +159,7 @@ class TableModel
159
159
  start_index.field
160
160
  )
161
161
  entity = searcher.search( start_index.entity )
162
-
162
+
163
163
  # return matched indexes
164
164
  unless entity.nil?
165
165
  found_row = collection.index_for_entity( entity )
@@ -169,12 +169,12 @@ class TableModel
169
169
  []
170
170
  end
171
171
  end
172
-
172
+
173
173
  class DataChange
174
174
  class ModelIndexProxy
175
175
  attr_accessor :row
176
176
  attr_accessor :column
177
-
177
+
178
178
  def initialize( other = nil )
179
179
  unless other.nil?
180
180
  @row = other.row
@@ -182,18 +182,18 @@ class TableModel
182
182
  end
183
183
  end
184
184
  end
185
-
185
+
186
186
  def top_left
187
187
  @top_left ||= ModelIndexProxy.new
188
188
  end
189
-
189
+
190
190
  def bottom_right
191
191
  @bottom_right ||= ModelIndexProxy.new
192
192
  end
193
-
193
+
194
194
  attr_writer :bottom_right
195
195
  attr_writer :top_left
196
-
196
+
197
197
  attr_reader :index
198
198
  def index=( other )
199
199
  self.top_left = ModelIndexProxy.new( other )
@@ -11,20 +11,20 @@ the matching record next after this.
11
11
  class TableSearcher
12
12
  include OrderedDataset
13
13
  attr_reader :search_criteria, :field
14
-
14
+
15
15
  # dataset is a Sequel::Dataset, which has an associated Sequel::Model
16
16
  # field is an instance of Clevic::Field
17
17
  # search_criteria responds to from_start?, direction, whole_words? and search_text
18
18
  def initialize( dataset, search_criteria, field )
19
19
  raise "field must be specified" if field.nil?
20
20
  raise "unknown order #{search_criteria.direction}" unless [:forwards, :backwards].include?( search_criteria.direction )
21
-
21
+
22
22
  self.dataset = dataset
23
-
23
+
24
24
  @search_criteria = search_criteria
25
25
  @field = field
26
26
  end
27
-
27
+
28
28
  # start_entity is the entity to start from, ie any record found after it will qualify
29
29
  # return the first entity found that matches the criteria
30
30
  def search( start_entity = nil )
@@ -36,12 +36,12 @@ protected
36
36
  def search_field_expression
37
37
  if field.association?
38
38
  raise "display not specified for #{field}" if field.display.nil?
39
-
39
+
40
40
  # for related table displays as procs
41
41
  unless [String,Symbol].include?( field.display.class )
42
42
  raise( "search field #{field.inspect} cannot search lambda display" )
43
43
  end
44
-
44
+
45
45
  # TODO this will only work with a path value with no dots
46
46
  # otherwise the SQL gets complicated with joins etc
47
47
  field.related_class \
@@ -52,7 +52,7 @@ protected
52
52
  field.attribute.to_sym
53
53
  end
54
54
  end
55
-
55
+
56
56
  # return an expression, or an array or expressions for representing search_criteria.search_text and whole_words?
57
57
  def search_text_expression
58
58
  if search_criteria.whole_words?
@@ -66,7 +66,7 @@ protected
66
66
  "%#{search_criteria.search_text}%"
67
67
  end
68
68
  end
69
-
69
+
70
70
  # Add the relevant conditions to use start_entity as the
71
71
  # entity where the search starts, ie the first one after it is found
72
72
  # start_entity is a model instance
@@ -76,12 +76,12 @@ protected
76
76
  # pure boolean expression - they need something to compare it to.
77
77
  dataset.filter( expression => true )
78
78
  end
79
-
79
+
80
80
  # return a dataset based on dataset which filters on search_criteria
81
81
  def search_dataset( start_entity )
82
82
  likes = Array[*search_text_expression].map{|ste| Sequel::SQL::StringExpression.like(search_field_expression, ste, {:case_insensitive=>true})}
83
83
  rv = dataset.filter( Sequel::SQL::BooleanExpression.new(:OR, *likes ) )
84
-
84
+
85
85
  # if we're not searching from the start, we need
86
86
  # to find the next match. Which is complicated from an SQL point of view.
87
87
  unless search_criteria.from_start?
@@ -89,25 +89,25 @@ protected
89
89
  # build up the ordering conditions
90
90
  rv = find_from( rv, start_entity )
91
91
  end
92
-
92
+
93
93
  # reverse order by direction if necessary
94
94
  rv = rv.reverse if search_criteria.direction == :backwards
95
-
95
+
96
96
  # return dataset
97
97
  rv
98
98
  end
99
-
99
+
100
100
  # recursively create a case statement to do the comparison
101
101
  # because and ... and ... and filters on *each* one rather than
102
102
  # consecutively.
103
103
  def build_recursive_comparison( start_entity, index = 0 )
104
104
  # end recursion
105
105
  return false if index == order_attributes.size
106
-
106
+
107
107
  # fetch the current order attribute and direction
108
108
  attribute, direction = order_attributes[index]
109
109
  value = start_entity.send( attribute )
110
-
110
+
111
111
  # build case statement using Sequel expressions, including recursion
112
112
  # pseudo-SQL is
113
113
  # case
@@ -115,7 +115,7 @@ protected
115
115
  # when attribute = value then #{build_recursive_comparison( operator, index+1 )}
116
116
  # else false
117
117
  # end
118
-
118
+
119
119
  {
120
120
  # if values are unequal, comparison levels end here
121
121
  attribute.identifier.send( comparator(direction), value ) => true,
@@ -123,7 +123,7 @@ protected
123
123
  { attribute => value } => build_recursive_comparison( start_entity, index+1 )
124
124
  }.case( false ) # the else (default) clause, ie we don't want to see these records
125
125
  end
126
-
126
+
127
127
  # return either > or < depending on both search_criteria.direction
128
128
  # and local_direction
129
129
  def comparator( local_direction = 1 )
@@ -132,11 +132,11 @@ protected
132
132
  when :forwards; 1
133
133
  when :backwards; -1
134
134
  end * local_direction
135
-
135
+
136
136
  # 1 indexes >, -1 indexes <
137
137
  ['','>','<'][comparator_direction]
138
138
  end
139
-
139
+
140
140
  end
141
141
 
142
142
  end