// Near Infinity - An Infinity Engine Browser and Editor // Copyright (C) 2001 - 2005 Jon Olav Hauglid // See LICENSE.txt for license information package org.infinity.gui.converter; import java.awt.AlphaComposite; import java.awt.BasicStroke; import java.awt.BorderLayout; import java.awt.CardLayout; import java.awt.Color; import java.awt.Component; import java.awt.Dimension; import java.awt.Graphics2D; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Image; import java.awt.Insets; import java.awt.Point; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; import java.awt.event.KeyEvent; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.image.BufferedImage; import java.awt.image.DataBuffer; import java.awt.image.DataBufferByte; import java.awt.image.DataBufferInt; import java.awt.image.IndexColorModel; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.BufferedWriter; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; import java.util.BitSet; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Vector; import javax.imageio.ImageIO; import javax.imageio.ImageReader; import javax.swing.AbstractAction; import javax.swing.AbstractListModel; import javax.swing.BorderFactory; import javax.swing.DefaultListCellRenderer; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JComponent; import javax.swing.JDialog; import javax.swing.JFileChooser; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JSpinner; import javax.swing.JTabbedPane; import javax.swing.JTextArea; import javax.swing.JTextField; import javax.swing.KeyStroke; import javax.swing.ListSelectionModel; import javax.swing.ProgressMonitor; import javax.swing.ScrollPaneConstants; import javax.swing.SpinnerNumberModel; import javax.swing.SwingConstants; import javax.swing.SwingWorker; import javax.swing.Timer; import javax.swing.UIManager; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import javax.swing.filechooser.FileNameExtensionFilter; import org.infinity.NearInfinity; import org.infinity.gui.ButtonPopupMenu; import org.infinity.gui.ChildFrame; import org.infinity.gui.FixedFocusTraversalPolicy; import org.infinity.gui.OpenResourceDialog; import org.infinity.gui.RenderCanvas; import org.infinity.gui.ViewerUtil; import org.infinity.gui.WindowBlocker; import org.infinity.icon.Icons; import org.infinity.resource.Profile; import org.infinity.resource.ResourceFactory; import org.infinity.resource.graphics.BamDecoder; import org.infinity.resource.graphics.BamV1Decoder; import org.infinity.resource.graphics.ColorConvert; import org.infinity.resource.graphics.DxtEncoder; import org.infinity.resource.graphics.PseudoBamDecoder; import org.infinity.resource.graphics.BamDecoder.BamControl; import org.infinity.resource.graphics.PseudoBamDecoder.PseudoBamControl; import org.infinity.resource.graphics.PseudoBamDecoder.PseudoBamCycleEntry; import org.infinity.resource.graphics.PseudoBamDecoder.PseudoBamFrameEntry; import org.infinity.resource.key.FileResourceEntry; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.IniMap; import org.infinity.util.IniMapEntry; import org.infinity.util.IniMapSection; import org.infinity.util.Misc; import org.infinity.util.Pair; import org.infinity.util.SimpleListModel; import org.infinity.util.io.FileManager; import org.infinity.util.io.StreamUtils; public class ConvertToBam extends ChildFrame implements ActionListener, PropertyChangeListener, FocusListener, ChangeListener, ListSelectionListener, MouseListener { // Available TabPane indices static final int TAB_FRAMES = 0; static final int TAB_CYCLES = 1; static final int TAB_PREVIEW = 2; static final int TAB_FILTERS = 3; static final String[] TabNames = {"Frames", "Cycles", "Preview", "Post-processing"}; // Available DXT compression types for BAM v2 export static final int COMPRESSION_AUTO = 0; static final int COMPRESSION_DXT1 = 1; static final int COMPRESSION_DXT5 = 2; static final String[] CompressionItems = {"Auto", "DXT1", "DXT5"}; // Available BAM versions static final int VERSION_BAMV1 = 0; static final int VERSION_BAMV2 = 1; static final String[] BamVersionItems = {"Legacy (v1)", "PVRZ-based (v2)"}; // Available playback modes for preview static final int MODE_CURRENT_CYCLE_ONCE = 0; static final int MODE_CURRENT_CYCLE_LOOPED = 1; static final int MODE_ALL_CYCLES_ONCE = 2; static final int MODE_ALL_CYCLES_LOOPED = 3; static final String[] PlaybackModeItems = {"Current cycle once", "Current cycle looped", "All cycles once", "All cycles looped"}; // PseudoBamDecoder->setOption(): Full path to frame source static final String BAM_FRAME_OPTION_PATH = "Path"; // PseudoBamDecoder->setOption(): frame source index (usually 0, except for BAM sources) static final String BAM_FRAME_OPTION_SOURCE_INDEX = "FrameIndex"; // Used as prefix if BAM source file is based on a biffed resource static final String BAM_FRAME_PATH_BIFF = "BIFF:/"; // Available lists of frame images static final int BAM_ORIGINAL = 0; // the original unprocessed frames list static final int BAM_FINAL = 1; // final frames list including palette and/or post-processor private static Path currentPath; // Global BamDecoder instance for managing frames and cycles private final PseudoBamDecoder bamDecoder = new PseudoBamDecoder(); // BamDecoder instance containing the final result of the current BAM structure private final PseudoBamDecoder bamDecoderFinal = new PseudoBamDecoder(); // Frame image lists (use FRAMELIST_XXX constants for access) private final List<List<PseudoBamFrameEntry>> listFrameEntries = new ArrayList<List<PseudoBamFrameEntry>>(2); // Frame entry used for preview in filter tab private final PseudoBamFrameEntry entryFilterPreview = new PseudoBamFrameEntry(null, 0, 0); // The palette dialog instance for BAM v1 export private final BamPaletteDialog paletteDialog = new BamPaletteDialog(this); private JTabbedPane tpMain, tpCyclesSection; private BamFramesListModel modelFrames; // Frames == FramesAvail private BamCycleFramesListModel modelCurCycle; private BamCyclesListModel modelCycles; private SimpleListModel<BamFilterBase> modelFilters; private JList<PseudoBamFrameEntry> listFrames, listFramesAvail, listCurCycle; private JList<PseudoBamCycleEntry> listCycles; private JList<BamFilterBase> listFilters; private JMenuItem miFramesAddFiles, miFramesAddResources, miFramesAddFolder, miFramesImportFile, miFramesImportResource, miFramesRemove, miFramesRemoveAll, miFramesDropUnused, miSessionExport, miSessionImport; private ButtonPopupMenu bpmFramesAdd, bpmFramesRemove, bpmSession; private JButton bOptions, bConvert, bCancel, bPalette, bVersionHelp, bCompressionHelp; private JButton bFramesUp, bFramesDown; private JButton bCyclesUp, bCyclesDown, bCyclesAdd, bCyclesRemove, bCyclesRemoveAll, bCurCycleUp, bCurCycleDown, bCurCycleAdd, bCurCycleRemove; private JButton bMacroAssignFrames, bMacroRemoveFrames, bMacroSortFramesAsc, bMacroDuplicateCycle, bMacroDuplicateFrames, bMacroReverseFrames, bMacroRemoveAll, bMacroReverseCycles; private JButton bPreviewCyclePrev, bPreviewCycleNext, bPreviewFramePrev, bPreviewFrameNext, bPreviewPlay, bPreviewStop; private JButton bFiltersAdd, bFiltersRemove, bFiltersRemoveAll, bFiltersUp, bFiltersDown; private JTextField tfFrameWidth, tfFrameHeight, tfFrameCenterX, tfFrameCenterY; private JCheckBox cbCloseOnExit, cbCompressFrame, cbCompressBam, cbPreviewShowMarker, cbPreviewZoom, cbFiltersShowMarker; private JPanel pFramesCurFrame, pCurrentCycle, pFramesOptionsVersion, pFiltersSettings; private JComboBox<String> cbPreviewMode; private JComboBox<BamFilterFactory.FilterInfo> cbFiltersAdd; private JComboBox<String> cbVersion, cbCompression; private JTextArea taFiltersDesc; private RenderCanvas rcFramesPreview, rcCyclesPreview, rcPreview, rcFiltersPreview; private JScrollPane scrollPreview, scrollFiltersPreview; private BufferedImage previewCanvas; private JLabel lPreviewCycle, lPreviewFrame; private JSpinner sPvrzIndex, sPreviewFps, sFiltersPreviewFrame; private Timer timer; private SwingWorker<List<String>, Void> workerConvert; private SwingWorker<Void, Void> workerProcess; private WindowBlocker blocker; private ProgressMonitor progress; private PseudoBamControl bamControlPreview; private boolean isPreviewModified, isPreviewPlaying; private double currentFps; private int pmCur, pmMax; private Path bamOutputFile; /** Validates numberString and modifies it to fit into the specified limits. */ public static int numberValidator(String numberString, int min, int max, int defaultValue) { int retVal = defaultValue; if (numberString != null) { try { retVal = Integer.parseInt(numberString); if (min > max) { int tmp = min; min = max; max = tmp; } if (retVal < min) retVal = min; else if (retVal > max) retVal = max; } catch (NumberFormatException e) { } } return retVal; } /** Validates numberString and modifies it to fit into the specified limits. */ public static double doubleValidator(String numberString, double min, double max, double defaultValue) { double retVal = defaultValue; if (numberString != null) { try { retVal = Double.parseDouble(numberString); if (min > max) { double tmp = min; min = max; max = tmp; } if (retVal < min) retVal = min; else if (retVal > max) retVal = max; } catch (NumberFormatException e) { } } return retVal; } /** Returns a list of supported input graphics file formats. */ public static FileNameExtensionFilter[] getGraphicsFilters() { FileNameExtensionFilter[] filters = new FileNameExtensionFilter[] { new FileNameExtensionFilter("Graphics files (*.bam, *.bmp, *.gif, *.png, *,jpg, *.jpeg)", "bam", "bmp", "gif", "png", "jpg", "jpeg"), new FileNameExtensionFilter("BAM files (*.bam)", "bam"), new FileNameExtensionFilter("BMP files (*.bmp)", "bmp"), new FileNameExtensionFilter("GIF files (*.gif)", "gif"), new FileNameExtensionFilter("PNG files (*.png)", "png"), new FileNameExtensionFilter("JPEG files (*.jpg, *.jpeg)", "jpg", "jpeg") }; return filters; } /** Returns a list of supported input file formats containing palettes. */ public static FileNameExtensionFilter[] getPaletteFilters() { FileNameExtensionFilter[] filters = new FileNameExtensionFilter[] { new FileNameExtensionFilter("Palette from files (*.bam, *.bmp, *.act, *.pal)", "bam", "bmp", "act", "pal"), new FileNameExtensionFilter("Palette from BAM files (*.bam)", "bam"), new FileNameExtensionFilter("Palette from BMP files (*.bmp)", "bmp"), new FileNameExtensionFilter("Adobe Color Table files (*.act)", "act"), new FileNameExtensionFilter("Microsoft Palette files (*.pal)", "pal"), }; return filters; } /** Returns a extension filter for BAM files. */ public static FileNameExtensionFilter getBamFilter() { return new FileNameExtensionFilter("BAM files (*.bam)", "bam"); } /** Returns a list of files that can be specified in an "Open file" dialog. */ public static Path[] getOpenFileName(Component parent, String title, Path rootPath, boolean selectMultiple, FileNameExtensionFilter[] filters, int filterIndex) { if (rootPath == null) { rootPath = currentPath; } JFileChooser fc = new JFileChooser(rootPath.toFile()); if (!Files.isDirectory(rootPath)) { fc.setSelectedFile(rootPath.toFile()); } if (title == null) { title = selectMultiple ? "Select file(s)" : "Select file"; } fc.setDialogTitle(title); fc.setDialogType(JFileChooser.OPEN_DIALOG); fc.setFileSelectionMode(JFileChooser.FILES_ONLY); fc.setMultiSelectionEnabled(selectMultiple); if (filters != null) { for (final FileNameExtensionFilter filter: filters) { fc.addChoosableFileFilter(filter); } if (filterIndex >= 0 && filterIndex < filters.length) { fc.setFileFilter(filters[filterIndex]); } } if (fc.showOpenDialog(parent) == JFileChooser.APPROVE_OPTION) { if (selectMultiple) { if (fc.getSelectedFiles().length > 0) { currentPath = fc.getSelectedFiles()[0].toPath().getParent(); } File[] files = fc.getSelectedFiles(); Path[] paths = new Path[files.length]; for (int i = 0; i < files.length; i++) { paths[i] = files[i].toPath(); } return paths; } else { currentPath = fc.getSelectedFile().toPath().getParent(); return new Path[]{fc.getSelectedFile().toPath()}; } } else { return null; } } /** Returns a path name from a "Select path" dialog. */ public static Path getOpenPathName(Component parent, String title, Path rootPath) { if (rootPath == null) { rootPath = currentPath; } JFileChooser fc = new JFileChooser(rootPath.toFile()); if (title == null) { title = "Select folder"; } fc.setDialogTitle(title); fc.setDialogType(JFileChooser.OPEN_DIALOG); fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); if (fc.showOpenDialog(parent) == JFileChooser.APPROVE_OPTION) { currentPath = fc.getSelectedFile().toPath(); return currentPath; } else { return null; } } /** Returns a filename that can be specified in a "Save file" dialog */ public static Path getSaveFileName(Component parent, String title, Path rootPath, FileNameExtensionFilter[] filters, int filterIndex) { if (rootPath == null) { rootPath = currentPath; } JFileChooser fc = new JFileChooser(rootPath.toFile()); if (!Files.isDirectory(rootPath)) { fc.setSelectedFile(rootPath.toFile()); } if (title == null) { title = "Specify filename"; } fc.setDialogTitle(title); fc.setDialogType(JFileChooser.SAVE_DIALOG); fc.setFileSelectionMode(JFileChooser.FILES_ONLY); if (filters != null) { for (final FileNameExtensionFilter filter: filters) { fc.addChoosableFileFilter(filter); } if (filterIndex >= 0 && filterIndex < filters.length) { fc.setFileFilter(filters[filterIndex]); } } if (fc.showSaveDialog(parent) == JFileChooser.APPROVE_OPTION) { currentPath = fc.getSelectedFile().toPath().getParent(); return fc.getSelectedFile().toPath(); } else { return null; } } public ConvertToBam() { this(null); } public ConvertToBam(ResourceEntry entry) { super("Convert image sequence to BAM", true); init(); framesImportBam(entry); } /** * Returns the BAM decoder instance that is used internally to manage the BAM structure. * @param bamType Either one of {@link #BAM_ORIGINAL} or {@link #BAM_FINAL}. */ public PseudoBamDecoder getBamDecoder(int bamType) { return (bamType == BAM_FINAL) ? bamDecoderFinal : bamDecoder; } /** Returns the BAM decoder instance that is used to display the BAM in the preview tab. */ public PseudoBamDecoder getPreviewBamDecoder() { return bamDecoderFinal; } /** Returns the associated BAM v1 palette dialog. */ public BamPaletteDialog getPaletteDialog() { return paletteDialog; } /** Convenience method: Returns true if BAM v1 has been selected. */ public boolean isBamV1Selected() { return (cbVersion.getSelectedIndex() == VERSION_BAMV1); } /** Returns the currently selected output BAM version. */ public int getBamVersion() { return cbVersion.getSelectedIndex(); } /** Returns the BAM output file. */ public Path getBamOutput() { return bamOutputFile; } /** Returns whether BAM v1 output is compressed. */ public boolean isBamV1Compressed() { return cbCompressBam.isSelected(); } /** Returns the threshold used to determine transparent colors. Range: [0, 255]. */ public int getTransparencyThreshold() { return (255*BamOptionsDialog.getTransparencyThreshold()) / 100; } /** Returns the start index for PVRZ files used for BAM v2 output. */ public int getPvrzIndex() { return ((Integer)sPvrzIndex.getValue()).intValue(); } /** Returns the selected DXT type for BAM v2. */ public DxtEncoder.DxtType getDxtType() { switch (cbCompression.getSelectedIndex()) { case COMPRESSION_DXT1: return DxtEncoder.DxtType.DXT1; case COMPRESSION_DXT5: return DxtEncoder.DxtType.DXT5; default: return getAutoDxtType(); } } //--------------------- Begin Class ChildFrame --------------------- @Override protected boolean windowClosing(boolean forced) throws Exception { if (forced || confirmCloseDialog()) { clear(); return true; } else { return false; } } //--------------------- End Class ChildFrame --------------------- //--------------------- Begin Interface ActionListener --------------------- @Override public void actionPerformed(ActionEvent event) { if (event.getSource() == timer) { if (!previewAdvanceAnimation()) { previewStop(); } } else if (event.getSource() == bOptions) { new BamOptionsDialog(this); } else if (event.getSource() == bConvert) { if (workerConvert == null) { final String msg = "BAM output file already exists. Overwrite?"; Path file = null; do { file = setBamOutput(); if (file != null) { if (!Files.exists(file) || JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog(this, msg, "Question", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE)) { file = null; workerConvert = new SwingWorker<List<String>, Void>() { @Override protected List<String> doInBackground() throws Exception { return convert(); } }; initProgressMonitor(this, "Converting BAM...", " ", 3, 0, 0); workerConvert.addPropertyChangeListener(this); blocker = new WindowBlocker(this); blocker.setBlocked(true); workerConvert.execute(); } file = null; } } while (file != null); } } else if (event.getSource() == bCancel) { hideWindow(false); } else if (event.getSource() == bFramesUp) { framesMoveUp(); } else if (event.getSource() == bFramesDown) { framesMoveDown(); } else if (event.getSource() == miFramesAddFiles) { framesAddFiles(); } else if (event.getSource() == miFramesAddResources) { framesAddResources(); } else if (event.getSource() == miFramesImportFile) { framesImportBamFile(); } else if (event.getSource() == miFramesImportResource) { framesImportBamResource(); } else if (event.getSource() == miFramesAddFolder) { framesAddFolder(); } else if (event.getSource() == miFramesRemove) { framesRemove(); } else if (event.getSource() == miFramesRemoveAll) { if (!modelFrames.isEmpty()) { final String msg = "Do you really want to remove all frames?"; if (JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog(this, msg, "Question", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE)) { framesRemoveAll(); } } } else if (event.getSource() == miFramesDropUnused) { int count = framesGetUnusedFramesCount(); if (count > 0) { String msg; if (count == 1) { msg = "1 unused frame can be dropped. Do you want to continue?"; } else { msg = String.format("%1$d unused frames can be dropped. Do you want to continue?", count); } int retVal = JOptionPane.showConfirmDialog(this, msg, "Question", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); if (retVal == JOptionPane.YES_OPTION) { framesDropUnusedFrames(); } } } else if (event.getSource() == miSessionExport) { Exporter exporter = new Exporter(this); try { exporter.exportData(false); } finally { exporter.close(); } } else if (event.getSource() == miSessionImport) { Exporter importer = new Exporter(this); try { if (importer.importData(false)) { if (importer.isFramesSelected() || importer.isCenterSelected()) { updateFramesList(); } if (importer.isCyclesSelected()) { updateCyclesList(); } if (importer.isFiltersSelected()) { updateFilterList(); } } } finally { importer.close(); } } else if (event.getSource() == cbCompressFrame) { framesUpdateCompressFrame(); } else if (event.getSource() == cbCompressBam) { updateCompressBam(); } else if (event.getSource() == bPalette) { paletteDialog.open(); outputSetModified(true); } else if (event.getSource() == bCompressionHelp) { showCompressionHelp(); } else if (event.getSource() == cbVersion) { setBamVersion(cbVersion.getSelectedIndex()); } else if (event.getSource() == bVersionHelp) { showVersionHelp(); } else if (event.getSource() == bCyclesAdd) { cyclesAdd(); } else if (event.getSource() == bCyclesRemove) { cyclesRemove(); } else if (event.getSource() == bCyclesRemoveAll) { if (!modelCycles.isEmpty()) { final String msg = "Do you really want to remove all cycles?"; if (JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog(this, msg, "Question", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE)) { cyclesRemoveAll(); } } } else if (event.getSource() == bCyclesUp) { cyclesMoveUp(); } else if (event.getSource() == bCyclesDown) { cyclesMoveDown(); } else if (event.getSource() == bMacroAssignFrames) { macroAssignFrames(); } else if (event.getSource() == bMacroDuplicateCycle) { macroDuplicateCycle(); } else if (event.getSource() == bMacroDuplicateFrames) { macroDuplicateFrames(); } else if (event.getSource() == bMacroRemoveAll) { macroRemoveAllFramesGlobal(); } else if (event.getSource() == bMacroRemoveFrames) { macroRemoveAllFrames(); } else if (event.getSource() == bMacroReverseCycles) { macroReverseCyclesOrder(); } else if (event.getSource() == bMacroReverseFrames) { macroReverseFramesOrder(); } else if (event.getSource() == bMacroSortFramesAsc) { macroSortFrames(); } else if (event.getSource() == bCurCycleAdd) { currentCycleAdd(); } else if (event.getSource() == bCurCycleRemove) { currentCycleRemove(); } else if (event.getSource() == bCurCycleUp) { currentCycleMoveUp(); } else if (event.getSource() == bCurCycleDown) { currentCycleMoveDown(); } else if (event.getSource() == bPreviewCyclePrev) { previewCycleDown(); } else if (event.getSource() == bPreviewCycleNext) { previewCycleUp(); } else if (event.getSource() == bPreviewFramePrev) { previewFrameDown(); } else if (event.getSource() == bPreviewFrameNext) { previewFrameUp(); } else if (event.getSource() == bPreviewPlay) { previewPlay(); } else if (event.getSource() == bPreviewStop) { previewStop(); } else if (event.getSource() == cbPreviewMode) { previewSetMode(cbPreviewMode.getSelectedIndex()); } else if (event.getSource() == cbPreviewZoom) { previewSetZoom(cbPreviewZoom.isSelected()); } else if (event.getSource() == cbPreviewShowMarker) { previewSetMarkerVisible(cbPreviewShowMarker.isSelected()); } else if (event.getSource() == bFiltersAdd) { filterAdd(); } else if (event.getSource() == bFiltersRemove) { filterRemove(); } else if (event.getSource() == bFiltersRemoveAll) { if (!modelFilters.isEmpty()) { final String msg = "Do you really want to remove all filters?"; if (JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog(this, msg, "Question", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE)) { filterRemoveAll(); } } } else if (event.getSource() == bFiltersUp) { filterMoveUp(); } else if (event.getSource() == bFiltersDown) { filterMoveDown(); } else if (event.getSource() == cbFiltersShowMarker) { filterSetPreviewFrame(filterGetPreviewFrameIndex(), false); } } //--------------------- End Interface ActionListener --------------------- //--------------------- Begin Interface PropertyChangeListener --------------------- @Override public void propertyChange(PropertyChangeEvent event) { if (event.getSource() == workerConvert) { if ("state".equals(event.getPropertyName()) && SwingWorker.StateValue.DONE == event.getNewValue()) { if (blocker != null) { blocker.setBlocked(false); blocker = null; } if (isProgressMonitorActive()) { releaseProgressMonitor(); } List<String> result = null; try { result = workerConvert.get(); } catch (Exception e) { e.printStackTrace(); } workerConvert = null; boolean isError = false; String s = null; if (result != null && !result.isEmpty()) { if (result.get(0) != null) { s = result.get(0); } else if (result.size() > 1 && result.get(1) != null) { s = result.get(1); isError = true; } } if (s != null) { if (isError) { JOptionPane.showMessageDialog(this, s, "Error", JOptionPane.ERROR_MESSAGE); } else { JOptionPane.showMessageDialog(this, s, "Information", JOptionPane.INFORMATION_MESSAGE); if (cbCloseOnExit.isSelected()) { hideWindow(true); } else if (BamOptionsDialog.getAutoClear()) { clear(); } } } else { JOptionPane.showMessageDialog(this, "Unexpected error!", "Error", JOptionPane.ERROR_MESSAGE); } } } else if (event.getSource() == workerProcess) { if ("state".equals(event.getPropertyName()) && SwingWorker.StateValue.DONE == event.getNewValue()) { if (blocker != null) { blocker.setBlocked(false); blocker = null; } if (isProgressMonitorActive()) { releaseProgressMonitor(); } workerProcess = null; } } } //--------------------- End Interface PropertyChangeListener --------------------- //--------------------- Begin Interface FocusListener --------------------- @Override public void focusGained(FocusEvent event) { if (event.getSource() == tfFrameCenterX || event.getSource() == tfFrameCenterY) { ((JTextField)event.getSource()).selectAll(); } } @Override public void focusLost(FocusEvent event) { if (event.getSource() == tfFrameCenterX || event.getSource() == tfFrameCenterY) { framesValidateCenterValue((JTextField)event.getSource()); } } //--------------------- End Interface FocusListener --------------------- //--------------------- Begin Interface ChangeListener --------------------- @Override public void stateChanged(ChangeEvent event) { if (event.getSource() == tpMain) { bamControlPreview = null; if (tpMain.getSelectedIndex() == TAB_PREVIEW) { if (workerProcess == null) { workerProcess = new SwingWorker<Void, Void>() { @Override protected Void doInBackground() { previewValidate(); return null; } }; initProgressMonitor(this, "Preparing BAM...", null, 1, 0, 0); workerProcess.addPropertyChangeListener(this); blocker = new WindowBlocker(this); blocker.setBlocked(true); workerProcess.execute(); } } else { previewStop(); if (tpMain.getSelectedIndex() == TAB_FILTERS) { WindowBlocker.blockWindow(this, true); try { filterUpdateControls(); filterUpdatePreviewFrameIndex(); filterSetPreviewFrame(filterGetPreviewFrameIndex(), true); } finally { WindowBlocker.blockWindow(this, false); } } } } else if (event.getSource() == sPreviewFps) { double v = (Double)sPreviewFps.getValue(); previewSetFrameRate(v); } else if (event.getSource() == sFiltersPreviewFrame) { filterSetPreviewFrame(filterGetPreviewFrameIndex(), true); } else if (event.getSource() instanceof BamFilterBase) { outputSetModified(true); filterSetPreviewFrame(filterGetPreviewFrameIndex(), false); } } //--------------------- End Interface ChangeListener --------------------- //--------------------- Begin Interface ListSelectionListener --------------------- @Override public void valueChanged(ListSelectionEvent event) { if (event.getSource() == listFrames) { int[] indices = listFrames.getSelectedIndices(); updateFramesList(); updateFrameInfo(indices); updateQuickPreview(rcFramesPreview, indices, true); } else if (event.getSource() == listCycles) { updateCyclesList(); } else if (event.getSource() == listCurCycle) { updateCurrentCycle(); } else if (event.getSource() == listFramesAvail) { int[] indices = listFramesAvail.getSelectedIndices(); updateCurrentCycle(); updateQuickPreview(rcCyclesPreview, indices, false); } else if (event.getSource() == listFilters) { updateFilterList(); } } //--------------------- End Interface ListSelectionListener --------------------- //--------------------- Begin Interface MouseListener --------------------- @Override public void mouseClicked(MouseEvent event) { if (event.getSource() == listFramesAvail && (event.getClickCount() & 1) == 0) { // double click on list element int idx = listFramesAvail.getSelectedIndex(); if (idx >= 0) { Rectangle rect = listFramesAvail.getCellBounds(idx, idx); if (rect != null && rect.contains(event.getX(), event.getY())) { currentCycleAdd(); } } } else if (event.getSource() == listCurCycle && (event.getClickCount() & 1) == 0) { // double click on list element int idx = listCurCycle.getSelectedIndex(); if (idx >= 0) { Rectangle rect = listCurCycle.getCellBounds(idx, idx); if (rect != null && rect.contains(event.getX(), event.getY())) { currentCycleRemove(); } } } } @Override public void mousePressed(MouseEvent event) { } @Override public void mouseReleased(MouseEvent event) { } @Override public void mouseEntered(MouseEvent event) { } @Override public void mouseExited(MouseEvent event) { } //--------------------- End Interface MouseListener --------------------- private void init() { setIconImage(Icons.getImage(Icons.ICON_APPLICATION_16)); BamOptionsDialog.loadSettings(false); if (BamOptionsDialog.getPath().isEmpty()) { currentPath = Profile.getGameRoot(); } else { currentPath = FileManager.resolve(BamOptionsDialog.getPath()); } // initializing frame image lists listFrameEntries.add(bamDecoder.getFramesList()); // original frames list listFrameEntries.add(bamDecoderFinal.getFramesList()); // processed frames list bamDecoderFinal.setCyclesList(bamDecoder.getCyclesList()); // shared cycles list JPanel pFrames = createFramesTab(); JPanel pCycles = createCyclesTab(); JPanel pPreview = createPreviewTab(); JPanel pFilters = createFiltersTab(); // setting up tabbed pane tpMain = new JTabbedPane(JTabbedPane.TOP); tpMain.addTab(TabNames[TAB_FRAMES], pFrames); tpMain.setMnemonicAt(TAB_FRAMES, KeyEvent.VK_F); tpMain.addTab(TabNames[TAB_CYCLES], pCycles); tpMain.setMnemonicAt(TAB_CYCLES, KeyEvent.VK_C); tpMain.addTab(TabNames[TAB_PREVIEW], pPreview); tpMain.setMnemonicAt(TAB_PREVIEW, KeyEvent.VK_P); tpMain.addTab(TabNames[TAB_FILTERS], pFilters); tpMain.setMnemonicAt(TAB_FILTERS, KeyEvent.VK_O); tpMain.addChangeListener(this); // setting up bottom button bar GridBagConstraints c = new GridBagConstraints(); miSessionExport = new JMenuItem("Export session..."); miSessionExport.addActionListener(this); miSessionImport = new JMenuItem("Import session..."); miSessionImport.addActionListener(this); bpmSession = new ButtonPopupMenu("BAM session", new JMenuItem[]{miSessionExport, miSessionImport}); bpmSession.setToolTipText("Export or import BAM frame, cycle or filter definitions."); bpmSession.setIcon(Icons.getIcon(Icons.ICON_ARROW_UP_15)); bpmSession.setIconTextGap(8); Insets i = bpmSession.getInsets(); bpmSession.setMargin(new Insets(i.top + 1, i.left, i.bottom + 1, i.right)); bOptions = new JButton("Options..."); bOptions.addActionListener(this); i = bOptions.getInsets(); bOptions.setMargin(new Insets(i.top + 1, i.left, i.bottom + 1, i.right)); cbCloseOnExit = new JCheckBox("Close dialog after conversion", BamOptionsDialog.getCloseOnExit()); bamOutputFile = null; bConvert = new JButton("Save output file..."); bConvert.addActionListener(this); i = bConvert.getInsets(); bConvert.setMargin(new Insets(i.top + 1, i.left, i.bottom + 1, i.right)); bCancel = new JButton("Cancel"); bCancel.addActionListener(this); i = bCancel.getInsets(); bCancel.setMargin(new Insets(i.top + 1, i.left, i.bottom + 1, i.right)); JPanel pButtons = new JPanel(new GridBagLayout()); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0); pButtons.add(bpmSession, c); c = ViewerUtil.setGBC(c, 1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(0, 6, 0, 0), 0, 0); pButtons.add(bOptions, c); c = ViewerUtil.setGBC(c, 2, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(0, 4, 0, 0), 0, 0); pButtons.add(cbCloseOnExit, c); c = ViewerUtil.setGBC(c, 3, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_END, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0); pButtons.add(bConvert, c); c = ViewerUtil.setGBC(c, 4, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_END, GridBagConstraints.NONE, new Insets(0, 4, 0, 0), 0, 0); pButtons.add(bCancel, c); // putting all sections together setLayout(new GridBagLayout()); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.LINE_START, GridBagConstraints.BOTH, new Insets(8, 8, 0, 8), 0, 0); add(tpMain, c); c = ViewerUtil.setGBC(c, 0, 1, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(8, 8, 8, 8), 0, 0); add(pButtons, c); // finalizing dialog initialization pack(); setMinimumSize(getPreferredSize()); setLocationRelativeTo(getParent()); updateFramesList(); updateFrameInfo(listFrames.getSelectedIndices()); updateQuickPreview(rcFramesPreview, listFrames.getSelectedIndices(), true); updateQuickPreview(rcCyclesPreview, listFramesAvail.getSelectedIndices(), false); initCurrentCycle(listCycles.getSelectedIndex()); updateStatus(); setVisible(true); } // Creating panel "Frames" private JPanel createFramesTab() { GridBagConstraints c = new GridBagConstraints(); // creating "Frames List" JPanel pFramesListArrows = new JPanel(new GridBagLayout()); bFramesUp = new JButton(Icons.getIcon(Icons.ICON_UP_16)); bFramesUp.setMargin(new Insets(bFramesUp.getInsets().top, 2, bFramesUp.getInsets().bottom, 2)); bFramesUp.addActionListener(this); bFramesDown = new JButton(Icons.getIcon(Icons.ICON_DOWN_16)); bFramesDown.setMargin(new Insets(bFramesDown.getInsets().top, 2, bFramesDown.getInsets().bottom, 2)); bFramesDown.addActionListener(this); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_END, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 16); pFramesListArrows.add(bFramesUp, c); c = ViewerUtil.setGBC(c, 0, 1, 1, 1, 0.0, 1.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.NONE, new Insets(8, 0, 0, 0), 0, 16); pFramesListArrows.add(bFramesDown, c); JPanel pFramesAdd = new JPanel(new GridBagLayout()); miFramesAddFiles = new JMenuItem("Add file(s)..."); miFramesAddFiles.setToolTipText("Add only frames from an external graphics file. Cycle definitions are ignored."); miFramesAddFiles.addActionListener(this); miFramesAddResources = new JMenuItem("Add resource(s)..."); miFramesAddResources.setToolTipText("Add only frames from an internal graphics resource. Cycle definitions are ignored."); miFramesAddResources.addActionListener(this); miFramesAddFolder = new JMenuItem("Add folder..."); miFramesAddFolder.addActionListener(this); miFramesImportFile = new JMenuItem("Import BAM file..."); miFramesImportFile.setToolTipText("Import both frame and cycle definitions from an external BAM file."); miFramesImportFile.addActionListener(this); miFramesImportResource = new JMenuItem("Import BAM resource..."); miFramesImportResource.setToolTipText("Import both frame and cycle definitions from an internal BAM resource."); miFramesImportResource.addActionListener(this); bpmFramesAdd = new ButtonPopupMenu("Add...", new JMenuItem[]{miFramesAddFiles, miFramesAddResources, miFramesAddFolder, miFramesImportFile, miFramesImportResource}, false, ButtonPopupMenu.Align.TOP); bpmFramesAdd.setIcon(Icons.getIcon(Icons.ICON_ARROW_UP_15)); bpmFramesAdd.setIconTextGap(8); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0); pFramesAdd.add(bpmFramesAdd, c); JPanel pFramesRemove = new JPanel(new GridBagLayout()); miFramesRemove = new JMenuItem("Remove"); miFramesRemove.addActionListener(this); miFramesRemoveAll = new JMenuItem("Remove all"); miFramesRemoveAll.addActionListener(this); miFramesDropUnused = new JMenuItem("Drop unused frames"); miFramesDropUnused.addActionListener(this); miFramesDropUnused.setToolTipText("Remove frames that are not used in any cycle definitions."); bpmFramesRemove = new ButtonPopupMenu("Remove...", new JMenuItem[]{miFramesRemove, miFramesRemoveAll, miFramesDropUnused}); bpmFramesRemove.setIcon(Icons.getIcon(Icons.ICON_ARROW_UP_15)); bpmFramesRemove.setIconTextGap(8); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0); pFramesRemove.add(bpmFramesRemove, c); JPanel pFramesListButtons = new JPanel(new GridBagLayout()); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0); pFramesListButtons.add(pFramesAdd, c); c = ViewerUtil.setGBC(c, 1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0); pFramesListButtons.add(new JPanel(), c); c = ViewerUtil.setGBC(c, 2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0); pFramesListButtons.add(pFramesRemove, c); JPanel pFramesList = new JPanel(new GridBagLayout()); JLabel lFramesTitle = new JLabel("Frames:"); modelFrames = new BamFramesListModel(this); listFrames = new JList<>(modelFrames); listFrames.setCellRenderer(new IndexedCellRenderer()); listFrames.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); listFrames.addListSelectionListener(this); JScrollPane scroll = new JScrollPane(listFrames); c = ViewerUtil.setGBC(c, 0, 0, 2, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 4, 0), 0, 0); pFramesList.add(lFramesTitle, c); c = ViewerUtil.setGBC(c, 0, 1, 1, 1, 1.0, 1.0, GridBagConstraints.LINE_START, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0); pFramesList.add(scroll, c); c = ViewerUtil.setGBC(c, 1, 1, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.VERTICAL, new Insets(0, 8, 0, 0), 0, 0); pFramesList.add(pFramesListArrows, c); c = ViewerUtil.setGBC(c, 0, 2, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(4, 0, 0, 0), 0, 0); pFramesList.add(pFramesListButtons, c); // creating "Current Frame" section pFramesCurFrame = new JPanel(new GridBagLayout()); pFramesCurFrame.setBorder(BorderFactory.createTitledBorder("No frame selected")); JLabel lFramesWidth = new JLabel("Width:"); JLabel lFramesHeight = new JLabel("Height:"); JLabel lFramesCenterX = new JLabel("Center X:"); JLabel lFramesCenterY = new JLabel("Center Y:"); tfFrameWidth = new JTextField("0", 6); tfFrameWidth.setEditable(false); tfFrameHeight = new JTextField("0", 6); tfFrameHeight.setEditable(false); String tip = "Tip: You can prefix the entered number by '++' for adding to or '- -' " + "for subtracting from the current center coordinate."; tfFrameCenterX = new JTextField("0", 6); tfFrameCenterX.setToolTipText(tip); tfFrameCenterX.addFocusListener(this); tfFrameCenterY = new JTextField("0", 6); tfFrameCenterY.setToolTipText(tip); tfFrameCenterY.addFocusListener(this); cbCompressFrame = new JCheckBox("Compress frame"); cbCompressFrame.setToolTipText("Selecting this option activates RLE compression for the current frame(s)."); cbCompressFrame.addActionListener(this); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(4, 8, 0, 0), 0, 0); pFramesCurFrame.add(lFramesWidth, c); c = ViewerUtil.setGBC(c, 1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(4, 8, 0, 0), 0, 0); pFramesCurFrame.add(tfFrameWidth, c); c = ViewerUtil.setGBC(c, 2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(4, 16, 0, 0), 0, 0); pFramesCurFrame.add(lFramesCenterX, c); c = ViewerUtil.setGBC(c, 3, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(4, 8, 0, 4), 0, 0); pFramesCurFrame.add(tfFrameCenterX, c); c = ViewerUtil.setGBC(c, 0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(4, 8, 0, 0), 0, 0); pFramesCurFrame.add(lFramesHeight, c); c = ViewerUtil.setGBC(c, 1, 1, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(4, 8, 0, 0), 0, 0); pFramesCurFrame.add(tfFrameHeight, c); c = ViewerUtil.setGBC(c, 2, 1, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(4, 16, 0, 0), 0, 0); pFramesCurFrame.add(lFramesCenterY, c); c = ViewerUtil.setGBC(c, 3, 1, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(4, 8, 0, 4), 0, 0); pFramesCurFrame.add(tfFrameCenterY, c); c = ViewerUtil.setGBC(c, 0, 2, 4, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(4, 4, 4, 4), 0, 0); pFramesCurFrame.add(cbCompressFrame, c); Component[] orderList = new Component[]{tfFrameWidth, tfFrameHeight, tfFrameCenterX, tfFrameCenterY}; pFramesCurFrame.setFocusTraversalPolicy(new FixedFocusTraversalPolicy(orderList)); pFramesCurFrame.setFocusTraversalPolicyProvider(true); // creating "Quick View" section JPanel pFramesQuickView = new JPanel(new GridBagLayout()); pFramesQuickView.setBorder(BorderFactory.createTitledBorder("Quick Preview ")); rcFramesPreview = new RenderCanvas(new BufferedImage(256, 256, BufferedImage.TYPE_INT_ARGB)); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.LINE_START, GridBagConstraints.BOTH, new Insets(4, 4, 4, 4), 0, 0); pFramesQuickView.add(rcFramesPreview, c); pFramesQuickView.setMinimumSize(pFramesQuickView.getPreferredSize()); // creating "Import" section JPanel pFramesImport = new JPanel(new GridBagLayout()); pFramesImport.setBorder(BorderFactory.createTitledBorder("Import ")); c = ViewerUtil.setGBC(c, 0, 0, 1, 2, 1.0, 1.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.BOTH, new Insets(4, 4, 4, 16), 0, 0); pFramesImport.add(pFramesList, c); c = ViewerUtil.setGBC(c, 1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(4, 0, 0, 4), 0, 0); pFramesImport.add(pFramesCurFrame, c); c = ViewerUtil.setGBC(c, 1, 1, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(8, 0, 8, 4), 0, 0); pFramesImport.add(pFramesQuickView, c); // creating "Export options" sections JPanel pFramesOptionsVersionV1 = new JPanel(new GridBagLayout()); cbCompressBam = new JCheckBox("Compress BAM", BamOptionsDialog.getCompressBam()); cbCompressBam.setToolTipText("Create a zlib compressed BAM file (BAMC)"); cbCompressBam.addActionListener(this); bPalette = new JButton("Palette..."); bPalette.addActionListener(this); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(0, 8, 0, 0), 0, 0); pFramesOptionsVersionV1.add(bPalette, c); c = ViewerUtil.setGBC(c, 1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(0, 8, 0, 0), 0, 0); pFramesOptionsVersionV1.add(cbCompressBam, c); JPanel pFramesOptionsVersionV2 = new JPanel(new GridBagLayout()); JLabel lPvrzIndex = new JLabel("PVRZ index starts at:"); sPvrzIndex = new JSpinner(new SpinnerNumberModel(BamOptionsDialog.getPvrzIndex(), 0, 99999, 1)); sPvrzIndex.setToolTipText("Enter a number from 0 to 99999"); sPvrzIndex.addChangeListener(this); JLabel lFramesCompression = new JLabel("Compression type:"); cbCompression = new JComboBox<>(CompressionItems); cbCompression.setSelectedIndex(BamOptionsDialog.getCompressionType()); bCompressionHelp = new JButton("?"); bCompressionHelp.setMargin(new Insets(2, 4, 2, 4)); bCompressionHelp.setToolTipText("About compression types"); bCompressionHelp.addActionListener(this); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(0, 8, 0, 0), 0, 0); pFramesOptionsVersionV2.add(lPvrzIndex, c); c = ViewerUtil.setGBC(c, 1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(0, 4, 0, 0), 0, 0); pFramesOptionsVersionV2.add(sPvrzIndex, c); c = ViewerUtil.setGBC(c, 2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(0, 16, 0, 0), 0, 0); pFramesOptionsVersionV2.add(lFramesCompression, c); c = ViewerUtil.setGBC(c, 3, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(0, 8, 0, 0), 8, 0); pFramesOptionsVersionV2.add(cbCompression, c); c = ViewerUtil.setGBC(c, 4, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(0, 4, 0, 4), 0, 0); pFramesOptionsVersionV2.add(bCompressionHelp, c); pFramesOptionsVersion = new JPanel(new CardLayout()); pFramesOptionsVersion.add(pFramesOptionsVersionV1, "V1"); pFramesOptionsVersion.add(pFramesOptionsVersionV2, "V2"); // creating "Output options" section JPanel pFramesExport = new JPanel(new GridBagLayout()); pFramesExport.setBorder(BorderFactory.createTitledBorder("Output options ")); JLabel lFramesVersion = new JLabel("BAM version:"); cbVersion = new JComboBox<>(BamVersionItems); cbVersion.addActionListener(this); cbVersion.setSelectedIndex(BamOptionsDialog.getBamVersion()); bVersionHelp = new JButton("?"); bVersionHelp.setMargin(new Insets(2, 4, 2, 4)); bVersionHelp.setToolTipText("About BAM versions"); bVersionHelp.addActionListener(this); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(8, 4, 8, 0), 0, 0); pFramesExport.add(lFramesVersion, c); c = ViewerUtil.setGBC(c, 1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(0, 8, 0, 0), 8, 0); pFramesExport.add(cbVersion, c); c = ViewerUtil.setGBC(c, 2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(0, 4, 0, 0), 0, 0); pFramesExport.add(bVersionHelp, c); c = ViewerUtil.setGBC(c, 3, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(0, 8, 0, 0), 0, 0); pFramesExport.add(pFramesOptionsVersion, c); // putting all together JPanel pFrames = new JPanel(new GridBagLayout()); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.BOTH, new Insets(8, 8, 0, 8), 0, 0); pFrames.add(pFramesImport, c); c = ViewerUtil.setGBC(c, 0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(0, 8, 8, 8), 0, 0); pFrames.add(pFramesExport, c); return pFrames; } private JPanel createCyclesTab() { GridBagConstraints c = new GridBagConstraints(); // creating "Cycles" section JPanel pCyclesButtons = new JPanel(new GridBagLayout()); bCyclesAdd = new JButton("Add cycle"); bCyclesAdd.addActionListener(this); bCyclesRemove = new JButton("Remove cycle(s)"); bCyclesRemove.addActionListener(this); bCyclesRemoveAll = new JButton("Remove all"); bCyclesRemoveAll.addActionListener(this); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0); pCyclesButtons.add(bCyclesAdd, c); c = ViewerUtil.setGBC(c, 1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(0, 4, 0, 0), 0, 0); pCyclesButtons.add(bCyclesRemove, c); c = ViewerUtil.setGBC(c, 2, 0, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(0, 4, 0, 0), 0, 0); pCyclesButtons.add(bCyclesRemoveAll, c); JPanel pCyclesArrows = new JPanel(new GridBagLayout()); bCyclesUp = new JButton(Icons.getIcon(Icons.ICON_UP_16)); bCyclesUp.setMargin(new Insets(bCyclesUp.getInsets().top, 2, bCyclesUp.getInsets().bottom, 2)); bCyclesUp.addActionListener(this); bCyclesDown = new JButton(Icons.getIcon(Icons.ICON_DOWN_16)); bCyclesDown.setMargin(new Insets(bCyclesDown.getInsets().top, 2, bCyclesDown.getInsets().bottom, 2)); bCyclesDown.addActionListener(this); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 16); pCyclesArrows.add(bCyclesUp, c); c = ViewerUtil.setGBC(c, 0, 1, 1, 1, 0.0, 1.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.NONE, new Insets(8, 0, 0, 0), 0, 16); pCyclesArrows.add(bCyclesDown, c); JPanel pCyclesList = new JPanel(new GridBagLayout()); JLabel lCycles = new JLabel("Cycles:"); modelCycles = new BamCyclesListModel(this); listCycles = new JList<>(modelCycles); listCycles.setCellRenderer(new IndexedCellRenderer()); listCycles.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); listCycles.addListSelectionListener(this); JScrollPane scroll = new JScrollPane(listCycles); c = ViewerUtil.setGBC(c, 0, 0, 2, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 4, 0), 0, 0); pCyclesList.add(lCycles, c); c = ViewerUtil.setGBC(c, 0, 1, 1, 1, 1.0, 1.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0); pCyclesList.add(scroll, c); c = ViewerUtil.setGBC(c, 1, 1, 1, 2, 0.0, 1.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.VERTICAL, new Insets(0, 4, 0, 0), 0, 0); pCyclesList.add(pCyclesArrows, c); c = ViewerUtil.setGBC(c, 0, 2, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(4, 0, 0, 0), 0, 0); pCyclesList.add(pCyclesButtons, c); // creating Macros/Preview section JPanel pMacros = new JPanel(new GridBagLayout()); JLabel lMacroCurCycle = new JLabel("Selected cycle(s):"); bMacroAssignFrames = new JButton("Assign all frames"); bMacroAssignFrames.addActionListener(this); bMacroRemoveFrames = new JButton("Remove all frames"); bMacroRemoveFrames.addActionListener(this); bMacroSortFramesAsc = new JButton("Sort frames"); bMacroSortFramesAsc.addActionListener(this); bMacroDuplicateFrames = new JButton("Duplicate each frame"); bMacroDuplicateFrames.addActionListener(this); bMacroDuplicateCycle = new JButton("Duplicate cycle(s)"); bMacroDuplicateCycle.addActionListener(this); bMacroReverseFrames = new JButton("Reverse frames order"); bMacroReverseFrames.addActionListener(this); JLabel lMacroAllCycles = new JLabel("All cycles:"); bMacroRemoveAll = new JButton("Remove all frames"); bMacroRemoveAll.addActionListener(this); bMacroReverseCycles = new JButton("Reverse cycles order"); bMacroReverseCycles.addActionListener(this); JPanel pMacroPanel = new JPanel(new GridBagLayout()); c = ViewerUtil.setGBC(c, 0, 0, 2, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0); pMacroPanel.add(lMacroCurCycle, c); c = ViewerUtil.setGBC(c, 0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(4, 0, 0, 0), 0, 0); pMacroPanel.add(bMacroAssignFrames, c); c = ViewerUtil.setGBC(c, 1, 1, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(4, 4, 0, 0), 0, 0); pMacroPanel.add(bMacroRemoveFrames, c); c = ViewerUtil.setGBC(c, 0, 2, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(4, 0, 0, 0), 0, 0); pMacroPanel.add(bMacroDuplicateFrames, c); c = ViewerUtil.setGBC(c, 1, 2, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(4, 4, 0, 0), 0, 0); pMacroPanel.add(bMacroDuplicateCycle, c); c = ViewerUtil.setGBC(c, 0, 3, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(4, 0, 0, 0), 0, 0); pMacroPanel.add(bMacroSortFramesAsc, c); c = ViewerUtil.setGBC(c, 1, 3, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(4, 4, 0, 0), 0, 0); pMacroPanel.add(bMacroReverseFrames, c); c = ViewerUtil.setGBC(c, 0, 4, 2, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(16, 0, 0, 0), 0, 0); pMacroPanel.add(lMacroAllCycles, c); c = ViewerUtil.setGBC(c, 0, 5, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(4, 0, 0, 0), 0, 0); pMacroPanel.add(bMacroRemoveAll, c); c = ViewerUtil.setGBC(c, 1, 5, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(4, 4, 0, 0), 0, 0); pMacroPanel.add(bMacroReverseCycles, c); c = ViewerUtil.setGBC(c, 0, 6, 2, 1, 1.0, 1.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0); pMacroPanel.add(new JPanel(), c); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.BOTH, new Insets(4, 8, 8, 8), 0, 0); pMacros.add(pMacroPanel, c); pMacros.setMinimumSize(pMacros.getPreferredSize()); JPanel pPreview = new JPanel(new GridBagLayout()); rcCyclesPreview = new RenderCanvas(new BufferedImage(256, 256, BufferedImage.TYPE_INT_ARGB)); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.LINE_START, GridBagConstraints.BOTH, new Insets(4, 4, 4, 4), 0, 0); pPreview.add(rcCyclesPreview, c); pPreview.setMinimumSize(pPreview.getPreferredSize()); tpCyclesSection = new JTabbedPane(JTabbedPane.TOP, JTabbedPane.SCROLL_TAB_LAYOUT); tpCyclesSection.addTab("Macros", pMacros); tpCyclesSection.addTab("Quick Preview", pPreview); tpCyclesSection.setSelectedIndex(0); JPanel pTopHalf = new JPanel(new GridBagLayout()); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.BOTH, new Insets(4, 8, 0, 0), 0, 0); pTopHalf.add(pCyclesList, c); c = ViewerUtil.setGBC(c, 1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(8, 16, 0, 0), 0, 0); pTopHalf.add(tpCyclesSection, c); // creating "Current Cycles" section JPanel pCycleTransfer = new JPanel(new GridBagLayout()); bCurCycleAdd = new JButton(Icons.getIcon(Icons.ICON_FORWARD_16)); bCurCycleAdd.setMargin(new Insets(2, bCurCycleAdd.getInsets().left, 2, bCurCycleAdd.getInsets().right)); bCurCycleAdd.addActionListener(this); bCurCycleRemove = new JButton(Icons.getIcon(Icons.ICON_BACK_16)); bCurCycleRemove.setMargin(new Insets(2, bCurCycleRemove.getInsets().left, 2, bCurCycleRemove.getInsets().right)); bCurCycleRemove.addActionListener(this); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 8, 0); pCycleTransfer.add(bCurCycleAdd, c); c = ViewerUtil.setGBC(c, 0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(4, 0, 0, 0), 8, 0); pCycleTransfer.add(bCurCycleRemove, c); JPanel pCurCycleArrows = new JPanel(new GridBagLayout()); bCurCycleUp = new JButton(Icons.getIcon(Icons.ICON_UP_16)); bCurCycleUp.setMargin(new Insets(bCurCycleUp.getInsets().top, 2, bCurCycleUp.getInsets().bottom, 2)); bCurCycleUp.addActionListener(this); bCurCycleDown = new JButton(Icons.getIcon(Icons.ICON_DOWN_16)); bCurCycleDown.setMargin(new Insets(bCurCycleDown.getInsets().top, 2, bCurCycleDown.getInsets().bottom, 2)); bCurCycleDown.addActionListener(this); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 16); pCurCycleArrows.add(bCurCycleUp, c); c = ViewerUtil.setGBC(c, 0, 1, 1, 1, 0.0, 1.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.NONE, new Insets(8, 0, 0, 0), 0, 16); pCurCycleArrows.add(bCurCycleDown, c); JLabel lCurCycleFrames = new JLabel("Available frames:"); listFramesAvail = new JList<>(modelFrames); listFramesAvail.setCellRenderer(new IndexedCellRenderer()); listFramesAvail.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); listFramesAvail.addListSelectionListener(this); listFramesAvail.addMouseListener(this); scroll = new JScrollPane(listFramesAvail); JLabel lCurCycle = new JLabel("Current cycle:"); modelCurCycle = new BamCycleFramesListModel(this); listCurCycle = new JList<>(modelCurCycle); listCurCycle.setCellRenderer(new IndexedCellRenderer()); listCurCycle.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); listCurCycle.addListSelectionListener(this); listCurCycle.addMouseListener(this); JScrollPane scroll2 = new JScrollPane(listCurCycle); pCurrentCycle = new JPanel(new GridBagLayout()); pCurrentCycle.setBorder(BorderFactory.createTitledBorder("No cycle selected ")); c = ViewerUtil.setGBC(c, 0, 0, 2, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.NONE, new Insets(0, 4, 0, 0), 0, 0); pCurrentCycle.add(lCurCycleFrames, c); c = ViewerUtil.setGBC(c, 2, 0, 2, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0); pCurrentCycle.add(lCurCycle, c); c = ViewerUtil.setGBC(c, 0, 1, 1, 1, 1.0, 1.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.BOTH, new Insets(4, 4, 4, 0), 0, 0); pCurrentCycle.add(scroll, c); c = ViewerUtil.setGBC(c, 1, 1, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(0, 4, 0, 4), 0, 0); pCurrentCycle.add(pCycleTransfer, c); c = ViewerUtil.setGBC(c, 2, 1, 1, 1, 1.0, 1.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.BOTH, new Insets(4, 0, 4, 0), 0, 0); pCurrentCycle.add(scroll2, c); c = ViewerUtil.setGBC(c, 3, 1, 1, 1, 0.0, 1.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.VERTICAL, new Insets(4, 4, 0, 4), 0, 0); pCurrentCycle.add(pCurCycleArrows, c); // putting all together JPanel pCycles = new JPanel(new GridBagLayout()); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.BOTH, new Insets(4, 4, 4, 4), 0, 0); pCycles.add(pTopHalf, c); c = ViewerUtil.setGBC(c, 0, 1, 1, 1, 1.0, 2.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.BOTH, new Insets(4, 4, 4, 4), 0, 0); pCycles.add(pCurrentCycle, c); return pCycles; } private JPanel createPreviewTab() { bamControlPreview = null; GridBagConstraints c = new GridBagConstraints(); // create bottom control bar lPreviewCycle = new JLabel("Cycle: X/Y"); bPreviewCyclePrev = new JButton(Icons.getIcon(Icons.ICON_BACK_16)); bPreviewCyclePrev.setMargin(new Insets(bPreviewCyclePrev.getMargin().top, 2, bPreviewCyclePrev.getMargin().bottom, 2)); bPreviewCyclePrev.addActionListener(this); bPreviewCycleNext = new JButton(Icons.getIcon(Icons.ICON_FORWARD_16)); bPreviewCycleNext.setMargin(new Insets(bPreviewCycleNext.getMargin().top, 2, bPreviewCycleNext.getMargin().bottom, 2)); bPreviewCycleNext.addActionListener(this); lPreviewFrame = new JLabel("Frame: X/Y"); bPreviewFramePrev = new JButton(Icons.getIcon(Icons.ICON_BACK_16)); bPreviewFramePrev.setMargin(new Insets(bPreviewFramePrev.getMargin().top, 2, bPreviewFramePrev.getMargin().bottom, 2)); bPreviewFramePrev.addActionListener(this); bPreviewFrameNext = new JButton(Icons.getIcon(Icons.ICON_FORWARD_16)); bPreviewFrameNext.setMargin(new Insets(bPreviewFrameNext.getMargin().top, 2, bPreviewFrameNext.getMargin().bottom, 2)); bPreviewFrameNext.addActionListener(this); bPreviewPlay = new JButton("Pause", Icons.getIcon(Icons.ICON_PLAY_16)); bPreviewPlay.setMinimumSize(bPreviewPlay.getPreferredSize()); bPreviewPlay.setText("Play"); bPreviewPlay.addActionListener(this); bPreviewStop = new JButton("Stop", Icons.getIcon(Icons.ICON_STOP_16)); bPreviewStop.addActionListener(this); JPanel pControls = new JPanel(new GridBagLayout()); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0); pControls.add(lPreviewCycle, c); c = ViewerUtil.setGBC(c, 1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(0, 4, 0, 0), 0, 0); pControls.add(bPreviewCyclePrev, c); c = ViewerUtil.setGBC(c, 2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(0, 2, 0, 0), 0, 0); pControls.add(bPreviewCycleNext, c); c = ViewerUtil.setGBC(c, 3, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(0, 16, 0, 0), 0, 0); pControls.add(lPreviewFrame, c); c = ViewerUtil.setGBC(c, 4, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(0, 4, 0, 0), 0, 0); pControls.add(bPreviewFramePrev, c); c = ViewerUtil.setGBC(c, 5, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(0, 2, 0, 0), 0, 0); pControls.add(bPreviewFrameNext, c); c = ViewerUtil.setGBC(c, 6, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(0, 16, 0, 0), 0, 0); pControls.add(bPreviewPlay, c); c = ViewerUtil.setGBC(c, 7, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(0, 4, 0, 0), 0, 0); pControls.add(bPreviewStop, c); JLabel lFps = new JLabel("Frames/second:"); SpinnerNumberModel spinnerModel = new SpinnerNumberModel(15.0, 1.0, 30.0, 1.0); sPreviewFps = new JSpinner(spinnerModel); sPreviewFps.addChangeListener(this); currentFps = (Double)spinnerModel.getValue(); JLabel lPreviewMode = new JLabel("Playback mode:"); cbPreviewMode = new JComboBox<>(PlaybackModeItems); cbPreviewMode.setSelectedIndex(MODE_CURRENT_CYCLE_LOOPED); cbPreviewMode.addActionListener(this); cbPreviewShowMarker = new JCheckBox("Show markers"); cbPreviewShowMarker.addActionListener(this); cbPreviewZoom = new JCheckBox("Zoom"); cbPreviewZoom.addActionListener(this); JPanel pOptions = new JPanel(new GridBagLayout()); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0); pOptions.add(lPreviewMode, c); c = ViewerUtil.setGBC(c, 1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(0, 4, 0, 0), 0, 0); pOptions.add(cbPreviewMode, c); c = ViewerUtil.setGBC(c, 2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(0, 16, 0, 0), 0, 0); pOptions.add(lFps, c); c = ViewerUtil.setGBC(c, 3, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(0, 4, 0, 0), 0, 0); pOptions.add(sPreviewFps, c); c = ViewerUtil.setGBC(c, 4, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(0, 16, 0, 0), 0, 0); pOptions.add(cbPreviewZoom, c); c = ViewerUtil.setGBC(c, 5, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(0, 16, 0, 0), 0, 0); pOptions.add(cbPreviewShowMarker, c); JPanel pCanvas = new JPanel(new GridBagLayout()); rcPreview = new RenderCanvas(); rcPreview.setInterpolationType(RenderCanvas.TYPE_NEAREST_NEIGHBOR); rcPreview.setScalingEnabled(true); scrollPreview = new JScrollPane(rcPreview); scrollPreview.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); scrollPreview.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER); scrollPreview.setBorder(BorderFactory.createEmptyBorder()); previewCanvas = null; c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0); pCanvas.add(scrollPreview, c); // putting all together JPanel pPreview = new JPanel(new GridBagLayout()); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(4, 8, 0, 8), 0, 0); pPreview.add(pOptions, c); c = ViewerUtil.setGBC(c, 0, 1, 1, 1, 1.0, 1.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.BOTH, new Insets(4, 4, 4, 4), 0, 0); pPreview.add(pCanvas, c); c = ViewerUtil.setGBC(c, 0, 2, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(4, 8, 8, 8), 0, 0); pPreview.add(pControls, c); timer = new Timer(0, this); isPreviewModified = true; isPreviewPlaying = false; previewSetFrameRate(currentFps); return pPreview; } private JPanel createFiltersTab() { GridBagConstraints c = new GridBagConstraints(); // creating "Filters" section Vector<BamFilterFactory.FilterInfo> filters = new Vector<BamFilterFactory.FilterInfo>(); for (int i = 0; i < BamFilterFactory.getFilterInfoSize(); i++) { filters.add(BamFilterFactory.getFilterInfo(i)); } Collections.sort(filters); cbFiltersAdd = new JComboBox<>(filters); bFiltersAdd = new JButton("Add"); bFiltersAdd.addActionListener(this); JPanel pFiltersAdd = new JPanel(new GridBagLayout()); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0); pFiltersAdd.add(cbFiltersAdd, c); c = ViewerUtil.setGBC(c, 1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(0, 4, 0, 0), 0, 0); pFiltersAdd.add(bFiltersAdd, c); bFiltersRemove = new JButton("Remove"); bFiltersRemove.addActionListener(this); bFiltersRemoveAll = new JButton("Remove all"); bFiltersRemoveAll.addActionListener(this); JPanel pFiltersRemove = new JPanel(new GridBagLayout()); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0); pFiltersRemove.add(bFiltersRemove, c); c = ViewerUtil.setGBC(c, 1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0); pFiltersRemove.add(new JPanel(), c); c = ViewerUtil.setGBC(c, 2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(0, 4, 0, 0), 0, 0); pFiltersRemove.add(bFiltersRemoveAll, c); bFiltersUp = new JButton(Icons.getIcon(Icons.ICON_UP_16)); bFiltersUp.setMargin(new Insets(16, 2, 16, 2)); bFiltersUp.addActionListener(this); bFiltersDown = new JButton(Icons.getIcon(Icons.ICON_DOWN_16)); bFiltersDown.setMargin(new Insets(16, 2, 16, 2)); bFiltersDown.addActionListener(this); JPanel pFiltersMove = new JPanel(new GridBagLayout()); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0); pFiltersMove.add(bFiltersUp, c); c = ViewerUtil.setGBC(c, 0, 1, 1, 1, 0.0, 1.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.NONE, new Insets(8, 0, 0, 0), 0, 0); pFiltersMove.add(bFiltersDown, c); JPanel pFiltersDesc = new JPanel(new GridBagLayout()); pFiltersDesc.setBorder(BorderFactory.createTitledBorder("Filter Description ")); taFiltersDesc = new JTextArea(8, 0); taFiltersDesc.setEditable(false); taFiltersDesc.setFont(UIManager.getFont("Label.font")); Color bg = UIManager.getColor("Label.background"); taFiltersDesc.setBackground(bg); taFiltersDesc.setSelectionColor(bg); taFiltersDesc.setSelectedTextColor(bg); taFiltersDesc.setWrapStyleWord(true); taFiltersDesc.setLineWrap(true); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0); pFiltersDesc.add(taFiltersDesc, c); modelFilters = new SimpleListModel<BamFilterBase>(); listFilters = new JList<>(modelFilters); listFilters.setCellRenderer(new IndexedCellRenderer()); listFilters.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); listFilters.addListSelectionListener(this); JScrollPane scroll = new JScrollPane(listFilters); JPanel pFiltersList = new JPanel(new GridBagLayout()); pFiltersList.setBorder(BorderFactory.createTitledBorder("Filters ")); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(0, 4, 0, 0), 0, 0); pFiltersList.add(pFiltersAdd, c); c = ViewerUtil.setGBC(c, 1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.NONE, new Insets(0, 0, 0, 4), 0, 0); pFiltersList.add(new JPanel(), c); c = ViewerUtil.setGBC(c, 0, 1, 1, 1, 1.0, 1.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.BOTH, new Insets(4, 4, 0, 0), 0, 0); pFiltersList.add(scroll, c); c = ViewerUtil.setGBC(c, 1, 1, 1, 1, 0.0, 1.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.VERTICAL, new Insets(4, 4, 0, 4), 0, 0); pFiltersList.add(pFiltersMove, c); c = ViewerUtil.setGBC(c, 0, 2, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(4, 4, 0, 0), 0, 0); pFiltersList.add(pFiltersRemove, c); c = ViewerUtil.setGBC(c, 1, 2, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.NONE, new Insets(0, 0, 0, 4), 0, 0); pFiltersList.add(new JPanel(), c); c = ViewerUtil.setGBC(c, 0, 3, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(8, 4, 4, 0), 0, 0); pFiltersList.add(pFiltersDesc, c); // creating "Filter settings" section JPanel pFiltersSettingsMain = new JPanel(new GridBagLayout()); pFiltersSettingsMain.setBorder(BorderFactory.createTitledBorder("Filter settings ")); pFiltersSettings = new JPanel(new BorderLayout()); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.BOTH, new Insets(0, 4, 4, 4), 0, 0); pFiltersSettingsMain.add(pFiltersSettings, c); // creating "Quick Preview" section JPanel pFiltersPreviewControls = new JPanel(new GridBagLayout()); JLabel l = new JLabel("Frame:"); cbFiltersShowMarker = new JCheckBox("Show markers", false); cbFiltersShowMarker.addActionListener(this); sFiltersPreviewFrame = new JSpinner(new SpinnerNumberModel(0, 0, 99999, 1)); sFiltersPreviewFrame.addChangeListener(this); JPanel pDummy = new JPanel(); pDummy.setPreferredSize(cbFiltersShowMarker.getPreferredSize()); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(4, 0, 4, 8), 0, 0); pFiltersPreviewControls.add(cbFiltersShowMarker, c); c = ViewerUtil.setGBC(c, 1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(4, 0, 4, 0), 0, 0); pFiltersPreviewControls.add(l, c); c = ViewerUtil.setGBC(c, 2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(4, 4, 4, 0), 0, 0); pFiltersPreviewControls.add(sFiltersPreviewFrame, c); c = ViewerUtil.setGBC(c, 3, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(4, 0, 4, 4), 0, 0); pFiltersPreviewControls.add(pDummy, c); JPanel pFiltersPreview = new JPanel(new GridBagLayout()); pFiltersPreview.setBorder(BorderFactory.createTitledBorder("Quick Preview ")); rcFiltersPreview = new RenderCanvas(new BufferedImage(320, 320, BufferedImage.TYPE_INT_ARGB)); rcFiltersPreview.setMinimumSize(rcFiltersPreview.getPreferredSize()); rcFiltersPreview.setScalingEnabled(false); rcFiltersPreview.setHorizontalAlignment(SwingConstants.CENTER); rcFiltersPreview.setVerticalAlignment(SwingConstants.CENTER); scrollFiltersPreview = new JScrollPane(rcFiltersPreview); scrollFiltersPreview.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); scrollFiltersPreview.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER); scrollFiltersPreview.setBorder(BorderFactory.createEmptyBorder()); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.BOTH, new Insets(0, 4, 0, 4), 0, 0); pFiltersPreview.add(scrollFiltersPreview, c); c = ViewerUtil.setGBC(c, 0, 1, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(4, 4, 4, 4), 0, 0); pFiltersPreview.add(pFiltersPreviewControls, c); // putting all together JPanel pFilters = new JPanel(new GridBagLayout()); c = ViewerUtil.setGBC(c, 0, 0, 1, 2, 0.0, 1.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.BOTH, new Insets(8, 8, 8, 0), 0, 0); pFilters.add(pFiltersList, c); c = ViewerUtil.setGBC(c, 1, 0, 1, 1, 1.0, 0.5, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.BOTH, new Insets(8, 8, 0, 8), 0, 0); pFilters.add(pFiltersSettingsMain, c); c = ViewerUtil.setGBC(c, 1, 1, 1, 1, 1.0, 1.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.BOTH, new Insets(8, 8, 8, 8), 0, 0); pFilters.add(pFiltersPreview, c); updateFilterList(); return pFilters; } // Close the main dialog manually. 'force' overrides the confirmation dialog. private void hideWindow(boolean force) { if (force || confirmCloseDialog()) { clear(); setVisible(false); } } // Presents a confirmation dialog if any frames or cycles are present private boolean confirmCloseDialog() { boolean isEmpty = (modelFrames.isEmpty() && modelCycles.isEmpty()); if (!isEmpty) { String msg = String.format("%1$d frame(s) and %2$d cycle(s) will be discarded.\n" + "Do you really want to close the dialog?", modelFrames.getSize(), modelCycles.getSize()); if (JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog(this, msg, "Close dialog", JOptionPane.YES_NO_OPTION)) { isEmpty = true; } } return isEmpty; } // resets the dialog state private void clear() { previewStop(); outputSetModified(true); filterRemoveAll(); cyclesRemoveAll(); framesRemoveAll(); paletteDialog.clear(); } // Updates the tab component state private void updateStatus() { boolean isReady = (!modelFrames.isEmpty() && !modelCycles.isEmpty()); boolean showTabs = !modelFrames.isEmpty(); bPalette.setEnabled(showTabs); miSessionExport.setEnabled(showTabs); bConvert.setEnabled(isReady); if (!showTabs) { tpMain.setSelectedIndex(TAB_FRAMES); } tpMain.setEnabledAt(TAB_CYCLES, showTabs); tpMain.setEnabledAt(TAB_PREVIEW, isReady); tpMain.setEnabledAt(TAB_FILTERS, showTabs); } // Updates relevant components to reflect the current state of the global frames list private void updateFramesList() { // updating button states Pair<Integer> bounds = getIndexBounds(listFrames.getSelectedIndices()); bFramesUp.setEnabled(!modelFrames.isEmpty() && bounds.getFirst() > 0); bFramesDown.setEnabled(!modelFrames.isEmpty() && bounds.getFirst() >= 0 && bounds.getSecond() < modelFrames.getSize() - 1); miFramesRemove.setEnabled(!modelFrames.isEmpty() && !listFrames.isSelectionEmpty()); miFramesRemoveAll.setEnabled(!modelFrames.isEmpty()); miFramesDropUnused.setEnabled(!modelFrames.isEmpty()); bpmFramesRemove.setEnabled(miFramesRemove.isEnabled() || miFramesRemoveAll.isEnabled() || miFramesDropUnused.isEnabled()); // updating palette paletteDialog.setPaletteModified(); updateCyclesList(); updateStatus(); } // Updates relevant components to reflect the current state of the cycles list private void updateCyclesList() { listCycles.repaint(); Pair<Integer> bounds = getIndexBounds(listCycles.getSelectedIndices()); int idx = (bounds.getFirst().compareTo(bounds.getSecond()) == 0) ? bounds.getFirst() : -1; if (idx >= 0) { listCycles.ensureIndexIsVisible(idx); } // updating button states bCyclesUp.setEnabled(!modelCycles.isEmpty() && bounds.getFirst() > 0); bCyclesDown.setEnabled(!modelCycles.isEmpty() && bounds.getFirst() >= 0 && bounds.getSecond() < modelCycles.getSize() - 1); bCyclesRemove.setEnabled(!listCycles.isSelectionEmpty()); bCyclesRemoveAll.setEnabled(!modelCycles.isEmpty()); // updating macro buttons bMacroAssignFrames.setEnabled(!listCycles.isSelectionEmpty()); bMacroRemoveFrames.setEnabled(!listCycles.isSelectionEmpty()); bMacroDuplicateCycle.setEnabled(!listCycles.isSelectionEmpty()); bMacroDuplicateFrames.setEnabled(!listCycles.isSelectionEmpty()); bMacroSortFramesAsc.setEnabled(!listCycles.isSelectionEmpty()); bMacroReverseFrames.setEnabled(!listCycles.isSelectionEmpty()); bMacroRemoveAll.setEnabled(!modelCycles.isEmpty()); bMacroReverseCycles.setEnabled(!modelCycles.isEmpty()); initCurrentCycle(bounds); updateStatus(); } // Updates relevant components to reflect the current state of the selected cycle private void updateCurrentCycle() { // updating button states Pair<Integer> bounds = getIndexBounds(listCurCycle.getSelectedIndices()); bCurCycleUp.setEnabled(!modelCurCycle.isEmpty() && bounds.getFirst() > 0); bCurCycleDown.setEnabled(!modelCurCycle.isEmpty() && bounds.getFirst() >= 0 && bounds.getSecond() < modelCurCycle.getSize() - 1); bCurCycleAdd.setEnabled(!listFramesAvail.isSelectionEmpty()); bCurCycleRemove.setEnabled(!listCurCycle.isSelectionEmpty()); listFramesAvail.invalidate(); listCurCycle.invalidate(); pCurrentCycle.validate(); } private void initCurrentCycle(int cycleIdx) { initCurrentCycle(new Pair<Integer>(cycleIdx, cycleIdx)); } // Initializes the "Current cycle" section of the Cycles tab private void initCurrentCycle(Pair<Integer> cycleIndices) { if (cycleIndices != null) { if (cycleIndices.getFirst().compareTo(cycleIndices.getSecond()) == 0 && cycleIndices.getFirst() >= 0 && cycleIndices.getFirst() < modelCycles.getSize()) { int cycleIdx = cycleIndices.getFirst(); // enabling components listFramesAvail.setEnabled(true); bCurCycleAdd.setEnabled(true); bCurCycleRemove.setEnabled(true); listCurCycle.setEnabled(true); // updating group box title pCurrentCycle.setBorder(BorderFactory.createTitledBorder(String.format("Cycle %1$d ", cycleIdx))); listFramesAvail.setSelectedIndices(new int[]{}); // updating current cycle list view modelCurCycle.setCycle(cycleIdx); listCurCycle.setSelectedIndices(new int[]{}); updateCurrentCycle(); } else { // no cycle selected listFramesAvail.setSelectedIndices(new int[]{}); listFramesAvail.setEnabled(false); bCurCycleAdd.setEnabled(false); bCurCycleRemove.setEnabled(false); listCurCycle.setSelectedIndices(new int[]{}); listCurCycle.setEnabled(false); if (cycleIndices.getFirst() < 0 || cycleIndices.getSecond() < 0) { pCurrentCycle.setBorder(BorderFactory.createTitledBorder("No cycle selected ")); } else { pCurrentCycle.setBorder(BorderFactory.createTitledBorder("Too many cycles selected ")); } modelCurCycle.setCycle(-1); } } } // Updates relevant components to reflect the current frames list selection in the Frames tab private void updateFrameInfo(int[] indices) { if (indices != null && indices.length > 0) { // enabling components tfFrameWidth.setEnabled(true); tfFrameHeight.setEnabled(true); tfFrameCenterX.setEnabled(true); tfFrameCenterY.setEnabled(true); cbCompressFrame.setEnabled(true); // evaluating data PseudoBamFrameEntry fe = getBamDecoder(BAM_ORIGINAL).getFrameInfo(indices[0]); int initialWidth = fe.getWidth(); int initialHeight = fe.getHeight(); int initialX = fe.getCenterX(); int initialY = fe.getCenterY(); boolean initialState = (fe.getOption(PseudoBamDecoder.OPTION_BOOL_COMPRESSED) != null) ? (Boolean)fe.getOption(PseudoBamDecoder.OPTION_BOOL_COMPRESSED) : false; boolean changedWidth = false, changedHeight = false; boolean changedCenterX = false, changedCenterY = false; boolean changedCompression = false; for (int i = 1; i < indices.length; i++) { fe = getBamDecoder(BAM_ORIGINAL).getFrameInfo(indices[i]); changedWidth |= (!changedWidth && initialWidth != fe.getWidth()); changedHeight |= (!changedHeight && initialHeight != fe.getHeight()); changedCenterX |= (!changedCenterX && initialX != fe.getCenterX()); changedCenterY |= (!changedCenterY && initialY != fe.getCenterY()); boolean b = (fe.getOption(PseudoBamDecoder.OPTION_BOOL_COMPRESSED) != null) ? (Boolean)fe.getOption(PseudoBamDecoder.OPTION_BOOL_COMPRESSED) : false; changedCompression |= (!changedCompression && initialState != b); if (changedWidth && changedHeight && changedCenterX && changedCenterY && changedCompression) { break; } } // setting frame info String title = null; if (indices.length > 1) { title = String.format("%1$d frames selected ", indices.length); } else { title = String.format("Frame %1$d ", indices[0]); } pFramesCurFrame.setBorder(BorderFactory.createTitledBorder(title)); tfFrameWidth.setText(changedWidth ? "" : Integer.toString(initialWidth)); tfFrameHeight.setText(changedHeight ? "" : Integer.toString(initialHeight)); tfFrameCenterX.setText(changedCenterX ? "" : Integer.toString(initialX)); tfFrameCenterY.setText(changedCenterY ? "" : Integer.toString(initialY)); cbCompressFrame.setSelected(changedCompression ? false : initialState); } else { // no frame selected final String zero = "0"; tfFrameWidth.setEnabled(false); tfFrameWidth.setText(zero); tfFrameHeight.setEnabled(false); tfFrameHeight.setText(zero); tfFrameCenterX.setEnabled(false); tfFrameCenterX.setText(zero); tfFrameCenterY.setEnabled(false); tfFrameCenterY.setText(zero); cbCompressFrame.setEnabled(false); cbCompressFrame.setSelected(false); pFramesCurFrame.setBorder(BorderFactory.createTitledBorder("No frame selected ")); } } // Updates the specified quick preview section private void updateQuickPreview(RenderCanvas target, int[] indices, boolean showCenter) { if (target != null) { int imgWidth = target.getImage().getWidth(null); int imgHeight = target.getImage().getHeight(null); Graphics2D g = (Graphics2D)target.getImage().getGraphics(); try { g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC)); g.setColor(new Color(0, true)); g.fillRect(0, 0, imgWidth, imgHeight); if (indices != null && indices.length == 1 && indices[0] >= 0 && indices[0] < modelFrames.getSize()) { // drawing frame int left = 0, top = 0; float ratio = 1.0f; int frameIdx = indices[0]; PseudoBamFrameEntry fe = getBamDecoder(BAM_ORIGINAL).getFrameInfo(frameIdx); PseudoBamControl control = getBamDecoder(BAM_ORIGINAL).createControl(); control.setMode(BamDecoder.BamControl.Mode.INDIVIDUAL); Image image = getBamDecoder(BAM_ORIGINAL).frameGet(control, frameIdx); control = null; boolean zoom = ((fe.getWidth() > imgWidth || fe.getHeight() > imgHeight)); if (zoom) { g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); float ratioX = (float)imgWidth / (float)image.getWidth(null); float ratioY = (float)imgHeight / (float)image.getHeight(null); Pair<Float> minMaxRatio = new Pair<Float>(Math.min(ratioX, ratioY), Math.max(ratioX, ratioY)); if ((float)image.getWidth(null)*minMaxRatio.getSecond() < (float)imgWidth && (float)image.getHeight(null)*minMaxRatio.getSecond() < (float)imgHeight) { ratio = minMaxRatio.getSecond(); } else { ratio = minMaxRatio.getFirst(); } int newWidth = (int)((float)image.getWidth(null)*ratio); int newHeight = (int)((float)image.getHeight(null)*ratio); left = (imgWidth - newWidth) >>> 1; top = (imgHeight - newHeight) >>> 1; g.drawImage(image, left, top, newWidth, newHeight, null); } else { left = (imgWidth - image.getWidth(null)) >>> 1; top = (imgHeight - image.getHeight(null)) >>> 1; g.drawImage(image, left, top, null); } if (showCenter) { // drawing center location int cx = left + (int)((float)fe.getCenterX()*ratio); int cy = top + (int)((float)fe.getCenterY()*ratio); if (cx >= 0 && cx < imgWidth && cy >= 0 && cy < imgHeight) { g.setStroke(new BasicStroke(3.0f)); g.setColor(Color.BLACK); g.drawLine(cx - 4, cy, cx + 4, cy); g.drawLine(cx, cy - 4, cx, cy + 4); g.setStroke(new BasicStroke(1.0f)); g.setColor(Color.RED); g.drawLine(cx - 4, cy, cx + 4, cy); g.drawLine(cx, cy - 4, cx, cy + 4); } } } } finally { g.dispose(); g = null; target.repaint(); } } } // Updates the preview tab private void updatePreview() { previewPrepare(false); previewDisplay(); // updating buttons bPreviewCyclePrev.setEnabled(bamControlPreview.cycleGet() > 0); bPreviewCycleNext.setEnabled(bamControlPreview.cycleGet() < bamControlPreview.cycleCount() - 1); bPreviewFramePrev.setEnabled(bamControlPreview.cycleGetFrameIndex() > 0); bPreviewFrameNext.setEnabled(bamControlPreview.cycleGetFrameIndex() < bamControlPreview.cycleFrameCount() - 1); lPreviewCycle.setText(String.format("Cycle: %1$d/%2$d", bamControlPreview.cycleGet(), bamControlPreview.cycleCount() - 1)); lPreviewFrame.setText(String.format("Frame: %1$d/%2$d", bamControlPreview.cycleGetFrameIndex(), bamControlPreview.cycleFrameCount() - 1)); } // Updates relevant components to reflect the current state of the filters list private void updateFilterList() { // updating button states int idx = listFilters.getSelectedIndex(); bFiltersUp.setEnabled(!modelFilters.isEmpty() && idx > 0); bFiltersDown.setEnabled(!modelFilters.isEmpty() && idx >= 0 && idx < modelFilters.size() - 1); bFiltersRemove.setEnabled(!modelFilters.isEmpty() && !listFilters.isSelectionEmpty()); bFiltersRemoveAll.setEnabled(!modelFilters.isEmpty()); updateFilterInfo(); updateFilterControls(); filterSetPreviewFrame(filterGetPreviewFrameIndex(), true); } // Updates the filter info box private void updateFilterInfo() { final String fmt = "Name: %1$s\n\nDescription:\n%2$s"; int idx = listFilters.getSelectedIndex(); if (idx >= 0) { BamFilterBase filter = modelFilters.get(idx); taFiltersDesc.setText(String.format(fmt, filter.getName(), filter.getDescription())); } else { taFiltersDesc.setText(String.format(fmt, "", "")); } } // Updates the filter settings section with the controls of the currently selected filter private void updateFilterControls() { pFiltersSettings.removeAll(); int idx = listFilters.getSelectedIndex(); if (idx >= 0) { BamFilterBase filter = modelFilters.get(idx); pFiltersSettings.add(filter.getControls(), BorderLayout.CENTER); } else { // insert empty dummy control pFiltersSettings.add(new JPanel(), BorderLayout.CENTER); } pFiltersSettings.validate(); pFiltersSettings.repaint(); } // Adjusts cycle indices after adding frames to the global frames list private void updateCyclesAddedFrames(int[] indices) { if (indices != null && indices.length > 0) { Arrays.sort(indices); for (final int index: indices) { for (int i = 0; i < modelCycles.getSize(); i++) { PseudoBamCycleEntry cycle = modelCycles.getElementAt(i); for (int j = 0; j < cycle.size(); j++) { int idx = cycle.get(j); if (idx >= index) { cycle.set(j, idx + 1); } } } } updateCurrentCycle(); } } // Adjusts cycle indices after removing frames from the global frames list private void updateCyclesRemovedFrames(int[] indices) { if (indices != null && indices.length > 0) { Arrays.sort(indices); for (int x = indices.length - 1; x >= 0; x--) { int index = indices[x]; for (int i = 0; i < modelCycles.getSize(); i++) { PseudoBamCycleEntry cycle = modelCycles.getElementAt(i); int j = 0; while (j < cycle.size()) { int idx = cycle.get(j); if (idx == index) { cycle.remove(j, 1); continue; } else if (idx > index) { cycle.set(j, idx - 1); } j++; } } } updateCurrentCycle(); } } // Adjusts cycle indices after moving frames within the global frames list private void updateCyclesMovedFrames(int index, int shift) { if (index >= 0 && shift != 0) { for (int i = 0; i < modelCycles.getSize(); i++) { PseudoBamCycleEntry cycle = modelCycles.getElementAt(i); for (int j = 0; j < cycle.size(); j++) { if (cycle.get(j) == index) { cycle.set(j, cycle.get(j) + shift); } else if (shift > 0 && cycle.get(j) > index && cycle.get(j) <= index+shift) { cycle.set(j, cycle.get(j) - 1); } else if (shift < 0 && cycle.get(j) < index && cycle.get(j) >= index+shift) { cycle.set(j, cycle.get(j) + 1); } } } updateCurrentCycle(); } } // Action for "Compress frame" private void framesUpdateCompressFrame() { int[] indices = listFrames.getSelectedIndices(); if (indices != null) { Boolean b = Boolean.valueOf(cbCompressFrame.isSelected()); for (int i = 0; i < indices.length; i++) { getBamDecoder(BAM_ORIGINAL).getFrameInfo(indices[i]).setOption(PseudoBamDecoder.OPTION_BOOL_COMPRESSED, b); } outputSetModified(true); } } // Evaluates the value in the specified "Center X/Y" text field private void framesValidateCenterValue(JTextField tf) { if (tf != null) { boolean isCenterX = (tf == tfFrameCenterX); int[] indices = listFrames.getSelectedIndices(); if (indices != null && indices.length > 0) { String s = tf.getText(); boolean isRelative; if (s.startsWith("++")) { s = s.substring(2); isRelative = true; } else if (s.startsWith("--")) { s = s.substring(1); isRelative = true; } else { isRelative = false; } int value = numberValidator(s, Short.MIN_VALUE, Short.MAX_VALUE, Integer.MAX_VALUE); if (value != Integer.MAX_VALUE) { int result = 0; for(int i = 0; i < indices.length; i++) { if (isCenterX) { if (isRelative) { result = getBamDecoder(BAM_ORIGINAL).getFrameInfo(indices[i]).getCenterX() + value; result = Math.min(Math.max(result, Short.MIN_VALUE), Short.MAX_VALUE); getBamDecoder(BAM_ORIGINAL).getFrameInfo(indices[i]).setCenterX(result); } else { getBamDecoder(BAM_ORIGINAL).getFrameInfo(indices[i]).setCenterX(value); } outputSetModified(true); } else { if (isRelative) { result = getBamDecoder(BAM_ORIGINAL).getFrameInfo(indices[i]).getCenterY() + value; result = Math.min(Math.max(result, Short.MIN_VALUE), Short.MAX_VALUE); getBamDecoder(BAM_ORIGINAL).getFrameInfo(indices[i]).setCenterY(result); } else { getBamDecoder(BAM_ORIGINAL).getFrameInfo(indices[i]).setCenterY(value); } outputSetModified(true); } } if (isRelative) { if (indices.length == 1) { tf.setText(Integer.toString(result)); } else { tf.setText(""); } } } else { updateFrameInfo(indices); } updateQuickPreview(rcFramesPreview, indices, true); } } } // Action for "Add..."->"Add file(s)...": adds image files to the frames list public void framesAddFiles() { Path[] files = getOpenFileName(this, "Choose file(s) to add", null, true, getGraphicsFilters(), 0); if (files != null) { try { WindowBlocker.blockWindow(this, true); framesAdd(files); } finally { WindowBlocker.blockWindow(this, false); } } } // Action for "Add..."->"Add resource(s)...": adds image resources to the frames list public void framesAddResources() { ResourceEntry[] entries = OpenResourceDialog.showOpenDialog(this, "Choose resource(s) to add", new String[]{"BAM", "BMP"}, true); if (entries != null) { try { WindowBlocker.blockWindow(this, true); framesAdd(entries); } finally { WindowBlocker.blockWindow(this, false); } } } // Called by framesAddLauncher. Can also be called directly. Makes use of a progress monitor if available. public void framesAdd(Path[] files) { if (files != null) { ResourceEntry[] entries = new ResourceEntry[files.length]; for (int i = 0; i < files.length; i++) { entries[i] = new FileResourceEntry(files[i]); } framesAdd(entries); } } public void framesAdd(ResourceEntry[] entries) { if (entries != null) { outputSetModified(true); List<String> skippedFiles = new ArrayList<String>(); int insertIndex = listFrames.getSelectedIndex(); int idx = insertIndex; if (idx < 0) idx = modelFrames.getSize() - 1; try { for (int i = 0; i < entries.length; i++) { if (isProgressMonitorActive()) { // updating progress monitor if (isProgressMonitorCancelled()) { // adding remaining files to skip list for (int j = i; j < entries.length; j++) { if (entries[j] != null) { skippedFiles.add(entries[j].toString()); } } break; } advanceProgressMonitor(String.format("Adding file %1$d/%2$d", i+1, entries.length)); } // adding files to global frames list if (entries[i] != null) { if (BamDecoder.isValid(entries[i])) { BamDecoder decoder = framesAddBam(idx + 1, entries[i]); if (decoder != null) { idx += decoder.frameCount(); } else { skippedFiles.add(entries[i].toString()); } } else { if (framesAddImage(idx + 1, entries[i], -1)) { idx++; } else { skippedFiles.add(entries[i].toString()); } } } } } finally { releaseProgressMonitor(); } listFrames.setSelectedIndex(idx); listFrames.ensureIndexIsVisible(idx); // adjusting cycle indices // int[] indices = new int[entries.length - skippedFiles.size()]; int[] indices = new int[idx - insertIndex]; // for (int i = 0; i < entries.length - skippedFiles.size(); i++) { for (int i = 0; i < indices.length; i++) { indices[i] = insertIndex + 1 + i; } updateCyclesAddedFrames(indices); updateFramesList(); listFrames.requestFocusInWindow(); // error handling if (!skippedFiles.isEmpty()) { StringBuilder sb = new StringBuilder(); if (skippedFiles.size() == 1) { sb.append(String.format("%1$d file has been skipped:\n", skippedFiles.size())); } else { sb.append(String.format("%1$d files have been skipped:\n", skippedFiles.size())); } int count = Math.min(5, skippedFiles.size()); for (int i = 0; i < count; i++) { sb.append(String.format(" - %1$s\n", skippedFiles.get(i))); } if (skippedFiles.size() > 5) { sb.append(" - ...\n"); } JOptionPane.showMessageDialog(this, sb.toString(), "Error", JOptionPane.ERROR_MESSAGE); } } } // Specific: Adds the specified source BAM to the frames list. // Returns the BamDecoder object of the source BAM, or null on error public BamDecoder framesAddBam(int listIndex, Path file) { return framesAddBam(listIndex, new FileResourceEntry(file)); } // Specific: Adds the specified source BAM to the frames list. // Returns the BamDecoder object of the source BAM, or null on error public BamDecoder framesAddBam(int listIndex, ResourceEntry entry) { if (listIndex >= 0 && entry != null && BamDecoder.isValid(entry)) { BamDecoder decoder = BamDecoder.loadBam(entry); BamDecoder.BamControl control = decoder.createControl(); control.setMode(BamDecoder.BamControl.Mode.INDIVIDUAL); // preparing palette-specific properties IndexColorModel cm = null; if (decoder instanceof BamV1Decoder) { int[] palette = ((BamV1Decoder.BamV1Control)control).getPalette(); int transColor = ((BamV1Decoder.BamV1Control)control).getTransparencyIndex(); cm = new IndexColorModel(8, 256, palette, 0, false, transColor, DataBuffer.TYPE_BYTE); } for (int j = 0; j < decoder.frameCount(); j++) { if (framesAddBamFrame(listIndex + j, decoder, control, j, cm) < 0) { return null; } } return decoder; } return null; } // Adds a single BAM frame to the frame list private int framesAddBamFrame(int listIndex, BamDecoder decoder, BamDecoder.BamControl control, int frameIndex, IndexColorModel cm) { if (decoder != null && frameIndex >= 0 && frameIndex < decoder.frameCount()) { listIndex = Math.max(0, Math.min(modelFrames.getSize(), listIndex)); if (control == null) { control = decoder.createControl(); } // preparing palette-specific properties if (cm == null && decoder instanceof BamV1Decoder) { int[] palette = ((BamV1Decoder.BamV1Control)control).getPalette(); int transColor = ((BamV1Decoder.BamV1Control)control).getTransparencyIndex(); cm = new IndexColorModel(8, 256, palette, 0, false, transColor, DataBuffer.TYPE_BYTE); } // adding frame boolean isCompressed; int rleIndex; BamDecoder.FrameEntry fe = decoder.getFrameInfo(frameIndex); BufferedImage image = null; if (cm != null) { if (fe.getWidth() > 0 && fe.getHeight() > 0) { image = new BufferedImage(fe.getWidth(), fe.getHeight(), BufferedImage.TYPE_BYTE_INDEXED, cm); } else { image = new BufferedImage(1, 1, BufferedImage.TYPE_BYTE_INDEXED, cm); } isCompressed = ((BamV1Decoder.BamV1FrameEntry)fe).isCompressed(); rleIndex = ((BamV1Decoder)decoder).getRleIndex(); } else { if (fe.getWidth() > 0 && fe.getHeight() > 0) { image = new BufferedImage(fe.getWidth(), fe.getHeight(), BufferedImage.TYPE_INT_ARGB); } else { image = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB); } isCompressed = false; rleIndex = 0; } if (fe.getWidth() > 0 && fe.getHeight() > 0) { decoder.frameGet(control, frameIndex, image); } modelFrames.insert(listIndex, image, new Point(fe.getCenterX(), fe.getCenterY())); // setting required extra options PseudoBamFrameEntry fe2 = getBamDecoder(BAM_ORIGINAL).getFrameInfo(listIndex); ResourceEntry entry = decoder.getResourceEntry(); if (entry instanceof FileResourceEntry) { fe2.setOption(BAM_FRAME_OPTION_PATH, entry.getActualPath().toString()); } else { fe2.setOption(BAM_FRAME_OPTION_PATH, BAM_FRAME_PATH_BIFF + entry.getResourceName()); } fe2.setOption(BAM_FRAME_OPTION_SOURCE_INDEX, Integer.valueOf(frameIndex)); fe2.setOption(PseudoBamDecoder.OPTION_STRING_LABEL, entry.getResourceName() + ":" + frameIndex); fe2.setOption(PseudoBamDecoder.OPTION_BOOL_COMPRESSED, Boolean.valueOf(isCompressed)); fe2.setOption(PseudoBamDecoder.OPTION_INT_RLEINDEX, Integer.valueOf(rleIndex)); return listIndex; } return -1; } private boolean framesAddImage(int listIndex, Path file, int frameIndex) { if (file != null) { return framesAddImage(listIndex, new FileResourceEntry(file), frameIndex); } else { return false; } } /** * Adds the specified source graphics file to the frames list. * @param listIndex The start position of the image or images in the frames list. * @param entry The resource entry pointing to the graphics file. * @param frameIndex An optional index for graphics files with multiple frames. * This is relevant for loading specific frames from animated GIFs. * Specify {@code -1} to load all available frames. * @return {@code true} if image or images have been successfully added to the frames list. */ private boolean framesAddImage(int listIndex, ResourceEntry entry, int frameIndex) { boolean retVal = false; if (listIndex >= 0 && entry != null) { try { InputStream is = entry.getResourceDataAsStream(); ImageReader reader = (ImageReader)ImageIO.getImageReadersBySuffix(entry.getExtension()).next(); reader.setInput(ImageIO.createImageInputStream(is), false); int numFrames = reader.getNumImages(true); retVal = (numFrames > 0); for (int frameIdx = 0, curFrameIdx = 0; frameIdx < numFrames; frameIdx++) { BufferedImage image = reader.read(frameIdx); if (image == null) { retVal = false; break; } if (frameIndex >= 0 && frameIdx != frameIndex) { // skip if frame has not been requested continue; } // transparency detection for paletted images if (image.getType() == BufferedImage.TYPE_BYTE_INDEXED) { if (!((IndexColorModel)image.getColorModel()).hasAlpha()) { int[] cmap = new int[256]; int transIndex = -1; IndexColorModel srcCm = (IndexColorModel)image.getColorModel(); int numColors = 1 << srcCm.getPixelSize(); for (int i = 0; i < numColors; i++) { cmap[i] = (srcCm.getRed(i) << 16) | (srcCm.getGreen(i) << 8) | srcCm.getBlue(i); // marking first occurence of "Green" as transparent if (transIndex < 0 && cmap[i] == 0x0000ff00) { transIndex = i; } } // fallback to index 0 as transparent color if (transIndex < 0) { transIndex = 0; } // Adding transparency to image IndexColorModel cm = new IndexColorModel(8, 256, cmap, 0, false, transIndex, DataBuffer.TYPE_BYTE); BufferedImage dstImage = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_BYTE_INDEXED, cm); byte[] srcBuffer = ((DataBufferByte)image.getRaster().getDataBuffer()).getData(); byte[] dstBuffer = ((DataBufferByte)dstImage.getRaster().getDataBuffer()).getData(); System.arraycopy(srcBuffer, 0, dstBuffer, 0, srcBuffer.length); srcBuffer = null; dstBuffer = null; cmap = null; image = dstImage; } } modelFrames.insert(listIndex + curFrameIdx, image, new Point()); // setting required extra options PseudoBamFrameEntry fe2 = modelFrames.getDecoder().getFrameInfo(listIndex + curFrameIdx); if (entry instanceof FileResourceEntry) { fe2.setOption(BAM_FRAME_OPTION_PATH, entry.getActualPath().toString()); } else { fe2.setOption(BAM_FRAME_OPTION_PATH, BAM_FRAME_PATH_BIFF + entry.getResourceName()); } fe2.setOption(BAM_FRAME_OPTION_SOURCE_INDEX, Integer.valueOf(frameIdx)); if (numFrames > 1) { fe2.setOption(PseudoBamDecoder.OPTION_STRING_LABEL, entry.getResourceName() + ":" + frameIdx); } else { fe2.setOption(PseudoBamDecoder.OPTION_STRING_LABEL, entry.getResourceName()); } curFrameIdx++; } } catch (Exception e) { retVal = false; e.printStackTrace(); } } return retVal; } // Action for "Import BAM file...": loads a complete frames/cycles structure public void framesImportBamFile() { Path[] files = getOpenFileName(this, "Import BAM file", null, false, new FileNameExtensionFilter[]{getBamFilter()}, 0); if (files != null && files.length > 0) { try { WindowBlocker.blockWindow(this, true); framesImportBam(StreamUtils.replaceFileExtension(files[0], "BAM")); } finally { WindowBlocker.blockWindow(this, false); } } } // Action for "Import BAM resource...": loads a complete frames/cycles structure public void framesImportBamResource() { ResourceEntry[] entries = OpenResourceDialog.showOpenDialog(this, "Import BAM resource", new String[]{"BAM"}, false); if (entries != null && entries.length > 0) { try { WindowBlocker.blockWindow(this, true); framesImportBam(entries[0]); } finally { WindowBlocker.blockWindow(this, false); } } } // Specific: Loads the whole frames/cycles structure from the specified BAM file public void framesImportBam(Path file) { framesImportBam(new FileResourceEntry(file)); } // Specific: Loads the whole frames/cycles structure from the specified BAM ResourceEntry object public void framesImportBam(ResourceEntry entry) { if (entry != null) { outputSetModified(true); boolean cancelled = false; boolean replace = true; if (!modelFrames.isEmpty()) { String[] options = {"Append", "Replace", "Cancel"}; String msg = "What do you want to do with the selected BAM file?"; int ret = JOptionPane.showOptionDialog(this, msg, "Question", JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, null, options, options[0]); cancelled = (ret == 2 || ret == JOptionPane.CLOSED_OPTION); replace = (ret == 1); } if (!cancelled) { if (replace) { clear(); } int frameBase = modelFrames.getSize(); BamDecoder decoder = framesAddBam(frameBase, entry); if (decoder != null) { // initializing cycles section BamDecoder.BamControl control = decoder.createControl(); int cycleBase = modelCycles.getSize(); for (int i = 0; i < control.cycleCount(); i++) { int[] indices = new int[control.cycleFrameCount(i)]; for (int j = 0; j < control.cycleFrameCount(i); j++) { indices[j] = control.cycleGetFrameIndexAbsolute(i, j)+frameBase; } cyclesAdd(i+cycleBase, indices); } // updating GUI controls updateFramesList(); listFrames.setSelectedIndex(0); listFrames.ensureIndexIsVisible(listFrames.getSelectedIndex()); listFrames.requestFocusInWindow(); if (!modelCycles.isEmpty()) { listCycles.setSelectedIndex(0); updateCyclesList(); } } else { JOptionPane.showMessageDialog(this, "Error while importing BAM file " + entry.getResourceName(), "Error", JOptionPane.ERROR_MESSAGE); } } } } // Action for "Add..."->"Add folder...": adds all supported files from the specified folder public void framesAddFolder() { framesAddFolder(getOpenPathName(this, "Select folder", null)); } public void framesAddFolder(Path path) { if (path != null && Files.isDirectory(path)) { // preparing list of valid files FileNameExtensionFilter filters = getGraphicsFilters()[0]; List<Path> validFiles = new ArrayList<>(); try (DirectoryStream<Path> dstream = Files.newDirectoryStream(path)) { for (final Path file: dstream) { if (Files.isRegularFile(file) && filters.accept(file.toFile())) { validFiles.add(file); } } } catch (IOException e) { e.printStackTrace(); return; } // adding entries to frames list try { WindowBlocker.blockWindow(this, true); framesAdd(validFiles.toArray(new Path[validFiles.size()])); } finally { WindowBlocker.blockWindow(this, false); } } } // Action for "Remove": removes the selected frame entry/entries from the frames list, updates cycle structures public void framesRemove() { try { WindowBlocker.blockWindow(this, true); int[] indices = listFrames.getSelectedIndices(); if (indices.length == modelFrames.getSize()) { framesRemoveAll(); } else { framesRemove(indices); } } finally { WindowBlocker.blockWindow(this, false); } } // Specific: Removes the selected frames from the frames list public void framesRemove(int[] indices) { if (indices != null && indices.length > 0) { outputSetModified(true); Arrays.sort(indices); int curIdx = indices[indices.length - 1] - indices.length + 1; for (int i = indices.length - 1; i >= 0; i--) { if (indices[i] >= 0 && indices[i] < modelFrames.getSize()) { modelFrames.remove(indices[i]); } } updateCyclesRemovedFrames(indices); updateFramesList(); curIdx = Math.min(modelFrames.getSize() - 1, curIdx); listFrames.setSelectedIndex(curIdx); listFrames.ensureIndexIsVisible(curIdx); listFrames.requestFocusInWindow(); } } // Action for "Remove all": removes all frame entries from frames list, updatees cycle structures public void framesRemoveAll() { outputSetModified(true); modelFrames.clear(); updateFramesList(); listFrames.requestFocusInWindow(); } // Action for "Up" button next to frames list private void framesMoveUp() { outputSetModified(true); int[] indices = listFrames.getSelectedIndices(); for (int i = 0; i < indices.length; i++) { if (indices[i] > 0 && indices[i] < modelFrames.getSize()) { modelFrames.move(indices[i], -1); updateCyclesMovedFrames(indices[i], -1); indices[i]--; } } listFrames.setSelectedIndices(indices); updateFramesList(); listFrames.requestFocusInWindow(); } // Action for "Down" button next to frames list private void framesMoveDown() { outputSetModified(true); int[] indices = listFrames.getSelectedIndices(); for (int i = indices.length - 1; i >= 0; i--) { if (indices[i] >= 0 && indices[i] < modelFrames.getSize() - 1) { modelFrames.move(indices[i], 1); updateCyclesMovedFrames(indices[i], 1); indices[i]++; } } listFrames.setSelectedIndices(indices); updateFramesList(); listFrames.requestFocusInWindow(); } // Action for "Drop unused frames": return number of unused frames private int framesGetUnusedFramesCount() { return framesGetUnusedFramesCount(framesGetUnusedFrames()); } // Action for "Drop unused frames": return number of unused frames private int framesGetUnusedFramesCount(BitSet framesUsed) { int retVal = 0; if (framesUsed != null) { for (int i = 0; i < modelFrames.getSize(); i++) { if (!framesUsed.get(i)) { retVal++; } } } return retVal; } // Action for "Drop unused frames": returns a bitset that maps the used state of all frames private BitSet framesGetUnusedFrames() { BitSet framesUsed = new BitSet(modelFrames.getSize()); for (int i = 0; i < modelCycles.getSize(); i++) { PseudoBamCycleEntry cycle = modelCycles.getElementAt(i); for (int j = 0; j < cycle.size(); j++) { int idx = cycle.get(j); if (idx >= 0) { framesUsed.set(idx); } } } return framesUsed; } // Action for "Drop unused frames": removes unused frames and adjusts cycle indices respectively private void framesDropUnusedFrames() { BitSet framesUsed = framesGetUnusedFrames(); int count = framesGetUnusedFramesCount(framesUsed); if (count > 0) { int[] indices = new int[count]; for (int i = 0, idx = 0; i < modelFrames.getSize(); i++) { if (!framesUsed.get(i)) { indices[idx] = i; idx++; } } framesRemove(indices); } } // Action for "Add cycle": adds a new empty cycle at the current cycle index to the cycles list private void cyclesAdd() { int idx = listCycles.getSelectedIndex() + 1; if (idx < 0) idx = modelCycles.getSize(); cyclesAdd(idx, new int[0]); } // Specific: adds a new cycle with the specified frame indices at the specified position private void cyclesAdd(int index, int[] indices) { if (index < 0) index = 0; else if (index > modelCycles.getSize()) index = modelCycles.getSize(); if (indices == null) indices = new int[0]; modelCycles.insert(index, indices); listCycles.setSelectedIndex(index); listCycles.ensureIndexIsVisible(index); updateCyclesList(); } // Action for "Remove cycle": removes the selected cycles from the cycles list private void cyclesRemove() { int[] indices = listCycles.getSelectedIndices(); if (indices.length > 0) { for (int i = indices.length - 1; i >= 0; i--) { if (indices[i] >= 0 && indices[i] < modelCycles.getSize()) { cyclesRemove(indices[i]); } } } } // Specific: removes the specified cycle from the cycles list private void cyclesRemove(int index) { if (index >= 0 && index < modelCycles.getSize()) { modelCycles.remove(index, 1); if (index > 0) { index--; } if (!modelCycles.isEmpty()) { listCycles.setSelectedIndex(index); } updateCyclesList(); } } // Action for "Remove all": removes all cycles from the cycles list private void cyclesRemoveAll() { modelCycles.clear(); updateCyclesList(); } // Action for "Up" button next to cycles list private void cyclesMoveUp() { int[] indices = listCycles.getSelectedIndices(); if (indices.length > 0) { for (int i = 0; i < indices.length; i++) { if (indices[i] > 0 && indices[i] < modelCycles.getSize()) { modelCycles.move(indices[i], -1); indices[i]--; } } listCycles.setSelectedIndices(indices); updateCyclesList(); listCycles.requestFocusInWindow(); } } // Action for "Down" button next to cycles list private void cyclesMoveDown() { int[] indices = listCycles.getSelectedIndices(); if (indices.length > 0) { for (int i = indices.length - 1; i >= 0; i--) { if (indices[i] >= 0 && indices[i] < modelCycles.getSize() - 1) { modelCycles.move(indices[i], 1); indices[i]++; } } listCycles.setSelectedIndices(indices); updateCyclesList(); listCycles.requestFocusInWindow(); } } // Action for macro "Selected cycle"->"Assign all frames": puts all available frames into the selected cycle (sorted) private void macroAssignFrames() { int[] indices = listCycles.getSelectedIndices(); for (int i = 0; i < indices.length; i++) { if (indices[i] >= 0 && indices[i] < modelCycles.getSize()) { modelCurCycle.setCycle(indices[i]); modelCurCycle.clear(); int[] frames = new int[modelFrames.getSize()]; for (int j = 0; j < frames.length; j++) { frames[j] = j; } modelCurCycle.add(frames); } } updateCyclesList(); } // Action for macro "Selected cycle"->"Remove all frames": Removes all frame indices private void macroRemoveAllFrames() { int[] indices = listCycles.getSelectedIndices(); for (int i = 0; i < indices.length; i++) { if (indices[i] >= 0 && indices[i] < modelCycles.getSize()) { modelCurCycle.setCycle(indices[i]); modelCurCycle.clear(); } } updateCyclesList(); } // Action for macro "Selected cycle"->"Duplicate cycle": Adds a duplicate below the selected cycle private void macroDuplicateCycle() { int[] indices = listCycles.getSelectedIndices(); for (int i = 0; i < indices.length; i++) { if (indices[i] >= 0 && indices[i] < modelCycles.getSize()) { int[] frames = new int[modelCycles.getElementAt(indices[i]).size()]; for (int j = 0; j < frames.length; j++) { frames[j] = modelCycles.getElementAt(indices[i]).get(j); } modelCycles.insert(indices[i]+1, frames); for (int j = i; j < indices.length; j++) { indices[j]++; } } } listCycles.setSelectedIndices(indices); updateCyclesList(); } // Action for macro "Selected cycle"->"Duplicate each frame": Duplicates each frame in the selected cycle private void macroDuplicateFrames() { int[] indices = listCycles.getSelectedIndices(); for (int i = 0; i < indices.length; i++) { if (indices[i] >= 0 && indices[i] < modelCycles.getSize()) { modelCurCycle.setCycle(indices[i]); int frameIdx = modelCurCycle.getSize() - 1; while (frameIdx >= 0) { modelCurCycle.insert(frameIdx+1, modelCurCycle.getControl().cycleGetFrameIndexAbsolute(frameIdx)); frameIdx--; } } } updateCyclesList(); } // Action for macro "Selected cycle"->"Sort frames": Sorts frame indices by value private void macroSortFrames() { int[] indices = listCycles.getSelectedIndices(); for (int i = 0; i < indices.length; i++) { if (indices[i] >= 0 && indices[i] < modelCycles.getSize()) { int[] frames = new int[modelCycles.getElementAt(indices[i]).size()]; for (int j = 0; j < frames.length; j++) { frames[j] = modelCycles.getElementAt(indices[i]).get(j); } Arrays.sort(frames); for (int j = 0; j < frames.length; j++) { modelCycles.getElementAt(indices[i]).set(j, frames[j]); } } } updateCyclesList(); } // Action for macro "Selected cycle"->"Reverse frames order": Reverses the current order of the frame indices private void macroReverseFramesOrder() { int[] indices = listCycles.getSelectedIndices(); for (int i = 0; i < indices.length; i++) { if (indices[i] >= 0 && indices[i] < modelCycles.getSize()) { int[] frames = new int[modelCycles.getElementAt(indices[i]).size()]; int len = modelCycles.getElementAt(indices[i]).size(); for (int j = 0; j < len; j++) { frames[j] = modelCycles.getElementAt(indices[i]).get(len - j - 1); } for (int j = 0; j < len; j++) { modelCycles.getElementAt(indices[i]).set(j, frames[j]); } } } updateCyclesList(); } // Action for macro "All cycles"->"Remove all frames": Removes all frame indices private void macroRemoveAllFramesGlobal() { if (!modelCycles.isEmpty()) { String msg = "Do you really want to remove all frame indices from each cycle?"; int ret = JOptionPane.showConfirmDialog(this, msg, "Question", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); if (ret == JOptionPane.YES_OPTION) { for (int i = 0; i < modelCycles.getSize(); i++) { modelCycles.getElementAt(i).clear(); } updateCyclesList(); } } } // Action for macro "All cycles"->"Reverse cycles order": Reverses the order of all cycle entries private void macroReverseCyclesOrder() { if (!modelCycles.isEmpty()) { int idx = listCycles.getSelectedIndex(); int[][] entries = new int[modelCycles.getSize()][]; for (int i = 0; i < entries.length; i++) { entries[i] = new int[modelCycles.getElementAt(i).size()]; for (int j = 0; j < entries[i].length; j++) { entries[i][j] = modelCycles.getElementAt(i).get(j); } } modelCycles.clear(); for (int i = 0; i < entries.length; i++) { modelCycles.add(entries[entries.length - i - 1]); } if (idx >= 0) { listCycles.setSelectedIndex(modelCycles.getSize() - idx - 1); listCycles.ensureIndexIsVisible(listCycles.getSelectedIndex()); } updateCyclesList(); } } // Action for "Right" button between available frames/current cycle: adds selected frames to the current cycle list private void currentCycleAdd() { int[] indices = listFramesAvail.getSelectedIndices(); if (indices != null && indices.length > 0) { Pair<Integer> dstBounds = getIndexBounds(listCurCycle.getSelectedIndices()); int dstIdx = dstBounds.getSecond() + 1; modelCurCycle.insert(dstIdx, indices); modelCycles.contentChanged(modelCurCycle.getCycle()); listFramesAvail.setSelectedIndices(new int[]{getIndexBounds(indices).getSecond()}); listCurCycle.setSelectedIndex(dstIdx + indices.length - 1); updateCurrentCycle(); } } // Action for "Left" button between available frames/current cycle: removes selected frames from the current cycle list private void currentCycleRemove() { int[] indices = listCurCycle.getSelectedIndices(); if (indices != null && indices.length > 0) { for (int i = indices.length - 1; i >= 0; i--) { modelCurCycle.remove(indices[i], 1); } modelCycles.contentChanged(listCycles.getSelectedIndex()); listCurCycle.setSelectedIndices(new int[0]); updateCurrentCycle(); } } // Action for "Up" button of current cycle list private void currentCycleMoveUp() { int[] indices = listCurCycle.getSelectedIndices(); for (int i = 0; i < indices.length; i++) { if (indices[i] > 0 && indices[i] < modelCurCycle.getSize()) { modelCurCycle.move(indices[i], -1); indices[i]--; } } modelCycles.contentChanged(listCycles.getSelectedIndex()); listCurCycle.setSelectedIndices(indices); listCurCycle.requestFocusInWindow(); updateCurrentCycle(); } // Action for "Down" button of current cycle list private void currentCycleMoveDown() { int[] indices = listCurCycle.getSelectedIndices(); for (int i = indices.length - 1; i >= 0; i--) { if (indices[i] >= 0 && indices[i] < modelCurCycle.getSize() - 1) { modelCurCycle.move(indices[i], 1); indices[i]++; } } modelCycles.contentChanged(listCycles.getSelectedIndex()); listCurCycle.setSelectedIndices(indices); listCurCycle.requestFocusInWindow(); updateCurrentCycle(); } // Action for "Play/Pause": start/pause playback without resetting current frame private void previewPlay() { if (previewIsPlaying()) { timer.stop(); isPreviewPlaying = false; bPreviewPlay.setText("Play"); bPreviewPlay.setIcon(Icons.getIcon(Icons.ICON_PLAY_16)); bPreviewStop.setEnabled(true); updatePreview(); } else { isPreviewPlaying = true; bPreviewPlay.setText("Pause"); bPreviewPlay.setIcon(Icons.getIcon(Icons.ICON_PAUSE_16)); bPreviewStop.setEnabled(true); timer.start(); } } // Action for "Stop": stops playback and resets current frame to 1 private void previewStop() { timer.stop(); isPreviewPlaying = false; bPreviewPlay.setText("Play"); bPreviewPlay.setIcon(Icons.getIcon(Icons.ICON_PLAY_16)); bPreviewStop.setEnabled(false); if (bamControlPreview != null) { bamControlPreview.cycleSetFrameIndex(0); } updatePreview(); } // Returns whether playback is active private boolean previewIsPlaying() { return isPreviewPlaying; } // Action for "Next cycle" button: selects next cycle index if available private void previewCycleUp() { if (bamControlPreview.cycleGet() < bamControlPreview.cycleCount() - 1) { bamControlPreview.cycleSet(bamControlPreview.cycleGet() + 1); bamControlPreview.cycleSetFrameIndex(0); updatePreview(); } } // Action for "Previous cycle" button: selects previous cycle index if available private void previewCycleDown() { if (bamControlPreview.cycleGet() > 0) { bamControlPreview.cycleSet(bamControlPreview.cycleGet() - 1); bamControlPreview.cycleSetFrameIndex(0); updatePreview(); } } // Action for "Next frame" button: selects next frame index if available private void previewFrameUp() { if (bamControlPreview.cycleGetFrameIndex() < bamControlPreview.cycleFrameCount() - 1) { bamControlPreview.cycleSetFrameIndex(bamControlPreview.cycleGetFrameIndex() + 1); updatePreview(); } } // Action for "Previous frame" button: selects previous frame index if available private void previewFrameDown() { if (bamControlPreview.cycleGetFrameIndex() > 0) { bamControlPreview.cycleSetFrameIndex(bamControlPreview.cycleGetFrameIndex() - 1); updatePreview(); } } // Returns the current playback mode private int previewGetMode() { return cbPreviewMode.getSelectedIndex(); } // Sets the current playback mode private void previewSetMode(int mode) { if (mode >= 0 && mode < cbPreviewMode.getItemCount()) { cbPreviewMode.setSelectedIndex(mode); } } // Sets a new frame rate private void previewSetFrameRate(double fps) { try { sPreviewFps.setValue(Double.valueOf(fps)); currentFps = (Double)sPreviewFps.getValue(); timer.setDelay((int)(1000.0 / currentFps)); } catch (IllegalArgumentException e) { } } // Returns whether the preview is in zoom mode private boolean previewIsZoomed() { return cbPreviewZoom.isSelected(); } // Sets the zoom mode of the preview private void previewSetZoom(boolean set) { cbPreviewZoom.setSelected(set); previewPrepare(false); } // Returns whether markers are visible private boolean previewIsMarkerVisible() { return cbPreviewShowMarker.isSelected(); } // Sets the visibility state of markers private void previewSetMarkerVisible(boolean set) { cbPreviewShowMarker.setSelected(set); previewPrepare(false); } // Advances the animation by one step, depending on the current playback mode. Returns false // Returns false if no more advancements can be done. private boolean previewAdvanceAnimation() { boolean retVal = true; int retries = bamControlPreview.cycleCount(); // advancing frame depends on current playback mode do { if (bamControlPreview.cycleGetFrameIndex() >= bamControlPreview.cycleFrameCount() - 1) { switch (previewGetMode()) { case MODE_CURRENT_CYCLE_ONCE: bamControlPreview.cycleSetFrameIndex(0); retVal = false; break; case MODE_CURRENT_CYCLE_LOOPED: bamControlPreview.cycleSetFrameIndex(0); break; case MODE_ALL_CYCLES_ONCE: if (bamControlPreview.cycleGet() == bamControlPreview.cycleCount() - 1) { bamControlPreview.cycleSet(0); retVal = false; } else { bamControlPreview.cycleSet(bamControlPreview.cycleGet() + 1); } bamControlPreview.cycleSetFrameIndex(0); break; case MODE_ALL_CYCLES_LOOPED: if (bamControlPreview.cycleGet() == bamControlPreview.cycleCount() - 1) { bamControlPreview.cycleSet(0); } else { bamControlPreview.cycleSet(bamControlPreview.cycleGet() + 1); } bamControlPreview.cycleSetFrameIndex(0); break; default: retVal = false; } } else { bamControlPreview.cycleSetFrameIndex(bamControlPreview.cycleGetFrameIndex() + 1); } } while (retVal && retries-- > 0 && bamControlPreview.cycleGetFrameIndex() >= bamControlPreview.cycleFrameCount()); updatePreview(); return retVal; } // Resets the current animation (can used as a reaction to the IsModified flag) private void previewValidate() { previewPrepare(true); updatePreview(); } // Updates the BAM control instance for previews private void previewInitControl() { if (bamControlPreview == null) { bamControlPreview = bamDecoderFinal.createControl(); bamControlPreview.setMode(BamDecoder.BamControl.Mode.SHARED); bamControlPreview.setSharedPerCycle(true); } else { int idx = bamControlPreview.cycleGet(); if (idx < 0) { bamControlPreview.cycleSet(0); bamControlPreview.cycleReset(); } else if (idx >= bamControlPreview.cycleCount()) { bamControlPreview.cycleSet(bamControlPreview.cycleCount() - 1); bamControlPreview.cycleReset(); } } } // Initializes the preview private void previewPrepare(boolean update) { if (update) { // updating finalized BAM structure try { updateFilteredBamDecoder(getBamVersion(), false); } catch (Exception e) { e.printStackTrace(); } } previewInitControl(); int zoom = previewIsZoomed() ? 2 : 1; Dimension dim = bamControlPreview.getSharedDimension(); if (previewCanvas == null || previewCanvas.getWidth() != dim.width|| previewCanvas.getHeight() != dim.height) { previewCanvas = new BufferedImage(dim.width, dim.height, BufferedImage.TYPE_INT_ARGB); } boolean sizeChanged = false; dim.width += 2; dim.height += 2; // adjusting for preview markers if (rcPreview.getImage() == null || rcPreview.getImage().getWidth(null) != dim.width || rcPreview.getImage().getHeight(null) != dim.height) { rcPreview.setImage(ColorConvert.createCompatibleImage(dim.width, dim.height, true)); sizeChanged = true; } if (rcPreview.getPreferredSize().width != dim.width*zoom || rcPreview.getPreferredSize().height != dim.height*zoom) { rcPreview.setPreferredSize(new Dimension(dim.width*zoom, dim.height*zoom)); rcPreview.setMinimumSize(rcPreview.getPreferredSize()); rcPreview.setSize(rcPreview.getPreferredSize()); sizeChanged = true; } if (sizeChanged) { rcPreview.invalidate(); scrollPreview.setMinimumSize(rcPreview.getPreferredSize()); scrollPreview.invalidate(); scrollPreview.getParent().validate(); } previewDisplay(); } // Displays the current frame on screen private synchronized void previewDisplay() { if (bamControlPreview != null && previewCanvas != null && rcPreview.getImage() != null) { // clearing old content Graphics2D g = previewCanvas.createGraphics(); try { g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC)); g.setColor(new Color(0, true)); g.fillRect(0, 0, previewCanvas.getWidth(), previewCanvas.getHeight()); } finally { g.dispose(); g = null; } // producing frame data bamControlPreview.cycleGetFrame(previewCanvas); // drawing markers g = (Graphics2D)rcPreview.getImage().getGraphics(); try { // clearing old content g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC)); g.setColor(new Color(0, true)); g.fillRect(0, 0, rcPreview.getImage().getWidth(null), rcPreview.getImage().getHeight(null)); // rendering frame g.drawImage(previewCanvas, 1, 1, null); if (previewIsMarkerVisible()) { Point origin = bamControlPreview.getSharedOrigin(); int frameIdx = bamControlPreview.cycleGetFrameIndexAbsolute(); int centerX = bamDecoderFinal.getFrameInfo(frameIdx).getCenterX(); int centerY = bamDecoderFinal.getFrameInfo(frameIdx).getCenterY(); int imgWidth = bamDecoderFinal.getFrameInfo(frameIdx).getWidth(); int imgHeight = bamDecoderFinal.getFrameInfo(frameIdx).getHeight(); // drawing bounding box g.setColor(new Color(0x00A000)); int x = -origin.x - centerX; int y = -origin.y - centerY; g.drawRect(x, y, imgWidth + 1, imgHeight + 1); // drawing center marker (if frame is big enough) int msize = 8; if (msize > imgWidth) msize = imgWidth; if (imgWidth > imgHeight) msize = imgHeight; if (imgWidth > 3 && imgHeight > 3) { x = -origin.x + 1; y = -origin.y + 1; g.setStroke(new BasicStroke(3.0f)); g.setColor(Color.BLACK); g.drawLine(x - 4, y, x + 4, y); g.drawLine(x, y - 4, x, y + 4); g.setStroke(new BasicStroke(1.0f)); g.setColor(Color.RED); g.drawLine(x - 4, y, x + 4, y); g.drawLine(x, y - 4, x, y + 4); } } } finally { g.dispose(); g = null; } } rcPreview.repaint(); } // Inserts a new filter into the filters list private BamFilterBase filterAdd() { BamFilterFactory.FilterInfo fi = (BamFilterFactory.FilterInfo)cbFiltersAdd.getSelectedItem(); if (fi != null) { return filterAdd(fi); } return null; } // Inserts the specified filter into the filters list private BamFilterBase filterAdd(BamFilterFactory.FilterInfo info) { if (info != null) { int idx = listFilters.getSelectedIndex(); // check if the filter can be inserted Class<? extends BamFilterBase> filterClass = info.getFilterClass(); if (BamFilterBaseOutput.class.isAssignableFrom(filterClass)) { for (int i = 0; i < modelFilters.getSize(); i++) { BamFilterBase filter = modelFilters.get(i); if (filter.getClass().equals(filterClass)) { listFilters.setSelectedIndex(i); return null; } } } // insert new filter BamFilterBase filter = BamFilterFactory.createInstance(this, filterClass); if (filter != null) { filter.addChangeListener(this); modelFilters.add(idx+1, filter); listFilters.setSelectedIndex(idx+1); outputSetModified(true); } else { JOptionPane.showMessageDialog(this, "Unable to create selected filter.", "Error", JOptionPane.ERROR_MESSAGE); } return filter; } return null; } // Removes the currently selected filter private void filterRemove() { filterRemove(listFilters.getSelectedIndex()); } // Removes the specified filter from the filters list private void filterRemove(int index) { if (index >= 0 && index < modelFilters.size()) { BamFilterBase filter = modelFilters.get(index); filter.close(); modelFilters.remove(index); if (index < modelFilters.size()) { listFilters.setSelectedIndex(index); } else { listFilters.setSelectedIndex(index - 1); } updateFilterList(); outputSetModified(true); } } // Removes all filters from the filters list private void filterRemoveAll() { listFilters.setSelectedIndex(-1); for (int i = modelFilters.size() - 1; i >= 0; i--) { BamFilterBase filter = modelFilters.get(i); filter.close(); } modelFilters.clear(); updateFilterList(); outputSetModified(true); } // Moves the currently selected filter up private void filterMoveUp() { int index = listFilters.getSelectedIndex(); if (index > 0 && index < modelFilters.size()) { BamFilterBase filter = modelFilters.get(index - 1); modelFilters.set(index - 1, modelFilters.get(index)); modelFilters.set(index, filter); } listFilters.setSelectedIndex(index - 1); updateFilterList(); listFilters.requestFocusInWindow(); outputSetModified(true); } // Moves the currently selected filter down private void filterMoveDown() { int index = listFilters.getSelectedIndex(); if (index >= 0 && index < modelFilters.size() - 1) { BamFilterBase filter = modelFilters.get(index + 1); modelFilters.set(index + 1, modelFilters.get(index)); modelFilters.set(index, filter); } listFilters.setSelectedIndex(index + 1); updateFilterList(); listFilters.requestFocusInWindow(); outputSetModified(true); } // Returns whether preview markers have been enabled private boolean filterIsMarkerVisible() { return cbFiltersShowMarker.isSelected(); } // Returns the currently selected frame index private int filterGetPreviewFrameIndex() { return ((Integer)sFiltersPreviewFrame.getValue()).intValue(); } private void filterUpdatePreviewFrameIndex() { SpinnerNumberModel model = (SpinnerNumberModel)sFiltersPreviewFrame.getModel(); int max = ((Integer)model.getMaximum()).intValue(); int cur = ((Integer)model.getValue()).intValue(); if (max != listFrameEntries.get(BAM_ORIGINAL).size()) { max = listFrameEntries.get(BAM_ORIGINAL).size(); if (cur >= max) { cur = Math.max(max - 1, 0); } model.setMaximum(Integer.valueOf(max)); model.setValue(Integer.valueOf(cur)); } } // Prepares the specified frame for quick preview private void filterSetPreviewFrame(int frameIdx, boolean complete) { PseudoBamFrameEntry entry = getFilteredBamFrame(getBamVersion(), filterGetPreviewFrameIndex(), complete); if (entry.getFrame().getWidth() != (rcFiltersPreview.getImage().getWidth(null) - 2) || entry.getFrame().getHeight() != (rcFiltersPreview.getImage().getHeight(null) - 2)) { rcFiltersPreview.setImage(new BufferedImage(entry.getFrame().getWidth() + 2, entry.getFrame().getHeight() + 2, BufferedImage.TYPE_INT_ARGB)); } filterDisplay(entry); } // Displays the specified entry in the filters quick preview private void filterDisplay(PseudoBamFrameEntry entry) { if (rcFiltersPreview.getImage() != null && entry != null && entry.getFrame() != null) { int canvasWidth = rcFiltersPreview.getImage().getWidth(null); int canvasHeight = rcFiltersPreview.getImage().getHeight(null); int centerX = entry.getCenterX(); int centerY = entry.getCenterY(); Graphics2D g = (Graphics2D)rcFiltersPreview.getImage().getGraphics(); try { // clearing old content g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC)); g.setColor(new Color(0, true)); g.fillRect(0, 0, canvasWidth, canvasHeight); // rendering frame g.drawImage(entry.getFrame(), 1, 1, null); if (filterIsMarkerVisible()) { // drawing bounding box g.setColor(new Color(0x00A000)); g.drawRect(0, 0, canvasWidth - 1, canvasHeight - 1); // drawing center marker (if frame is big enough) int msize = 8; if (msize > canvasWidth) msize = canvasWidth; if (canvasWidth > canvasHeight) msize = canvasHeight; if (canvasWidth > 3 && canvasHeight > 3) { g.setStroke(new BasicStroke(3.0f)); g.setColor(Color.BLACK); g.drawLine(centerX - 4, centerY, centerX + 4, centerY); g.drawLine(centerX, centerY - 4, centerX, centerY + 4); g.setStroke(new BasicStroke(1.0f)); g.setColor(Color.RED); g.drawLine(centerX - 4, centerY, centerX + 4, centerY); g.drawLine(centerX, centerY - 4, centerX, centerY + 4); } } } finally { g.dispose(); g = null; } rcFiltersPreview.repaint(); } } // Triggers the update function for each filter in the list, enabling them to react to the // current state of the converter private void filterUpdateControls() { for (int i = 0; i < modelFilters.size(); i++) { modelFilters.get(i).updateControls(); } } // Action for selecting BAM version in export section: 0=BAM v1, 1=BAM v2 private void setBamVersion(int index) { String s; switch (index) { case VERSION_BAMV1: s = "V1"; break; case VERSION_BAMV2: s = "V2"; break; default: s = null; } if (s != null) { cbVersion.setSelectedIndex(index); // setting BAM version-specific export controls CardLayout cl = (CardLayout)pFramesOptionsVersion.getLayout(); cl.show(pFramesOptionsVersion, s); // setting enabled state of frame compression flag in info box cbCompressFrame.setEnabled(index == 0); } outputSetModified(true); } // Action for BAM version help: Displays a message dialog with information private void showVersionHelp() { final String helpMsg = "\"Legacy (v1)\" is the old and proven BAM format supported by all available\n" + "Infinity Engine games. It uses a global 256 color table for all frames\n" + "and supports simple bitmasked transparency.\n\n" + "\"PVRZ-based (v2)\" uses a new BAM format introduced by BG:EE. Graphics data\n" + "is stored separately in PVRZ files. Each frame supports interpolated alpha\n" + "transitions and is not limited to a global 256 color table.\n" + "It is only supported by the Enhanced Editions of the Baldur's Gate series."; JOptionPane.showMessageDialog(this, helpMsg, "About BAM versions", JOptionPane.INFORMATION_MESSAGE); } // Action for BAM v2 compression help: Displays a message dialog with information private void showCompressionHelp() { final String helpMsg = "\"DXT1\" provides the highest compression ratio. It supports only 1 bit alpha\n" + "(i.e. either no or full transparency) and is the preferred type for TIS or MOS resources.\n\n" + "\"DXT5\" provides an average compression ratio. It features interpolated\n" + "alpha transitions and is the preferred type for BAM resources.\n\n" + "\"Auto\" selects the most appropriate compression type based on the input data."; JOptionPane.showMessageDialog(this, helpMsg, "About compression types", JOptionPane.INFORMATION_MESSAGE); } // Action for "Compress BAM" in BAM v1 export private void updateCompressBam() { Boolean b = Boolean.valueOf(cbCompressBam.isSelected()); getBamDecoder(BAM_ORIGINAL).setOption(PseudoBamDecoder.OPTION_BOOL_COMPRESSED, b); } // Specify a BAM output file private Path setBamOutput() { Path rootPath = null; if (bamOutputFile != null) { rootPath = FileManager.resolve(bamOutputFile); } Path outFile = getSaveFileName(this, "Specify output file", rootPath, new FileNameExtensionFilter[]{getBamFilter()}, 0); if (outFile != null) { outFile = StreamUtils.replaceFileExtension(outFile, "BAM"); bamOutputFile = outFile; } return outFile; } // Returns the min/max values from the specified array of indices in a Pair object. private Pair<Integer> getIndexBounds(int[] indices) { Pair<Integer> retVal = new Pair<Integer>(Integer.valueOf(-1), Integer.valueOf(-1)); if (indices != null && indices.length > 0) { retVal.setFirst(Integer.valueOf(Integer.MAX_VALUE)); retVal.setSecond(Integer.valueOf(Integer.MIN_VALUE)); for (int i = 0; i < indices.length; i++) { if (indices[i] < retVal.getFirst()) { retVal.setFirst(Integer.valueOf(indices[i])); } if (indices[i] > retVal.getSecond()) { retVal.setSecond(Integer.valueOf(indices[i])); } } } return retVal; } // Attempts to find the most appropriate DXT compression type based on the source frames private DxtEncoder.DxtType getAutoDxtType() { DxtEncoder.DxtType dxtType = DxtEncoder.DxtType.DXT1; PseudoBamControl control = bamDecoder.createControl(); control.setMode(BamControl.Mode.SHARED); control.setSharedPerCycle(false); Dimension dim = control.getSharedDimension(); control.setMode(BamControl.Mode.INDIVIDUAL); BufferedImage canvas = new BufferedImage(dim.width, dim.height, BufferedImage.TYPE_INT_ARGB); boolean typeFound = false; for (int i = 0; i < bamDecoder.frameCount(); i++) { Graphics2D g = canvas.createGraphics(); try { g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC)); g.setColor(new Color(0, true)); g.fillRect(0, 0, canvas.getWidth(), canvas.getHeight()); } finally { g.dispose(); g = null; } bamDecoder.frameGet(control, i, canvas); dim.width = bamDecoder.getFrameInfo(i).getWidth(); dim.height = bamDecoder.getFrameInfo(i).getHeight(); int[] buffer = ((DataBufferInt)canvas.getRaster().getDataBuffer()).getData(); if (buffer != null) { for (int y = 0; y < dim.height; y++) { int ofs = y*canvas.getWidth(); for (int x = 0; x < dim.width; x++, ofs++) { if ((buffer[ofs] & 0xff000000) != 0xff000000 && (buffer[ofs] & 0xff000000) != 0) { dxtType = DxtEncoder.DxtType.DXT5; typeFound = true; break; } } if (typeFound) break; } } buffer = null; if (typeFound) break; } canvas.flush(); canvas = null; control = null; return dxtType; } private List<String> convert() { List<String> result = new Vector<String>(2); try { updateFilteredBamDecoder(getBamVersion(), false); List<BamFilterBaseOutput> outList = createOutputFilterList(); if (outList != null && !outList.isEmpty()) { for (int idx = 0; idx < outList.size(); idx++) { // processing output filter outList.get(idx).process(bamDecoderFinal); } } else { throw new Exception("No output filter specified."); } result.add("Conversion finished successfully."); } catch (Exception e) { e.printStackTrace(); result.add(null); result.add(String.format("Error while exporting BAM files.\n(%1$s)", e.getMessage())); } return result; } // Returns whether the output/preview BAM has been modified private boolean outputIsModified() { return isPreviewModified; } // Defines whether the internal BAM structure has changed in any way private void outputSetModified(boolean isModified) { isPreviewModified = isModified; } // Returns a single filtered BAM frame. Set "complete" to recreate the whole filter chain. private PseudoBamFrameEntry getFilteredBamFrame(int bamVersion, int frameIdx, boolean complete) { int curFilterIdx = listFilters.getSelectedIndex(); // recreating filtered preview if (complete) { updateFinalBamFrame(bamVersion, frameIdx); // processing each filter that exists before the selected filter PseudoBamFrameEntry entry = entryFilterPreview; for (int i = 0; i < curFilterIdx; i++) { if (modelFilters.get(i) instanceof BamFilterBase) { BamFilterBase filter = modelFilters.get(i); entry = filter.updatePreview(entry); } } entryFilterPreview.setFrame(entry.getFrame()); entryFilterPreview.setCenterX(entry.getCenterX()); entryFilterPreview.setCenterY(entry.getCenterY()); } // updating currently selected filter PseudoBamFrameEntry entry = entryFilterPreview; if (curFilterIdx >= 0 && curFilterIdx < modelFilters.size()) { entry = new PseudoBamFrameEntry(ColorConvert.cloneImage(entryFilterPreview.getFrame()), entryFilterPreview.getCenterX(), entryFilterPreview.getCenterY()); BamFilterBase filter = modelFilters.get(curFilterIdx); if (filter != null) { entry = filter.updatePreview(entry); } } return entry; } // Updates the final BAM structure in bamDecoderFinal private void updateFilteredBamDecoder(int bamVersion, boolean force) throws Exception { if (outputIsModified() || force) { outputSetModified(false); updateFinalBamDecoder(bamVersion); // Processing each filter sequentially List<BamFilterBase> filters = createFilterList(false); if (filters != null) { for (int idx = 0; idx < filters.size(); idx++) { if (filters.get(idx) instanceof BamFilterBaseColor) { // processing color filter try { BamFilterBaseColor filter = (BamFilterBaseColor)filters.get(idx); for (int frameIdx = 0; frameIdx < listFrameEntries.get(BAM_FINAL).size(); frameIdx++) { BufferedImage image = filter.process(listFrameEntries.get(BAM_FINAL).get(frameIdx).getFrame()); if (image != null) { listFrameEntries.get(BAM_FINAL).get(frameIdx).setFrame(image); image = null; } else { throw new Exception(); } } } catch (Exception e) { e.printStackTrace(); throw e; } } else if (filters.get(idx) instanceof BamFilterBaseTransform) { // processing transform filter try { BamFilterBaseTransform filter = (BamFilterBaseTransform)filters.get(idx); for (int frameIdx = 0; frameIdx < listFrameEntries.get(BAM_FINAL).size(); frameIdx++) { PseudoBamFrameEntry entry = filter.process(listFrameEntries.get(BAM_FINAL).get(frameIdx)); if (entry != null) { if (entry != listFrameEntries.get(BAM_FINAL).get(frameIdx)) { listFrameEntries.get(BAM_FINAL).set(frameIdx, entry); } } else { throw new Exception(); } } } catch (Exception e) { e.printStackTrace(); throw e; } } else if (filters.get(idx) instanceof BamFilterBaseOutput) { // skipping output filter } else { if (filters.get(idx) != null) { System.err.println(String.format("Unrecognized filter at index %1$d: %2$s", idx, filters.get(idx))); } else { System.err.println(String.format("null filter at index %1$d", idx)); } } } } } } // Creates a sorted list including all selected filters in the post-processing tab private List<BamFilterBase> createFilterList(boolean includeOutputFilters) { List<BamFilterBase> retVal = new ArrayList<BamFilterBase>(); List<BamFilterBase> outFilters = new ArrayList<BamFilterBase>(); for (int i = 0; i < modelFilters.size(); i++) { BamFilterBase filter = modelFilters.get(i); if (filter instanceof BamFilterBaseOutput) { outFilters.add(filter); } else { retVal.add(filter); } } if (includeOutputFilters) { if (outFilters.isEmpty()) { outFilters.add(BamFilterFactory.createInstance(this, BamFilterOutputDefault.class)); } for (int i = 0; i < outFilters.size(); i++) { retVal.add(outFilters.get(i)); } } return retVal; } // Creates a list of selected output filters only private List<BamFilterBaseOutput> createOutputFilterList() { List<BamFilterBaseOutput> retVal = new ArrayList<BamFilterBaseOutput>(); for (int i = 0; i < modelFilters.size(); i++) { if (modelFilters.get(i) instanceof BamFilterBaseOutput) { retVal.add((BamFilterBaseOutput)modelFilters.get(i)); } } if (retVal.isEmpty()) { retVal.add((BamFilterBaseOutput)BamFilterFactory.createInstance(this, BamFilterOutputDefault.class)); } return retVal; } // Creates a new BAM structure from the existing structure that is compatible with the // specified target BAM version. private void updateFinalBamDecoder(int bamVersion) throws Exception { listFrameEntries.get(BAM_FINAL).clear(); if (bamVersion == VERSION_BAMV1 || bamVersion == VERSION_BAMV2) { List<PseudoBamFrameEntry> srcListFrames = listFrameEntries.get(BAM_ORIGINAL); List<PseudoBamFrameEntry> dstListFrames = listFrameEntries.get(BAM_FINAL); // copying global options String[] options = bamDecoder.getOptionNames(); for (int i = 0; i < options.length; i++) { bamDecoderFinal.setOption(options[i], bamDecoder.getOption(options[i])); } if (bamVersion == VERSION_BAMV1) { // BAM v1: creating paletted version of each source frame paletteDialog.updateGeneratedPalette(); final int Green = 0x0000ff00; bamDecoderFinal.setOption(PseudoBamDecoder.OPTION_INT_RLEINDEX, Integer.valueOf(paletteDialog.getRleIndex())); bamDecoderFinal.setOption(PseudoBamDecoder.OPTION_BOOL_COMPRESSED, Boolean.valueOf(isBamV1Compressed())); // preparing palette int[] palette = paletteDialog.getPalette(paletteDialog.getPaletteType()); int threshold = getTransparencyThreshold(); int transIndex = -1; for (int i = 0; i < palette.length; i++) { int c = palette[i] & 0x00ffffff; if (transIndex < 0 && c == Green) { transIndex = i; break; } } if (transIndex < 0) { transIndex = 0; } int[] hclPalette = new int[palette.length]; ColorConvert.toHclPalette(palette, hclPalette); HashMap<Integer, Byte> colorCache = new HashMap<Integer, Byte>(4096); for (int i = 0; i < palette.length; i++) { if (i != transIndex) { colorCache.put(Integer.valueOf(palette[i]), Byte.valueOf((byte)i)); } } // processing frames IndexColorModel cm = new IndexColorModel(8, 256, palette, 0, false, transIndex, DataBuffer.TYPE_BYTE); for (int i = 0; i < srcListFrames.size(); i++) { PseudoBamFrameEntry srcEntry = srcListFrames.get(i); BufferedImage srcImage = ColorConvert.toBufferedImage(srcEntry.getFrame(), true, true); int[] srcBuf = ((DataBufferInt)srcImage.getRaster().getDataBuffer()).getData(); BufferedImage dstImage = new BufferedImage(srcEntry.getWidth(), srcEntry.getHeight(), BufferedImage.TYPE_BYTE_INDEXED, cm); byte[] dstBuf = ((DataBufferByte)dstImage.getRaster().getDataBuffer()).getData(); for (int ofs = 0; ofs < srcBuf.length; ofs++) { int c = srcBuf[ofs]; if (PseudoBamDecoder.isTransparentColor(c, threshold)) { dstBuf[ofs] = (byte)transIndex; } else { c &= 0x00ffffff; Byte colIdx = colorCache.get(Integer.valueOf(c)); if (colIdx != null) { int ci = colIdx.intValue() & 0xff; if (ci >= transIndex) ci++; dstBuf[ofs] = colIdx.byteValue();//(byte)ci; } else { byte color = (byte)ColorConvert.nearestColor(srcBuf[ofs], hclPalette); //int ci = (color < transIndex) ? color : (color + 1); dstBuf[ofs] = color;//(byte)ci; colorCache.put(Integer.valueOf(c), Byte.valueOf(color)); } } } srcBuf = null; srcImage.flush(); srcImage = null; dstBuf = null; PseudoBamFrameEntry dstEntry = new PseudoBamFrameEntry(dstImage, srcEntry.getCenterX(), srcEntry.getCenterY()); // adding frame-specific options options = srcEntry.getOptionNames(); for (int j = 0; j < options.length; j++) { dstEntry.setOption(options[j], srcEntry.getOption(options[j])); } dstListFrames.add(dstEntry); } } else { // BAM v2: create truecolored version of each frame for (int i = 0; i < srcListFrames.size(); i++) { PseudoBamFrameEntry srcEntry = srcListFrames.get(i); BufferedImage dstImage = ColorConvert.toBufferedImage(srcEntry.getFrame(), true, true); PseudoBamFrameEntry dstEntry = new PseudoBamFrameEntry(dstImage, srcEntry.getCenterX(), srcEntry.getCenterY()); // adding frame-specific options options = srcEntry.getOptionNames(); for (int j = 0; j < options.length; j++) { dstEntry.setOption(options[j], srcEntry.getOption(options[j])); } dstListFrames.add(dstEntry); } } } else { throw new Exception("Unknown target BAM format"); } } // Creates a new single frame that is compatible with the specified BAM version. private void updateFinalBamFrame(int bamVersion, int frameIdx) { if (frameIdx >= 0 && frameIdx < listFrameEntries.get(BAM_ORIGINAL).size()) { PseudoBamFrameEntry srcEntry = listFrameEntries.get(BAM_ORIGINAL).get(frameIdx); BufferedImage srcImage = srcEntry.getFrame(); BufferedImage dstImage = null; if (bamVersion == VERSION_BAMV1) { // BAM v1: creating paletted version of the source frame paletteDialog.updateGeneratedPalette(); // preparing palette final int Green = 0x0000ff00; int[] palette = paletteDialog.getPalette(paletteDialog.getPaletteType()); int threshold = getTransparencyThreshold(); int transIndex = -1; for (int i = 0; i < palette.length; i++) { int c = palette[i] & 0x00ffffff; if (transIndex < 0 && c == Green) { transIndex = i; break; } } if (transIndex < 0) { transIndex = 0; } int[] hclPalette = new int[palette.length]; ColorConvert.toHclPalette(palette, hclPalette); HashMap<Integer, Byte> colorCache = new HashMap<Integer, Byte>(4096); for (int i = 0; i < palette.length; i++) { if (i != transIndex) { colorCache.put(Integer.valueOf(palette[i]), Byte.valueOf((byte)i)); } } IndexColorModel cm = new IndexColorModel(8, 256, palette, 0, false, transIndex, DataBuffer.TYPE_BYTE); // converting frame srcImage = ColorConvert.toBufferedImage(srcImage, true, true); int[] srcBuf = ((DataBufferInt)srcImage.getRaster().getDataBuffer()).getData(); dstImage = new BufferedImage(srcImage.getWidth(), srcImage.getHeight(), BufferedImage.TYPE_BYTE_INDEXED, cm); byte[] dstBuf = ((DataBufferByte)dstImage.getRaster().getDataBuffer()).getData(); for (int ofs = 0; ofs < srcBuf.length; ofs++) { int c = srcBuf[ofs]; if (PseudoBamDecoder.isTransparentColor(c, threshold)) { dstBuf[ofs] = (byte)transIndex; } else { c &= 0x00ffffff; Byte colIdx = colorCache.get(Integer.valueOf(c)); if (colIdx != null) { int ci = colIdx.intValue() & 0xff; if (ci >= transIndex) ci++; dstBuf[ofs] = colIdx.byteValue();//(byte)ci; } else { byte color = (byte)ColorConvert.nearestColor(srcBuf[ofs], hclPalette); //int ci = (color < transIndex) ? color : (color + 1); dstBuf[ofs] = color;//(byte)ci; colorCache.put(Integer.valueOf(c), Byte.valueOf(color)); } } } srcBuf = null; srcImage.flush(); srcImage = null; dstBuf = null; } else if (bamVersion == VERSION_BAMV2) { // BAM v2: creating truecolored version of the source frame dstImage = ColorConvert.toBufferedImage(srcImage, true, true); } entryFilterPreview.setFrame(dstImage); entryFilterPreview.setCenterX(srcEntry.getCenterX()); entryFilterPreview.setCenterY(srcEntry.getCenterY()); dstImage = null; } } // Initializes a new ProgressMonitor instance void initProgressMonitor(Component parent, String msg, String note, int maxProgress, int msDecide, int msWait) { if (parent == null) parent = NearInfinity.getInstance(); if (maxProgress <= 0) maxProgress = 1; releaseProgressMonitor(); pmMax = maxProgress; pmCur = 0; progress = new ProgressMonitor(parent, msg, note, 0, pmMax); progress.setMillisToDecideToPopup(msDecide); progress.setMillisToPopup(msWait); progress.setProgress(pmCur); } // Closes an active instance of the ProgressMonitor void releaseProgressMonitor() { if (progress != null) { progress.close(); progress = null; } } // Advances the active ProgressMonitor instance by one step void advanceProgressMonitor(String note) { if (progress != null) { if (pmCur < pmMax) { pmCur++; if (note != null) { progress.setNote(note); } progress.setProgress(pmCur); } } } // Returns the current progress of the active ProgressMonitor instance int getProgressMonitorStage() { if (progress != null) { return pmCur; } else { return 0; } } // Sets the current stage of the active ProgressMonitor instance void setProgressMonitorState(int stage) { if (progress != null) { if (stage < progress.getMinimum()) stage = progress.getMinimum(); if (stage > progress.getMaximum()) stage = progress.getMaximum(); progress.setProgress(stage); } } // Returns the max. stage of the active ProgressMonitor instance int getProgressMonitorMax() { if (progress != null) { return pmMax; } else { return 0; } } // Sets a new max. stage to the active ProgressMonitor instance void setProgressMonitorMax(int max) { if (progress != null) { if (max >= 0) { progress.setMaximum(max); } } } // Sets a new note to the active ProgressMonitor instance void setProgressMonitorNote(String note) { if (progress != null) { progress.setNote(note); } } // Returns whether the active ProgressMonitor instance has been cancelled by the user boolean isProgressMonitorCancelled() { if (isProgressMonitorActive()) { return progress.isCanceled(); } return false; } // Returns whether a ProgressMonitor instance is active boolean isProgressMonitorActive() { return (progress != null); } // Returns the ProgressMonitor instance, or null if not active ProgressMonitor getProgressMonitor() { return progress; } //-------------------------- INNER CLASSES -------------------------- // Manages the frames aspect of BAM resources private static class BamFramesListModel extends AbstractListModel<PseudoBamFrameEntry> { private final ConvertToBam converter; private final PseudoBamDecoder decoder; public BamFramesListModel(ConvertToBam converter) { if (converter == null) { throw new NullPointerException(); } this.converter = converter; this.decoder = this.converter.getBamDecoder(BAM_ORIGINAL); } /** Returns the parent converter object. */ public ConvertToBam getConverter() { return converter; } /** Returns the associated BamDecoder object. */ public PseudoBamDecoder getDecoder() { return decoder; } // /** Adds a new frame to the global frames list. */ // public void add(BufferedImage image, Point center) // { // insert(getDecoder().frameCount(), new BufferedImage[]{image}, new Point[]{center}); // } // /** Adds an array of images to the global frames list. */ // public void add(BufferedImage[] images, Point[] centers) // { // insert(getDecoder().frameCount(), images, centers); // } /** Inserts the image into the global frames list. */ public void insert(int pos, BufferedImage image, Point center) { insert(pos, new BufferedImage[]{image}, new Point[]{center}); } /** Inserts the array of images into the global frames list. */ public void insert(int pos, BufferedImage[] images, Point[] centers) { if (images != null && pos >= 0 && pos <= getDecoder().frameCount()) { int count = 0; PseudoBamControl control = getDecoder().createControl(); control.setMode(BamDecoder.BamControl.Mode.INDIVIDUAL); for (int i = 0; i < images.length; i++) { if (images[i] != null) { // adding frame to global list Point center = (centers.length > i && centers[i] != null) ? centers[i] : null; getDecoder().frameInsert(pos+i, images[i], center); // registering colors values in global HashMap BufferedImage image = ColorConvert.toBufferedImage(images[i], true, false); PseudoBamDecoder.registerColors(getConverter().paletteDialog.getColorMap(), image); getConverter().paletteDialog.setPaletteModified(); count++; } } if (count > 0) { fireIntervalAdded(this, pos, pos+count-1); } } } /** * Removes a single entry from the global frames list. */ public void remove(int pos) { remove(pos, 1); } /** * Removes a number of entries from the global frames list. */ public void remove(int pos, int count) { if (count > 0) { if (pos < 0) pos = 0; if (pos >= getDecoder().frameCount()) pos = getDecoder().frameCount() - 1; if (pos + count > getDecoder().frameCount()) { count = getDecoder().frameCount() - pos; } PseudoBamControl control = getDecoder().createControl(); control.setMode(BamDecoder.BamControl.Mode.INDIVIDUAL); // unregistering color values in global color map for (int i = 0; i < count; i++) { BufferedImage image = ColorConvert.toBufferedImage(getDecoder().frameGet(control, pos+i), true, false); PseudoBamDecoder.unregisterColors(getConverter().paletteDialog.getColorMap(), image); getConverter().paletteDialog.setPaletteModified(); } getDecoder().frameRemove(pos, count); fireIntervalRemoved(this, pos, pos+count-1); } } /** * Removes all entries from the global frame list. */ public void clear() { int count; count = getDecoder().frameCount(); PseudoBamControl control = getDecoder().createControl(); control.setMode(BamDecoder.BamControl.Mode.INDIVIDUAL); // clearing global color map getConverter().paletteDialog.getColorMap().clear(); getConverter().paletteDialog.setPaletteModified(); getDecoder().frameClear(); if (count > 0) { fireIntervalRemoved(this, 0, count-1); } } /** * Moves the specified entry within the list by {@code offset}. * @param index The index of the frame. * @param offset The number of positions to move. */ public void move(int index, int offset) { int retVal, pos1 = index, pos2 = index; // moving positions retVal = getDecoder().frameMove(index, offset); // preparing interval if (retVal >= 0) { if (retVal > pos1) { pos2 = retVal; } else { pos1 = retVal; } } // notifying listeners if (pos2 > pos1) { fireContentsChanged(this, pos1, pos2); } } /** Returns {@code true} if, and only if {@code getDecoder().getFrameCount()} is 0. */ public boolean isEmpty() { return getDecoder().isEmpty(); } @Override public int getSize() { return getDecoder().frameCount(); } @Override public PseudoBamFrameEntry getElementAt(int index) { if (index >= 0 && index < getDecoder().frameCount()) { return getDecoder().getFrameInfo(index); } else { return null; } } } // Manages frames within a cycle private static class BamCycleFramesListModel extends AbstractListModel<PseudoBamFrameEntry> { private final ConvertToBam converter; private final PseudoBamDecoder decoder; private final PseudoBamDecoder.PseudoBamControl control; public BamCycleFramesListModel(ConvertToBam converter) { if (converter == null) { throw new NullPointerException(); } this.converter = converter; this.decoder = getConverter().getBamDecoder(BAM_ORIGINAL); this.control = getDecoder().createControl(); } /** Returns the parent converter object. */ public ConvertToBam getConverter() { return converter; } /** Returns the associated BamDecoder object. */ public PseudoBamDecoder getDecoder() { return decoder; } /** Returns the associated BamDecoder control. */ public PseudoBamDecoder.PseudoBamControl getControl() { return control; } /** Returns the active cycle. */ public int getCycle() { return getControl().cycleGet(); } /** Sets a new active cycle. */ public void setCycle(int cycle) { if (cycle < 0) cycle = 0; else if (cycle >= getControl().cycleCount()) cycle = getControl().cycleCount() - 1; if (cycle != getControl().cycleGet()) { int oldCount = getControl().cycleFrameCount(); getControl().cycleSet(cycle); int newCount = getControl().cycleFrameCount(); // firing change event if needed int changed = Math.min(oldCount, newCount); if (changed > 0) { fireContentsChanged(this, 0, changed - 1); } // firing remove/added events if needed int diff = Math.max(oldCount, newCount) - changed; if (diff < 0) { fireIntervalRemoved(this, changed, changed - diff - 1); } else if (diff > 0) { fireIntervalAdded(this, changed, changed + diff - 1); } } } // /** Adds one frame index to the current cycle. */ // public void add(int index) // { // insert(getControl().cycleFrameCount(), new int[]{index}); // } /** Adds the specified array of frame indices to the current cycle. */ public void add(int[] indices) { insert(getControl().cycleFrameCount(), indices); } /** Inserts one frame index into the current cycle. */ public void insert(int pos, int index) { insert(pos, new int[]{index}); } /** Inserts the array of frame indices into the current cycle. */ public void insert(int pos, int[] indices) { if (indices != null && pos >= 0 && pos <= getControl().cycleFrameCount()) { int count = indices.length; getControl().cycleInsertFrames(getControl().cycleGet(), pos, indices); fireIntervalAdded(this, pos, pos+count-1); } } // /** Removes one entry from the current cycle. */ // public void remove(int pos) // { // remove(pos, 1); // } /** * Removes a number of entries from the current cycle. */ public void remove(int pos, int count) { if (count > 0 && pos >= 0 && pos < getControl().cycleFrameCount()) { if (pos + count > getControl().cycleFrameCount()) { count = getControl().cycleFrameCount() - pos; } getControl().cycleRemoveFrames(getControl().cycleGet(), pos, count); fireIntervalRemoved(this, pos, pos+count-1); } } /** * Removes all entries from the current cycle. */ public void clear() { int count = getControl().cycleFrameCount(); getControl().cycleClearFrames(); if (count > 0) { fireIntervalRemoved(this, 0, count-1); } } /** * Moves the specified entry within the list by {@code offset}. * @param index The index of the frame. * @param offset The number of positions to move. */ public void move(int index, int offset) { // moving positions int retVal = getControl().cycleMoveFrame(index, offset); // preparing interval int pos1 = index, pos2 = index; if (retVal >= 0) { if (retVal > pos1) { pos2 = retVal; } else { pos1 = retVal; } } // notifying listeners if (pos2 > pos1) { fireContentsChanged(this, pos1, pos2); } } /** Returns {@code true} if, and only if {@code getControl().cycleFrameCount()} is 0. */ public boolean isEmpty() { return (getControl().cycleFrameCount() == 0); } @Override public int getSize() { return getControl().cycleFrameCount(); } @Override public PseudoBamFrameEntry getElementAt(int index) { if (index >= 0 && index < getControl().cycleFrameCount()) { return getDecoder().getFrameInfo(getControl().cycleGetFrameIndexAbsolute(index)); } else { return null; } } } // Manages the cycles aspect of BAM resources private static class BamCyclesListModel extends AbstractListModel<PseudoBamCycleEntry> { private final ConvertToBam converter; private final PseudoBamDecoder decoder; private final PseudoBamDecoder.PseudoBamControl control; public BamCyclesListModel(ConvertToBam converter) { if (converter == null) { throw new NullPointerException(); } this.converter = converter; this.decoder = getConverter().getBamDecoder(BAM_ORIGINAL); this.control = getDecoder().createControl(); } /** Returns the parent converter object. */ public ConvertToBam getConverter() { return converter; } /** Returns the associated BamDecoder object. */ public PseudoBamDecoder getDecoder() { return decoder; } /** Returns the associated BamDecoder control. */ public PseudoBamDecoder.PseudoBamControl getControl() { return control; } // /** Adds a new empty cycle to the cycles list. */ // public void add() // { // insert(getControl().cycleCount(), new int[0]); // } // /** Adds a new cycle with the specified frame index to the cycles list. */ // public void add(int index) // { // insert(getControl().cycleCount(), new int[]{index}); // } /** Adds a new cycle with the specified frame indices to the cycles list. */ public void add(int[] indices) { insert(getControl().cycleCount(), indices); } // /** Insert an empty cycle at the specified cycle position. */ // public void insert(int pos) // { // insert(pos, new int[0]); // } // /** Inserts a cycle with one frame index at the specified cycle position. */ // public void insert(int pos, int index) // { // insert(pos, new int[]{index}); // } /** Inserts a cycle with the specified frame indices at the specified cycle position. */ public void insert(int pos, int[] indices) { if (pos >= 0 && pos <= getControl().cycleCount() && indices != null) { getControl().cycleInsert(pos, indices); fireIntervalAdded(this, pos, pos); } } // /** Removes one cycle at the specified cycle position. */ // public void remove(int pos) // { // remove(pos, 1); // } /** Removes a number of cycles at the specified cycle position. */ public void remove(int pos, int count) { if (pos >= 0 && pos < getControl().cycleCount() && count > 0) { if (pos + count > getControl().cycleCount()) { count = getControl().cycleCount() - pos; } getControl().cycleRemove(pos, count); if (count > 0) { fireIntervalRemoved(this, pos, pos+count-1); } } } /** Removes all cycles from the cycles list. */ public void clear() { int count = getControl().cycleCount(); getControl().cycleClear(); if (count > 0) { fireIntervalRemoved(this, 0, count-1); } } /** * Moves the specified cycle entry within the cycles list by {@code offset}. * @param cycleIdx The index of the cycle. * @param offset The number of positions to move. */ public void move(int cycleIdx, int offset) { if (cycleIdx >= 0 && cycleIdx < getControl().cycleCount()) { int pos1 = cycleIdx, pos2 = cycleIdx; int retVal = getControl().cycleMove(cycleIdx, offset); if (retVal >= 0) { if (retVal > pos1) { pos2 = retVal; } else { pos1 = retVal; } } if (pos2 > pos1) { fireContentsChanged(this, pos1, pos2); } } } /** Fires a change event for the cycle at the specified index. */ public void contentChanged(int index) { contentsChanged(index, index); } /** Fires a change event for the cycle range defined by the specified indices. */ public void contentsChanged(int index0, int index1) { if (index0 >= 0 && index0 < getControl().cycleCount() && index1 >= 0 && index1 < getControl().cycleCount()) { if (index0 > index1) { int tmp = index0; index0 = index1; index1 = tmp; } fireContentsChanged(this, index0, index1); } } /** Returns {@code true} if, and only if {@code getControl().cycleCount()} is 0. */ public boolean isEmpty() { return getControl().isEmpty(); } @Override public int getSize() { return getControl().cycleCount(); } @Override public PseudoBamDecoder.PseudoBamCycleEntry getElementAt(int index) { if (index >= 0 && index < getControl().cycleCount()) { return getControl().getCycleInfo(index); } else { return null; } } } // Adds a prefix to the cell's visual output private static class IndexedCellRenderer extends DefaultListCellRenderer { public IndexedCellRenderer() { super(); } @Override public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus) { String template = "%1$0" + String.format("%1$d", Integer.toString(list.getModel().getSize()).length()) + "d - %2$s"; return super.getListCellRendererComponent(list, String.format(template, index, value), index, isSelected, cellHasFocus); } } // Provides methods for importing or exporting BAM configuration data via INI file, such as // frame sources, center position data or cycle definitions private static class Exporter extends JDialog implements ActionListener { /* * INI format: * 1. Section "[Global]" (mandatory) * - contains a single entry "version" with a version number * 2. Section "[Frames]" (optional) * - contains any number of frame source definitions in the format * - key: zero-based frame index * - value: full path to graphics file, * optionally separated by colon ':' followed by a frame index * (only for input files containing multiple frames, default: 0) * Example: 0=c:/myfolder/myfile.bam:12 <- to load frame 12 of myfile.bam * 3. Section "[Center]" (optional) * - contains any number of center position entries for individual frames in the format * - key: zero-based frame index * - value: a sequence of two numbers for x and y, separated by comma ',' * Example: 0=12,-55 <- for position [12.-55] * 4. Section "[Cycles]" (optional) * - contains cycle definitions for the BAM in the format * - key: zero-based cycle index * - value: a sequence of numbers specifying frame indices, separated by comma ',' * Example: 0=0,1,2,3,4,5,90,91,92,93 * 5. Section "[Filters]" (optional) * - contains a list of filters to apply, including filter configurations * - uses name and config entries * - name key: name_n (where n is a positive number) * - name value: the filter name * - config key: config_n (where n is a positive number) * - config value: a configuration string (can be empty) * - Example: * name_0=Brightness/Contrast/Gamma * config_0=25;100;128;[0,18,19,20,192,193,194,195] */ private static final String SECTION_GLOBAL = "Global"; // global section name private static final String SECTION_FRAMES = "Frames"; // frames section name private static final String SECTION_CENTER = "Center"; // center point section name private static final String SECTION_CYCLES = "Cycles"; // cycles section name private static final String SECTION_FILTERS = "Filters"; // filters section name private static final String KEY_VERSION = "version"; // key in global section private static final String KEY_FILTER_NAME = "name_"; // key in global section private static final String KEY_FILTER_CONFIG = "config_"; // key in global section private static final char SEPARATOR_FRAME = ':'; // used in frame source definition to separate frame name from index private static final char SEPARATOR_NUMBER = ','; // number separator for cycle definitions or center point data private static final int VERSION = 1; // supported file version private static final String QUESTION_EXPORT = "What do you want to export?"; private static final String QUESTION_IMPORT = "What do you want to import?"; private final JLabel lSelect = new JLabel(); private final JCheckBox cbFrames = new JCheckBox("Frame source files", true); private final JCheckBox cbCenter = new JCheckBox("Frame center coordinates", true); private final JCheckBox cbCycles = new JCheckBox("Cycle definitions", true); private final JCheckBox cbFilters = new JCheckBox("Filter configurations", true); private final JButton bAccept = new JButton("Accept"); private final JButton bCancel = new JButton("Cancel"); private final ConvertToBam bam; private IniMapSection sectionFrames, sectionCenter, sectionCycles, sectionFilters; private boolean accepted; /** Returns a extension filter for INI files. */ private static FileNameExtensionFilter getIniFilter() { return new FileNameExtensionFilter("INI files (*.ini)", "ini"); } public Exporter(ConvertToBam bam) { super(bam, true); this.bam = bam; init(); } //--------------------- Begin Interface ActionListener --------------------- @Override public void actionPerformed(ActionEvent event) { if (event.getSource() == bAccept) { accept(); } else if (event.getSource() == bCancel) { cancel(); } else if (event.getSource() instanceof JCheckBox) { bAccept.setEnabled(cbFrames.isSelected() || cbCenter.isSelected() || cbCycles.isSelected() || cbFilters.isSelected()); } } //--------------------- End Interface ActionListener --------------------- /** Must be called at the end to clean up dialog resources. */ public void close() { dispose(); } /** * Opens dialog to choose what to export and exports selected data. * Returns whether export was successful. */ public boolean exportData(boolean silent) { resetData(); // trying to determine default output filename Path root = getDefaultIniName("data.ini"); Path outFile = getSaveFileName(bam, "Export BAM session", root, new FileNameExtensionFilter[]{getIniFilter()}, 0); if (outFile != null) { if (getSelection(true)) { try { WindowBlocker.blockWindow(bam, true); return saveData(outFile, silent); } finally { WindowBlocker.blockWindow(bam, false); } } } return false; } /** * Opens dialog to choose what to import and imports selected data. * Returns whether import was successful. */ public boolean importData(boolean silent) { resetData(); Path[] files = getOpenFileName(bam, "Import BAM session", null, false, new FileNameExtensionFilter[]{getIniFilter()}, 0); if (files != null && files.length > 0) { if (loadData(files[0], silent)) { if (getSelection(false)) { try { WindowBlocker.blockWindow(bam, true); return applyData(silent); } finally { WindowBlocker.blockWindow(bam, false); } } } } return false; } // Loads data from the specified file without user-interaction and optionally without feedback. private boolean loadData(Path inFile, boolean silent) { if (inFile != null) { IniMap ini = new IniMap(new FileResourceEntry(inFile)); try { // checking integrity if (ini.getSection(SECTION_GLOBAL) == null || ini.getSection(SECTION_GLOBAL).getEntry(KEY_VERSION) == null) { throw new Exception("Invalid BAM session file."); } if (Misc.toNumber(ini.getSection(SECTION_GLOBAL).getEntry(KEY_VERSION).getValue(), -1) != VERSION) { throw new Exception("Invalid or unsupported file version."); } if (ini.getSection(SECTION_FRAMES) != null) { if (!loadFrameData(ini.getSection(SECTION_FRAMES))) { throw new Exception("Error loading frame source files."); } } if (ini.getSection(SECTION_CENTER) != null) { if (!loadCenterData(ini.getSection(SECTION_CENTER))) { throw new Exception("Error loading frame center coordinates."); } } if (ini.getSection(SECTION_CYCLES) != null) { if (!loadCycleData(ini.getSection(SECTION_CYCLES))) { throw new Exception("Error loading cycle definitions."); } } if (ini.getSection(SECTION_FILTERS) != null) { if (!loadFilterData(ini.getSection(SECTION_FILTERS))) { throw new Exception("Error loading filters."); } } return true; } catch (Exception e) { // parsing failed resetData(); if (!silent && e.getMessage() != null && !e.getMessage().isEmpty()) { JOptionPane.showMessageDialog(bam, e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE); } } } return false; } private boolean loadFrameData(IniMapSection frames) throws Exception { if (frames != null && frames.getName().equalsIgnoreCase(SECTION_FRAMES)) { for (int i = 0; i < frames.getEntryCount(); i++) { IniMapEntry entry = frames.getEntry(i); if (Misc.toNumber(entry.getKey(), -1) < 0) { throw new Exception("Invalid key value found at line " + (entry.getLine() + 1)); } String value = entry.getValue().trim(); if (value.isEmpty()) { throw new Exception("Empty frame source path found at line " + (entry.getLine() + 1)); } int sepIdx = value.lastIndexOf(SEPARATOR_FRAME); int frameIdx = Integer.MIN_VALUE; if (sepIdx >= 0) { frameIdx = Misc.toNumber(value.substring(sepIdx + 1), Integer.MIN_VALUE); value = value.substring(0, sepIdx); } if (frameIdx == Integer.MIN_VALUE) { throw new Exception("Frame source path does not contain frame index at line " + (entry.getLine() + 1)); } if (value.startsWith(BAM_FRAME_PATH_BIFF)) { String resName = value.substring(BAM_FRAME_PATH_BIFF.length(), value.length()); if (!ResourceFactory.resourceExists(resName)) { throw new Exception("Frame source path not found at line " + (entry.getLine() + 1)); } } else { Path file = FileManager.resolve(value); if (!Files.isRegularFile(file)) { throw new Exception("Frame source path not found at line " + (entry.getLine() + 1)); } } } sectionFrames = frames; return true; } return true; } private boolean loadCenterData(IniMapSection centers) throws Exception { if (centers != null && centers.getName().equalsIgnoreCase(SECTION_CENTER)) { for (int i = 0; i < centers.getEntryCount(); i++) { IniMapEntry entry = centers.getEntry(i); if (Misc.toNumber(entry.getKey(), -1) < 0) { throw new Exception("Invalid key value found at line " + (entry.getLine() + 1)); } if (!entry.getValue().trim().matches("-?\\d+\\s*,\\s*-?\\d+")) { throw new Exception("Invalid value found at line " + (entry.getLine() + 1)); } } sectionCenter = centers; return true; } return false; } private boolean loadCycleData(IniMapSection cycles) throws Exception { if (cycles != null && cycles.getName().equalsIgnoreCase(SECTION_CYCLES)) { for (int i = 0; i < cycles.getEntryCount(); i++) { IniMapEntry entry = cycles.getEntry(i); if (Misc.toNumber(entry.getKey(), -1) < 0) { throw new Exception("Invalid key value found at line " + (entry.getLine() + 1)); } if (!entry.getValue().trim().matches("(\\d+\\s*(,\\s*\\d+\\s*)*)?")) { throw new Exception("Invalid value found at line " + (entry.getLine() + 1)); } } sectionCycles = cycles; return true; } return false; } private boolean loadFilterData(IniMapSection filters) throws Exception { if (filters != null && filters.getName().equalsIgnoreCase(SECTION_FILTERS)) { for (int i = 0; i < filters.getEntryCount(); i++) { IniMapEntry entry = filters.getEntry(i); String key = entry.getKey().trim(); String value = entry.getValue().trim(); if (key.matches(KEY_FILTER_NAME + "\\d+")) { if (BamFilterFactory.getFilterInfo(value) == null) { throw new Exception("BAM filter \"" + value.substring(0, Math.min(value.length(), 256)) + "\" does not exist."); } } else if (!key.matches(KEY_FILTER_CONFIG + "\\d+")) { throw new Exception("Invalid key value found at line " + (entry.getLine() + 1)); } } sectionFilters = filters; return true; } return false; } // Applies available data to the converter without user-interaction and optionally without feedback. private boolean applyData(boolean silent) { bam.previewStop(); bam.outputSetModified(true); try { if (sectionFrames != null) { if (!applyFramesData(silent)) { throw new Exception("Error adding frame entries."); } } if (sectionCenter != null) { if (!applyCenterData(silent)) { throw new Exception("Error applying frame center coordinates."); } } if (sectionCycles != null) { if (!applyCycleData(silent)) { throw new Exception("Error adding cycle definitions."); } } if (sectionFilters != null) { if (!applyFilterData(silent)) { throw new Exception("Error adding filters."); } } return true; } catch (Exception e) { resetData(); if (!silent && e.getMessage() != null && !e.getMessage().isEmpty()) { WindowBlocker.blockWindow(bam, false); JOptionPane.showMessageDialog(bam, e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE); } } return false; } private boolean applyFramesData(boolean silent) throws Exception { // Storage for ResourceEntry and frame index for convenience class SourceFrame { public final ResourceEntry entry; public final int index; public SourceFrame(ResourceEntry entry, int index) { this.entry = entry; this.index = index; } } // Primarily used for caching BAM decoder instances class SourceData { public final boolean isBam; // bam-specific public final BamDecoder decoder; public final BamDecoder.BamControl control; public final IndexColorModel cm; // image-specific public final Path file; public SourceData(BamDecoder decoder) { this.isBam = true; this.decoder = decoder; this.control = this.decoder.createControl(); if (this.decoder instanceof BamV1Decoder) { int[] palette = ((BamV1Decoder.BamV1Control)control).getPalette(); int transColor = ((BamV1Decoder.BamV1Control)control).getTransparencyIndex(); this.cm = new IndexColorModel(8, 256, palette, 0, false, transColor, DataBuffer.TYPE_BYTE); } else { this.cm = null; } this.file = null; } public SourceData(Path image) { this.isBam = false; this.decoder = null; this.control = null; this.cm = null; this.file = image; } } if (sectionFrames != null) { // preparing frames int entryCount = sectionFrames.getEntryCount(); SourceFrame[] frames = new SourceFrame[entryCount]; for (int i = 0; i < entryCount; i++) { IniMapEntry entry = sectionFrames.getEntry(i); // checking list indices int listIndex = Misc.toNumber(entry.getKey(), -1); if (listIndex < 0 || listIndex >= entryCount) { throw new Exception("Target frame index out of range [: " + listIndex + "] at line " + (entry.getLine() + 1)); } // checking frame source paths and indices String value = entry.getValue().trim(); int frameIndex = -1; int sepIdx = value.lastIndexOf(SEPARATOR_FRAME); if (sepIdx >= 0) { frameIndex = Misc.toNumber(value.substring(sepIdx + 1), -1); value = value.substring(0, sepIdx); } if (frameIndex < 0 || value.isEmpty()) { throw new Exception("Source frame index out of range [: " + listIndex + "] at line " + (entry.getLine() + 1)); } ResourceEntry resource = null; if (value.startsWith(BAM_FRAME_PATH_BIFF)) { value = value.substring(BAM_FRAME_PATH_BIFF.length()); if (ResourceFactory.resourceExists(value)) { resource = ResourceFactory.getResourceEntry(value); } } else { Path file = FileManager.resolve(value); if (Files.isRegularFile(file)) { resource = new FileResourceEntry(file); } } if (resource == null) { throw new Exception("Resource does not exist at line " + (entry.getLine() + 1)); } frames[listIndex] = new SourceFrame(resource, frameIndex); } for (int i = 0; i < frames.length; i++) { if (frames[i] == null) { throw new Exception("Undefined target frame index " + i); } } bam.filterRemoveAll(); bam.cyclesRemoveAll(); bam.framesRemoveAll(); bam.getPaletteDialog().clear(); // applying frames HashMap<ResourceEntry, SourceData> sourceMap = new HashMap<ResourceEntry, SourceData>(); for (int i = 0; i < frames.length; i++) { SourceFrame frame = frames[i]; SourceData data = sourceMap.get(frame.entry); if (data == null) { if (BamDecoder.isValid(frame.entry)) { data = new SourceData(BamDecoder.loadBam(frame.entry)); } else { data = new SourceData(frame.entry.getActualPath()); } sourceMap.put(frame.entry, data); } if (data.isBam) { bam.framesAddBamFrame(i, data.decoder, data.control, frame.index, data.cm); } else { bam.framesAddImage(i, data.file, frame.index); } } bam.updateFramesList(); return true; } return false; } private boolean applyCenterData(boolean silent) throws Exception { if (sectionCenter != null) { int entryCount = sectionCenter.getEntryCount(); for (int i = 0; i < entryCount; i++) { IniMapEntry entry = sectionCenter.getEntry(i); int listIndex = Misc.toNumber(entry.getKey(), -1); if (listIndex >= 0 && listIndex < bam.modelFrames.getSize()) { String[] numbers = entry.getValue().trim().split(Character.toString(SEPARATOR_NUMBER)); if (numbers.length >= 2) { int x = Misc.toNumber(numbers[0].trim(), Integer.MIN_VALUE); int y = Misc.toNumber(numbers[1].trim(), Integer.MIN_VALUE); if (x != Integer.MIN_VALUE && y != Integer.MIN_VALUE) { PseudoBamFrameEntry bfe = bam.modelFrames.getElementAt(listIndex); bfe.setCenterX(x); bfe.setCenterY(y); } } } } bam.updateFramesList(); return true; } return false; } private boolean applyCycleData(boolean silent) throws Exception { if (sectionCycles != null) { if (bam.modelFrames.getSize() == 0) { throw new Exception("Unable to add cycle definitions. No frames available."); } // preparing cycle definitions int entryCount = sectionCycles.getEntryCount(); HashMap<Integer, int[]> cycles = new HashMap<Integer, int[]>(); int maxCycle = -1; for (int i = 0; i < entryCount; i++) { IniMapEntry entry = sectionCycles.getEntry(i); int cycleIndex = Misc.toNumber(entry.getKey(), -1); if (cycleIndex >= 0) { String value = entry.getValue().trim(); String[] values = (value.isEmpty()) ? new String[0] : value.split(Character.toString(SEPARATOR_NUMBER)); int[] cycleList = new int[values.length]; for (int j = 0; j < cycleList.length; j++) { int n = Misc.toNumber(values[j].trim(), -1); n = Math.max(0, Math.min(bam.modelFrames.getSize() - 1, n)); cycleList[j] = n; } cycles.put(Integer.valueOf(cycleIndex), cycleList); maxCycle = Math.max(maxCycle, cycleIndex); } } if (maxCycle < 0) { // no cycles defined -> return successfully return true; } // post-processing int[][] cycleArray = new int[maxCycle + 1][]; for (Iterator<Integer> iter = cycles.keySet().iterator(); iter.hasNext();) { Integer idx = iter.next(); cycleArray[idx] = cycles.get(idx); } bam.filterRemoveAll(); bam.cyclesRemoveAll(); // applying cycle definitions final int[] emptyCycle = new int[0]; for (int i = 0; i < cycleArray.length; i++) { int[] curCycle = (cycleArray[i] != null) ? cycleArray[i] : emptyCycle; bam.modelCycles.add(curCycle); } bam.updateCyclesList(); return true; } return false; } private boolean applyFilterData(boolean silent) throws Exception { class Config { public String name; public String param; public Config() {} } if (sectionFilters != null) { if (bam.modelFrames.getSize() == 0) { throw new Exception("Unable to add filters. No frames available."); } // preparing filter list int entryCount = sectionFilters.getEntryCount(); HashMap<Integer, Config> filterMap = new HashMap<Integer, Config>(); int maxIndex = -1; for (int i = 0; i < entryCount; i++) { IniMapEntry entry = sectionFilters.getEntry(i); String key = entry.getKey(); if (key.startsWith(KEY_FILTER_NAME)) { Integer idx = Integer.valueOf(Misc.toNumber(key.substring(KEY_FILTER_NAME.length()), -1)); if (idx >= 0) { String name = entry.getValue().trim(); Config config = filterMap.get(idx); if (config == null) { config = new Config(); filterMap.put(idx, config); } config.name = name; maxIndex = Math.max(maxIndex, idx); } } else if (key.startsWith(KEY_FILTER_CONFIG)) { Integer idx = Integer.valueOf(Misc.toNumber(key.substring(KEY_FILTER_CONFIG.length()), -1)); if (idx >= 0) { String param = entry.getValue().trim(); Config config = filterMap.get(idx); if (config == null) { config = new Config(); filterMap.put(idx, config); } config.param = param; maxIndex = Math.max(maxIndex, idx); } } } if (maxIndex < 0) { // no filters defined -> return successfully return true; } // post-processing data Config[] configArray = new Config[maxIndex + 1]; for (Iterator<Integer> iter = filterMap.keySet().iterator(); iter.hasNext();) { Integer idx = iter.next(); Config config = filterMap.get(idx); if (config.name != null) { if (config.param == null) { config.param = ""; } configArray[idx] = config; } } // applying filter list bam.filterRemoveAll(); for (int i = 0; i < configArray.length; i++) { Config config = configArray[i]; if (config != null) { BamFilterFactory.FilterInfo info = BamFilterFactory.getFilterInfo(config.name); if (info != null) { BamFilterBase filter = bam.filterAdd(info); if (filter != null) { filter.setConfiguration(config.param); } } } } bam.updateFilterList(); return true; } return false; } // Saves data to specified INI file without user-interaction and optionally without feedback. private boolean saveData(Path outFile, boolean silent) { boolean retVal = false; if (outFile != null) { StringBuilder sb = new StringBuilder(); // creating global section sb.append('[').append(SECTION_GLOBAL).append(']').append(Misc.LINE_SEPARATOR); sb.append(KEY_VERSION).append('=').append(VERSION).append(Misc.LINE_SEPARATOR); sb.append(Misc.LINE_SEPARATOR); // creating frames section if (isFramesSelected()) { sb.append('[').append(SECTION_FRAMES).append(']').append(Misc.LINE_SEPARATOR); for (int i = 0; i < bam.modelFrames.getSize(); i++) { PseudoBamFrameEntry entry = bam.modelFrames.getElementAt(i); String path = entry.getOption(BAM_FRAME_OPTION_PATH).toString(); int index = ((Number)entry.getOption(BAM_FRAME_OPTION_SOURCE_INDEX)).intValue(); sb.append(Integer.toString(i)).append('=').append(path); sb.append(SEPARATOR_FRAME).append(Integer.toString(index)); sb.append(Misc.LINE_SEPARATOR); } sb.append(Misc.LINE_SEPARATOR); } // creating center section if (isCenterSelected()) { sb.append('[').append(SECTION_CENTER).append(']').append(Misc.LINE_SEPARATOR); for (int i = 0; i < bam.modelFrames.getSize(); i++) { PseudoBamFrameEntry entry = bam.modelFrames.getElementAt(i); sb.append(Integer.toString(i)).append('='); sb.append(entry.getCenterX()).append(SEPARATOR_NUMBER).append(entry.getCenterY()); sb.append(Misc.LINE_SEPARATOR); } sb.append(Misc.LINE_SEPARATOR); } // creating cycles section if (isCyclesSelected()) { sb.append('[').append(SECTION_CYCLES).append(']').append(Misc.LINE_SEPARATOR); for (int i = 0; i < bam.modelCycles.getSize(); i++) { PseudoBamCycleEntry entry = bam.modelCycles.getElementAt(i); sb.append(Integer.toString(i)).append('='); for (int j = 0; j < entry.size(); j++) { if (j > 0) { sb.append(SEPARATOR_NUMBER); } sb.append(entry.get(j)); } sb.append(Misc.LINE_SEPARATOR); } sb.append(Misc.LINE_SEPARATOR); } // creating filters section if (isFiltersSelected()) { sb.append('[').append(SECTION_FILTERS).append(']').append(Misc.LINE_SEPARATOR); for (int i = 0; i < bam.modelFilters.getSize(); i++) { BamFilterBase filter = bam.modelFilters.getElementAt(i); sb.append(KEY_FILTER_NAME).append(i).append('=').append(filter.getName()).append(Misc.LINE_SEPARATOR); sb.append(KEY_FILTER_CONFIG).append(i).append('=').append(filter.getConfiguration()).append(Misc.LINE_SEPARATOR); } sb.append(Misc.LINE_SEPARATOR); } // writing data to disk try (BufferedWriter bw = Files.newBufferedWriter(outFile)) { bw.write(sb.toString()); if (!silent) { JOptionPane.showMessageDialog(bam, "Export completed.", "Message", JOptionPane.INFORMATION_MESSAGE); } retVal = true; } catch (IOException e) { e.printStackTrace(); if (!silent) { JOptionPane.showMessageDialog(bam, "Error exporting BAM session.", "Error", JOptionPane.ERROR_MESSAGE); } } } return retVal; } // Clears all BAM session data private void resetData() { sectionFrames = null; sectionCenter = null; sectionCycles = null; sectionFilters = null; } // Shows options dialog and returns whether user selected "Accept" or "Cancel" private boolean getSelection(boolean isExport) { if (isExport) { setTitle("Export BAM session"); lSelect.setText(QUESTION_EXPORT); cbFrames.setEnabled(bam.modelFrames.getSize() > 0); cbCenter.setEnabled(bam.modelFrames.getSize() > 0); cbCycles.setEnabled(bam.modelCycles.getSize() > 0); cbFilters.setEnabled(bam.modelFilters.getSize() > 0); } else { setTitle("Import BAM session"); lSelect.setText(QUESTION_IMPORT); cbFrames.setEnabled(sectionFrames != null); cbCenter.setEnabled(sectionCenter != null); cbCycles.setEnabled(sectionCycles != null); cbFilters.setEnabled(sectionFilters != null); } cbFrames.setSelected(cbFrames.isEnabled()); cbCenter.setSelected(cbCenter.isEnabled()); cbCycles.setSelected(cbCycles.isEnabled()); cbFilters.setSelected(cbFilters.isEnabled()); pack(); setLocationRelativeTo(bam); bAccept.requestFocusInWindow(); setVisible(true); return isAccepted(); } // Attempts to determine a fitting default name for the ini file. private Path getDefaultIniName(String defaultName) { Path retVal = null; if (bam.modelFrames.getSize() > 0) { String name = bam.modelFrames.getElementAt(0).getOption(PseudoBamDecoder.OPTION_STRING_LABEL).toString(); if (name != null) { if (name.indexOf(':') > 0) { name = name.substring(0, name.indexOf(':')); } if (!name.isEmpty()) { Path file = ConvertToBam.currentPath.resolve(name); retVal = StreamUtils.replaceFileExtension(file, "ini"); } } } if (retVal == null) { Path file = ConvertToBam.currentPath.resolve(defaultName); retVal = StreamUtils.replaceFileExtension(file, "ini"); } return retVal; } // Returns whether the dialog options have been accepted. private boolean isAccepted() { return accepted; } // Returns whether the frames option has been selected. private boolean isFramesSelected() { return (cbFrames.isEnabled() && cbFrames.isSelected()); } // Returns whether the center position option has been selected. private boolean isCenterSelected() { return (cbCenter.isEnabled() && cbCenter.isSelected()); } // Returns whether the cycle definition option has been selected. private boolean isCyclesSelected() { return (cbCycles.isEnabled() && cbCycles.isSelected()); } // Returns whether the filter configuration option has been selected. private boolean isFiltersSelected() { return (cbFilters.isEnabled() && cbFilters.isSelected()); } // Disposes the dialog and marks it as accepted private void accept() { setVisible(false); accepted = true; } // Disposes the dialog and marks it as cancelled private void cancel() { setVisible(false); accepted = false; } // Initializes the basic dialog layout private void init() { setLayout(new BorderLayout()); GridBagConstraints c = new GridBagConstraints(); bAccept.addActionListener(this); bCancel.addActionListener(this); getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), bCancel); getRootPane().getActionMap().put(bCancel, new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { cancel(); } }); getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), bAccept); getRootPane().getActionMap().put(bAccept, new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { accept(); } }); JPanel pList = new JPanel(new GridBagLayout()); lSelect.setText(QUESTION_EXPORT); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(4, 0, 0, 0), 0, 0); pList.add(lSelect, c); c = ViewerUtil.setGBC(c, 0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(8, 0, 0, 0), 0, 0); pList.add(cbFrames, c); c = ViewerUtil.setGBC(c, 0, 2, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(4, 0, 0, 0), 0, 0); pList.add(cbCenter, c); c = ViewerUtil.setGBC(c, 0, 3, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(4, 0, 0, 0), 0, 0); pList.add(cbCycles, c); c = ViewerUtil.setGBC(c, 0, 4, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(4, 0, 0, 0), 0, 0); pList.add(cbFilters, c); JPanel pBottom = new JPanel(new GridBagLayout()); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0); pBottom.add(new JPanel(), c); c = ViewerUtil.setGBC(c, 1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0); pBottom.add(bAccept, c); c = ViewerUtil.setGBC(c, 2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(0, 8, 0, 0), 0, 0); pBottom.add(bCancel, c); c = ViewerUtil.setGBC(c, 3, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0); pBottom.add(new JPanel(), c); JPanel pMain = new JPanel(new GridBagLayout()); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.BOTH, new Insets(8, 8, 16, 8), 0, 0); pMain.add(pList, c); c = ViewerUtil.setGBC(c, 0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.BOTH, new Insets(8, 8, 8, 8), 0, 0); pMain.add(pBottom, c); add(pMain, BorderLayout.CENTER); pack(); setMinimumSize(getPreferredSize()); setDefaultCloseOperation(JDialog.HIDE_ON_CLOSE); } } }