Problem z apletem: nie działa poprawnie w przeglądarkach

0

Witam!

Moja przygoda z Javą właściwie dopiero się zaczyna i wiele aspektów tego języka stanowi dla mnie tajemnicę. Jedną z nich jest niepoprawne działanie małego apleciku, który napisałem. Właściwie nie przedstawia on nic sensownego, pisanie go było dla mnie formą treningu. Aplet składa się z panelu i dwóch przycisków. Naciśnięcie przycisku "Add ball" powoduje, że uruchamiany jest nowy wątek, który rysuje na panelu piłkę i w pętli powoduje jej ruch i przerysowanie panelu. Piłka swobodnie sobie "lata" i odbija się od krawędzi. Drugi przycisk "Clear" powoduje przerwanie tego wątku i usunięcie z panelu piłek. W środowisku Netbeans w przeglądarce apletów wszystko działa poprawnie, ale już w przeglądarkach internetowych nie: naciśnięcie guzika "Clear" nie ma żadnego efektu - wątek nie jest przerywany a piłeczki dalej sobie latają.

Stąd właśnie moja prośba do was drodzy koledzy i koleżanki - czy ktoś mógłby zerknąć na mój kod źródłowy i znaleźć błąd, który powoduje niepoprawne zachowanie się programu? Będę bardzo wdzięczny.

Plik Ball.java:

package bouncingballs;

import java.awt.Color;
import java.awt.geom.Ellipse2D;


public class Ball extends Ellipse2D.Double {
    
    /* Kąt pomiędzy osią OX a wektorem ruchu piłki.*/
    private double motionDir = 3.0/2.0 * Math.PI;
    
    /* Kolor wypełnienia koła */
    private Color fillingColor;
    
    /* Kolor konturu koła */
    private Color contourColor;
    
    

    /**
     * Tworzy piłkę o zadanych rozmiarach.
     * @param x współrzędna X lewego górnego rogu prostokąta opisanego na kole
     * @param y współrzędna Y lewego górnego rogu prostokąta opisanego na kole
     * @param radius promień koła 
     * @param fillingColor kolor wypełnienia koła
     * @param contourColor kolor konturu koła
     */
    public Ball(double x, double y, double radius, Color fillingColor, Color contourColor) {
        super(x, y, radius, radius);
        this.fillingColor = fillingColor;
        this.contourColor = contourColor;
    }
    
    /**
     * Ustala nowy kierunek poruszania się koła na podstawie podanego kąta.
     * @param radians kąt pomiędzy osią oX a wektorem ruchu piłki.
     */
    public void setMotionDir(double radians) {
        motionDir = radians;
    }
    
    /**
     * Zwraca kąt, pod jakim porusza się piłka względem osi oX.
     * @return kąt ruchu piłki
     */
    public double getMotionDir() {
        return motionDir;
    }

    /**
     * Zwraca kolor konturu piłki
     * @return kolor konturu piłki
     */
    public Color getContourColor() {
        return contourColor;
    }

    /**
     * Ustala nowy kolor konturu piłki
     * @param contourColor kolor konturu piłki
     */
    public void setContourColor(Color contourColor) {
        this.contourColor = contourColor;
    }

    /**
     * Zwraca kolor wypełnianie piłki.
     * @return kolor wypełnienia piłki
     */
    public Color getFillingColor() {
        return fillingColor;
    }

    /**
     * Ustala nowy kolor wypełnienia piłki
     * @param fillingColor kolor wypełnienia piłki
     */
    public void setFillingColor(Color fillingColor) {
        this.fillingColor = fillingColor;
    }
    
    /**
     * Przemieszcza piłkę o odległość 

distance wzdłuż wektora przecinającego
* oś oX pod kątem zwracanym przez metodę

getMotionDir()<code/>.
     * @param distance odległość, o jaką ma się przemieścić piłka
     */
    public void move(double distance) {
        
        //Przesunięcie wzdłuż osi y
        double dy = distance * Math.sin(motionDir);
        
        //Przesunięci wzdłuż osi x = sqrt(distance^2 - dy^2)
        double dx = distance * Math.cos(motionDir);
        
        dy = -dy;
        setFrame(getX() + dx, getY() + dy, getWidth(), getWidth());
    }
}

Plik BallPanel.java:

package bouncingballs;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import javax.swing.*;


public class BallPanel extends JPanel {
    
    /* Lista wszystkich piłek dodanych do panelu */
    private ArrayList<Ball> balls = new ArrayList<>();
    
    /* Odległość, o jaką zostaje przesunięta piłka w czasie odrysowania */
    private Double dist = null;
    
