clevic 0.11.1 → 0.12.0

Sign up to get free protection for your applications and to get access to all the features.
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.