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,95 @@
1
+ module Clevic
2
+
3
+ # This is the glue class that interfaces with JTable's API
4
+ # There's usually only ever one of them for any given JTable,
5
+ # so it's created once, and then re-used repeatedly.
6
+ # Must inherit from JComponent so that it gets focus
7
+ # when the editing starts
8
+ class CellEditor < javax.swing.JComponent
9
+ include javax.swing.table.TableCellEditor
10
+
11
+ def initialize( table_view )
12
+ super()
13
+ @table_view = table_view
14
+ @listeners = []
15
+ end
16
+
17
+ attr_accessor :listeners
18
+ attr_reader :index
19
+
20
+ def delegate
21
+ index.field.delegate
22
+ end
23
+
24
+ # override TableCellEditor methods
25
+ # basically, initialize a component to send back to the JTable, and store
26
+ # a bunch of state information
27
+ def getTableCellEditorComponent(jtable, value, selected, row_index, column_index)
28
+ # remember index for later. The delegate and the editor and the value
29
+ # all come from it.
30
+ @index = @table_view.model.create_index( row_index, column_index )
31
+
32
+ # use the delegate's component. It actually comes from the index, which
33
+ # is a bit weird. But anyway.
34
+ delegate.entity = @index.entity
35
+ # need self so combo boxes can get back here and stop editing when enter is pressed
36
+ delegate.init_component( self )
37
+ delegate.editor
38
+ end
39
+
40
+ # Adds a listener to the list that's notified when the editor stops, or cancels editing.
41
+ def addCellEditorListener(cell_editor_listener)
42
+ listeners << cell_editor_listener
43
+ end
44
+
45
+ def change_event
46
+ @change_event ||= javax.swing.event.ChangeEvent.new( self )
47
+ end
48
+
49
+ # Tells the editor to cancel editing and not accept any partially edited value.
50
+ def cancelCellEditing
51
+ listeners.each do |listener|
52
+ listener.editingCancelled( change_event )
53
+ end
54
+ end
55
+
56
+ # Returns the value contained in the editor.
57
+ def getCellEditorValue
58
+ delegate.value
59
+ end
60
+
61
+ # Asks the editor if it can start editing using anEvent.
62
+ def isCellEditable(event_object)
63
+ true
64
+ end
65
+
66
+ # Removes a listener from the list that's notified
67
+ def removeCellEditorListener(cell_editor_listener)
68
+ listeners.delete cell_editor_listener
69
+ end
70
+
71
+ # Docs say not used, as of Java-1.2. But it is used. Not sure
72
+ # what to do with it, really.
73
+ def shouldSelectCell(event_object)
74
+ true
75
+ end
76
+
77
+ # Tells the editor to stop editing and accept any partially edited value as the value of the editor
78
+ # true if editing was stopped, false otherwise
79
+ def stopCellEditing
80
+ listeners.each do |listener|
81
+ listener.editingStopped( change_event )
82
+ end
83
+
84
+ # can return false here if editing should not stop
85
+ # for some reason, ie validation didn't succeed
86
+ true
87
+ rescue
88
+ puts
89
+ puts $!.backtrace
90
+ puts "returning false from stopCellEditing"
91
+ false
92
+ end
93
+ end
94
+
95
+ end
@@ -0,0 +1,44 @@
1
+ module Clevic
2
+
3
+ class CellRenderer < javax.swing.table.DefaultTableCellRenderer
4
+ def initialize( table_view )
5
+ super()
6
+ @table_view = table_view
7
+ end
8
+
9
+ def getTableCellRendererComponent( table, value, selected, has_focus, row_index, column_index )
10
+ index = table.model.create_index( row_index, column_index )
11
+ component = super( table, index.display_value, selected, has_focus, row_index, column_index )
12
+
13
+ # set alignment
14
+ component.horizontal_alignment = index.field.swing_alignment
15
+
16
+ # set text colour
17
+ component.foreground =
18
+ case
19
+ # read-only
20
+ when index.field.read_only? || index.entity.andand.readonly? || @table_view.model.read_only?
21
+ java.awt.Color.lightGray
22
+
23
+ # errors
24
+ when index.entity.errors.has_key?( index.field.id )
25
+ java.awt.Color.red
26
+
27
+ # whatever the view says
28
+ else
29
+ index.field.foreground_for( index.entity )
30
+ end
31
+
32
+ # set tooltip
33
+ component.tool_tip_text = index.tooltip
34
+
35
+ component
36
+ rescue
37
+ puts $!.backtrace
38
+ puts $!.message
39
+ puts index.entity.inspect
40
+ nil
41
+ end
42
+ end
43
+
44
+ end
@@ -0,0 +1,135 @@
1
+ # need this just in case it hasn't been loaded yet
2
+ java.awt.datatransfer.DataFlavor
3
+
4
+ module Java
5
+ module JavaAwtDatatransfer
6
+ class DataFlavor
7
+ def inspect
8
+ "#<DataFlavor #{mime_type}>"
9
+ end
10
+
11
+ def simple_type
12
+ mime_type.split('; ').first
13
+ end
14
+ end
15
+ end
16
+ end
17
+
18
+ require 'io/like'
19
+
20
+ module Clevic
21
+
22
+ # Wrapper for java.io.InputStream to make it nicer for Ruby
23
+ class Stream
24
+ def initialize( input_stream )
25
+ @input_stream = input_stream
26
+ end
27
+
28
+ include IO::Like
29
+ def unbuffered_read( length )
30
+ nex = @input_stream.read
31
+ raise EOFError if nex == -1
32
+
33
+ begin
34
+ (0...length).inject([nex]) do |buf,i|
35
+ nex = @input_stream.read
36
+ if nex == -1
37
+ break( buf )
38
+ else
39
+ buf << nex
40
+ end
41
+ end.pack('c*')
42
+ rescue
43
+ raise SystemCallError, $!.message
44
+ end
45
+ end
46
+ end
47
+
48
+ # Wrapper for framework-specigfic clipboard code. Used by TableView.
49
+ #
50
+ # could also use a javax.activation.DataHandler
51
+ # for a more sophisticated API
52
+ # TODO use javaJVMLocalObjectMimeType
53
+ # file:///usr/share/doc/java-sdk-docs-1.6.0.10/html/api/java/awt/datatransfer/DataFlavor.html#javaJVMLocalObjectMimeType
54
+ # also use a DataFlavor with mimetype application/x-java-serialized-object
55
+ # to transfer between cells.
56
+ class Clipboard
57
+ def system
58
+ @system ||= java.awt.Toolkit.default_toolkit.system_clipboard
59
+ end
60
+
61
+ def flavours
62
+ system.available_data_flavors.to_a
63
+ end
64
+
65
+ def text?
66
+ mime_types.include?( 'text/plain' )
67
+ end
68
+
69
+ def text; contents('text/plain'); end
70
+
71
+ def text=( value )
72
+ transferable = java.awt.datatransfer.StringSelection.new( value )
73
+ system.setContents( transferable, transferable )
74
+ end
75
+
76
+ def html?
77
+ mime_types.include?( 'text/html' )
78
+ end
79
+
80
+ def html; contents('text/html'); end
81
+
82
+ def mime_types
83
+ flavours.map( &:simple_type ).sort.uniq
84
+ end
85
+
86
+ def full_mime_types
87
+ flavours.map( &:mime_type )
88
+ end
89
+
90
+ # matcher is either a string or a regex, ie something
91
+ # that can be passed to Array#grep
92
+ def has?( matcher )
93
+ !full_mime_types.grep( matcher ).empty?
94
+ end
95
+
96
+ # try a bunch of encodings for the given mime_type, and give
97
+ # back a String containing the result
98
+ def contents( mime_type )
99
+ case
100
+ # try UTF-8 first because it seems more robust
101
+ when has?( %r{#{mime_type}.*String.*utf-8}i )
102
+ data "#{mime_type}; class=java.lang.String; charset=UTF-8"
103
+
104
+ # now string Unicode, just in case
105
+ when has?( %r{#{mime_type}.*String.*unicode}i )
106
+ data "#{mime_type}; class=java.lang.String; charset=unicode"
107
+
108
+ # This is to handle clevic-clevic pastes
109
+ when has?( %r{#{mime_type}.*Stream.*unicode}i )
110
+ stream( "#{mime_type}; class=java.io.InputStream; charset=unicode" ).read
111
+
112
+ else
113
+ raise "Don't know how to get #{mime_type}"
114
+ end
115
+ end
116
+
117
+ def data( full_mime_type )
118
+ flavor = java.awt.datatransfer.DataFlavor.new( full_mime_type )
119
+ system.getData( flavor )
120
+ end
121
+
122
+ def stream( full_mime_type )
123
+ Stream.new( data( full_mime_type ) )
124
+ end
125
+
126
+ def []( mime_type )
127
+ contents( mime_type ).unpack('U*').inject([]) do |collect,byte|
128
+ # ignore BOM
129
+ collect << byte unless byte == 65533
130
+ collect
131
+ end.pack( "U*" )
132
+ end
133
+ end
134
+
135
+ end
@@ -0,0 +1,336 @@
1
+ require 'andand'
2
+ require 'clevic/swing/delegate'
3
+
4
+ module Clevic
5
+
6
+ # all this just to format a display item...
7
+ # .... and work around various other Swing stupidities
8
+ class ComboBox < javax.swing.JComboBox
9
+ def initialize( field )
10
+ super()
11
+ @field = field
12
+ end
13
+
14
+ # set to true by processKeyBinding when a character
15
+ # key is pressed. Used by the autocomplete code.
16
+ attr_reader :typing
17
+
18
+ def configureEditor( combo_box_editor, item )
19
+ value =
20
+ if @field.related_class && item.is_a?( @field.related_class )
21
+ @field.transform_attribute( item )
22
+ else
23
+ item
24
+ end
25
+
26
+ combo_box_editor.item = value
27
+ end
28
+
29
+ # Get the first keystroke when editing starts, and make sure it's entered
30
+ # into the combo box text edit component, if it's not an action key.
31
+ def processKeyBinding( key_stroke, key_event, condition, pressed )
32
+ if key_event.typed? && !key_event.action_key?
33
+ editor.editor_component.text = java.lang.Character.new( key_event.key_char ).toString
34
+ end
35
+ @typing = !key_event.action_key?
36
+ super
37
+ end
38
+ end
39
+
40
+ =begin rdoc
41
+ Base class for other delegates using Combo boxes.
42
+ =end
43
+ # FIXME error with keyboard handling
44
+ # To duplicate:
45
+ # - press F2
46
+ # - use arrows to select
47
+ # - Press Enter
48
+ # - press Tab or Shift-Tab
49
+ class ComboDelegate < Delegate
50
+ def initialize( field )
51
+ super
52
+ @autocompleting = false
53
+ end
54
+
55
+ # Return the GUI component / widget that is displayed when editing.
56
+ # Usually this will be a combo box widget, but it can be a text editor
57
+ # in some cases.
58
+ attr_reader :editor
59
+
60
+ def combo_class
61
+ ComboBox
62
+ end
63
+
64
+ # the cell must be selected before the edit can be clicked
65
+ def needs_pre_selection?
66
+ true
67
+ end
68
+
69
+ def create_combo_box
70
+ # create a new combo class each time, otherwise
71
+ # we have to get into managing cleaning out the model
72
+ # and so on
73
+ combo_class.new( field ).tap do |combo|
74
+ combo.font = Clevic.tahoma
75
+
76
+ # allow for transform of objects to their requested display values
77
+ @original_renderer = combo.renderer
78
+ combo.renderer = self
79
+ end
80
+ end
81
+
82
+ include javax.swing.ListCellRenderer
83
+
84
+ # return the component to render the values in the list
85
+ # we just transform the value, and pass it to the
86
+ # pre-existing renderer for the combo.
87
+ def getListCellRendererComponent(jlist, value, index, selected, cell_has_focus)
88
+ @original_renderer.getListCellRendererComponent(jlist, display_for( value ), index, selected, cell_has_focus)
89
+ end
90
+
91
+ # Return a string to be shown to the user.
92
+ # model_value is an item stored in the combo box model.
93
+ def display_for( model_value )
94
+ field.transform_attribute( model_value )
95
+ end
96
+
97
+ # return a new text editor. This is for distinct_delegate when there
98
+ # are no other values to choose from.
99
+ # TODO move into distinct_delegate then?
100
+ def line_editor( value )
101
+ @line_editor ||= javax.swing.JTextField.new( value ).tap do |line|
102
+ line.font = Clevic.tahoma
103
+ end
104
+ end
105
+
106
+ # Some GUIs (Qt) can just set this. Swing can't.
107
+ def configure_prefix
108
+ end
109
+
110
+ # TODO kinda redundant because all combos must be editable
111
+ # to support prefix matching
112
+ def configure_editable
113
+ editor.editable = true
114
+ end
115
+
116
+ # Create a GUI widget and fill it with the possible values.
117
+ def init_component( cell_editor = nil )
118
+ if needs_combo?
119
+ @editor = create_combo_box
120
+
121
+ # add all entries from population
122
+ population.each do |item|
123
+ editor << item
124
+ end
125
+
126
+ # create a nil entry
127
+ add_nil_item if allow_null?
128
+
129
+ # allow prefix matching from the keyboard
130
+ configure_prefix
131
+
132
+ # don't allow text editing if restricted
133
+ configure_editable
134
+
135
+ # set the correct value in the list
136
+ select_current
137
+
138
+ # pick up events from editor
139
+ # but only after all the other config, otherwise we get
140
+ # events triggered by the setup, which isn't helpful.
141
+ editor.editor.editor_component.document.add_document_listener do |event|
142
+ # don't do anything if autocomplete manipulations are in progress
143
+ unless @autocompleting || !editor.typing
144
+ if event.type == javax.swing.event.DocumentEvent::EventType::REMOVE
145
+ invoke_later do
146
+ repopulate
147
+ end
148
+ else
149
+ # only suggest on inserts and updates. Not on deletes.
150
+ filter_prefix( editor.editor.item )
151
+ end
152
+ end
153
+ end
154
+
155
+ # catch the enter key action event
156
+ editor.editor.editor_component.add_action_listener do |event|
157
+ cell_editor.andand.stopCellEditing
158
+ end
159
+
160
+ # set initial focus and selection in edit part of combo
161
+ editor.editor.editor_component.with do |text_edit|
162
+ unless text_edit.text.nil?
163
+ # highlight the suggested match, and leave caret
164
+ # at the end of the selected text
165
+ text_edit.caret_position = 0
166
+ text_edit.move_caret_position( text_edit.text.length )
167
+ text_edit.request_focus_in_window
168
+ end
169
+ end
170
+
171
+ else
172
+ @editor =
173
+ if restricted?
174
+ show_message( empty_set_message )
175
+ nil
176
+ else
177
+ line_editor( edit_value )
178
+ end
179
+ end
180
+ editor
181
+ end
182
+
183
+ # Recreate the model and fill it with anything in population that
184
+ # matches the prefix first, followed by anything in the population that
185
+ # doesn't match the prefix.
186
+ # Then set the editor text value to either text, or to the previous
187
+ # value.
188
+ # Order is important: if the text is set first it's overridden when
189
+ # the model is populated.
190
+ def repopulate( prefix = nil, text = nil )
191
+ autocomplete do
192
+ # save text and popup
193
+ save_item = editor.editor.item
194
+ dropdown_visible = editor.popup_visible?
195
+
196
+ # repopulate based on the prefix
197
+ prefix ||= editor.editor.item
198
+ editor.model = editor.model.class.new
199
+ # split set into things to display at the top, and things to display further down
200
+ matching, non_matching = population.partition{ |item| display_for( item ) =~ /^#{prefix}/i }
201
+ matching.each {|item| editor << item}
202
+ non_matching.each {|item| editor << item}
203
+
204
+ # restore text and popup
205
+ editor.editor.item = text || save_item
206
+ editor.popup_visible = dropdown_visible
207
+ end
208
+ end
209
+
210
+ # make sure we don't react to document change events
211
+ # while we're doing autocompletion. Reentrant
212
+ def autocomplete( &block )
213
+ @autocompleting = true
214
+ yield
215
+ ensure
216
+ @autocompleting = false
217
+ end
218
+
219
+ # http://www.drdobbs.com/184404457 for autocompletion steps
220
+ def filter_prefix( prefix )
221
+ # search for matching item in the UI display_for for the items in the combo model
222
+ candidate = population.map{|item| display_for( item ) }.select {|x| x =~ /^#{prefix}/i }.first
223
+ unless candidate.nil?
224
+ first_not_of = candidate.match( /^#{prefix}/i ).offset(0).last
225
+ invoke_later do
226
+ autocomplete do
227
+ # set the shortlist, and the text editor value
228
+ repopulate prefix, candidate
229
+
230
+ # set the suggestion selection
231
+ editor.editor.editor_component.with do |text_edit|
232
+ # highlight the suggested match, and leave caret
233
+ # at the beginning of the suggested text
234
+ text_edit.caret_position = candidate.length
235
+ text_edit.move_caret_position( first_not_of )
236
+ end
237
+ end
238
+ end
239
+ end
240
+ end
241
+
242
+ # open the combo box, just like if F4 was pressed
243
+ # big trouble here with JComboBox firing an comboEdited action
244
+ # (probably) on focusGained
245
+ # which causes the popup to be hidden again
246
+ def full_edit
247
+ if is_combo?
248
+ # Must request focus and then once focus is received, show popup.
249
+ # Otherwise focus received hides popup.
250
+ invoke_later do
251
+ #~ editor.add_focus_listener( LittleFocusPopper.new )
252
+ #~ editor.request_focus_in_window
253
+ editor.show_popup
254
+ end
255
+ end
256
+ end
257
+
258
+ # open the combo box, just like if f4 was pressed
259
+ def minimal_edit
260
+ editor.hide_popup if is_combo?
261
+ end
262
+
263
+ # returns true if the editor allows values outside of a predefined
264
+ # range, false otherwise.
265
+ def restricted?
266
+ false
267
+ end
268
+
269
+ # TODO fetch this from the model definition
270
+ def allow_null?
271
+ true
272
+ end
273
+
274
+ # Subclasses should override this to fill the combo box
275
+ # list with values.
276
+ # TODO resolve whether the current item is included, even
277
+ # if it isn't in the population already, ie it's excluded
278
+ # by date or something like that.
279
+ def population
280
+ raise "subclass responsibility"
281
+ end
282
+
283
+ # return true if this delegate needs a combo, false otherwise
284
+ def needs_combo?
285
+ raise "subclass responsibility"
286
+ end
287
+
288
+ def is_combo?
289
+ # Assume we're a combo if we don't have an editor yet, otherwise
290
+ # check
291
+ editor.nil? || editor.is_a?( javax.swing.JComboBox )
292
+ end
293
+
294
+ # return true if this field has no data (needs_combo? is false)
295
+ # and is at the same time restricted (ie needs data from somewhere else)
296
+ def empty_set?
297
+ !needs_combo? && restricted?
298
+ end
299
+
300
+ # the message to display if the set is empty, and
301
+ # the delegate is restricted to a predefined set.
302
+ def empty_set_message
303
+ raise "subclass responsibility"
304
+ end
305
+
306
+ # if this delegate has an empty set, return the message, otherwise
307
+ # return nil.
308
+ def if_empty_message
309
+ empty_set_message if empty_set?
310
+ end
311
+
312
+ def add_nil_item
313
+ editor << nil unless editor.include?( nil )
314
+ end
315
+
316
+ def select_current
317
+ editor.selected_item = attribute_value
318
+ end
319
+
320
+ def value
321
+ # editor could be either a combo or a line (DistinctDelegate with no values yet)
322
+ if is_combo?
323
+ if restricted?
324
+ editor.selected_item
325
+ else
326
+ puts "#{__FILE__}:#{__LINE__}:get the editor's text field value. Take away this output when we know it works. Ie when this gets printed."
327
+ editor.editor.item
328
+ end
329
+ else
330
+ puts "#{__FILE__}:#{__LINE__}:line item value: #{editor.text}"
331
+ editor.text
332
+ end
333
+ end
334
+ end
335
+
336
+ end