clevic 0.11.1 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/History.txt CHANGED
@@ -1,3 +1,24 @@
1
+ == 0.12.0
2
+ * fix some breakage from Qt-4.5.x
3
+ * paste single value to multiple cells
4
+ * text field for multiline text editing
5
+ * fields now receive notify_data_changed. Clevic::View still receives it
6
+ but by default passes the notification to the fields.
7
+ * add Clevic::TableModel#data_changed because the Qt dataChanged forced severe
8
+ clunkiness defining new ModelIndex instances.
9
+ * Ctrl-S now saves current row. Display doesn't always update immediately though.
10
+ * add timestamp to types recognised as dates/times
11
+ * move field_column from Clevic::TableView to Clevic::TableModel
12
+ * Decimal fields now accept more variations - ',' as thousands separators
13
+ is stripped out and a space can be used instead of a decimal point. No
14
+ explicit internationalised decimal formats though.
15
+ * fix some weirdness with filtering
16
+ * fields now take an id property. So one field in the db can be displayed
17
+ in several UI fields.
18
+ * fields can take default values in the UI definition
19
+ * save records after cell contents deleted
20
+ * various other bug fixes
21
+
1
22
  == 0.11.1
2
23
  * Define views in separate classes (subclass of Clevic::View) while
3
24
  maintaining view definition inside the ActiveRecord::Base subclass.
data/Manifest.txt CHANGED
@@ -4,8 +4,6 @@ README.txt
4
4
  Rakefile
5
5
  TODO
6
6
  bin/clevic
7
- config/hoe.rb
8
- config/requirements.rb
9
7
  lib/clevic/dirty.rb
10
8
  lib/clevic.rb
11
9
  lib/clevic/browser.rb
@@ -15,6 +13,7 @@ lib/clevic/default_view.rb
15
13
  lib/clevic/delegates.rb
16
14
  lib/clevic/extensions.rb
17
15
  lib/clevic/field.rb
16
+ lib/clevic/filter_command.rb
18
17
  lib/clevic/item_delegate.rb
19
18
  lib/clevic/model_builder.rb
20
19
  lib/clevic/model_column.rb
@@ -25,6 +24,7 @@ lib/clevic/sql_dialects.rb
25
24
  lib/clevic/table_model.rb
26
25
  lib/clevic/table_searcher.rb
27
26
  lib/clevic/table_view.rb
27
+ lib/clevic/text_delegate.rb
28
28
  lib/clevic/view.rb
29
29
  lib/clevic/ui/.gitignore
30
30
  lib/clevic/ui/browser.ui
@@ -46,6 +46,8 @@ sql/accounts.sql
46
46
  sql/times.sql
47
47
  sql/times_sqlite.sql
48
48
  tasks/website.rake
49
+ tasks/clevic.rake
50
+ tasks/rdoc.rake
49
51
  test/test_cache_table.rb
50
52
  test/test_helper.rb
51
53
  test/test_model_index_extensions.rb