    /* Maksymalna ilość piłek, które można dodać do panelu */
    private static final int BALLS_LIMIT = 10;
    
    /* Domyślna odległość, o jaką zostaje przesunięta piłka w czasie odrysowania */
    private static final int DEFAULT_MOVEMENT_DISTANCE = 2;
    
    
    /**
     * Tworzy panel o układzie krawędziowym.
     */
    public BallPanel() {
        super(new BorderLayout());
        setBackground(Color.white);
    }
    
    /**
     * Dodaje piłkę do panelu i powoduje jego odrysowanie.
     * @param ball piłka
     * @return 

false, jeśli piłka nie została dodana, tzn. jeśli w
* panelu znajduje się już maksymalna ilość piłek; w przeciwnym wypadku
*

true<code/>
     */
    public boolean addBall(Ball ball) {
        
        if(balls.size() == BALLS_LIMIT)
            return false;
        
        balls.add(ball);
        return true;
    }
    
    /**
     * Usuwa wszystkie piłki i przerysowuje panel.
     */
    public void removeAllBalls() {
        balls.clear();
        repaint();
    }
    
    /**
     * Przesuwa wszystkie piłki na zdefinowaną odległość a następnie przerysowuje panel. 
     * Jeżeli odległość nie została określona, piłka jest przesuwana o domyślną odległość 
     * równą 1.
     * @param g kontekst graficzny
     */
    @Override
    public void paintComponent(Graphics g) {
        
        super.paintComponent(g);
        Graphics2D g2 = (Graphics2D)g;
        
        
        for(Ball ball : balls) {
            
            //Rysuje kontur piłki
            g2.setPaint(ball.getContourColor());
            g2.draw(ball);

            //Wypełnia piłkę kolorem
            g2.setPaint(ball.getFillingColor());
            g2.fill(ball);
        }
    }
    
    /**
     * Przemieszcza wszystkie piłki o określoną odległość. W przypadku, gdy odległość 
     * nie została określona, piłki przesuwane są o domyślną odległość równą 1.
     */
    public void moveBalls() {
        
        for(Ball ball : balls) {
            
            int boundNr = intersectsBound(ball);
            
            if(boundNr != 0) {
                double newAngle = 0;
                
                if(boundNr == 1 || boundNr == 3)
                    newAngle = Math.PI - ball.getMotionDir();
                else if(boundNr == 2 || boundNr == 4)
                    newAngle = -ball.getMotionDir();
                
                newAngle = newAngle < 0 ? 2*Math.PI + newAngle : newAngle;
                ball.setMotionDir(newAngle);
            }
            
            ball.move(dist == null ? DEFAULT_MOVEMENT_DISTANCE : dist);
        }
    }
    
    /**
     * Sprawdza, czy panel w całości zawiera daną piłkę.
     * @param ball piłka
     * @return 

true, jeśli piłka w całości może być narysowana na
* panelu,

false<code/> gdy piłka wykracza poza granice tego panelu.
     */
    public boolean containsBall(Ball ball) {
        
        //Prostokąt ograniczający piłkę
        Rectangle2D framingRect = ball.getFrame();
        
        //Prostokąt wyznaczający granice panelu
        Rectangle bounds = getBounds();
        
        return bounds.contains(framingRect);
    }
    
    /**
     * Metoda sprawdza, do której granicy panelu dotarła piłka.
     * @param ball badana piłka
     * @return 1 - lewa granica,<br/> 2 - górna granica,<br/> 3 - prawa granica,<br/>
     *  4 - dolna granica,<br/> 0 - piłka nie dotarła do żadnej granicy.
     */
    public int intersectsBound(Ball ball) {
        
        Rectangle bounds = getBounds();
        Rectangle2D framingRect = ball.getFrame();
        
        //Sprawdza, czy piłka dotarła do lewej granicy panelu
        Line2D line1 = new Line2D.Double(bounds.getMinX(), bounds.getMinY(),
                bounds.getMinX(), bounds.getMaxY());
        Line2D line2 = new Line2D.Double(framingRect.getMinX(), framingRect.getMinY(),
                framingRect.getMaxX(), framingRect.getMinY());
        
        if(line1.intersectsLine(line2))
            return 1;
        
        
        //Sprawdza, czy piłka dotarła do górnej granicy panelu
        line1.setLine(bounds.getMinX(), bounds.getMinY(), bounds.getMaxX(), bounds.getMinY());
        line2.setLine(framingRect.getMinX(), framingRect.getMinY(), framingRect.getMinX(), framingRect.getMaxY());
        
        if(line1.intersectsLine(line2))
            return 2;
        
        
        //Sprawdza, czy piłka dotarła do prawej granicy panelu
        line1.setLine(bounds.getMaxX(), bounds.getMinY(), bounds.getMaxX(), bounds.getMaxY());
        line2.setLine(framingRect.getMinX(), framingRect.getMinY(), framingRect.getMaxX(), framingRect.getMinY());
        
        if(line1.intersectsLine(line2))
            return 3;
        
        //Sprawdza, czy piłka dotarła do dolnej granicy panelu
        line1.setLine(bounds.getMinX(), bounds.getMaxY(), bounds.getMaxX(), bounds.getMaxY());
        line2.setLine(framingRect.getMinX(), framingRect.getMinY(), framingRect.getMinX(), framingRect.getMaxY());
        
        if(line1.intersectsLine(line2))
            return 4;
        
        
        return 0;
    }
    
