1

Am trying to modify a java code that implements Analog Clock using BufferedImage. I want the face of the clock to be changed dynamically when a user clicks on a button that scrolls through an array preloaded with the locations of images. So far my attempts have failed. I have two classes; The first Class creates and displays the Analog, and the second class attempts to change the face of the Analog clock using a mutator (setAnalogClockFace()) and an accessor (getAnalogClockFace()). The following is the code for the first Class as found on here, with minor modifications:

package panels;

import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.swing.*;
import java.util.*;

public class Clock extends JPanel{
/**
 * 
 */
private static final long serialVersionUID = 1L;
private int hours = 0;
private int minutes = 0;
private int seconds = 0;
private int millis = 0;
private static final int spacing = 10;
private static final float threePi = (float)(3.0 * Math.PI);
private static final float radPerSecMin = (float)(Math.PI/30.0);
private int size; //height and width of clockface
private int centerX;//X coord of middle of clock
private int centerY;//Y coord of middle of clock
private BufferedImage clockImage;
private BufferedImage imageToUse;
private javax.swing.Timer t;

public Clock(){
    this.setPreferredSize(new Dimension(203,203));
    this.setBackground(getBackground());
    this.setForeground(Color.darkGray);

    t = new javax.swing.Timer(1000, new ActionListener(){
        public void actionPerformed(ActionEvent ae){
            update();
        }
    });
}

public void update(){
    this.repaint();
}

public void start(){
    t.start();
}

public void stop(){
    t.stop();
}

public void paintComponent(Graphics g){
    super.paintComponent(g);
    Graphics2D g2d = (Graphics2D)g;
    g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, 
                                RenderingHints.VALUE_ANTIALIAS_ON);

    int w = getWidth();
    int h = getHeight();
    size = ((w < h) ? w : h) - 2 * spacing;

    centerX = size/2 + spacing;
    centerY = size/2 + spacing;

    setClockFace(new AnalogClockTimer().getAnalogClockFace());
    clockImage = getClockFace();

    if (clockImage == null | clockImage.getWidth() != w | clockImage.getHeight() != h){
        clockImage = (BufferedImage)(this.createImage(w,h));
        Graphics2D gc = clockImage.createGraphics();
        gc.setRenderingHint(RenderingHints.KEY_ANTIALIASING, 
                                RenderingHints.VALUE_ANTIALIAS_ON);

        drawClockFace(gc);
    }


    Calendar now = Calendar.getInstance();
    hours = now.get(Calendar.HOUR);
    minutes = now.get(Calendar.MINUTE);
    seconds = now.get(Calendar.SECOND);
    millis = now.get(Calendar.MILLISECOND);

    g2d.drawImage(clockImage, null, 0, 0);
    drawClockHands(g);
}

private void drawClockHands(Graphics g){
    int secondRadius = size/2;
    int minuteRadius = secondRadius * 3/4;
    int hourRadius = secondRadius/2;

    float fseconds = seconds + (float)millis/1000;
    float secondAngle = threePi - (radPerSecMin * fseconds);

    drawRadius(g, centerX, centerY, secondAngle, 0, secondRadius);

    float fminutes = (float)(minutes + fseconds/60.0);
    float minuteAngle = threePi - (radPerSecMin * fminutes);

    drawRadius(g, centerX, centerY, minuteAngle, 0 , minuteRadius);

    float fhours = (float)(hours + fminutes/60.0);
    float hourAngle = threePi - (5 * radPerSecMin * fhours);

    drawRadius(g, centerX, centerY, hourAngle, 0 ,hourRadius);
}

private void drawClockFace(Graphics g){
    //g.setColor(Color.lightGray);
    g.fillOval(spacing, spacing, size, size);
    //g.setColor(new Color(168,168,168));
    g.drawOval(spacing, spacing, size, size);

    for(int sec = 0; sec < 60; sec++){
        int tickStart;

        if(sec%5 == 0){
            tickStart = size/2-10;
        }else{
            tickStart = size/2-5;
        }

        drawRadius(g, centerX, centerY, radPerSecMin * sec, tickStart, size/2);
    }
}

