/*
 * $Id: BoardPicker.java 4155 2008-09-29 16:03:01Z uckelman $
 *
 * Copyright (c) 2000-2003 by Rodney Kinney
 *
 * 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.build.module.map;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.GridLayout;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Vector;

import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.JToolBar;
import javax.swing.WindowConstants;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import VASSAL.build.Buildable;
import VASSAL.build.Builder;
import VASSAL.build.Configurable;
import VASSAL.build.GameModule;
import VASSAL.build.module.GameComponent;
import VASSAL.build.module.GameSetupStep;
import VASSAL.build.module.Map;
import VASSAL.build.module.documentation.HelpFile;
import VASSAL.build.module.map.boardPicker.Board;
import VASSAL.build.module.map.boardPicker.BoardSlot;
import VASSAL.command.Command;
import VASSAL.command.CommandEncoder;
import VASSAL.configure.ConfigureTree;
import VASSAL.configure.Configurer;
import VASSAL.configure.DoubleConfigurer;
import VASSAL.configure.IntConfigurer;
import VASSAL.configure.StringConfigurer;
import VASSAL.configure.ValidationReport;
import VASSAL.configure.ValidityChecker;
import VASSAL.i18n.ComponentI18nData;
import VASSAL.i18n.Localization;
import VASSAL.i18n.Resources;
import VASSAL.i18n.Translatable;
import VASSAL.tools.SequenceEncoder;

/**
 * This class is responsible for maintaining the {@link Board}s on a {@link Map}. As a {@link CommandEncoder}, it
 * recognizes {@link Command}s that specify the set of boards to be used on a map. As a {@link GameComponent} it reacts
 * to the start of a game by prompting the player to select boards if none have been specified.
 */
public class BoardPicker implements ActionListener, GameComponent, GameSetupStep, Configurable, CommandEncoder, ValidityChecker {
  private static final long serialVersionUID = 1L;
  public static final String ID = "BoardPicker"; //$NON-NLS-1$
  protected List<Board> possibleBoards = new ArrayList<Board>();
  protected List<Board> currentBoards = null;
  protected Dimension psize = new Dimension(350, 125);
  protected double slotScale = 0.2;
  @Deprecated
  protected JTextField status;
  protected JLabel statusLabel;
  protected Map map;
  protected JPanel slotPanel;
  protected String version = "0.0"; //$NON-NLS-1$
  protected int nx = 1, ny = 1;
  protected JToolBar toolbar;
  protected JPanel controls;
  protected JButton addRowButton;
  protected JButton addColumnButton;
  protected boolean allowMultiple;
  protected int maxColumns;
  protected String title = Resources.getString("BoardPicker.choose_boards"); //$NON-NLS-1$
  protected String addRowButtonText = Resources.getString("BoardPicker.add_row"); //$NON-NLS-1$
  protected String addColumnButtonText = Resources.getString("BoardPicker.add_column"); //$NON-NLS-1$
  protected String boardPrompt = Resources.getString("BoardPicker.select_board"); //$NON-NLS-1$
  protected String defaultSetup;
  protected List<JButton> multipleButtons;
  public static final String SCALE = "slotScale"; //$NON-NLS-1$
  public static final String SLOT_HEIGHT = "slotHeight"; //$NON-NLS-1$
  public static final String SLOT_WIDTH = "slotWidth"; //$NON-NLS-1$
  public static final String SETUP = "setup"; //$NON-NLS-1$
  public static final String DIALOG_TITLE = "title"; //$NON-NLS-1$
  public static final String ADD_ROW_BUTTON_TEXT = "addRowText"; //$NON-NLS-1$
  public static final String ADD_COLUMN_BUTTON_TEXT = "addColumnText"; //$NON-NLS-1$
  public static final String BOARD_PROMPT = "boardPrompt"; //$NON-NLS-1$
  public static final String MAX_COLUMNS = "maxColumns"; //$NON-NLS-1$
  protected JButton clearButton;
  protected JButton okButton;
  protected ComponentI18nData myI18nData;
  protected JScrollPane slotScroll;

