github
Advanced Search
  • Home
  • Pricing and Signup
  • Explore GitHub
  • Blog
  • Login

keithrbennett / multilanguage-swing

  • Admin
  • Watch Unwatch
  • Fork
  • Your Fork
  • Pull Request
  • Download Source
    • 1
    • 0
  • Source
  • Commits
  • Network (0)
  • Issues (0)
  • Downloads (0)
  • Wiki (1)
  • Graphs
  • Tree: 5744fde

click here to add a description

click here to add a homepage

  • Branches (2)
    • gh-pages
    • master
  • Tags (0)
Sending Request…
Enable Donations

Pledgie Donations

Once activated, we'll place the following badge in your repository's detail box:
Pledgie_example
This service is courtesy of Pledgie.

A simple Swing app implemented in Java, JRuby, ...? — Read more

  cancel

  cancel
  • Private
  • Read-Only
  • HTTP Read-Only

This URL has Read+Write access

Moved SimpleDocumentListener and SwingAction classes into main file. 
keithrbennett (author)
Thu Mar 12 06:44:02 -0700 2009
commit  5744fde82696f1ed1ede8396301a225868f8e2bd
tree    c045d9a9e54b93e2c00ebdc38d4c94817e811634
parent  5b127275fe96e01542a1c6aab4e2fc187f00a539
multilanguage-swing / src / FrameInRuby.rb src/FrameInRuby.rb
100644 443 lines (331 sloc) 13.586 kb
edit raw blame history
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
# FrameInRuby
# Copyright, Keith Bennett, 2009
 
 
require 'java'
 
# In Java, classes in the java.lang package do not need to be imported.
# In JRuby, they do, so that JRuby can distinguish it from a Ruby constant.
import java.lang.Double
import java.lang.NumberFormatException
 
# In the Java version of the program, it was necessary to import Dimension
# because it was needed to define the type of a variable. In Ruby, it is
# not necessary to specify the type of a variable's value, so we never
# use the term 'Dimension', and there is no need to import it.
 
import java.awt.BorderLayout
import java.awt.Event
import java.awt.GridLayout
import java.awt.Toolkit
 
import java.awt.event.KeyEvent
 
import javax.swing.AbstractAction
import javax.swing.Action
import javax.swing.BorderFactory
import javax.swing.JButton
import javax.swing.JFrame
import javax.swing.JLabel
import javax.swing.JMenu
import javax.swing.JMenuBar
import javax.swing.JPanel
import javax.swing.JTextField
import javax.swing.KeyStroke
 
 
# In Java, all classes specified in the same package as the file being compiled
# will be found by the compiler without explicitly importing them. In contrast,
# in Ruby, we need to "require" files, even though they are in the same
# directory as the file being interpreted.
 
# =======================================================================
# TemperatureConversion module
# =======================================================================
 
module TemperatureConversion
 
  # Converts a temperature from Fahrenheit to Celsius.
  def f2c(f)
    ((f - 32) * 5.0 / 9.0)
  end
 
 
  # Converts a temperature from Celsius to Fahrenheit.
  def c2f(c)
    (c * 9.0 / 5.0) + 32
  end
end
 
 
# =======================================================================
# FrameInRuby class
# =======================================================================
 
# Main application frame containing menus, text fields for the temperatures,
# and buttons. The menu items and buttons are driven by shared actions,
# which are disabled and enabled based on the state of the text fields.
#
# Temperatures can be converted in either direction, Fahrenheit to Celsius
# or Celsius to Fahrenheit. The convert actions (F2C, C2F) are each enabled
# when the respective source text field (F for F2C, C for C2F) contains text
# that can successfully be parsed to a Double.
#
# The Clear action is enabled when there is any text in either of the text fields.
 
