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 @@
1
+
@@ -0,0 +1,35 @@
1
+ require 'clevic/qt/combo_delegate.rb'
2
+
3
+ module Clevic
4
+
5
+ # Provide a list of all values in this field,
6
+ # and allow new values to be entered.
7
+ # :frequency can be set as an option. Boolean. If it's true
8
+ # the options are sorted in order of most frequently used first.
9
+ class DistinctDelegate < ComboDelegate
10
+
11
+ def needs_combo?
12
+ # works except when there is a '' in the column
13
+ entity_class.adaptor.count( attribute.to_s, find_options ) > 0
14
+ end
15
+
16
+ def populate_current( editor, model_index )
17
+ # already done in the SQL query in populate, so don't even check
18
+ end
19
+
20
+ def populate( editor, model_index )
21
+ # we only use the first column, so use the second
22
+ # column to sort by, since SQL requires the order by clause
23
+ # to be in the select list where distinct is involved
24
+ entity_class.adaptor.attribute_list( attribute, model_index.attribute_value, field.description, field.frequency, find_options ) do |row|
25
+ value = row[attribute]
26
+ editor.add_item( value, value.to_variant )
27
+ end
28
+ end
29
+
30
+ def translate_from_editor_text( editor, text )
31
+ text
32
+ end
33
+ end
34
+
35
+ end
@@ -0,0 +1,52 @@
1
+ require 'set'
2
+
3
+ require 'qtext/flags.rb'
4
+ require 'clevic/table_index.rb'
5
+
6
+ # convenience methods
7
+ module Qt
8
+ PasteRole = UserRole + 1 unless defined?( PasteRole )
9
+
10
+ class AbstractItemDelegate
11
+ # overridden in EntryDelegate subclasses
12
+ def full_edit
13
+ end
14
+ end
15
+
16
+ # This provides a bunch of methods to get easy access to the entity
17
+ # and it's values directly from the index without having to keep
18
+ # asking the model and jumping through other unncessary hoops
19
+ class ModelIndex
20
+ include Clevic::TableIndex
21
+ end
22
+
23
+ class ItemSelectionModel
24
+ # return an array of integer indexes for currently selected rows
25
+ def row_indexes
26
+ selected_indexes.inject(Set.new) do |set,index|
27
+ set << index.row
28
+ end.to_a
29
+ end
30
+
31
+ # return a collection of selection ranges
32
+ # in Qt this means an ItemSelection instance
33
+ def ranges
34
+ selection
35
+ end
36
+
37
+ def single_cell?
38
+ ranges.size == 1 && ranges.first.single_cell?
39
+ end
40
+ end
41
+
42
+ # implement accepted? and rejected? for TableView#confirm_dialog and friends
43
+ class MessageBox
44
+ def accepted?
45
+ [ Qt::Dialog::Accepted, Qt::MessageBox::Yes, Qt::MessageBox::Ok ].include?( result )
46
+ end
47
+
48
+ def rejected?
49
+ [ Qt::Dialog::Rejected, Qt::MessageBox::No, Qt::MessageBox::Cancel ].include?( result )
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,18 @@
1
+ module Clevic
2
+
3
+ class Field
4
+ # Convert something that responds to to_s into a Qt::Color,
5
+ # or just return the argument if it's already a Qt::Color
6
+ def string_or_color( s_or_c )
7
+ case s_or_c
8
+ when NilClass
9
+ nil
10
+ when Qt::Color
11
+ s_or_c
12
+ else
13
+ Qt::Color.new( s_or_c.to_s )
14
+ end
15
+ end
16
+ end
17
+
18
+ end
@@ -13,8 +13,10 @@ module Clevic
13
13
  class ItemDelegate < Qt::ItemDelegate
14
14
  attr_reader :field
15
15
 
16
- def initialize( parent, field )
17
- super( parent )
16
+ def initialize( field )
17
+ raise "field is nil" if field.nil?
18
+ # pass nil as Qt object's parent. Will set parent later.
19
+ super( nil )
18
20
  @field = field
19
21
  end
20
22
 
@@ -40,8 +42,10 @@ class ItemDelegate < Qt::ItemDelegate
40
42
  end
