001package jmri.jmrit.pragotronclock;
002
003import java.awt.Color;
004import java.awt.Image;
005import java.awt.event.ActionEvent;
006import java.awt.event.ActionListener;
007import java.awt.event.ComponentAdapter;
008import java.awt.event.ComponentEvent;
009import java.beans.PropertyChangeEvent;
010import java.beans.PropertyChangeListener;
011import java.util.Date;
012
013import javax.swing.BoxLayout;
014import javax.swing.JButton;
015import javax.swing.JLabel;
016
017import jmri.InstanceManager;
018import jmri.Timebase;
019import jmri.jmrit.catalog.NamedIcon;
020import jmri.util.JmriJFrame;
021
022/**
023 * Frame providing a simple clock showing Pragotron clock.
024 * <p>
025 * A Run/Stop button is built into this, but because I don't like the way it
026 * looks, it's not currently displayed in the GUI.
027 *
028 * @author Petr Sidlo Copyright (C) 2019
029 *
030 * Based on Nixie clock by Bob Jacobsen.
031 */
032public class PragotronClockFrame extends JmriJFrame implements java.beans.PropertyChangeListener {
033
034    // GUI member declarations
035    JLabel h24;  // hours
036    JLabel m1;  // msb of minutes
037    JLabel m2;
038    JLabel colon;
039
040    double aspect;
041    double iconAspect10;
042    double iconAspectDot;
043    double iconAspect24;
044    int runPauseButtonWidth;
045
046    Timebase clock;
047    private final PropertyChangeListener minuteListener = (PropertyChangeEvent evt) -> update();
048
049    JButton runPauseButton;
050
051    NamedIcon[] foldingSheets10 = new NamedIcon[10];
052    NamedIcon[] baseFoldingSheets10 = new NamedIcon[10];
053    NamedIcon[] foldingSheets24 = new NamedIcon[24];
054    NamedIcon[] baseFoldingSheets24 = new NamedIcon[24];
055    NamedIcon colonIcon;
056    NamedIcon baseColon;
057    //"base" variables used to hold original gifs, other variables used with scaled images
058
059    public PragotronClockFrame() {
060        super(Bundle.getMessage("MenuItemPragotronClock"));
061
062        this.getContentPane().setBackground(new Color(0x3D3D3D));    // set background to black
063
064        clock = InstanceManager.getDefault(jmri.Timebase.class);
065
066        //Load the images (these are now the larger version of the original gifs
067        for (int i = 0; i < 10; i++) {
068            baseFoldingSheets10[i] = new NamedIcon("resources/icons/misc/Pragotron/M" + i + ".png", "resources/icons/misc/Pragotron/M" + i + ".png");
069            foldingSheets10[i] = new NamedIcon("resources/icons/misc/Pragotron/M" + i + ".png", "resources/icons/misc/Pragotron/M" + i + ".png");
070        }
071        for (int i = 0; i < 24; i++) {
072            baseFoldingSheets24[i] = new NamedIcon("resources/icons/misc/Pragotron/H" + i + ".png", "resources/icons/misc/Pragotron/H" + i + ".png");
073            foldingSheets24[i] = new NamedIcon("resources/icons/misc/Pragotron/H" + i + ".png", "resources/icons/misc/Pragotron/H" + i + ".png");
074        }
075        colonIcon = new NamedIcon("resources/icons/misc/Pragotron/dot.png", "resources/icons/misc/Pragotron/dot.png");
076        baseColon = new NamedIcon("resources/icons/misc/Pragotron/dot.png", "resources/icons/misc/Pragotron/dot.png");
077        // set initial size the same as the original gifs
078        for (int i = 0; i < 10; i++) {
079            Image scaledImage = baseFoldingSheets10[i].getImage().getScaledInstance(32, 48, Image.SCALE_SMOOTH);  // 152 / 192
080            foldingSheets10[i].setImage(scaledImage);
081        }
082        for (int i = 0; i < 24; i++) {
083            Image scaledImage = baseFoldingSheets24[i].getImage().getScaledInstance(80, 48, Image.SCALE_SMOOTH);  // 320 / 192
084            foldingSheets24[i].setImage(scaledImage);
085        }
086        Image scaledImage = baseColon.getImage().getScaledInstance(10, 48, Image.SCALE_SMOOTH);  // 40 / 192
087        colonIcon.setImage(scaledImage);
088
089        // create the run/pause button and get it's size
090        runPauseButton = new JButton(Bundle.getMessage("ButtonPauseClock"));
091        runPauseButton.setText( Bundle.getMessage( "ButtonPauseClock") );
092        runPauseButtonWidth = runPauseButton.getPreferredSize().width;
093
094        // determine aspect ratio of a single digit graphic
095        iconAspect10 = 152.0 / 192.0;       // 152 : 192
096        iconAspect24 = 320.0 / 192.0;       // 320 : 192
097        iconAspectDot = 40.0 / 192.0;       // 40 : 192
098
099        // determine the aspect ratio of the 1 hour digit, dot and 2 minutes digit
100        if (!clock.getShowStopButton()) {
101            aspect = (320.0 + 40.0 + 2 * 152.0) / 192.0; // pick up clock prefs choice: no button
102        } else {
103            aspect = (320.0 + 40.0 + 2 * 152.0 + runPauseButtonWidth) / 192.0; // pick up clock prefs choice: add width of a stop/start button
104        }
105
106
107        // init GUI
108        m1 = new JLabel(foldingSheets10[0]);
109        m2 = new JLabel(foldingSheets10[0]);
110        h24 = new JLabel(foldingSheets24[0]);
111        colon = new JLabel(colonIcon);
112
113        getContentPane().setLayout(new BoxLayout(getContentPane(), BoxLayout.X_AXIS));
114        getContentPane().add(h24);
115        getContentPane().add(colon);
116        getContentPane().add(m1);
117        getContentPane().add(m2);
118
119        getContentPane().add(runPauseButton);
120        runPauseButton.addActionListener(new ButtonListener());
121        // since Run/Stop button looks crummy, user may turn it on in clock prefs
122        runPauseButton.setVisible(clock.getShowStopButton()); // pick up clock prefs choice
123        updateButtonText();
124        update();
125        pack();
126
127        // listen for changes to the Timebase parameters
128        clock.addPropertyChangeListener(this);
129
130        // request callback to update time
131        clock.addMinuteChangeListener(minuteListener);
132
133        // Add component listener to handle frame resizing event
134        this.addComponentListener(
135                new ComponentAdapter() {
136                    @Override
137                    public void componentResized(ComponentEvent e) {
138                        scaleImage();
139                    }
140                });
141
142    }
143
144    // Added method to scale the clock digit images to fit the
145    // size of the display window
146    public void scaleImage() {
147        int iconHeight10;
148        int iconWidth10;
149        int iconHeight24;
150        int iconWidth24;
151        int iconHeightDot;
152        int iconWidthDot;
153        int frameHeight = this.getContentPane().getSize().height;
154        int frameWidth = this.getContentPane().getSize().width;
155        if ((double) frameWidth / (double) frameHeight > aspect) {
156            iconHeight10 = frameHeight;
157            iconWidth10 = (int) (iconAspect10 * iconHeight10);
158            iconHeight24 = frameHeight;
159            iconWidth24 = (int) (iconAspect24 * iconHeight24);
160            iconHeightDot = frameHeight;
161            iconWidthDot = (int) (iconAspectDot * iconHeightDot);
162        } else {
163            // allow space in width for run stop button
164            int workingWidth = frameWidth;
165            if (clock.getShowStopButton()) workingWidth = frameWidth - runPauseButtonWidth;
166            iconWidth10 = (int) (workingWidth / 664.0 * 152.0);
167            iconHeight10 = (int) (iconWidth10 / iconAspect10);
168            iconWidth24 = (int) (workingWidth / 664.0 * 320.0);
169            iconHeight24 = (int) (iconWidth24 / iconAspect24);
170            iconWidthDot = (int) (workingWidth / 664.0 * 40.0);
171            iconHeightDot = (int) (iconWidthDot / iconAspectDot);
172        }
173        for (int i = 0; i < 10; i++) {
174            Image scaledImage = baseFoldingSheets10[i].getImage().getScaledInstance(Math.max(1,iconWidth10), Math.max(1,iconHeight10), Image.SCALE_SMOOTH);
175            foldingSheets10[i].setImage(scaledImage);
176        }
177        for (int i = 0; i < 24; i++) {
178            Image scaledImage = baseFoldingSheets24[i].getImage().getScaledInstance(Math.max(1,iconWidth24), Math.max(1,iconHeight24), Image.SCALE_SMOOTH);
179            foldingSheets24[i].setImage(scaledImage);
180        }
181        Image scaledImage = baseColon.getImage().getScaledInstance(Math.max(1,iconWidthDot) , Math.max(1,iconHeightDot), Image.SCALE_SMOOTH);
182        colonIcon.setImage(scaledImage);
183
184        // update the images on screen
185        this.getContentPane().revalidate();
186    }
187
188    @SuppressWarnings("deprecation") // Date.getHours, getMinutes, getSeconds
189    void update() {
190        Date now = clock.getTime();
191        int hours = now.getHours();
192        int minutes = now.getMinutes();
193
194        h24.setIcon(foldingSheets24[hours]);
195        m1.setIcon(foldingSheets10[minutes / 10]);
196        m2.setIcon(foldingSheets10[minutes - (minutes / 10) * 10]);
197    }
198
199    /**
200     * Handle a change to clock properties.
201     * @param e unused.
202     */
203    @Override
204    public void propertyChange(PropertyChangeEvent e) {
205        updateButtonText();
206    }
207
208    /**
209     * Update clock button text.
210     */
211    private void updateButtonText(){
212        runPauseButton.setText( Bundle.getMessage( clock.getRun() ? "ButtonPauseClock" : "ButtonRunClock") );
213    }
214
215    private class ButtonListener implements ActionListener {
216        @Override
217        public void actionPerformed(ActionEvent a) {
218            clock.setRun(!clock.getRun());
219            updateButtonText();
220        }
221    }
222
223    @Override
224    public void dispose() {
225        clock.removeMinuteChangeListener(minuteListener);
226        clock.removePropertyChangeListener(this);
227        super.dispose();
228    }
229
230}