// Near Infinity - An Infinity Engine Browser and Editor
// Copyright (C) 2001 - 2005 Jon Olav Hauglid
// See LICENSE.txt for license information
package org.infinity.datatype;
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.ByteBuffer;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import org.infinity.gui.StructViewer;
import org.infinity.gui.TextListPanel;
import org.infinity.gui.ViewFrame;
import org.infinity.icon.Icons;
import org.infinity.resource.AbstractStruct;
import org.infinity.resource.ResourceFactory;
import org.infinity.resource.StructEntry;
import org.infinity.resource.key.ResourceEntry;
/**
* Datatype for selecting resource entries, constructed from a predefined list of key/value pairs.
*/
public class ResourceBitmap extends Datatype
implements Editable, IsNumeric, IsReference, ActionListener, ListSelectionListener
{
/** Print resource reference together with search name in parentheses and value after hyphen. */
public static final String FMT_REF_NAME_VALUE = "%1$s (%2$s) - %3$s";
/** Print search name together with resource reference in parentheses and value after hyphen. */
public static final String FMT_NAME_REF_VALUE = "%2$s (%1$s) - %3$s";
/** Print resource reference together with its search name after hyphen. */
public static final String FMT_REF_HYPHEN_NAME = "%1$s - %2$s";
/** Print search name together with its resource reference after hyphen. */
public static final String FMT_NAME_HYPHEN_REF = "%2$s - %1$s";
/** Print resource reference together with value after hyphen. */
public static final String FMT_REF_HYPHEN_VALUE = "%1$s - %3$s";
/** Print resource reference together with its search name. */
public static final String FMT_REF_NAME = "%1$s (%2$s)";
/** Print search name together with its resource reference. */
public static final String FMT_NAME_REF = "%2$s (%1$s)";
/** Print resource reference together with value in parentheses. */
public static final String FMT_REF_VALUE = "%1$s (%3$s)";
/** Print resource reference only. */
public static final String FMT_REF_ONLY = "%1$s";
/** Print only the search name of the resource. */
public static final String FMT_NAME_ONLY = "%2$s";
/** Print resource value only. */
public static final String FMT_VALUE_ONLY = "%3$s";
private final List<RefEntry> resources;
private final String defaultLabel;
private final String formatString;
private JButton bView;
private TextListPanel list;
private long value;
public ResourceBitmap(StructEntry parent, ByteBuffer buffer, int offset, int length, String name,
List<RefEntry> resources)
{
this(parent, buffer, offset, length, name, resources, null, null);
}
public ResourceBitmap(StructEntry parent, ByteBuffer buffer, int offset, int length, String name,
List<RefEntry> resources, String defLabel)
{
this(parent, buffer, offset, length, name, resources, defLabel, null);
}
public ResourceBitmap(StructEntry parent, ByteBuffer buffer, int offset, int length, String name,
List<RefEntry> resources, String defLabel, String fmt)
{
super(parent, offset, length, name);
this.formatString = (fmt != null) ? fmt : FMT_REF_VALUE;
this.defaultLabel = (defLabel != null) ? defLabel : "Unknown";
this.resources = new ArrayList<RefEntry>((resources != null) ? resources.size() : 10);
if (resources != null) {
for (final RefEntry entry: resources) {
entry.setFormatString(this.formatString);
this.resources.add(entry);
}
}
Collections.sort(this.resources);
read(buffer, offset);
}
//--------------------- Begin Interface ActionListener ---------------------
@Override
public void actionPerformed(ActionEvent event)
{
if (event.getSource() == bView) {
Object selected = list.getSelectedValue();
if (selected instanceof RefEntry) {
RefEntry re = (RefEntry)selected;
ResourceEntry entry = re.getResourceEntry();
if (entry != null) {
new ViewFrame(list.getTopLevelAncestor(), ResourceFactory.getResource(entry));
} else {
new ViewFrame(list.getTopLevelAncestor(), null);
}
}
}
}
//--------------------- End Interface ActionListener ---------------------
//--------------------- Begin Interface ListSelectionListener ---------------------
@Override
public void valueChanged(ListSelectionEvent e)
{
Object selected = list.getSelectedValue();
if (selected instanceof RefEntry) {
RefEntry re = (RefEntry)selected;
bView.setEnabled(re.isResource());
}
}
//--------------------- End Interface ListSelectionListener ---------------------
//--------------------- Begin Interface Editable ---------------------
@Override
public JComponent edit(final ActionListener container)
{
list = new TextListPanel(resources, false);
list.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent event)
{
if (event.getClickCount() == 2)
container.actionPerformed(new ActionEvent(this, 0, StructViewer.UPDATE_VALUE));
}
});
RefEntry curEntry = getRefEntry(value);
if (curEntry == null) {
curEntry = getRefEntry(0L);
}
if (curEntry == null && resources.size() > 0) {
curEntry = resources.get(0);
}
if (curEntry != null) {
list.setSelectedValue(curEntry, true);
}
JButton bUpdate = new JButton("Update value", Icons.getIcon(Icons.ICON_REFRESH_16));
bUpdate.addActionListener(container);
bUpdate.setActionCommand(StructViewer.UPDATE_VALUE);
bView = new JButton("View/Edit", Icons.getIcon(Icons.ICON_ZOOM_16));
bView.addActionListener(this);
bView.setEnabled(list.getSelectedValue() instanceof RefEntry &&
((RefEntry)list.getSelectedValue()).isResource());
list.addListSelectionListener(this);
GridBagLayout gbl = new GridBagLayout();
GridBagConstraints gbc = new GridBagConstraints();
JPanel panel = new JPanel(gbl);
gbc.weightx = 1.0;
gbc.weighty = 1.0;
gbc.fill = GridBagConstraints.BOTH;
gbc.gridheight = 2;
gbl.setConstraints(list, gbc);
panel.add(list);
gbc.gridheight = 1;
gbc.weightx = 0.0;
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.insets = new Insets(3, 6, 3, 0);
gbc.anchor = GridBagConstraints.SOUTH;
gbc.gridwidth = GridBagConstraints.REMAINDER;
gbl.setConstraints(bUpdate, gbc);
panel.add(bUpdate);
gbc.gridx = 1;
gbc.gridy = 1;
gbc.anchor = GridBagConstraints.NORTH;
gbl.setConstraints(bView, gbc);
panel.add(bView);
panel.setMinimumSize(DIM_MEDIUM);
panel.setPreferredSize(DIM_MEDIUM);
return panel;
}
@Override
public void select()
{
list.ensureIndexIsVisible(list.getSelectedIndex());
}
@Override
public boolean updateValue(AbstractStruct struct)
{
Object selected = list.getSelectedValue();
if (selected instanceof RefEntry) {
RefEntry re = (RefEntry)selected;
value = re.getValue();
} else {
return false;
}
// notifying listeners
fireValueUpdated(new UpdateEvent(this, struct));
return true;
}
//--------------------- End Interface Editable ---------------------
//--------------------- Begin Interface Writeable ---------------------
@Override
public void write(OutputStream os) throws IOException
{
writeLong(os, value);
}
//--------------------- End Interface Writeable ---------------------
//--------------------- Begin Interface Readable ---------------------
@Override
public int read(ByteBuffer buffer, int offset)
{
buffer.position(offset);
switch (getSize()) {
case 1:
value = buffer.get() & 0xff;
break;
case 2:
value = buffer.getShort() & 0xffff;
break;
case 4:
value = buffer.getInt() & 0xffffffff;
break;
default:
throw new IllegalArgumentException();
}
return offset + getSize();
}
//--------------------- End Interface Readable ---------------------
@Override
public String toString()
{
String resName, searchString;
RefEntry ref = getRefEntry(value);
if (ref != null) {
resName = ref.getResourceName();
searchString = ref.getSearchString();
} else {
resName = defaultLabel;
searchString = "";
}
return String.format(formatString, resName, searchString, Long.toString(value));
}
//--------------------- Begin Interface IsNumeric ---------------------
@Override
public int getValue()
{
return (int)value;
}
@Override
public long getLongValue()
{
return value;
}
//--------------------- End Interface IsNumeric ---------------------
//--------------------- Begin Interface IsReference ---------------------
@Override
public String getResourceName()
{
RefEntry entry = getRefEntry(value);
if (entry != null) {
return entry.getResourceName();
} else {
return "";
}
}
//--------------------- End Interface IsReference ---------------------
public List<RefEntry> getResourceList()
{
return resources;
}
public String getFormatString()
{
return formatString;
}
private RefEntry getRefEntry(long value)
{
for (int i = 0, size = resources.size(); i < size; i++) {
RefEntry entry = resources.get(i);
if (entry.getValue() == value) {
return entry;
}
}
return null;
}
//-------------------------- INNER CLASSES --------------------------
public static class RefEntry implements Comparable<RefEntry>
{
private final long value; // associated ID
private final String name; // alternate label if ResourceEntry is empty
private final ResourceEntry entry; // contains resource if available
private final String searchString; // resource-dependent search string
private String fmt; // format string for textual representation
private String desc; // cached textual output for toString() method
public RefEntry(long value, String ref)
{
this(value, ref, null, null);
}
public RefEntry(long value, String ref, String search)
{
this(value, ref, search, null);
}
public RefEntry(long value, String ref, String search, List<Path> searchDirs)
{
this.value = value;
this.entry = (ref.lastIndexOf('.') > 0) ? ResourceFactory.getResourceEntry(ref, true, searchDirs) : null;
if (this.entry != null) {
this.searchString = (search != null) ? search : entry.getSearchString();
this.name = null;
} else {
this.searchString = (search != null) ? search : "";
this.name = ref;
}
this.fmt = FMT_REF_VALUE;
this.desc = String.format(fmt, getResourceName(), getSearchString(), Long.toString(value));
}
@Override
public String toString()
{
return desc;
}
@Override
public boolean equals(Object o)
{
return toString().equalsIgnoreCase(o.toString());
}
@Override
public int compareTo(RefEntry o)
{
return toString().compareToIgnoreCase(o.toString());
}
public boolean isResource() { return (entry != null); }
public long getValue() { return value; }
public ResourceEntry getResourceEntry() { return entry; }
public String getResourceName()
{
if (entry != null) {
return entry.getResourceName();
} else {
return name;
}
}
public String getSearchString()
{
return searchString;
}
public String getFormatString() { return fmt; }
public void setFormatString(String fmt)
{
if (fmt != null) {
this.fmt = fmt;
} else {
this.fmt = FMT_REF_VALUE;
}
this.desc = String.format(fmt, getResourceName(), getSearchString(), Long.toString(value));
}
}
}