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
data/lib/clevic.rb DELETED
@@ -1,4 +0,0 @@
1
- # This provides enough to define UIs.
2
- require 'clevic/db_options.rb'
3
- require 'clevic/record.rb'
4
- require 'clevic/view.rb'
@@ -1,112 +0,0 @@
1
- require 'active_record'
2
-
3
- module Clevic
4
- =begin rdoc
5
- This class is intended to set up db options for a ActiveRecord
6
- connection to a particular database. Like this:
7
-
8
- Clevic::DbOptions.connect( $options ) do
9
- database :accounts
10
- adapter :postgresql
11
- username 'accounts_user'
12
- end
13
-
14
- When the block ends, a check is done to see that the :database key
15
- exists. If not, an exception is thrown. Finally the relevant calls to
16
- establish the ActiveRecord connection are performed.
17
-
18
- Method calls are translated to insertions into a hash with the same
19
- key as the method being called. The hash is initialised
20
- with the options value passed in (in this case $options).
21
- Values have to_s called on them so they can be symbols or strings.
22
-
23
- #--
24
- TODO inherit from HashCollector
25
- =end
26
- class DbOptions
27
- attr_reader :options
28
-
29
- def initialize( options = nil, &block )
30
- @options = options || {}
31
-
32
- # make sure the relevant entries exist, so method_missing works
33
- @options[:adapter] ||= ''
34
- @options[:host] ||= 'localhost'
35
- @options[:username] ||= ''
36
- @options[:password] ||= ''
37
- @options[:database] ||= ''
38
-
39
- unless block.nil?
40
- if block.arity == -1
41
- instance_eval &block
42
- else
43
- yield self
44
- end
45
- end
46
- end
47
-
48
- def connect
49
- if @options[:database].nil? || @options[:database].empty?
50
- raise "Please define database using DbOptions. Current value is #{@options[:database].inspect}."
51
- end
52
-
53
- # connect to db
54
- ActiveRecord::Base.establish_connection( options )
55
- ActiveRecord::Base.logger = Logger.new(STDOUT) if options[:verbose]
56
- #~ ActiveRecord.colorize_logging = @options[:verbose]
57
- self
58
- end
59
-
60
- # convenience method so we can do things like
61
- # Clevic::DbOptions.connect do
62
- # database :accounts
63
- # adapter :postgresql
64
- # username 'accounts_user'
65
- # end
66
- # the block is evaluated in the context of the a new DbOptions
67
- # object. You can also pass a block parameter and it will receive
68
- # the DbOptions instance, like this:
69
- # Clevic::DbOptions.connect do |dbo|
70
- # dbo.database :accounts
71
- # dbo.adapter :postgresql
72
- # dbo.username 'accounts_user'
73
- # end
74
- def self.connect( args = {}, &block )
75
- inst = self.new( args, &block )
76
- inst.connect
77
- inst
78
- end
79
-
80
- # translate method calls in the context of an instance
81
- # of this object to setting values in the @options
82
- # variable
83
- def method_missing(meth, *args, &block)
84
- if @options.has_key? meth.to_sym
85
- if args.size == 0
86
- @options[meth.to_sym]
87
- else
88
- @options[meth.to_sym] = args[0].to_s
89
- end
90
- else
91
- super
92
- end
93
- end
94
-
95
- # convenience to find out if we're in debug mode
96
- def debug?
97
- @options[:debug] == true
98
- end
99
- end
100
-
101
- end
102
-
103
- # workaround for the date freeze issue, if it exists
104
- begin
105
- Date.new.freeze.to_s
106
- rescue TypeError
107
- class Date
108
- def freeze
109
- self
110
- end
111
- end
112
- end
@@ -1,386 +0,0 @@
1
- require 'clevic/item_delegate.rb'
2
-
3
- module Clevic
4
-
5
- =begin rdoc
6
- Base class for other delegates using Combo boxes. Emit focus out signals,
7
- because ComboBox stupidly doesn't.
8
-
9
- Generally these will be created using a Clevic::ModelBuilder.
10
- =end
11
- class ComboDelegate < Clevic::ItemDelegate
12
- def initialize( parent, field )
13
- super
14
- end
15
-
16
- # Convert Qt:: constants from the integer value to a string value.
17
- def hint_string( hint )
18
- hs = String.new
19
- Qt::AbstractItemDelegate.constants.each do |x|
20
- hs = x if eval( "Qt::AbstractItemDelegate::#{x}.to_i" ) == hint.to_i
21
- end
22
- hs
23
- end
24
-
25
- def dump_editor_state( editor )
26
- if $options[:debug]
27
- puts "#{self.class.name}"
28
- puts "editor.completer.completion_count: #{editor.completer.completion_count}"
29
- puts "editor.completer.current_completion: #{editor.completer.current_completion}"
30
- puts "editor.find_text( editor.completer.current_completion ): #{editor.find_text( editor.completer.current_completion )}"
31
- puts "editor.current_text: #{editor.current_text}"
32
- puts "editor.count: #{editor.count}"
33
- puts "editor.completer.current_row: #{editor.completer.current_row}"
34
- puts "editor.item_data( editor.current_index ): #{editor.item_data( editor.current_index ).inspect}"
35
- puts
36
- end
37
- end
38
-
39
- # open the combo box, just like if f4 was pressed
40
- def full_edit
41
- if is_combo?( @editor )
42
- @editor.show_popup
43
- end
44
- end
45
-
46
- # returns true if the editor allows values outside of a predefined
47
- # range, false otherwise.
48
- def restricted?
49
- false
50
- end
51
-
52
- # TODO fetch this from the model definition
53
- def allow_null?
54
- true
55
- end
56
-
57
- # Subclasses should override this to fill the combo box
58
- # list with values.
59
- def populate( editor, model_index )
60
- raise "subclass responsibility"
61
- end
62
-
63
- # return true if this delegate needs a combo, false otherwise
64
- def needs_combo?
65
- raise "subclass responsibility"
66
- end
67
-
68
- def is_combo?( editor )
69
- editor.class == Qt::ComboBox
70
- end
71
-
72
- # return true if this field has no data (needs_combo? is false)
73
- # and is at the same time restricted (ie needs data from somewhere else)
74
- def empty_set?
75
- !needs_combo? && restricted?
76
- end
77
-
78
- # the message to display if the set is empty, and
79
- # the delegate is restricted to a predefined set.
80
- def empty_set_message
81
- raise "subclass responsibility"
82
- end
83
-
84
- # if this delegate has an empty set, return the message, otherwise
85
- # return nil.
86
- def if_empty_message
87
- if empty_set?
88
- empty_set_message
89
- end
90
- end
91
-
92
- def populate_current( editor, model_index )
93
- # add the current entry, if it isn't there already
94
- # TODO add it in the correct order
95
- if ( editor.find_data( model_index.gui_value.to_variant ) == -1 )
96
- editor.add_item( model_index.gui_value, model_index.gui_value.to_variant )
97
- end
98
- end
99
-
100
- def add_nil_item( editor )
101
- if ( editor.find_data( nil.to_variant ) == -1 )
102
- editor.add_item( '', nil.to_variant )
103
- end
104
- end
105
-
106
- # Override the Qt method. Create a ComboBox widget and fill it with the possible values.
107
- def createEditor( parent_widget, style_option_view_item, model_index )
108
- if needs_combo?
109
- @editor = Qt::ComboBox.new( parent_widget )
110
-
111
- # subclasses fill in the rest of the entries
112
- populate( @editor, model_index )
113
-
114
- # add the current item, if it isn't there already
115
- populate_current( @editor, model_index )
116
-
117
- # create a nil entry
118
- add_nil_item( @editor ) if allow_null?
119
-
120
- # allow prefix matching from the keyboard
121
- @editor.editable = true
122
-
123
- # don't insert if restricted
124
- @editor.insert_policy = Qt::ComboBox::NoInsert if restricted?
125
- else
126
- @editor =
127
- if restricted?
128
- emit parent.status_text( empty_set_message )
129
- nil
130
- else
131
- Qt::LineEdit.new( model_index.gui_value, parent_widget )
132
- end
133
- end
134
- @editor
135
- end
136
-
137
- # Override the Qt::ItemDelegate method.
138
- def updateEditorGeometry( editor, style_option_view_item, model_index )
139
- rect = style_option_view_item.rect
140
-
141
- # ask the editor for how much space it wants, and set the editor
142
- # to that size when it displays in the table
143
- rect.set_width( [editor.size_hint.width,rect.width].max ) if is_combo?( editor )
144
- editor.set_geometry( rect )
145
- end
146
-
147
- # Override the Qt method to send data to the editor from the model.
148
- def setEditorData( editor, model_index )
149
- if is_combo?( editor )
150
- editor.current_index = editor.find_data( model_index.attribute_value.to_variant )
151
- editor.line_edit.select_all if editor.editable
152
- else
153
- editor.text = model_index.gui_value
154
- end
155
- end
156
-
157
- # This translates the text from the editor into something that is
158
- # stored in an underlying model. Intended to be overridden by subclasses.
159
- def translate_from_editor_text( editor, text )
160
- index = editor.find_text( text )
161
-
162
- if index == -1
163
- text unless restricted?
164
- else
165
- editor.item_data( index ).value
166
- end
167
- end
168
-
169
- # Send the data from the editor to the model. The data will
170
- # be translated by translate_from_editor_text,
171
- def setModelData( editor, abstract_item_model, model_index )
172
- if is_combo?( editor )
173
- dump_editor_state( editor )
174
- value =
175
- if editor.completer.current_row == -1
176
- # item doesn't exist in the list, add it if not restricted
177
- editor.current_text unless restricted?
178
- elsif editor.completer.completion_count == editor.count
179
- # selection from drop down. if it's empty, we want a nil
180
- editor.current_text
181
- else
182
- # there is a matching completion, so use it
183
- editor.completer.current_completion
184
- end
185
-
186
- if value != nil
187
- model_index.attribute_value = translate_from_editor_text( editor, value )
188
- end
189
-
190
- else
191
- model_index.attribute_value = editor.text
192
- end
193
- abstract_item_model.data_changed( model_index )
194
- end
195
-
196
- end
197
-
198
- # Provide a list of all values in this field,
199
- # and allow new values to be entered.
200
- # :frequency can be set as an option. Boolean. If it's true
201
- # the options are sorted in order of most frequently used first.
202
- class DistinctDelegate < ComboDelegate
203
-
204
- def needs_combo?
205
- # works except when there is a '' in the column
206
- entity_class.count( attribute.to_s, find_options ) > 0
207
- end
208
-
209
- def populate_current( editor, model_index )
210
- # already done in the SQL query in populate, so don't even check
211
- end
212
-
213
- def query_order_description( conn, model_index )
214
- <<-EOF
215
- select distinct #{attribute.to_s}, lower(#{attribute.to_s})
216
- from #{entity_class.table_name}
217
- where (#{find_options[:conditions] || '1=1'})
218
- or #{conn.quote_column_name( attribute.to_s )} = #{conn.quote( model_index.attribute_value )}
219
- order by lower(#{attribute.to_s})
220
- EOF
221
- end
222
-
223
- def query_order_frequency( conn, model_index )
224
- <<-EOF
225
- select distinct #{attribute.to_s}, count(#{attribute.to_s})
226
- from #{entity_class.table_name}
227
- where (#{find_options[:conditions] || '1=1'})
228
- or #{conn.quote_column_name( attribute.to_s )} = #{conn.quote( model_index.attribute_value )}
229
- group by #{attribute.to_s}
230
- order by count(#{attribute.to_s}) desc
231
- EOF
232
- end
233
-
234
- def populate( editor, model_index )
235
- # we only use the first column, so use the second
236
- # column to sort by, since SQL requires the order by clause
237
- # to be in the select list where distinct is involved
238
- conn = entity_class.connection
239
- query =
240
- case
241
- when field.description
242
- query_order_description( conn, model_index )
243
- when field.frequency
244
- query_order_frequency( conn, model_index )
245
- else
246
- query_order_frequency( conn, model_index )
247
- end
248
- rs = conn.execute( query )
249
- rs.each do |row|
250
- value = row[attribute.to_s]
251
- editor.add_item( value, value.to_variant )
252
- end
253
- end
254
-
255
- def translate_from_editor_text( editor, text )
256
- text
257
- end
258
- end
259
-
260
- # A Combo box which allows a set of values. May or may not
261
- # be restricted to the set.
262
- class SetDelegate < ComboDelegate
263
- # options must contain a :set => [ ... ] to specify the set of values.
264
- def initialize( parent, field )
265
- raise "RestrictedDelegate must have a :set in options" if field.set.nil?
266
- super
267
- end
268
-
269
- def needs_combo?
270
- true
271
- end
272
-
273
- def restricted?
274
- field.restricted || false
275
- end
276
-
277
- def populate( editor, model_index )
278
- field.set_for( model_index.entity ).each do |item|
279
- if item.is_a?( Array )
280
- # this is a hash-like set, so use key as db value
281
- # and value as display value
282
- editor.add_item( item.last, item.first.to_variant )
283
- else
284
- editor.add_item( item, item.to_variant )
285
- end
286
- end
287
- end
288
-
289
- def createEditor( parent_widget, style_option_view_item, model_index )
290
- editor = super
291
-
292
- # the set is provided, so never insert things
293
- editor.insert_policy = Qt::ComboBox::NoInsert
294
- editor
295
- end
296
-
297
- end
298
-
299
- # Edit a relation from an id and display a list of relevant entries.
300
- #
301
- # attribute is the method to call on the row entity to retrieve the related object.
302
- #
303
- # The ids of the ActiveRecord models are stored in the item data
304
- # and the item text is fetched from them using attribute_path.
305
- class RelationalDelegate < ComboDelegate
306
-
307
- def initialize( parent, field )
308
- super
309
- unless find_options[:conditions].nil?
310
- find_options[:conditions].gsub!( /true/, entity_class.connection.quoted_true )
311
- find_options[:conditions].gsub!( /false/, entity_class.connection.quoted_false )
312
- end
313
- end
314
-
315
- def entity_class
316
- @entity_class ||= ( field.class_name || field.attribute.to_s.classify ).constantize
317
- end
318
-
319
- def needs_combo?
320
- entity_class.count( :conditions => find_options[:conditions] ) > 0
321
- end
322
-
323
- def empty_set_message
324
- "There must be records in #{entity_class.name.humanize} for this field to be editable."
325
- end
326
-
327
- # add the current item, unless it's already in the combo data
328
- def populate_current( editor, model_index )
329
- # always add the current selection, if it isn't already there
330
- # and it makes sense. This is to make sure that if the list
331
- # is filtered, we always have the current value if the filter
332
- # excludes it
333
- unless model_index.nil?
334
- item = model_index.attribute_value
335
- if item
336
- item_index = editor.find_data( item.id.to_variant )
337
- if item_index == -1
338
- add_to_list( editor, model_index, item )
339
- end
340
- end
341
- end
342
- end
343
-
344
- def populate( editor, model_index )
345
- # add set of all possible related entities
346
- entity_class.find( :all, find_options ).each do |x|
347
- add_to_list( editor, model_index, x )
348
- end
349
- end
350
-
351
- def add_to_list( editor, model_index, item )
352
- editor.add_item( model_index.field.transform_attribute( item ), item.id.to_variant )
353
- end
354
-
355
- # send data to the editor
356
- def setEditorData( editor, model_index )
357
- if is_combo?( editor )
358
- unless model_index.attribute_value.nil?
359
- editor.current_index = editor.find_data( model_index.attribute_value.id.to_variant )
360
- end
361
- editor.line_edit.select_all
362
- end
363
- end
364
-
365
- # don't allow new values
366
- def restricted?
367
- true
368
- end
369
-
370
- # return an AR entity object
371
- def translate_from_editor_text( editor, text )
372
- item_index = editor.find_text( text )
373
-
374
- # fetch record id from editor item_data
375
- item_data = editor.item_data( item_index )
376
- if item_data.valid?
377
- # get the entity it refers to, if there is one
378
- # use find_by_id so that if it's not found, nil will
379
- # be returned
380
- entity_class.find_by_id( item_data.to_int )
381
- end
382
- end
383
-
384
- end
385
-
386
- end