// Near Infinity - An Infinity Engine Browser and Editor
// Copyright (C) 2001 - 2005 Jon Olav Hauglid
// See LICENSE.txt for license information
package org.infinity.resource.sav;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JComponent;
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.ListSelectionModel;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.filechooser.FileNameExtensionFilter;
import org.infinity.NearInfinity;
import org.infinity.gui.ButtonPanel;
import org.infinity.gui.ButtonPopupMenu;
import org.infinity.gui.ResourceChooser;
import org.infinity.gui.ViewFrame;
import org.infinity.gui.WindowBlocker;
import org.infinity.icon.Icons;
import org.infinity.resource.Closeable;
import org.infinity.resource.Profile;
import org.infinity.resource.Resource;
import org.infinity.resource.ResourceFactory;
import org.infinity.resource.ViewableContainer;
import org.infinity.resource.Writeable;
import org.infinity.resource.key.FileResourceEntry;
import org.infinity.resource.key.ResourceEntry;
import org.infinity.util.SimpleListModel;
public final class SavResource implements Resource, Closeable, Writeable,
ActionListener, ListSelectionListener
{
private static final JLabel lhelp = new JLabel("<html><b>Instructions:</b><ol>" +
"<li>Decompress the SAV file." +
"<li>View/edit the individual files." +
"<li>If any changes have been made, " +
"Compress to rebuild SAV file.</ol></html>");
private static final ButtonPanel.Control CtrlCompress = ButtonPanel.Control.CUSTOM_1;
private static final ButtonPanel.Control CtrlDecompress = ButtonPanel.Control.CUSTOM_2;
private static final ButtonPanel.Control CtrlEdit = ButtonPanel.Control.CUSTOM_3;
private static final ButtonPanel.Control CtrlDelete = ButtonPanel.Control.CUSTOM_4;
private static final ButtonPanel.Control CtrlAdd = ButtonPanel.Control.ADD;
private final IOHandler handler;
private final ResourceEntry entry;
private final ButtonPanel buttonPanel = new ButtonPanel();
private SimpleListModel<ResourceEntry> listModel;
private JList<ResourceEntry> filelist;
private JPanel panel;
private List<ResourceEntry> entries;
private JMenuItem miAddExternal;
private JMenuItem miAddInternal;
public SavResource(ResourceEntry entry) throws Exception
{
this.entry = entry;
handler = new IOHandler(entry);
}
// --------------------- Begin Interface ActionListener ---------------------
@Override
public void actionPerformed(ActionEvent event)
{
if (buttonPanel.getControlByType(CtrlCompress) == event.getSource()) {
compressData(true);
} else if (buttonPanel.getControlByType(CtrlDecompress) == event.getSource()) {
decompressData(true);
} else if (buttonPanel.getControlByType(CtrlEdit) == event.getSource()) {
ResourceEntry fileentry = entries.get(filelist.getSelectedIndex());
Resource res = ResourceFactory.getResource(fileentry);
new ViewFrame(panel.getTopLevelAncestor(), res);
} else if (buttonPanel.getControlByType(ButtonPanel.Control.EXPORT_BUTTON) == event.getSource()) {
ResourceFactory.exportResource(entry, panel.getTopLevelAncestor());
} else if (buttonPanel.getControlByType(CtrlDelete) == event.getSource()) {
if (!filelist.isSelectionEmpty()) {
String fileName = filelist.getSelectedValue().toString();
int ret = JOptionPane.showConfirmDialog(panel.getTopLevelAncestor(),
"Delete file " + fileName + "?",
"Delete file", JOptionPane.YES_NO_OPTION,
JOptionPane.QUESTION_MESSAGE);
if (ret == JOptionPane.YES_OPTION) {
removeResource(filelist.getSelectedIndex());
}
}
} else if (miAddExternal == event.getSource()) {
JFileChooser fc = new JFileChooser(Profile.getGameRoot().toFile());
fc.setDialogTitle("Open external file");
fc.setDialogType(JFileChooser.OPEN_DIALOG);
fc.setFileSelectionMode(JFileChooser.FILES_ONLY);
fc.setMultiSelectionEnabled(false);
FileNameExtensionFilter filter =
new FileNameExtensionFilter("Supported file types", Profile.getAvailableResourceTypes());
fc.addChoosableFileFilter(filter);
fc.setFileFilter(filter);
if (fc.showOpenDialog(panel.getTopLevelAncestor()) == JFileChooser.APPROVE_OPTION) {
addResource(new FileResourceEntry(fc.getSelectedFile().toPath()));
}
} else if (miAddInternal == event.getSource()) {
ResourceChooser rc = new ResourceChooser();
if (rc.showDialog(panel.getTopLevelAncestor()) == ResourceChooser.APPROVE_OPTION) {
addResource(rc.getSelectedItem());
}
}
}
// --------------------- End Interface ActionListener ---------------------
// --------------------- Begin Interface ListSelectionListener ---------------------
@Override
public void valueChanged(ListSelectionEvent e)
{
if (filelist.isEnabled()) {
buttonPanel.getControlByType(CtrlDelete).setEnabled(!filelist.isSelectionEmpty());
buttonPanel.getControlByType(CtrlEdit).setEnabled(!filelist.isSelectionEmpty());
}
}
// --------------------- End Interface ListSelectionListener ---------------------
// --------------------- Begin Interface Closeable ---------------------
@Override
public void close()
{
if (buttonPanel.getControlByType(CtrlCompress).isEnabled()) {
final String msg = getResourceEntry().getResourceName() + " is still decompressed. Compress it?";
if (JOptionPane.showConfirmDialog(panel.getTopLevelAncestor(), msg, "Question", JOptionPane.YES_NO_OPTION,
JOptionPane.QUESTION_MESSAGE) == JOptionPane.YES_OPTION) {
compressData(true);
}
}
handler.close();
}
// --------------------- End Interface Closeable ---------------------
// --------------------- Begin Interface Resource ---------------------
@Override
public ResourceEntry getResourceEntry()
{
return entry;
}
// --------------------- End Interface Resource ---------------------
// --------------------- Begin Interface Viewable ---------------------
@Override
public JComponent makeViewer(ViewableContainer container)
{
listModel = new SimpleListModel<ResourceEntry>();
for (int i = 0; i < handler.getFileEntries().size(); i++) {
listModel.addElement(handler.getFileEntries().get(i));
}
filelist = new JList<>(listModel);
filelist.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
filelist.addListSelectionListener(this);
filelist.addMouseListener(new MouseAdapter()
{
@Override
public void mouseClicked(MouseEvent event)
{
if (event.getClickCount() == 2) {
ResourceEntry fileentry = entries.get(filelist.getSelectedIndex());
Resource res = ResourceFactory.getResource(fileentry);
new ViewFrame(panel.getTopLevelAncestor(), res);
}
}
});
JButton bDecompress = new JButton("Decompress", Icons.getIcon(Icons.ICON_EXPORT_16));
bDecompress.setMnemonic('d');
bDecompress.addActionListener(this);
JButton bEdit = new JButton("View/Edit", Icons.getIcon(Icons.ICON_ZOOM_16));
bEdit.setMnemonic('v');
bEdit.addActionListener(this);
bEdit.setEnabled(false);
JButton bDelete = new JButton("Delete file", Icons.getIcon(Icons.ICON_DELETE_16));
bDelete.addActionListener(this);
bDelete.setEnabled(false);
miAddExternal = new JMenuItem("External file");
miAddExternal.addActionListener(this);
miAddInternal = new JMenuItem("Game resource");
miAddInternal.addActionListener(this);
ButtonPopupMenu bpmAdd = new ButtonPopupMenu("Add...", new JMenuItem[]{miAddExternal, miAddInternal});
bpmAdd.setIcon(Icons.getIcon(Icons.ICON_ADD_16));
bpmAdd.setEnabled(false);
JButton bCompress = new JButton("Compress", Icons.getIcon(Icons.ICON_IMPORT_16));
bCompress.setMnemonic('c');
bCompress.addActionListener(this);
bCompress.setEnabled(false);
filelist.setEnabled(false);
JPanel centerpanel = new JPanel();
GridBagLayout gbl = new GridBagLayout();
GridBagConstraints gbc = new GridBagConstraints();
centerpanel.setLayout(gbl);
JLabel label = new JLabel("Contents of " + entry.toString());
JScrollPane scroll = new JScrollPane(filelist);
Dimension size = scroll.getPreferredSize();
scroll.setPreferredSize(new Dimension(2 * (int)size.getWidth(), 2 * (int)size.getHeight()));
gbc.weightx = 0.0;
gbc.weighty = 0.0;
gbc.insets = new Insets(6, 0, 0, 0);
gbc.fill = GridBagConstraints.NONE;
gbc.gridwidth = GridBagConstraints.REMAINDER;
gbl.setConstraints(label, gbc);
centerpanel.add(label);
gbc.insets.top = 3;
gbc.weightx = 1.0;
gbc.weighty = 1.0;
gbc.fill = GridBagConstraints.VERTICAL;
gbl.setConstraints(scroll, gbc);
centerpanel.add(scroll);
gbc.weighty = 0.0;
gbl.setConstraints(lhelp, gbc);
centerpanel.add(lhelp);
buttonPanel.addControl(bDecompress, CtrlDecompress);
buttonPanel.addControl(bEdit, CtrlEdit);
buttonPanel.addControl(bpmAdd, CtrlAdd);
buttonPanel.addControl(bDelete, CtrlDelete);
buttonPanel.addControl(bCompress, CtrlCompress);
((JButton)buttonPanel.addControl(ButtonPanel.Control.EXPORT_BUTTON)).addActionListener(this);
panel = new JPanel(new BorderLayout());
panel.add(centerpanel, BorderLayout.CENTER);
panel.add(buttonPanel, BorderLayout.SOUTH);
centerpanel.setBorder(BorderFactory.createLoweredBevelBorder());
return panel;
}
// --------------------- End Interface Viewable ---------------------
// --------------------- Begin Interface Writeable ---------------------
@Override
public void write(OutputStream os) throws IOException
{
handler.write(os);
}
// --------------------- End Interface Writeable ---------------------
public IOHandler getFileHandler()
{
return handler;
}
private boolean compressData(boolean showError)
{
try {
WindowBlocker block = new WindowBlocker(NearInfinity.getInstance());
try {
block.setBlocked(true);
handler.compress(entries);
ResourceFactory.saveResource(this, panel.getTopLevelAncestor());
buttonPanel.getControlByType(CtrlDecompress).setEnabled(true);
filelist.setEnabled(false);
buttonPanel.getControlByType(CtrlEdit).setEnabled(false);
buttonPanel.getControlByType(CtrlAdd).setEnabled(false);
buttonPanel.getControlByType(CtrlDelete).setEnabled(false);
buttonPanel.getControlByType(CtrlCompress).setEnabled(false);
} finally {
block.setBlocked(false);
block = null;
}
} catch (Exception e) {
if (showError) {
JOptionPane.showMessageDialog(panel, "Error compressing file", "Error", JOptionPane.ERROR_MESSAGE);
}
e.printStackTrace();
return false;
}
return true;
}
private boolean decompressData(boolean showError)
{
try {
WindowBlocker block = new WindowBlocker(NearInfinity.getInstance());
try {
block.setBlocked(true);
entries = handler.decompress();
buttonPanel.getControlByType(CtrlCompress).setEnabled(true);
filelist.setEnabled(true);
buttonPanel.getControlByType(CtrlEdit).setEnabled(true);
buttonPanel.getControlByType(CtrlAdd).setEnabled(true);
buttonPanel.getControlByType(CtrlDelete).setEnabled(true);
buttonPanel.getControlByType(CtrlDecompress).setEnabled(false);
filelist.setSelectedIndex(0);
} finally {
block.setBlocked(false);
block = null;
}
} catch (Exception e) {
if (showError) {
JOptionPane.showMessageDialog(panel, "Error decompressing file", "Error", JOptionPane.ERROR_MESSAGE);
}
e.printStackTrace();
return false;
}
return true;
}
private void addResource(String resourceName)
{
addResource(ResourceFactory.getResourceEntry(resourceName));
}
private void addResource(ResourceEntry resourceEntry)
{
if (resourceEntry != null) {
Path output = handler.getTempFolder().resolve(resourceEntry.getResourceName());
try {
if (Files.exists(output)) {
String msg = "File " + resourceEntry.getResourceName() + " already exists. Overwrite?";
int ret = JOptionPane.showConfirmDialog(panel.getTopLevelAncestor(),
msg, "Overwrite file?", JOptionPane.YES_NO_OPTION,
JOptionPane.QUESTION_MESSAGE);
if (ret != JOptionPane.YES_OPTION) {
JOptionPane.showMessageDialog(panel.getTopLevelAncestor(), "Operation cancelled.");
return;
}
}
ResourceFactory.exportResource(resourceEntry, output);
ResourceEntry newEntry = new FileResourceEntry(output);
int idx = 0;
for (int count = entries.size(); idx < count; idx++) {
if (newEntry.compareTo(entries.get(idx)) == 0) {
filelist.setSelectedIndex(idx);
idx = -1;
break;
} else if (newEntry.compareTo(entries.get(idx)) < 0) {
entries.add(idx, newEntry);
listModel.add(idx, newEntry);
filelist.setSelectedIndex(idx);
filelist.revalidate();
filelist.repaint();
break;
}
}
buttonPanel.getControlByType(CtrlDelete).setEnabled(filelist.getSelectedIndex() >= 0);
buttonPanel.getControlByType(CtrlEdit).setEnabled(filelist.getSelectedIndex() >= 0);
} catch (Exception e) {
e.printStackTrace();
JOptionPane.showMessageDialog(panel.getTopLevelAncestor(),
e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
}
}
}
private void removeResource(int entryIndex)
{
if (entryIndex >= 0 && entryIndex < entries.size()) {
ResourceEntry resourceEntry = entries.get(entryIndex);
Path file = resourceEntry.getActualPath();
if (Files.exists(file)) {
try {
Files.delete(file);
} catch (IOException e) {
e.printStackTrace();
}
}
entries.remove(resourceEntry);
listModel.remove(entryIndex);
if (entryIndex == listModel.size()) {
entryIndex--;
}
filelist.setSelectedIndex(entryIndex);
filelist.revalidate();
filelist.repaint();
if (listModel.size() == 0) {
buttonPanel.getControlByType(CtrlDelete).setEnabled(false);
buttonPanel.getControlByType(CtrlEdit).setEnabled(false);
}
} else {
JOptionPane.showMessageDialog(panel.getTopLevelAncestor(),
"Error removing selected resource from the list.",
"Error", JOptionPane.ERROR_MESSAGE);
}
}
}