clevic 0.13.0.b3 → 0.13.0.b5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. data/History.txt +21 -0
  2. data/Manifest.txt +91 -85
  3. data/README.txt +33 -18
  4. data/Rakefile +2 -3
  5. data/TODO +8 -14
  6. data/bin/clevic +18 -20
  7. data/lib/clevic.rb +7 -1
  8. data/lib/clevic/action_builder.rb +4 -1
  9. data/lib/clevic/ar_methods.rb +72 -57
  10. data/lib/clevic/attribute_list.rb +4 -0
  11. data/lib/clevic/cache_table.rb +43 -69
  12. data/lib/clevic/dataset_roller.rb +22 -0
  13. data/lib/clevic/delegate.rb +11 -5
  14. data/lib/clevic/delegates/combo_delegate.rb +156 -0
  15. data/lib/clevic/delegates/distinct_delegate.rb +48 -0
  16. data/lib/clevic/delegates/relational_delegate.rb +59 -0
  17. data/lib/clevic/delegates/set_delegate.rb +31 -0
  18. data/lib/clevic/field.rb +39 -55
  19. data/lib/clevic/field_valuer.rb +23 -10
  20. data/lib/clevic/filter_command.rb +22 -36
  21. data/lib/clevic/framework.rb +37 -0
  22. data/lib/clevic/generic_format.rb +5 -1
  23. data/lib/clevic/many_field.rb +28 -3
  24. data/lib/clevic/model_builder.rb +27 -32
  25. data/lib/clevic/ordered_dataset.rb +45 -0
  26. data/lib/clevic/qt.rb +4 -1
  27. data/lib/clevic/qt/action_builder.rb +9 -1
  28. data/lib/clevic/qt/browser.rb +1 -1
  29. data/lib/clevic/qt/clipboard.rb +3 -3
  30. data/lib/clevic/qt/combo_delegate.rb +25 -89
  31. data/lib/clevic/qt/delegate.rb +25 -0
  32. data/lib/clevic/qt/distinct_delegate.rb +5 -23
  33. data/lib/clevic/qt/extensions.rb +8 -1
  34. data/lib/clevic/qt/qt_combo_box.rb +58 -0
  35. data/lib/clevic/qt/relational_delegate.rb +18 -58
  36. data/lib/clevic/qt/set_delegate.rb +4 -34
  37. data/lib/clevic/qt/simplest_delegate.rb +19 -0
  38. data/lib/clevic/qt/table_model.rb +10 -10
  39. data/lib/clevic/qt/table_view.rb +7 -23
  40. data/lib/clevic/qt/text_delegate.rb +2 -2
  41. data/lib/clevic/qt/ui/browser_ui.rb +1 -1
  42. data/lib/clevic/qt/ui/search_dialog_ui.rb +1 -1
  43. data/lib/clevic/rails_models_loaders.rb +13 -0
  44. data/lib/clevic/record.rb +2 -2
  45. data/lib/clevic/sampler.rb +85 -39
  46. data/lib/clevic/sequel_ar_adapter.rb +1 -28
  47. data/lib/clevic/sequel_clevic.rb +68 -0
  48. data/lib/clevic/sequel_meta.rb +1 -13
  49. data/lib/clevic/subclasses.rb +18 -0
  50. data/lib/clevic/swing.rb +2 -1
  51. data/lib/clevic/swing/action.rb +27 -3
  52. data/lib/clevic/swing/action_builder.rb +0 -2
  53. data/lib/clevic/swing/browser.rb +1 -10
  54. data/lib/clevic/swing/combo_delegate.rb +45 -133
  55. data/lib/clevic/swing/delegate.rb +2 -0
  56. data/lib/clevic/swing/distinct_delegate.rb +2 -14
  57. data/lib/clevic/swing/relational_delegate.rb +2 -20
  58. data/lib/clevic/swing/set_delegate.rb +13 -28
  59. data/lib/clevic/swing/table_view.rb +1 -1
  60. data/lib/clevic/table_model.rb +3 -4
  61. data/lib/clevic/table_searcher.rb +10 -31
  62. data/lib/clevic/table_view.rb +97 -43
  63. data/lib/clevic/ui/browser_ui.rb +133 -0
  64. data/lib/clevic/ui/search_dialog_ui.rb +106 -0
  65. data/lib/clevic/version.rb +2 -2
  66. data/models/accounts_models.rb +24 -21
  67. data/models/times_models.rb +34 -28
  68. data/models/times_psql_models.rb +9 -3
  69. data/models/times_sqlite_models.rb +24 -1
  70. data/sql/times_sqlite.sql +3 -3
  71. data/tasks/clevic.rake +2 -2
  72. data/test/test_cache_table.rb +9 -19
  73. data/test/test_table_searcher.rb +2 -5
  74. metadata +95 -91
  75. data/lib/clevic/order_attribute.rb +0 -63
  76. data/lib/clevic/qt/boolean_delegate.rb +0 -8
  77. data/lib/clevic/qt/delegates.rb +0 -1
  78. data/lib/clevic/qt/item_delegate.rb +0 -66
  79. data/lib/clevic/sql_dialects.rb +0 -33
  80. data/tasks/website.rake +0 -25
  81. data/test/test_order_attribute.rb +0 -62
  82. data/test/test_sql_dialects.rb +0 -77