class FrameInRuby < JFrame
 
  include TemperatureConversion
 
  attr_accessor :fahr_text_field, :cels_text_field
 
  # These actions will be shared by menu items and buttons.
  attr_accessor :f2c_action, :c2f_action, :clear_action, :exit_action
 
 
  # Sets up frame with all components, centers on screen,
  # and sets it up to exit the program when closed.
  def initialize
    super "Fahrenheit <--> Celsius Converter"
 
    # In Ruby, the double colon is used to refer to static data members
    # (class variables as opposed to instance variables):
    getContentPane.add create_converters_panel, BorderLayout::CENTER
    setup_actions
    getContentPane.add create_buttons_panel, BorderLayout::SOUTH
    setJMenuBar create_menu_bar
    getContentPane.setBorder(BorderFactory.createEmptyBorder(12, 12, 12, 12))
    setDefaultCloseOperation(JFrame::EXIT_ON_CLOSE)
    pack
    centerOnScreen
  end
 
 
 
  # Creates the Fahrenheit and Celsius text fields.
  def create_text_fields
 
    # Note that in JRuby we can use lambdas as lightweight nested functions
    # to increase DRYness:
    create_field = lambda do
      f = JTextField.new(15);
      f.setToolTipText "Input a temperature."
      f
    end
 
    self.fahr_text_field = create_field.call
    self.cels_text_field = create_field.call
 
    setup_text_field_listeners
  end
 
 
 
  # Creates the panel containing the Fahrenheit and Celsius labels
  # and text fields.
  def create_converters_panel
 
    # Another lambda as lightweight nested function:
    create_an_inner_panel = lambda { JPanel.new(GridLayout.new(0, 1, 5, 5))}
 
    labelPanel = create_an_inner_panel.call
    labelPanel.add(JLabel.new("Fahrenheit: "))
    labelPanel.add(JLabel.new("Celsius: "))
 
    create_text_fields
 
    textFieldPanel = create_an_inner_panel.call
    textFieldPanel.add(fahr_text_field)
    textFieldPanel.add(cels_text_field)
 
    panel = JPanel.new(BorderLayout.new())
    panel.add(labelPanel, BorderLayout::WEST)
    panel.add(textFieldPanel, BorderLayout::CENTER)
    panel
  end
 
 
 
  # Creates the menu bar with File, Edit, and Convert menus.
  def create_menu_bar
 
    menubar = JMenuBar.new
 
    file_menu = JMenu.new "File"
    file_menu.add exit_action
    menubar.add file_menu
 
    edit_menu = JMenu.new "Edit"
    edit_menu.add clear_action
    menubar.add edit_menu
 
    convert_menu = JMenu.new "Convert"
    convert_menu.add f2c_action
    convert_menu.add c2f_action
    menubar.add convert_menu
 
    menubar
  end
 
 
  # Sets up the listeners that will determine the enabled states of the
  # temperature conversion (f2c and c2f) and clear actions.
  def setup_text_field_listeners
 
    # In Java, a separate class is required for each type
    # of action. In contrast, Ruby supports code blocks, lambdas, and procs.
    # This allows us to use instances of the same SimpleDocumentListener
    # class simply creating them with different lambdas.
 
    clear_enabler = lambda {
      ctext = cels_text_field.getText
      ftext = fahr_text_field.getText
      should_enable =
          (ctext && ctext.length > 0) ||
          (ftext && ftext.length > 0)
      clear_action.setEnabled should_enable
    }
 
    fahr_text_field.getDocument.addDocumentListener(
        SimpleDocumentListener.new do
          f2c_action.setEnabled double_string_valid?(fahr_text_field.getText)
        end)
    cels_text_field.getDocument.addDocumentListener(
        SimpleDocumentListener.new do
          c2f_action.setEnabled double_string_valid?(cels_text_field.getText)
        end)
 
    clear_document_listener = SimpleDocumentListener.new &clear_enabler
    fahr_text_field.getDocument.addDocumentListener clear_document_listener
    cels_text_field.getDocument.addDocumentListener clear_document_listener
 
  end
 
 
 
  # Sets up the temperature conversion, clear, and exit actions, including
  # name, behavior, tooltip, and accelerator key.
  def setup_actions
    self.f2c_action = SwingAction.new("Fahr --> Cels",
        Action::SHORT_DESCRIPTION => "Convert from Fahrenheit to Celsius",
        Action::ACCELERATOR_KEY =>
            KeyStroke.getKeyStroke(KeyEvent::VK_S, Event::CTRL_MASK),
        &f2c_behavior)
 
    f2c_action.setEnabled false
 
    self.c2f_action = SwingAction.new("Cels --> Fahr",
        Action::SHORT_DESCRIPTION => "Convert from Celsius to Fahrenheit",
        Action::ACCELERATOR_KEY =>
            KeyStroke.getKeyStroke(KeyEvent::VK_T, Event::CTRL_MASK),
        & c2f_behavior)
 
    c2f_action.setEnabled false
 
    self.exit_action = SwingAction.new("Exit",
        Action::SHORT_DESCRIPTION => "Exit this program",
        Action::ACCELERATOR_KEY =>
            KeyStroke.getKeyStroke(KeyEvent::VK_X, Event::CTRL_MASK))\
        do |event|
          java.lang.System::exit 0
        end
 
    self.clear_action = SwingAction.new("Clear",
        Action::SHORT_DESCRIPTION => "Reset to empty the temperature fields",
        Action::ACCELERATOR_KEY =>
            KeyStroke.getKeyStroke(KeyEvent::VK_L, Event::CTRL_MASK),
        &clear_behavior)
    clear_action.setEnabled false
 
 
  end
 
 
  # Creates the button panel laid out such that the buttons will always
  # stay at the right side of the window.
  def create_buttons_panel
 
    innerPanel = JPanel.new(GridLayout.new(1, 0, 5, 5))
    innerPanel.add(JButton.new f2c_action)
    innerPanel.add(JButton.new c2f_action)
    innerPanel.add(JButton.new clear_action)
    innerPanel.add(JButton.new exit_action)
 
    outerPanel = JPanel.new(BorderLayout.new())
    outerPanel.add innerPanel, BorderLayout::EAST
    outerPanel.setBorder(BorderFactory.createEmptyBorder(10, 0, 0, 0))
    outerPanel
 
  end
 
 
  # Defines and returns the behavior for the Fahrenheit to Celsius conversion.
  def f2c_behavior
    lambda do |event|
      text = fahr_text_field.getText
 
      if text != nil and text.length > 0
        fahr = Double::parseDouble(text)
        cels = f2c fahr
        cels_text = Double::toString(cels)
        cels_text_field.setText cels_text
      end
    end
  end
 
 
 
  # Defines and returns the behavior for the Celsius to Fahrenheit conversion.
  def c2f_behavior
    lambda do |event|
      text = cels_text_field.getText
 
      if text != nil and text.length > 0
        cels = Double::parseDouble(text)
        fahr = c2f cels
 
        fahr_text = Double::toString(fahr)
        fahr_text_field.setText fahr_text
      end
    end
  end
 
 
 
  # Defines and returns the behavior for the clear action.
  def clear_behavior
    lambda do |event|
      fahr_text_field.setText ''
      cels_text_field.setText ''
    end
  end
 
 
 
  # A nice touch in Ruby is the ability to name functions with names
  # that end in "?" to indicate that they return a boolean value.
  def double_string_valid?(str)
 
    begin
      Double::parseDouble(str) # convert but discard converted value
      is_valid = true
    rescue NumberFormatException
      is_valid = false
    end
 
    is_valid
  end
 
 
  # Centers the window on the screen based on the graphical information
  # reported by the java.awt.Toolkit. Note that in some cases, such as
  # use of multiple nonmirrored displays, the position may be odd, since
  # the toolkit may report the sum of all display space across all available
  # displays.
  def centerOnScreen
    screenSize = Toolkit.getDefaultToolkit().getScreenSize()
    componentSize = getSize()
    new_x = (screenSize.getWidth() - componentSize.getWidth()) / 2
    new_y = (screenSize.getHeight() - componentSize.getHeight()) / 2
    setLocation(new_x, new_y)
  end
 
