clevic 0.13.0.b3 → 0.13.0.b5

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.
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'