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.
Files changed (119) hide show
  1. data/History.txt +10 -0
  2. data/Manifest.txt +209 -30
  3. data/README.txt +16 -20
  4. data/Rakefile +8 -8
  5. data/TODO +6 -7
  6. data/bin/clevic +12 -73
  7. data/lib/clevic/action_builder.rb +168 -0
  8. data/lib/clevic/ar_methods.rb +120 -0
  9. data/lib/clevic/attribute_list.rb +56 -0
  10. data/lib/clevic/cache_table.rb +60 -37
  11. data/lib/clevic/default_view.rb +3 -16
  12. data/lib/clevic/delegate.rb +46 -0
  13. data/lib/clevic/emitter.rb +38 -0
  14. data/lib/clevic/extensions.rb +61 -114
  15. data/lib/clevic/field.rb +159 -228
  16. data/lib/clevic/field_valuer.rb +165 -0
  17. data/lib/clevic/filter_command.rb +2 -6
  18. data/lib/clevic/generic_format.rb +52 -0
  19. data/lib/clevic/{ui → icons}/icon.png +0 -0
  20. data/lib/clevic/many_field.rb +7 -0
  21. data/lib/clevic/model_builder.rb +234 -146
  22. data/lib/clevic/model_column.rb +61 -13
  23. data/lib/clevic/order_attribute.rb +10 -0
  24. data/lib/clevic/qt.rb +35 -0
  25. data/lib/clevic/qt/action_builder.rb +47 -0
  26. data/lib/clevic/qt/boolean_delegate.rb +8 -0
  27. data/lib/clevic/{browser.rb → qt/browser.rb} +35 -14
  28. data/lib/clevic/qt/clipboard.rb +35 -0
  29. data/lib/clevic/qt/combo_delegate.rb +198 -0
  30. data/lib/clevic/qt/delegates.rb +1 -0
  31. data/lib/clevic/qt/distinct_delegate.rb +35 -0
  32. data/lib/clevic/qt/extensions.rb +52 -0
  33. data/lib/clevic/qt/field.rb +18 -0
  34. data/lib/clevic/{item_delegate.rb → qt/item_delegate.rb} +8 -4
  35. data/lib/clevic/qt/relational_delegate.rb +87 -0
  36. data/lib/clevic/{search_dialog.rb → qt/search_dialog.rb} +1 -11
  37. data/lib/clevic/qt/set_delegate.rb +44 -0
  38. data/lib/clevic/qt/table_model.rb +331 -0
  39. data/lib/clevic/qt/table_view.rb +344 -0
  40. data/lib/clevic/qt/text_area_delegate.rb +8 -0
  41. data/lib/clevic/{text_delegate.rb → qt/text_delegate.rb} +6 -4
  42. data/lib/clevic/{ui → qt/ui}/.gitignore +0 -0
  43. data/lib/clevic/{ui → qt/ui}/browser.ui +0 -0
  44. data/lib/clevic/{ui → qt/ui}/search_dialog.ui +0 -0
  45. data/lib/clevic/rails_models_loaders.rb +56 -0
  46. data/lib/clevic/record.rb +2 -17
  47. data/lib/clevic/sampler.rb +81 -0
  48. data/lib/clevic/sequel_ar_adapter.rb +215 -0
  49. data/lib/clevic/sequel_length_validation.rb +23 -0
  50. data/lib/clevic/sequel_meta.rb +65 -0
  51. data/lib/clevic/sequel_naked.rb +30 -0
  52. data/lib/clevic/swing.rb +38 -0
  53. data/lib/clevic/swing/action.rb +125 -0
  54. data/lib/clevic/swing/action_builder.rb +47 -0
  55. data/lib/clevic/swing/boolean_delegate.rb +26 -0
  56. data/lib/clevic/swing/browser.rb +282 -0
  57. data/lib/clevic/swing/cell_editor.rb +95 -0
  58. data/lib/clevic/swing/cell_renderer.rb +44 -0
  59. data/lib/clevic/swing/clipboard.rb +135 -0
  60. data/lib/clevic/swing/combo_delegate.rb +336 -0
  61. data/lib/clevic/swing/confirm_dialog.rb +57 -0
  62. data/lib/clevic/swing/delegate.rb +40 -0
  63. data/lib/clevic/swing/distinct_delegate.rb +30 -0
  64. data/lib/clevic/swing/extensions.rb +274 -0
  65. data/lib/clevic/swing/field.rb +35 -0
  66. data/lib/clevic/swing/relational_delegate.rb +48 -0
  67. data/lib/clevic/swing/row_header.rb +210 -0
  68. data/lib/clevic/swing/search_dialog.rb +230 -0
  69. data/lib/clevic/swing/selection_model.rb +90 -0
  70. data/lib/clevic/swing/set_delegate.rb +41 -0
  71. data/lib/clevic/swing/swing_table_index.rb +43 -0
  72. data/lib/clevic/swing/table_model.rb +200 -0
  73. data/lib/clevic/swing/table_view.rb +385 -0
  74. data/lib/clevic/swing/table_view_focus.rb +47 -0
  75. data/lib/clevic/swing/tag_delegate.rb +127 -0
  76. data/lib/clevic/swing/tag_editor.rb +101 -0
  77. data/lib/clevic/swing/text_area_delegate.rb +46 -0
  78. data/lib/clevic/swing/text_delegate.rb +31 -0
  79. data/lib/clevic/swing/ui/build.xml +74 -0
  80. data/lib/clevic/swing/ui/dist/README.TXT +33 -0
  81. data/lib/clevic/swing/ui/dist/lib/swing-layout-1.0.3.jar +0 -0
  82. data/lib/clevic/swing/ui/manifest.mf +3 -0
  83. data/lib/clevic/swing/ui/nbproject/build-impl.xml +731 -0
  84. data/lib/clevic/swing/ui/nbproject/genfiles.properties +8 -0
  85. data/lib/clevic/swing/ui/nbproject/private/config.properties +0 -0
  86. data/lib/clevic/swing/ui/nbproject/private/private.properties +6 -0
  87. data/lib/clevic/swing/ui/nbproject/private/private.xml +4 -0
  88. data/lib/clevic/swing/ui/nbproject/project.properties +70 -0
  89. data/lib/clevic/swing/ui/nbproject/project.xml +14 -0
  90. data/lib/clevic/swing/ui/src/SearchDialog.form +158 -0
  91. data/lib/clevic/swing/ui/src/SearchDialog.java +163 -0
  92. data/lib/clevic/swing/ui/src/TagEditor.form +106 -0
  93. data/lib/clevic/swing/ui/src/TagEditor.java +108 -0
  94. data/lib/clevic/swing/ui/src/resources/SearchDialog.properties +0 -0
  95. data/lib/clevic/table_index.rb +100 -0
  96. data/lib/clevic/table_model.rb +54 -425
  97. data/lib/clevic/table_searcher.rb +113 -116
  98. data/lib/clevic/table_view.rb +171 -399
  99. data/lib/clevic/table_view_paste.rb +199 -0
  100. data/lib/clevic/version.rb +3 -2
  101. data/lib/clevic/view.rb +94 -43
  102. data/models/accounts_models.rb +13 -13
  103. data/models/minimal_models.rb +5 -9
  104. data/models/times_models.rb +19 -14
  105. data/models/times_psql_models.rb +10 -0
  106. data/models/times_sqlite_models.rb +1 -8
  107. data/models/values_models.rb +2 -8
  108. data/tasks/clevic.rake +1 -1
  109. data/tasks/rdoc.rake +1 -5
  110. data/tasks/website.rake +1 -1
  111. data/test/test_cache_table.rb +15 -29
  112. data/test/test_helper.rb +14 -83
  113. data/test/test_order_attribute.rb +1 -1
  114. data/test/test_table_model.rb +0 -21
  115. data/test/test_table_searcher.rb +67 -61
  116. metadata +262 -78
  117. data/lib/clevic.rb +0 -4
  118. data/lib/clevic/db_options.rb +0 -112
  119. data/lib/clevic/delegates.rb +0 -386
