Files
ATCS/src/com/gpl/rpg/atcontentstudio/ui/sprites/SpritesheetEditor.java
Zukero 940996aa30 Some refactoring. Replaced all "listeners" list by instances of
CopyOnWriteArrayList, to allow listeners to unregister themselves due to
an event while preventing ConcurrentModificationExceptions.
Modified all GameDataElement.elementChanged concrete implementation to
remove the backlink from the oldOne.An element pointed by an altered
element will not show the game source element in its backlink list
anymore.
2017-03-02 13:53:24 +01:00

452 lines
14 KiB
Java

package com.gpl.rpg.atcontentstudio.ui.sprites;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.swing.BorderFactory;
import javax.swing.DefaultListCellRenderer;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSpinner;
import javax.swing.JTable;
import javax.swing.ListModel;
import javax.swing.ListSelectionModel;
import javax.swing.event.ListDataEvent;
import javax.swing.event.ListDataListener;
import javax.swing.event.TableModelListener;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableModel;
import com.gpl.rpg.atcontentstudio.ATContentStudio;
import com.gpl.rpg.atcontentstudio.Notification;
import com.gpl.rpg.atcontentstudio.model.GameDataElement;
import com.gpl.rpg.atcontentstudio.model.ProjectTreeNode;
import com.gpl.rpg.atcontentstudio.model.maps.TMXMap;
import com.gpl.rpg.atcontentstudio.model.sprites.Spritesheet;
import com.gpl.rpg.atcontentstudio.ui.DefaultIcons;
import com.gpl.rpg.atcontentstudio.ui.Editor;
import com.gpl.rpg.atcontentstudio.ui.FieldUpdateListener;
import com.gpl.rpg.atcontentstudio.utils.DesktopIntegration;
import com.jidesoft.swing.JideBoxLayout;
import com.jidesoft.swing.JideTabbedPane;
public class SpritesheetEditor extends Editor {
private static final long serialVersionUID = 3956109815682889863L;
Map<String, JPanel> editorTabs = new LinkedHashMap<String, JPanel>();
JideTabbedPane editorTabsHolder;
private JSpinner widthField;
private JSpinner heightField;
private JCheckBox animatedBox;
@SuppressWarnings("rawtypes")
private JComboBox categoryBox;
private JPanel spriteViewPane;
public static JComponent getWarningLabel() {
JLabel label = new JLabel(
"<html><i>" +
"The data accompamying the image here is not part of the game.<br/>" +
"What you change here will be changed in your ATCS project only.<br/>" +
"None of this is exported to JSON or TMX, although it must be set correctly in order to choose tiles & icons correctly.<br/>" +
"</i></html>");
return label;
}
public SpritesheetEditor(Spritesheet sheet) {
super();
this.icon = new ImageIcon(sheet.getIcon(0));
this.name = sheet.id;
this.target = sheet;
JPanel pane = new JPanel();
final FieldUpdateListener listener = new SpritesheetFieldUpdater();
pane.setLayout(new JideBoxLayout(pane, JideBoxLayout.PAGE_AXIS, 6));
JPanel buttonPane = new JPanel();
buttonPane.setLayout(new JideBoxLayout(buttonPane, JideBoxLayout.LINE_AXIS));
JButton openImage = new JButton(new ImageIcon(DefaultIcons.getTileLayerImage()));
openImage.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
DesktopIntegration.openImage(((Spritesheet)target).spritesheetFile);
}
});
buttonPane.add(openImage, JideBoxLayout.FIX);
buttonPane.add(getWarningLabel(), JideBoxLayout.FIX);
buttonPane.add(new JPanel(), JideBoxLayout.VARY);
pane.add(buttonPane, JideBoxLayout.FIX);
addLabelField(pane, "Spritesheet ID: ", sheet.id);
addLabelField(pane, "File: ", sheet.spritesheetFile.getAbsolutePath());
widthField = addIntegerField(pane, "Sprite width (px): ", sheet.spriteWidth, false, true, listener);
heightField = addIntegerField(pane, "Sprite height (px): ", sheet.spriteHeight, false, true, listener);
animatedBox = addBooleanBasedCheckBox(pane, "Is an animation", sheet.animated, true, listener);
categoryBox = addEnumValueBox(pane, "Category: ", Spritesheet.Category.values(), sheet.category, true, listener);
spriteViewPane = new JPanel();
updateView(spriteViewPane);
pane.add(spriteViewPane, JideBoxLayout.FIX);
addBacklinksList(pane, sheet);
//Placeholder. Fills the eventual remaining space.
pane.add(new JPanel(), JideBoxLayout.VARY);
setLayout(new BorderLayout());
editorTabsHolder = new JideTabbedPane(JideTabbedPane.BOTTOM);
editorTabsHolder.setTabShape(JideTabbedPane.SHAPE_FLAT);
editorTabsHolder.setUseDefaultShowCloseButtonOnTab(false);
editorTabsHolder.setShowCloseButtonOnTab(false);
add(editorTabsHolder, BorderLayout.CENTER);
JScrollPane sheetScroller = new JScrollPane(pane);
sheetScroller.getVerticalScrollBar().setUnitIncrement(16);
editorTabsHolder.add("Spritesheet",sheetScroller);
JScrollPane rawScroller = new JScrollPane(new JLabel(new ImageIcon(sheet.spritesheet)));
rawScroller.getVerticalScrollBar().setUnitIncrement(16);
editorTabsHolder.add("Raw image", rawScroller);
}
private Thread animator = new Thread();
private boolean animate = true;
private JLabel iconLabel;
private List<ImageIcon> icons = null;
public void updateView(JPanel pane) {
Spritesheet sheet = (Spritesheet)target;
pane.removeAll();
pane.setLayout(new BorderLayout());
if (sheet.animated) {
iconLabel = new JLabel();
iconLabel.setBackground(Color.WHITE);
if (icons == null) {
icons = new ArrayList<ImageIcon>();
} else {
icons.clear();
}
int i = 0;
Image img;
while ((img = sheet.getImage(i++)) != null) {
icons.add(new ImageIcon(img));
}
if (i > 0) {
iconLabel.setIcon(icons.get(0));
}
pane.add(iconLabel, BorderLayout.CENTER);
resetAnimator();
} else {
JTable spritesTable = new JTable(new SpritesheetTableModel(sheet));
spritesTable.setDefaultRenderer(Image.class, new SpritesheetCellRenderer(sheet));
spritesTable.setCellSelectionEnabled(true);
spritesTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
spritesTable.getTableHeader().setVisible(false);
Enumeration<TableColumn> columns = spritesTable.getColumnModel().getColumns();
TableColumn col;
while (columns.hasMoreElements()) {
col = columns.nextElement();
col.setMinWidth(sheet.spriteWidth + 4);
col.setMaxWidth(sheet.spriteWidth + 4);
}
spritesTable.setRowHeight(sheet.spriteHeight + 4);
pane.add(new JScrollPane(spritesTable), BorderLayout.CENTER);
}
}
@SuppressWarnings({ "rawtypes", "unchecked" })
public static JList addBacklinksList(JPanel pane, Spritesheet sheet) {
final JList list = new JList(new SpritesheetsBacklinksListModel(sheet));
list.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() == 2) {
if (list.getSelectedValue() instanceof TMXMap) {
ATContentStudio.frame.openEditor((TMXMap)list.getSelectedValue());
ATContentStudio.frame.selectInTree((TMXMap)list.getSelectedValue());
} else if (list.getSelectedValue() instanceof GameDataElement) {
ATContentStudio.frame.openEditor((GameDataElement)list.getSelectedValue());
ATContentStudio.frame.selectInTree((GameDataElement)list.getSelectedValue());
}
}
}
});
list.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
if (list.getSelectedValue() instanceof TMXMap) {
ATContentStudio.frame.openEditor((TMXMap)list.getSelectedValue());
ATContentStudio.frame.selectInTree((TMXMap)list.getSelectedValue());
} else if (list.getSelectedValue() instanceof GameDataElement) {
ATContentStudio.frame.openEditor((GameDataElement)list.getSelectedValue());
ATContentStudio.frame.selectInTree((GameDataElement)list.getSelectedValue());
}
}
}
});
list.setCellRenderer(new BacklinkCellRenderer(true));
JScrollPane scroller = new JScrollPane(list);
scroller.setBorder(BorderFactory.createTitledBorder("Elements pointing to this spritesheet."));
pane.add(scroller, JideBoxLayout.FIX);
return list;
}
public static class SpritesheetTableModel implements TableModel {
Spritesheet sheet;
public SpritesheetTableModel(Spritesheet sheet) {
this.sheet = sheet;
}
@Override
public int getRowCount() {
return (sheet.spritesheet.getHeight() / sheet.spriteHeight) + ((sheet.spritesheet.getHeight() % sheet.spriteHeight) == 0 ? 0 : 1);
}
@Override
public int getColumnCount() {
return (sheet.spritesheet.getWidth() / sheet.spriteWidth) + ((sheet.spritesheet.getWidth() % sheet.spriteWidth) == 0 ? 0 : 1);
}
@Override
public String getColumnName(int columnIndex) {
return "";
}
@Override
public Class<?> getColumnClass(int columnIndex) {
return Image.class;
}
@Override
public boolean isCellEditable(int rowIndex, int columnIndex) {
return false;
}
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
return sheet.getImage((rowIndex * getColumnCount()) + columnIndex);
}
@Override
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
}
List<TableModelListener> listeners = new CopyOnWriteArrayList<TableModelListener>();
@Override
public void addTableModelListener(TableModelListener l) {
listeners.add(l);
}
@Override
public void removeTableModelListener(TableModelListener l) {
listeners.remove(l);
}
}
public static class SpritesheetCellRenderer extends DefaultTableCellRenderer {
private static final long serialVersionUID = -4213756343124247612L;
Spritesheet sheet;
public SpritesheetCellRenderer(Spritesheet sheet) {
super();
this.sheet = sheet;
}
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
Component c = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
if (c instanceof JLabel) {
((JLabel)c).setText("");
if (value != null) {
((JLabel)c).setIcon(new ImageIcon((Image)value));
((JLabel)c).setToolTipText(sheet.id+":"+((row * table.getColumnCount())+column));
}
}
return c;
}
}
@Override
public void setVisible(boolean aFlag) {
super.setVisible(aFlag);
animate = aFlag;
if (aFlag && animator != null) {
resetAnimator();
}
}
private void resetAnimator() {
new Thread() {
public void run() {
if (animator != null && animator.isAlive()) {
try {
animator.join();
} catch (InterruptedException e) {}
}
animate = true;
animator = new Thread() {
public void run() {
int i = -1;
while (animate) {
if (icons != null) {
synchronized (icons) {
i = (i + 1) % icons.size();
iconLabel.setIcon(icons.get(i));
}
iconLabel.revalidate();
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {}
}
};
};
animator.start();
}
}.start();
}
public static class SpritesheetsBacklinksListModel implements ListModel<ProjectTreeNode> {
Spritesheet sheet;
public SpritesheetsBacklinksListModel(Spritesheet sheet) {
this.sheet = sheet;
}
@Override
public int getSize() {
return sheet.getBacklinks().size();
}
@Override
public ProjectTreeNode getElementAt(int index) {
for (ProjectTreeNode node : sheet.getBacklinks()) {
if (index == 0) return node;
index --;
}
return null;
}
List<ListDataListener> listeners = new CopyOnWriteArrayList<ListDataListener>();
@Override
public void addListDataListener(ListDataListener l) {
listeners.add(l);
}
@Override
public void removeListDataListener(ListDataListener l) {
listeners.remove(l);
}
public void fireListChanged() {
for (ListDataListener l : listeners) {
l.contentsChanged(new ListDataEvent(this, ListDataEvent.CONTENTS_CHANGED, 0, this.getSize()));
}
}
}
public static class BacklinkCellRenderer extends DefaultListCellRenderer {
private static final long serialVersionUID = 6819681566800482793L;
private boolean includeType = false;
public BacklinkCellRenderer(boolean includeType) {
super();
this.includeType = includeType;
}
@Override
public Component getListCellRendererComponent(@SuppressWarnings("rawtypes") JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
JLabel label = (JLabel) super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
if (value == null) {
label.setText("none");
} else {
if (includeType && ((ProjectTreeNode)value).getDataType() != null) {
label.setText(((ProjectTreeNode)value).getDataType().toString()+"/"+((ProjectTreeNode)value).getDesc());
} else {
label.setText(((ProjectTreeNode)value).getDesc());
}
if (((ProjectTreeNode)value).getIcon() == null) {
Notification.addError("Unable to find icon for "+((ProjectTreeNode)value).getDesc());
} else {
label.setIcon(new ImageIcon(((ProjectTreeNode)value).getIcon()));
}
}
return label;
}
}
@Override
public void targetUpdated() {
this.icon = new ImageIcon(((Spritesheet)target).getIcon(0));
this.name = ((Spritesheet)target).id;
}
public class SpritesheetFieldUpdater implements FieldUpdateListener {
@Override
public void valueChanged(JComponent source, Object value) {
Spritesheet sheet = (Spritesheet) target;
if (source == widthField) {
sheet.spriteWidth = (Integer) value;
sheet.clearCache();
updateView(spriteViewPane);
spriteViewPane.revalidate();
spriteViewPane.repaint();
} else if (source == heightField) {
sheet.spriteHeight = (Integer) value;
sheet.clearCache();
updateView(spriteViewPane);
spriteViewPane.revalidate();
spriteViewPane.repaint();
} else if (source == animatedBox) {
sheet.animated = (Boolean) value;
if (!sheet.animated) {
animate = false;
}
updateView(spriteViewPane);
spriteViewPane.revalidate();
spriteViewPane.repaint();
} else if (source == categoryBox) {
sheet.category = (Spritesheet.Category) value;
}
}
}
}