Neurogami-jimpanzee 1.0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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