@@ -0,0 +1,108 @@
1
+ /*
2
+ * To change this template, choose Tools | Templates
3
+ * and open the template in the editor.
4
+ */
5
+
6
+ /*
7
+ * TagEditor.java
8
+ *
9
+ * Created on Oct 8, 2010, 11:28:47 AM
10
+ */
11
+
12
+ /**
13
+ *
14
+ * @author panic
15
+ */
16
+ public class TagEditor extends javax.swing.JPanel {
17
+
18
+ /** Creates new form TagEditor */
19
+ public TagEditor() {
20
+ initComponents();
21
+ }
22
+
23
+ /** This method is called from within the constructor to
24
+ * initialize the form.
25
+ * WARNING: Do NOT modify this code. The content of this method is
26
+ * always regenerated by the Form Editor.
27
+ */
28
+ @SuppressWarnings("unchecked")
29
+ // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
30
+ private void initComponents() {
31
+
32
+ text_field = new javax.swing.JTextField();
33
+ item_list = new javax.swing.JList();
34
+ ok_button = new javax.swing.JButton();
35
+ cancel_button = new javax.swing.JButton();
36
+ add_button = new javax.swing.JButton();
37
+ remove_button = new javax.swing.JButton();
38
+
39
+ text_field.setName("text_field"); // NOI18N
40
+
41
+ item_list.setName("item_list"); // NOI18N
42
+
43
+ ok_button.setText("OK"); // NOI18N
44
+ ok_button.setToolTipText("Accept selection"); // NOI18N
45
+ ok_button.setName("ok_button"); // NOI18N
46
+
47
+ cancel_button.setText("Cancel"); // NOI18N
48
+ cancel_button.setName("cancel_button"); // NOI18N
49
+
50
+ add_button.setText("+"); // NOI18N
51
+ add_button.setToolTipText("Add a new item"); // NOI18N
52
+ add_button.setBorder(null);
53
+ add_button.setName("add_button"); // NOI18N
54
+
55
+ remove_button.setText("-"); // NOI18N
56
+ remove_button.setToolTipText("Remove selected item"); // NOI18N
57
+ remove_button.setBorder(null);
58
+ remove_button.setName("remove_button"); // NOI18N
59
+
60
+ org.jdesktop.layout.GroupLayout layout = new org.jdesktop.layout.GroupLayout(this);
61
+ this.setLayout(layout);
62
+ layout.setHorizontalGroup(
63
+ layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
64
+ .add(layout.createSequentialGroup()
65
+ .addContainerGap()
66
+ .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
67
+ .add(text_field, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 210, Short.MAX_VALUE)
68
+ .add(layout.createSequentialGroup()
69
+ .add(ok_button)
70
+ .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED, 82, Short.MAX_VALUE)
71
+ .add(cancel_button))
72
+ .add(layout.createSequentialGroup()
73
+ .add(add_button, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 27, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
74
+ .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
75
+ .add(remove_button, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 27, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE))
76
+ .add(item_list, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 207, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE))
77
+ .addContainerGap())
78
+ );
79
+ layout.setVerticalGroup(
80
+ layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
81
+ .add(layout.createSequentialGroup()
82
+ .addContainerGap()
83
+ .add(text_field, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
84
+ .addPreferredGap(org.jdesktop.layout.LayoutStyle.UNRELATED)
85
+ .add(item_list, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 199, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
86
+ .add(15, 15, 15)
87
+ .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE)
88
+ .add(add_button, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 20, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
89
+ .add(remove_button, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 19, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE))
90
+ .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED, 30, Short.MAX_VALUE)
91
+ .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE)
92
+ .add(ok_button)
93
+ .add(cancel_button))
94
+ .addContainerGap())
95
+ );
96
+ }// </editor-fold>//GEN-END:initComponents
97
+
98
+
99
+ // Variables declaration - do not modify//GEN-BEGIN:variables
100
+ public javax.swing.JButton add_button;
101
+ public javax.swing.JButton cancel_button;
102
+ public javax.swing.JList item_list;
103
+ public javax.swing.JButton ok_button;
104
+ public javax.swing.JButton remove_button;
105
+ public javax.swing.JTextField text_field;
106
+ // End of variables declaration//GEN-END:variables
107
+
108
+ }
@@ -0,0 +1,100 @@
1
+ require 'clevic/field_valuer.rb'
2
+
3
+ module Clevic
4
+ # to be included in something that responds to model, row, column
5
+ module TableIndex
6
+ include FieldValuer
7
+
8
+ # return the Clevic::Field for this index
9
+ def field
10
+ @field ||= model.field_for_index( self )
11
+ end
12
+
13
+ def dump
14
+ if valid?
15
+ <<-EOF
16
+ field: #{field_name} => #{field_value}
17
+ attribute: #{attribute.inspect} => #{attribute_value.inspect}
18
+ meta: #{meta.inspect}
19
+ EOF
20
+ else
21
+ 'invalid'
22
+ end
23
+ end
24
+
25
+ def prev
26
+ choppy( :row => row - 1 )
27
+ end
28
+
29
+ # return the attribute of the underlying entity corresponding
30
+ # to the column of this index
31
+ def attribute
32
+ model.attributes[column]
33
+ end
34
+
35
+ # returns the list of ModelColumn metadata
36
+ def meta
37
+ # use the optimised version
38
+ # TODO just use the model version instead?
39
+ field.meta
40
+ end
41
+
42
+ # return the table's field name. For associations, this would
43
+ # be suffixed with _id
44
+ def field_name
45
+ meta.name
46
+ end
47
+
48
+ # return the value of the field, it may be the _id value
49
+ def field_value
50
+ entity.send( field_name )
51
+ end
52
+
53
+ # the underlying entity
54
+ # TODO caching doesn't help with Qt because ModelIndex objects are
55
+ # extremely short-lived. Not sure about swing.
56
+ def entity
57
+ return nil if model.nil?
58
+ model.collection[row]
59
+ end
60
+
61
+ attr_writer :entity
62
+
63
+ # return true if validation failed for this indexes field
64
+ def has_errors?
65
+ # virtual fields don't have metadata
66
+ if meta.nil?
67
+ false
68
+ else
69
+ entity.errors.invalid?( field_name.to_sym )
70
+ end
71
+ end
72
+
73
+ # return a collection of errors. Unlike AR, this
74
+ # will always return an array that will have zero, one
75
+ # or many elements.
76
+ def errors
77
+ [ entity.errors[field_name.to_sym] ].flatten
78
+ end
79
+
80
+ # sort by row, then column
81
+ def <=>( other )
82
+ row_comp = self.row <=> other.row
83
+ if row_comp == 0
84
+ self.column <=> other.column
85
+ else
86
+ row_comp
87
+ end
88
+ end
89
+
90
+ def inspect
91
+ "#<TableIndex (#{row},#{column}) '#{raw_value rescue "no raw value: #{$!.message}"}'>"
92
+ end
93
+
94
+ # return a string (row,column)
95
+ # suitable for displaying to users, ie 1-based not 0-based
96
+ def rc
97
+ "(#{row+1},#{column+1})"
98
+ end
99
+ end
100
+ end
@@ -1,9 +1,5 @@
1
- require 'Qt4'
2
1
  require 'date'
