/* * Clock2 - Demo on GridBagLayout, Concurrentcy, Fonts, spinner, combobox, drawing, etc */ import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.swing.event.*; import java.util.GregorianCalendar; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.awt.geom.Ellipse2D; import java.awt.geom.Line2D; import java.awt.geom.Point2D; public class Clock2 extends JFrame implements GUIClock, ActionListener, ChangeListener, ItemListener, WindowListener { private JTextField textF = null; private JComboBox fontB = null; private ClockTimer timer = null; private SimpleDateFormat dateF = null; private JSpinner fsSpinner = null; private JSpinner updateSpinner = null; private String font = Font.MONOSPACED; // default monospace font private int fSize = 12; private int fStyle = Font.PLAIN; private AnalogClockFace aClock = null; private JPanel cPane = null; // TODO, divide to several classes // more features: // choice of digital clock format (fields) // hide/show each clock // oval analog clock // private int[] fSizes = {6, 7, 8, 9, 10, 12, 14, 16, 20, 24, 32, 48, 64}; public Clock2() { dateF = new SimpleDateFormat("HH:mm:ss"); draw(); } /* * Draw the UI initially. * TODO: split */ private void draw() { JFrame mainFrame = this; // new JFrame("Clock2"); mainFrame.setTitle("Clock2"); mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); cPane = (JPanel)mainFrame.getContentPane(); cPane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); cPane.setLayout(new GridBagLayout()); // placement class to be used on placing each component GridBagConstraints constr = new GridBagConstraints(); // fill all space constr.fill = GridBagConstraints.BOTH; // Digital clock display placement // 1st column in 1st rows for reserve constr.gridx = 1; constr.gridy = 0; constr.weightx = 0.2; // no much resizing constr.weighty = 0.1; // even less (does not affect yet) // size constr.gridheight = 1; constr.gridwidth = 2; // text field to display time textF = new JTextField("", 8); textF.setEditable(false); textF.setAlignmentX((float)0.5); // centered textF.setBorder(BorderFactory.createLineBorder(Color.black)); cPane.add(textF, constr); // update period selector SpinnerModel updateModel = new SpinnerNumberModel(100, 50, 20*1000, 50); updateSpinner = new JSpinner(updateModel); updateSpinner.addChangeListener(this); updateSpinner.setBorder(BorderFactory .createLineBorder(Color.black)); constr.gridx = 3; constr.gridwidth = 1; cPane.add(updateSpinner, constr); // font family selector String[] fontNames = GraphicsEnvironment .getLocalGraphicsEnvironment() .getAvailableFontFamilyNames(); fontB = new JComboBox(fontNames); fontB.setEditable(false); fontB.addActionListener(this); fontB.setSelectedItem(font); fontB.setBorder(BorderFactory.createLineBorder(Color.black)); constr.gridx = 0; constr.gridy = 1; constr.gridwidth = 2; constr.weighty = 0.05; // even less resizing cPane.add(fontB, constr); // font size selector SpinnerModel fSizeModel = new SpinnerNumberModel(fSize, 6, 64, 1); fsSpinner = new JSpinner(fSizeModel); fsSpinner.addChangeListener(this); fsSpinner.setBorder(BorderFactory .createLineBorder(Color.black)); constr.gridx = 2; cPane.add(fsSpinner, constr); // analog clock aClock = new AnalogClockFace(); constr.gridx = 0; constr.gridy = 2; constr.gridwidth = 4; constr.weightx = 1; // most resizing comes here constr.weighty = 1; // cPane.add(aClock, constr); // analog clock controls are fitted in a horisontal box Box bBox = new Box(BoxLayout.X_AXIS); constr.gridy = 3; constr.gridwidth = 4; constr.weightx = 1; constr.weighty = 0; // fixed size, no resizing cPane.add(bBox, constr); // check boxes /* JCheckBox button = new JCheckBox("Seconds (TODO)"); button.setSelected(aClock.showSeconds); button.addItemListener(this); button.setName("Seconds"); bBox.add(button); */ // start the clock timer thread to update this clock timer = new ClockTimer(this, 50); timer.execute(); //Display the window. mainFrame.pack(); mainFrame.setVisible(true); mainFrame.addWindowListener(this); } // draw() // Action listener public void actionPerformed(ActionEvent e) { Object comp = e.getSource(); if (comp == fontB) { font = (String)((JComboBox)comp).getSelectedItem(); updateFormat(); } } // Item listener public void itemStateChanged(ItemEvent e) { JComponent comp = (JComponent)e.getSource(); try { // here we use component name just for an example of its usage if (comp.getName().equals("Seconds")) { aClock.showSeconds = ((JCheckBox)(comp)).isSelected(); } } catch (Exception ex) {} } // Change listener public void stateChanged(ChangeEvent e) { Object comp = e.getSource(); if (comp == fsSpinner) { int value = (Integer) fsSpinner.getValue(); fSize = value; updateFormat(); } else if (comp == updateSpinner) { int value = (Integer) updateSpinner.getValue(); timer.setPeriod(value); } } // window listener public void windowClosing(WindowEvent e) { } public void windowClosed(WindowEvent e) { } public void windowOpened(WindowEvent e) { } public void windowIconified(WindowEvent e) { timer.suspend(); } public void windowDeiconified(WindowEvent e) { timer.resume(); } public void windowActivated(WindowEvent e) { } public void windowDeactivated(WindowEvent e) { } // update formatting of digital clock private void updateFormat() { textF.setFont(new Font(font, fStyle, fSize)); // as textF may change size, we redraw cPane cPane.revalidate(); cPane.repaint(); } // update is called from the ClockTimer.process() by event dispatcher thread public void update(GregorianCalendar t) { // digital clock String ts = dateF.format(t.getTime()); textF.setText(ts); // analog clock aClock.update(t); } /* * Start the gui. */ public static void main(String[] args) { javax.swing.SwingUtilities.invokeLater(new Runnable() { public void run() { Clock2 cgui = new Clock2(); } }); } // main() } // class Clock2 // TODO size adjusting class AnalogClockFace extends JPanel implements ComponentListener { private int pSize = 200; private int size = pSize; // needed? private double fillFactor = 0.45; private int radius = (int)(size * fillFactor); private double sLen = 0.98; private double mLen = 0.85; private double hLen = 0.65; // TODO widths not used yet private int sWidth = 1; private int mWidth = 2; private int hWidth = 4; private Point2D.Double origin = new Point2D.Double(size/2, size/2); protected boolean showSeconds = true; // TODO: not used GregorianCalendar previous = null; GregorianCalendar now = new GregorianCalendar(); public AnalogClockFace() { setBorder(BorderFactory.createLineBorder(Color.black)); updateSize(); this.addComponentListener(this); } @Override public Dimension getPreferredSize() { return new Dimension(pSize, pSize); } private void updateSize() { int w = getWidth(); int h = getHeight(); if (w < h) h = w; if (h != radius) { radius = (int)(h * fillFactor); size = h; repaint(); origin.setLocation(h/2.0, h/2.0); } } public void update(GregorianCalendar n) { now = n; repaint(); } /* * computes location of a minute in the circle of a clock of a radius */ private Point2D.Double mPoint(double minute, double radius, Point2D.Double origin) { double angle = minute * Math.PI / 30.0; return new Point2D.Double((int)(origin.x + radius * Math.sin(angle)), (int)(origin.y - radius * Math.cos(angle))); } @Override public void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2 = (Graphics2D) g; // circle and center g2.draw(new Ellipse2D.Double(origin.x-radius, origin.y-radius, radius*2, radius*2)); g2.draw(new Ellipse2D.Double(origin.x-2, origin.y-2, 4, 4)); // tics for (int h = 1; h <= 12; h++) g2.draw(new Line2D.Double(mPoint(h*60.0/12.0, radius*0.90, origin), mPoint(h*60.0/12.0, radius, origin))); if (radius > 100) for (int m = 1; m < 60; m++) g2.draw(new Line2D.Double(mPoint(m, radius*0.96, origin), mPoint(m, radius, origin))); // hands // hour g2.draw(new Line2D.Double(origin, mPoint(now.get(now.HOUR)*60.0/12.0 + (double)now.get(now.MINUTE)/12.0, radius*hLen, origin))); // minute g2.draw(new Line2D.Double(origin, mPoint(now.get(now.MINUTE) + (double)now.get(now.SECOND)/60.0, radius*mLen, origin))); // second // TODO: choice of full second/continous movement according to the update frequency // full second movement is nicer if we update only once in a second // millisecond version may stop between seconds // g2.draw(new Line2D.Double(origin, mPoint(now.get(now.SECOND), radius*sLen, origin))); g2.draw(new Line2D.Double(origin, mPoint(now.get(now.SECOND) + (double)now.get(now.MILLISECOND)/1000.0, radius*sLen, origin))); } // paintComponent() // component listener public void componentHidden(ComponentEvent e) { } public void componentMoved(ComponentEvent e) { } public void componentResized(ComponentEvent e) { updateSize(); } public void componentShown(ComponentEvent e) { } } // class AnalogClockFace