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,38 @@
1
+ =begin
2
+ Require this file to do Clevic in Swing with JRuby
3
+ =end
4
+
5
+ require 'pathname'
6
+
7
+ # This seems to be required for jruby-1.5.x (at least for 1.5.2)
8
+ require 'java'
9
+
10
+ # require these first, so TableModel and TableView get the correct ancestors
11
+ require 'clevic/swing/table_model.rb'
12
+ require 'clevic/swing/table_view.rb'
13
+ ( Pathname.new( __FILE__ ).parent + 'swing' ).children.grep( /.rb$/ ).each do |child|
14
+ require child.to_s
15
+ end
16
+
17
+ # now require the generic parts
18
+ require 'clevic/table_model'
19
+ require 'clevic/table_view'
20
+ require 'clevic.rb'
21
+
22
+ module Clevic
23
+
24
+ def self.tahoma
25
+ if @font.nil?
26
+ @font =
27
+ begin
28
+ found = java.awt.GraphicsEnvironment.local_graphics_environment.all_fonts.select {|f| f.font_name == "Tahoma"}.first
29
+ found.deriveFont( 13.0 )
30
+ java.awt.Font.new( 'DialogInput', java.awt.Font::PLAIN, 13 )
31
+ rescue
32
+ java.awt.Font.new( 'DialogInput', java.awt.Font::PLAIN, 13 )
33
+ end
34
+ end
35
+ @font
36
+ end
37
+
38
+ end
@@ -0,0 +1,125 @@
1
+ require 'gather'
2
+
3
+ module Clevic
4
+
5
+ class Action
6
+ include Gather
7
+ property :shortcut, :method, :handler, :tool_tip, :visible, :name, :text, :checkable
8
+
9
+ def initialize( parent, options = {}, &block )
10
+ @parent = parent
11
+ gather( options, &block )
12
+ end
13
+ attr_reader :parent, :menu_item
14
+
15
+ def plain_text
16
+ text.gsub( /&/, '' )
17
+ end
18
+
19
+ # find the java.awt.event.KeyEvent::VK constant
20
+ # for the letter after the &. Then set it on the item's
21
+ # mnemonic. Because JMenuItem.setMnemonic won't take a nil
22
+ def mnemonic
23
+ if @mnemonic.nil?
24
+ ix = text.index '&'
25
+ @mnemonic =
26
+ if ix
27
+ eval( "java.awt.event.KeyEvent::VK_#{text[ix+1..ix+1].upcase}" )
28
+ else
29
+ false
30
+ end
31
+ end
32
+ @mnemonic
33
+ end
34
+
35
+ def menu_item
36
+ if @menu_item.nil?
37
+ @menu_item =
38
+ if checkable
39
+ javax.swing.JCheckBoxMenuItem.new
40
+ else
41
+ javax.swing.JMenuItem.new
42
+ end
43
+
44
+ menu_item.text = plain_text
45
+ menu_item.mnemonic = mnemonic if mnemonic
46
+ menu_item.accelerator = parse_shortcut( shortcut ) unless shortcut.nil?
47
+ menu_item.tool_tip_text = tool_tip
48
+ menu_item.add_action_listener do |event|
49
+ handler.call( event )
50
+ end
51
+ end
52
+ @menu_item
53
+ end
54
+
55
+ # parse a Qt-style Ctrl+D shortcut specification
56
+ # and return a javax.swing.KeyStroke
57
+ def parse_shortcut( sequence )
58
+ # munge keystroke to something getKeyStroke recognises
59
+ # file:///usr/share/doc/java-sdk-docs-1.6.0.10/html/api/javax/swing/KeyStroke.html#getKeyStroke%28java.lang.String%29
60
+ # Yes, the space MUST be last in the charset, otherwise Ctrl-" fails
61
+ modifiers = sequence.split( /[-+ ]/ )
62
+ last = modifiers.pop
63
+
64
+ modifier_mask = modifiers.inject(0) do |mask,value|
65
+ mask |
66
+ if value =~ /ctrl/i
67
+ java.awt.Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()
68
+ else
69
+ eval( "java.awt.event.InputEvent::#{value.upcase}_DOWN_MASK" )
70
+ end
71
+ end
72
+
73
+ keystroke =
74
+ if last.length == 1
75
+ case last
76
+ # these two seem to break the KeyStroke parsing algorithm
77
+ when "'"
78
+ javax.swing.KeyStroke.getKeyStroke( java.awt.event.KeyEvent::VK_QUOTE, modifier_mask )
79
+
80
+ when '"'
81
+ javax.swing.KeyStroke.getKeyStroke( java.awt.event.KeyEvent::VK_QUOTE, modifier_mask | java.awt.event.InputEvent::SHIFT_DOWN_MASK )
82
+
83
+ # the conversion in else doesn't work for these
84
+ when '['
85
+ javax.swing.KeyStroke.getKeyStroke( java.awt.event.KeyEvent::VK_OPEN_BRACKET, modifier_mask )
86
+
87
+ when ']'
88
+ javax.swing.KeyStroke.getKeyStroke( java.awt.event.KeyEvent::VK_CLOSE_BRACKET, modifier_mask )
89
+
90
+ when ';'
91
+ javax.swing.KeyStroke.getKeyStroke( java.awt.event.KeyEvent::VK_SEMICOLON, modifier_mask )
92
+
93
+ else
94
+ keystring = javax.swing.KeyStroke.getKeyStroke( java.lang.Character.new( last.to_char ), modifier_mask ).toString
95
+ puts "keystring: #{keystring.inspect}"
96
+ # have to do this conversion for Mac OS X
97
+ javax.swing.KeyStroke.getKeyStroke( keystring.gsub( /typed/, 'pressed' ) )
98
+ end
99
+ else
100
+ # F keys
101
+ # insert, delete, tab etc
102
+ found = java.awt.event.KeyEvent.constants.grep( /#{last}/i )
103
+ raise "too many options for #{sequence}: #{found.inspect}" if found.size != 1
104
+ javax.swing.KeyStroke.getKeyStroke( eval( "java.awt.event.KeyEvent::#{found.first}" ), modifier_mask )
105
+ end
106
+ puts "keystroke: #{keystroke.inspect}"
107
+ keystroke || raise( "unknown keystroke #{sequence} => #{modifiers.inspect} #{last}" )
108
+ end
109
+ end
110
+
111
+ # dummy class for creating a menu separator
112
+ class Separator < Action
113
+ def initialize
114
+ super(nil)
115
+ end
116
+
117
+ def shortcut
118
+ nil
119
+ end
120
+
121
+ def menu_item
122
+ end
123
+ end
124
+
125
+ end
@@ -0,0 +1,47 @@
1
+ require 'clevic/action_builder.rb'
2
+ require 'clevic/swing/action.rb'
3
+ require 'clevic/swing/extensions.rb'
4
+
5
+ require 'changes'
6
+
7
+ module Clevic
8
+
9
+ module ActionBuilder
10
+ # Create a new separator and add a new separator.
11
+ def separator
12
+ Separator.new.tap do |action|
13
+ add_action action
14
+ collect_actions << action
15
+ end
16
+ end
17
+
18
+ def create_action( &block )
19
+ Action.new( self ).tap( &block )
20
+ end
21
+
22
+ # set up the code to be executed when an action is triggered,
23
+ def action_method_or_block( action, options, &block )
24
+ # connect the action to some code
25
+ if options.has_key?( :method )
26
+ action.handler do |event|
27
+ puts "#{__FILE__}:#{__LINE__}:action.name: #{action.name.inspect}"
28
+ action_triggered do
29
+ # active is from Qt checkbox-menu-items
30
+ send_args = [ options[:method], options.has_key?( :checkable ) ? action.menu_item.selected? : nil ].compact
31
+ send( *send_args )
32
+ end
33
+ end
34
+ else
35
+ unless block.nil?
36
+ # TODO not sure why triggered is outside here, but not in the method section
37
+ action_triggered do
38
+ action.handler do |event|
39
+ yield( event )
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+ end # Clevic
@@ -0,0 +1,26 @@
1
+ require 'clevic/swing/delegate'
2
+
3
+ module Clevic
4
+
5
+ class BooleanDelegate < Delegate
6
+ def init_component( cell_editor )
7
+ editor.selected = attribute_value
8
+ end
9
+
10
+ def editor
11
+ @editor ||= javax.swing.JCheckBox.new.tap do |e|
12
+ # TODO this is common to all delegates
13
+ e.horizontal_alignment = field.swing_alignment
14
+ end
15
+ end
16
+
17
+ def value
18
+ editor.selected
19
+ end
20
+
21
+ def native
22
+ java.lang.Boolean
23
+ end
24
+ end
25
+
26
+ end
@@ -0,0 +1,282 @@
1
+ #~ require 'clevic/search_dialog.rb'
2
+ require 'clevic/view'
3
+ require 'clevic/swing/action_builder'
4
+
5
+ module Clevic
6
+
7
+ =begin rdoc
8
+ The main application class. Display one tabs for each descendant of Clevic::View
9
+ in Clevic::View.order. DefaultView classes created by Clevic::Record are included.
10
+ =end
11
+ class Browser < javax.swing.JFrame
12
+ #~ slots *%w{dump() refresh_table() filter_by_current(bool) next_tab() previous_tab()}
13
+
14
+ attr_reader :menu_edit, :menu_search, :menu_table
15
+
16
+ def initialize
17
+ super
18
+
19
+ # set OSX application title
20
+ java.lang.System.setProperty( "com.apple.mrj.application.apple.menu.about.name", title_string )
21
+
22
+ self.jmenu_bar = menu_bar
23
+ self.icon_image = icon
24
+
25
+ # add the tables tab
26
+ add( tables_tab, java.awt.BorderLayout::CENTER )
27
+
28
+ # add the status bar
29
+ add( status_bar, java.awt.BorderLayout::SOUTH )
30
+
31
+ load_views
32
+ update_menus
33
+ self.title = title_string
34
+ end
35
+
36
+ def title_string
37
+ [database_name, 'Clevic'].compact.join ' '
38
+ end
39
+
40
+ def menu_bar
41
+ javax.swing.JMenuBar.new.tap do |menu_bar|
42
+ menu_bar << javax.swing.JMenu.new( 'File' ).tap do |menu|
43
+ menu.mnemonic = java.awt.event.KeyEvent::VK_F
44
+ menu << "Open"
45
+ menu << "Close"
46
+ end
47
+
48
+ @menu_edit = javax.swing.JMenu.new( 'Edit' ).tap {|m| m.mnemonic = java.awt.event.KeyEvent::VK_E}
49
+ menu_bar << menu_edit
50
+
51
+ @menu_search = javax.swing.JMenu.new( 'Search' ).tap {|m| m.mnemonic = java.awt.event.KeyEvent::VK_S}
52
+ menu_bar << menu_search
53
+
54
+ @menu_table = javax.swing.JMenu.new( 'Table' ).tap do |menu|
55
+ menu.mnemonic = java.awt.event.KeyEvent::VK_T
56
+ menu << Action.new( self ) do |action|
57
+ action.name = :next_tab
58
+ action.text = "&Next"
59
+ action.shortcut = "Ctrl+Tab"
60
+ action.handler do |event|
61
+ puts "next tab"
62
+ next_tab
63
+ end
64
+ end
65
+
66
+ menu << Action.new( self ) do |action|
67
+ action.name = :previous_tab
68
+ action.text = "&Previous"
69
+ action.shortcut = "Shift+Ctrl+Tab"
70
+ action.handler do |event|
71
+ puts "previous tab"
72
+ previous_tab
73
+ end
74
+ end
75
+
76
+ if $options[:debug]
77
+ menu << Action.new( self ) do |action|
78
+ action.name = :dump
79
+ action.text = "&Dump"
80
+ action.shortcut = "Ctrl+D"
81
+ action.handler do |event|
82
+ dump
83
+ end
84
+ end
85
+ end
86
+ end
87
+ menu_bar << @menu_table
88
+ end
89
+ end
90
+
91
+ def icon
92
+ @icon ||=
93
+ begin
94
+ icon_path = Pathname.new( __FILE__ ).parent.parent + "icons/icon.png"
95
+ raise "icon.png not found" unless icon_path.file?
96
+ javax.swing.ImageIcon.new( icon_path.realpath.to_s ).image
97
+ end
98
+ end
99
+
100
+ def tables_tab
101
+ @tables_tab ||= javax.swing.JTabbedPane.new.tap do |tables_tab|
102
+ # tell tab to not take focus
103
+ tables_tab.focusable = false
104
+
105
+ # tab navigation
106
+ tables_tab.add_change_listener do |change_event|
107
+ puts "change_event: #{change_event.source.inspect}"
108
+ puts "change_event.source.selected_index: #{change_event.source.selected_index.inspect}"
109
+ current_changed
110
+ # TODO tell exiting tab to save currently editing row/cell
111
+ end
112
+ end
113
+ end
114
+
115
+ def status_bar
116
+ @status_bar ||= javax.swing.JLabel.new.tap do |status_bar|
117
+ status_bar.horizontal_alignment = javax.swing.SwingConstants::RIGHT
118
+ # just so the bar actually displays
119
+ status_bar.text = "Welcome to Clevic"
120
+ end
121
+ end
122
+
123
+ def status_bar_timer
124
+ @status_bar_timer ||= javax.swing.Timer.new( 15000, nil ).tap do |timer|
125
+ timer.repeats = false
126
+ # This is only for 1.6
127
+ #~ timer.action_command = 'hide_status_message'
128
+ timer.add_action_listener do |event|
129
+ status_bar.text = nil
130
+ timer.stop
131
+ end
132
+ end
133
+ end
134
+
135
+ # Set the main window title to the name of the database, if we can find it.
136
+ def database_name
137
+ table_view.model.entity_class.db.url rescue ''
138
+ end
139
+
140
+ # called by current_changed to update the Edit menu to the menus
141
+ # defined by the currently selected view
142
+ def update_menus
143
+ # update edit menu
144
+ menu_edit.remove_all
145
+
146
+ # do the model-specific menu items first
147
+ table_view.model_actions.each do |action|
148
+ menu_edit << action
149
+ end
150
+
151
+ # now do the generic edit items
152
+ table_view.edit_actions.each do |action|
153
+ menu_edit << action
154
+ end
155
+
156
+ # update search menu
157
+ menu_search.remove_all
158
+ table_view.search_actions.each do |action|
159
+ menu_search << action
160
+ end
161
+ end
162
+
163
+ # activated by Ctrl-Shift-D for debugging
164
+ def dump
165
+ puts "table_view.model: #{table_view.model.inspect}"
166
+ puts "table_view.model.entity_class: #{table_view.model.entity_class.inspect}"
167
+ end
168
+
169
+ # return the Clevic::TableView object in the currently displayed tab
170
+ def table_view
171
+ tables_tab.selected_component
172
+ end
173
+
174
+ # slot to handle Ctrl-Tab and move to next tab, or wrap around
175
+ def next_tab
176
+ tables_tab.selected_index =
177
+ if tables_tab.selected_index >= tables_tab.count - 1
178
+ 0
179
+ else
180
+ tables_tab.selected_index + 1
181
+ end
182
+ end
183
+
184
+ # slot to handle Ctrl-Backtab and move to previous tab, or wrap around
185
+ def previous_tab
186
+ tables_tab.selected_index =
187
+ if tables_tab.selected_index <= 0
188
+ tables_tab.count - 1
189
+ else
190
+ tables_tab.selected_index - 1
191
+ end
192
+ end
193
+
194
+ # slot to handle the currentChanged signal from tables_tab, and
195
+ # set focus on the grid
196
+ def current_changed
197
+ update_menus
198
+ table_view.request_focus
199
+ end
200
+
201
+ # Create the tabs, each with a collection for a particular entity class.
202
+ # views come from Clevic::View.order
203
+ def load_views
204
+ views = Clevic::View.views
205
+ Kernel.raise "no views to display" if views.empty?
206
+
207
+ # Add all existing model objects as tabs, one each
208
+ views.each do |view_class|
209
+ begin
210
+ view = view_class.new
211
+ unless view.entity_class.table_exists?
212
+ puts "Browser::load_views: No table for #{view.entity_class.inspect}"
213
+ next
214
+ end
215
+
216
+ # create the the table_view and the table_model for the entity_class
217
+ tab = Clevic::TableView.new( view )
218
+
219
+ # add a new tab
220
+ tables_tab.add( tab.title, tab )
221
+
222
+ # add the table to the Table menu
223
+ menu_table << Action.new( self ) do |action|
224
+ action.text = tab.title
225
+ action.handler do
226
+ tables_tab.current = tab
227
+ end
228
+ end
229
+
230
+ init_connections( tab )
231
+ rescue Exception => e
232
+ puts "UI from #{view} will not be available: #{e.message}"
233
+ puts e.backtrace
234
+ end
235
+ end
236
+ end
237
+
238
+ def init_connections( tab )
239
+ tab.emit_status_text do |msg|
240
+ status_bar.text = msg
241
+ # hide the message after a while.
242
+ status_bar_timer.start
243
+ end
244
+
245
+ # handle filter status changed, so we can provide a visual indication
246
+ tab.emit_filter_status do |status|
247
+ # update the tab, so there's a visual indication of filtering
248
+ filter_title = ( tab.filtered? ? '| ' : '' ) + tab.title
249
+ tables_tab.set_title_at( tables_tab.selected_index, filter_title )
250
+
251
+ if tab.filtered?
252
+ tables_tab.set_tool_tip_text_at( tables_tab.selected_index, tab.filtered.status_message )
253
+ else
254
+ tables_tab.set_tool_tip_text_at( tables_tab.selected_index, nil )
255
+ end
256
+ end
257
+ end
258
+
259
+ # make sure all outstanding records are saved
260
+ def save_all
261
+ tables_tab.each {|x| x.save_row( x.current_index ) }
262
+ end
263
+
264
+ def self.run( args )
265
+ # make it more appley
266
+ java.lang.System.setProperty( "apple.laf.useScreenMenuBar", "true" )
267
+ javax.swing.UIManager.setLookAndFeel( javax.swing.UIManager.getSystemLookAndFeelClassName() )
268
+
269
+ # load models
270
+ args.each do |arg|
271
+ load_models( Pathname.new( arg ) )
272
+ end
273
+
274
+ # make top-level UI
275
+ browser = Browser.new
276
+ browser.default_close_operation = javax.swing.JFrame::EXIT_ON_CLOSE
277
+ browser.pack
278
+ browser.visible = true
279
+ end
280
+ end
281
+
282
+ end