/* * Copyright 2017 Laszlo Balazs-Csiki * * This file is part of Pixelitor. Pixelitor is free software: you * can redistribute it and/or modify it under the terms of the GNU * General Public License, version 3 as published by the Free * Software Foundation. * * Pixelitor is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Pixelitor. If not, see <http://www.gnu.org/licenses/>. */ package pixelitor.filters.animation; import org.jdesktop.swingx.combobox.EnumComboBoxModel; import pixelitor.gui.utils.BrowseFilesSupport; import pixelitor.gui.utils.GridBagHelper; import pixelitor.gui.utils.TFValidationLayerUI; import pixelitor.gui.utils.TextFieldValidator; import pixelitor.gui.utils.ValidatedForm; import pixelitor.io.Directories; import pixelitor.utils.Messages; import javax.swing.*; import javax.swing.plaf.LayerUI; import java.awt.FlowLayout; import java.awt.GridBagLayout; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.io.File; import static pixelitor.gui.utils.BrowseFilesSupport.SelectionMode.DIRECTORY; import static pixelitor.gui.utils.BrowseFilesSupport.SelectionMode.FILE; /** * The settings for the tweening animation output */ public class OutputSettingsPanel extends ValidatedForm implements TextFieldValidator { private final JTextField nrSecondsTF = new JTextField("2", 3); private final JTextField fpsTF = new JTextField("24", 3); private int nrFrames; private JLabel nrFramesLabel; private double fps; private JComboBox<Interpolation> ipCB; private JComboBox<TweenOutputType> outputTypeCB; private final JCheckBox pingPongCB = new JCheckBox(); private final BrowseFilesSupport browseFilesSupport = new BrowseFilesSupport(Directories.getLastSaveDir().getAbsolutePath()); private JTextField fileNameTF; private String errorMessage; public OutputSettingsPanel() { super(new GridBagLayout()); // A single TFValidationLayerUI for all the textfields. LayerUI<JTextField> tfLayerUI = new TFValidationLayerUI(this); GridBagHelper gbHelper = new GridBagHelper(this); addOutputTypeSelector(gbHelper); addAnimationLengthSelector(tfLayerUI, gbHelper); addInterpolationSelector(gbHelper); addPingPongSelector(gbHelper); addFileSelector(tfLayerUI, gbHelper); } private void addOutputTypeSelector(GridBagHelper gbHelper) { //noinspection unchecked EnumComboBoxModel<TweenOutputType> model = new EnumComboBoxModel(TweenOutputType.class); outputTypeCB = new JComboBox<>(model); outputTypeCB.addActionListener(e -> outputTypeChanged()); outputTypeChanged(); // initial setup gbHelper.addLabelWithControl("Output Type:", outputTypeCB); } private void addAnimationLengthSelector(LayerUI<JTextField> tfLayerUI, GridBagHelper gbHelper) { gbHelper.addLabelWithControl("Number of Seconds:", new JLayer<>(nrSecondsTF, tfLayerUI)); KeyListener keyListener = new KeyAdapter() { @Override public void keyReleased(KeyEvent keyEvent) { updateCalculations(); } }; nrSecondsTF.addKeyListener(keyListener); gbHelper.addLabelWithControl("Frames per Second:", new JLayer<>(fpsTF, tfLayerUI)); fpsTF.addKeyListener(keyListener); nrFramesLabel = new JLabel(); updateCalculations(); gbHelper.addLabelWithControl("Number of Frames:", nrFramesLabel); } private void addInterpolationSelector(GridBagHelper gbHelper) { EnumComboBoxModel<Interpolation> ipCBM = new EnumComboBoxModel<>(Interpolation.class); ipCB = new JComboBox<>(ipCBM); gbHelper.addLabelWithControl("Interpolation:", ipCB); } private void addPingPongSelector(GridBagHelper gbHelper) { gbHelper.addLabelWithControl("Ping Pong:", pingPongCB); pingPongCB.addActionListener(e -> updateCalculations()); } private void addFileSelector(LayerUI<JTextField> tfLayerUI, GridBagHelper gbHelper) { JPanel filePanel = new JPanel(new FlowLayout()); filePanel.setBorder(BorderFactory.createTitledBorder("Output File/Folder")); fileNameTF = browseFilesSupport.getNameTF(); filePanel.add(new JLayer<>(fileNameTF, tfLayerUI)); filePanel.add(browseFilesSupport.getBrowseButton()); gbHelper.addOnlyControlToRow(filePanel, 6); } private void outputTypeChanged() { TweenOutputType selected = (TweenOutputType) outputTypeCB.getSelectedItem(); if(selected.needsDirectory()) { browseFilesSupport.setSelectionMode(DIRECTORY); browseFilesSupport.setDialogTitle("Select Output Folder"); } else { browseFilesSupport.setSelectionMode(FILE); browseFilesSupport.setDialogTitle("Select Output File"); browseFilesSupport.setFileFilter(selected.getFileFilter()); } if (fileNameTF != null) { // not the initial setup fileNameTF.repaint(); } } private void updateCalculations() { try { nrFramesLabel.setText(calculateNrFramesText()); } catch (NumberFormatException e) { // expected behaviour, we can swallow the exception nrFramesLabel.setText("??"); } catch (Exception e) { Messages.showException(e); nrFramesLabel.setText("??"); } } private String calculateNrFramesText() { double nrSeconds = Double.parseDouble(nrSecondsTF.getText().trim()); fps = Double.parseDouble(fpsTF.getText().trim()); nrFrames = (int) (nrSeconds * fps); String labelText = String.valueOf(nrFrames); if (pingPongCB.isSelected()) { int totalFrames = 2 * nrFrames - 2; double totalSeconds = totalFrames / fps; labelText += String.format(" (with PP: %d frames, %.2f seconds)", totalFrames, totalSeconds); } return labelText; } @Override public boolean isDataValid() { return isValid(nrSecondsTF) && isValid(fpsTF) && isValid(fileNameTF); } @Override public String getErrorMessage() { return errorMessage; } @Override public boolean isValid(JTextField textField) { if (textField == nrSecondsTF || textField == fpsTF) { return isTextFieldWithDoubleValid(textField); } else if (textField == fileNameTF) { TweenOutputType outputType = (TweenOutputType) outputTypeCB.getSelectedItem(); errorMessage = outputType.checkFile(new File(textField.getText().trim())); return (errorMessage == null); } else { throw new IllegalStateException("unexpected JTextField"); } } private boolean isTextFieldWithDoubleValid(JTextField textField) { boolean valid = true; String text = textField.getText().trim(); try { //noinspection ResultOfMethodCallIgnored Double.parseDouble(text); } catch (NumberFormatException ex) { valid = false; errorMessage = text + " is not a valid number."; } return valid; } public void copySettingsInto(TweenAnimation animation) { TweenOutputType type = (TweenOutputType) outputTypeCB.getSelectedItem(); animation.setOutputType(type); File output = browseFilesSupport.getSelectedFile(); animation.setOutput(output); animation.setNumFrames(nrFrames); animation.setMillisBetweenFrames((int) (1000.0 / fps)); animation.setInterpolation((Interpolation) ipCB.getSelectedItem()); animation.setPingPong(pingPongCB.isSelected()); if (output.isDirectory()) { Directories.setLastSaveDir(output); } else { Directories.setLastSaveDir(output.getParentFile()); } } }