Neurogami-jimpanzee 1.0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,68 @@
1
+ # This code is a modified version of the Inflector class
2
+ # from the Ruby on Rails project (http://www.rubyonrails.com)
3
+
4
+ module Monkeybars
5
+ module Inflector
6
+ # The reverse of +camelize+. Makes an underscored form from the expression in the string.
7
+ #
8
+ # Changes '::' to '/' to convert namespaces to paths.
9
+ #
10
+ # Examples
11
+ # "ActiveRecord".underscore #=> "active_record"
12
+ # "ActiveRecord::Errors".underscore #=> active_record/errors
13
+ def underscore()
14
+ self.to_s.gsub(/::/, '/').
15
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
16
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
17
+ tr("-", "_").
18
+ downcase
19
+ end
20
+
21
+ # Constantize tries to find a declared constant with the name specified
22
+ # in the string. It raises a NameError when the name is not in CamelCase
23
+ # or is not initialized.
24
+ #
25
+ # Examples
26
+ # "Module".constantize #=> Module
27
+ # "Class".constantize #=> Class
28
+ def constantize()
29
+ unless /\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/ =~ self.to_s
30
+ raise NameError, "#{self.inspect} is not a valid constant name!"
31
+ end
32
+
33
+ Object.module_eval("::#{$1}", __FILE__, __LINE__)
34
+ end
35
+
36
+ # By default, camelize converts strings to UpperCamelCase. If the argument to camelize
37
+ # is set to ":lower" then camelize produces lowerCamelCase.
38
+ #
39
+ # camelize will also convert '/' to '::' which is useful for converting paths to namespaces
40
+ #
41
+ # Examples
42
+ # "active_record".camelize #=> "ActiveRecord"
43
+ # "active_record".camelize(:lower) #=> "activeRecord"
44
+ # "active_record/errors".camelize #=> "ActiveRecord::Errors"
45
+ # "active_record/errors".camelize(:lower) #=> "activeRecord::Errors"
46
+ def camelize(first_letter_in_uppercase = true)
47
+ if first_letter_in_uppercase
48
+ self.to_s.gsub(/\/(.?)/) { "::" + $1.upcase }.gsub(/(^|_)(.)/) { $2.upcase }
49
+ else
50
+ self.to_s[0..0] + camelize(self.to_s)[1..-1]
51
+ end
52
+ end
53
+ end
54
+ end
55
+
56
+ class String
57
+ include Monkeybars::Inflector
58
+ end
59
+
60
+ class Symbol
61
+ include Monkeybars::Inflector
62
+ end
63
+
64
+ class Class
65
+ def constantize
66
+ self
67
+ end
68
+ end
@@ -0,0 +1,206 @@
1
+ module Monkeybars
2
+ class Key
3
+ @@codes = {}
4
+ @@symbols = {}
5
+ [[:VK_0,48],
6
+ [:VK_1,49],
7
+ [:VK_2,50],
8
+ [:VK_3,51],
9
+ [:VK_4,52],
10
+ [:VK_5,53],
11
+ [:VK_6,54],
12
+ [:VK_7,55],
13
+ [:VK_8,56],
14
+ [:VK_9,57],
15
+ [:VK_A,65],
16
+ [:VK_ACCEPT,30],
17
+ [:VK_ADD,107],
18
+ [:VK_AGAIN,65481],
19
+ [:VK_ALL_CANDIDATES,256],
20
+ [:VK_ALPHANUMERIC,240],
21
+ [:VK_ALT,18],
22
+ [:VK_ALT_GRAPH,65406],
23
+ [:VK_AMPERSAND,150],
24
+ [:VK_ASTERISK,151],
25
+ [:VK_AT,512],
26
+ [:VK_B,66],
27
+ [:VK_BACK_QUOTE,192],
28
+ [:VK_BACK_SLASH,92],
29
+ [:VK_BACK_SPACE,8],
30
+ [:VK_BEGIN,65368],
31
+ [:VK_BRACELEFT,161],
32
+ [:VK_BRACERIGHT,162],
33
+ [:VK_C,67],
34
+ [:VK_CANCEL,3],
35
+ [:VK_CAPS_LOCK,20],
36
+ [:VK_CIRCUMFLEX,514],
37
+ [:VK_CLEAR,12],
38
+ [:VK_CLOSE_BRACKET,93],
39
+ [:VK_CODE_INPUT,258],
40
+ [:VK_COLON,513],
41
+ [:VK_COMMA,44],
42
+ [:VK_COMPOSE,65312],
43
+ [:VK_CONTEXT_MENU,525],
44
+ [:VK_CONTROL,17],
45
+ [:VK_CONVERT,28],
46
+ [:VK_COPY,65485],
47
+ [:VK_CUT,65489],
48
+ [:VK_D,68],
49
+ [:VK_DEAD_ABOVEDOT,134],
50
+ [:VK_DEAD_ABOVERING,136],
51
+ [:VK_DEAD_ACUTE,129],
52
+ [:VK_DEAD_BREVE,133],
53
+ [:VK_DEAD_CARON,138],
54
+ [:VK_DEAD_CEDILLA,139],
55
+ [:VK_DEAD_CIRCUMFLEX,130],
56
+ [:VK_DEAD_DIAERESIS,135],
57
+ [:VK_DEAD_DOUBLEACUTE,137],
58
+ [:VK_DEAD_GRAVE,128],
59
+ [:VK_DEAD_IOTA,141],
60
+ [:VK_DEAD_MACRON,132],
61
+ [:VK_DEAD_OGONEK,140],
62
+ [:VK_DEAD_SEMIVOICED_SOUND,143],
63
+ [:VK_DEAD_TILDE,131],
64
+ [:VK_DEAD_VOICED_SOUND,142],
65
+ [:VK_DECIMAL,110],
66
+ [:VK_DELETE,127],
67
+ [:VK_DIVIDE,111],
68
+ [:VK_DOLLAR,515],
69
+ [:VK_DOWN,40],
70
+ [:VK_E,69],
71
+ [:VK_END,35],
72
+ [:VK_ENTER,10],
73
+ [:VK_EQUALS,61],
74
+ [:VK_ESCAPE,27],
75
+ [:VK_EURO_SIGN,516],
76
+ [:VK_EXCLAMATION_MARK,517],
77
+ [:VK_F,70],
78
+ [:VK_F1,112],
79
+ [:VK_F10,121],
80
+ [:VK_F11,122],
81
+ [:VK_F12,123],
82
+ [:VK_F13,61440],
83
+ [:VK_F14,61441],
84
+ [:VK_F15,61442],
85
+ [:VK_F16,61443],
86
+ [:VK_F17,61444],
87
+ [:VK_F18,61445],
88
+ [:VK_F19,61446],
89
+ [:VK_F2,113],
90
+ [:VK_F20,61447],
91
+ [:VK_F21,61448],
92
+ [:VK_F22,61449],
93
+ [:VK_F23,61450],
94
+ [:VK_F24,61451],
95
+ [:VK_F3,114],
96
+ [:VK_F4,115],
97
+ [:VK_F5,116],
98
+ [:VK_F6,117],
99
+ [:VK_F7,118],
100
+ [:VK_F8,119],
101
+ [:VK_F9,120],
102
+ [:VK_FINAL,24],
103
+ [:VK_FIND,65488],
104
+ [:VK_FULL_WIDTH,243],
105
+ [:VK_G,71],
106
+ [:VK_GREATER,160],
107
+ [:VK_H,72],
108
+ [:VK_HALF_WIDTH,244],
109
+ [:VK_HELP,156],
110
+ [:VK_HIRAGANA,242],
111
+ [:VK_HOME,36],
112
+ [:VK_I,73],
113
+ [:VK_INPUT_METHOD_ON_OFF,263],
114
+ [:VK_INSERT,155],
115
+ [:VK_INVERTED_EXCLAMATION_MARK,518],
116
+ [:VK_J,74],
117
+ [:VK_JAPANESE_HIRAGANA,260],
118
+ [:VK_JAPANESE_KATAKANA,259],
119
+ [:VK_JAPANESE_ROMAN,261],
120
+ [:VK_K,75],
121
+ [:VK_KANA,21],
122
+ [:VK_KANA_LOCK,262],
123
+ [:VK_KANJI,25],
124
+ [:VK_KATAKANA,241],
125
+ [:VK_KP_DOWN,225],
126
+ [:VK_KP_LEFT,226],
127
+ [:VK_KP_RIGHT,227],
128
+ [:VK_KP_UP,224],
129
+ [:VK_L,76],
130
+ [:VK_LEFT,37],
131
+ [:VK_LEFT_PARENTHESIS,519],
132
+ [:VK_LESS,153],
133
+ [:VK_M,77],
134
+ [:VK_META,157],
135
+ [:VK_MINUS,45],
136
+ [:VK_MODECHANGE,31],
137
+ [:VK_MULTIPLY,106],
138
+ [:VK_N,78],
139
+ [:VK_NONCONVERT,29],
140
+ [:VK_NUM_LOCK,144],
141
+ [:VK_NUMBER_SIGN,520],
142
+ [:VK_NUMPAD0,96],
143
+ [:VK_NUMPAD1,97],
144
+ [:VK_NUMPAD2,98],
145
+ [:VK_NUMPAD3,99],
146
+ [:VK_NUMPAD4,100],
147
+ [:VK_NUMPAD5,101],
148
+ [:VK_NUMPAD6,102],
149
+ [:VK_NUMPAD7,103],
150
+ [:VK_NUMPAD8,104],
151
+ [:VK_NUMPAD9,105],
152
+ [:VK_O,79],
153
+ [:VK_OPEN_BRACKET,91],
154
+ [:VK_P,80],
155
+ [:VK_PAGE_DOWN,34],
156
+ [:VK_PAGE_UP,33],
157
+ [:VK_PASTE,65487],
158
+ [:VK_PAUSE,19],
159
+ [:VK_PERIOD,46],
160
+ [:VK_PLUS,521],
161
+ [:VK_PREVIOUS_CANDIDATE,257],
162
+ [:VK_PRINTSCREEN,154],
163
+ [:VK_PROPS,65482],
164
+ [:VK_Q,81],
165
+ [:VK_QUOTE,222],
166
+ [:VK_QUOTEDBL,152],
167
+ [:VK_R,82],
168
+ [:VK_RIGHT,39],
169
+ [:VK_RIGHT_PARENTHESIS,522],
170
+ [:VK_ROMAN_CHARACTERS,245],
171
+ [:VK_S,83],
172
+ [:VK_SCROLL_LOCK,145],
173
+ [:VK_SEMICOLON,59],
174
+ [:VK_SEPARATER,108],
175
+ [:VK_SEPARATOR,108],
176
+ [:VK_SHIFT,16],
177
+ [:VK_SLASH,47],
178
+ [:VK_SPACE,32],
179
+ [:VK_STOP,65480],
180
+ [:VK_SUBTRACT,109],
181
+ [:VK_T,84],
182
+ [:VK_TAB,9],
183
+ [:VK_U,85],
184
+ [:VK_UNDEFINED,0],
185
+ [:VK_UNDERSCORE,523],
186
+ [:VK_UNDO,65483],
187
+ [:VK_UP,38],
188
+ [:VK_V,86],
189
+ [:VK_W,87],
190
+ [:VK_WINDOWS,524],
191
+ [:VK_X,88],
192
+ [:VK_Y,89],
193
+ [:VK_Z,90]].each do |pair|
194
+ @@codes[pair[1]] = pair[0]
195
+ @@symbols[pair[0]] = pair[1]
196
+ end
197
+
198
+ def self.code_to_symbol(code)
199
+ @@codes[code]
200
+ end
201
+
202
+ def self.symbol_to_code(symbol)
203
+ @@symbols[symbol]
204
+ end
205
+ end
206
+ end
@@ -0,0 +1,47 @@
1
+ include_class "foxtrot.Worker"
2
+ include_class "foxtrot.Job"
3
+
4
+ module Monkeybars
5
+ # Module that contains methods and classes used to take care of background
6
+ # task processing. Primarily this is the repaint_while method.
7
+ module TaskProcessor
8
+ # Passes the supplied block to a separate thread and returns the result
9
+ # of the executed block back to the caller. This should be utilized for long-
10
+ # running tasks that ought not tie up the Swing Event Dispatch Thread.
11
+ # Passing a block to this method will allow the GUI to remain responsive
12
+ # (and repaint), while the long-running task is executing.
13
+ def repaint_while(&task)
14
+ runner = Runner.new(&task)
15
+ Worker.post(runner)
16
+ end
17
+
18
+ def on_edt(&task)
19
+ if javax.swing.SwingUtilities.event_dispatch_thread?
20
+ javax.swing.SwingUtilities.invoke_later Runnable.new(task)
21
+ else
22
+ javax.swing.SwingUtilities.invoke_and_wait Runnable.new(task)
23
+ end
24
+ end
25
+
26
+ class Runner < Job
27
+ def initialize(&proc)
28
+ @proc = proc
29
+ end
30
+
31
+ def run
32
+ @proc.call
33
+ end
34
+ end
35
+
36
+ class Runnable
37
+ include Java::java::lang::Runnable
38
+ def initialize(explicit_block=nil, &block)
39
+ @block = explicit_block || block
40
+ end
41
+
42
+ def run
43
+ @block.call
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,22 @@
1
+ class InvalidHashKeyError < Exception; end
2
+
3
+ module ValidatedHash
4
+ # Raises an exception if a key in the hash does not exist in the list of valid keys
5
+ def validate_only(*keys)
6
+ self.keys.each {|key| raise InvalidHashKeyError.new("#{key} is not a valid key for this hash") unless keys.member?(key)}
7
+ end
8
+
9
+ # Raises an exception if any of the keys provided are not found in the hash
10
+ def validate_all(*keys)
11
+ keys.each {|key| raise InvalidHashKeyError.new("#{key} is required for this hash") unless self.keys.member?(key)}
12
+ end
13
+
14
+ # Raises an exception if any of the keys provided are found in the hash
15
+ def validate_none(*keys)
16
+ keys.each {|key| raise InvalidHashKeyError.new("#{key} is not allowed for this hash") if self.keys.member?(key)}
17
+ end
18
+ end
19
+
20
+ class Hash
21
+ include ValidatedHash
22
+ end
@@ -0,0 +1,609 @@
1
+ include_class javax.swing.JComponent
2
+ include_class javax.swing.KeyStroke
3
+
4
+ require 'monkeybars/exceptions'
5
+ require 'monkeybars/inflector'
6
+ require 'monkeybars/validated_hash'
7
+ require 'monkeybars/view_mapping'
8
+ require 'monkeybars/task_processor'
9
+ require 'monkeybars/view_nesting'
10
+ require 'monkeybars/view_positioning'
11
+ require "monkeybars/event_handler_registration_and_dispatch_mixin"
12
+
13
+ module Monkeybars
14
+ # The view is the gatekeeper to the actual Java (or sometimes non-Java) view class.
15
+ # The view defines how data moves into and out of the view via the model.
16
+ #
17
+ # Any property of the underlying "real" view can be accessed as if it were a
18
+ # property of the view class.
19
+ #
20
+ # Thus if you have a JFrame that has a member variable okButton, you could do
21
+ # the following:
22
+ #
23
+ # okButton.text = "Confirm"
24
+ #
25
+ # You must use the exact name that is used in the underlying view, no normal
26
+ # JRuby javaName to java_name conversion is performed. ok_button.text = "Confirm"
27
+ # would fail.
28
+ #
29
+ # Example, (assume a JFrame with a label, text area and button):
30
+ #
31
+ # require 'monkeybars'
32
+ #
33
+ # class MyView < Monkeybars::View
34
+ # set_java_class "com.project.MyCoolJFrame"
35
+ # map :view => "titleLabel.text", :model => :title_text
36
+ # map :view => "okButton.text", :model => :button_text
37
+ # map :view => "mainTextArea.text", :model => :text, :using => [:convert_to_string, :convert_to_array]
38
+ # map :view => "titleLabel.selected_text_color", :transfer => :text_color
39
+ # COLOR_TRANSLATION = {:red => Color.new(1.0, 0.0, 0.0), :green => Color.new(0.0, 1.0, 0.0), :blue => Color.new(0.0, 0.0, 1.0)}
40
+ # map :view => "titleLabel.selected_text_color", :model => :text, :translate_using => COLOR_TRANSLATION
41
+ #
42
+ # def convert_to_string(model)
43
+ # model.text.join("\n")
44
+ # end
45
+ #
46
+ # def convert_to_array(model)
47
+ # mainTextArea.text.split("\n")
48
+ # end
49
+ # end
50
+ #
51
+ # It is important that you do not implement your own initialize method, doing so will
52
+ # interfere with the operation of the View class (or if you must, remember to
53
+ # call super on the first line of your initialize).
54
+ #
55
+ # It is possible to have a view that is not related to a Java class, in which
56
+ # case no set_java_class delcaration is used. If using a pure Ruby view (or
57
+ # manually wrapping a Java class) you must set the @main_view_component
58
+ # member variable yourself. The object you assign to @main_view_component
59
+ # should respond to any methods that normally interact with the Java object such
60
+ # as visisble?, hide, and dispose
61
+ class View
62
+ include TaskProcessor
63
+ include EventHandlerRegistrationAndDispatchMixin
64
+ include Positioning
65
+
66
+ module CloseActions #:nodoc:
67
+ DO_NOTHING = javax::swing::WindowConstants::DO_NOTHING_ON_CLOSE
68
+ DISPOSE = javax::swing::WindowConstants::DISPOSE_ON_CLOSE
69
+ EXIT = javax::swing::WindowConstants::EXIT_ON_CLOSE
70
+ HIDE = javax::swing::WindowConstants::HIDE_ON_CLOSE
71
+ METHOD = :method
72
+ end
73
+
74
+ private
75
+ @@view_nestings_for_child_view ||= {}
76
+ def self.view_nestings
77
+ @@view_nestings_for_child_view[self] ||= {}
78
+ end
79
+
80
+ @@view_mappings_for_child_view ||= {}
81
+ def self.view_mappings
82
+ @@view_mappings_for_child_view[self] ||= []
83
+ end
84
+
85
+ @@java_class_for_child_view ||= {}
86
+ def self.instance_java_class
87
+ @@java_class_for_child_view[self]
88
+ end
89
+
90
+ def self.instance_java_class=(java_class)
91
+ @@java_class_for_child_view[self] = java_class
92
+ end
93
+
94
+ @@signal_mappings ||= {}
95
+ def self.signal_mappings
96
+ @@signal_mappings[self] ||= {}
97
+ end
98
+
99
+ public
100
+ # Declares what class to instantiate when creating the view. Any listeners
101
+ # set on the component are added to this class as well as the setting of the
102
+ # close action that is defined in the controller.
103
+ # Accepts a string that is the Java package, or a class for the Java or JRuby UI object
104
+ def self.set_java_class(java_class)
105
+ # We're allowing two options: The existing "Give me a string", and
106
+ # passing a constant (which is new behavior).
107
+ # In a view class, the develoepr can simply give the name of a defined class
108
+ # to use, in which case this code does not need to try to load anything.
109
+ if java_class.is_a?(String)
110
+ include_class java_class
111
+ class_name = /.*?\.?(\w+)$/.match(java_class)[1]
112
+ self.instance_java_class = const_get(class_name)
113
+ elsif java_class.is_a?(Class)
114
+ self.instance_java_class = java_class
115
+ else
116
+ raise "Setting the view class requires either a string naming the class to load, or an actual class constant. set_java_class was given #{java_class.inspect}."
117
+ end
118
+ end
119
+
120
+ # Declares a mapping between the properties of the view and either the model's
121
+ # or the transfer's properties. This mapping is used when creating the model.
122
+ # If you wish to trigger subsequent updates of the view, you may call
123
+ # update_view manually from the controller.
124
+ #
125
+ # There are several ways to declare a mapping based on what level of control
126
+ # you need over the process. The simplest form is:
127
+ #
128
+ # map :view => :foo, :model => :bar
129
+ #
130
+ # or
131
+ #
132
+ # map :view => :foo, :transfer => :bar
133
+ #
134
+ # Which means, when update is called, self.foo = model.bar and
135
+ # self.foo = transfer[:bar] respectively.
136
+ #
137
+ # Strings may be used interchangably with symbols for model mappings. If you
138
+ # have nested view or properties you may specify them as a string:
139
+ #
140
+ # map :view => "foo.sub_property", :model => "bar.other_sub_property"
141
+ #
142
+ # which means, self.foo.sub_property = model.bar.other_sub_property
143
+ #
144
+ # It should be noted that these mappings are bi-directional. They are
145
+ # referenced for both update and write_state. When used
146
+ # for write_state the assignment direction is reversed, so a view with
147
+ #
148
+ # map :view => :foo, :model => :bar
149
+ #
150
+ # would mean model.bar = self.foo
151
+ #
152
+ # When a direct assignment is not sufficient you may provide a method to
153
+ # filter or adapt the contents of the model's value before assignment to
154
+ # the view. This is accomplished by adding a :using key to the hash.
155
+ # The key's value is an array of the method names to be used when converting from
156
+ # the model to the view, and when converting from the view back to the model.
157
+ # If you only want to use a custom method for either the conversion from a model
158
+ # to a view or vice versa, you can specify :default, for the other parameter
159
+ # and the normal mapping will take place. If you want to disable the copying
160
+ # of data in one direction you can pass nil as the method parameter.
161
+ #
162
+ # map :view => :foo, :model => :bar, :using => [:from_model, :to_model]
163
+ #
164
+ # would mean self.foo = from_model() when called by update and
165
+ # model.bar = to_model() when called by write_state.
166
+ #
167
+ # map :view => :foo, :model => :bar, :using => [:from_model, :default]
168
+ #
169
+ # would mean self.foo = from_model() when called by update and
170
+ # model.bar = self.foo when called by write_state.
171
+ #
172
+ # map :view => :foo, :model => :bar, :using => [:from_model, nil]
173
+ #
174
+ # would mean self.foo = from_model() when called by update and
175
+ # would do nothing when called by write_state.
176
+ #
177
+ # For constant value translation, :translate_using provides a one-line approach.
178
+ # :translate_using takes a hash with the model values as the key, and the view values as the value.
179
+ # This only works when the view state and the model state has a one-to-one translation.
180
+ #
181
+ # COLOR_TRANSLATION = {:red => Color.new(1.0, 0.0, 0.0), :green => Color.new(0.0, 1.0, 0.0), :blue => Color.new(0.0, 0.0, 1.0)}
182
+ # map :view => "titleLabel.selected_text_color", :model => :text, :translate_using => COLOR_TRANSLATION
183
+ #
184
+ # If you want to invoke disable_handlers during the call to update
185
+ # you can add the :ignoring key. The key's value is either a single type or
186
+ # an array of types to be ignored during the update process.
187
+ #
188
+ # map :view => :foo, :model => :bar, :ignoring => :item
189
+ #
190
+ # This will wrap up the update in a call to disable_handlers on the view
191
+ # component, which is assumed to be the first part of the mapping UP TO THE
192
+ # FIRST PERIOD. This means that
193
+ #
194
+ # map :view => "foo.bar", :model => :model_property, :ignoring => :item
195
+ #
196
+ # would translate to
197
+ #
198
+ # foo.disable_handlers(:item) do
199
+ # foo.bar = model.model_property
200
+ # end
201
+ #
202
+ # during a call to update. During write_to_model, the ignoring
203
+ # definition has no meaning as there are no event handlers on models.
204
+ #
205
+ # The final option for mapping properties is a simply your own method. As with
206
+ # a method provided via the using method you may provide a method for conversion
207
+ # into the view, out of the view or both.
208
+ #
209
+ # raw_mapping :from_model, :to_model
210
+ #
211
+ # would simply invoke the associated method when update or
212
+ # write_state was called. Thus any assignment to view properties
213
+ # must be done within the method (hence the 'raw').
214
+ #
215
+ # To disable handlers in a raw mapping, call Component#disable_handlers
216
+ # inside your mapping method
217
+ #
218
+ def self.map(properties)
219
+ mapping = Mapping.new(properties)
220
+ view_mappings << mapping
221
+ end
222
+
223
+ # See View.map
224
+ def self.raw_mapping(to_view_method, from_view_method, handlers_to_ignore = [])
225
+ view_mappings << Mapping.new(:using => [to_view_method, from_view_method], :ignoring => handlers_to_ignore)
226
+ end
227
+
228
+ # Declares a mapping between a signal and a method to process the signal. When
229
+ # the signal is received, the method is called with the model and the transfer as parameters.
230
+ # If a signal is sent that is not defined, an UnknownSignalError exception is raised.
231
+ #
232
+ # define_signal :name => :error_state, :handler => :disable_go_button
233
+ #
234
+ # def disable_go_button(model, transfer)
235
+ # go_button.enabled = false
236
+ # end
237
+ #
238
+ def self.define_signal(options, method_name = nil)
239
+ if options.kind_of? Hash
240
+ begin
241
+ options.validate_only :name, :handler
242
+ options.validate_all :name, :handler
243
+ rescue InvalidHashKeyError
244
+ raise InvalidSignalError, ":signal and :handler must be provided for define_signal. Options provided: #{options.inspect}"
245
+ end
246
+ signal_mappings[options[:name]] = options[:handler]
247
+ else
248
+ #support two styles for now, deprecating the old (signal, method_name) style
249
+ warn "The usage of define_signal(signal, method_name) has been deprecated, please use define_signal :name => :signal, :handler => :method_name"
250
+ signal_mappings[options] = method_name
251
+ end
252
+
253
+ end
254
+
255
+ # Declares how nested views from their respective nested controllers are to be
256
+ # added and removed from the view. Multiple nestings for the different nested
257
+ # controllers are possible through the :sub_view value, which is basically a grouping
258
+ # name. Two kinds of mapping are possible: A property nesting, and a method nesting.
259
+ # Property nesting:
260
+ #
261
+ # nest :sub_view => :user_list, :view => :user_panel
262
+ #
263
+ # This essentially calls user_panel.add nested_view_component on Monkeybars::Controller#add_nested_controller
264
+ # and user_panel.remove nested_view_componenent on Monkeybars::Controller#remove_nested_controller.
265
+ # A layout on the container being used is preferred, as no orientational information will be conveyed
266
+ # to either component.
267
+ #
268
+ # Method nesting:
269
+ #
270
+ # nest :sub_view => :user_list, :using => [:add_user, :remove_user]
271
+ #
272
+ # def add_user(nested_view, nested_component, model, transfer)
273
+ # user_panel.add nested_component
274
+ # nested_component.set_location(nested_component.x, nested_component.height * transfer[:user_list_size])
275
+ # end
276
+ #
277
+ # def remove_user(nested_view, nested_component, model, transfer)
278
+ # # nested_view is the Ruby view object
279
+ # # nested_component is the Java form, aka @main_view_component
280
+ #
281
+ # user_panel.remove nested_component
282
+ # # lots of code to re-order previous components
283
+ # end
284
+ #
285
+ # Method nesting calls the methods in :using (in the same way that :using works for mapping) during
286
+ # add and remove (Monkeybars::Controller#add_nested_controller and Monkeybars::Controller#remove_nested_controller).
287
+ # Both methods are passed in the view, the view's main view component, the mode, and the transfer, respectively.
288
+ #
289
+ # New users using Netbeans will need to know that the GroupLayout (aka FreeDesign) is a picky layout that demands
290
+ # constraints while adding components. By default, all containers use GroupLayout in Netbeans's GUI builder. If you're
291
+ # not sure what all this means, just start off with a BoxLayout (which is not picky). If your layout needs constraints,
292
+ # you will need to pass them in with your Method Nesting. Some layouts even need @main_view_component#revalidate to be
293
+ # called. In short, be aware of Swing's quirks.
294
+ def self.nest(properties)
295
+ view_nestings[properties[:sub_view]] ||= []
296
+ view_nestings[properties[:sub_view]] << Nesting.new(properties)
297
+ end
298
+
299
+ def initialize
300
+ @__field_references = {}
301
+ # We have at three possibilities:
302
+ # - The UI form is a Java class all the way; that it, it came from Java code compiled into a .class file.
303
+ # - The UI form was written in Ruby, but inherits from a Java class (e.g. JFrame). It is quite servicable
304
+ # for the UI, but will behave differently in regards to Java reflection -- We'll refer to this as a JRuby class
305
+ # - The UI form is not Java at all.
306
+ @main_view_component = create_main_view_component
307
+ raise MissingMainViewComponentError, "Missing @main_view_component. Use View.set_java_class or override create_main_view_component." if @main_view_component.nil?
308
+ @is_java_class = !@main_view_component.class.respond_to?(:java_proxy_class)
309
+ setup_implicit_and_explicit_event_handlers
310
+ load
311
+ end
312
+
313
+ # This is set via the constructor, do not call directly unless you know what
314
+ # you are doing.
315
+ #
316
+ # Defines the close action for a visible window. This method is only applicable
317
+ # for JFrame or decendants, it is ignored for all other classes.
318
+ #
319
+ # close_action expects an action from Monkeybars::View::CloseActions
320
+ #
321
+ # The CloseActions::METHOD action expects a second parameter which should be a
322
+ # MonkeybarsWindowAdapter.
323
+ def close_action(action, handler = nil)
324
+ if @main_view_component.kind_of?(javax.swing.JFrame) || @main_view_component.kind_of?(javax.swing.JInternalFrame) || @main_view_component.kind_of?(javax.swing.JDialog)
325
+ if CloseActions::METHOD == action
326
+ @main_view_component.default_close_operation = CloseActions::DO_NOTHING
327
+ unless @main_view_component.kind_of?(javax.swing.JInternalFrame)
328
+ @main_view_component.add_window_listener(handler)
329
+ else
330
+ @main_view_component.add_internal_frame_listener(handler)
331
+ end
332
+ else
333
+ @main_view_component.set_default_close_operation(action)
334
+ end
335
+ end
336
+ end
337
+
338
+ # This method is called when Controller#load has completed (usually during Controller#open)
339
+ # but before the view is shown. This method is meant to be overriden in views that
340
+ # need control over how their mappings are initially run. By overriding this method
341
+ # you could use disable_handlers to disable certain handlers during the
342
+ # initial mapping process or perform some actions after the mappings complete.
343
+ #
344
+ # When overriding on_first_update, you must at some point make a call to
345
+ # super or the View#update method in order for your view's mappings to be invoked.
346
+ def on_first_update(model, transfer)
347
+ update(model, transfer)
348
+ end
349
+
350
+ def visible?
351
+ return @main_view_component.visible
352
+ end
353
+
354
+ def visible=(visibility)
355
+ @main_view_component.visible = visibility
356
+ end
357
+
358
+ def show
359
+ @main_view_component.visible = true
360
+ end
361
+
362
+ def hide
363
+ @main_view_component.visible = false
364
+ end
365
+
366
+ # This is set via the controller, do not call directly unless you know what
367
+ # you are doing.
368
+ #
369
+ # Looks up the appropriate component and calls addXXXListener on the
370
+ # component. Components can be nested, so textField.document would be a valid
371
+ # component and the listner would be added to the document object of the text
372
+ # field.
373
+ #
374
+ # add_handler returns a hash of objects as keys and their normalized (underscored
375
+ # and . replaced with _ ) names as values
376
+ def add_handler(handler, component)
377
+ component = component.to_s
378
+ if "global" == component
379
+ raise "Global handler declarations are not yet supported"
380
+ elsif "java_window" == component
381
+ begin
382
+ @main_view_component.send("add#{handler.type.camelize}Listener", handler)
383
+ rescue NameError
384
+ raise InvalidHandlerError, "There is no listener of type #{handler.type} on #{@main_view_component}"
385
+ end
386
+ else
387
+ begin
388
+ object = instance_eval(component, __FILE__, __LINE__)
389
+ rescue NameError
390
+ raise UndefinedComponentError, "Cannot add #{handler.type} handler to #{component} on #{self}, the component could not be found"
391
+ end
392
+
393
+ begin
394
+ object.send("add#{handler.type.camelize}Listener", handler)
395
+ rescue NameError
396
+ raise InvalidHandlerError, "There is no listener of type #{handler.type} on #{component}"
397
+ end
398
+ end
399
+ end
400
+
401
+ # Attempts to find a member variable in the underlying @main_view_component
402
+ # object if one is set, otherwise falls back to default method_missing implementation.
403
+ #
404
+ # Also, detect if the user is trying to set a new value for a given instance
405
+ # variable in the form. If so, the field will be updated to refer to the provided
406
+ # value. The passed in argument MUST BE A JAVA OBJECT or this call will fail.
407
+ def method_missing(method, *args, &block)
408
+ if match = /(.*)=$/.match(method.to_s)
409
+ if @is_java_class
410
+ field = get_field(match[1])
411
+ field.set_value(Java.ruby_to_java(@main_view_component), Java.ruby_to_java(args[0]))
412
+ else
413
+ set_jruby_field(match[1], args[0])
414
+ end
415
+ else
416
+ begin
417
+ return get_field_value(method)
418
+ rescue NameError
419
+ super
420
+ end
421
+ end
422
+ end
423
+
424
+ def set_jruby_field(getter, value)
425
+ @main_view_component.send("#{getter}=", value)
426
+ end
427
+
428
+ def add_nested_view(nested_name, nested_view, nested_component, model, transfer) #:nodoc:
429
+ self.class.view_nestings[nested_name].select{|nesting| nesting.nests_with_add?}.each {|nesting| nesting.add(self, nested_view, nested_component, model, transfer)}
430
+ end
431
+
432
+ def remove_nested_view(nested_name, nested_view, nested_component, model, transfer) #:nodoc:
433
+ self.class.view_nestings[nested_name].select{|nesting|
434
+ nesting.nests_with_remove?
435
+ }.each {|nesting|
436
+ nesting.remove(self, nested_view, nested_component, model, transfer)
437
+ }
438
+ end
439
+
440
+ def update(model, transfer)
441
+ self.class.view_mappings.select{|mapping| mapping.maps_to_view?}.each {|mapping| mapping.to_view(self, model, transfer)}
442
+ transfer.clear
443
+ end
444
+
445
+ # The inverse of update. Called when view_state is called in the controller.
446
+ def write_state(model, transfer)
447
+ transfer.clear
448
+ self.class.view_mappings.select{|mapping| mapping.maps_from_view?}.each {|mapping| mapping.from_view(self, model, transfer)}
449
+ end
450
+
451
+ def process_signal(signal, model, transfer, &block)
452
+ handler = self.class.signal_mappings[signal]
453
+ if handler.nil?
454
+ raise UndefinedSignalError, "There is no signal '#{signal}' defined"
455
+ else
456
+ raise InvalidSignalHandlerError, "There is no handler method '#{handler}' on view #{self.class}" unless respond_to?(handler)
457
+ self.send(handler, model, transfer, &block) unless handler.nil?
458
+ end
459
+ end
460
+
461
+ # Stub to be overriden in sub-class. This is where you put the code you would
462
+ # normally put in initialize. Load will be called whenever a new class is instantiated
463
+ # which happens when the Controller's instance method is called on a non-instantiated
464
+ # controller. Thus this method will always be called before the Controller's
465
+ # load method (which is called during Controlller#open).
466
+ def load; end
467
+
468
+ # Stub to be overriden in sub-class. This is called whenever the view is closed.
469
+ def unload; end
470
+
471
+ # Uses get_field to retrieve the value of a particular field, this is typically
472
+ # a component on a Java form. Used internally by method missing to enable:
473
+ #
474
+ # some_component.method
475
+ def get_field_value(field_name)
476
+ if "java_window" == field_name.to_s
477
+ @main_view_component
478
+ else
479
+ field_name = field_name.to_sym
480
+ if @is_java_class
481
+ field_object = get_field(field_name)
482
+ Java.java_to_ruby(field_object.value(Java.ruby_to_java(@main_view_component)))
483
+ else
484
+ get_field(field_name).call
485
+ end
486
+ end
487
+ end
488
+
489
+ # Uses reflection to pull a private field out of the Java objects. In cases where
490
+ # no Java object is being used, the view object itself is referenced. A field
491
+ # is not the same as the object it refers to, you only need this method if you
492
+ # want to change what a view field references using the set_value method.
493
+ #
494
+ # field = get_field("my_button")
495
+ # field.set_value(Java.ruby_to_java(@main_view_component), Java.ruby_to_java(my_new_button))
496
+ def get_field(field_name)
497
+ field_name = field_name.to_sym
498
+ field = @__field_references[field_name]
499
+
500
+ if field.nil?
501
+ if @is_java_class
502
+ [field_name.to_s, field_name.camelize, field_name.camelize(false), field_name.underscore].uniq.each do |name|
503
+ begin
504
+ field = self.class.instance_java_class.java_class.declared_field(name)
505
+ rescue NameError, NoMethodError
506
+ end
507
+ break unless field.nil?
508
+ end
509
+ raise UndefinedComponentError, "There is no component named #{field_name} on view #{@main_view_component.class}" if field.nil?
510
+
511
+ field.accessible = true
512
+ else
513
+ begin
514
+ field = @main_view_component.method(field_name)
515
+ rescue NameError, NoMethodError
516
+ raise UndefinedComponentError, "There is no component named #{field_name} on view #{@main_view_component.class}"
517
+ end
518
+ end
519
+ @__field_references[field_name] = field
520
+ end
521
+ field
522
+ end
523
+
524
+ def dispose
525
+ @main_view_component.dispose if @main_view_component.respond_to? :dispose
526
+ end
527
+
528
+ def get_field_names
529
+ fields = []
530
+ if @is_java_class
531
+ klass = self.class.instance_java_class.java_class
532
+ while(klass.name !~ /^java[x]?\./)
533
+ fields << klass.declared_fields
534
+ klass = klass.superclass
535
+ end
536
+ fields.flatten.map! {|field| field.name }
537
+ else
538
+ @main_view_component.send(:instance_variables).map! {|name| name.sub('@', '')}
539
+ end
540
+ end
541
+
542
+ private
543
+ # Creates and returns the main view component to be assigned to @main_view_component.
544
+ # Override this when a non-default constructor is needed.
545
+ def create_main_view_component
546
+ self.class.instance_java_class.new if self.class.instance_java_class.respond_to?(:new)
547
+ end
548
+
549
+ # Retrieves all the components on the main view. This will work even if
550
+ # @main_view_component is not a Java object as long as it implements
551
+ # a components method.
552
+ def get_all_components(list = [], components = @main_view_component.components)
553
+ components.each do |component|
554
+ list << component
555
+ get_all_components(list, component.components) if component.respond_to? :components
556
+ end
557
+ list
558
+ end
559
+ end
560
+
561
+ end
562
+
563
+ class InvalidSignalError < Exception; end
564
+ class MissingMainViewComponentError< Exception; end
565
+
566
+ module HandlerContainer
567
+ # Removes the handlers associated with a component for the duration of the block.
568
+ # All handlers are re-added to the component afterwards.
569
+ #
570
+ # some_text_field.disable_handlers(:action, :key) do
571
+ # # some code that we don't want to trigger action listener methods or key listener methods for
572
+ # end
573
+ #
574
+ def disable_handlers(*types)
575
+ types.map! { |t| t.camelize }
576
+ listeners = {}
577
+ types.each do |type|
578
+ listener_class = if Monkeybars::Handlers::AWT_TYPES.member?(type)
579
+ instance_eval("java.awt.event.#{type}Listener", __FILE__, __LINE__)
580
+ elsif Monkeybars::Handlers::SWING_TYPES.member?(type)
581
+ instance_eval("javax.swing.event.#{type}Listener", __FILE__, __LINE__)
582
+ elsif Monkeybars::Handlers::BEAN_TYPES.member?(type)
583
+ instance_eval("java.beans.#{type}Listener", __FILE__, __LINE__)
584
+ end
585
+ listeners[type] = self.get_listeners(listener_class.java_class)
586
+ listeners[type].each do |listener|
587
+ self.send("remove#{type}Listener", listener)
588
+ end
589
+ end
590
+ yield
591
+ types.each do |type|
592
+ listeners[type].each do |listener|
593
+ self.send("add#{type}Listener", listener)
594
+ end
595
+ end
596
+ end
597
+ end
598
+
599
+ PlainDocument = javax.swing.text.PlainDocument
600
+ class PlainDocument
601
+ include HandlerContainer
602
+ end
603
+
604
+ Component = java.awt.Component
605
+ # The java.awt.Component class is opened and a new method is added to allow
606
+ # you to ignore certain events during a call to update_view.
607
+ class Component
608
+ include HandlerContainer
609
+ end