private void drawRadius(Graphics g, int x, int y, double angle,
        int minRadius, int maxRadius){
    float sine = (float)Math.sin(angle);
    float cosine = (float)Math.cos(angle);

    int dxmin = (int)(minRadius * sine);
    int dymin = (int)(minRadius * cosine);

    int dxmax = (int)(maxRadius * sine);
    int dymax = (int)(maxRadius * cosine);

    g.drawLine(x+dxmin, y+dymin, x+dxmax,y+dymax);
}

public void setClockFace(BufferedImage img){
    if(img != null){
        imageToUse = img;
    }else{
        try{
            imageToUse =  
ImageIO.read(getClass().getClassLoader().getResource("https://i.stack.imgur.com/6TEsB.jpg"));
        }catch(IOException io){
            io.printStackTrace();
        }
    }

}

public BufferedImage getClockFace(){
    return imageToUse;
}

}

The second Class that attempts to set the Clock face is:

package panels;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.image.BufferedImage;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.SwingConstants;
import javax.swing.WindowConstants;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import panels.Clock;


public class AnalogClockTimer extends javax.swing.JPanel implements ActionListener,  
MouseListener {
private Clock clock;
/**
 * 
 */
private static final long serialVersionUID = 1L;
private JButton prevBtn;
private JTextField clockFaceCount;
private JButton nextBtn;
private JPanel buttonedPanel,leftPanel,rightPanel;
private BufferedImage image;

String[] clockFace;
int arrayPointer = 1;


/**
* Auto-generated main method to display this 
* JPanel inside a new JFrame.
*/
public static void main(String[] args) {
    JFrame frame = new JFrame();
    frame.getContentPane().add(new AnalogClockTimer());
    frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
    frame.pack();
    frame.setVisible(true);
}

public AnalogClockTimer() {
    super();
    initGUI();
}

private void initGUI() {
    try {
        BorderLayout thisLayout = new BorderLayout();
        this.setLayout(thisLayout);
        this.setPreferredSize(new java.awt.Dimension(283, 233));
        this.setBackground(getBackground());

        {
            clock = new Clock();
            add(clock,BorderLayout.CENTER);
            clock.start();
            clock.setSize(203, 203);
        }
        {
            buttonedPanel = new JPanel();
            FlowLayout buttonedPanelLayout = new FlowLayout();
            buttonedPanel.setLayout(buttonedPanelLayout);
            this.add(buttonedPanel, BorderLayout.SOUTH);
            buttonedPanel.setPreferredSize(new java.awt.Dimension(283, 30));
            {
                prevBtn = new JButton(new ImageIcon(getClass().getClassLoader().getResource("icons/settings_left_rest.png")));
                prevBtn.setPreferredSize(new java.awt.Dimension(20, 19));
                prevBtn.setBackground(getBackground());
                prevBtn.setBorderPainted(false);
                prevBtn.setEnabled(false);
                prevBtn.addMouseListener(this);
                prevBtn.addActionListener(this);
                buttonedPanel.add(prevBtn);
            }
            {
                nextBtn = new JButton(new ImageIcon(getClass().getClassLoader().getResource("icons/settings_right_rest.png")));
                nextBtn.setPreferredSize(new java.awt.Dimension(20, 19));
                nextBtn.setBackground(getBackground());
                nextBtn.setBorderPainted(false);
                nextBtn.addMouseListener(this);
                nextBtn.addActionListener(this);
            }
            {
                clockFaceCount = new JTextField();
                clockFaceCount.setPreferredSize(new java.awt.Dimension(50, 19));
                clockFaceCount.setBackground(getBackground());
                clockFaceCount.setBorder(null);
                clockFaceCount.setHorizontalAlignment(SwingConstants.CENTER);
                clockFaceCount.setEditable(false);
                buttonedPanel.add(clockFaceCount);

                buttonedPanel.add(nextBtn);
            }
            {
                clockFace = new String[]{"https://i.stack.imgur.com/NprNw.jpg","https://i.stack.imgur.com/FuBgt.jpg",
                        "https://i.stack.imgur.com/6TEsB.jpg"};

                for(int i = 1; i <= clockFace.length; i++){

                    if(i == 1){
                        nextBtn.setEnabled(true);
                        prevBtn.setEnabled(false);
                    }
                    clockFaceCount.setText(arrayPointer+" of "+clockFace.length);
                }
            }
            {
                leftPanel = new JPanel();
                leftPanel.setPreferredSize(new Dimension(40, getHeight()));
                add(leftPanel,BorderLayout.WEST);
            }
            {
                rightPanel = new JPanel();
                rightPanel.setPreferredSize(new Dimension(40,getHeight()));
                add(rightPanel,BorderLayout.EAST);
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

@Override
public void mouseClicked(MouseEvent me) {
    if(me.getSource() == prevBtn)
        prevBtn.setIcon(new ImageIcon(getClass().getClassLoader().getResource("icons/settings_left_pressed.png")));
    else
        nextBtn.setIcon(new ImageIcon(getClass().getClassLoader().getResource("icons/settings_right_pressed.png")));
}

@Override
public void mouseEntered(MouseEvent me) {
    if(me.getSource()==prevBtn)
        prevBtn.setIcon(new ImageIcon(getClass().getClassLoader().getResource("icons/settings_left_hover.png")));
    else
        nextBtn.setIcon(new ImageIcon(getClass().getClassLoader().getResource("icons/settings_right_hover.png")));
}

@Override
public void mouseExited(MouseEvent me) {
    if(me.getSource() == prevBtn)
        prevBtn.setIcon(new ImageIcon(getClass().getClassLoader().getResource("icons/settings_left_rest.png")));
    else
        nextBtn.setIcon(new ImageIcon(getClass().getClassLoader().getResource("icons/settings_right_rest.png")));

}

@Override
public void mousePressed(MouseEvent me) {
    if(me.getSource() == prevBtn){
        prevBtn.setIcon(new ImageIcon(getClass().getClassLoader().getResource("icons/settings_left_pressed.png")));
    }else{
        nextBtn.setIcon(new ImageIcon(getClass().getClassLoader().getResource("icons/settings_right_pressed.png")));
    }
}

@Override
public void mouseReleased(MouseEvent me) {
    if(me.getSource() == prevBtn)
        prevBtn.setIcon(new ImageIcon(getClass().getClassLoader().getResource("icons/settings_left_hover.png")));
    else
        nextBtn.setIcon(new ImageIcon(getClass().getClassLoader().getResource("icons/settings_right_hover.png")));

}

@Override
public void actionPerformed(ActionEvent ae) {
    if(ae.getSource() == nextBtn){
        {
            if(arrayPointer < clockFace.length){
                ++arrayPointer;
                prevBtn.setEnabled(true);

                if(arrayPointer == clockFace.length)
                    nextBtn.setEnabled(false);
            }

            clockFaceCount.setText(arrayPointer + " of " + clockFace.length);
            setAnalogClockFace(arrayPointer);
        }
    }else if(ae.getSource() == prevBtn){
        {
            if(arrayPointer > 1){
                --arrayPointer;
                nextBtn.setEnabled(true);

                if(arrayPointer == 1){
                    clockFaceCount.setText(arrayPointer+" of "+clockFace.length);
                    prevBtn.setEnabled(false);
                }
            }
            clockFaceCount.setText(arrayPointer + " of "+clockFace.length);
            setAnalogClockFace(arrayPointer);
        }
    }

}

private void setAnalogClockFace(int pointerPosition) {
    try{
        image = ImageIO.read(getClass().getClassLoader().getResource(clockFace[pointerPosition]));
    }catch(IOException io){
        io.printStackTrace();
    }
}

public BufferedImage getAnalogClockFace(){
    return image;
}

}

I've been working on this for days, can someone please help me understand what am not doing right? Any help would be greatly appreciated.

Here is an MCVE using a JLabel to achieve the same result:

package panels;

import java.awt.Dimension;

import javax.swing.ImageIcon;
import javax.swing.SwingConstants;
import javax.swing.WindowConstants;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JButton;
import javax.swing.JTextField;

import java.awt.event.*;
import java.awt.BorderLayout;

@SuppressWarnings({"serial"})
public class ClockPanel extends javax.swing.JPanel implements ActionListener {
private JLabel clockImageLabel;
private JPanel buttonsPanel;
private JButton next,prev;
private JTextField displayCount;

String[] imageFaces;
int pointer = 1;
/**
* Auto-generated main method to display this 
* JPanel inside a new JFrame.
*/
public static void main(String[] args) {
    JFrame frame = new JFrame();
    frame.getContentPane().add(new ClockPanel());
    frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
    frame.pack();
    frame.setVisible(true);
}

public ClockPanel() {
    super();
    initGUI();
}

private void initGUI() {
    try {
        setPreferredSize(new Dimension(203, 237));
        BorderLayout panelLayout = new BorderLayout();
        setLayout(panelLayout);

        {
            clockImageLabel = new JLabel();
            clockImageLabel.setPreferredSize(new Dimension(203,203));
            clockImageLabel.setHorizontalAlignment(SwingConstants.CENTER);
            clockImageLabel.setVerticalAlignment(SwingConstants.CENTER);
            add(clockImageLabel,BorderLayout.NORTH);
        }
        {
            buttonsPanel = new JPanel();

            {
                next = new JButton(">");
                next.addActionListener(this);
            }
            {
                prev = new JButton("<");
                prev.addActionListener(this);

                displayCount = new JTextField();
                displayCount.setBorder(null);
                displayCount.setEditable(false);
                displayCount.setBackground(getBackground());
                displayCount.setHorizontalAlignment(SwingConstants.CENTER);
                buttonsPanel.add(prev);
                buttonsPanel.add(displayCount);
                displayCount.setPreferredSize(new java.awt.Dimension(45, 23));
                buttonsPanel.add(next);

                add(buttonsPanel,BorderLayout.SOUTH);
            }
            {
                imageFaces = new String[]{"https://i.stack.imgur.com/6TEsB.jpg",
                        "https://i.stack.imgur.com/NprNw.jpg","https://i.stack.imgur.com/FuBgt.jpg"
                };

                for(int i = 1; i < imageFaces.length; i++){
                    if(i == 1){
                        next.setEnabled(true);
                        prev.setEnabled(false);
                    }
                    displayCount.setText(pointer+" of "+imageFaces.length);
                    setLabelImage(pointer);
                }
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

private void setLabelImage(int pointerPosition){
    clockImageLabel.setIcon(new ImageIcon(getClass().getClassLoader().getResource(imageFaces[pointerPosition])));
    this.repaint();
}

@Override
public void actionPerformed(ActionEvent ae) {
    if(ae.getSource() == next){
            if(pointer < imageFaces.length){
                ++pointer;
                prev.setEnabled(true);

                if(pointer == imageFaces.length)next.setEnabled(false);

                displayCount.setText(pointer+" of "+imageFaces.length);
                setLabelImage(pointer);
            }
    }else if(ae.getSource() == prev){
        if(pointer > 1){
            --pointer;
            next.setEnabled(true);

            if(pointer == 1)prev.setEnabled(false);

            displayCount.setText(pointer+" of "+imageFaces.length);
            setLabelImage(pointer);
        }
    }
}

}

Here are 1/2 Images for the code: HandlessAnalogClock 1 HandlessAnalogClock 2 HandlessAnalogClock 3

aknessy
  • 55
  • 8
  • 1
    1) For better help sooner, post an [MCVE](http://stackoverflow.com/help/mcve) (Minimal Complete Verifiable Example). 2) One way to get images for an example, is to hot link to images seen in [this Q&A](http://stackoverflow.com/q/19209650/418556). – Andrew Thompson Nov 03 '14 at 22:25
  • 1
    Note that your MCVE should be significantly less than the 380+ lines of code of those 2 classes... – Andrew Thompson Nov 03 '14 at 22:27
  • A complete, working example is seen in this [duplicate](http://stackoverflow.com/q/26211683/230513). – trashgod Nov 04 '14 at 09:46
  • @AndrewThompson Apologies for my lateness in commenting on your previous post; I replicated the above problem using a JLabel as an example, and i discovered it's much easier to change the face of the analog clocks as stated earlier. What i don't understand is why i seem unable to do the same on a JPanel – aknessy Nov 05 '14 at 06:28
  • @AndrewThompson I have edited my post to include an MCVE using the same images on a JLabel. – aknessy Nov 05 '14 at 06:39
  • @trashgod Thanks for the comments, but i noticed the example on the link provided has no direct connection to my problem. The above code works, what isn't working is making the Analog clock face work dynamically with BufferedImages. – aknessy Nov 05 '14 at 06:45
  • `"icons/HandlessAnalogClock.png"` I don't have that image here, which is why I suggested to ***hot link*** to images, so anyone can run the sample. – Andrew Thompson Nov 05 '14 at 07:17
  • @AndrewThompson I've uploaded just 2 Clock images(.png) that i used for the code. i hope that's what you meant? Thanks – aknessy Nov 05 '14 at 08:57
  • In your [complete example](http://stackoverflow.com/help/mcve), access posted images via `URL`, as shown [here](http://stackoverflow.com/a/10862262/230513). – trashgod Nov 05 '14 at 11:12
  • @trashgod @ I uploaded three of the images to IMGUR and i have edited the post to include URL to the images, you can as well download them to your computer and link them as necessary. Hope i have understood you clearly. – aknessy Nov 06 '14 at 22:19
  • @AndrewThompson i have added the URL to the images. Am grateful for your patience. Hopefully with your help i can solve this dilemma. – aknessy Nov 06 '14 at 22:51

2 Answers2

3

This MCVE will successfully flip between two of the three images, but still has problems with ArrayIndexOutOfBoundsException.

That last part is 'Batteries Not Included' (left as an exercise for the user to correct).

import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import javax.swing.*;
import javax.imageio.ImageIO;
import java.net.URL;

@SuppressWarnings({"serial"})
public class ClockPanel extends JPanel implements ActionListener {

    private JLabel clockImageLabel;
    private JPanel buttonsPanel;
    private JButton next, prev;
    private JTextField displayCount;

    BufferedImage[] images;
    int pointer = 1;

    /**
     * Auto-generated main method to display this JPanel inside a new JFrame.
     */
    public static void main(String[] args) {
        JFrame frame = new JFrame();
        frame.getContentPane().add(new ClockPanel());
        frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
        frame.setLocationByPlatform(true);
        frame.pack();
        frame.setVisible(true);
    }

    public ClockPanel() {
        super();
        initGUI();
    }

    private void initGUI() {
        try {
            setPreferredSize(new Dimension(203, 237));
            BorderLayout panelLayout = new BorderLayout();
            setLayout(panelLayout);

            clockImageLabel = new JLabel();
            clockImageLabel.setPreferredSize(new Dimension(203, 203));
            clockImageLabel.setHorizontalAlignment(SwingConstants.CENTER);
            clockImageLabel.setVerticalAlignment(SwingConstants.CENTER);
            add(clockImageLabel, BorderLayout.NORTH);

            buttonsPanel = new JPanel();

            next = new JButton(">");
            next.addActionListener(this);

            prev = new JButton("<");
            prev.addActionListener(this);

            displayCount = new JTextField();
            displayCount.setBorder(null);
            displayCount.setEditable(false);
            displayCount.setBackground(getBackground());
            displayCount.setHorizontalAlignment(SwingConstants.CENTER);
            buttonsPanel.add(prev);
            buttonsPanel.add(displayCount);
            displayCount.setPreferredSize(new java.awt.Dimension(45, 23));
            buttonsPanel.add(next);

            add(buttonsPanel, BorderLayout.SOUTH);

            String[] imageFaces = new String[]{
                "http://i.imgur.com/T8x0I29.png",
                "http://i.imgur.com/079QSdJ.png",
                "http://i.imgur.com/vQ7tZjI.png"
            };
            images = new BufferedImage[imageFaces.length];
            for (int ii=0; ii<imageFaces.length; ii++) {
                System.out.println("loading image: " + ii);
                images[ii] = ImageIO.read(new URL(imageFaces[ii]));
                System.out.println("loaded image: " + images[ii]);
            }
            setLabelImage(0);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void setLabelImage(int pointerPosition) {
        System.out.println("setLabelImage: " + pointerPosition);
        clockImageLabel.setIcon(new ImageIcon(images[pointerPosition]));
        this.repaint();
    }

    @Override
    public void actionPerformed(ActionEvent ae) {
        if (ae.getSource() == next) {
            if (pointer < images.length) {
                ++pointer;
                prev.setEnabled(true);

                if (pointer == images.length) {
                    next.setEnabled(false);
                }

                displayCount.setText(pointer + " of " + images.length);
                setLabelImage(pointer);
            }
        } else if (ae.getSource() == prev) {
            if (pointer > 1) {
                --pointer;
                next.setEnabled(true);

                if (pointer == 1) {
                    prev.setEnabled(false);
                }

                displayCount.setText(pointer + " of " + images.length);
                setLabelImage(pointer);
            }
        }
    }
}
Andrew Thompson
  • 168,117
  • 40
  • 217
  • 433
  • Thanks for the post,but i think it's much easier to flip through the images when displayed on a JLabel, than when the images are painted on a JPanel. I think i posted an MCVE of that in my earlier posts! – aknessy Nov 07 '14 at 08:42
  • *"I think i posted an MCVE of that in my earlier posts!"* It's odd. You write that as though you expect me to go trawling though your past posts before trying to help.. Each Q&A should be entirely self contained. – Andrew Thompson Nov 07 '14 at 08:44
  • 1
    +1 for MCVE, but needs less `setPreferredSize()`. :-) @aknessy: Also consider wrap-around indexing, seen in the example cited [here](http://stackoverflow.com/a/10290860/230513). – trashgod Nov 07 '14 at 11:49
  • @trashgod Aw.. there's only ***..6 calls*** to set preferred sizes?!? I completely missed those. OP See [Should I avoid the use of set(Preferred|Maximum|Minimum)Size methods in Java Swing?](http://stackoverflow.com/q/7229226/418556) (Yes.) – Andrew Thompson Nov 07 '14 at 11:53
  • 1
    @AndrewThompson: I do it, too. :-) More [here](http://stackoverflow.com/a/26801408/230513). – trashgod Nov 07 '14 at 12:38
  • @AndrewThompson if my comment sounded rude, i apologize. I only thought you'd gone through it. Thanks Though. – aknessy Nov 07 '14 at 16:17
3

Expanding on @Andrew's example, consider arranging for the index of the current image to wrap-around, as shown in the example cited here.

private int pointer = 0;
...
private void initGUI() {
    ...
    setLabelImage(pointer);
    ...
}

private void setLabelImage(int pointerPosition) {
    System.out.println("setLabelImage: " + pointerPosition);
    clockImageLabel.setIcon(new ImageIcon(images[pointerPosition]));
    displayCount.setText((pointerPosition + 1) + " of " + images.length);
    this.repaint();
}

@Override
public void actionPerformed(ActionEvent ae) {
    if (ae.getSource() == next) {
        pointer++;
        if (pointer >= images.length) {
            pointer = 0;
        }
    } else if (ae.getSource() == prev) {
        pointer--;
        if (pointer < 0) {
            pointer = images.length - 1;
        }
    }
    setLabelImage(pointer);
}

As an exercise, try to factor out the repeated instantiation of ImageIcon in setLabelImage() by creating a List<ImageIcon> in initGUI().

Community
  • 1
  • 1
trashgod
  • 203,806
  • 29
  • 246
  • 1,045