41
43
 
42
44
  # This catches the event that begins the edit process.
43
- #~ def editorEvent ( event, model, style_option_view_item, model_index )
44
- #~ end
45
+ def editorEvent ( event, model, style_option_view_item, model_index )
46
+ parent.before_edit_index = model_index
47
+ super
48
+ end
45
49
 
46
50
  # This is called when one of the EditTriggers is pressed. So
47
51
  # it's only good for opening a generic keystroke editor, not
@@ -0,0 +1,87 @@
1
+ require 'clevic/qt/combo_delegate.rb'
2
+
3
+ module Clevic
4
+
5
+ # Edit a relation from an id and display a list of relevant entries.
6
+ #
7
+ # attribute is the method to call on the row entity to retrieve the related object.
8
+ #
9
+ # The ids of the model objects are stored in the item data
10
+ # and the item text is fetched from them using attribute_path.
11
+ class RelationalDelegate < ComboDelegate
12
+ def initialize( field )
13
+ super
14
+ unless find_options[:conditions].nil?
15
+ find_options[:conditions].gsub!( /true/, field.related_class.adaptor.quoted_true )
16
+ find_options[:conditions].gsub!( /false/, field.related_class.adaptor.quoted_false )
17
+ end
18
+ end
19
+
20
+ def needs_combo?
21
+ field.related_class.adaptor.count( :conditions => find_options[:conditions] ) > 0
22
+ end
23
+
24
+ def empty_set_message
25
+ "There must be records in #{field.related_class.name.humanize} for this field to be editable."
26
+ end
27
+
28
+ # add the current item, unless it's already in the combo data
29
+ def populate_current( editor, model_index )
30
+ # always add the current selection, if it isn't already there
31
+ # and it makes sense. This is to make sure that if the list
32
+ # is filtered, we always have the current value if the filter
33
+ # excludes it
34
+ unless model_index.nil?
35
+ item = model_index.attribute_value
36
+ if item
37
+ item_index = editor.find_data( item.id.to_variant )
38
+ if item_index == -1
39
+ add_to_list( editor, model_index, item )
40
+ end
41
+ end
42
+ end
43
+ end
44
+
45
+ def populate( editor, model_index )
46
+ # add set of all possible related entities
47
+ field.related_class.adaptor.find( :all, find_options ).each do |x|
48
+ add_to_list( editor, model_index, x )
49
+ end
50
+ end
51
+
52
+ def add_to_list( editor, model_index, item )
53
+ editor.add_item( model_index.field.transform_attribute( item ), item.id.to_variant )
54
+ end
55
+
56
+ # send data to the editor
57
+ def setEditorData( editor, model_index )
58
+ if is_combo?( editor )
59
+ unless model_index.attribute_value.nil?
60
+ editor.current_index = editor.find_data( model_index.attribute_value.id.to_variant )
61
+ end
62
+ editor.line_edit.select_all
63
+ end
64
+ end
65
+
66
+ # don't allow new values
67
+ def restricted?
68
+ true
69
+ end
70
+
71
+ # return an AR entity object
72
+ def translate_from_editor_text( editor, text )
73
+ item_index = editor.find_text( text )
74
+
75
+ # fetch record id from editor item_data
76
+ item_data = editor.item_data( item_index )
77
+ if item_data.valid?
78
+ # get the entity it refers to, if there is one
79
+ # use find_by_id so that if it's not found, nil will
80
+ # be returned
81
+ field.related_class.adaptor.find( item_data.to_int )
82
+ end
83
+ end
84
+
85
+ end
86
+
87
+ end
@@ -1,5 +1,5 @@
1
1
  require 'Qt4'
2
- require 'clevic/ui/search_dialog_ui.rb'
2
+ require 'clevic/qt/ui/search_dialog_ui.rb'
3
3
  require 'qtext/flags.rb'
4
4
 
5
5
  module Clevic
@@ -59,16 +59,6 @@ module Clevic
59
59
  search_combo.add_item( search_combo.current_text )
60
60
  end
61
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
62
  retval
73
63
  end
74
64
 
