/* * $Id$ * * Copyright (c) 2008-2012 by Brent Easton * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License (LGPL) as published by the Free Software Foundation. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, copies are available * at http://www.opensource.org. */ package VASSAL.counters; import java.awt.Color; import java.awt.Component; import java.awt.Dimension; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Enumeration; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.regex.Pattern; import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.DefaultCellEditor; import javax.swing.DefaultListModel; import javax.swing.JButton; import javax.swing.JDialog; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.JTextField; import javax.swing.SwingConstants; import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableColumnModel; import org.jdesktop.swingx.JXTreeTable; import org.jdesktop.swingx.treetable.DefaultMutableTreeTableNode; import org.jdesktop.swingx.treetable.DefaultTreeTableModel; import VASSAL.build.Configurable; import VASSAL.build.GameModule; import VASSAL.build.module.PrototypeDefinition; import VASSAL.build.module.documentation.HelpFile; import VASSAL.build.widget.CardSlot; import VASSAL.build.widget.PieceSlot; import VASSAL.configure.BooleanConfigurer; import VASSAL.configure.ConfigureTree; import VASSAL.configure.DirectoryConfigurer; import VASSAL.configure.StringConfigurer; import VASSAL.configure.StringEnumConfigurer; import VASSAL.i18n.Resources; import VASSAL.tools.BrowserSupport; import VASSAL.tools.SequenceEncoder; import VASSAL.tools.image.ImageUtils; import VASSAL.tools.swing.Dialogs; /** * Class to load a directory full of images and create counters * */ public class MassPieceLoader { protected static final int DESC_COL = 0; protected static final int NAME_COL = 2; protected static final int IMAGE_COL = 1; protected static final int SKIP_COL = 3; protected static final int COPIES_COL = 4; protected static final int COLUMN_COUNT = 5; protected static final Color EDITABLE_COLOR = Color.blue; protected Configurable target; protected ConfigureTree configureTree; protected ArrayList<String> imageNames = new ArrayList<String>(); protected ArrayList<String> baseImages = new ArrayList<String>(); protected ArrayList<String> levelImages = new ArrayList<String>(); protected HashMap<String, PieceInfo> pieceInfo = new HashMap<String, PieceInfo>(); protected ArrayList<Emb> layers = new ArrayList<Emb>(); protected MassLoaderDialog dialog; private static DirectoryConfigurer dirConfig = new DirectoryConfigurer(null, "Image Directory: "); private static BooleanConfigurer basicConfig = new BooleanConfigurer(null, "Do not load images into Basic Piece traits?", Boolean.FALSE); private static MassPieceDefiner definer = new MassPieceDefiner(); public MassPieceLoader(ConfigureTree tree, Configurable target) { this.target = target; this.configureTree = tree; } // The Dialog does all the work. public void load() { dialog = new MassLoaderDialog(); dialog.setVisible(true); if (!dialog.isCancelled()) { dialog.load(); } } /** * Mass Piece Loader dialog */ class MassLoaderDialog extends JDialog { private static final long serialVersionUID = 1L; protected boolean cancelled = false; protected DefineDialog defineDialog; protected MyTreeTable tree; protected MyTreeTableModel model; protected BasicNode root; protected File loadDirectory; public MassLoaderDialog() { super(configureTree.getFrame()); setModal(true); setTitle("Load Multiple Pieces"); setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS)); setPreferredSize(new Dimension(800, 600)); dirConfig.addPropertyChangeListener(new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent e) { if (e.getNewValue() != null) { buildTree((File) e.getNewValue()); } } }); add(dirConfig.getControls()); basicConfig.addPropertyChangeListener(new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent e) { if (e.getNewValue() != null) { buildTree(dirConfig.getFileValue()); } } }); add(basicConfig.getControls()); defineDialog = new DefineDialog(this); final JButton defineButton = new JButton("Edit Piece Template"); defineButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { final GamePiece savePiece = definer.getPiece(); defineDialog.setVisible(true); if (defineDialog.isCancelled()) { definer.setPiece(savePiece); } else { buildTree(dirConfig.getFileValue()); } } }); add(defineButton); tree = new MyTreeTable(); buildTree(dirConfig.getFileValue()); final JScrollPane scrollPane = new JScrollPane(tree); add(scrollPane); final Box buttonBox = Box.createHorizontalBox(); final JButton okButton = new JButton(Resources.getString(Resources.OK)); okButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { save(); } }); buttonBox.add(okButton); final JButton cancelButton = new JButton(Resources .getString(Resources.CANCEL)); cancelButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { cancel(); } }); buttonBox.add(cancelButton); final JButton helpButton = new JButton(Resources .getString(Resources.HELP)); helpButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { final HelpFile h = HelpFile.getReferenceManualPage("MassPieceLoader.htm"); BrowserSupport.openURL(h.getContents().toString()); } }); buttonBox.add(helpButton); add(buttonBox); pack(); setLocationRelativeTo(getParent()); setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent we) { cancel(); } }); } public void cancel() { cancelled = true; dispose(); } public void save() { // Count the pieces to be loaded int pieceCount = 0; for (int i = 0; i < root.getChildCount(); i++) { final PieceNode node = (PieceNode) root.getChildAt(i); if (! node.isSkip()) { pieceCount += node.getCopies(); } } // Do they really want to do this? if (pieceCount > 0) { final String message = "This will load " + pieceCount + " piece" + (pieceCount > 1 ? "s" : "") + " into component " + target.getConfigureName() + ". There is no UNDO option for this process. " + "Are you sure you wish to continue with the load?"; final int result = Dialogs.showConfirmDialog(null, "Confirm Load", "Confirm Load", message, JOptionPane.WARNING_MESSAGE, JOptionPane.YES_NO_CANCEL_OPTION); if (result == 1) { cancel(); return; } else if (result == 2) { return; } } cancelled = false; dispose(); } public boolean isCancelled() { return cancelled; } public File getDirectory() { return dirConfig.getFileValue(); } /** * Build a tree representing the Game Pieces, Layers, Levels and images to * be loaded. This tree is used as a model for the JxTreeTable, and also as * the guide to load the counters. * * @param dir * Directory containing images */ protected void buildTree(File dir) { loadDirectory = dir; // Make a list of the Layer traits in the template layers.clear(); GamePiece piece = definer.getPiece(); while (piece instanceof Decorator) { piece = Decorator.getDecorator(piece, Emb.class); if (piece instanceof Emb) { layers.add(0, (Emb) piece); piece = ((Emb) piece).getInner(); } } // Find all of the images in the target directory loadImageNames(dir); // Check each image in the target directory to see if it matches the // level specification in any of the Embellishments in the template. // The remaining images that do not match any Level are our baseImages baseImages.clear(); levelImages.clear(); for (String imageName : imageNames) { boolean match = false; for (Emb emb : layers) { match = emb.matchLayer(imageName); if (match) { break; } } if (match) { levelImages.add(imageName); } else { baseImages.add(imageName); } } // Generate a table node for each base Image. // Create a child Layer Node for each layer that has at least one image root = new BasicNode(); for (String baseImage : baseImages) { final BasicNode pieceNode = new PieceNode(baseImage); for (Emb emb : layers) { final Emb newLayer = new Emb(emb.getType(), null); if (newLayer.buildLayers(baseImage, levelImages)) { final BasicNode layerNode = new LayerNode(newLayer.getLayerName()); for (int i = 0; i < newLayer.getImageNames().length; i++) { final String levelName = newLayer.getLevelNames()[i]; final BasicNode levelNode = new LevelNode( levelName == null ? "" : levelName, newLayer .getImageNames()[i], i); layerNode.add(levelNode); } pieceNode.add(layerNode); } } root.add(pieceNode); } // Set the tree model = new MyTreeTableModel(root); tree.setTreeTableModel(model); final TableColumnModel tcm = tree.getColumnModel(); tcm.getColumn(DESC_COL).setPreferredWidth(100); tcm.getColumn(DESC_COL).setCellRenderer(new ImageNameRenderer()); tcm.getColumn(NAME_COL).setPreferredWidth(200); tcm.getColumn(NAME_COL).setCellRenderer(new NameRenderer()); tcm.getColumn(NAME_COL).setCellEditor(new NameEditor(new JTextField())); tcm.getColumn(IMAGE_COL).setPreferredWidth(200); tcm.getColumn(IMAGE_COL).setCellRenderer(new ImageNameRenderer()); tcm.getColumn(SKIP_COL).setPreferredWidth(50); tcm.getColumn(SKIP_COL).setMaxWidth(50); tcm.getColumn(COPIES_COL).setPreferredWidth(50); tcm.getColumn(COPIES_COL).setMaxWidth(50); tcm.getColumn(COPIES_COL).setCellRenderer(new CopiesRenderer()); } class NameRenderer extends DefaultTableCellRenderer { private static final long serialVersionUID = 1L; public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int col) { Component c = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, col); final BasicNode node = (BasicNode) tree.getPathForRow(row) .getLastPathComponent(); c.setEnabled(!node.isSkip()); c.setForeground(EDITABLE_COLOR); return c; } } class CopiesRenderer extends DefaultTableCellRenderer { private static final long serialVersionUID = 1L; public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { JLabel renderedLabel = (JLabel) super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); final BasicNode node = (BasicNode) tree.getPathForRow(row).getLastPathComponent(); renderedLabel.setHorizontalAlignment(SwingConstants.CENTER); renderedLabel.setEnabled(!node.isSkip()); renderedLabel.setForeground(EDITABLE_COLOR); return renderedLabel; } } class ImageNameRenderer extends DefaultTableCellRenderer { private static final long serialVersionUID = 1L; public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int col) { final DefaultTableCellRenderer c = (DefaultTableCellRenderer) super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, col); final BasicNode node = (BasicNode) tree.getPathForRow(row) .getLastPathComponent(); c.setEnabled(!node.isSkip()); if (node instanceof PieceNode) { final String image = ((PieceNode) node).getImageName(); final String i = "<html><img src=\"file:/"+loadDirectory.getAbsolutePath()+"/"+image+"\"></html>"; c.setToolTipText(i); } return c; } } class NameEditor extends DefaultCellEditor { private static final long serialVersionUID = 1L; public NameEditor(JTextField textField) { super(textField); } public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { Component c = super.getTableCellEditorComponent(table, value, isSelected, row, column); c.setForeground(Color.blue); return c; } } class SkipRenderer extends DefaultTableCellRenderer { private static final long serialVersionUID = 1L; public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int col) { Component c = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, col); if (!(tree.getPathForRow(row).getLastPathComponent() instanceof PieceNode)) { return new JLabel(""); } return c; } } /** * Load all image names in the target directory * * @param dir */ protected void loadImageNames(File dir) { imageNames.clear(); if (dir != null && dir.isDirectory()) { for (File file : dir.listFiles()) { final String imageName = file.getName(); if (ImageUtils.hasImageSuffix(imageName)) { imageNames.add(imageName); } } } } /** * Load the Pieces based on the Node Tree built while user was editing */ public void load() { // Check a Directory has been entered final File dir = dialog.getDirectory(); if (dir == null) { return; } // For each PieceNode, load the required images into the module and // generate the piece for (int i = 0; i < root.getChildCount(); i++) { final PieceNode pieceNode = (PieceNode) root.getChildAt(i); if (!pieceNode.isSkip()) { load(pieceNode); } } } /** * Load a specific piece and and all referenced images. * * @param pieceNode * Sub-tree representing piece */ public void load(PieceNode pieceNode) { // Add the Base Image to the module final String baseImage = pieceNode.getImageName(); addImageToModule(baseImage); // Create the BasicPiece final String basicType = new SequenceEncoder("", ';').append("").append( baseImage).append(pieceNode.getName()).getValue(); final BasicPiece basic = (BasicPiece) GameModule.getGameModule() .createPiece(BasicPiece.ID + basicType); // Build the piece from the template GamePiece template = definer.getPiece(); ArrayList<Decorator> traits = new ArrayList<Decorator>(); // Reverse the order of the traits to innermost out while (template != null && template instanceof Decorator) { traits.add(0, (Decorator) template); template = ((Decorator) template).getInner(); } for (int count = 0; count < pieceNode.getCopies(); count++) { GamePiece piece = basic; // Build the new piece. Note special Handling for Embellishment templates // that will // have actual images added for references to matching images. If an // Embellishment // has no matching images at all, do not add it to the new counter. for (Decorator trait : traits) { if (trait instanceof Emb) { Emb newLayer = new Emb(trait.getType(), null); if (newLayer.buildLayers(baseImage, levelImages)) { for (String image : newLayer.getBuiltImageList()) { addImageToModule(image); } newLayer.setInner(piece); final String saveState = newLayer.getState(); piece = GameModule.getGameModule().createPiece(newLayer.getType()); piece.setState(saveState); } } else { final Decorator newTrait = (Decorator) GameModule.getGameModule().createPiece(trait.getType()); newTrait.setState(trait.getState()); newTrait.setInner(piece); final String saveState = newTrait.getState(); piece = GameModule.getGameModule().createPiece(newTrait.getType()); piece.setState(saveState); } } // Create the PieceSlot for the new piece PieceSlot slot = null; final Class<?>[] c = target.getAllowableConfigureComponents(); for (int i = 0; i < c.length && slot == null; i++) { if (c[i].equals(CardSlot.class)) { slot = new CardSlot(); slot.setPiece(piece); } } if (slot == null) { slot = new PieceSlot(piece); } // Generate a gpid configureTree.updateGpIds(slot); // Add the new piece to the tree configureTree.externalInsert(target, slot); } } } /** * Add the named image to the module * * @param name * Image name */ protected void addImageToModule(String name) { if (name != null && name.length() > 0) { try { GameModule.getGameModule().getArchiveWriter().addImage( new File(dirConfig.getFileValue(), name).getCanonicalPath(), name); } catch (IOException e) { // FIXME: Log error properly // ErrorLog.log() } } } /** * Maintain a record of all names changed by the user for image basenames. * Default name is image name with image suffix stripped. * * @param baseName * Image name * @return user modified name */ protected String getPieceName(String baseName) { final PieceInfo info = pieceInfo.get(baseName); return info == null ? ImageUtils.stripImageSuffix(baseName) : info .getName(); } /** * * A custom piece definer based on the Prototype piece definer * */ static class MassPieceDefiner extends PrototypeDefinition.Config.Definer { private static final long serialVersionUID = 1L; protected static DefaultListModel newModel; public MassPieceDefiner() { super(GameModule.getGameModule().getGpIdSupport()); // Build a replacement model that uses a modified Embellishment trait // with a replacement ImagePicker. if (newModel == null) { newModel = new DefaultListModel(); for (Enumeration<?> e = availableModel.elements(); e.hasMoreElements();) { Object o = e.nextElement(); if (o instanceof Embellishment) { newModel.addElement(new MassPieceLoader.Emb()); } else { newModel.addElement(o); } } } availableList.setModel(newModel); setPiece(null); } } /** * * Dialog to hold the PieceDefiner used to specify the multi-load piece * template. * */ class DefineDialog extends JDialog { private static final long serialVersionUID = 1L; protected boolean cancelled = false; public DefineDialog(JDialog owner) { setModal(true); setVisible(false); setTitle("Multiple Piece Template"); setLocationRelativeTo(owner); final Box box = Box.createVerticalBox(); box.add(definer); final JButton saveButton = new JButton(Resources .getString(Resources.SAVE)); saveButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { save(); } }); final JButton canButton = new JButton(Resources .getString(Resources.CANCEL)); canButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { cancel(); } }); final Box bbox = Box.createHorizontalBox(); bbox.add(saveButton); bbox.add(canButton); box.add(bbox); add(box); setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent we) { cancel(); } }); pack(); } protected void save() { cancelled = false; setVisible(false); } protected void cancel() { cancelled = true; setVisible(false); } public boolean isCancelled() { return cancelled; } } /** * ************************************************************************* * Custom Tree table model:- - Return column count - Return column headings */ private class MyTreeTableModel extends DefaultTreeTableModel { public MyTreeTableModel(BasicNode rootNode) { super(rootNode); } public int getColumnCount() { return COLUMN_COUNT; } public String getColumnName(int col) { switch (col) { case DESC_COL: return "Item"; case NAME_COL: return "Piece Name"; case IMAGE_COL: return "Image File"; case SKIP_COL: return "Skip?"; case COPIES_COL: return "Copies"; } return ""; } public Class<?> getColumnClass(int column) { if (column == SKIP_COL) { return Boolean.class; } else if (column == COPIES_COL) { return Integer.class; } else { return String.class; } } public boolean isCellEditable(Object node, int column) { if (node instanceof PieceNode) { if (column == NAME_COL || column == COPIES_COL) { return !((PieceNode) node).isSkip(); } else if (column == SKIP_COL) { return true; } } return false; } public Object getValueAt(Object node, int column) { return ((BasicNode) node).getValueAt(column); } public void setValueAt(Object value, Object node, int column) { if (node instanceof PieceNode) { if (column == NAME_COL) { ((PieceNode) node).setName((String) value); } else if (column == SKIP_COL) { ((PieceNode) node).setSkip(((Boolean) value).booleanValue()); } else if (column == COPIES_COL) { int val = value == null ? 1 : ((Integer) value).intValue(); if (val < 1) val = 1; ((PieceNode) node).setCopies(val); } } else { super.setValueAt(value, node, column); } } } /** * Custom implementation of JXTreeTable Fix for bug on startup generating * illegal column numbers * */ class MyTreeTable extends JXTreeTable { private static final long serialVersionUID = 1L; public MyTreeTable() { super(); setRootVisible(false); } // // public String getToolTipText(MouseEvent event) { // if (getComponentAt(event.getPoint().x, event.getPoint().y) == null) // return null; // return super.getToolTipText(event); // } // Hide the Skip checkbox on rows other than Piece rows public TableCellRenderer getCellRenderer(int row, int column) { if (column == SKIP_COL || column == COPIES_COL) { if (!(this.getPathForRow(row).getLastPathComponent() instanceof PieceNode)) { return new NullRenderer(); } } return super.getCellRenderer(row, column); } } /** * Blank Cell Renderer */ class NullRenderer implements TableCellRenderer { public Component getTableCellRendererComponent(JTable arg0, Object arg1, boolean arg2, boolean arg3, int arg4, int arg5) { return new JLabel(""); } } /** * ************************************************************************* * Custom TreeTable Node */ class BasicNode extends DefaultMutableTreeTableNode { protected String name; protected String imageName; protected boolean skip; protected int copies; public BasicNode() { this("", ""); } public BasicNode(String name, String imageName) { this.name = name; this.imageName = imageName; this.skip = false; this.copies = 1; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getImageName() { return imageName; } public void setImageName(String imageName) { this.imageName = imageName; } public boolean isSkip() { return skip; } public void setSkip(boolean b) { skip = b; } public void setCopies(int i) { copies = i; } public int getCopies() { return copies; } public String getDescription() { return "Root"; } public Object getValueAt(int col) { switch (col) { case DESC_COL: return getDescription(); case NAME_COL: return getName(); case IMAGE_COL: return getImageName(); case SKIP_COL: return Boolean.valueOf(isSkip()); case COPIES_COL: return Integer.valueOf(getCopies()); } return ""; } } /** * Node representing a GamePiece to be loaded. imageName is the name of the * Basic Piece image * */ class PieceNode extends BasicNode { boolean noBasicPieceImage; public PieceNode(String imageName) { super(); setImageName(imageName); final PieceInfo info = pieceInfo.get(imageName); if (info == null) { setName(ImageUtils.stripImageSuffix(imageName)); setSkip(false); } else { setName(info.getName()); setSkip(info.isSkip()); } } public String getImageName() { if (basicConfig.booleanValue().booleanValue()) { return ""; } else { return super.getImageName(); } } public String getDescription() { return "Piece"; } public void setName(String name) { super.setName(name); pieceInfo.put(getImageName(), new PieceInfo(name, isSkip())); } public void setSkip(boolean skip) { super.setSkip(skip); pieceInfo.put(getImageName(), new PieceInfo(getName(), skip)); } } /** * Node representing a Layer trait of a GamePiece */ class LayerNode extends BasicNode { public LayerNode(String name) { super(name, ""); } public String getDescription() { return "Layer" + (name.length() > 0 ? " [" + name + "]" : ""); } public String getName() { return ""; } } /** * Node representing an individual Image Level within a Layer trait * */ class LevelNode extends BasicNode { int levelNumber; public LevelNode(String name, String imageName, int level) { super(name, imageName); levelNumber = level; } public String getDescription() { return "Level " + (levelNumber + 1) + (name.length() > 0 ? " [" + name + "]" : ""); } public String getName() { return ""; } } /** * Utility class to hold user changes about pieces - Updated piece name - Skip * load flag * */ class PieceInfo { protected String name; protected boolean skip; public PieceInfo(String name, boolean skip) { this.name = name; this.skip = skip; } public String getName() { return name; } public void setName(String name) { this.name = name; } public boolean isSkip() { return skip; } public void setSkip(boolean b) { skip = b; } } /** * Subclass of Embellishment to allow us to directly manipulate its members */ static class Emb extends Embellishment { private ArrayList<String> builtImages = new ArrayList<String>(); public Emb() { super(); } public Emb(String state, GamePiece p) { super(state, p); } public String[] getImageNames() { return imageName; } public String[] getLevelNames() { return commonName; } // Return true if the specified file name matches the level // definition of any level in this layer public boolean matchLayer(String s) { for (String levelName : imageName) { if (match(s.split("\\.")[0], levelName)) { return true; } } return false; } protected boolean match(String s, String levelName) { if (levelName != null && levelName.length() > 1) { // Check 1 to skip // command char if (levelName.charAt(0) == BASE_IMAGE.charAt(0)) { return false; } else if (levelName.charAt(0) == ENDS_WITH.charAt(0)) { return s.endsWith(levelName.substring(1)); } else if (levelName.charAt(0) == INCLUDES.charAt(0)) { return s.indexOf(levelName.substring(1)) >= 0; } else if (levelName.charAt(0) == EQUALS.charAt(0)) { return s.equals(levelName.substring(1)); } else { try { return Pattern.matches(levelName.substring(1), s); } catch (Exception ex) { // Invalid pattern } } } return false; } /** * Set the actual layer images based on a base image and the layer image * template specification. * * @param baseImage * base Image name * @param levelImages * List of available level images * @return true if at least one layer built */ public boolean buildLayers(String baseImage, ArrayList<String> levelImages) { final String base = baseImage.split("\\.")[0]; int count = 0; builtImages.clear(); for (int i = 0; i < imageName.length; i++) { final String imageTemplate = imageName[i]; String thisImage = null; if (imageTemplate.charAt(0) == BASE_IMAGE.charAt(0)) { thisImage = baseImage; } else { for (Iterator<String> it = levelImages.iterator(); it.hasNext() && thisImage == null;) { final String checkImage = it.next(); final String checkImageBase = checkImage.split("\\.")[0]; if (imageTemplate.charAt(0) == EQUALS.charAt(0)) { if (match(checkImageBase, imageTemplate)) { thisImage = checkImage; } } else { if (checkImage.startsWith(base)) { if (match(checkImageBase, imageTemplate)) { thisImage = checkImage; } } } } } imageName[i] = thisImage; if (thisImage != null) { count++; builtImages.add(thisImage); } } return count > 0; } public List<String> getBuiltImageList() { return builtImages; } public class LoaderEd extends Embellishment.Ed { public LoaderEd(Embellishment e) { super(e); } protected MultiImagePicker getImagePicker() { return new LoaderImagePicker(); } } public PieceEditor getEditor() { return new LoaderEd(this); } } /** * Replacement class for the MultiImagePicker to specify image match strings */ static class LoaderImagePicker extends MultiImagePicker { private static final long serialVersionUID = 1L; public void addEntry() { String entry = "Image " + (imageListElements.size() + 1); imageListElements.addElement(entry); Entry pick = new Entry(); multiPanel.add(entry, pick); imageList.setSelectedIndex(imageListElements.size() - 1); cl.show(multiPanel, (String) imageList.getSelectedValue()); } public List<String> getImageNameList() { final int size = imageListElements.size(); final ArrayList<String> names = new ArrayList<String>(size); for (int i = 0; i < size; ++i) { names.add((((Entry) multiPanel.getComponent(i)).toString())); } return names; } public void clear() { multiPanel.removeAll(); imageListElements.removeAllElements(); } public void setImageList(String names[]) { while (names.length > multiPanel.getComponentCount()) { addEntry(); } for (int i = 0; i < names.length; ++i) { ((Entry) multiPanel.getComponent(i)).setImageName(names[i]); } } } protected static final String ENDS_WITH = "ends with"; protected static final String INCLUDES = "includes"; protected static final String MATCHES = "matches"; protected static final String EQUALS = "same as"; protected static final String BASE_IMAGE = "use Base Image"; static class Entry extends JPanel { private static final long serialVersionUID = 1L; private StringEnumConfigurer typeConfig; private StringConfigurer nameConfig; private JLabel warning = new JLabel("Warning - Image suffix included"); public Entry() { setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); add(new JLabel( "Do NOT include image suffix (eg. .gif, .png) in level match strings")); Box entry = Box.createHorizontalBox(); entry.add(new JLabel("Image name ")); typeConfig = new StringEnumConfigurer(null, "", new String[] { ENDS_WITH, INCLUDES, MATCHES, EQUALS, BASE_IMAGE }); entry.add(typeConfig.getControls()); nameConfig = new StringConfigurer(null, ""); entry.add(nameConfig.getControls()); add(entry); warning.setVisible(false); add(warning); typeConfig.addPropertyChangeListener(new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent e) { updateVisibility(); } }); nameConfig.addPropertyChangeListener(new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent e) { updateVisibility(); } }); } protected void updateVisibility() { warning .setVisible(ImageUtils.hasImageSuffix(nameConfig.getValueString())); nameConfig.getControls().setVisible( !typeConfig.getValueString().equals(BASE_IMAGE)); } public String toString() { return typeConfig.getValueString().charAt(0) + nameConfig.getValueString(); } public void setImageName(String s) { switch (s.charAt(0)) { case 'e': typeConfig.setValue(ENDS_WITH); break; case 'i': typeConfig.setValue(INCLUDES); break; case 'm': typeConfig.setValue(MATCHES); break; case 's': typeConfig.setValue(EQUALS); break; case 'u': typeConfig.setValue(BASE_IMAGE); break; } nameConfig.setValue(s.substring(1)); } } }