package org.geogebra.desktop.export; import java.awt.BorderLayout; import java.awt.Container; import java.awt.FlowLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.image.BufferedImage; import java.io.File; import java.util.Iterator; import java.util.TreeSet; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.DefaultComboBoxModel; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JDialog; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JTextField; import org.geogebra.common.euclidian3D.EuclidianView3DInterface; import org.geogebra.common.kernel.Kernel; import org.geogebra.common.kernel.geos.AnimationExportSlider; import org.geogebra.common.kernel.geos.GeoElement; import org.geogebra.common.kernel.geos.GeoNumeric; import org.geogebra.common.util.FileExtensions; import org.geogebra.desktop.gui.GuiManagerD; import org.geogebra.desktop.gui.util.AnimatedGifEncoder; import org.geogebra.desktop.main.AppD; import org.geogebra.desktop.main.LocalizationD; import org.geogebra.desktop.util.FrameCollector; /** * Dialog to export a slider as animation. * * TODO What happens with the slider context menu entry */ public class AnimationExportDialogD extends JDialog { /** */ private static final long serialVersionUID = 1L; /** * Application instance. */ private AppD app; /** * List with all sliders in the worksheet. */ private JComboBox cbSliders; /** * Loop in Animation? */ private JCheckBox cbLoop; /** * Time between two frames. */ private JTextField tfTimeBetweenFrames; /** * Buttons to close the dialog or start the actual export. */ private JButton cancelButton, exportButton; private LocalizationD loc; /** * Construct dialog. * * @param app * App instance */ public AnimationExportDialogD(AppD app) { super(app.getFrame(), false); this.app = app; this.loc = app.getLocalization(); initGUI(); } /** * Initialize the GUI. */ private void initGUI() { setResizable(false); Container contentPane = getContentPane(); contentPane.setLayout(new BorderLayout()); // slider selection JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEFT)); panel.add(new JLabel(loc.getMenu("Slider") + ":")); // combo box with all sliders DefaultComboBoxModel comboModel = new DefaultComboBoxModel(); TreeSet<GeoElement> sortedSet = app.getKernel().getConstruction() .getGeoSetNameDescriptionOrder(); // add rotation around Oz slider if 3D view if (app.getActiveEuclidianView().isEuclidianView3D()) { addRotOzSlider(comboModel); } // lists for combo boxes to select input and output objects // fill combobox models Iterator<GeoElement> it = sortedSet.iterator(); while (it.hasNext()) { GeoElement geo = it.next(); if (geo.isGeoNumeric() && ((GeoNumeric) geo).isIntervalMinActive() && ((GeoNumeric) geo).isIntervalMaxActive()) { comboModel.addElement(geo); } } cbSliders = new JComboBox(comboModel); panel.add(cbSliders); contentPane.add(panel, BorderLayout.NORTH); // options panel = new JPanel(new FlowLayout(FlowLayout.LEFT)); panel.setBorder( BorderFactory.createTitledBorder(loc.getMenu("Options"))); panel.add(new JLabel(loc.getMenu("TimeBetweenFrames") + ":")); tfTimeBetweenFrames = new JTextField(5); tfTimeBetweenFrames.setText("500"); panel.add(tfTimeBetweenFrames); panel.add(new JLabel("ms")); panel.add(Box.createHorizontalStrut(10)); cbLoop = new JCheckBox(loc.getMenu("AnimationLoop")); panel.add(cbLoop); contentPane.add(panel, BorderLayout.CENTER); // buttons panel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); exportButton = new JButton(loc.getMenu("Export")); exportButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { export(); } }); // disable controls if there are no sliders if (comboModel.getSize() == 0) { cbLoop.setEnabled(false); tfTimeBetweenFrames.setEnabled(false); exportButton.setEnabled(false); // leave cbSliders active to let the user see that there are no // sliders } cancelButton = new JButton(loc.getMenu("Cancel")); cancelButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { setVisible(false); } }); panel.add(exportButton); panel.add(cancelButton); contentPane.add(panel, BorderLayout.SOUTH); setTitle(loc.getMenu("AnimatedGIFExport")); pack(); setLocationRelativeTo(app.getMainComponent()); setVisible(true); } private RotOzSlider rotOzSlider; private static class RotOzSlider implements AnimationExportSlider { private String description; private EuclidianView3DInterface view3D; private double value; // starts with Ox on the right static final private double min = Math.PI / 2; // ends 2pi later static final private double max = min + 2 * Math.PI; // 1 degree step ) static final private double step = Math.PI / 180; public RotOzSlider(EuclidianView3DInterface view3D) { this.view3D = view3D; } /** * set description displayed in combo box * * @param description * description */ public void setDescription(String description) { this.description = description; } @Override public String toString() { return description; } @Override public void updateRepaint() { // use -value for anti-clockwise view3D.setRotAnimation(-value, false, false); view3D.repaintView(); } @Override public double getIntervalMin() { return min; } @Override public double getIntervalMax() { return max; } @Override public double getAnimationStep() { return step; } @Override public int getAnimationType() { return GeoElement.ANIMATION_INCREASING; } @Override public void setValue(double x) { value = x; } } private void addRotOzSlider(DefaultComboBoxModel comboModel) { if (rotOzSlider == null) { rotOzSlider = new RotOzSlider(app.getEuclidianView3D()); } rotOzSlider.setDescription( app.getLocalization().getMenu("RotationAroundVerticalAxis")); comboModel.addElement(rotOzSlider); } /** * Logic for exporting the selected slider as animation. */ public void export() { int timeBetweenFrames = 500; // try to parse textfield value (and check that it is > 0) try { timeBetweenFrames = Integer.parseInt(tfTimeBetweenFrames.getText()); // negative values or zero are bad too if (timeBetweenFrames <= 0) { throw new NumberFormatException(); } } catch (NumberFormatException e) { app.showError("InvalidInput", tfTimeBetweenFrames.getText()); return; } app.getKernel().getAnimatonManager().stopAnimation(); File file = ((GuiManagerD) app.getGuiManager()).showSaveDialog( FileExtensions.GIF, null, loc.getMenu("gif") + " " + loc.getMenu("Files"), true, false); AnimationExportSlider num = (AnimationExportSlider) cbSliders .getSelectedItem(); int type = num.getAnimationType(); double min = num.getIntervalMin(); double max = num.getIntervalMax(); double val; double step; int n; switch (type) { case GeoElement.ANIMATION_DECREASING: step = -num.getAnimationStep(); n = (int) ((max - min) / -step); if (Kernel.isZero(((max - min) / -step) - n)) { n++; } if (n == 0) { n = 1; } val = max; break; case GeoElement.ANIMATION_OSCILLATING: step = num.getAnimationStep(); n = (int) ((max - min) / step) * 2; if (Kernel.isZero(((max - min) / step * 2) - n)) { n++; } if (n == 0) { n = 1; } val = min; break; default: // GeoElement.ANIMATION_INCREASING: // GeoElement.ANIMATION_INCREASING_ONCE: step = num.getAnimationStep(); n = (int) ((max - min) / step); if (Kernel.isZero(((max - min) / step) - n)) { n++; } if (n == 0) { n = 1; } val = min; } final AnimatedGifEncoder gifEncoder = new AnimatedGifEncoder(); gifEncoder.setQuality(1); gifEncoder.start(file); gifEncoder.setDelay(timeBetweenFrames); // miliseconds if (cbLoop.isSelected()) { gifEncoder.setRepeat(0); } FrameCollector collector = new FrameCollector() { @Override public void addFrame(BufferedImage img) { gifEncoder.addFrame(img); } @Override public void finish() { gifEncoder.finish(); } }; // hide dialog setVisible(false); app.setWaitCursor(); try { app.exportAnimatedGIF(app.getActiveEuclidianView(), collector, num, n, val, min, max, step); } catch (Exception ex) { app.showError("SaveFileFailed"); ex.printStackTrace(); } finally { app.setDefaultCursor(); } } }