data/Rakefile CHANGED
@@ -1,143 +1,36 @@
1
- require 'rubygems'
2
- require 'rake/clean'
3
- require 'hoe'
4
- require 'pathname'
5
-
6
- require 'config/requirements'
7
- require 'config/hoe' # setup Hoe + all gem configuration
8
-
9
- Dir['tasks/**/*.rake'].each { |rake| load rake }
10
-
11
- # generate a _ui.rb filename from a .ui filename
12
- def ui_rb_file( ui_file )
13
- ui_file.gsub( /\.ui$/, '_ui.rb' )
14
- end
15
-
16
- # list of .ui files
17
- UI_FILES = FileList.new( 'lib/clevic/ui/*.ui' )
18
- CLEAN.include( 'ChangeLog', 'coverage', 'profiling' )
19
- CLOBBER.include( 'ChangeLog', 'pkg', 'lib/clevic/ui/*_ui.rb' )
20
-
21
- UI_FILES.each do |ui_file|
22
- # make tasks to generate _ui.rb files
23
- file ui_rb_file( ui_file ) => [ ui_file ] do |t|
24
- sh "rbuic4 #{t.prerequisites} -o #{t.name}"
25
- end
26
-
27
- # make tasks to start designer when the ui file is named
28
- desc "Start Qt designer with #{ui_file}"
29
- namespace :ui do |n|
30
- task Pathname.new(ui_file).basename.to_s.ext do |t|
31
- sh "designer #{ui_file}"
32
- end
33
- end
34
- end
35
-
36
- desc 'Generate all _ui.rb files'
37
- task :ui => UI_FILES.map{|x| ui_rb_file( x ) }
38
-
39
- namespace :ui do
40
- desc 'Start Qt designer with the argument, or all .ui files.'
41
- task :design do |t|
42
- ARGV.shift()
43
- if ARGV.size == 0
44
- # start designer with all ui files
45
- sh "designer #{UI_FILES.join(' ')}"
46
- else
47
- # start designer with all files that match an argument
48
- sh "designer #{ ARGV.map{|x| UI_FILES.grep( /\/#{x}/ ) }.join(' ') }"
49
- end
50
- true
51
- end
52
- end
53
-
54
- desc "Runs Clevic in normal mode, with live database."
55
- task :run => :ui do |t|
56
- ARGV.shift()
57
- exec "ruby -Ilib bin/clevic #{ARGV.join(' ')}"
58
- end
59
-
60
- desc "Runs Clevic in debug mode, with test databases"
61
- task :debug => :ui do |t|
62
- ARGV.shift()
63
- exec "ruby -w -rdebug -Ilib bin/clevic -D #{ARGV.join(' ')}"
64
- end
65
-
66
- desc "irb in this project's context"
67
- task :irb do |t|
68
- ARGV.shift()
69
- ENV['RUBYLIB'] ||= ''
70
- ENV['RUBYLIB'] += ":#{File.expand_path('.')}/lib"
71
- exec "irb -Ilib -rclevic"
72
- end
73
-
74
- # generate tasks for all model definition files
75
- MODELS_LIST = FileList.new( '**/*models.rb' )
76
-
77
- def short_model( model_file )
78
- Pathname.new( model_file ).basename.to_s.gsub( /_models.rb/, '' )
79
- end
80
-
81
- MODELS_LIST.each do |model_file|
82
- # generate irb contexts
83
- desc "irb with #{model_file}"
84
- namespace :irb do
85
- task short_model( model_file ) do |t|
86
- ARGV.shift()
87
- ARGV.shift() if ARGV[0] == '--'
88
- ENV['RUBYLIB'] ||= '.'
89
- ENV['RUBYLIB'] += ":#{File.expand_path('.')}/lib"
90
- exec "irb -Ilib -rclevic -r#{model_file} -rclevic/db_options.rb"
91
- end
92
- end
1
+ %w[rubygems rake rake/clean fileutils newgem rubigen].each { |f| require f }
2
+ require File.dirname(__FILE__) + '/lib/clevic/version.rb'
3
+
4
+ # Generate all the Rake tasks
5
+ # Run 'rake -T' to see list of generated tasks (from gem root directory)
6
+ $hoe = Hoe.new('clevic', Clevic::VERSION::STRING) do |p|
7
+ p.developer('John Anderson', 'panic@semiosix.com')
8
+ p.changes = p.paragraphs_of("History.txt", 0..1).join("\n\n")
9
+ p.rubyforge_name = p.name # TODO this is default value
10
+ p.description = "SQL table GUI with Qt"
11
+ p.extra_deps = [
12
+ ['activesupport','>= 2.0.2'],
13
+ ['qtext', '>=0.6.5'],
14
+ ['activerecord', '>=2.0.2'],
15
+ ['fastercsv', '>=1.2.3'],
16
+ ['gather', '>=0.0.4'],
17
+ ['facets', '>=2.4.1']
18
+ # This isn't always installed from gems
19
+ #~ ['qtruby4', '>=1.4.9']
20
+ # bsearch can't be installed from gems
21
+ ]
22
+ p.extra_dev_deps = [
23
+ ['newgem', ">= #{::Newgem::VERSION}"]
24
+ ]
93
25
 