@@ -0,0 +1,37 @@
1
+ =begin rdoc
2
+ To handle multiple GUI frameworks, Clevic makes use of
3
+ Ruby's open classes. Whenever there is a class that
4
+ interacts with a GUI framework (say Qt, or Java Swing)
5
+ the framework-specific part of the class is loaded
6
+ first to get access to the framework's inheritance
7
+ hierarchy, then the file with the framework-neutral
8
+ code is loaded. The code below helps to check
9
+ that the relevant methods from framework-neutral code
10
+ are already defined by the time the framework-neutral
11
+ class definition is executed.
12
+ =end
13
+ class Class
14
+
15
+ # method_name can be a symbol or a string.
16
+ #
17
+ # If a method of this name doesn't already exist
18
+ # add it, so that if when it's called later it raises
19
+ # and exception. Otherwise if the named method already
20
+ # exists, just leave it alone.
21
+ def framework_responsibility( method_name )
22
+ unless instance_methods.include?( method_name.to_s )
23
+ define_method method_name do
24
+ raise "Framework-specific code has not defined for for #{self.class}##{method_name}"
25
+ end
26
+ end
27
+ end
28
+
29
+ def subclass_responsibility( method_name )
30
+ unless instance_methods.include?( method_name.to_s )
31
+ define_method( method_name ) do
32
+ raise "#{method_name} is subclass responsibility for #{self.class}"
33
+ end
34
+ end
35
+ end
36
+
37
+ end
@@ -1,6 +1,9 @@
1
1
  module Clevic
2
2
 
3
- #includers must provide meta and display
3
+ # Format values for display / edit. Essentially a common interface
4
+ # for % for Strings and strftime for Dates and Times.
5
+ #
6
+ # includers must provide meta and display
4
7
  module GenericFormat
5
8
  # Return true if the field is a date, datetime, time or timestamp.
6
9
  # If display is nil, the value is calculated, so we need
@@ -38,6 +41,7 @@ module GenericFormat
38
41
  value
39
42
  end
40
43
  rescue Exception => e
44
+ puts "self: #{self.inspect}"
41
45
  puts "format: #{format.inspect}"
42
46
  puts "value.class: #{value.class.inspect}"
43
47
  puts "value: #{value.inspect}"
@@ -1,7 +1,32 @@
1
1
  module Clevic
2
2
 
3
- # Field for the other end of a one-to-many, or maybe also a many-to-many.
4
- class ManyField < Field
5
- end
3
+ # Preliminary code for multi-valued fields. Not working yet.
4
+ module ManyField
5
+ # x_to_many fields are by definition collections of other entities
6
+ def many( &block )
7
+ if block
8
+ many_view( &block )
9
+ else
10
+ many_view do |mb|
11
+ # TODO should fetch this from one of the field definitions
12
+ mb.plain related_attribute
13
+ end
14
+ end
15
+ end
16
+
17
+ def many_builder
18
+ @many_view.builder
19
+ end
20
+
21
+ def many_fields
22
+ many_builder.fields
23
+ end
24
+
25
+ # return an instance of Clevic::View that represents the many items
26
+ # for this field
27
+ def many_view( &block )
28
+ @many_view ||= View.new( :entity_class => related_class, &block )
29
+ end
30
+ end
6
31
 
7
32
  end
@@ -513,15 +513,17 @@ class ModelBuilder
513
513
  field.delegate = BooleanDelegate.new( field )
514
514
  end
515
515
 
516
- # mostly used in the new block to define the set of records
517
- # for the TableModel, but may also be
518
- # used as an accessor for records.
519
- def records( args = {} )
520
- if args.size == 0
521
- get_records
522
- else
523
- set_records( args )
524
- end
516
+ # specify the dataset but just calling and chaining, thusly
517
+ # dataset.order( :some_field ).filter( :active => true )
518
+ def dataset
519
+ @dataset_roller = DatasetRoller.new( entity_class.dataset )
520
+ end
521
+
522
+ def records( *args )
523
+ puts "ModelBuilder#records is deprecated. Use ModelBuilder#dataset instead"
524
+ require 'clevic/sequel_ar_adapter.rb'
525
+ entity_class.plugin :ar_methods
526
+ @cache_table = CacheTable.new( entity_class, entity_class.translate( args.first ) )
525
527
  end