  public BoardPicker() {
    allowMultiple = false;
  }

  protected void initComponents() {
    multipleButtons = new ArrayList<JButton>();
    controls = new JPanel(new BorderLayout());
    statusLabel = new JLabel(""); //$NON-NLS-1$
    statusLabel.setForeground(Color.BLUE);
    slotPanel = new JPanel();
    toolbar = new JToolBar();
    toolbar.setFloatable(false);
    toolbar.setLayout(new BoxLayout(toolbar, BoxLayout.Y_AXIS));
    addRowButton = addButton(addRowButtonText);
    multipleButtons.add(addRowButton);
    addColumnButton = addButton(addColumnButtonText);
    multipleButtons.add(addColumnButton);
    clearButton = addButton(Resources.getString("BoardPicker.clear")); //$NON-NLS-1$
    multipleButtons.add(clearButton);
    setAllowMultiple(allowMultiple);
    controls.add(BorderLayout.NORTH, statusLabel);
    JPanel pp = new JPanel();
    pp.add(toolbar);
    controls.add(BorderLayout.WEST, pp);
    slotScroll = new JScrollPane(slotPanel);
    controls.add(BorderLayout.CENTER, slotScroll);
    reset();
  }

  public Dimension getDefaultSlotSize() {
    return psize;
  }

  /**
   * @return the zoom factory at which to display boards when selecting them
   */
  public double getSlotScale() {
    return slotScale;
  }

  public void warn(String s) {
    if (statusLabel != null) {
      statusLabel.setText(s);
    }
  }

  public void addTo(Buildable b) {
    map = (Map) b;
    map.setBoardPicker(this);
    for (Board board : possibleBoards) {
      board.setMap(map);
    }
    if (b instanceof Translatable) {
      getI18nData().setOwningComponent((Translatable) b);
    }
    GameModule.getGameModule().getGameState().addGameSetupStep(this);
  }

  public void build(Element e) {
    if (e == null) {
      Board b = new Board();
      b.build(null);
      b.addTo(this);
    }
    else {
      NodeList l = e.getElementsByTagName(SETUP);
      if (l.getLength() > 0) {
        Element setupEl = (Element) l.item(0);
        defaultSetup = Builder.getText(setupEl);
        Node nextSibling = setupEl.getNextSibling();
        e.removeChild(setupEl);
        Builder.build(e, this);
        e.insertBefore(setupEl, nextSibling);
      }
      else {
        Builder.build(e, this);
      }

      try {
        psize = new Dimension(Integer.parseInt(e.getAttribute(SLOT_WIDTH)),
                              Integer.parseInt(e.getAttribute(SLOT_HEIGHT)));
      }
      catch (NumberFormatException ex) {
        // Use default values if attribute doesn't parse. 
        // Correct value will be written when module is saved.
      }

      try {
        slotScale = Double.valueOf(e.getAttribute(SCALE)).doubleValue();
      }
      catch (NumberFormatException ex) {
        // Use default values if attribute doesn't parse. 
        // Correct value will be written when module is saved.
      }

      try {
        maxColumns = Integer.parseInt(e.getAttribute(MAX_COLUMNS));
      }
      catch (NumberFormatException ex) {
        // Use default values if attribute doesn't parse.
        // Correct value will be written when module is saved.
        maxColumns = 0;
      }

      String value = e.getAttribute(DIALOG_TITLE);
      if (value != null && value.length() > 0) {
        title = value;
      }
      /*
       * 'Add Row' and 'Add Column' text are no longer configurable, just use the standard (possibly translated) text
       * 
       * value = e.getAttribute(ADD_ROW_BUTTON_TEXT); if (value != null && value.length() > 0) { addRowButtonText =
       * value; } value = e.getAttribute(ADD_COLUMN_BUTTON_TEXT); if (value != null && value.length() > 0) {
       * addColumnButtonText = value; }
       */
      value = e.getAttribute(BOARD_PROMPT);
      if (value != null && value.length() > 0) {
        boardPrompt = value;
      }
      // Record attributes for later translation
      Localization.getInstance().saveTranslatableAttribute(this, BOARD_PROMPT, boardPrompt);
      Localization.getInstance().saveTranslatableAttribute(this, DIALOG_TITLE, title);
    }
  }

