/**
* Copyright (C) 2002-2012 The FreeCol Team
*
* This file is part of FreeCol.
*
* FreeCol is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* FreeCol 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with FreeCol. If not, see <http://www.gnu.org/licenses/>.
*/
package net.sf.freecol.client.gui.panel;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
import net.sf.freecol.client.ClientOptions;
import net.sf.freecol.client.FreeColClient;
import net.sf.freecol.client.control.InGameController;
import net.sf.freecol.client.gui.Canvas;
import net.sf.freecol.client.gui.GUI;
import net.sf.freecol.client.gui.ImageLibrary;
import net.sf.freecol.client.gui.i18n.Messages;
import net.sf.freecol.common.model.Colony;
import net.sf.freecol.common.model.Game;
import net.sf.freecol.common.model.HistoryEvent;
import net.sf.freecol.common.model.Modifier;
import net.sf.freecol.common.model.Player;
import net.sf.freecol.common.model.Specification;
import net.sf.freecol.common.model.StringTemplate;
import net.sf.freecol.common.model.Turn;
import net.sf.freecol.common.option.IntegerOption;
import net.sf.freecol.common.option.Option;
import net.sf.freecol.common.resources.ResourceManager;
import org.freecolandroid.repackaged.java.awt.Color;
import org.freecolandroid.repackaged.java.awt.Component;
import org.freecolandroid.repackaged.java.awt.Cursor;
import org.freecolandroid.repackaged.java.awt.Dimension;
import org.freecolandroid.repackaged.java.awt.FlowLayout;
import org.freecolandroid.repackaged.java.awt.Font;
import org.freecolandroid.repackaged.java.awt.Insets;
import org.freecolandroid.repackaged.java.awt.LayoutManager;
import org.freecolandroid.repackaged.java.awt.Point;
import org.freecolandroid.repackaged.java.awt.event.ActionEvent;
import org.freecolandroid.repackaged.java.awt.event.ActionListener;
import org.freecolandroid.repackaged.java.awt.event.KeyEvent;
import org.freecolandroid.repackaged.java.awt.event.MouseAdapter;
import org.freecolandroid.repackaged.javax.swing.AbstractButton;
import org.freecolandroid.repackaged.javax.swing.Action;
import org.freecolandroid.repackaged.javax.swing.BorderFactory;
import org.freecolandroid.repackaged.javax.swing.Icon;
import org.freecolandroid.repackaged.javax.swing.InputMap;
import org.freecolandroid.repackaged.javax.swing.JButton;
import org.freecolandroid.repackaged.javax.swing.JComponent;
import org.freecolandroid.repackaged.javax.swing.JLabel;
import org.freecolandroid.repackaged.javax.swing.JPanel;
import org.freecolandroid.repackaged.javax.swing.JTextArea;
import org.freecolandroid.repackaged.javax.swing.JTextPane;
import org.freecolandroid.repackaged.javax.swing.KeyStroke;
import org.freecolandroid.repackaged.javax.swing.border.Border;
import org.freecolandroid.repackaged.javax.swing.text.AttributeSet;
import org.freecolandroid.repackaged.javax.swing.text.DefaultStyledDocument;
import org.freecolandroid.repackaged.javax.swing.text.Style;
import org.freecolandroid.repackaged.javax.swing.text.StyleConstants;
import org.freecolandroid.repackaged.javax.swing.text.StyleContext;
/**
* Superclass for all panels in FreeCol.
*/
//public abstract class FreeColPanel extends JPanel implements ActionListener {
public abstract class FreeColPanel extends JPanel implements ActionListener {
@SuppressWarnings("unused")
private static final Logger logger = Logger.getLogger(FreeColPanel.class.getName());
protected static final String OK = "OK";
protected static final String HELP = "HELP";
public static final Insets emptyMargin = new Insets(0,0,0,0);
private static final int cancelKeyCode = KeyEvent.VK_ESCAPE;
// The decimal format to use for Modifiers
protected static final DecimalFormat modifierFormat = new DecimalFormat("0.00");
// Font to use for text areas
protected static final Font defaultFont = ResourceManager.getFont("NormalFont", 13f);
// Fonts to use for report headers, etc.
protected static final Font smallHeaderFont = ResourceManager.getFont("HeaderFont", 24f);
protected static final Font mediumHeaderFont = ResourceManager.getFont("HeaderFont", 36f);
protected static final Font bigHeaderFont = ResourceManager.getFont("HeaderFont", 48f);
// How many columns (em-widths) to use in the text area
protected static final int COLUMNS = 20;
// The margin to use.
protected static final int margin = 3;
// The color to use for things the player probably shouldn't do
protected static final Color WARNING_COLOR
= ResourceManager.getColor("lookAndFeel.warning.color");
// The color to use for links
protected static final Color LINK_COLOR
= ResourceManager.getColor("lookAndFeel.link.color");
// The color to use for borders
protected static final Color BORDER_COLOR
= ResourceManager.getColor("lookAndFeel.border.color");
// The borders to use for table cells
public static final Border TOPCELLBORDER =
BorderFactory.createCompoundBorder(BorderFactory.createMatteBorder(1, 0, 1, 1, BORDER_COLOR),
BorderFactory.createEmptyBorder(2, 2, 2, 2));
public static final Border CELLBORDER =
BorderFactory.createCompoundBorder(BorderFactory.createMatteBorder(0, 0, 1, 1, BORDER_COLOR),
BorderFactory.createEmptyBorder(2, 2, 2, 2));
public static final Border LEFTCELLBORDER =
BorderFactory.createCompoundBorder(BorderFactory.createMatteBorder(0, 1, 1, 1, BORDER_COLOR),
BorderFactory.createEmptyBorder(2, 2, 2, 2));
public static final Border TOPLEFTCELLBORDER =
BorderFactory.createCompoundBorder(BorderFactory.createMatteBorder(1, 1, 1, 1, BORDER_COLOR),
BorderFactory.createEmptyBorder(2, 2, 2, 2));
/**
* Registers enter key for a JButton.
*
* @param button
*/
public static void enterPressesWhenFocused(JButton button) {
button.registerKeyboardAction(
button.getActionForKeyStroke(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0, false)), KeyStroke
.getKeyStroke(KeyEvent.VK_ENTER, 0, false), JComponent.WHEN_FOCUSED);
button.registerKeyboardAction(button.getActionForKeyStroke(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0, true)),
KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0, true), JComponent.WHEN_FOCUSED);
}
/**
* Get a JTextPane with default styles.
*
* @return a <code>JTextPane</code> value
*/
public static JTextPane getDefaultTextPane() {
return getDefaultTextPane(null);
}
/**
* Get a JTextPane with default styles and given text.
*
* @param text a <code>String</code> value
* @return a <code>JTextPane</code> value
*/
public static JTextPane getDefaultTextPane(String text) {
DefaultStyledDocument document = new DefaultStyledDocument(styleContext) {
@Override
public Font getFont(AttributeSet attr) {
Font font = ResourceManager.getFont(StyleConstants.getFontFamily(attr),
StyleConstants.getFontSize(attr));
if (font == null) {
return super.getFont(attr);
} else {
int fontStyle = Font.PLAIN;
if (StyleConstants.isBold(attr)) fontStyle |= Font.BOLD;
if (StyleConstants.isItalic(attr)) fontStyle |= Font.ITALIC;
return (fontStyle == Font.PLAIN) ? font : font.deriveFont(fontStyle);
}
}
};
JTextPane textPane = new JTextPane(document);
textPane.setOpaque(false);
textPane.setEditable(false);
textPane.setLogicalStyle(styleContext.getStyle("regular"));
textPane.setText(text);
return textPane;
}
/**
* Return a button suitable for linking to another panel
* (e.g. ColopediaPanel).
*
* @param text a <code>String</code> value
* @param icon an <code>Icon</code> value
* @param action a <code>String</code> value
* @return a <code>JButton</code> value
*/
public static JButton getLinkButton(String text, Icon icon, String action) {
JButton button = new JButton(text, icon);
button.setMargin(emptyMargin);
button.setOpaque(false);
button.setForeground(LINK_COLOR);
button.setAlignmentY(0.8f);
button.setBorder(BorderFactory.createEmptyBorder());
button.setActionCommand(action);
button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
return button;
}
/**
* Returns the default modifier value format.
*
* @return a <code>DecimalFormat</code> value
*/
public static final DecimalFormat getModifierFormat() {
return modifierFormat;
}
/**
* Returns the default header for panels.
*
* @param text a <code>String</code> value
* @return a <code>JLabel</code> value
*/
protected static JLabel getDefaultHeader(String text) {
JLabel header = new JLabel(text, JLabel.CENTER);
header.setFont(bigHeaderFont);
header.setBorder(BorderFactory.createEmptyBorder(20, 0, 20, 0));
return header;
}
/**
* Returns a text area with standard settings suitable for use in FreeCol
* dialogs.
*
* @param text The text to display in the text area.
* @return a text area with standard settings suitable for use in FreeCol
* dialogs.
*/
protected static JTextArea getDefaultTextArea(String text) {
return getDefaultTextArea(text, COLUMNS);
}
/**
* Returns a text area with standard settings suitable for use in FreeCol
* dialogs.
*
* @param text The text to display in the text area.
* @param columns an <code>int</code> value
* @return a text area with standard settings suitable for use in FreeCol
* dialogs.
*/
protected static JTextArea getDefaultTextArea(String text, int columns) {
JTextArea textArea = new JTextArea(text);
textArea.setColumns(columns);
textArea.setOpaque(false);
textArea.setLineWrap(true);
textArea.setWrapStyleWord(true);
textArea.setFocusable(false);
textArea.setFont(defaultFont);
// necessary because of resizing
textArea.setSize(textArea.getPreferredSize());
return textArea;
}
protected boolean editable = true;
protected JButton okButton = new JButton(Messages.message("ok"));
private FreeColClient freeColClient;
private GUI gui;
protected static StyleContext styleContext = new StyleContext();
static {
Style defaultStyle = StyleContext.getDefaultStyleContext()
.getStyle(StyleContext.DEFAULT_STYLE);
Style regular = styleContext.addStyle("regular", defaultStyle);
StyleConstants.setFontFamily(regular, "NormalFont");
StyleConstants.setFontSize(regular, 13);
Style buttonStyle = styleContext.addStyle("button", regular);
StyleConstants.setForeground(buttonStyle, LINK_COLOR);
Style right = styleContext.addStyle("right", regular);
StyleConstants.setAlignment(right, StyleConstants.ALIGN_RIGHT);
}
/**
* Constructor.
*
* @param parent <code>Canvas</code>
*/
public FreeColPanel(FreeColClient freeColClient, GUI gui) {
this(freeColClient, gui, new FlowLayout());
}
/**
* Default constructor.
*
* @param parent The <code>Canvas</code> all panels belong to.
* @param layout The <code>LayoutManager</code> to be used.
*/
public FreeColPanel(FreeColClient freeColClient, GUI gui, LayoutManager layout) {
super(layout);
this.freeColClient = freeColClient;
this.gui = gui;
setFocusCycleRoot(true);
setBorder(FreeColImageBorder.imageBorder);
// See the message of Ulf Onnen for more information about the presence
// of this fake mouse listener.
addMouseListener(new MouseAdapter() {
});
okButton.setActionCommand(OK);
okButton.addActionListener(this);
enterPressesWhenFocused(okButton);
setCancelComponent(okButton);
}
/**
* This function analyses an event and calls the right methods to take care
* of the user's requests.
*
* @param event The incoming ActionEvent.
*/
public void actionPerformed(ActionEvent event) {
String command = event.getActionCommand();
if (command.equals(OK)) {
getGUI().removeFromCanvas(this);
}
}
/**
* Add a routine to be called when this panel closes.
* Triggered by notifyClose() above.
*
* @param runnable Some code to run on close.
*/
public void addClosingCallback(final Runnable runnable) {
final FreeColPanel fcp = this;
addPropertyChangeListener(new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent e) {
if ("closing".equals(e.getPropertyName())) {
runnable.run();
fcp.removePropertyChangeListener(this);
}
}
});
}
/**
* Returns the saved position of this panel, null by default.
*
* @return a <code>Point</code> value
*/
public Point getSavedPosition() {
try {
return new Point(getInteger(".x"), getInteger(".y"));
} catch(Exception e) {
return null;
}
}
/**
* Notify this panel that it is being removed from the
* canvas. Saves the current size and position of the panel to the
* ClientOptions, which are included in the savegame file.
*
* @see Canvas#remove(Component)
*/
public void notifyClose() {
firePropertyChange("closing", false, true);
// Component frame = SwingUtilities.getAncestorOfClass(JInternalFrame.class, this);
// if (frame != null
// && getClientOptions() != null
// && getClientOptions().getBoolean("model.option.rememberPanelPositions")) {
// saveInteger(".x", frame.getLocation().x);
// saveInteger(".y", frame.getLocation().y);
// }
saveSize();
}
/**
* The OK button requests focus.
*
*/
@Override
public void requestFocus() {
okButton.requestFocus();
}
/**
* Make the given button the CANCEL button.
*
* @param cancelButton an <code>AbstractButton</code> value
*/
public void setCancelComponent(AbstractButton cancelButton) {
if (cancelButton == null) {
throw new NullPointerException();
}
InputMap inputMap = getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
inputMap.put(KeyStroke.getKeyStroke(cancelKeyCode, 0, true), "release");
Action cancelAction = cancelButton.getAction();
getActionMap().put("release", cancelAction);
}
protected JButton createColonyButton(Colony colony) {
JButton button = getLinkButton(colony.getName(), null, colony.getId());
button.addActionListener(this);
return button;
}
/**
* Get the <code>Canvas</code> value.
*
* @return a <code>Canvas</code> value
*/
protected final Canvas getCanvas() {
return gui.getCanvas();
}
/**
* Return the client's <code>ClientOptions</code>.
*
* @return a <code>ClientOptions</code> value
*/
protected ClientOptions getClientOptions() {
return freeColClient == null ? null : freeColClient.getClientOptions();
}
/**
* Describe <code>getController</code> method here.
*
* @return an <code>InGameController</code> value
*/
protected InGameController getController() {
return freeColClient.getInGameController();
}
/**
* Returns the <code>Turn</code>s during which a Player's
* FoundingFathers were elected to the Continental Congress
*
* @return a <code>Turn</code> value
*/
protected Map<String, Turn> getElectionTurns() {
Map<String, Turn> result = new HashMap<String, Turn>();
if (!getMyPlayer().getFathers().isEmpty()) {
for (HistoryEvent event : getMyPlayer().getHistory()) {
if (event.getEventType() == HistoryEvent.EventType.FOUNDING_FATHER) {
result.put(event.getReplacement("%father%").getId(), event.getTurn());
}
}
}
return result;
}
/**
* Gets the FreeColClient from the canvas.
*
* @return A current <code>FreeColClient</code>.
*/
protected FreeColClient getFreeColClient() {
return freeColClient;
}
/**
* Describe <code>getGame</code> method here.
*
* @return a <code>Game</code> value
*/
protected Game getGame() {
return freeColClient.getGame();
}
protected GUI getGUI() {
return gui;
}
/**
* Returns the ImageLibrary.
*
* @return the ImageLibrary.
*/
protected ImageLibrary getLibrary() {
return gui.getImageLibrary();
}
/**
* Describe <code>getMyPlayer</code> method here.
*
* @return a <code>Player</code> value
*/
protected Player getMyPlayer() {
return freeColClient.getMyPlayer();
}
/**
* Returns the saved size of this panel, null by default.
*
* @return a <code>Dimension</code> value
*/
protected final Dimension getSavedSize() {
try {
return new Dimension(getInteger(".w"), getInteger(".h"));
} catch(Exception e) {
return null;
}
}
/**
* Return the player's Colonies, sorted according to player's
* <code>ClientOptions</code>.
*
* @return a sorted List of Colonies
*/
protected List<Colony> getSortedColonies() {
return freeColClient.getClientOptions()
.getSortedColonies(getMyPlayer());
}
/**
* Describe <code>getSpecification</code> method here.
*
* @return a <code>Specification</code> value
*/
protected Specification getSpecification() {
return freeColClient.getGame().getSpecification();
}
/**
* Checks if this panel is editable
* @return boolean
*/
protected boolean isEditable() {
return editable;
}
/**
* Return a JLabel with Messages.message(key) as text.
*
* @param key a <code>String</code> value
* @return a <code>JLabel</code> value
*/
protected JLabel localizedLabel(String key) {
return new JLabel(Messages.message(key));
}
/**
* Return a JLabel with Messages.localize(template) as text.
*
* @param template a <code>StringTemplate</code> value
* @return a <code>JLabel</code> value
*/
protected JLabel localizedLabel(StringTemplate template) {
return new JLabel(Messages.message(template));
}
/**
* Set preferred size to saved size, or to the given
* <code>Dimension</code> if no saved size was found. Call this
* method in the constructor of a FreeColPanel in order to
* remember its size and position.
*
* @param d a <code>Dimension</code> value
*/
protected void restoreSavedSize(Dimension d) {
Dimension size = getSavedSize();
if (size == null) {
size = d;
saveSize(size);
}
if (!getPreferredSize().equals(size)) {
setPreferredSize(size);
}
}
/**
* Set preferred size to saved size, or to [w, h] if no saved size
* was found. Call this method in the constructor of a
* FreeColPanel in order to remember its size and position.
*
* @param w an <code>int</code> value
* @param h an <code>int</code> value
*/
protected void restoreSavedSize(int w, int h) {
restoreSavedSize(new Dimension(w, h));
}
/**
* Set the <code>Editable</code> value.
*
* @param newEditable boolean, the new Editable value
*/
protected void setEditable(boolean newEditable) {
this.editable = newEditable;
}
/**
* Sort the given modifiers according to type.
*
* @param result Set of <code>Modifier</code>
* @return a sorted Set of <code>Modifier</code>
*/
protected Set<Modifier> sortModifiers(Set<Modifier> result) {
EnumMap<Modifier.Type, List<Modifier>> modifierMap =
new EnumMap<Modifier.Type, List<Modifier>>(Modifier.Type.class);
for (Modifier.Type type : Modifier.Type.values()) {
modifierMap.put(type, new ArrayList<Modifier>());
}
for (Modifier modifier : result) {
modifierMap.get(modifier.getType()).add(modifier);
}
Set<Modifier> sortedResult = new LinkedHashSet<Modifier>();
for (Modifier.Type type : Modifier.Type.values()) {
sortedResult.addAll(modifierMap.get(type));
}
return sortedResult;
}
/**
* Return an <code>int</code> associated with the name of the
* panel's class plus the given key from the saved ClientOptions.
*
* @param key a <code>String</code> value
* @return an <code>int</code> value
*/
private int getInteger(String key) {
return freeColClient.getClientOptions()
.getInteger(getClass().getName() + key);
}
/**
* Save an <code>int</code> value to the saved ClientOptions,
* using the name of the panel's class plus the given key as and
* ID.
*
* @param key a <code>String</code> value
* @param value an <code>int</code> value
*/
private void saveInteger(String key, int value) {
if (freeColClient != null
&& freeColClient.getClientOptions() != null) {
Option o = freeColClient.getClientOptions()
.getOption(getClass().getName() + key);
if (o == null) {
IntegerOption io = new IntegerOption(getClass().getName() + key);
io.setValue(value);
freeColClient.getClientOptions().add(io);
} else if (o instanceof IntegerOption) {
((IntegerOption) o).setValue(value);
}
}
}
/**
* Save the current size of the panel.
*
*/
private void saveSize() {
saveSize(getSize());
}
/**
* Save the given Dimension as size of the panel.
*
* @param size a <code>Dimension</code> value
*/
private void saveSize(Dimension size) {
saveInteger(".w", size.width);
saveInteger(".h", size.height);
}
}