526
528
 
527
529
  # Tell this field not to show up in the UI.
@@ -560,7 +562,15 @@ class ModelBuilder
560
562
  entity_class.attributes.each do |column,model_column|
561
563
  begin
562
564
  if model_column.association?
563
- relational column
565
+ relational column do |f|
566
+ # TODO this should be tableize or equivalent
567
+ %W{#{model_column.related_class.name.downcase} name title username}.each do |name|
568
+ if model_column.related_class.instance_methods.include?( name )
569
+ f.display = name.to_sym
570
+ break
571
+ end
572
+ end
573
+ end
564
574
  else
565
575
  plain column
566
576
  end
@@ -572,7 +582,6 @@ class ModelBuilder
572
582
  plain column
573
583
  end
574
584
  end
575
- records :order => entity_class.primary_key
576
585
  end
577
586
 
578
587
  # return the named Clevic::Field object
@@ -605,7 +614,7 @@ class ModelBuilder
605
614
  end
606
615
 
607
616
  # the data
608
- @model.collection = records
617
+ @model.collection = create_cache_table
609
618
 
610
619
  @model
611
620
  end
@@ -632,34 +641,20 @@ protected
632
641
  when entity_class.instance_methods.include?( attribute.to_s )
633
642
  # read-only if there's no setter for the attribute
634
643
  !entity_class.instance_methods.include?( "#{attribute.to_s}=" )
644
+
635
645
  else
636
646
  # default to not read-only
637
647
  false
638
648
  end
639
649
  end
640
650
 
641
- # The collection of model objects to display in a table
642
- # arg can either be a Hash, in which case a new CacheTable
643
- # is created, or it can be an array.
644
- # Called by records( *args )
645
- def set_records( arg )
646
- if arg.class == Hash
647
- # need to defer this until all fields are collected
648
- @find_options = arg
649
- else
650
- @records = arg
651
+ def create_cache_table
652
+ if @dataset_roller
653
+ @cache_table = CacheTable.new( entity_class, @dataset_roller.dataset )
651
654
  end
655
+ # otherwise just default it
656
+ @cache_table ||= CacheTable.new( entity_class )
652
657
  end
653
-
654
- # Return a collection of records. Usually this will be a CacheTable.
655
- # Called by records( *args )
656
- def get_records
657
- if @records.nil?
658
- @records = CacheTable.new( entity_class, @find_options )
659
- end
660
- @records
661
- end
662
-
663
658
  end
664
659
 
665
660
  end
@@ -0,0 +1,45 @@
1
+ module Clevic
2
+
3
+ # Provides a nice way of getting to Sequel::Dataset's
4
+ # opts[:order] information
5
+ #
6
+ # Including class must call dataset= before calling order_attributes
7
+ module OrderedDataset
8
+
9
+ # returns a collection of [ attribute, (1|-1) ]
10
+ # where 1 is forward/asc (>) and -1 is backward/desc (<)
11
+ def order_attributes
12
+ if @order_attributes.nil?
13
+ @order_attributes =
14
+ dataset.opts[:order].map do |order_expr|
15
+ case order_expr
16
+ when Symbol
17
+ [ order_expr, 1 ]
18
+
19
+ when Sequel::SQL::OrderedExpression
20
+ [ order_expr.expression, order_expr.descending ? -1 : 1 ]
21
+
22
+ else
23
+ raise "unknown order_expr: #{order_expr.inspect}"
24
+ end
25
+ end
26
+ end
27
+ @order_attributes
28
+ end
29
+
30
+ attr_reader :dataset
31
+
32
+ # Set default dataset ordering to primary key if it doesn't
33
+ # already have an order.
34
+ def dataset=( other )
35
+ @dataset =
36
+ if other.opts[:order].nil?
37
+ other.order( other.model.primary_key )
38
+ else
39
+ other
40
+ end
41
+ end
42
+
43
+ end
44
+
45
+ end
data/lib/clevic/qt.rb CHANGED
@@ -2,13 +2,16 @@
2
2
  Require this file to do Clevic in Qt
3
3
  =end
4
4
 
5
- require 'pathname'
5
+ require 'clevic/framework'
6
+
7
+ require 'Qt4'
6
8
 
7
9
  # require these first, so TableModel and TableView get the correct ancestors
8
10
  require 'clevic/qt/table_model.rb'
9
11
  require 'clevic/qt/table_view.rb'
10
12
 
11
13
  # all other files in the qt subdirectory
14
+ require 'pathname'
12
15
  ( Pathname.new( __FILE__ ).parent + 'qt' ).children.grep( /.rb$/ ).each do |child|
13
16
  require child.to_s
14
17
  end
@@ -35,7 +35,15 @@ module ActionBuilder
35
35
  unless block.nil?
36
36
  action_triggered do
37
37
  qt_action.connect SIGNAL( signal_name ) do |active|
38
- yield( active )
38
+ # need this rescue here because otherwise Qt
39
+ # doesn't catch it and the whole app comes down
40
+ begin
41
+ yield( active )
42
+ rescue
43
+ # TODO how to display this in the UI?
44
+ puts $!.message
45
+ puts $!.backtrace
46
+ end
39
47
  end
40
48
  end
41
49
  end
@@ -154,7 +154,7 @@ class Browser < Qt::Widget
154
154
  # handle filter status changed, so we can provide a visual indication
155
155
  tab.connect SIGNAL( 'filter_status_signal(bool)' ) do |status|
156
156
  # update the tab, so there's a visual indication of filtering
157
- filter_title = ( tab.filtered ? '| ' : '' ) + translate( tab.title )
157
+ filter_title = ( tab.filtered? ? '| ' : '' ) + translate( tab.title )
158
158
  tables_tab.set_tab_text( tables_tab.current_index, filter_title )
159
159
  end
160
160
  rescue Exception => e
@@ -24,9 +24,9 @@ module Clevic
24
24
  system.mime_data.has_html
25
25
  end
26
26
 
27
- # TODO figure out why Qt never has anything other than text
28
- # could be because the event loop isn't running when testing
29
- # from irb
27
+ # TODO figure out why Qt never has anything other than text.
28
+ # Could be because the event loop isn't running when testing
29
+ # from irb.
30
30
  def html
31
31
  system.mime_data.html
32
32
  end
@@ -1,4 +1,5 @@
1
- require 'clevic/qt/item_delegate.rb'
1
+ require 'clevic/qt/delegate.rb'
2
+ require 'clevic/qt/qt_combo_box.rb'
2
3
 
3
4
  module Clevic
4
5
 
@@ -8,12 +9,9 @@ because ComboBox stupidly doesn't.
8
9
 
9
10
  Generally these will be created using a Clevic::ModelBuilder.
10
11
  =end
11
- class ComboDelegate < Clevic::ItemDelegate
12
- def initialize( field )
13
- super
14
- end
15
-
12
+ class ComboDelegate < Clevic::Delegate
16
13
  # Convert Qt:: constants from the integer value to a string value.
14
+ # TODO this really shouldn't be here. qtext, or extensions.rb
17
15
  def hint_string( hint )
18
16
  hs = String.new
19
17
  Qt::AbstractItemDelegate.constants.each do |x|
@@ -38,102 +36,38 @@ class ComboDelegate < Clevic::ItemDelegate
38
36
 
39
37
  # open the combo box, just like if f4 was pressed
40
38
  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"
39
+ editor.show_popup if is_combo?( editor )
61
40
  end
62
41
 
63
- # return true if this delegate needs a combo, false otherwise
64
- def needs_combo?
65
- raise "subclass responsibility"
66
- end
67
-
68
42
  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?
43
+ editor.is_a?( Qt::ComboBox )
76
44
  end
77
45
 
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
46
+ def create_combo_box( *args )
47
+ Qt::ComboBox.new( parent ).tap do |combo|
48
+ # all combos are editable so that prefix matching will work
49
+ combo.editable = true
89
50
  end
90
51
  end
91
52
 
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.display_value.to_variant ) == -1 )
96
- editor.add_item( model_index.display_value, model_index.display_value.to_variant )
97
- end
53
+ # Override the Qt method. Create a ComboBox widget and fill it with the possible values.
54
+ def createEditor( parent_widget, style_option_view_item, model_index )
55
+ self.parent = parent_widget
56
+ self.entity = model_index.entity
57
+ init_component( parent_widget, style_option_view_item, model_index )
58
+ editor.delegate = self
59
+ editor
98
60
  end
99
61
 
100
- def add_nil_item( editor )
101
- if ( editor.find_data( nil.to_variant ) == -1 )
102
- editor.add_item( '', nil.to_variant )
103
- end
62
+ def line_editor( edit_value )
63
+ @line_editor ||= Qt::LineEdit.new( edit_value, parent )
104
64
  end
105
65
 
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.edit_value, parent_widget )
132
- end
133
- end
134
- @editor
66
+ def framework_setup( *args )
67
+ # don't need to do anything here
68
+ # might need to once prefix-matching is implemented
135
69
  end
136
-
70
+
137
71
  # Override the Qt::ItemDelegate method.
138
72
  def updateEditorGeometry( editor, style_option_view_item, model_index )
139
73
  rect = style_option_view_item.rect
@@ -196,3 +130,5 @@ class ComboDelegate < Clevic::ItemDelegate
196
130
  end
197
131
 
198
132
  end
133
+
134
+ require 'clevic/delegates/combo_delegate'