3
2
 
4
- require 'qtext/flags.rb'
5
- require 'qtext/extensions.rb'
6
-
7
3
  require 'clevic/extensions.rb'
8
4
  require 'clevic/model_column'
9
5
 
@@ -12,12 +8,10 @@ module Clevic
12
8
  =begin rdoc
13
9
  An instance of Clevic::TableModel is constructed by Clevic::ModelBuilder from the
14
10
  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.
11
+ including the Clevic::Record module in a Sequel::Model subclass.
16
12
  =end
17
- class TableModel < Qt::AbstractTableModel
18
- include QtFlags
19
-
20
- # the CacheTable of Clevic::Record or ActiveRecord::Base objects
13
+ class TableModel
14
+ # the CacheTable of Clevic::Record or Sequel::Model objects
21
15
  attr_reader :collection
22
16
  alias_method :cache_table, :collection
23
17
 
@@ -35,25 +29,19 @@ class TableModel < Qt::AbstractTableModel
35
29
  attr_accessor :entity_view
36
30
  attr_accessor :builder
37
31
 
32
+ # If somre or all the entities in the collection is related to a single entity
33
+ # somewhere else, this is it. Not sure if this is the right
34
+ # way to do it, but try anyway.
35
+ attr_accessor :one
36
+
38
37
  def entity_class
