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,344 @@
1
+ require 'rubygems'
2
+ require 'Qt4'
3
+ require 'fastercsv'
4
+ require 'qtext/action_builder.rb'
5
+
6
+ require 'clevic/model_builder.rb'
7
+ require 'clevic/filter_command.rb'
8
+
9
+ module Clevic
10
+
11
+ # The view class
12
+ # TODO not sure if we still need override_next_index and friends
13
+ class TableView < Qt::TableView
14
+ include ActionBuilder
15
+
16
+ # status_text is emitted when this object was to display something in the status bar
17
+ # filter_status is emitted when the filtering changes. Param is true for filtered, false for not filtered.
18
+ signals 'status_text_signal(QString)', 'filter_status_signal(bool)'
19
+
20
+ def emit_filter_status( bool )
21
+ emit filter_status_signal( bool )
22
+ end
23
+
24
+ def emit_status_text( string )
25
+ emit status_text_signal( string )
26
+ end
27
+
28
+ # arg is:
29
+ # - an instance of Clevic::View
30
+ # - an instance of TableModel
31
+ def initialize( arg, parent = nil, &block )
32
+ # need the empty block here, otherwise Qt bindings grab &block
33
+ super( parent ) {}
34
+
35
+ framework_init( arg, &block )
36
+
37
+ # see closeEditor
38
+ @next_index = nil
39
+
40
+ # set some Qt things
41
+ self.horizontal_header.movable = false
42
+ # TODO might be useful to allow movable vertical rows,
43
+ # but need to change the shortcut ideas of next and previous rows
44
+ self.vertical_header.movable = false
45
+ self.sorting_enabled = false
46
+
47
+ self.context_menu_policy = Qt::ActionsContextMenu
48
+ end
49
+
50
+ def connect_view_signals( entity_view )
51
+ model.connect SIGNAL( 'dataChanged ( const QModelIndex &, const QModelIndex & )' ) do |top_left, bottom_right|
52
+ begin
53
+ entity_view.notify_data_changed( self, top_left, bottom_right )
54
+ rescue Exception => e
55
+ puts
56
+ puts "#{model.entity_view.class.name}: #{e.message}"
57
+ puts e.backtrace
58
+ end
59
+ end
60
+ end
61
+
62
+ # return a collection of collections of TableIndex objects
63
+ # indicating the indices of the current selection
64
+ def selected_rows
65
+ rows = []
66
+ selection_model.selection.each do |selection_range|
67
+ (selection_range.top..selection_range.bottom).each do |row|
68
+ rows << (selection_range.top_left.column..selection_range.bottom_right.column).map do |col|
69
+ model.create_index( row, col )
70
+ end
71
+ end
72
+ end
73
+ rows
74
+ end
75
+
76
+ def status_text( msg )
77
+ emit status_text( msg )
78
+ end
79
+
80
+ def open_editor
81
+ edit( current_index )
82
+ delegate = item_delegate( current_index )
83
+ delegate.full_edit
84
+ end
85
+
86
+ def itemDelegate( model_index )
87
+ @pre_delegate_index = model_index
88
+ super
89
+ end
90
+
91
+ # set the size of the column from the sample
92
+ def auto_size_column( col, sample )
93
+ self.set_column_width( col, column_size( col, sample ).width )
94
+ end
95
+
96
+ # set the size of the column from the string value of the data
97
+ # mostly copied from qheaderview.cpp:2301
98
+ def column_size( col, data )
99
+ opt = Qt::StyleOptionHeader.new
100
+
101
+ # fetch font size
102
+ fnt = font
103
+ fnt.bold = true
104
+ opt.fontMetrics = Qt::FontMetrics.new( fnt )
105
+
106
+ # set data
107
+ opt.text = data.to_s
108
+
109
+ # icon size. Not needed
110
+ #~ variant = d->model->headerData(logicalIndex, d->orientation, Qt::DecorationRole);
111
+ #~ opt.icon = qvariant_cast<QIcon>(variant);
112
+ #~ if (opt.icon.isNull())
113
+ #~ opt.icon = qvariant_cast<QPixmap>(variant);
114
+
115
+ size = Qt::Size.new( 100, 30 )
116
+ # final parameter could be header section
117
+ style.sizeFromContents( Qt::Style::CT_HeaderSection, opt, size );
118
+ end
119
+
120
+ # make sure row size is correct
121
+ # show error messages for data
122
+ def setModel( model )
123
+ # must do this otherwise model gets garbage collected
124
+ @model = model
125
+
126
+ # make sure we get nice spacing
127
+ vertical_header.default_section_size = vertical_header.minimum_section_size
128
+ super
129
+
130
+ # set delegates
131
+ model.fields.each_with_index do |field, index|
132
+ set_item_delegate_for_column( index, field.delegate )
133
+ end
134
+
135
+ # data errors
136
+ model.connect( SIGNAL( 'data_error(QModelIndex, QVariant, QString)' ) ) do |index,variant,msg|
137
+ show_error( "Incorrect value '#{variant.value}' entered for field [#{index.attribute.to_s}].\nMessage was: #{msg}" )
138
+ end
139
+ end
140
+
141
+ def show_error( msg )
142
+ error_message = Qt::ErrorMessage.new( self )
143
+ error_message.show_message( msg )
144
+ error_message.show
145
+ end
146
+
147
+ # and override this because the Qt bindings don't call
148
+ # setModel otherwise
149
+ def model=( model )
150
+ setModel( model )
151
+ resize_columns
152
+ end
153
+
154
+ def moveCursor( cursor_action, modifiers )
155
+ # TODO use this as a preload indicator
156
+ super
157
+ end
158
+
159
+ # returns the Qt::MessageBox
160
+ def confirm_dialog( question, title )
161
+ msg = Qt::MessageBox.new(
162
+ Qt::MessageBox::Question,
163
+ title,
164
+ question,
165
+ Qt::MessageBox::Yes | Qt::MessageBox::No,
166
+ self
167
+ )
168
+ msg.exec
169
+ msg
170
+ end
171
+
172
+ def keyPressEvent( event )
173
+ handle_key_press( event )
174
+ super
175
+ end
176
+
177
+ def set_model_data( table_index, value )
178
+ model.setData( table_index, value.to_variant, Qt::PasteRole )
179
+ end
180
+
181
+ # save the entity in the row of the given index
182
+ # actually, model.save will check if the record
183
+ # is really changed before writing to DB.
184
+ def show_error( msg )
185
+ error_message = Qt::ErrorMessage.new( self )
186
+ error_message.show_message( msg )
187
+ error_message.show
188
+ end
189
+
190
+ # This is to allow entity model UI handlers to tell the view
191
+ # whence to move the cursor when the current editor closes
192
+ # (see closeEditor).
193
+ # TODO not used?
194
+ def override_next_index( model_index )
195
+ self.next_index = model_index
196
+ end
197
+
198
+ # Call set_current_index with next_index ( from override_next_index )
199
+ # or model_index, in that order. Set next_index to nil afterwards.
200
+ def set_current_unless_override( model_index )
201
+ set_current_index( @next_index || model_index )
202
+ self.next_index = nil
203
+ end
204
+
205
+ # work around situation where an ItemDelegate is open
206
+ # when the surrouding tab is changed, but the right events
207
+ # don't arrive.
208
+ def hideEvent( event )
209
+ # can't call super here, for some reason. Qt binding says method not found.
210
+ # super
211
+ @hiding = true
212
+ end
213
+
214
+ # work around situation where an ItemDelegate is open
215
+ # when the surrouding tab is changed, but the right events
216
+ # don't arrive.
217
+ def showEvent( event )
218
+ super
219
+ @hiding = false
220
+ end
221
+
222
+ def focusOutEvent( event )
223
+ super
224
+ #~ save_current_row
225
+ end
226
+
227
+ # this is the only method that is called when an itemDelegate is open
228
+ # and the tabs are changed.
229
+ # Work around situation where an ItemDelegate is open
230
+ # when the surrouding tab is changed, but the right events
231
+ # don't arrive.
232
+ def commitData( editor )
233
+ super
234
+ save_current_row if @hiding
235
+ rescue
236
+ show_error "Error saving data from #{editor.inspect}: #{$!.message}"
237
+ end
238
+
239
+ # bool QAbstractItemView::edit ( const QModelIndex & index, EditTrigger trigger, QEvent * event )
240
+ def edit( model_index, trigger = nil, event = nil )
241
+ self.before_edit_index = model_index
242
+ #~ puts "edit model_index: #{model_index.inspect}"
243
+ #~ puts "trigger: #{trigger.inspect}"
244
+ #~ puts "event: #{event.inspect}"
245
+ if trigger.nil? && event.nil?
246
+ super( model_index )
247
+ else
248
+ super( model_index, trigger, event )
249
+ end
250
+
251
+ rescue Exception => e
252
+ raise RuntimeError, "#{model.entity_view.class.name}.#{model_index.field.id}: #{e.message}", caller(0)
253
+ end
254
+
255
+ attr_accessor :before_edit_index
256
+ attr_reader :next_index
257
+ def next_index=( other_index )
258
+ if $options[:debug]
259
+ puts "debug trace only - not a rescue"
260
+ puts caller
261
+ puts "next index to #{other_index.inspect}"
262
+ puts
263
+ end
264
+ @next_index = other_index
265
+ end
266
+
267
+ # set and move to index. Leave index value in next_index
268
+ # so that it's not overridden later.
269
+ # TODO All this next_index stuff is becoming a horrible hack.
270
+ def next_index!( model_index )
271
+ self.current_index = self.next_index = model_index
272
+ end
273
+
274
+ # override to prevent tab pressed from editing next field
275
+ # also takes into account that override_next_index may have been called
276
+ def closeEditor( editor, end_edit_hint )
277
+ if $options[:debug]
278
+ puts "end_edit_hint: #{Qt::AbstractItemDelegate.constants.find {|x| Qt::AbstractItemDelegate.const_get(x) == end_edit_hint } }"
279
+ puts "next_index: #{next_index.inspect}"
280
+ end
281
+
282
+ subsequent_index =
283
+ case end_edit_hint
284
+ when Qt::AbstractItemDelegate.EditNextItem
285
+ super( editor, Qt::AbstractItemDelegate.NoHint )
286
+ before_edit_index.choppy { |i| i.column += 1 }
287
+
288
+ when Qt::AbstractItemDelegate.EditPreviousItem
289
+ super( editor, Qt::AbstractItemDelegate.NoHint )
290
+ before_edit_index.choppy { |i| i.column -= 1 }
291
+
292
+ else
293
+ super
294
+ nil
295
+ end
296
+
297
+ unless subsequent_index.nil?
298
+ puts "subsequent_index: #{subsequent_index.inspect}" if $options[:debug]
299
+ # TODO all this really does is reset next_index
300
+ set_current_unless_override( next_index || subsequent_index || before_edit_index )
301
+ self.before_edit_index = nil
302
+ end
303
+ end
304
+
305
+ # find the TableView instance for the given entity_view
306
+ # or entity_model. Return nil if no match found.
307
+ # TODO doesn't really belong here because TableView will not always
308
+ # be in a TabWidget context.
309
+ def find_table_view( entity_model_or_view )
310
+ parent.children.find do |x|
311
+ if x.is_a? TableView
312
+ x.model.entity_view.class == entity_model_or_view || x.model.entity_class == entity_model_or_view
313
+ end
314
+ end
315
+ end
316
+
317
+ # execute the block with the TableView instance
318
+ # currently handling the entity_model_or_view.
319
+ # Don't execute the block if nothing is found.
320
+ # TODO doesn't really belong here because TableView will not always
321
+ # be in a TabWidget context.
322
+ # TODO put it in a module and add the module when the tab widgets
323
+ # are being built.
324
+ def with_table_view( entity_model_or_view, &block )
325
+ tv = find_table_view( entity_model_or_view )
326
+ yield( tv ) unless tv.nil?
327
+ end
328
+
329
+ # make this window visible if it's in a TabWidget
330
+ # TODO doesn't really belong here because TableView will not always
331
+ # be in a TabWidget context.
332
+ def raise_widget
333
+ # the tab's parent is a StackedWiget, and its parent is TabWidget
334
+ tab_widget = parent.parent
335
+ tab_widget.current_widget = self if tab_widget.class == Qt::TabWidget
336
+ end
337
+
338
+ def busy_cursor( &block )
339
+ override_cursor( Qt::BusyCursor, &block )
340
+ end
341
+
342
+ end
343
+
344
+ end
@@ -0,0 +1,8 @@
1
+ require 'clevic/qt/text_delegate.rb'
2
+
3
+ module Clevic
4
+
5
+ # TODO alias this for now
6
+ TextAreaDelegate = TextDelegate
7
+
8
+ end
@@ -1,4 +1,4 @@
1
- require 'clevic/item_delegate.rb'
1
+ require 'clevic/qt/item_delegate.rb'
2
2
 
