package holeg.ui.view.main; import holeg.preferences.ColorPreference; import holeg.preferences.ImagePreference; import holeg.ui.controller.Control; import holeg.ui.model.GuiSettings; import holeg.ui.view.image.Import; import holeg.utility.listener.LostFocusListener; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Point; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.util.Hashtable; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import javax.swing.AbstractAction; import javax.swing.Box; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JSlider; import javax.swing.JTextField; import javax.swing.KeyStroke; import javax.swing.SwingConstants; import javax.swing.Timer; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.plaf.basic.BasicSliderUI; /** * This Class represents a Panel where the User can start and stop the Simulation. He Can also reset * the Simulation and click through every Iteration. */ public class TimePanel extends JPanel implements ActionListener { private static final int MAX_ITERATIONS = 100000; /* * variable for calculating the performance */ final JButton playBtn = new JButton(); final JButton timeResetBtn = new JButton(); final JButton timeForwardBtn = new JButton(); final JButton timeBackwardBtn = new JButton(); final JLabel iterationsLabel = new JLabel("Iterations:", SwingConstants.CENTER); private final Control control; private final JSlider speedSlider = new JSlider(); private final Timer timer; JTextField iterationsField; JLabel hint = new JLabel("Invalid", SwingConstants.RIGHT); JSlider timeSlider = new JSlider() { { // Make the slider jump to mouse position on left click MouseListener[] listeners = getMouseListeners(); for (MouseListener l : listeners) { removeMouseListener(l); // remove UI-installed TrackListener } final BasicSliderUI ui = (BasicSliderUI) getUI(); BasicSliderUI.TrackListener tl = ui.new TrackListener() { // this is where we jump to absolute value of click @Override public void mouseClicked(MouseEvent e) { Point p = e.getPoint(); int value = ui.valueForXPosition(p.x); setValue(value); } // disable check that will invoke scrollDueToClickInTrack @Override public boolean shouldScroll(int dir) { return false; } }; addMouseListener(tl); } }; private ScheduledFuture futureTask; private int dragResetIteration = 0; private boolean running = false; /** * Constructor * * @param cont the Controller */ public TimePanel(Control cont) { super(); this.control = cont; // One Iteration timer = new Timer(0, clicked -> timerAction()); // Time Slider. Panels and Buttons this.setLayout(new BorderLayout(0, 0)); this.setBorder(null); // Slider timeSlider.setPaintTicks(true); timeSlider.setPaintLabels(true); timeSlider.setMajorTickSpacing( (int) Math.ceil(((double) cont.getModel().getMaxIterations()) / 20)); timeSlider.setMinorTickSpacing( (int) Math.ceil(((double) cont.getModel().getMaxIterations()) / 100)); timeSlider.setToolTipText("Time Slider"); timeSlider.setMaximum(cont.getModel().getMaxIterations() - 1); timeSlider.setValue(0); timeSlider.addChangeListener( changeEvent -> control.getModel().setCurrentIteration(timeSlider.getValue())); this.setBorder(null); timeSlider.addChangeListener(changeEvent -> { control.updateStateForIteration(timeSlider.getValue()); }); timeSlider.addMouseListener(new MouseAdapter() { @Override public void mousePressed(MouseEvent e) { dragResetIteration = cont.getModel().getCurrentIteration(); } }); timeSlider.addMouseMotionListener(new MouseAdapter() { @Override public void mouseDragged(MouseEvent e) { if (dragResetIteration != cont.getModel().getCurrentIteration()) { if (running) { play(); } } } }); // Panel JPanel timeBtnPanel = new JPanel(); timeBtnPanel.setBorder(null); timeBtnPanel.setLayout(new FlowLayout(FlowLayout.CENTER)); // Buttons playBtn.setToolTipText("Play"); playBtn.setContentAreaFilled(false); playBtn.setBorderPainted(false); playBtn.setBorder(null); playBtn.setIcon(new ImageIcon(Import.loadImage(ImagePreference.Button.TimePanel.Play, 30, 30))); playBtn.addActionListener(clicked -> play()); timeResetBtn.setToolTipText("Reset"); timeResetBtn.setContentAreaFilled(false); timeResetBtn.setBorder(null); timeResetBtn.setIcon( new ImageIcon(Import.loadImage(ImagePreference.Button.TimePanel.Reset, 30, 30))); timeResetBtn.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ae) { timeSlider.setValue(timeSlider.getMinimum()); control.getModel().setCurrentIteration(timeSlider.getValue()); control.resetSimulation(); control.updateStateForCurrentIteration(); if (running) { play(); } } }); timeForwardBtn.setToolTipText("Forward"); timeForwardBtn.setContentAreaFilled(false); timeForwardBtn.setBorder(null); timeForwardBtn.setIcon( new ImageIcon(Import.loadImage(ImagePreference.Button.TimePanel.Forward, 30, 30))); timeForwardBtn.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ae) { timeSlider.setValue(timeSlider.getValue() + 1); control.getModel().setCurrentIteration(timeSlider.getValue()); } }); timeBackwardBtn.setToolTipText("Backward"); timeBackwardBtn.setBorder(null); timeBackwardBtn.setIcon( new ImageIcon(Import.loadImage(ImagePreference.Button.TimePanel.Backward, 30, 30))); timeBackwardBtn.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ae) { timeSlider.setValue(timeSlider.getValue() - 1); control.getModel().setCurrentIteration(timeSlider.getValue()); } }); timeBtnPanel.add(playBtn); timeBtnPanel.add(Box.createRigidArea(new Dimension(10, 0))); timeBtnPanel.add(timeResetBtn); timeBtnPanel.add(Box.createRigidArea(new Dimension(10, 0))); timeBtnPanel.add(timeForwardBtn); // Speed Panel JPanel speedPanel = new JPanel(); JLabel simSpeedLabel = new JLabel("Speed:"); speedPanel.add(simSpeedLabel); speedPanel.add(speedSlider); speedSlider.setPaintTicks(true); speedSlider.setPaintLabels(true); speedSlider.setMaximum(6); speedSlider.setMinimum(0); speedSlider.setValue(1); speedSlider.setPaintLabels(true); Hashtable table = new Hashtable(); table.put(0, new JLabel("1x")); table.put(1, new JLabel("2x")); table.put(2, new JLabel("4x")); table.put(3, new JLabel("8x")); table.put(4, new JLabel("16x")); table.put(5, new JLabel("32x")); table.put(6, new JLabel("64x")); speedSlider.setLabelTable(table); speedSlider.addChangeListener(new ChangeListener() { @Override public void stateChanged(ChangeEvent e) { /* * Shifting Powers of two: e.g. 1<<0 -> 1 step per Second 1<<3 -> 8 steps per * Second and so on, */ int calculationsPerSecond = 1 << speedSlider.getValue(); GuiSettings.timerSpeed = (1024 >> speedSlider.getValue()); speedSlider.setToolTipText("Speed: " + calculationsPerSecond + " Calculations per Second."); } }); speedSlider.setToolTipText("Change the Number of Calculations per Secons"); // Buttons and Speed Panel JPanel btnAndSpeedPanel = new JPanel(); btnAndSpeedPanel.setLayout(new BorderLayout(0, 0)); btnAndSpeedPanel.setBorder(null); btnAndSpeedPanel.add(timeBtnPanel, BorderLayout.NORTH); btnAndSpeedPanel.add(speedPanel, BorderLayout.CENTER); JPanel iterationsPanel = new JPanel(); iterationsPanel.setLayout(new GridBagLayout()); GridBagConstraints c = new GridBagConstraints(); c.anchor = GridBagConstraints.CENTER; c.fill = GridBagConstraints.HORIZONTAL; c.gridx = 0; c.gridy = 0; iterationsPanel.add(iterationsLabel, c); hint.setForeground(Color.red); hint.setText(" "); iterationsField = new JTextField(6);// Considering hundreds of thousands in an extreme case iterationsField.setText("" + cont.getModel().getMaxIterations()); iterationsField.setToolTipText("0-" + MAX_ITERATIONS); iterationsField.addActionListener(this); ScheduledThreadPoolExecutor s = new ScheduledThreadPoolExecutor(1); iterationsField.addCaretListener((e) -> { try { iterationsField.setBackground(Color.WHITE);// red stings if (futureTask != null) { futureTask.cancel(true); } futureTask = s.schedule((Runnable) this::updateIterationsInput, 1, TimeUnit.SECONDS); hint.setText(" "); } catch (NumberFormatException n) { iterationsField.setBackground(ColorPreference.TimePanel.Invalid);// red stings hint.setText("Invalid"); } }); iterationsField.addFocusListener((LostFocusListener) (e) -> updateIterationsInput()); c.gridy = 1; iterationsPanel.add(iterationsField, c); c.gridy = 2; iterationsPanel.add(hint, c); // iterationsPanel.add(new JLabel(), BorderLayout.SOUTH); JPanel timePanel = new JPanel(); timePanel.setLayout(new BorderLayout()); ; timePanel.add(iterationsPanel, BorderLayout.WEST); timePanel.add(timeSlider, BorderLayout.CENTER); this.add(btnAndSpeedPanel, BorderLayout.WEST); add(timePanel); // Disable Keys timeSlider.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, false), "UP_ARROW"); timeSlider.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, false), "DOWN_ARROW"); timeSlider.getInputMap() .put(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, 0, false), "PAGE_DOWN"); timeSlider.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP, 0, false), "PAGE_UP"); timeSlider.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_END, 0, false), "END"); timeSlider.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_HOME, 0, false), "HOME"); // Left arrow Key timeSlider.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, false), "LEFT_ARROW"); timeSlider.getActionMap().put("LEFT_ARROW", new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { timeSlider.setValue(timeSlider.getValue() - 1); control.resetSimulation(); control.calculateStateForCurrentIteration(); } }); // Right arrow Key timeSlider.getInputMap() .put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, false), "Right_ARROW"); timeSlider.getActionMap().put("Right_ARROW", new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { timeSlider.setValue(timeSlider.getValue() + 1); } }); } public void timerAction() { timeSlider.setValue(timeSlider.getValue() + 1); control.getModel().setCurrentIteration(timeSlider.getValue()); timer.setDelay(GuiSettings.timerSpeed); if (timeSlider.getValue() >= control.getModel().getMaxIterations() - 1) { running = false; playBtn.setIcon( new ImageIcon(Import.loadImage(ImagePreference.Button.TimePanel.Play, 30, 30))); timer.stop(); } } public void play() { if (control.getModel().getCurrentIteration() == control.getModel().getMaxIterations() - 1) { timeSlider.setValue(timeSlider.getMinimum()); } running = !running; if (running) { timer.setDelay(GuiSettings.timerSpeed); timer.start(); playBtn.setIcon( new ImageIcon(Import.loadImage(ImagePreference.Button.TimePanel.Pause, 30, 30))); } else { timer.stop(); playBtn.setIcon( new ImageIcon(Import.loadImage(ImagePreference.Button.TimePanel.Play, 30, 30))); } } @Override public void actionPerformed(ActionEvent arg0) { updateIterationsInput(); } /** * Update the Text field and apply valid changes to update Iterations from to model. *

* Executed by user input. */ private void updateIterationsInput() { try { int iterations = Integer.parseInt(iterationsField.getText()); // iterationsLblHint.reset(); boolean resetField = true; if (iterations < 1) { iterations = 1; } else if (iterations > MAX_ITERATIONS) { iterations = MAX_ITERATIONS; } else { resetField = false; } if (resetField) { iterationsField.setText("" + iterations); } control.getModel().setIterations(Integer.parseInt(iterationsField.getText())); timeSlider.setMaximum(control.getModel().getMaxIterations() - 1); timeSlider.setLabelTable(null);// Otherwise the ticks won't update timeSlider.setMajorTickSpacing((int) Math.ceil(((double) iterations) / 20)); timeSlider.setMinorTickSpacing( (int) Math.ceil(((double) iterations) / 100));// Even though the final mark // can't actually be // reached. } catch (NumberFormatException ignored) { } } }