39
38
  entity_view.entity_class
40
39
  end
41
40
 
42
- signals(
43
- # index where error occurred, value, message
44
- 'data_error(QModelIndex,QVariant,QString)'
45
- )
46
-
47
- def initialize( parent = nil )
48
- super
49
- @metadatas = []
50
- end
51
-
52
41
  def fields=( arr )
53
42
  @fields = arr
54
43
 
55
44
  # reset these
56
- @metadatas = []
57
45
  @labels = nil
58
46
  @attributes = nil
59
47
  end
@@ -79,87 +67,53 @@ class TableModel < Qt::AbstractTableModel
79
67
  def collection=( arr )
80
68
  @collection = arr
81
69
  # fill in an empty record for data entry
82
- if collection.size == 0 && auto_new?
83
- collection << entity_class.new
84
- end
85
- end
86
-
87
- def sort( col, order )
88
- puts 'sort'
89
- puts "col: #{col.inspect}"
90
- #~ Qt::AscendingOrder
91
- #~ Qt::DescendingOrder
92
- puts "order: #{order.inspect}"
93
- super
94
- end
95
-
96
- # this is called for read-only tables.
97
- def match( start_index, role, search_value, hits, match_flags )
98
- #~ Qt::MatchExactly 0 Performs QVariant-based matching.
99
- #~ Qt::MatchFixedString 8 Performs string-based matching. String-based comparisons are case-insensitive unless the MatchCaseSensitive flag is also specified.
100
- #~ Qt::MatchContains 1 The search term is contained in the item.
101
- #~ Qt::MatchStartsWith 2 The search term matches the start of the item.
102
- #~ Qt::MatchEndsWith 3 The search term matches the end of the item.
103
- #~ Qt::MatchCaseSensitive 16 The search is case sensitive.
104
- #~ Qt::MatchRegExp 4 Performs string-based matching using a regular expression as the search term.
105
- #~ Qt::MatchWildcard 5 Performs string-based matching using a string with wildcards as the search term.
106
- #~ 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.
107
- #~ super
108
- []
109
- end
110
-
111
- # cache metadata (ActiveRecord#column_for_attribute) because it's not going
112
- # to change over the lifetime of the table
113
- # if the column is an attribute, create a ModelColumn
114
- # TODO use ActiveRecord::Base.reflections instead
115
- def metadata( column )
116
- if @metadatas[column].nil?
117
- meta = entity_class.columns_hash[attributes[column].to_s]
118
- if meta.nil?
119
- meta = entity_class.columns_hash[ "#{attributes[column]}_id" ]
120
- if meta.nil?
121
- return nil
122
- else
123
- @metadatas[column] = ModelColumn.new( attributes[column], :association, meta )
124
- end
125
- else
126
- @metadatas[column] = meta
127
- end
128
- end
129
- @metadatas[column]
70
+ add_new_item if collection.empty? && auto_new?
130
71
  end