@@ -0,0 +1,44 @@
1
+ require 'clevic/qt/combo_delegate.rb'
2
+
3
+ module Clevic
4
+
5
+ # A Combo box which allows a set of values. May or may not
6
+ # be restricted to the set.
7
+ class SetDelegate < ComboDelegate
8
+ # options must contain a :set => [ ... ] to specify the set of values.
9
+ def initialize( field )
10
+ raise "RestrictedDelegate must have a :set in options" if field.set.nil?
11
+ super
12
+ end
13
+
14
+ def needs_combo?
15
+ true
16
+ end
17
+
18
+ def restricted?
19
+ field.restricted || false
20
+ end
21
+
22
+ def populate( editor, model_index )
23
+ field.set_for( model_index.entity ).each do |item|
24
+ if item.is_a?( Array )
25
+ # this is a hash-like set, so use key as db value
26
+ # and value as display value
27
+ editor.add_item( item.last, item.first.to_variant )
28
+ else
29
+ editor.add_item( item, item.to_variant )
30
+ end
31
+ end
32
+ end
33
+
34
+ def createEditor( parent_widget, style_option_view_item, model_index )
35
+ editor = super
36
+
37
+ # the set is provided, so never insert things
38
+ editor.insert_policy = Qt::ComboBox::NoInsert
39
+ editor
40
+ end
41
+
42
+ end
43
+
44
+ end
@@ -0,0 +1,331 @@
1
+ require 'Qt4'
2
+ require 'date'
3
+
4
+ require 'andand'
5
+
6
+ require 'qtext/flags.rb'
7
+ require 'qtext/extensions.rb'
8
+
9
+ require 'clevic/extensions.rb'
10
+ require 'clevic/qt/extensions.rb'
11
+ require 'clevic/model_column'
12
+
13
+ module Clevic
14
+
15
+ =begin rdoc
16
+ An instance of Clevic::TableModel is constructed by Clevic::ModelBuilder from the
17
+ UI definition in a Clevic::View, or from the default Clevic::View created by
18
+ including the Clevic::Record module in a Sequel::Model subclass.
19
+ =end
20
+ class TableModel < Qt::AbstractTableModel
21
+ include QtFlags
22
+
23
+ signals(
24
+ # index where error occurred, value, message
25
+ 'data_error(QModelIndex,QVariant,QString)'
26
+ )
27
+
28
+ def emit_data_error( index, data, string )
29
+ emit data_error( index, data.to_variant, string )
30
+ end
31
+
32
+ def initialize( parent = nil )
33
+ super
34
+ end
35
+
36
+ # add a new item, and set defaults from the Clevic::View
37
+ def add_new_item_start
38
+ begin_insert_rows( Qt::ModelIndex.invalid, row_count, row_count )
39
+ end
40
+
41
+ def add_new_item_end
42
+ # notify listeners that the model has changed
43
+ end_insert_rows
44
+ end
45
+
46
+ def remove_notify( rows, &block )
47
+ begin_remove_rows( Qt::ModelIndex.invalid, rows.first, rows.last )
48
+ # do the removal
49
+ yield
50
+ end_remove_rows
51
+ end
52
+
53
+ # save the AR model at the given index, if it's dirty
54
+ def update_vertical_header( index )
55
+ raise "preferably use data_changed here, if possible"
56
+ emit headerDataChanged( Qt::Vertical, index.row, index.row )
57
+ end
58
+
59
+ def rowCount( parent = nil )
60
+ collection.size
61
+ end
62
+
63
+ # Not looked up or aliased properly by Qt bindings
64
+ def row_count
65
+ collection.size
66
+ end
67
+
68
+ def columnCount( parent = nil )
69
+ fields.size
70
+ end
71
+
72
+ # Not looked up or aliased properly by Qt bindings
73
+ def column_count
74
+ fields.size
75
+ end
76
+
77
+ def flags( model_index )
78
+ retval = super
79
+
80
+ # sometimes this actually happens.
81
+ # TODO probably a bug in the combo editor exit code
82
+ return retval if model_index.column >= columnCount
83
+
84
+ # TODO don't return IsEditable if the model is read-only
85
+ if model_index.meta.type == :boolean
86
+ retval = item_boolean_flags
87
+ end
88
+
89
+ unless model_index.field.read_only? || model_index.entity.andand.readonly? || read_only?
90
+ retval |= qt_item_is_editable.to_i
91
+ end
92
+ retval
93
+ end
94
+
95
+ # values for horizontal and vertical headers
96
+ def headerData( section, orientation, role )
97
+ value =
98
+ case role
99
+ when qt_display_role
100
+ case orientation
101
+ when Qt::Horizontal
102
+ labels[section]
103
+ when Qt::Vertical
104
+ # don't force a fetch from the db
105
+ if collection.cached_at?( section )
106
+ collection[section].id
107
+ else
108
+ section
109
+ end
110
+ end
111
+
112
+ when qt_text_alignment_role
113
+ case orientation
114
+ when Qt::Vertical
115
+ Qt::AlignRight | Qt::AlignVCenter
116
+ end
117
+
118
+ when Qt::SizeHintRole
119
+ # anything other than nil here makes the headers disappear.
120
+ nil
121
+
122
+ when qt_tooltip_role
123
+ case orientation
124
+ when Qt::Horizontal
125
+ fields[section].tooltip
126
+
127
+ when Qt::Vertical
128
+ case
129
+ when !collection[section].errors.empty?
130
+ 'Invalid data'
131
+ when collection[section].changed?
132
+ 'Unsaved changes'
133
+ end
134
+ end
135
+
136
+ when qt_background_role
137
+ if orientation == Qt::Vertical
138
+ item = collection[section]
139
+ case
140
+ when !item.errors.empty?
141
+ Qt::Color.new( 'orange' )
142
+ when item.changed?
143
+ Qt::Color.new( 'yellow' )
144
+ end
145
+ end
146
+
147
+ else
148
+ #~ puts "headerData section: #{section}, role: #{const_as_string(role)}" if $options[:debug]
149
+ nil
150
+ end
151
+
152
+ return value.to_variant
153
+ end
154
+
155
+ # Provide data to UI.
156
+ def data( index, role = qt_display_role )
157
+ #~ puts "data for index: #{index.inspect}, field #{index.field.attribute.inspect} and role: #{const_as_string role}"
158
+ begin
159
+ case role
160
+ when qt_display_role
161
+ # boolean values generally don't have text next to them in this context
162
+ # check this explicitly to avoid fetching the entity from
163
+ # the model's collection (and maybe db) when we
164
+ # definitely don't need to
165
+ unless index.meta.type == :boolean
166
+ value = index.display_value
167
+ end
168
+
169
+ when qt_edit_role
170
+ # see comment for qt_display_role
171
+ unless index.meta.type == :boolean
172
+ value = index.edit_value
173
+ end
174
+
175
+ when qt_checkstate_role
176
+ if index.meta.type == :boolean
177
+ index.raw_value ? qt_checked : qt_unchecked
178
+ end
179
+
180
+ when qt_text_alignment_role
181
+ case index.field.alignment
182
+ when :left; qt_alignleft
183
+ when :right; qt_alignright
184
+ when :centre; qt_aligncenter
185
+ when :justified; qt_alignjustified
186
+ end
187
+
188
+ # just here to make debug output quieter
189
+ when qt_size_hint_role;
190
+
191
+ # show field with a red background if there's an error
192
+ when qt_background_role
193
+ index.field.background_for( index.entity ) || Qt::Color.new( 'red' ) if index.has_errors?
194
+
195
+ when qt_font_role;
196
+
197
+ when qt_foreground_role
198
+ index.field.foreground_for( index.entity ) ||
199
+ if index.field.read_only? || index.entity.andand.readonly? || read_only?
200
+ Qt::Color.new( 'dimgray' )
201
+ end
202
+
203
+ when qt_decoration_role;
204
+ index.field.decoration_for( index.entity )
205
+
206
+ when qt_tooltip_role
207
+ index.tooltip
208
+
209
+ else
210
+ puts "data index: #{index}, role: #{const_as_string(role)}" if $options[:debug]
211
+ nil
212
+ # return the variant
213
+ end.to_variant
214
+
215
+ rescue Exception => e
216
+ # this can generate a lot of errors from view code, so don't emit data_error every one
217
+ puts "#{entity_view.class.name}.#{index.field.id}: #{index.inspect} for role: #{const_as_string role} #{value.inspect} #{index.entity.inspect}"
218
+ puts e.message
219
+ puts e.backtrace
220
+ nil.to_variant
221
+ end
222
+ end
223
+
224
+ # data sent from UI
225
+ # return true if conversion from variant was successful,
226
+ # or false if something went wrong.
227
+ def setData( index, variant, role = qt_edit_role )
228
+ if index.valid?
229
+ case role
230
+ when qt_edit_role
231
+ # Don't allow the primary key to be changed
232
+ return false if index.attribute == entity_class.primary_key.to_sym
233
+
234
+ if ( index.column < 0 || index.column >= column_count )
235
+ raise "invalid column #{index.column}"
236
+ end
237
+
238
+ begin
239
+ index.attribute_value =
240
+ case
241
+ when value.class.name == 'Qt::Date'
242
+ Date.new( value.year, value.month, value.day )
243
+
244
+ when value.class.name == 'Qt::Time'
245
+ Time.new( value.hour, value.min, value.sec )
246
+
247
+ else
248
+ translate_to_db_object( index, variant.value )
249
+ end
250
+
251
+ # value conversion was successful
252
+ data_changed( table_index )
253
+ true
254
+ rescue Exception => e
255
+ puts e.backtrace.join( "\n" )
256
+ puts e.message
257
+ emit data_error( index, variant, e.message )
258
+ # value conversion was not successful
259
+ false
260
+ end
261
+
262
+ when qt_checkstate_role
263
+ if index.meta.type == :boolean
264
+ index.entity.toggle( index.attribute )
265
+ true
266
+ else
267
+ false
268
+ end
269
+
270
+ # user-defined role
271
+ # TODO this only works with single-dotted paths
272
+ when qt_paste_role
273
+ if index.meta.type == :association
274
+ field = index.field
275
+ candidates = field.related_class.find( :all, :conditions => [ "#{field.attribute_path[1]} = ?", variant.value ] )
276
+ case candidates.size
277
+ when 0; puts "No match for #{variant.value}"
278
+ when 1; index.attribute_value = candidates[0]
279
+ else; puts "Too many for #{variant.value}"
280
+ end
281
+ else
282
+ index.attribute_value = variant.value
283
+ end
284
+ true
285
+
286
+ else
287
+ puts "role: #{role.inspect}"
288
+ true
289
+
290
+ end
291
+ else
292
+ false
293
+ end
294
+ end
295
+
296
+ # A rubyish way of doing dataChanged
297
+ # - if args has one element, it's either a single ModelIndex
298
+ # or something that understands top_left and bottom_right. These
299
+ # will be turned into a ModelIndex by calling create_index
300
+ # - if args has two element, assume it's a two ModelIndex instances
301
+ # - otherwise create a new DataChange and pass it to the block.
302
+ def data_changed( *args, &block )
303
+ case args.size
304
+ when 1
305
+ arg = args.first
306
+ if ( arg.respond_to?( :top_left ) && arg.respond_to?( :bottom_right ) ) || arg.is_a?( Qt::ItemSelectionRange )
307
+ # object is a DataChange, or a SelectionRange
308
+ top_left_index = create_index( arg.top_left.row, arg.top_left.column )
309
+ bottom_right_index = create_index( arg.bottom_right.row, arg.bottom_right.column )
310
+ emit dataChanged( top_left_index, bottom_right_index )
311
+ else
312
+ # assume it's a ModelIndex
313
+ emit dataChanged( arg, arg )
314
+ end
315
+
316
+ when 2
317
+ emit dataChanged( args.first, args.last )
318
+
319
+ else
320
+ unless block.nil?
321
+ change = DataChange.new
322
+ block.call( change )
323
+ # recursive call
324
+ data_changed( change )
325
+ end
326
+ end
327
+ end
328
+
329
+ end
330
+
331
+ end #module