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.
- data/History.txt +10 -0
- data/Manifest.txt +209 -30
- data/README.txt +16 -20
- data/Rakefile +8 -8
- data/TODO +6 -7
- data/bin/clevic +12 -73
- data/lib/clevic/action_builder.rb +168 -0
- data/lib/clevic/ar_methods.rb +120 -0
- data/lib/clevic/attribute_list.rb +56 -0
- data/lib/clevic/cache_table.rb +60 -37
- data/lib/clevic/default_view.rb +3 -16
- data/lib/clevic/delegate.rb +46 -0
- data/lib/clevic/emitter.rb +38 -0
- data/lib/clevic/extensions.rb +61 -114
- data/lib/clevic/field.rb +159 -228
- data/lib/clevic/field_valuer.rb +165 -0
- data/lib/clevic/filter_command.rb +2 -6
- data/lib/clevic/generic_format.rb +52 -0
- data/lib/clevic/{ui → icons}/icon.png +0 -0
- data/lib/clevic/many_field.rb +7 -0
- data/lib/clevic/model_builder.rb +234 -146
- data/lib/clevic/model_column.rb +61 -13
- data/lib/clevic/order_attribute.rb +10 -0
- data/lib/clevic/qt.rb +35 -0
- data/lib/clevic/qt/action_builder.rb +47 -0
- data/lib/clevic/qt/boolean_delegate.rb +8 -0
- data/lib/clevic/{browser.rb → qt/browser.rb} +35 -14
- data/lib/clevic/qt/clipboard.rb +35 -0
- data/lib/clevic/qt/combo_delegate.rb +198 -0
- data/lib/clevic/qt/delegates.rb +1 -0
- data/lib/clevic/qt/distinct_delegate.rb +35 -0
- data/lib/clevic/qt/extensions.rb +52 -0
- data/lib/clevic/qt/field.rb +18 -0
- data/lib/clevic/{item_delegate.rb → qt/item_delegate.rb} +8 -4
- data/lib/clevic/qt/relational_delegate.rb +87 -0
- data/lib/clevic/{search_dialog.rb → qt/search_dialog.rb} +1 -11
- data/lib/clevic/qt/set_delegate.rb +44 -0
- data/lib/clevic/qt/table_model.rb +331 -0
- data/lib/clevic/qt/table_view.rb +344 -0
- data/lib/clevic/qt/text_area_delegate.rb +8 -0
- data/lib/clevic/{text_delegate.rb → qt/text_delegate.rb} +6 -4
- data/lib/clevic/{ui → qt/ui}/.gitignore +0 -0
- data/lib/clevic/{ui → qt/ui}/browser.ui +0 -0
- data/lib/clevic/{ui → qt/ui}/search_dialog.ui +0 -0
- data/lib/clevic/rails_models_loaders.rb +56 -0
- data/lib/clevic/record.rb +2 -17
- data/lib/clevic/sampler.rb +81 -0
- data/lib/clevic/sequel_ar_adapter.rb +215 -0
- data/lib/clevic/sequel_length_validation.rb +23 -0
- data/lib/clevic/sequel_meta.rb +65 -0
- data/lib/clevic/sequel_naked.rb +30 -0
- data/lib/clevic/swing.rb +38 -0
- data/lib/clevic/swing/action.rb +125 -0
- data/lib/clevic/swing/action_builder.rb +47 -0
- data/lib/clevic/swing/boolean_delegate.rb +26 -0
- data/lib/clevic/swing/browser.rb +282 -0
- data/lib/clevic/swing/cell_editor.rb +95 -0
- data/lib/clevic/swing/cell_renderer.rb +44 -0
- data/lib/clevic/swing/clipboard.rb +135 -0
- data/lib/clevic/swing/combo_delegate.rb +336 -0
- data/lib/clevic/swing/confirm_dialog.rb +57 -0
- data/lib/clevic/swing/delegate.rb +40 -0
- data/lib/clevic/swing/distinct_delegate.rb +30 -0
- data/lib/clevic/swing/extensions.rb +274 -0
- data/lib/clevic/swing/field.rb +35 -0
- data/lib/clevic/swing/relational_delegate.rb +48 -0
- data/lib/clevic/swing/row_header.rb +210 -0
- data/lib/clevic/swing/search_dialog.rb +230 -0
- data/lib/clevic/swing/selection_model.rb +90 -0
- data/lib/clevic/swing/set_delegate.rb +41 -0
- data/lib/clevic/swing/swing_table_index.rb +43 -0
- data/lib/clevic/swing/table_model.rb +200 -0
- data/lib/clevic/swing/table_view.rb +385 -0
- data/lib/clevic/swing/table_view_focus.rb +47 -0
- data/lib/clevic/swing/tag_delegate.rb +127 -0
- data/lib/clevic/swing/tag_editor.rb +101 -0
- data/lib/clevic/swing/text_area_delegate.rb +46 -0
- data/lib/clevic/swing/text_delegate.rb +31 -0
- data/lib/clevic/swing/ui/build.xml +74 -0
- data/lib/clevic/swing/ui/dist/README.TXT +33 -0
- data/lib/clevic/swing/ui/dist/lib/swing-layout-1.0.3.jar +0 -0
- data/lib/clevic/swing/ui/manifest.mf +3 -0
- data/lib/clevic/swing/ui/nbproject/build-impl.xml +731 -0
- data/lib/clevic/swing/ui/nbproject/genfiles.properties +8 -0
- data/lib/clevic/swing/ui/nbproject/private/config.properties +0 -0
- data/lib/clevic/swing/ui/nbproject/private/private.properties +6 -0
- data/lib/clevic/swing/ui/nbproject/private/private.xml +4 -0
- data/lib/clevic/swing/ui/nbproject/project.properties +70 -0
- data/lib/clevic/swing/ui/nbproject/project.xml +14 -0
- data/lib/clevic/swing/ui/src/SearchDialog.form +158 -0
- data/lib/clevic/swing/ui/src/SearchDialog.java +163 -0
- data/lib/clevic/swing/ui/src/TagEditor.form +106 -0
- data/lib/clevic/swing/ui/src/TagEditor.java +108 -0
- data/lib/clevic/swing/ui/src/resources/SearchDialog.properties +0 -0
- data/lib/clevic/table_index.rb +100 -0
- data/lib/clevic/table_model.rb +54 -425
- data/lib/clevic/table_searcher.rb +113 -116
- data/lib/clevic/table_view.rb +171 -399
- data/lib/clevic/table_view_paste.rb +199 -0
- data/lib/clevic/version.rb +3 -2
- data/lib/clevic/view.rb +94 -43
- data/models/accounts_models.rb +13 -13
- data/models/minimal_models.rb +5 -9
- data/models/times_models.rb +19 -14
- data/models/times_psql_models.rb +10 -0
- data/models/times_sqlite_models.rb +1 -8
- data/models/values_models.rb +2 -8
- data/tasks/clevic.rake +1 -1
- data/tasks/rdoc.rake +1 -5
- data/tasks/website.rake +1 -1
- data/test/test_cache_table.rb +15 -29
- data/test/test_helper.rb +14 -83
- data/test/test_order_attribute.rb +1 -1
- data/test/test_table_model.rb +0 -21
- data/test/test_table_searcher.rb +67 -61
- metadata +262 -78
- data/lib/clevic.rb +0 -4
- data/lib/clevic/db_options.rb +0 -112
- 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
|