131
72
 
132
73
  # add a new item, and set defaults from the Clevic::View
74
+ # add_new_item_start and add_new_item_end are provided by
75
+ # the GUI framework-specific class
133
76
  def add_new_item
134
- begin_insert_rows( Qt::ModelIndex.invalid, row_count, row_count )
135
- # set default values without triggering changed
77
+ add_new_item_start
78
+
136
79
  entity = entity_class.new
80
+
81
+ # set default values without triggering changed
137
82
  fields.each do |f|
138
- unless f.default.nil?
139
- f.set_default_for( entity )
140
- end
83
+ f.set_default_for( entity ) unless f.default.nil?
141
84
  end
142
85
 
143
86
  collection << entity
144
87
 
145
- end_insert_rows
88
+ add_new_item_end
146
89
  end
147
90
 
148
91
  # rows is a collection of integers specifying row indices to remove
149
92
  def remove_rows( rows )
150
- # delete from the end to avoid holes affecting the indexing
151
- rows.uniq.sort.reverse.each do |index|
152
- # remove the item from the collection
153
- # NOTE call this within each iteration because
154
- # the rows array may be non-contiguous
155
- begin_remove_rows( Qt::ModelIndex.invalid, index, index )
156
- removed = collection.delete_at( index )
157
- end_remove_rows
158
- # destroy the db object, and its associated table row
159
- removed.destroy
93
+ # don't delete rows twice
94
+ # put the entities to be removed in a separate collection
95
+ # I can't figure out why the collection fails when they're
96
+ # removed directly
97
+ rows_in_order = rows.uniq.sort
98
+ removals = rows_in_order.map do |index|
99
+ collection[index]
100
+ end
101
+
102
+ remove_notify( rows_in_order ) do
103
+ removals.each do |to_remove, index|
104
+ # destroy the db object, and its associated table row
105
+ to_remove.destroy unless to_remove.nil? || to_remove.new?
106
+ end
160
107
  end
161
108
 
109
+ # because it's too bloody complicated to figure out which items
110
+ # were deleted and then remove them from the collection. And there
111
+ # most likely isn't a big hit for doing this, unless there's a lot
112
+ # of cached calcuation in the entities.
113
+ self.collection = collection.renew
114
+
162
115
  # create a new row if auto_new is on
