diff --git a/.github/workflows/ant.yml b/.github/workflows/ant.yml deleted file mode 100644 index 534f50ec6..000000000 --- a/.github/workflows/ant.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: Java CI with Apache Ant - -on: - push: - branches: [ devel, master ] - pull_request: - branches: [ devel, master ] - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Set up JDK 1.8 - uses: actions/setup-java@v1 - with: - java-version: 1.8 - - name: Build with Ant - run: ant -noinput -buildfile build.xml diff --git a/src/org/infinity/gui/ProgressCellRenderer.java b/src/org/infinity/gui/ProgressCellRenderer.java new file mode 100644 index 000000000..560f68c45 --- /dev/null +++ b/src/org/infinity/gui/ProgressCellRenderer.java @@ -0,0 +1,50 @@ +// 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; + +import javax.swing.JProgressBar; +import javax.swing.JTable; +import javax.swing.table.TableCellRenderer; +import java.awt.Component; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class ProgressCellRenderer extends JProgressBar implements TableCellRenderer { + + /** + * Creates a JProgressBar with the range 0,100. + */ + public ProgressCellRenderer() { + super(0, 100); + setValue(0); + setString("0%"); + setStringPainted(true); + } + + public Component getTableCellRendererComponent( + JTable table, + Object value, + boolean isSelected, + boolean hasFocus, + int row, + int column) { + + //value is a percentage e.g. 95% + final String sValue = value.toString(); + int index = sValue.indexOf('%'); + if (index != -1) { + int p = 0; + try { + p = Integer.parseInt(sValue.substring(0, index)); + } catch (NumberFormatException e) { + Logger.getLogger(String.valueOf(ProgressCellRenderer.class)).log(Level.WARNING, "Number Format Exception : " + e.getMessage()); + } + + this.setValue(p); + this.setString(sValue); + } + return this; + } +} diff --git a/src/org/infinity/gui/converter/ConvertToMos.java b/src/org/infinity/gui/converter/ConvertToMos.java index 57af8805f..f8dff3633 100644 --- a/src/org/infinity/gui/converter/ConvertToMos.java +++ b/src/org/infinity/gui/converter/ConvertToMos.java @@ -14,41 +14,52 @@ import java.awt.Insets; import java.awt.Point; import java.awt.Rectangle; -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.*; import java.awt.image.BufferedImage; import java.awt.image.DataBufferInt; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; +import java.io.File; import java.io.OutputStream; +import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Vector; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.logging.Level; +import java.util.logging.Logger; import javax.imageio.ImageIO; -import javax.swing.BorderFactory; +import javax.swing.JTabbedPane; +import javax.swing.JTable; +import javax.swing.JTextField; import javax.swing.JButton; -import javax.swing.JCheckBox; -import javax.swing.JComboBox; -import javax.swing.JFileChooser; +import javax.swing.JTabbedPane; +import javax.swing.JSpinner; import javax.swing.JLabel; +import javax.swing.JComboBox; +import javax.swing.JCheckBox; +import javax.swing.JScrollPane; +import javax.swing.SwingWorker; +import javax.swing.ProgressMonitor; import javax.swing.JOptionPane; import javax.swing.JPanel; -import javax.swing.JSpinner; -import javax.swing.JTabbedPane; -import javax.swing.JTextField; -import javax.swing.ProgressMonitor; -import javax.swing.SpinnerNumberModel; -import javax.swing.SwingWorker; +import javax.swing.BorderFactory; +import javax.swing.SwingUtilities; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; +import javax.swing.JFileChooser; +import javax.swing.SpinnerNumberModel; import javax.swing.filechooser.FileNameExtensionFilter; +import javax.swing.table.AbstractTableModel; +import javax.swing.table.DefaultTableModel; +import javax.swing.table.TableColumn; import org.infinity.gui.ChildFrame; +import org.infinity.gui.ProgressCellRenderer; import org.infinity.gui.ViewerUtil; import org.infinity.gui.WindowBlocker; import org.infinity.icon.Icons; @@ -59,360 +70,45 @@ import org.infinity.util.BinPack2D; import org.infinity.util.DynamicArray; import org.infinity.util.IntegerHashMap; -import org.infinity.util.io.FileEx; import org.infinity.util.io.FileManager; import org.infinity.util.io.StreamUtils; -public class ConvertToMos extends ChildFrame - implements ActionListener, PropertyChangeListener, ChangeListener, FocusListener -{ +/** + * Class converts images to MOS type v1 and v2. v1 has the ability to convert multiple files at once. + */ +public class ConvertToMos extends ChildFrame implements ActionListener, PropertyChangeListener, ChangeListener, FocusListener { private static String currentDir = Profile.getGameRoot().toString(); + private int fileDoneCount = 0; + private JTabbedPane tabPane; - private JTextField tfInputV1, tfOutputV1, tfInputV2, tfOutputV2; + private JTable tfInputTableV1; + private JTable tfOutputTableV1; + // location where output will be saved + private JTextField tfOutputV1; + private JTextField tfInputV2; + private JTextField tfOutputV2; private JButton bInputV1, bOutputV1, bInputV2, bOutputV2, bCompressionHelp; private JButton bConvert, bCancel; private JSpinner sPvrzIndex; private JLabel lPvrzInfo; private JComboBox cbCompression; private JCheckBox cbCompress, cbCloseOnExit; - private SwingWorker, Void> workerConvert; - private WindowBlocker blocker; - - /** - * Converts an image into a MOS V1 resource. - * @param parent This parameter is needed for the progress monitor only. - * @param img The source image to convert into a MOS resource. - * @param mosFileName The name of the resulting MOS file. - * @param compressed If {@code true}, converts into a compressed BAMC file. - * @param fastConvert If {@code true}, uses a fast but less accurate color reduction algorith. - * @param result Returns more specific information about the conversion process. Data placed in the - * first item indicates success, data in the second item indicates failure. - * @param showProgress Specify whether to show a progress monitor (needs a valid 'parent' parameter). - * @return {@code true} if the conversion finished successfully, {@code false} otherwise. - */ - public static boolean convertV1(Component parent, BufferedImage img, String mosFileName, - boolean compressed, List result, boolean showProgress) - { - // checking parameters - if (result == null) { - return false; - } - if (img == null) { - result.add(null); - result.add("No source image specified."); - return false; - } - if (mosFileName == null || mosFileName.isEmpty()) { - result.add(null); - result.add("No output filename specified."); - return false; - } - - // preparing MOS V1 header - int width = img.getWidth(); - int height = img.getHeight(); - int cols = (width + 63) / 64; - int rows = (height + 63) / 64; - int tileCount = cols * rows; - int palOfs = 24; - int tableOfs = palOfs + tileCount*1024; - int dataOfs = tableOfs + tileCount*4; - byte[] dst = new byte[dataOfs + width*height]; - System.arraycopy("MOS V1 ".getBytes(), 0, dst, 0, 8); - DynamicArray.putShort(dst, 8, (short)width); - DynamicArray.putShort(dst, 10, (short)height); - DynamicArray.putShort(dst, 12, (short)cols); - DynamicArray.putShort(dst, 14, (short)rows); - DynamicArray.putInt(dst, 16, 64); - DynamicArray.putInt(dst, 20, palOfs); - - ProgressMonitor progress = null; - try { - String note = "Converting tile %d / %d"; - int progressIndex = 0, progressMax = tileCount; - if (showProgress) { - progress = new ProgressMonitor(parent, "Converting MOS...", - String.format(note, progressIndex, progressMax), 0, progressMax); - progress.setMillisToDecideToPopup(250); - progress.setMillisToPopup(500); - } - - // creating list of tiles as int[] arrays - List tileList = new ArrayList<>(cols*rows); - for (int y = 0; y < rows; y++) { - for (int x = 0; x < cols; x++) { - int tileX = x * 64; - int tileY = y * 64; - int tileW = (tileX + 64 < width) ? 64 : (width - tileX); - int tileH = (tileY + 64 < height) ? 64 : (height - tileY); - int[] rgbArray = new int[tileW*tileH]; - img.getRGB(tileX, tileY, tileW, tileH, rgbArray, 0, tileW); - tileList.add(rgbArray); - } - } - - // applying color reduction to each tile - int[] palette = new int[255]; - byte[] tilePalette = new byte[1024]; - byte[] tileData = new byte[64*64]; - int curPalOfs = palOfs, curTableOfs = tableOfs, curDataOfs = dataOfs; - IntegerHashMap colorCache = new IntegerHashMap<>(1536); // caching RGBColor -> index - for (int tileIdx = 0; tileIdx < tileList.size(); tileIdx++) { - colorCache.clear(); - if (showProgress) { - if (progress.isCanceled()) { - dst = null; - result.add(null); - result.add("Conversion has been cancelled."); - return false; - } - progressIndex++; - if ((progressIndex % 10) == 0) { - progress.setProgress(progressIndex); - progress.setNote(String.format(note, progressIndex, progressMax)); - } - } - int[] pixels = tileList.get(tileIdx); - if (ColorConvert.medianCut(pixels, 255, palette, true)) { - // filling palette - // first palette entry denotes transparency - tilePalette[0] = tilePalette[2] = tilePalette[3] = 0; tilePalette[1] = (byte)255; - for (int i = 1; i < 256; i++) { - tilePalette[(i << 2) + 0] = (byte)(palette[i - 1] & 0xff); - tilePalette[(i << 2) + 1] = (byte)((palette[i - 1] >>> 8) & 0xff); - tilePalette[(i << 2) + 2] = (byte)((palette[i - 1] >>> 16) & 0xff); - tilePalette[(i << 2) + 3] = 0; - colorCache.put(palette[i - 1], (byte)(i - 1)); - } - // filling pixel data - for (int i = 0; i < pixels.length; i++) { - if ((pixels[i] & 0xff000000) == 0) { - tileData[i] = 0; - } else { - Byte palIndex = colorCache.get(pixels[i]); - if (palIndex != null) { - tileData[i] = (byte)(palIndex + 1); - } else { - byte color = (byte)ColorConvert.getNearestColor(pixels[i], palette, 0.0, null); - tileData[i] = (byte)(color + 1); - colorCache.put(pixels[i], color); - } - } - } - } else { - // error handling - dst = null; - result.add(null); - result.add(String.format("Error processing tile #%d. Conversion cancelled.", tileIdx)); - return false; - } - - System.arraycopy(tilePalette, 0, dst, curPalOfs, 1024); - curPalOfs += 1024; - DynamicArray.putInt(dst, curTableOfs, curDataOfs - dataOfs); - curTableOfs += 4; - System.arraycopy(tileData, 0, dst, curDataOfs, pixels.length); - curDataOfs += pixels.length; - } - tileList.clear(); tileList = null; - tileData = null; tilePalette = null; /*hclPalette = null;*/ palette = null; - - // optionally compressing to MOSC V1 - if (compressed) { - dst = Compressor.compress(dst, "MOSC", "V1 "); - } - - // writing MOS file to disk - Path mosFile = FileManager.resolve(mosFileName); - try (OutputStream os = StreamUtils.getOutputStream(mosFile, true)) { - os.write(dst); - } catch (Exception e) { - e.printStackTrace(); - result.add(null); - result.add("Error writing TIS file to disk."); - return false; - } - } finally { - // some cleaning up - if (showProgress) { - progress.close(); - progress = null; - } - } - - // generating conversion summary - result.add("Conversion finished successfully."); - return true; - } - - /** - * Converts an image into a MOS V2 resource. - * @param parent This parameter is needed for the progress monitor only. - * @param img The source image to convert into a MOS resource. - * @param mosFileName The name of the resulting MOS file. - * @param dxtType The desired compression type. - * @param pvrzIndex The starting index for PVRZ files. - * @param result Returns more specific information about the conversion process. Data placed in the - * first item indicates success, data in the second item indicates failure. - * @param showProgress Specify whether to show a progress monitor (needs a valid 'parent' parameter). - * @return {@code true} if the conversion finished successfully, {@code false} otherwise. - */ - public static boolean convertV2(Component parent, BufferedImage img, String mosFileName, - DxtEncoder.DxtType dxtType, int pvrzIndex, - List result, boolean showProgress) - { - // checking parameters - if (result == null) { - return false; - } - if (img == null) { - result.add(null); - result.add("No source image specified."); - return false; - } - if (mosFileName == null || mosFileName.isEmpty()) { - result.add(null); - result.add("No output filename specified."); - return false; - } - if (pvrzIndex < 0 || pvrzIndex > 99999) { - result.add(null); - result.add("PVRZ index is out of range [0..99999]."); - return false; - } - - // preparing variables - ProgressMonitor progress = null; - int width = img.getWidth(); - int height = img.getHeight(); - List pageList = new ArrayList<>(); - List entryList = new ArrayList<>(); - - try { - if (showProgress) { - // preparing progress meter - progress = new ProgressMonitor(parent, "Converting MOS...", "Preparing data", 0, 5); - progress.setMillisToDecideToPopup(0); - progress.setMillisToPopup(0); - progress.setProgress(0); - } - - // processing tiles - final int pageDim = 1024; - final BinPack2D.HeuristicRules binPackRule = BinPack2D.HeuristicRules.BOTTOM_LEFT_RULE; - - int x = 0, y = 0, pOfs = 0; - while (pOfs < width*height) { - int w = Math.min(pageDim, width - x); - int h = Math.min(pageDim, height - y); - Dimension space = new Dimension((w+3) & ~3, (h+3) & ~3); - int pageIdx = -1; - Rectangle rectMatch = null; - for (int i = 0; i < pageList.size(); i++) { - BinPack2D packer = pageList.get(i); - rectMatch = packer.insert(space.width, space.height, binPackRule); - if (rectMatch.height > 0) { - pageIdx = i; - break; - } - } - - // create new page? - if (pageIdx < 0) { - BinPack2D packer = new BinPack2D(pageDim, pageDim); - pageList.add(packer); - pageIdx = pageList.size() - 1; - rectMatch = packer.insert(space.width, space.height, binPackRule); - } - - // register page entry - MosEntry entry = new MosEntry(pvrzIndex + pageIdx, - new Point(rectMatch.x, rectMatch.y), - w, h, new Point(x, y)); - entryList.add(entry); - - // advance scanning - if (x + pageDim >= width) { - x = 0; - y += pageDim; - } else { - x += pageDim; - } - pOfs = y*width + x; - } - - // check PVRZ index again - if (pvrzIndex + pageList.size() > 100000) { - result.add(null); - result.add(String.format("One or more PVRZ indices exceed the max. possible value of 99999.\n" + - "Please choose a start index smaller than or equal to %d.", - 100000 - pageList.size())); - return false; - } - - byte[] dst = new byte[24 + entryList.size()*28]; // header + tiles - int dstOfs = 0; - - // writing MOS header and data - System.arraycopy("MOS V2 ".getBytes(), 0, dst, 0, 8); - DynamicArray.putInt(dst, 8, width); - DynamicArray.putInt(dst, 12, height); - DynamicArray.putInt(dst, 16, entryList.size()); - DynamicArray.putInt(dst, 20, 24); - dstOfs += 24; - for (int i = 0; i < entryList.size(); i++, dstOfs += 28) { - MosEntry entry = entryList.get(i); - DynamicArray.putInt(dst, dstOfs, entry.page); - DynamicArray.putInt(dst, dstOfs + 4, entry.srcLocation.x); - DynamicArray.putInt(dst, dstOfs + 8, entry.srcLocation.y); - DynamicArray.putInt(dst, dstOfs + 12, entry.width); - DynamicArray.putInt(dst, dstOfs + 16, entry.height); - DynamicArray.putInt(dst, dstOfs + 20, entry.dstLocation.x); - DynamicArray.putInt(dst, dstOfs + 24, entry.dstLocation.y); - } - - // writing MOS file to disk - Path mosFile = FileManager.resolve(mosFileName); - try (OutputStream os = StreamUtils.getOutputStream(mosFile, true)) { - os.write(dst); - } catch (Exception e) { - e.printStackTrace(); - result.add(null); - result.add("Error writing MOS file to disk."); - return false; - } - dst = null; - - // generating PVRZ files - if (!createPvrzPages(mosFile.getParent(), img, dxtType, pageList, entryList, - result, progress)) { - return false; - } - } finally { - // some cleaning up - img.flush(); - if (progress != null) { - progress.close(); - progress = null; - } - } - - // generating conversion summary - result.add("Conversion finished successfully."); - return true; - } + private DefaultTableModel inputTableModel; + private DefaultTableModel outputTableModel; + private JScrollPane spInputScroll; + private JScrollPane spOutputScroll; // Returns a list of supported graphics file formats private static FileNameExtensionFilter[] getInputFilters() { FileNameExtensionFilter[] filters = new FileNameExtensionFilter[] { - new FileNameExtensionFilter("Graphics files (*.bmp, *.png, *,jpg, *.jpeg)", - "bam", "bmp", "png", "jpg", "jpeg"), - new FileNameExtensionFilter("BMP files (*.bmp)", "bmp"), - new FileNameExtensionFilter("PNG files (*.png)", "png"), - new FileNameExtensionFilter("JPEG files (*.jpg, *.jpeg)", "jpg", "jpeg") + new FileNameExtensionFilter("Graphics files (*.bmp, *.png, *,jpg, *.jpeg)", + "bam", "bmp", "png", "jpg", "jpeg"), + new FileNameExtensionFilter("BMP files (*.bmp)", "bmp"), + new FileNameExtensionFilter("PNG files (*.png)", "png"), + new FileNameExtensionFilter("JPEG files (*.jpg, *.jpeg)", "jpg", "jpeg") }; return filters; } @@ -528,6 +224,392 @@ protected boolean windowClosing(boolean forced) throws Exception } //--------------------- End Class ChildFrame --------------------- +class TaskPropertyChange implements PropertyChangeListener { + public int fileCount = 0; + public void setFileCount(int fileCount) { + this.fileCount = fileCount; + } + + @Override + public void propertyChange(PropertyChangeEvent event) { + if ("progress".equals(event.getPropertyName())) { + Integer progressIndex = (Integer) event.getNewValue(); + tfOutputTableV1.setValueAt(progressIndex +"%", fileCount, 1); + +// System.out.println(fileCount); + if(progressIndex == 100) { + fileDoneCount++; + Logger.getLogger(this.getClass().getName()).log(Level.INFO, "--- File Done Count --- " + fileDoneCount); + + if(fileDoneCount == tfOutputTableV1.getRowCount()) { + fileDoneCount = 0; + Logger.getLogger(this.getClass().getName()).log(Level.INFO, "--- All files converted --- "); + + if(cbCloseOnExit.isSelected()) { + hideWindow(); + } + } + } + } + } +} + + class Task extends SwingWorker, Integer> { + public String inputPath; + public Integer fileCount; + public Integer progressIndex; + + public String getInputPath() { + return inputPath; + } + + public void setInputPath(String inputPath) { + this.inputPath = inputPath; + } + + public Integer getFileCount() { + return fileCount; + } + + public void setFileCount(Integer fileCount) { + this.fileCount = fileCount; + } + + + public Integer getProgressIndex() { + return progressIndex; + } + + /** + * Converts an image into a MOS V1 resource. + * @param img The source image to convert into a MOS resource. + * @param mosFileName The name of the resulting MOS file. + * @param compressed If {@code true}, converts into a compressed BAMC file. + * @param result Returns more specific information about the conversion process. Data placed in the + * first item indicates success, data in the second item indicates failure. + * @return {@code true} if the conversion finished successfully, {@code false} otherwise. + */ + public boolean convertV1(BufferedImage img, String mosFileName, boolean compressed, List result) + { + // checking parameters + if (result == null) { + return false; + } + if (img == null) { + result.add(null); + result.add("No source image specified."); + return false; + } + if (mosFileName == null || mosFileName.isEmpty()) { + result.add(null); + result.add("No output filename specified."); + return false; + } + + // preparing MOS V1 header + int width = img.getWidth(); + int height = img.getHeight(); + int cols = (width + 63) / 64; + int rows = (height + 63) / 64; + int tileCount = cols * rows; + int palOfs = 24; + int tableOfs = palOfs + tileCount*1024; + int dataOfs = tableOfs + tileCount*4; + byte[] dst = new byte[dataOfs + width*height]; + System.arraycopy("MOS V1 ".getBytes(), 0, dst, 0, 8); + DynamicArray.putShort(dst, 8, (short)width); + DynamicArray.putShort(dst, 10, (short)height); + DynamicArray.putShort(dst, 12, (short)cols); + DynamicArray.putShort(dst, 14, (short)rows); + DynamicArray.putInt(dst, 16, 64); + DynamicArray.putInt(dst, 20, palOfs); + + try { + String note = "Converting tile %d / %d"; + double progressIndexPer = 0, progressMaxPer = 0; + int progressIndex = 0, progressMax = tileCount; + + // creating list of tiles as int[] arrays + List tileList = new ArrayList(cols*rows); + for (int y = 0; y < rows; y++) { + for (int x = 0; x < cols; x++) { + int tileX = x * 64; + int tileY = y * 64; + int tileW = (tileX + 64 < width) ? 64 : (width - tileX); + int tileH = (tileY + 64 < height) ? 64 : (height - tileY); + int[] rgbArray = new int[tileW*tileH]; + img.getRGB(tileX, tileY, tileW, tileH, rgbArray, 0, tileW); + tileList.add(rgbArray); + } + } + + // applying color reduction to each tile + int[] palette = new int[255]; + byte[] tilePalette = new byte[1024]; + byte[] tileData = new byte[64*64]; + int curPalOfs = palOfs; + int curTableOfs = tableOfs; + int curDataOfs = dataOfs; + IntegerHashMap colorCache = new IntegerHashMap(1536); // caching RGBColor -> index + for (int tileIdx = 0; tileIdx < tileList.size(); tileIdx++) { + colorCache.clear(); + + progressIndex++; + progressIndexPer = progressIndex; + progressMaxPer = progressMax; + double percentage = Math.round((progressIndexPer / progressMaxPer) * 100); + Logger.getLogger(this.getClass().getName()).log(Level.INFO, progressIndex + " " + percentage); + setProgress((int)percentage); + + int[] pixels = tileList.get(tileIdx); + if (ColorConvert.medianCut(pixels, 255, palette, true)) { + // filling palette + // first palette entry denotes transparency + tilePalette[0] = tilePalette[2] = tilePalette[3] = 0; tilePalette[1] = (byte)255; + for (int i = 1; i < 256; i++) { + tilePalette[(i << 2) + 0] = (byte)(palette[i - 1] & 0xff); + tilePalette[(i << 2) + 1] = (byte)((palette[i - 1] >>> 8) & 0xff); + tilePalette[(i << 2) + 2] = (byte)((palette[i - 1] >>> 16) & 0xff); + tilePalette[(i << 2) + 3] = 0; + colorCache.put(palette[i - 1], (byte)(i - 1)); + } + // filling pixel data + for (int i = 0; i < pixels.length; i++) { + if ((pixels[i] & 0xff000000) == 0) { + tileData[i] = 0; + } else { + Byte palIndex = colorCache.get(pixels[i]); + if (palIndex != null) { + tileData[i] = (byte)(palIndex + 1); + } else { + byte color = (byte)ColorConvert.getNearestColor(pixels[i], palette, 0.0, null); + tileData[i] = (byte)(color + 1); + colorCache.put(pixels[i], color); + } + } + } + } else { + // error handling + dst = null; + result.add(null); + result.add(String.format("Error processing tile #%d. Conversion cancelled.", tileIdx)); + return false; + } + + System.arraycopy(tilePalette, 0, dst, curPalOfs, 1024); + curPalOfs += 1024; + DynamicArray.putInt(dst, curTableOfs, curDataOfs - dataOfs); + curTableOfs += 4; + System.arraycopy(tileData, 0, dst, curDataOfs, pixels.length); + curDataOfs += pixels.length; + } + tileList.clear(); tileList = null; + tileData = null; tilePalette = null; /*hclPalette = null;*/ palette = null; + + // optionally compressing to MOSC V1 + if (compressed) { + dst = Compressor.compress(dst, "MOSC", "V1 "); + } + + // writing MOS file to disk + Path mosFile = FileManager.resolve(mosFileName); + try (OutputStream os = StreamUtils.getOutputStream(mosFile, true)) { + os.write(dst); + } catch (Exception e) { + Logger.getLogger(this.getClass().getName()).log(Level.SEVERE, "Error writing file to disk : " + e.getMessage()); + result.add(null); + result.add("Error writing MOS file to disk."); + return false; + } + } catch (Exception e) { + Logger.getLogger(this.getClass().getName()).log(Level.SEVERE, "Unable to convert tile" + e.getMessage()); + } + + // generating conversion summary + result.add("Conversion finished successfully."); + + return true; + } + + /** + * Converts an image into a MOS V2 resource. + * @param img The source image to convert into a MOS resource. + * @param mosFileName The name of the resulting MOS file. + * @param dxtType The desired compression type. + * @param pvrzIndex The starting index for PVRZ files. + * @param result Returns more specific information about the conversion process. Data placed in the + * first item indicates success, data in the second item indicates failure. + * @return {@code true} if the conversion finished successfully, {@code false} otherwise. + */ + public boolean convertV2(BufferedImage img, String mosFileName, DxtEncoder.DxtType dxtType, int pvrzIndex, List result) { + // checking parameters + if (result == null) { + return false; + } + + if (img == null) { + result.add(null); + result.add("No source image specified."); + return false; + } + + if (mosFileName == null || mosFileName.isEmpty()) { + result.add(null); + result.add("No output filename specified."); + return false; + } + + if (pvrzIndex < 0 || pvrzIndex > 99999) { + result.add(null); + result.add("PVRZ index is out of range [0..99999]."); + return false; + } + + // preparing variables + ProgressMonitor progress = null; + int width = img.getWidth(); + int height = img.getHeight(); + List pageList = new ArrayList(); + List entryList = new ArrayList(); + + try { + // processing tiles + final int pageDim = 1024; + final BinPack2D.HeuristicRules binPackRule = BinPack2D.HeuristicRules.BOTTOM_LEFT_RULE; + + int x = 0, y = 0, pOfs = 0; + while (pOfs < width*height) { + int w = Math.min(pageDim, width - x); + int h = Math.min(pageDim, height - y); + Dimension space = new Dimension((w+3) & ~3, (h+3) & ~3); + int pageIdx = -1; + Rectangle rectMatch = null; + for (int i = 0; i < pageList.size(); i++) { + BinPack2D packer = pageList.get(i); + rectMatch = packer.insert(space.width, space.height, binPackRule); + if (rectMatch.height > 0) { + pageIdx = i; + break; + } + } + + // create new page? + if (pageIdx < 0) { + BinPack2D packer = new BinPack2D(pageDim, pageDim); + pageList.add(packer); + pageIdx = pageList.size() - 1; + rectMatch = packer.insert(space.width, space.height, binPackRule); + } + + // register page entry + MosEntry entry = new MosEntry(pvrzIndex + pageIdx, + new Point(rectMatch.x, rectMatch.y), + w, h, new Point(x, y)); + entryList.add(entry); + + // advance scanning + if (x + pageDim >= width) { + x = 0; + y += pageDim; + } else { + x += pageDim; + } + pOfs = y*width + x; + } + + // check PVRZ index again + if (pvrzIndex + pageList.size() > 100000) { + result.add(null); + result.add(String.format("One or more PVRZ indices exceed the max. possible value of 99999.\n" + + "Please choose a start index smaller than or equal to %d.", + 100000 - pageList.size())); + return false; + } + + byte[] dst = new byte[24 + entryList.size()*28]; // header + tiles + int dstOfs = 0; + + // writing MOS header and data + System.arraycopy("MOS V2 ".getBytes(), 0, dst, 0, 8); + DynamicArray.putInt(dst, 8, width); + DynamicArray.putInt(dst, 12, height); + DynamicArray.putInt(dst, 16, entryList.size()); + DynamicArray.putInt(dst, 20, 24); + dstOfs += 24; + for (int i = 0; i < entryList.size(); i++, dstOfs += 28) { + MosEntry entry = entryList.get(i); + DynamicArray.putInt(dst, dstOfs, entry.page); + DynamicArray.putInt(dst, dstOfs + 4, entry.srcLocation.x); + DynamicArray.putInt(dst, dstOfs + 8, entry.srcLocation.y); + DynamicArray.putInt(dst, dstOfs + 12, entry.width); + DynamicArray.putInt(dst, dstOfs + 16, entry.height); + DynamicArray.putInt(dst, dstOfs + 20, entry.dstLocation.x); + DynamicArray.putInt(dst, dstOfs + 24, entry.dstLocation.y); + } + + // writing MOS file to disk + Path mosFile = FileManager.resolve(mosFileName); + try (OutputStream os = StreamUtils.getOutputStream(mosFile, true)) { + os.write(dst); + } catch (Exception e) { + Logger.getLogger(this.getClass().getName()).log(Level.SEVERE, "Unable to save MOS to disk : " + e.getMessage()); + result.add(null); + result.add("Error writing MOS file to disk."); + return false; + } + dst = null; + + // generating PVRZ files + if (!createPvrzPages(mosFile.getParent(), img, dxtType, pageList, entryList, + result, progress)) { + return false; + } + } finally { + // some cleaning up + img.flush(); + if (progress != null) { + progress.close(); + } + } + + // generating conversion summary + result.add("Conversion finished successfully."); + return true; + } + + public void setProgressIndex(Integer progressIndex) { + setProgress(this.progressIndex); + this.progressIndex = progressIndex; + } + + public Integer progressMax; + public Integer getProgressMax() { + return progressMax; + } + + public void setProgressMax(Integer progressMax) { + this.progressMax = progressMax; + } + + public Task(String inputPath, Integer fileCount) { + this.inputPath = inputPath; + this.fileCount = fileCount; + + this.progressIndex = 0; + this.progressMax = 100; + } + + @Override + public List doInBackground() { + return convert(this); + } + + @Override + protected void done() { + super.done(); + setProgress(100); + } + } //--------------------- Begin Interface ActionListener --------------------- @@ -535,63 +617,60 @@ protected boolean windowClosing(boolean forced) throws Exception public void actionPerformed(ActionEvent event) { if (event.getSource() == bConvert) { - if (workerConvert == null) { final String msg = "MOS output file already exists. Overwrite?"; - Path file = null; - do { - if (tabPane.getSelectedIndex() == 0 && !tfOutputV1.getText().isEmpty()) { - file = FileManager.resolve(tfOutputV1.getText()); - } else if (tabPane.getSelectedIndex() == 1 & !tfOutputV2.getText().isEmpty()) { - file = FileManager.resolve(tfOutputV2.getText()); - } - if (file != null) { - if (!FileEx.create(file).exists() || - JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog(this, msg, "Question", - JOptionPane.YES_NO_OPTION, - JOptionPane.QUESTION_MESSAGE)) { + //loop over each file being converted + int fileCount = 0; + for (String inputPath : this.getTableInputPaths()) { + Path file = null; + if (tabPane.getSelectedIndex() == 0 && !inputPath.isEmpty()) { + file = FileManager.resolve(getTableOutputNames().get(fileCount)); + } else if (tabPane.getSelectedIndex() == 1 & !tfOutputV2.getText().isEmpty()) { + file = FileManager.resolve(tfOutputV2.getText()); + } + + if (file != null) { + if (!Files.exists(file) || JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog(this, msg, "Question", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE)) { + file = null; + Task workerConvert = new Task(inputPath, fileCount); + TaskPropertyChange taskPropertyChange = new TaskPropertyChange(); + taskPropertyChange.setFileCount(fileCount); + workerConvert.addPropertyChangeListener(taskPropertyChange); + workerConvert.execute(); + } file = null; - workerConvert = new SwingWorker, Void>() { - @Override - public List doInBackground() - { - return convert(); - } - }; - workerConvert.addPropertyChangeListener(this); - blocker = new WindowBlocker(this); - blocker.setBlocked(true); - workerConvert.execute(); } - file = null; - } - } while (file != null); - } + fileCount++; + } + } else if (event.getSource() == bCancel) { hideWindow(); + } else if (event.getSource() == tfInputTableV1) { + } else if (event.getSource() == bInputV1 || event.getSource() == bInputV2) { - String fileName = tfInputV1.getText().isEmpty() ? currentDir : tfInputV1.getText(); - Path file = FileManager.resolve(fileName).toAbsolutePath(); - if ((fileName = getImageFileName(file)) != null) { - file = FileManager.resolve(fileName).toAbsolutePath(); - currentDir = file.getParent().toString(); - tfInputV1.setText(fileName); - tfInputV2.setText(fileName); - if (tfOutputV1.getText().isEmpty()) { - fileName = StreamUtils.replaceFileExtension(fileName, "MOS"); - tfOutputV1.setText(fileName); - tfOutputV2.setText(fileName); + String fileName = currentDir; + + File[] filePaths = getImageFileName(); + if (filePaths != null) { + for (File fileSelected : filePaths) { + Logger.getLogger(this.getClass().getName()).log(Level.INFO, "File Selected : " + fileSelected.getName()); + inputTableModel.addRow(new Object[] { fileSelected.getAbsolutePath()}); + + fileName = StreamUtils.replaceFileExtension(fileSelected.getName(), "MOS"); + outputTableModel.addRow(new Object[] { fileName, 0 }); + bConvert.setEnabled(isReady()); } - bConvert.setEnabled(isReady()); } + } else if (event.getSource() == bOutputV1 || event.getSource() == bOutputV2) { String fileName = tfOutputV1.getText().isEmpty() ? currentDir : tfOutputV1.getText(); Path file = FileManager.resolve(fileName).toAbsolutePath(); - if ((fileName = getMosFileName(file)) != null) { + if ((fileName = getMosOutputDirectory(file)) != null) { file = FileManager.resolve(fileName).toAbsolutePath(); currentDir = file.getParent().toString(); tfOutputV1.setText(fileName); tfOutputV2.setText(fileName); } + bConvert.setEnabled(isReady()); } else if (event.getSource() == bCompressionHelp) { final String helpMsg = @@ -611,47 +690,7 @@ public List doInBackground() @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; - } - List sl = null; - try { - sl = workerConvert.get(); - } catch (Exception e) { - e.printStackTrace(); - } - workerConvert = null; - - boolean isError = false; - String s = null; - if (sl != null && !sl.isEmpty()) { - if (sl.get(0) != null) { - s = sl.get(0); - } else if (sl.size() > 1 && sl.get(1) != null) { - s = sl.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(); - } else { - clear(); - } - } - } else { - JOptionPane.showMessageDialog(this, "Unknown error!", "Error", JOptionPane.ERROR_MESSAGE); - } - } - } + // required method for class } //--------------------- End Interface PropertyChangeListener --------------------- @@ -679,13 +718,7 @@ public void focusGained(FocusEvent event) @Override public void focusLost(FocusEvent event) { - if (event.getSource() == tfInputV1) { - tfInputV2.setText(tfInputV1.getText()); - bConvert.setEnabled(isReady()); - } else if (event.getSource() == tfInputV2) { - tfInputV1.setText(tfInputV2.getText()); - bConvert.setEnabled(isReady()); - } else if (event.getSource() == tfOutputV1) { + if (event.getSource() == tfOutputV1) { tfOutputV2.setText(tfOutputV1.getText()); bConvert.setEnabled(isReady()); } else if (event.getSource() == tfOutputV2) { @@ -702,36 +735,71 @@ private void init() GridBagConstraints c = new GridBagConstraints(); // setting up input/output section (Legacy V1) - JPanel pFilesV1 = new JPanel(new GridBagLayout()); - pFilesV1.setBorder(BorderFactory.createTitledBorder("Input & Output ")); - JLabel lInputV1 = new JLabel("Input file:"); - JLabel lOutputV1 = new JLabel("Output file:"); - tfInputV1 = new JTextField(); - tfInputV1.addFocusListener(this); + JPanel pFilesOutputV1 = new JPanel(new GridBagLayout()); + pFilesOutputV1.setBorder(BorderFactory.createTitledBorder("Output Files")); + + JPanel pFilesInputV1 = new JPanel(new GridBagLayout()); + pFilesInputV1.setBorder(BorderFactory.createTitledBorder("Input Files")); + + // Output directory choice will default to source directory maybe? tfOutputV1 = new JTextField(); + tfOutputV1.setText(""); tfOutputV1.addFocusListener(this); - bInputV1 = new JButton("..."); + + //input table settings + inputTableModel = new DefaultTableModel(0, 0); + inputTableModel.addColumn("Input"); + tfInputTableV1 = new JTable(inputTableModel) { + public boolean isCellEditable(int rowIndex, int colIndex) { + return false; + } + }; + tfInputTableV1.setModel(inputTableModel); + tfInputTableV1.setPreferredScrollableViewportSize(new Dimension(500,200)); + spInputScroll = new JScrollPane(tfInputTableV1); + + //output table settings + outputTableModel = new DefaultTableModel(0, 0); + outputTableModel.addColumn("Output"); + outputTableModel.addColumn("Progress"); + tfOutputTableV1 = new JTable(outputTableModel); + tfOutputTableV1.setModel(outputTableModel); + TableColumn tfOutputTableProgressCol = tfOutputTableV1.getColumnModel().getColumn(1); + tfOutputTableProgressCol.setCellRenderer(new ProgressCellRenderer()); + tfOutputTableV1.setPreferredScrollableViewportSize(new Dimension(500,200)); + spOutputScroll = new JScrollPane(tfOutputTableV1); + + bInputV1 = new JButton("Select Input"); bInputV1.addActionListener(this); - bOutputV1 = new JButton("..."); + + bOutputV1 = new JButton("Change Output Directory"); bOutputV1.addActionListener(this); - c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, - GridBagConstraints.HORIZONTAL, new Insets(0, 4, 0, 0), 0, 0); - pFilesV1.add(lInputV1, c); - c = ViewerUtil.setGBC(c, 1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, - GridBagConstraints.HORIZONTAL, new Insets(0, 8, 0, 0), 0, 0); - pFilesV1.add(tfInputV1, c); - c = ViewerUtil.setGBC(c, 2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, - GridBagConstraints.HORIZONTAL, new Insets(0, 4, 0, 4), 0, 0); - pFilesV1.add(bInputV1, c); - c = ViewerUtil.setGBC(c, 0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, - GridBagConstraints.HORIZONTAL, new Insets(0, 4, 4, 0), 0, 0); - pFilesV1.add(lOutputV1, c); - c = ViewerUtil.setGBC(c, 1, 1, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, - GridBagConstraints.HORIZONTAL, new Insets(0, 8, 4, 0), 0, 0); - pFilesV1.add(tfOutputV1, c); - c = ViewerUtil.setGBC(c, 2, 1, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, - GridBagConstraints.HORIZONTAL, new Insets(0, 4, 4, 4), 0, 0); - pFilesV1.add(bOutputV1, c); + + //input file frame start + c = ViewerUtil.setGBC(c, 4, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(0, 4, 0, 4), 0, 0); + pFilesInputV1.add(bInputV1, c); + + c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(0, 8, 0, 0), 0, 0); +// spInputScroll.add(tfInputTableV1, c); + pFilesInputV1.add(spInputScroll, c); + //input file frame end + + //out file frame start + c = ViewerUtil.setGBC(c, 4, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(0, 4, 0, 4), 0, 0); + pFilesOutputV1.add(bOutputV1, c); + + c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(0, 8, 4, 0), 0, 0); + pFilesOutputV1.add(tfOutputV1, c); + + c = ViewerUtil.setGBC(c, 0, 2, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, + GridBagConstraints.HORIZONTAL, new Insets(0, 8, 0, 0), 0, 0); +// spOutputScroll.add(tfOutputTableV1, c); + pFilesOutputV1.add(spOutputScroll, c); + //out file frame end // setting up input/output section (PVRZ-based V2) JPanel pFilesV2 = new JPanel(new GridBagLayout()); @@ -747,22 +815,22 @@ private void init() bOutputV2 = new JButton("..."); bOutputV2.addActionListener(this); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, - GridBagConstraints.NONE, new Insets(0, 4, 0, 0), 0, 0); + GridBagConstraints.NONE, new Insets(0, 4, 0, 0), 0, 0); pFilesV2.add(lInputV2, c); c = ViewerUtil.setGBC(c, 1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, - GridBagConstraints.HORIZONTAL, new Insets(0, 8, 0, 0), 0, 0); + GridBagConstraints.HORIZONTAL, new Insets(0, 8, 0, 0), 0, 0); pFilesV2.add(tfInputV2, c); c = ViewerUtil.setGBC(c, 2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, - GridBagConstraints.NONE, new Insets(0, 4, 0, 4), 0, 0); + GridBagConstraints.NONE, new Insets(0, 4, 0, 4), 0, 0); pFilesV2.add(bInputV2, c); c = ViewerUtil.setGBC(c, 0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, - GridBagConstraints.NONE, new Insets(0, 4, 4, 0), 0, 0); + GridBagConstraints.NONE, new Insets(0, 4, 4, 0), 0, 0); pFilesV2.add(lOutputV2, c); c = ViewerUtil.setGBC(c, 1, 1, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, - GridBagConstraints.HORIZONTAL, new Insets(0, 8, 4, 0), 0, 0); + GridBagConstraints.HORIZONTAL, new Insets(0, 8, 4, 0), 0, 0); pFilesV2.add(tfOutputV2, c); c = ViewerUtil.setGBC(c, 2, 1, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, - GridBagConstraints.NONE, new Insets(0, 4, 4, 4), 0, 0); + GridBagConstraints.NONE, new Insets(0, 4, 4, 4), 0, 0); pFilesV2.add(bOutputV2, c); // setting up options section (legacy V1) @@ -770,13 +838,13 @@ private void init() pOptionsV1.setBorder(BorderFactory.createTitledBorder("Options ")); cbCompress = new JCheckBox("Compressed (MOSC)", false); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, - GridBagConstraints.NONE, new Insets(4, 0, 0, 0), 0, 0); + GridBagConstraints.NONE, new Insets(4, 0, 0, 0), 0, 0); pOptionsV1.add(cbCompress, c); c = ViewerUtil.setGBC(c, 1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, - GridBagConstraints.HORIZONTAL, new Insets(4, 0, 0, 0), 0, 0); + GridBagConstraints.HORIZONTAL, new Insets(4, 0, 0, 0), 0, 0); pOptionsV1.add(new JPanel(), c); c = ViewerUtil.setGBC(c, 0, 1, 2, 1, 1.0, 1.0, GridBagConstraints.FIRST_LINE_START, - GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0); + GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0); pOptionsV1.add(new JPanel(), c); // setting up options section (PVRZ-based V2) @@ -793,48 +861,61 @@ private void init() bCompressionHelp.setToolTipText("About compression types"); bCompressionHelp.addActionListener(this); bCompressionHelp.setMargin(new Insets(bCompressionHelp.getInsets().top, 4, - bCompressionHelp.getInsets().bottom, 4)); + bCompressionHelp.getInsets().bottom, 4)); lPvrzInfo = new JLabel(pvrzInfoString(sPvrzIndex.getValue())); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, - GridBagConstraints.NONE, new Insets(0, 4, 0, 0), 0, 0); + GridBagConstraints.NONE, new Insets(0, 4, 0, 0), 0, 0); pOptionsV2.add(lPvrzIndex, c); c = ViewerUtil.setGBC(c, 1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, - GridBagConstraints.NONE, new Insets(0, 8, 0, 0), 0, 0); + GridBagConstraints.NONE, new Insets(0, 8, 0, 0), 0, 0); pOptionsV2.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); + GridBagConstraints.NONE, new Insets(0, 16, 0, 0), 0, 0); pOptionsV2.add(lCompression, c); c = ViewerUtil.setGBC(c, 3, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, - GridBagConstraints.NONE, new Insets(0, 8, 0, 0), 0, 0); + GridBagConstraints.NONE, new Insets(0, 8, 0, 0), 0, 0); pOptionsV2.add(cbCompression, c); c = ViewerUtil.setGBC(c, 4, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, - GridBagConstraints.NONE, new Insets(0, 4, 0, 4), 0, 0); + GridBagConstraints.NONE, new Insets(0, 4, 0, 4), 0, 0); pOptionsV2.add(bCompressionHelp, c); c = ViewerUtil.setGBC(c, 0, 1, 5, 1, 1.0, 0.0, GridBagConstraints.LINE_START, - GridBagConstraints.NONE, new Insets(4, 4, 4, 4), 0, 0); + GridBagConstraints.NONE, new Insets(4, 4, 4, 4), 0, 0); pOptionsV2.add(lPvrzInfo, c); // setting up tabbed pane tabPane = new JTabbedPane(JTabbedPane.TOP); - JPanel pTabV1 = new JPanel(new GridBagLayout()); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, - GridBagConstraints.HORIZONTAL, new Insets(4, 4, 2, 4), 0, 0); - pTabV1.add(pFilesV1, c); + GridBagConstraints.HORIZONTAL, new Insets(4, 4, 2, 4), 0, 0); + +// pTabV1.add(spInputScroll, c); + pTabV1.add(pFilesInputV1, c); c = ViewerUtil.setGBC(c, 0, 1, 1, 1, 1.0, 1.0, GridBagConstraints.LINE_START, - GridBagConstraints.BOTH, new Insets(2, 4, 4, 4), 0, 0); + GridBagConstraints.BOTH, new Insets(2, 4, 4, 4), 0, 0); + +// pTabV1.add(spOutputScroll, c); + pTabV1.add(pFilesOutputV1, c); + c = ViewerUtil.setGBC(c, 0, 2, 1, 1, 1.0, 1.0, GridBagConstraints.LINE_START, + GridBagConstraints.BOTH, new Insets(2, 4, 4, 4), 0, 0); + pTabV1.add(pOptionsV1, c); tabPane.addTab("Legacy (V1)", pTabV1); tabPane.setMnemonicAt(0, KeyEvent.VK_1); JPanel pTabV2 = new JPanel(new GridBagLayout()); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, - GridBagConstraints.HORIZONTAL, new Insets(4, 4, 2, 4), 0, 0); + GridBagConstraints.HORIZONTAL, new Insets(0, 4, 2, 4), 0, 0); pTabV2.add(pFilesV2, c); - c = ViewerUtil.setGBC(c, 0, 1, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, - GridBagConstraints.HORIZONTAL, new Insets(2, 4, 4, 4), 0, 0); + c = ViewerUtil.setGBC(c, 0, 1, 1, 1, 1.0, 0.0, GridBagConstraints.NORTHWEST, + GridBagConstraints.HORIZONTAL, new Insets(2, 4, 4, 4), 0, 0); pTabV2.add(pOptionsV2, c); + + JPanel spacePanel = new JPanel(new GridBagLayout()); + c = ViewerUtil.setGBC(c, 0, 1, 1, 1, 1.0, 1.0, GridBagConstraints.NORTHWEST, + GridBagConstraints.HORIZONTAL, new Insets(2, 4, 4, 4), 0, 0); + + pTabV2.add(spacePanel, c); tabPane.addTab("PVRZ-based (V2)", pTabV2); tabPane.setMnemonicAt(1, KeyEvent.VK_2); tabPane.setSelectedIndex(0); @@ -853,33 +934,34 @@ private void init() 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); + GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0); pButtons.add(cbCloseOnExit, 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); + GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0); pButtons.add(new JPanel(), c); c = ViewerUtil.setGBC(c, 2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_END, - GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0); + GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0); pButtons.add(bConvert, c); c = ViewerUtil.setGBC(c, 3, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_END, - GridBagConstraints.NONE, new Insets(0, 4, 0, 0), 0, 0); + GridBagConstraints.NONE, new Insets(0, 4, 0, 0), 0, 0); pButtons.add(bCancel, c); // putting all together setLayout(new GridBagLayout()); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, - GridBagConstraints.HORIZONTAL, new Insets(8, 8, 0, 8), 0, 0); + GridBagConstraints.HORIZONTAL, new Insets(8, 8, 0, 8), 0, 0); add(tabPane, c); c = ViewerUtil.setGBC(c, 0, 1, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, - GridBagConstraints.HORIZONTAL, new Insets(8, 8, 0, 8), 0, 0); + GridBagConstraints.HORIZONTAL, new Insets(8, 8, 0, 8), 0, 0); add(pButtons, c); c = ViewerUtil.setGBC(c, 0, 2, 1, 1, 1.0, 1.0, GridBagConstraints.FIRST_LINE_START, - GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0); + GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0); add(new JPanel(), c); // finalizing dialog initialization pack(); - setMinimumSize(getPreferredSize()); + setMinimumSize(new Dimension(1000, 800)); //getPreferredSize() setLocationRelativeTo(getParent()); setVisible(true); } @@ -889,12 +971,31 @@ private void hideWindow() { clear(); setVisible(false); + this.dispose(); + this.dispatchEvent(new WindowEvent(this, WindowEvent.WINDOW_CLOSING)); } // resetting dialog state private void clear() { - tfInputV1.setText(""); + tfInputTableV1.clearSelection(); + tfOutputTableV1.clearSelection(); + + while(tfInputTableV1.getRowCount() > 0) + { + ((DefaultTableModel) tfInputTableV1.getModel()).removeRow(0); + } + + while(tfOutputTableV1.getRowCount() > 0) + { + ((DefaultTableModel) tfOutputTableV1.getModel()).removeRow(0); + } + + inputTableModel.setRowCount(0); + inputTableModel.setNumRows(0); + outputTableModel.setRowCount(0); + outputTableModel.setNumRows(0); + tfInputV2.setText(""); tfOutputV1.setText(""); tfOutputV2.setText(""); @@ -904,12 +1005,15 @@ private void clear() // got enough data to start conversion? private boolean isReady() { - boolean ret = false; - if (!tfInputV1.getText().isEmpty() && !tfOutputV1.getText().isEmpty()) { - Path file = FileManager.resolve(tfInputV1.getText()); - ret = FileEx.create(file).isFile(); + if (tfInputTableV1.getRowCount() > 0 && !tfOutputV1.getText().isEmpty()) { + for (String outPutTableFiles : getTableInputPaths()) { + Path file = FileManager.resolve(outPutTableFiles); + if (!Files.isRegularFile(file)) { + return false; + } + } } - return ret; + return true; } private int getPvrzIndex(Object o) @@ -934,12 +1038,15 @@ private String pvrzInfoString(Object o) return String.format("Resulting in MOS%04d.PVRZ, MOS%04d.PVRZ, ...", index, index+1); } - private String getImageFileName(Path path) + private File[] getImageFileName() { - JFileChooser fc = new JFileChooser(path.toFile()); - fc.setDialogTitle("Select input graphics file"); + Path file = FileManager.resolve(currentDir); + JFileChooser fc = new JFileChooser(file.toFile()); + + fc.setDialogTitle("Select input graphics files"); fc.setDialogType(JFileChooser.OPEN_DIALOG); fc.setFileSelectionMode(JFileChooser.FILES_ONLY); + fc.setMultiSelectionEnabled(true); FileNameExtensionFilter[] filters = getInputFilters(); for (final FileNameExtensionFilter filter: filters) { fc.addChoosableFileFilter(filter); @@ -947,21 +1054,24 @@ private String getImageFileName(Path path) fc.setFileFilter(filters[0]); int ret = fc.showOpenDialog(this); if (ret == JFileChooser.APPROVE_OPTION) { - return fc.getSelectedFile().toString(); + currentDir = fc.getSelectedFiles()[0].toPath().getParent().toString(); + File[] filePaths = fc.getSelectedFiles(); + for(File fileSelected : filePaths) { + Logger.getLogger(this.getClass().getName()).log(Level.INFO, "File Selected : " + fileSelected.getName()); + } + return filePaths; } else { return null; } } - private String getMosFileName(Path path) + private String getMosOutputDirectory(Path path) { JFileChooser fc = new JFileChooser(path.toFile()); - fc.setDialogTitle("Specify output filename"); + fc.setDialogTitle("Specify output directory"); fc.setDialogType(JFileChooser.SAVE_DIALOG); - fc.setFileSelectionMode(JFileChooser.FILES_ONLY); - FileNameExtensionFilter filter = new FileNameExtensionFilter("MOS files (*.mos)", "mos"); - fc.addChoosableFileFilter(filter); - fc.setFileFilter(filter); + fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + int ret = fc.showSaveDialog(this); if (ret == JFileChooser.APPROVE_OPTION) { return fc.getSelectedFile().toString(); @@ -970,15 +1080,42 @@ private String getMosFileName(Path path) } } - private List convert() + private ArrayList getTableInputPaths() { + ArrayList list = new ArrayList<>(); + if(tfInputTableV1.getModel().getRowCount() > 0) { + for (int i = 0; i < tfInputTableV1.getModel().getRowCount(); i++) { + list.add(tfInputTableV1.getModel().getValueAt(i, 0).toString()); + } + + return list; + } else { + return null; + } + } + + private ArrayList getTableOutputNames() { + final int count = tfOutputTableV1.getModel().getRowCount(); + if (count > 0) { + final ArrayList list = new ArrayList<>(count); + for (int i = 0; i < tfOutputTableV1.getModel().getRowCount(); i++) { + list.add(tfOutputTableV1.getModel().getValueAt(i, 0).toString()); + } + + return list; + } else { + return null; + } + } + + private List convert(Task workerTask) { - List result = new Vector<>(2); + List result = new Vector(2); - // validating input file - Path inFile = FileManager.resolve(tfInputV1.getText()); - if (!FileEx.create(inFile).isFile()) { + // validating input files + Path inFile = FileManager.resolve(workerTask.getInputPath()); + if (!Files.isRegularFile(inFile)) { result.add(null); - result.add(String.format("Input file \"%s\" does not exist.", tfInputV1.getText())); + result.add(String.format("Input file \"%s\" does not exist.", workerTask.getInputPath())); return result; } @@ -1017,9 +1154,15 @@ private List convert() // converting if (tabPane.getSelectedIndex() == 0) { - convertV1(this, srcImage, tfOutputV1.getText(), isMOSC, result, true); + //default to current directory + String mosFilePath = currentDir + File.separator + getTableOutputNames().get(workerTask.getFileCount()); + if(!tfOutputV1.getText().isEmpty()) { + mosFilePath = tfOutputV1.getText() + File.separator + getTableOutputNames().get(workerTask.getFileCount()); + } + + workerTask.convertV1(srcImage, mosFilePath, isMOSC, result); } else if (tabPane.getSelectedIndex() == 1) { - convertV2(this, srcImage, tfOutputV2.getText(), dxtType, pvrzIndex, result, true); + workerTask.convertV2(srcImage, tfOutputV2.getText(), dxtType, pvrzIndex, result); } else { result.add(null); result.add("No MOS type specified!"); @@ -1027,24 +1170,22 @@ private List convert() return result; } - - //-------------------------- INNER CLASSES -------------------------- - private static class MosEntry - { - public int page; - public int width, height; - public Point srcLocation; - public Point dstLocation; - - public MosEntry(int page, Point srcLocation, int width, int height, Point dstLocation) - { - this.page = page; - this.srcLocation = srcLocation; - this.width = width; - this.height = height; - this.dstLocation = dstLocation; - } - } -} + private static class MosEntry + { + public int page; + public int width, height; + public Point srcLocation; + public Point dstLocation; + + public MosEntry(int page, Point srcLocation, int width, int height, Point dstLocation) + { + this.page = page; + this.srcLocation = srcLocation; + this.width = width; + this.height = height; + this.dstLocation = dstLocation; + } + } +} \ No newline at end of file diff --git a/src/org/infinity/gui/converter/MassConvert.java b/src/org/infinity/gui/converter/MassConvert.java new file mode 100644 index 000000000..46455baa0 --- /dev/null +++ b/src/org/infinity/gui/converter/MassConvert.java @@ -0,0 +1,8 @@ +package org.infinity.gui.converter; + +import javax.swing.*; + +//place holder +public final class MassConvert extends JPanel { +//might be nice to have an integrated interface to convert anything? +}