3
3
  module Clevic
4
4
 
@@ -40,7 +40,7 @@ module Clevic
40
40
 
41
41
  # Override the Qt method
42
42
  def createEditor( parent_widget, style_option_view_item, model_index )
43
- if false && model_index.gui_value.count("\n") == 0
43
+ if false && model_index.edit_value.count("\n") == 0
44
44
  # futzing about here, really
45
45
  @editor = Qt::LineEdit.new( parent_widget )
46
46
  else
@@ -69,14 +69,16 @@ module Clevic
69
69
 
70
70
  # Override the Qt method to send data to the editor from the model.
71
71
  def setEditorData( editor, model_index )
72
- editor.plain_text = model_index.gui_value
72
+ editor.plain_text = model_index.edit_value
73
73
  end
74
74
 
75
75
  # Send the data from the editor to the model. The data will
76
76
  # be translated by translate_from_editor_text,
77
77
  def setModelData( editor, abstract_item_model, model_index )
78
- model_index.attribute_value = editor.to_plain_text
78
+ model_index.edit_value = editor.to_plain_text
79
79
  abstract_item_model.data_changed( model_index )
80
+ rescue
81
+ abstract_item_model.emit_data_error( model_index, editor.to_plain_text, $!.message )
80
82
  end
81
83
 