116
+ # should really be in a signal handler
163
117
  add_new_item if collection.empty? && auto_new?
164
118
  end
165
119
 
@@ -170,7 +124,7 @@ class TableModel < Qt::AbstractTableModel
170
124
  if item.changed?
171
125
  if item.valid?
172
126
  retval = item.save
173
- emit headerDataChanged( Qt::Vertical, index.row, index.row )
127
+ data_changed( index )
174
128
  retval
175
129
  else
176
130
  false
@@ -179,322 +133,30 @@ class TableModel < Qt::AbstractTableModel
179
133
  # AR model not changed
180
134
  true
181
135
  end
182
- end
183
-
184
- def rowCount( parent = nil )
185
- collection.size
186
- end
187
-
188
- # Not looked up or aliased properly by Qt bindings
189
- def row_count
190
- collection.size
191
- end
192
-
193
- def columnCount( parent = nil )
194
- fields.size
195
- end
196
-
197
- # Not looked up or aliased properly by Qt bindings
198
- def column_count
199
- fields.size
200
- end
201
-
202
- def flags( model_index )
203
- retval = super
204
-
205
- # sometimes this actually happens.
206
- # TODO probably a bug in the combo editor exit code
207
- return retval if model_index.column >= columnCount
208
-
209
- # TODO don't return IsEditable if the model is read-only
210
- if model_index.metadata.type == :boolean
211
- retval = item_boolean_flags
212
- end
213
-
214
- unless model_index.field.read_only? || model_index.entity.readonly? || read_only?
215
- retval |= qt_item_is_editable.to_i
216
- end
217
- retval
136
+ rescue
137
+ puts $!.message
138
+ puts $!.backtrace
139
+ emit_data_error( index, nil, $!.message )
218
140
  end
219
141
 
220
142
  def reload_data( options = nil )
221
143
  # renew cache. All records will be dropped and reloaded.
222
- self.collection = self.collection.renew( options )
144
+ self.collection = self.cache_table.renew( options )
223
145
  # tell the UI we had a major data change
224
146
  reset
225
147
  end
226
148
 