  public void validate(Buildable target, ValidationReport report) {
    if (possibleBoards.isEmpty()) {
      report.addWarning(Resources.getString("BoardPicker.must_define", ConfigureTree.getConfigureName(map))); //$NON-NLS-1$
    }
    HashSet<String> names = new HashSet<String>();
    for (Board b : possibleBoards) {
      if (names.contains(b.getName())) {
        report.addWarning(Resources.getString("BoardPicker.more_than_one", b.getName(), ConfigureTree.getConfigureName(map))); //$NON-NLS-1$
      }
      names.add(b.getName());
      if (b.getName() == null) {
        report.addWarning(Resources.getString("BoardPicker.no_name",ConfigureTree.getConfigureName(map)));
      }
      b.validate(b, report);
    }
  }

  private String getDefaultSetup() {
    String s = defaultSetup;
    if (defaultSetup == null || defaultSetup.length() == 0) {
      if (possibleBoards.size() == 1) {
        Board b = possibleBoards.get(0);
        if (!"true".equals(b.getAttributeValueString(Board.REVERSIBLE))) { //$NON-NLS-1$
          s = encode(new SetBoards(this, Collections.singletonList(b)));
        }
      }
    }
    return s;
  }

  /**
   * Add a board to the list of those available for the user to choose from
   */
  public void add(Buildable b) {
    if (b instanceof Board) {
      possibleBoards.add((Board) b);
    }
    if (b instanceof Translatable) {
      ((Translatable) b).getI18nData().setOwningComponent(this);
    }
  }

  /**
   * Remove a board from the list of those available for the user to choose from
   */
  public void remove(Buildable b) {
    if (b instanceof Board) {
      possibleBoards.remove(b);
    }
  }

  public void removeFrom(Buildable parent) {
    GameModule.getGameModule().getGameState().removeGameSetupStep(this);
  }

  public static String getConfigureTypeName() {
    return Resources.getString("Editor.BoardPicker.component_type"); //$NON-NLS-1$
  }

  public String getConfigureName() {
    return null;
  }

  public String getBoardDelimiter() {
    return "bd\t"; //$NON-NLS-1$
  }

  public HelpFile getHelpFile() {
    return null;
  }

  public Configurer getConfigurer() {
    return new Config();
  }

  public Configurable[] getConfigureComponents() {
    return possibleBoards.toArray(new Configurable[possibleBoards.size()]);
  }

  public Class[] getAllowableConfigureComponents() {
    return new Class[]{Board.class};
  }

  public void addPropertyChangeListener(java.beans.PropertyChangeListener l) {
  }

  public void setBoards(Collection<Board> c) {
    reset();
    for (Board b : c) {
      if (b.relativePosition().x > nx - 1)
        addColumn();
      if (b.relativePosition().y > ny - 1)
        addRow();
    }
    for (Board b : c) {
      getSlot(b.relativePosition().x + nx * b.relativePosition().y).setBoard(b);
    }
  }

  /** @deprecated Use {@link #setBoards(Collection<Board>)} instead. */
  @Deprecated
  @SuppressWarnings("unchecked")
  public void setBoards(Enumeration bdEnum) {
    setBoards(Collections.list(bdEnum));
  }