    /**
     * Ustala odległość, o jaką zostaje przesunięta piłka w czasie odrysowania
     * @param dist odleglość
     */
    public void setMovementDistance(double dist) {
        this.dist = new Double(dist);
    }
    
    /**
     * Zwraca domyślną odlełość, o jaką zostaną przesunięte piłki, gdy inna 
     * odległość nie została określona.
     * @return 
     */
    public double getDefaultMovementDistance() {
        return DEFAULT_MOVEMENT_DISTANCE;
    }
    
    /**
     * Zwraca zdefiniowaną odległość, o jaką przesunie się piłka podczas odrysowywania.
     * @return ogległość lub 

null, gdy odległość nie została zdefiniowana
*/
public Double getMovementDistance() {
return dist == null ? dist : new Double(dist);
}
}




Plik ControlPanel.java:
```java
package bouncingballs;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.swing.*;


public class ControlPanel extends JPanel {
    
    private JButton addBall = new JButton("Add ball");
    private JButton clear = new JButton("Clear");
    private final BallPanel ballPanel = new BallPanel();
    private boolean firstBall = true;
    private static Random rand = new Random();
    private ExecutorService executor;
    
    
    
    public ControlPanel() {
        super(new BorderLayout());
        setBorder(BorderFactory.createLineBorder(Color.black));
        
        Box buttonPanel = new Box(BoxLayout.X_AXIS);
        buttonPanel.add(Box.createHorizontalGlue());
        buttonPanel.add(addBall);
        buttonPanel.add(Box.createHorizontalStrut(15));
        buttonPanel.add(clear);
        buttonPanel.add(Box.createHorizontalGlue());
        
        add(ballPanel, BorderLayout.CENTER);
        add(buttonPanel, BorderLayout.SOUTH);
        addListeners();
    }
    
    private void addListeners() {
        
        addBall.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                
                if(firstBall) {
                    executor = Executors.newCachedThreadPool();
                    firstBall = false;                    
                    executor.execute(new BouncingBallTask(ballPanel));
                }
                
                synchronized(ballPanel) {
                    Ball ball = new Ball(
                            ballPanel.getWidth() / 2.0,
                            ballPanel.getHeight() / 2.0,
                            15,
                            Color.red,
                            Color.black);

                    ball.setMotionDir(Math.toRadians(rand.nextInt(360) + 1));
                    ballPanel.addBall(ball);
                }
            }
        });
        
        clear.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                
                executor.shutdownNow();
                
                synchronized(ballPanel) {
                    ballPanel.removeAllBalls();
                    repaint();
                }
                
                firstBall = true;
            }
        });
    }
}

Plik BouncingBallTask.java:

package bouncingballs;

import java.util.concurrent.TimeUnit;


public class BouncingBallTask implements Runnable {
    
    private final BallPanel ballPanel;

    
    
    public BouncingBallTask(BallPanel ballPanel) {
        this.ballPanel = ballPanel;
    }
    
    @Override
    public void run() {
        
        try {
            while(!Thread.currentThread().isInterrupted()) {

                synchronized(ballPanel) {
                    TimeUnit.MILLISECONDS.sleep(5);
                    ballPanel.moveBalls();
                    ballPanel.repaint();
                }
            }
        }
        catch(InterruptedException ignore) {}
    }
}
package bouncingballs;
import javax.swing.*;



public class BouncingBalls extends JApplet {
    
    @Override
    public void init() {
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                add(new ControlPanel());
            }
        });
    }

}

Plik BouncingBalls.java:

0

Skompilowałem, uruchomiłem i zobaczyłem w konsoli Javy przyczynę błędu. Nie mogłeś tego zrobić sam?
Tego

executor.shutdownNow();

apletom nie wolno. Podpisz aplet.

1 użytkowników online, w tym zalogowanych: 0, gości: 1