227
- # values for horizontal and vertical headers
228
- def headerData( section, orientation, role )
229
- value =
230
- case role
231
- when qt_display_role
232
- case orientation
233
- when Qt::Horizontal
234
- labels[section]
235
- when Qt::Vertical
236
- # don't force a fetch from the db
237
- if collection.cached_at?( section )
238
- collection[section].id
239
- else
240
- section
241
- end
242
- end
243
-
244
- when qt_text_alignment_role
245
- case orientation
246
- when Qt::Vertical
247
- Qt::AlignRight | Qt::AlignVCenter
248
- end
249
-
250
- when Qt::SizeHintRole
251
- # anything other than nil here makes the headers disappear.
252
- nil
253
-
254
- when qt_tooltip_role
255
- case orientation
256
- when Qt::Horizontal
257
- fields[section].tooltip
258
-
259
- when Qt::Vertical
260
- case
261
- when !collection[section].errors.empty?
262
- 'Invalid data'
263
- when collection[section].changed?
264
- 'Unsaved changes'
265
- end
266
- end
267
-
268
- when qt_background_role
269
- if orientation == Qt::Vertical
270
- item = collection[section]
271
- case
272
- when !item.errors.empty?
273
- Qt::Color.new( 'orange' )
274
- when item.changed?
275
- Qt::Color.new( 'yellow' )
276
- end
277
- end
278
-
279
- else
280
- #~ puts "headerData section: #{section}, role: #{const_as_string(role)}" if $options[:debug]
281
- nil
282
- end
283
-
284
- return value.to_variant
285
- end
286
-
287
- # Provide data to UI.
288
- def data( index, role = qt_display_role )
289
- #~ puts "data for index: #{index.inspect}, field #{index.field.attribute.inspect} and role: #{const_as_string role}"
290
- begin
291
- retval =
292
- case role
293
- when qt_display_role
294
- # boolean values generally don't have text next to them in this context
295
- # check this explicitly to avoid fetching the entity from
296
- # the model's collection (and maybe db) when we
297
- # definitely don't need to
298
- unless index.metadata.type == :boolean
299
- value = index.gui_value
300
- index.field.do_format( value ) unless value.nil?
301
- end
302
-
303
- when qt_edit_role
304
- # see comment for qt_display_role
305
- unless index.metadata.type == :boolean
306
- value = index.gui_value
307
- index.field.do_edit_format( value ) unless value.nil?
308
- end
309
-
310
- when qt_checkstate_role
311
- if index.metadata.type == :boolean
312
- index.gui_value ? qt_checked : qt_unchecked
313
- end
314
-
315
- when qt_text_alignment_role
316
- case index.field.alignment
317
- when :left; qt_alignleft
318
- when :right; qt_alignright
319
- when :centre; qt_aligncenter
320
- when :justified; qt_alignjustified
321
- end
322
-
323
- # just here to make debug output quieter
324
- when qt_size_hint_role;
325
-
326
- # show field with a red background if there's an error
327
- when qt_background_role
328
- index.field.background_for( index.entity ) || Qt::Color.new( 'red' ) if index.has_errors?
329
-
330
- when qt_font_role;
331
-
332
- when qt_foreground_role
333
- index.field.foreground_for( index.entity ) ||
334
- if index.field.read_only? || index.entity.readonly? || read_only?
335
- Qt::Color.new( 'dimgray' )
336
- end
337
-
338
- when qt_decoration_role;
339
- index.field.decoration_for( index.entity )
340
-
341
- when qt_tooltip_role
342
- case
343
- # show ActiveRecord validation errors
344
- when index.has_errors?
345
- index.errors.join("\n")
346
-
347
- # provide a tooltip when an empty relational field is encountered
348
- # TODO should be part of field definition
349
- when index.metadata.type == :association
350
- index.field.delegate.if_empty_message
351
-
352
- # read-only field
353
- when index.field.read_only?
354
- 'Read-only'
355
-
356
- else
357
- index.field.tooltip_for( index.entity )
358
- end
359
- else
360
- puts "data index: #{index}, role: #{const_as_string(role)}" if $options[:debug]
361
- nil
362
- end
363
-
364
- # return a variant
365
- #~ puts "retval: #{retval.inspect}"
366
- retval.to_variant
367
- rescue Exception => e
368
- puts "#{index.inspect} #{value.inspect} #{index.entity.inspect} for and role: #{const_as_string role}"
369
- puts e.message
370
- puts e.backtrace
371
- nil.to_variant
372
- end
373
- end
374
-
375
- # data sent from UI
376
- # return true if conversion from variant was successful,
377
- # or false if something went wrong.
378
- def setData( index, variant, role = qt_edit_role )
379
- if index.valid?
380
- case role
381
- when qt_edit_role
382
- # Don't allow the primary key to be changed
383
- return false if index.attribute == entity_class.primary_key.to_sym
384
-
385
- if ( index.column < 0 || index.column >= column_count )
386
- raise "invalid column #{index.column}"
387
- end
388
-
389
- type = index.metadata.type
390
- value = variant.value
391
- #~ puts "#{type.inspect} is #{value}"
392
-
393
- # translate the value from the ui to something that
394
- # the AR model will understand
395
- begin
396
- index.attribute_value =
397
- case
398
- when value.class.name == 'Qt::Date'
399
- Date.new( value.year, value.month, value.day )
400
-
401
- when value.class.name == 'Qt::Time'
402
- Time.new( value.hour, value.min, value.sec )
403
-
404
- # allow flexibility in entering dates. For example
405
- # 16jun, 16-jun, 16 jun, 16 jun 2007 would be accepted here
406
- # TODO need to be cleverer about which year to use
407
- # for when you're entering 16dec and you're in the next
408
- # year
409
- when [:date,:datetime].include?( type ) && value =~ %r{^(\d{1,2})[ /-]?(\w{3})$}
410
- Date.parse( "#$1 #$2 #{Time.now.year.to_s}" )
411
-
412
- # if a digit only is entered, fetch month and year from
413
- # previous row
414
- when [:date,:datetime].include?( type ) && value =~ %r{^(\d{1,2})$}
415
- previous_entity = collection[index.row - 1]
416
- # year,month,day
417
- Date.new( previous_entity.date.year, previous_entity.date.month, $1.to_i )
418
-
419
- # this one is mostly to fix date strings that have come
420
- # out of the db and been formatted
421
- when [:date,:datetime].include?( type ) && value =~ %r{^(\d{2})[ /-](\w{3})[ /-](\d{2})$}
422
- Date.parse( "#$1 #$2 20#$3" )
423
-
424
- # allow lots of flexibility in entering times
425
- # 01:17, 0117, 117, 1 17, are all accepted
426
- when type == :time && value =~ %r{^(\d{1,2}).?(\d{2})$}
427
- Time.parse( "#$1:#$2" )
428
-
429
- # remove thousand separators, allow for space and comma
430
- # instead of . as a decimal separator
431
- when type == :decimal
432
- # do various transforms
433
- value =
434
- case
435
- # accept a space or a comma instead of a . for floats
436
- when value =~ /(.*?)(\d)[ ,](\d{2})$/
437
- "#$1#$2.#$3"
438
- else
439
- value
440
- end
441
-
442
- # strip remaining commas
443
- value.gsub( ',', '' )
444
-
445
- else
446
- value
447
- end
448
-
449
- data_changed( index )
450
- # value conversion was successful
451
- true
452
- rescue Exception => e
453
- puts e.backtrace.join( "\n" )
454
- puts e.message
455
- emit data_error( index, variant, e.message )
456
- # value conversion was not successful
457
- false
458
- end
459
-
460
- when qt_checkstate_role
461
- if index.metadata.type == :boolean
462
- index.entity.toggle( index.attribute )
463
- true
464
- else
465
- false
466
- end
467
-
468
- # user-defined role
469
- # TODO this only works with single-dotted paths
470
- when qt_paste_role
471
- if index.metadata.type == :association
472
- field = index.field
473
- association_class = field.class_name.constantize
474
- candidates = association_class.find( :all, :conditions => [ "#{field.attribute_path[1]} = ?", variant.value ] )
475
- case candidates.size
476
- when 0; puts "No match for #{variant.value}"
477
- when 1; index.attribute_value = candidates[0]
478
- else; puts "Too many for #{variant.value}"
479
- end
480
- else
481
- index.attribute_value = variant.value
482
- end
483
- true
484
-
485
- else
486
- puts "role: #{role.inspect}"
487
- true
488
-
489
- end
490
- else
491
- false
492
- end
493
- end
494
-
495
- # return a set of indexes that match the search criteria
149
+ # return a collection of indexes that match the search criteria
150
+ # at the moment this only returns the first index found
151
+ # TODO could handle dataset creation better
496
152
  def search( start_index, search_criteria )