94
- # generate runs
95
- namespace :run do
96
- desc "run clevic with #{model_file}"
97
- task short_model( model_file ) => :ui do |t|
98
- ARGV.shift()
99
- ARGV.shift() if ARGV[0] == '--'
100
- cmd = "ruby -Ilib bin/clevic -D #{model_file} #{ARGV.join(' ')}"
101
- puts "cmd: #{cmd.inspect}"
102
- exec cmd
103
- end
104
- end
105
-
106
- namespace :warn do
107
- desc "run clevic with #{model_file} and warnings on"
108
- task short_model( model_file ) => :ui do |t|
109
- ARGV.shift()
110
- exec "ruby -w -Ilib bin/clevic -D #{model_file} #{ARGV.join(' ')}"
111
- end
112
- end
113
- end
114
-
115
- task :package => :ui
116
-
117
- desc "Update ChangeLog from the SVN log"
118
- task :changelog do |t|
119
- ARGV.shift
120
- exec "svn2cl --break-before-msg -o ChangeLog #{ARGV.join(' ')}"
26
+ p.clean_globs |= %w[**/.DS_Store tmp *.log]
27
+ path = (p.rubyforge_name == p.name) ? p.rubyforge_name : "\#{p.rubyforge_name}/\#{p.name}"
28
+ p.remote_rdoc_dir = File.join(path.gsub(/^#{p.rubyforge_name}\/?/,''), 'rdoc')
29
+ p.rsync_args = '-av --delete --ignore-errors'
121
30
  end
122
31
 
123
- # remove hoe documentation task
124
- Rake::Task['docs'].clear
32
+ require 'newgem/tasks' # load /tasks/*.rake
33
+ Dir['tasks/**/*.rake'].each { |t| load t }
125
34
 
126
- # make this respond to docs, so it fits in with the rest of the build
127
- Rake::RDocTask.new do |rdoc|
128
- rdoc.name = :docs
129
- rdoc.title = "Clevic DB UI builder"
130
- rdoc.main = 'README.txt'
131
- rdoc.rdoc_dir = 'doc'
132
- rdoc.rdoc_files.include %w{History.txt lib/**/*.rb README.txt TODO}
133
- rdoc.options += [
134
- '-SHN',
135
- '-f', 'darkfish', # This is the important bit
136
- '-A', 'property=Property',
137
- #~ '--quiet',
138
- "--opname=index.html",
139
- #~ "--line-numbers",
140
- #~ '--format=darkfish',
141
- #~ "--inline-source"
142
- ]
143
- end
35
+ # TODO - want other tests/tasks run by default? Add them to the list
36
+ # task :default => [:spec, :features]
data/TODO CHANGED
@@ -1,14 +1,19 @@
1
- need a :name specified for Field that defaults to the attribute name
2
- allow directories for a setup.
3
- implement the :default field option
4
- ModelBuilder#modify_field to override default_ui definitions
5
- rename ModelBuilder to ViewBuilder?
1
+ relational_delegate not used?
2
+ cut'n'paste in text/plain, text/csv, text/yml and application/clevic
3
+ HasFinder gem for complex reusable sql
4
+ provide for easier directories for a setup.
5
+ ModelBuilder#modify_field to override default_ui definitions. Or a better way to reuse field definitions.
6
+ use rubigen for creating model definition files?
7
+ undoable_command signal for delegates
6
8
 
7
- need a text field (multiple lines, edit in a separate window)
8
9
  need a map field (For ie -1 = short, 1 = long). Already in restricted, but it's kinda clunky.
9
10
  :records missing in define_ui causes a crash
11
+ :records must take a NamedScope
12
+
13
+ Add additional :filter record sets that can be selected from menus?
14
+
15
+ filter and search by virtual fields
10
16
 
11
- :display and :format in ModelBuilder
12
17
  tests. Use ZenTest to generate tests
13
18
  Check out ar-extensions. Like operators look nice.
14
19
  make sure ActiveRecord doesn't keep updating column definitions. ie production mode.
@@ -122,7 +127,6 @@ maybe
122
127
  acts_as_shellable looks nice
123
128
  consolidate read-only-ness checks
124
129
  Look at DataMapper. Not suitable - need to declare properties.
125
- use rubigen for creating model definition files?
126
130
  ActiveMDB for migrating?
127
131
  allow moving of rows
128
132
  discontiguous copying of entities/csv
@@ -140,10 +144,3 @@ QueryBuilder
140
144
  Accounts
141
145
  --------
142
146
  paste of "common" records with different dates
143
-
144
- Times
145
- -----
146
-
147
- Ctrl-Shift-" should not copy date if it already exists, and should not copy time if it's a different date.
148
- Ctrl-Shift-" after it's done, tab doesn't change fields
149
- look up invoice for project leaves the wrong fields highlighted, and focus in the wrong field.
data/bin/clevic CHANGED
@@ -26,6 +26,11 @@ oparser.on( '-t', '--table TABLE', 'Table to display', String ) { |o| $options[:
26
26
  oparser.on( '-d', '--database DATABASE', 'Database name', String ) { |o| $options[:database] = o }
27
27
  oparser.on( '-D', '--debug' ) { |o| $options[:debug] = true }
28
28
  oparser.on( '-v', '--verbose' ) { |o| $options[:verbose] = true }
29
+ oparser.on( '-V', '--version' ) do
30
+ require 'clevic/version.rb'
31
+ puts "clevic-#{Clevic::VERSION::STRING}"
32
+ exit 0
33
+ end
29
34
  oparser.on( '-h', '-?', '--help' ) do |o|
30
35
  puts oparser.to_s
31
36
  exit( 1 )
@@ -169,7 +169,7 @@ class Browser < Qt::Widget
169
169
 
170
170
  # make sure all outstanding records are saved
171
171
  def save_all
172
- tables_tab.each {|x| x.save_row( x.current_index ) }
172
+ tables_tab.tabs.each {|x| x.save_row( x.current_index ) }
173
173
  end
174
174
  end
175
175
 
@@ -49,7 +49,7 @@ class CacheTable < Array
49
49
  # The count of the records according to the db, which may be different to
50
50
  # the records in the cache
51
51
  def sql_count
52
- @entity_class.count( @options.reject{|k,v| k == :order} )
52
+ entity_class.count( options.reject{|k,v| k == :order} )
53
53
  end
54
54
 
55
55
  # Return the set of OrderAttribute objects for this collection.
@@ -63,26 +63,22 @@ class CacheTable < Array
63
63
  # add the primary key if nothing is specified
64
64
  # because we need an ordering of some kind otherwise
65
65
  # index_for_entity will not work
66
- if !@order_attributes.any? {|x| x.attribute == @entity_class.primary_key }
67
- @order_attributes << OrderAttribute.new( @entity_class, @entity_class.primary_key )
66
+ if !@order_attributes.any? {|x| x.attribute == entity_class.primary_key }
67
+ @order_attributes << OrderAttribute.new( entity_class, entity_class.primary_key )
68
68
  end
69
69
  end
70
70
  @order_attributes
71
71
  end
72
72
 
73
73
  # add an id to options[:order] if it's not in there
74
- # also create @order_attributes, and @auto_new
74
+ # also create @order_attributes
75
75
  def sanitise_options!
76
- # save this for later
77
- @auto_new = @options[:auto_new]
78
- @options.delete :auto_new
79
-
80
- # make sure we have a string here
81
- @options[:order] ||= ''
76
+ # make sure we have a string here, even if it's blank
77
+ options[:order] ||= ''
82
78
 
83
79
  # recreate the options[:order] entry to include default
84
- # TODO why though?
85
- @options[:order] = order_attributes.map{|x| x.to_sql}.join(',')
80
+ # TODO why though? Can't remember
81
+ options[:order] = order_attributes.map{|x| x.to_sql}.join(',')
86
82
  end
87
83
 
88
84
  # Execute the block with the specified preload_count,
@@ -104,7 +100,7 @@ class CacheTable < Array
104
100
  offset = index < 0 ? index + @row_count : index
105
101
 
106
102
  # fetch self.preload_count records
107
- records = @entity_class.find( :all, @options.merge( :offset => offset, :limit => preload_count ) )
103
+ records = entity_class.find( :all, options.merge( :offset => offset, :limit => preload_count ) )
108
104
  records.each_with_index {|x,i| self[i+index] = x if !cached_at?( i+index )}
109
105
 
110
106
  # return the first one
@@ -118,10 +114,11 @@ class CacheTable < Array
118
114
  end
119
115
 
120
116
  # make a new instance that has the attributes of this one, but an empty
121
- # data set. pass in ActiveRecord options to filter
122
- def renew( options = {} )
117
+ # data set. pass in ActiveRecord options to filter.
118
+ # TODO using named scopes might make filtering easier.
119
+ def renew( args = nil )
123
120
  clear
124
- self.class.new( @entity_class, @options.merge( options ) )
121
+ self.class.new( entity_class, args || options )
125
122
  end
126
123
 
127
124
  # find the index for the given entity, using a binary search algorithm (bsearch).
@@ -129,8 +126,7 @@ class CacheTable < Array
129
126
  # 0 is returned if the entity is nil
130
127
  # nil is returned if the array is empty
131
128
  def index_for_entity( entity )
132
- return nil if size == 0
133
- return nil if entity.nil?
129
+ return nil if size == 0 || entity.nil?
134
130
 
135
131
  # only load one record at a time, because mostly we only
136
132
  # need one for the binary seach. No point in pulling several out.
@@ -165,24 +161,9 @@ class CacheTable < Array
165
161
  end
166
162
  end
167
163
 
168
- def auto_new?
169
- @auto_new
170
- end
171
-
172
164
  def search( field, search_criteria, start_entity )
173
165
  Clevic::TableSearcher.new( entity_class, order_attributes, search_criteria, field ).search( start_entity )
174
166
  end
175
-
176
- # delete the given index. If the size ends up as 0,
177
- # make sure there's always at least one empty record
178
- def delete_at( index )
179
- retval = super
180
- if self.size == 0 && auto_new?
181
- self << @entity_class.new
182
- end
183
- retval
184
- end
185
-
186
167
  end
187
168
 
188
169
  # This is part of Array in case the programmer wants to use
@@ -193,7 +174,7 @@ class Array
193
174
  !at(index).nil?
194
175
  end
195
176
 
196
- def searcher
177
+ def search
197
178
  raise "not implemented"
198
179
  end
199
180
  end
@@ -41,6 +41,8 @@ module Clevic
41
41
  entity_class.actions( table_view, action_builder )
42
42
  elsif entity_class.respond_to?( :define_actions )
43
43
  entity_class.define_actions( table_view, action_builder )
44
+ else
45
+ super
44
46
  end
45
47
  end
46
48
 
@@ -50,6 +52,8 @@ module Clevic
50
52
  entity_class.data_changed( top_left_model_index, bottom_right_model_index, table_view )
51
53
  elsif entity_class.respond_to?( :notify_data_changed )
52
54
  entity_class.notify_data_changed( table_view, top_left_model_index, bottom_right_model_index )
55
+ else
56
+ super
53
57
  end
54
58
  end
55
59
 
@@ -59,6 +63,8 @@ module Clevic
59
63
  entity_class.key_press_event( key_press_event, current_model_index, table_view )
60
64
  elsif entity_class.respond_to?( :notify_key_press )
61
65
  entity_class.notify_key_press( table_view, key_press_event, current_model_index )
66
+ else
67
+ super
62
68
  end
63
69
  end
64
70
  end
@@ -96,6 +96,12 @@ class ComboDelegate < Clevic::ItemDelegate
96
96
  editor.add_item( model_index.gui_value, model_index.gui_value.to_variant )
97
97
  end
98
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
99
105
 
100
106
  # Override the Qt method. Create a ComboBox widget and fill it with the possible values.
101
107
  def createEditor( parent_widget, style_option_view_item, model_index )
@@ -109,11 +115,7 @@ class ComboDelegate < Clevic::ItemDelegate
109
115
  populate_current( @editor, model_index )
110
116
 
111
117
  # create a nil entry
112
- if allow_null?
113
- if ( @editor.find_data( nil.to_variant ) == -1 )
114
- @editor.add_item( '', nil.to_variant )
115
- end
116
- end
118
+ add_nil_item( @editor ) if allow_null?
117
119
 
118
120
  # allow prefix matching from the keyboard
119
121
  @editor.editable = true
@@ -156,13 +158,14 @@ class ComboDelegate < Clevic::ItemDelegate
156
158
  # stored in an underlying model. Intended to be overridden by subclasses.
157
159
  def translate_from_editor_text( editor, text )
158
160
  index = editor.find_text( text )
161
+
159
162
  if index == -1
160
163
  text unless restricted?
161
164
  else
162
165
  editor.item_data( index ).value
163
166
  end
164
167
  end
165
-
168
+
166
169
  # Send the data from the editor to the model. The data will
167
170
  # be translated by translate_from_editor_text,
168
171
  def setModelData( editor, abstract_item_model, model_index )
@@ -187,7 +190,7 @@ class ComboDelegate < Clevic::ItemDelegate
187
190
  else
188
191
  model_index.attribute_value = editor.text
189
192
  end
190
- emit abstract_item_model.dataChanged( model_index, model_index )
193
+ abstract_item_model.data_changed( model_index )
191
194
  end
192
195
 
193
196
  end
@@ -242,7 +245,6 @@ class DistinctDelegate < ComboDelegate
242
245
  else
243
246
  query_order_frequency( conn, model_index )
244
247
  end
245
- puts "query: #{query}"
246
248
  rs = conn.execute( query )
247
249
  rs.each do |row|
248
250
  value = row[attribute.to_s]
@@ -255,8 +257,9 @@ class DistinctDelegate < ComboDelegate
255
257
  end
256
258
  end
257
259
 
258
- # A Combo box which only allows a restricted set of value to be entered.
259
- class RestrictedDelegate < ComboDelegate
260
+ # A Combo box which allows a set of values. May or may not
261
+ # be restricted to the set.
262
+ class SetDelegate < ComboDelegate
260
263
  # options must contain a :set => [ ... ] to specify the set of values.
261
264
  def initialize( parent, field )
262
265
  raise "RestrictedDelegate must have a :set in options" if field.set.nil?
@@ -268,13 +271,13 @@ class RestrictedDelegate < ComboDelegate
268
271
  end
269
272
 
270
273
  def restricted?
271
- true
274
+ field.restricted || false
272
275
  end
273
276
 
274
277
  def populate( editor, model_index )
275
- field.set.each do |item|
278
+ field.set_for( model_index.entity ).each do |item|
276
279
  if item.is_a?( Array )
277
- # this is a hash, so use key as db value
280
+ # this is a hash-like set, so use key as db value
278
281
  # and value as display value
279
282
  editor.add_item( item.last, item.first.to_variant )
280
283
  else
@@ -282,12 +285,15 @@ class RestrictedDelegate < ComboDelegate
282
285
  end
283
286
  end
284
287
  end
285
-
286
- #~ def translate_from_editor_text( editor, text )
287
- #~ item_index = editor.find_text( text )
288
- #~ item_data = editor.item_data( item_index )
289
- #~ item_data.to_int
290
- #~ 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
+
291
297
  end
292
298
 
293
299
  # Edit a relation from an id and display a list of relevant entries.