I am trying to translate a Java class into its JRuby counter part, but I don't know how to translate a Java internal class.
This problem is well known and often comes from visibility/scope difference between Java and Ruby. This can be solved sometimes using blocs instead of inner class.
The Java example is the following, that can be found on Zetcode site. I also give my Ruby attempt below.
The problem is located in the internal ScheduleTask class, that extends TimerClass. While running (in Java), the run() method of ScheduleTask allows to call the repaint() method of the Board object to which it belongs. This is very handy in Java : at regular time interval, the Board panel is repaint. How to translate this in JRuby ? I simply tried to translate this literally, but then the repaint method is not known by the Ruby internal ScheduleTask...
package com.zetcode;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Toolkit;
import java.util.Timer;
import java.util.TimerTask;
import javax.swing.ImageIcon;
import javax.swing.JPanel;
public class Board extends JPanel {
private final int B_WIDTH = 350;
private final int B_HEIGHT = 350;
private final int INITIAL_X = -40;
private final int INITIAL_Y = -40;
private final int INITIAL_DELAY = 100;
private final int PERIOD_INTERVAL = 25;
private Image star;
private Timer timer;
private int x, y;
public Board() {
initBoard();
}
private void loadImage() {
ImageIcon ii = new ImageIcon("star.png");
star = ii.getImage();
}
private void initBoard() {
setBackground(Color.BLACK);
setPreferredSize(new Dimension(B_WIDTH, B_HEIGHT));
setDoubleBuffered(true);
loadImage();
x = INITIAL_X;
y = INITIAL_Y;
timer = new Timer();
timer.scheduleAtFixedRate(new ScheduleTask(),
INITIAL_DELAY, PERIOD_INTERVAL);
}
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
drawStar(g);
}
private void drawStar(Graphics g) {
g.drawImage(star, x, y, this);
Toolkit.getDefaultToolkit().sync();
}
private class ScheduleTask extends TimerTask {
@Override
public void run() {
x += 1;
y += 1;
if (y > B_HEIGHT) {
y = INITIAL_Y;
x = INITIAL_X;
}
repaint();
}
}
Here is the JRuby attempt, where the ScheduleTask class has been extracted from Board...
include Java
import java.awt.Color
import java.awt.Dimension
import java.awt.Graphics
import java.awt.Image
import java.awt.Toolkit
import java.util.Timer
import java.util.TimerTask
import javax.swing.ImageIcon
import javax.swing.JPanel
B_WIDTH = 650
B_HEIGHT = 450
INITIAL_X = -40
INITIAL_Y = -40
INITIAL_DELAY = 100
PERIOD_INTERVAL = 25
class Board < JPanel
def initialize
super
initBoard()
end
def loadImage
ii = ImageIcon.new("mario2.png")
@star = ii.getImage()
end
def initBoard
setBackground(Color.new(255, 255, 255))
setPreferredSize(Dimension.new(B_WIDTH, B_HEIGHT))
setDoubleBuffered(true)
loadImage()
@x = INITIAL_X
@y = INITIAL_Y
@timer = Timer.new()
task=ScheduleTask.new
task.init(@x,@y)
@timer.scheduleAtFixedRate(task, INITIAL_DELAY, PERIOD_INTERVAL)
end
def paintComponent(g)
repaint() # WAS : super.paintComponent(g)
drawStar(g)
end
def drawStar(g)
g.drawImage(@star, @x, @y, self)
Toolkit.getDefaultToolkit().sync()
end
class ScheduleTask < TimerTask
def init x,y
@x,@y=x,y
end
def run
@x += 1
@y += 1
if (@y > B_HEIGHT)
@y = INITIAL_Y
@x = INITIAL_X
end
repaint() # <===== ERROR in Ruby. Unknown method
end
end #ScheduleTask
end #Board
And finally here is the way to start the application :
require_relative 'board'
include Java
import java.awt.EventQueue;
import javax.swing.JFrame;
import javax.swing.SwingUtilities
class UtilityTimerExample < JFrame
def initialize
super
initUI();
setVisible(true)
end
def initUI
add(Board.new());
setResizable(false);
pack();
setTitle("Star");
setLocationRelativeTo(nil)
setDefaultCloseOperation(JFrame::EXIT_ON_CLOSE)
end
end
SwingUtilities.invoke_later do
UtilityTimerExample.new
end
I finally succeeded to solve my problem, using this blog.