497
- entity = collection.search( start_index.field, search_criteria, start_index.entity )
153
+ ordered_dataset = entity_class.dataset.order( *cache_table.order_attributes.map{|oa| oa.attribute.to_sym.send( oa.direction ) } )
154
+ searcher = Clevic::TableSearcher.new(
155
+ ordered_dataset,
156
+ search_criteria,
157
+ start_index.field
158
+ )
159
+ entity = searcher.search( start_index.entity )
498
160
 
499
161
  # return matched indexes
500
162
  if entity != nil
@@ -536,39 +198,6 @@ class TableModel < Qt::AbstractTableModel
536
198
  end
537
199
  end
538
200
 
539
- # A rubyish way of doing dataChanged
540
- # - if args has one element, it's either a single ModelIndex
541
- # or something that understands top_left and bottom_right. These
542
- # will be turned into a ModelIndex by calling create_index
543
- # - if args has two element, assume it's a two ModelIndex instances
544
- # - otherwise create a new DataChange and pass it to the block.
545
- def data_changed( *args, &block )
546
- case args.size
547
- when 1
548
- arg = args.first
549
- if ( arg.respond_to?( :top_left ) && arg.respond_to?( :bottom_right ) ) || arg.is_a?( Qt::ItemSelectionRange )
550
- # object is a DataChange, or a SelectionRange
551
- top_left_index = create_index( arg.top_left.row, arg.top_left.column )
552
- bottom_right_index = create_index( arg.bottom_right.row, arg.bottom_right.column )
553
- emit dataChanged( top_left_index, bottom_right_index )
554
- else
555
- # assume it's a ModelIndex
556
- emit dataChanged( arg, arg )
557
- end
558
-
559
- when 2
560
- emit dataChanged( args.first, args.last )
561
-
562
- else
563
- unless block.nil?
564
- change = DataChange.new
565
- block.call( change )
566
- # recursive call
567
- data_changed( change )
568
- end
569
- end
570
- end
571
-
572
201
  end
573
202
 
574
203
  end #module