end
 
 
# =======================================================================
# SimpleDocumentListener class
# =======================================================================
 
# Simple implementation of javax.swing.event.DocumentListener that
# enables specifying a single code block that will be called
# when any of the three DocumentListener methods are called.
#
# Note that unlike Java, where it is necessary to subclass the abstract
# Java class SimpleDocumentListener, we can merely create an instance of
# the Ruby class SimpleDocumentListener with the code block we want
# executed when a DocumentEvent occurs. This code can be in the form of
# a code block, lambda, or proc.
 
import javax.swing.event.DocumentListener
 
 
class SimpleDocumentListener
 
  # This is how we declare that this class implements the Java
  # DocumentListener interface in JRuby:
  include DocumentListener
 
  attr_accessor :behavior
 
  def initialize(&behavior)
    self.behavior = behavior
  end
 
 
  def changedUpdate(event); behavior.call event; end
  def insertUpdate(event); behavior.call event; end
  def removeUpdate(event); behavior.call event; end
 
end
 
 
# =======================================================================
# SwingAction class
# =======================================================================
 
# When running FrameInRuby, this will generate a warning because
# it is already imported in FrameInRuby. In JRuby, unfortunately,
# imports of Java classes are not confined to the file
# in which they are specified; once you import a class,
# it will be imported for other classes as well.
# This may be fixed in a future version of JRuby.
import javax.swing.AbstractAction
 
 
# This class enables the specification of a Swing action
# in a format natural to Ruby.
#
# It takes and stores a code block, lambda, or proc as the
# action's behavior, so there is no need to define a new class
# for each behavior. Also, it allows the optional specification
# of the action's properties via the passing of hash entries,
# which are effectively named parameters.
class SwingAction < AbstractAction
 
  attr_accessor :behavior
 
 
# Creates the action object with a behavior, name, and options:
#
# behavior - a behavior can be a code block, lambda,
# or a Proc.
#
# name - this is the name that will be used for the menu option,
# button caption, etc. Note that if an app is internationalized,
# the name will vary by locale, so it is better to identify an action
# by the action instance itself rather than its name.
#
# options - these are hash entries that will be passed to
# AbstractAction.putValue(). Keys should be constants from the
# javax.swing.Action interface, such as Action.SHORT_DESCRIPTION.
# Ruby allows hash entries to passed as the last parameters to a
# function, and they can be accessed inside the method as a single
# hash object.
#
# Example:
#
# self.exit_action = SwingAction.new(
# "Exit",
# Action::SHORT_DESCRIPTION => "Exit this program",
# Action::ACCELERATOR_KEY =>
# KeyStroke.getKeyStroke(KeyEvent::VK_X, Event::CTRL_MASK)) do
# System.exit 0
# end
#
  def initialize(name, options=nil, &behavior)
    super name
    options.each { |key, value| putValue key, value } if options
    self.behavior = behavior
  end
 
  def actionPerformed(action_event)
    behavior.call action_event
  end
end
 
 
# =======================================================================
# Program entry point:
# =======================================================================
 
FrameInRuby.new.setVisible true
 
Blog | Support | Training | Contact | API | Status | Twitter | Help | Security
© 2010 GitHub Inc. All rights reserved. | Terms of Service | Privacy Policy
Powered by the Dedicated Servers and
Cloud Computing of Rackspace Hosting®
Dedicated Server