  protected void selectBoards(Component c) {
    reset();
    final JDialog d = new JDialog((Frame) null, true);
    d.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
    final Box b = Box.createVerticalBox();
    final Box buttons = Box.createHorizontalBox();
    final JButton ok = new JButton(Resources.getString(Resources.OK));
    ok.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        final List<Board> l = getBoardsFromControls();
        defaultSetup = l.isEmpty() ? null : encode(new SetBoards(BoardPicker.this, l));
        d.dispose();
      }
    });
    buttons.add(ok);
    final JButton cancel = new JButton(Resources.getString(Resources.CANCEL));
    cancel.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        d.dispose();
      }
    });
    buttons.add(cancel);
    b.add(controls);
    b.add(buttons);
    d.add(b);
    d.pack();
    d.setLocationRelativeTo(c);
    d.setVisible(true);
    currentBoards = new ArrayList<Board>(getBoardsFromControls());
  }

  /**
   * @return a Collection of boards that have been selected either by the user via the dialog or from reading a savefile
   */
  public Collection<Board> getSelectedBoards() {
    if (currentBoards == null) {
      List<Board> empty = Collections.emptyList();
      return empty;
    }
    else {
      return Collections.unmodifiableCollection(currentBoards);
    }
  }

  /**
   * @return an Enumeration of boards that have been selected either by the user via the dialog or from reading a
   *         savefile
   * @deprecated Use {@link #getSelectedBoards()} instead.
   */
  @Deprecated
  public Enumeration getCurrentBoards() {
    return Collections.enumeration(getSelectedBoards());
  }

  /**
   * @return an array of the names of all boards from which the user may choose
   */
  public String[] getAllowableBoardNames() {
    final ArrayList<String> s = new ArrayList<String>(possibleBoards.size());
    for (Board b : possibleBoards) {
      s.add(b.getName());
    }
    return s.toArray(new String[s.size()]);
  }

  public String[] getAllowableLocalizedBoardNames() {
    final ArrayList<String> s = new ArrayList<String>(possibleBoards.size());
    for (Board b : possibleBoards) {
      s.add(b.getLocalizedName());
    }
    return s.toArray(new String[s.size()]);
  }

  /**
   * @return a Board with the given name.
   */
  public Board getBoard(String boardName) {
    return getBoard(boardName, false);
  }

  public Board getLocalizedBoard(String localizedBoardName) {
    return getBoard(localizedBoardName, true);
  }

  protected Board getBoard(String boardName, boolean localized) {
    for (Board b : possibleBoards) {
      final String checkName = localized ? b.getLocalizedName() : b.getName();
      if (checkName.equals(boardName)) {
        return b;
      }
    }
    warn(Resources.getString("BoardPicker.board_not_found", boardName)); //$NON-NLS-1$
    return null;
  }

  /**
   * When starting a game, check to see if any boards have been specified (via an encoded {@link Command}. If not, show
   * a dialog to prompt the user for boards. When ending a game, clear the selected boards
   */
  public void setup(boolean show) {
    if (show) {
      if (currentBoards == null) {
        final String setup = getDefaultSetup();
        if (setup != null) {
          final Command c = decode(setup);
          if (c != null) {
            c.execute();
          }
        }
      }
      map.setBoards(getSelectedBoards());
    }
    else {
      currentBoards = null;
    }
  }

  public void finish() {
    currentBoards = new ArrayList<Board>(getBoardsFromControls());
    map.setBoards(getSelectedBoards());
  }

  public Component getControls() {
    reset();
    return controls;
  }

  public String getStepTitle() {
    return title;
  }

  public boolean isFinished() {
    return currentBoards != null || getDefaultSetup() != null;
  }

  /**
   * The restore command of a BoardPicker, when executed, sets the boards of its {@link Map} to
   * {@link #getSelectedBoards}
   */
  public Command getRestoreCommand() {
    return new SetBoards(this, currentBoards);
  }

  protected JButton addButton(String s) {
    return addButton(s, -1);
  }

  protected JButton addButton(String s, int index) {
    JButton b = new JButton(s);
    b.addActionListener(this);
    toolbar.add(b, null, index);
    return b;
  }

  protected void addRow() {
    slotPanel.setLayout(new GridLayout(++ny, nx));
    for (int i = 0; i < nx; ++i) {
      slotPanel.add(new BoardSlot(this, boardPrompt), -1);
    }
    slotPanel.revalidate();
  }

  protected void addColumn() {
    slotPanel.setLayout(new GridLayout(ny, ++nx));
    for (int j = 0; j < ny; ++j) {
      slotPanel.add(new BoardSlot(this, boardPrompt), (j + 1) * nx - 1);
    }
    slotPanel.revalidate();
  }

  public void actionPerformed(ActionEvent e) {
    if (addColumnButton == e.getSource()) {
      if (maxColumns == 0 || nx < maxColumns) {
        addColumn();
      }
      else {
        addRow();
      }
    }
    else if (addRowButton == e.getSource()) {
      addRow();
    }
    else if (clearButton == e.getSource()) {
      reset();
    }
  }

  /**
   * @deprecated Use {@link #getBoardsFromControls()}.
   */
  @Deprecated
  public Vector<Board> pickBoards() {
    return new Vector<Board>(getBoardsFromControls());
  }

  /**
   * Return the list of boards as specified in the current controls
   * 
   * @return
   */
  public List<Board> getBoardsFromControls() {
    ArrayList<Board> boardList = new ArrayList<Board>();
    if (toolbar != null) {
      // Adjust the bounds of each board according to its relative position
      for (int i = 0; i < nx; ++i) {
        for (int j = 0; j < ny; ++j) {
          BoardSlot slot = getSlot(i + nx * j);
          if (slot != null) {
            Board b = slot.getBoard();
            if (b != null) {
              b.relativePosition().move(i, j);
              boardList.add(b);
            }
          }
        }
      }
    }
    return boardList;
  }

  public void reset() {
    if (toolbar == null) {
      initComponents();
    }
    else {
      warn(""); //$NON-NLS-1$
      removeAllBoards();
      slotPanel.add(new BoardSlot(this, boardPrompt), 0);
    }
    controls.revalidate();
  }

  public int getRowCount() {
    return ny;
  }

  public int getColumnCount() {
    return ny;
  }

  public BoardSlot getNeighbor(BoardSlot slot, int dx, int dy) {
    int x = -1, y = -1;
    for (int i = 0; i < nx; ++i) {
      for (int j = 0; j < ny; ++j) {
        if (getSlot(i + j * nx) == slot) {
          x = i;
          y = j;
          break;
        }
        if (x >= 0 && y >= 0) {
          break;
        }
      }
    }
    x += dx;
    y += dy;
    if (x < 0 || x >= nx || y < 0 || y >= ny) {
      return null;
    }
    int index = x + y * nx;
    if (index < 0 || index >= nx * ny) {
      return null;
    }
    return getSlot(index);
  }

  public BoardSlot getSlot(int i) {
    return i >= 0 && i < slotPanel.getComponentCount() ? (BoardSlot) slotPanel.getComponent(i) : null;
  }

  public void repaintAll() {
    for (int i = 0; i < nx; ++i) {
      for (int j = 0; j < ny; ++j) {
        getSlot(i + nx * j).repaint();
      }
    }
  }

  protected void removeAllBoards() {
    slotPanel.removeAll();
    slotPanel.setLayout(new GridLayout(1, 1));
    nx = ny = 1;
  }

  /**
   * @return true if multiple boards per map window are allowed
   */
  public boolean isAllowMultiple() {
    return allowMultiple;
  }

  public void setAllowMultiple(boolean val) {
    allowMultiple = val;
    if (multipleButtons != null) {
      for (JButton b : multipleButtons) {
        b.setVisible(allowMultiple);
      }
    }
  }

  public Element getBuildElement(Document doc) {
    final Element el = doc.createElement(getClass().getName());
    el.setAttribute(SLOT_WIDTH, String.valueOf(psize.width));
    el.setAttribute(SLOT_HEIGHT, String.valueOf(psize.height));
    el.setAttribute(SCALE, String.valueOf(getSlotScale()));
    el.setAttribute(DIALOG_TITLE, title);
    el.setAttribute(ADD_ROW_BUTTON_TEXT, addRowButtonText);
    el.setAttribute(ADD_COLUMN_BUTTON_TEXT, addColumnButtonText);
    el.setAttribute(BOARD_PROMPT, boardPrompt);
    if (maxColumns > 0) {
      el.setAttribute(MAX_COLUMNS, String.valueOf(maxColumns));
    }
    if (defaultSetup != null) {
      Element setupEl = doc.createElement(SETUP);
      setupEl.appendChild(doc.createTextNode(defaultSetup));
      el.appendChild(setupEl);
    }
    for (Board b : possibleBoards) {
      el.appendChild(b.getBuildElement(doc));
    }
    return el;
  }

  public Command decode(String command) {
    if (command.startsWith(map.getId() + ID) ||
        command.startsWith(map.getConfigureName() + ID)) {
      ArrayList<Board> bds = new ArrayList<Board>();
      SequenceEncoder.Decoder st = new SequenceEncoder.Decoder(command, '\t');
      st.nextToken();
      while (st.hasMoreTokens()) {
        SequenceEncoder.Decoder st2 = new SequenceEncoder.Decoder(st.nextToken(), '/');
        String name = st2.nextToken();
        boolean reversed = false;
        if (st2.hasMoreTokens()) {
          reversed = "rev".equals(st2.nextToken()); //$NON-NLS-1$
        }
        Point p = new Point(st.nextInt(0), st.nextInt(0));
        Board b = getBoard(name);
        if (b != null) {
          if (bds.contains(b)) {
            b = b.copy();
          }
          b.setReversed(reversed);
          b.relativePosition().move(p.x, p.y);
          bds.add(b);
        }
      }
      return new SetBoards(this, bds);
    }
    else {
      return null;
    }
  }

  public String encode(Command c) {
    if (c instanceof SetBoards && map != null && ((SetBoards) c).target == this) {
      final SequenceEncoder se =
        new SequenceEncoder(map.getIdentifier() + ID, '\t');
      final List<Board> bds = ((SetBoards) c).boards;
      if (bds != null) {
        for (Board b : bds) {
          if (b.getName() != null) {
            final SequenceEncoder se2 = new SequenceEncoder(b.getName(), '/');
            if (b.isReversed()) {
              se2.append("rev"); //$NON-NLS-1$
            }
            se.append(se2.getValue())
              .append(b.relativePosition().x)
              .append(b.relativePosition().y);
          }
        }
      }
      return se.getValue();
    }
    else {
      return null;
    }
  }

  public static class SetBoards extends Command {
    private BoardPicker target;
    private List<Board> boards;

    public SetBoards(BoardPicker picker, List<Board> bds) {
      target = picker;
      boards = bds;
    }

    /** @deprecated Use {@link #SetBoards(BoardPicker,List<Board>)}. */
    @Deprecated
    @SuppressWarnings("unchecked")
    public SetBoards(BoardPicker target, Vector boards) {
      this.target = target;
      this.boards = boards;
    }

    protected void executeCommand() {
      target.currentBoards = boards;
      if (GameModule.getGameModule().getGameState().isGameStarted()) {
        target.map.setBoards(target.getSelectedBoards());
        target.map.getView().revalidate();
      }
    }

    protected Command myUndoCommand() {
      return null;
    }
  }

  private class Config extends Configurer {
    private JPanel controls;
    private JButton selectButton;
    private IntConfigurer width;
    private IntConfigurer height;
    private DoubleConfigurer scale;
    private StringConfigurer title;
    private StringConfigurer prompt;

    public Config() {
      super(null, null);
      controls = new JPanel();
      controls.setLayout(new BoxLayout(controls, BoxLayout.Y_AXIS));
      title = new StringConfigurer(null, Resources.getString("Editor.BoardPicker.dialog_title"), BoardPicker.this.title); //$NON-NLS-1$
      title.addPropertyChangeListener(new PropertyChangeListener() {
        public void propertyChange(PropertyChangeEvent evt) {
          if (evt.getNewValue() != null) {
            BoardPicker.this.title = (String) evt.getNewValue();
          }
        }
      });
      controls.add(title.getControls());
      prompt = new StringConfigurer(null, Resources.getString("Editor.BoardPicker.board_prompt"), BoardPicker.this.boardPrompt); //$NON-NLS-1$
      prompt.addPropertyChangeListener(new PropertyChangeListener() {
        public void propertyChange(PropertyChangeEvent evt) {
          if (evt.getNewValue() != null) {
            BoardPicker.this.boardPrompt = (String) evt.getNewValue();
          }
        }
      });
      controls.add(prompt.getControls());
      scale = new DoubleConfigurer(null, Resources.getString("Editor.BoardPicker.cell_scale_factor"), new Double(slotScale)); //$NON-NLS-1$
      scale.addPropertyChangeListener(new PropertyChangeListener() {
        public void propertyChange(PropertyChangeEvent evt) {
          if (evt.getNewValue() != null) {
            slotScale = ((Double) evt.getNewValue()).doubleValue();
          }
        }
      });
      controls.add(scale.getControls());
      width = new IntConfigurer(null, Resources.getString("Editor.BoardPicker.cell_width"), new Integer(psize.width)); //$NON-NLS-1$
      width.addPropertyChangeListener(new PropertyChangeListener() {
        public void propertyChange(PropertyChangeEvent evt) {
          if (evt.getNewValue() != null) {
            psize.width = ((Integer) evt.getNewValue()).intValue();
          }
        }
      });
      controls.add(width.getControls());
      height = new IntConfigurer(null, Resources.getString("Editor.BoardPicker.cell_height"), new Integer(psize.height)); //$NON-NLS-1$
      height.addPropertyChangeListener(new PropertyChangeListener() {
        public void propertyChange(PropertyChangeEvent evt) {
          if (evt.getNewValue() != null) {
            psize.height = ((Integer) evt.getNewValue()).intValue();
          }
        }
      });
      controls.add(height.getControls());
      selectButton = new JButton(Resources.getString("BoardPicker.select_default")); //$NON-NLS-1$
      selectButton.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          selectBoards(e.getSource() instanceof Component ? (Component) e.getSource() : null);
        }
      });
      controls.add(selectButton);
    }

    public Component getControls() {
      return controls;
    }

    public String getValueString() {
      return null;
    }

    public void setValue(String s) {
    }
  }

  /*
   * Record which attributes are translatable
   */
  public ComponentI18nData getI18nData() {
    if (myI18nData == null) {
      myI18nData = new ComponentI18nData(this, "", null, //$NON-NLS-1$
          new String[]{DIALOG_TITLE, BOARD_PROMPT}, new boolean[]{true, true},
          new String[]{Resources.getString("Editor.BoardPicker.dialog_title"), Resources.getString("Editor.BoardPicker.board_prompt") //$NON-NLS-1$ //$NON-NLS-2$
          });
    }
    return myI18nData;
  }

  public void setAttribute(String key, Object value) {
    if (DIALOG_TITLE.equals(key)) {
      title = (String) value;
    }
    else if (BOARD_PROMPT.equals(key)) {
      boardPrompt = (String) value;
    }
  }

  public String getAttributeValueString(String attr) {
    if (DIALOG_TITLE.equals(attr)) {
      return title;
    }
    else if (BOARD_PROMPT.equals(attr)) {
      return boardPrompt;
    }
    return null;
  }

  public void repaint() {
    if (controls != null) {
      controls.repaint();
    }
  }

  @Deprecated
  public void pack() {
  }
}
