// 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 java.awt.BorderLayout;
import java.awt.Component;
import java.awt.FlowLayout;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.datatransfer.DataFlavor;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.dnd.DropTargetEvent;
import java.awt.dnd.DropTargetListener;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
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.List;
import java.util.Locale;
import java.util.SortedMap;
import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.JTextField;
import javax.swing.UIManager;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import org.infinity.NearInfinity;
import org.infinity.icon.Icons;
import org.infinity.resource.Profile;
import org.infinity.resource.ResourceFactory;
import org.infinity.resource.bcs.Compiler;
import org.infinity.resource.bcs.Decompiler;
import org.infinity.resource.key.FileResourceEntry;
import org.infinity.util.Misc;
import org.infinity.util.io.FileManager;
final class BcsDropFrame extends ChildFrame implements ActionListener, ListSelectionListener
{
private final JButton bOpen = new JButton("Open selected", Icons.getIcon(Icons.ICON_OPEN_16));
private final JButton bSelectDir = new JButton(Icons.getIcon(Icons.ICON_OPEN_16));
private final JCheckBox cbIgnoreWarnings = new JCheckBox("Ignore compiler warnings", true);
private final JFileChooser fc = new JFileChooser(Profile.getGameRoot().toFile());
private final JLabel compZone = new JLabel("Compiler drop zone (BAF)", JLabel.CENTER);
private final JLabel decompZone = new JLabel("Decompiler drop zone (BCS/BS)", JLabel.CENTER);
private final JLabel statusMsg = new JLabel(" Drag and drop files or folders into the zones");
private final JRadioButton rbSaveBS = new JRadioButton("BS", false);
private final JRadioButton rbSaveBCS = new JRadioButton("BCS", true);
private final JRadioButton rbOrigDir = new JRadioButton("Same directory as input", true);
private final JRadioButton rbOtherDir = new JRadioButton("Other ", false);
private final JTabbedPane tabbedPane = new JTabbedPane();
private final JTextField tfOtherDir = new JTextField(10);
private final SortableTable table;
private final WindowBlocker blocker;
BcsDropFrame()
{
super("Script Drop Zone");
setIconImage(Icons.getIcon(Icons.ICON_HISTORY_16).getImage());
blocker = new WindowBlocker(this);
compZone.setBorder(BorderFactory.createLineBorder(UIManager.getColor("controlDkShadow")));
decompZone.setBorder(BorderFactory.createLineBorder(UIManager.getColor("controlDkShadow")));
List<Class<? extends Object>> colClasses = new ArrayList<Class<? extends Object>>(3);
colClasses.add(Object.class); colClasses.add(Object.class); colClasses.add(Integer.class);
table = new SortableTable(Arrays.asList(new String[]{"File", "Errors/Warnings", "Line"}),
colClasses, Arrays.asList(new Integer[]{200, 400, 100}));
table.getSelectionModel().addListSelectionListener(this);
table.addMouseListener(new MouseAdapter()
{
@Override
public void mouseReleased(MouseEvent e)
{
if (e.getClickCount() == 2) {
FileResourceEntry resourceEntry = (FileResourceEntry)table.getValueAt(table.getSelectedRow(), 0);
if (resourceEntry != null)
new ViewFrame(table.getTopLevelAncestor(),
ResourceFactory.getResource(resourceEntry));
}
}
});
JPanel centerPanel = new JPanel(new GridLayout(2, 1, 0, 6));
centerPanel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
centerPanel.add(compZone);
centerPanel.add(decompZone);
JScrollPane scrollTable = new JScrollPane(table);
scrollTable.getViewport().setBackground(table.getBackground());
bOpen.addActionListener(this);
bOpen.setEnabled(false);
JPanel bPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
bPanel.add(bOpen);
JPanel errorPanel = new JPanel(new BorderLayout());
errorPanel.add(scrollTable, BorderLayout.CENTER);
errorPanel.add(bPanel, BorderLayout.SOUTH);
ButtonGroup bg = new ButtonGroup();
bg.add(rbSaveBCS);
bg.add(rbSaveBS);
bg = new ButtonGroup();
bg.add(rbOrigDir);
bg.add(rbOtherDir);
rbOrigDir.addActionListener(this);
rbOtherDir.addActionListener(this);
tfOtherDir.setEditable(false);
bSelectDir.setEnabled(false);
bSelectDir.addActionListener(this);
fc.setDialogTitle("Select output directory");
fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
JPanel otherDir = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0));
bSelectDir.setMargin(new Insets(0, 0, 0, 0));
otherDir.add(rbOtherDir);
otherDir.add(tfOtherDir);
otherDir.add(bSelectDir);
JLabel label1 = new JLabel("Save compiled scripts as:");
JLabel label2 = new JLabel("Output directory:");
cbIgnoreWarnings.setToolTipText("Write script files even if they have compile errors");
GridBagLayout gbl = new GridBagLayout();
GridBagConstraints gbc = new GridBagConstraints();
JPanel optionsPanel = new JPanel(gbl);
gbc.weightx = 0.0;
gbc.weighty = 0.0;
gbc.anchor = GridBagConstraints.WEST;
gbc.insets = new Insets(3, 3, 3, 3);
gbl.setConstraints(label1, gbc);
optionsPanel.add(label1);
gbl.setConstraints(rbSaveBCS, gbc);
optionsPanel.add(rbSaveBCS);
gbc.gridwidth = GridBagConstraints.REMAINDER;
gbl.setConstraints(rbSaveBS, gbc);
optionsPanel.add(rbSaveBS);
gbc.gridwidth = 1;
JPanel dummy2 = new JPanel();
gbl.setConstraints(dummy2, gbc);
optionsPanel.add(dummy2);
gbc.gridwidth = GridBagConstraints.REMAINDER;
gbl.setConstraints(cbIgnoreWarnings, gbc);
optionsPanel.add(cbIgnoreWarnings);
gbc.insets.top = 9;
gbc.gridwidth = 1;
gbl.setConstraints(label2, gbc);
optionsPanel.add(label2);
gbc.gridwidth = GridBagConstraints.REMAINDER;
gbl.setConstraints(rbOrigDir, gbc);
optionsPanel.add(rbOrigDir);
gbc.insets.top = 3;
gbc.gridwidth = 1;
JPanel dummy = new JPanel();
gbl.setConstraints(dummy, gbc);
optionsPanel.add(dummy);
gbc.gridwidth = GridBagConstraints.REMAINDER;
gbl.setConstraints(otherDir, gbc);
optionsPanel.add(otherDir);
tabbedPane.add("Drop zones", centerPanel);
tabbedPane.add("Compiler errors", errorPanel);
tabbedPane.add("Options", optionsPanel);
statusMsg.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createEmptyBorder(1, 1, 1, 1),
BorderFactory.createLineBorder(
UIManager.getColor("controlShadow"))));
JPanel pane = (JPanel)getContentPane();
pane.setLayout(new BorderLayout());
pane.add(tabbedPane, BorderLayout.CENTER);
pane.add(statusMsg, BorderLayout.SOUTH);
pane.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
new DropTarget(compZone, new MyDropTargetListener(compZone));
new DropTarget(decompZone, new MyDropTargetListener(decompZone));
setSize(500, 400);
Center.center(this, NearInfinity.getInstance().getBounds());
}
// --------------------- Begin Interface ActionListener ---------------------
@Override
public void actionPerformed(ActionEvent event)
{
if (event.getSource() == bOpen) {
FileResourceEntry resourceEntry = (FileResourceEntry)table.getValueAt(table.getSelectedRow(), 0);
if (resourceEntry != null)
new ViewFrame(this, ResourceFactory.getResource(resourceEntry));
}
else if (event.getSource() == rbOrigDir) {
bSelectDir.setEnabled(false);
tfOtherDir.setEnabled(false);
}
else if (event.getSource() == rbOtherDir) {
bSelectDir.setEnabled(true);
tfOtherDir.setEnabled(true);
}
else if (event.getSource() == bSelectDir) {
if (fc.showDialog(this, "Select") == JFileChooser.APPROVE_OPTION)
tfOtherDir.setText(fc.getSelectedFile().toString());
}
}
// --------------------- End Interface ActionListener ---------------------
// --------------------- Begin Interface ListSelectionListener ---------------------
@Override
public void valueChanged(ListSelectionEvent event)
{
bOpen.setEnabled(table.getSelectedRowCount() > 0);
}
// --------------------- End Interface ListSelectionListener ---------------------
private SortedMap<Integer, String> compileFile(Path file)
{
StringBuffer source = new StringBuffer();
try (BufferedReader br = Files.newBufferedReader(file)) {
String line = br.readLine();
while (line != null) {
source.append(line).append('\n');
line = br.readLine();
}
} catch (IOException e) {
e.printStackTrace();
return null;
}
Compiler compiler = new Compiler(source.toString());
String compiled = compiler.getCode();
SortedMap<Integer, String> errors = compiler.getErrors();
SortedMap<Integer, String> warnings = compiler.getWarnings();
if (!cbIgnoreWarnings.isSelected()) {
errors.putAll(warnings);
}
if (errors.size() == 0) {
String filename = file.getFileName().toString();
filename = filename.substring(0, filename.lastIndexOf((int)'.'));
if (rbSaveBCS.isSelected()) {
filename += ".BCS";
} else {
filename += ".BS";
}
Path output;
if (rbOrigDir.isSelected()) {
output = file.getParent().resolve(filename);
} else {
output = FileManager.resolve(tfOtherDir.getText(), filename);
}
try (BufferedWriter bw = Files.newBufferedWriter(output)) {
bw.write(compiled);
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
return errors;
}
private boolean decompileFile(Path file)
{
StringBuffer code = new StringBuffer();
try (BufferedReader br = Files.newBufferedReader(file)) {
String line = br.readLine();
while (line != null) {
code.append(line).append('\n');
line = br.readLine();
}
} catch (IOException e) {
e.printStackTrace();
return false;
}
String filename = file.getFileName().toString();
filename = filename.substring(0, filename.lastIndexOf((int)'.')) + ".BAF";
Path output;
if (rbOrigDir.isSelected()) {
output = file.getParent().resolve(filename);
} else {
output = FileManager.resolve(tfOtherDir.getText(), filename);
}
Decompiler decompiler = new Decompiler(code.toString(), Decompiler.ScriptType.BCS, true);
try (BufferedWriter bw = Files.newBufferedWriter(output)) {
bw.write(decompiler.getSource().replaceAll("\r?\n", Misc.LINE_SEPARATOR));
bw.newLine();
} catch (IOException e) {
e.printStackTrace();
return false;
}
return true;
}
private void filesDropped(Component component, List<File> files)
{
blocker.setBlocked(true);
table.clear();
long startTime = System.currentTimeMillis();
int ok = 0, failed = 0;
if (component == compZone) {
for (int i = 0; i < files.size(); i++) {
Path file = files.get(i).toPath();
if (Files.isDirectory(file)) {
try (DirectoryStream<Path> dstream = Files.newDirectoryStream(file)) {
for (final Path p: dstream) {
files.add(p.toFile());
}
} catch (IOException e) {
e.printStackTrace();
}
}
else if (file.getFileName().toString().toUpperCase(Locale.ENGLISH).endsWith(".BAF")) {
SortedMap<Integer, String> errors = compileFile(file);
if (errors == null) {
failed++;
} else {
if (errors.size() == 0) {
ok++;
} else {
for (final Integer lineNr : errors.keySet()) {
table.addTableItem(new CompileError(file, lineNr.intValue(), errors.get(lineNr)));
}
failed++;
}
}
}
}
if (failed > 0) {
tabbedPane.setSelectedIndex(1);
}
}
else if (component == decompZone) {
for (int i = 0; i < files.size(); i++) {
Path file = files.get(i).toPath();
if (Files.isDirectory(file)) {
try (DirectoryStream<Path> dstream = Files.newDirectoryStream(file)) {
for (final Path p: dstream) {
files.add(p.toFile());
}
} catch (IOException e) {
e.printStackTrace();
}
}
else if (file.getFileName().toString().toUpperCase(Locale.ENGLISH).endsWith(".BCS") ||
file.getFileName().toString().toUpperCase(Locale.ENGLISH).endsWith(".BS")) {
if (decompileFile(file)) {
ok++;
} else {
failed++;
}
}
}
}
long time = System.currentTimeMillis() - startTime;
table.tableComplete();
statusMsg.setText(" " + ok + " files (de)compiled ok, " + failed + " failed in " + time + " ms.");
blocker.setBlocked(false);
}
// -------------------------- INNER CLASSES --------------------------
private final class MyDropTargetListener implements DropTargetListener, Runnable
{
private final Component component;
private List<File> files;
private MyDropTargetListener(Component component)
{
this.component = component;
}
@Override
public void dragEnter(DropTargetDragEvent event)
{
}
@Override
public void dragOver(DropTargetDragEvent event)
{
}
@Override
public void dropActionChanged(DropTargetDragEvent event)
{
}
@Override
public void dragExit(DropTargetEvent event)
{
}
@SuppressWarnings("unchecked")
@Override
public void drop(DropTargetDropEvent event)
{
if (event.isLocalTransfer() || !event.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
event.rejectDrop();
return;
}
try {
event.acceptDrop(DnDConstants.ACTION_COPY);
files = (List<File>)event.getTransferable().getTransferData(DataFlavor.javaFileListFlavor);
} catch (Exception e) {
e.printStackTrace();
event.dropComplete(false);
return;
}
event.dropComplete(true);
new Thread(this).start();
}
@Override
public void run()
{
filesDropped(component, new ArrayList<File>(files));
}
}
private static final class CompileError implements TableItem
{
private final FileResourceEntry resourceEntry;
private final Integer linenr;
private final String error;
private CompileError(Path file, int linenr, String error)
{
resourceEntry = new FileResourceEntry(file);
this.linenr = new Integer(linenr);
this.error = error;
}
@Override
public Object getObjectAt(int columnIndex)
{
if (columnIndex == 0)
return resourceEntry;
else if (columnIndex == 1)
return error;
else
return linenr;
}
}
}