82
84
  end
File without changes
File without changes
File without changes
@@ -0,0 +1,56 @@
1
+ require 'active_support'
2
+
3
+ def load_rails_models( root, config, models )
4
+ # initialize Rails
5
+ load config / 'environment.rb'
6
+ require 'initializer.rb'
7
+ Rails::Initializer.run do |config|
8
+ config.frameworks -= [ :action_mailer, :action_pack, :active_resource ]
9
+ end
10
+
11
+ # load lib/ files for the rails project
12
+ $: << ( root / 'lib' ).realpath.to_s
13
+ ( root / 'lib' ).children.each do |filename|
14
+ load filename if filename.file?
15
+ end
16
+
17
+ # include Dirty if it isn't already
18
+ begin
19
+ ActiveRecord::Dirty
20
+ rescue NameError
21
+ ActiveRecord::Base.send(:include, ActiveRecord::Dirty)
22
+ end
23
+
24
+ # load models
25
+ models.find do |dir_entry|
26
+ # don't load directory entries
27
+ next unless dir_entry.file?
28
+ # only load .rb files
29
+ next unless dir_entry.basename.to_s =~ /\.rb$/
30
+ begin
31
+ load dir_entry
32
+ rescue Exception => e
33
+ puts "Error loading #{dir_entry.basename.to_s}: #{e.message}"
34
+ puts e.backtrace
35
+ end
36
+ end
37
+
38
+ # include the Clevic::Record module in each descendant of
39
+ # the entity class so that the default views will be created.
40
+ subclasses( Clevic.base_entity_class ).each do |model|
41
+ if model.table_exists?
42
+ model.send :include, Clevic::Record unless model.abstract_class?
43
+ end
44
+ end
45
+ end
46
+
47
+ def maybe_load_rails_models
48
+ config = pathname / 'config'
49
+ app = pathname / 'app'
50
+ models = app / 'models'
51
+ # check if this is a Rails directory
52
+ if config.exist? && app.exist? && models.exist?
53
+ # this is probably a Rails project"
54
+ load_rails_models( pathname, config, models )
55
+ end
56
+ end