/* * BiographyInfoPane.java * Copyright 2011 Connor Petty <cpmeister@users.sourceforge.net> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * Created on Aug 26, 2011, 7:55:47 PM */ package pcgen.gui2.tabs.bio; import java.awt.Dimension; 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.ItemEvent; import java.awt.event.ItemListener; import java.util.ArrayList; import java.util.Arrays; import java.util.EnumMap; import java.util.EnumSet; import java.util.List; import java.util.Map; import javax.swing.AbstractAction; import javax.swing.Box; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JFormattedTextField; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextField; import javax.swing.ScrollPaneConstants; import javax.swing.SwingConstants; import javax.swing.border.EmptyBorder; import org.apache.commons.lang3.StringUtils; import pcgen.cdom.base.Constants; import pcgen.cdom.enumeration.BiographyField; import pcgen.facade.core.AlignmentFacade; import pcgen.facade.core.CharacterFacade; import pcgen.facade.core.DeityFacade; import pcgen.facade.core.GenderFacade; import pcgen.facade.core.HandedFacade; import pcgen.facade.core.SimpleFacade; import pcgen.facade.util.event.ListEvent; import pcgen.facade.util.event.ListListener; import pcgen.facade.util.ListFacade; import pcgen.gui2.tabs.CharacterInfoTab; import pcgen.gui2.tabs.TabTitle; import pcgen.gui2.tabs.models.CharacterComboBoxModel; import pcgen.gui2.tabs.models.FormattedFieldHandler; import pcgen.gui2.tabs.models.TextFieldHandler; import pcgen.gui2.util.ScrollablePanel; import pcgen.system.LanguageBundle; /** * The Class {@code BiographyInfoPane} is a panel within the Description * tab. It contains biography and physical description fields which may be * updated by the user. * * <br> * * @author Connor Petty <cpmeister@users.sourceforge.net> */ @SuppressWarnings("serial") public class BiographyInfoPane extends JPanel implements CharacterInfoTab { private static final String ALL_COMMAND = "ALL"; //$NON-NLS-1$ private static final String NONE_COMMAND = "NONE"; //$NON-NLS-1$ private static final JTextField templateTextField = new JTextField( "PrototypeDisplayText"); //$NON-NLS-1$; /** The fields that we always display */ private static final EnumSet<BiographyField> defaultBioFieds = EnumSet .range(BiographyField.NAME, BiographyField.WEIGHT); private final TabTitle title = new TabTitle( LanguageBundle.getString("in_descBiography"), null); //$NON-NLS-1$ private final JButton allButton; private final JButton noneButton; private final JPanel itemsPanel; private JButton addCustomItemButton; private JScrollPane detailsScroll; /** * Create a new instance of BiographyInfoPane. */ public BiographyInfoPane() { this.allButton = new JButton(); this.noneButton = new JButton(); this.itemsPanel = new ScrollablePanel(20); initComponents(); } private void initComponents() { setLayout(new GridBagLayout()); Box vbox = Box.createVerticalBox(); allButton.setText(LanguageBundle.getString("in_all")); //$NON-NLS-1$ allButton.setActionCommand(ALL_COMMAND); noneButton.setText(LanguageBundle.getString("in_none")); //$NON-NLS-1$ noneButton.setActionCommand(NONE_COMMAND); Box hbox = Box.createHorizontalBox(); hbox.add(new JLabel(LanguageBundle.getString("in_descCheckItem"))); //$NON-NLS-1$ hbox.add(Box.createRigidArea(new Dimension(5, 0))); hbox.add(allButton); hbox.add(Box.createRigidArea(new Dimension(3, 0))); hbox.add(noneButton); vbox.add(hbox); itemsPanel.setLayout(new GridBagLayout()); itemsPanel.setBorder(new EmptyBorder(8, 5, 8, 5) ); vbox.add(Box.createVerticalStrut(10)); detailsScroll = new JScrollPane(itemsPanel); detailsScroll.setPreferredSize(detailsScroll.getMaximumSize()); detailsScroll.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED); detailsScroll.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED); detailsScroll.setMinimumSize(new Dimension(600, 0)); vbox.add(detailsScroll); vbox.add(Box.createVerticalStrut(10)); hbox = Box.createHorizontalBox(); hbox.add(Box.createHorizontalGlue()); addCustomItemButton = new JButton(); hbox.add(addCustomItemButton); hbox.add(Box.createHorizontalGlue()); vbox.add(hbox); vbox.add(Box.createVerticalGlue()); GridBagConstraints gbc = new GridBagConstraints(); gbc.anchor = GridBagConstraints.NORTHWEST; gbc.weightx = 1; gbc.fill = GridBagConstraints.VERTICAL; gbc.weighty = 1; gbc.insets = new Insets(5, 5, 5, 5); add(vbox, gbc); } @Override public ModelMap createModels(final CharacterFacade character) { ModelMap models = new ModelMap(); models.put(ItemHandler.class, new ItemHandler(character)); models.put(AddCustomAction.class, new AddCustomAction(character)); return models; } @Override public void restoreModels(ModelMap models) { models.get(ItemHandler.class).install(this); addCustomItemButton.setAction(models.get(AddCustomAction.class)); } @Override public void storeModels(ModelMap models) { models.get(ItemHandler.class).uninstall(this); } @Override public TabTitle getTabTitle() { return title; } private class ItemHandler implements ListListener<BiographyField> { private final ListFacade<BiographyField> customFields; private List<BioItem> bioItems = new ArrayList<>(); private Map<BiographyField, BioItem> customFieldMap = new EnumMap<>( BiographyField.class); private final CharacterFacade character; private BiographyInfoPane detailsPane; public ItemHandler(CharacterFacade character2) { this.character = character2; bioItems.add(new NameItem(character)); bioItems.add(new PlayerNameItem(character)); bioItems.add(new GenderItem(character)); bioItems.add(new HandedItem(character)); bioItems.add(new AlignmentItem(character)); bioItems.add(new DeityItem(character)); bioItems.add(new AgeItem(character)); bioItems.add(new SkinColorItem(character)); bioItems.add(new HairColorItem(character)); bioItems.add(new HairStyleItem(character)); bioItems.add(new EyeColorItem(character)); bioItems.add(new HeightItem(character)); bioItems.add(new WeightItem(character)); customFields = character.getDescriptionFacade().getCustomBiographyFields(); for (BiographyField field : customFields) { BioItem item; if (field == BiographyField.REGION) { item = new RegionItem(character); } else { item = new BiographyFieldBioItem(field, character); } customFieldMap.put(field, item); } } public void install(BiographyInfoPane parent) { detailsPane = parent; itemsPanel.removeAll(); // for (BioItem bioItem : bioItems) { bioItem.addComponents(itemsPanel); bioItem.install(parent); } for (BioItem bioItem : customFieldMap.values()) { bioItem.addComponents(itemsPanel); bioItem.install(parent); } customFields.addListListener(this); detailsScroll.setPreferredSize(itemsPanel.getPreferredSize()); detailsScroll.invalidate(); } public void uninstall(BiographyInfoPane parent) { for (BioItem bioItem : bioItems) { bioItem.uninstall(parent); } for (BioItem bioItem : customFieldMap.values()) { bioItem.uninstall(parent); } detailsPane = null; customFields.removeListListener(this); } @Override public void elementAdded(ListEvent<BiographyField> e) { BiographyField field = e.getElement(); BioItem bioItem = new BiographyFieldBioItem(field, character); customFieldMap.put(field, bioItem); bioItem.addComponents(itemsPanel); bioItem.install(detailsPane); detailsPane.validate(); detailsScroll.setPreferredSize(itemsPanel.getPreferredSize()); detailsScroll.repaint(); } @Override public void elementRemoved(ListEvent<BiographyField> e) { BiographyField field = e.getElement(); BioItem bioItem = new BiographyFieldBioItem(field, character); customFieldMap.put(field, bioItem); bioItem.uninstall(detailsPane); detailsPane.invalidate(); } @Override public void elementsChanged(ListEvent<BiographyField> e) { BiographyInfoPane parent = detailsPane; uninstall(parent); install(parent); detailsPane.invalidate(); } @Override public void elementModified(ListEvent<BiographyField> e) { // Ignored. } } private static class NameItem extends BioItem { public NameItem(final CharacterFacade character) { super("in_nameLabel", BiographyField.NAME, character); //$NON-NLS-1$ setTextFieldHandler(new TextFieldHandler(new JTextField(30), character.getNameRef()) { @Override protected void textChanged(String text) { character.setName(text); } }); } } private static class PlayerNameItem extends BioItem { public PlayerNameItem(final CharacterFacade character) { super("in_player", BiographyField.PLAYERNAME, character); //$NON-NLS-1$ setTextFieldHandler(new TextFieldHandler(new JTextField(), character.getPlayersNameRef()) { @Override protected void textChanged(String text) { character.setPlayersName(text); } }); } } private static class GenderItem extends BioItem { private CharacterComboBoxModel<GenderFacade> genderModel; public GenderItem(final CharacterFacade character) { super("in_gender", BiographyField.GENDER, character); //$NON-NLS-1$ genderModel = new CharacterComboBoxModel<GenderFacade>() { @Override public void setSelectedItem(Object anItem) { character.setGender((GenderFacade) anItem); } }; genderModel.setReference(character.getGenderRef()); genderModel.setListFacade(character.getAvailableGenders()); setComboBoxModel(genderModel); checkVisible(); } private void checkVisible() { setVisible(genderModel.getSize() != 0); } } private static class HandedItem extends BioItem { private CharacterComboBoxModel<HandedFacade> handsModel; public HandedItem(final CharacterFacade character) { super("in_handString", BiographyField.HANDED, character); //$NON-NLS-1$ handsModel = new CharacterComboBoxModel<HandedFacade>() { @Override public void setSelectedItem(Object anItem) { character.setHanded((HandedFacade) anItem); } }; handsModel.setReference(character.getHandedRef()); handsModel.setListFacade(character.getAvailableHands()); setComboBoxModel(handsModel); checkVisible(); } private void checkVisible() { setVisible(handsModel.getSize() != 0); } } private static class AlignmentItem extends BioItem { public AlignmentItem(final CharacterFacade character) { super("in_alignString", BiographyField.ALIGNMENT, character); //$NON-NLS-1$ CharacterComboBoxModel<AlignmentFacade> alignmentModel = new CharacterComboBoxModel<AlignmentFacade>() { @Override public void setSelectedItem(Object anItem) { character.setAlignment((AlignmentFacade) anItem); } }; alignmentModel.setListFacade(character.getDataSet().getAlignments()); alignmentModel.setReference(character.getAlignmentRef()); setComboBoxModel(alignmentModel); } } private static class DeityItem extends BioItem { public DeityItem(final CharacterFacade character) { super("in_deity", BiographyField.DEITY, character); //$NON-NLS-1$ CharacterComboBoxModel<DeityFacade> deityModel = new CharacterComboBoxModel<DeityFacade>() { @Override public void setSelectedItem(Object anItem) { character.setDeity((DeityFacade) anItem); } }; deityModel.setListFacade(character.getDataSet().getDeities()); deityModel.setReference(character.getDeityRef()); setComboBoxModel(deityModel); } } private static class AgeItem extends BioItem { public AgeItem(final CharacterFacade character) { super("in_age", BiographyField.AGE, character); //$NON-NLS-1$ CharacterComboBoxModel<SimpleFacade> ageModel = new CharacterComboBoxModel<SimpleFacade>() { @Override public void setSelectedItem(Object anItem) { character.setAgeCategory((SimpleFacade) anItem); } }; ageModel.setListFacade(character.getAgeCategories()); ageModel.setReference(character.getAgeCategoryRef()); setComboBoxModel(ageModel); setFormattedFieldHandler(new FormattedFieldHandler(new JFormattedTextField(), character.getAgeRef()) { @Override protected void valueChanged(int value) { character.setAge(value); } }); } } private static class SkinColorItem extends BioItem { public SkinColorItem(final CharacterFacade character) { super("in_appSkintoneColor", BiographyField.SKIN_TONE, character); //$NON-NLS-1$ setTextFieldHandler(new TextFieldHandler(new JTextField(), character.getSkinColorRef()) { @Override protected void textChanged(String text) { character.setSkinColor(text); } }); } } private static class HairColorItem extends BioItem { public HairColorItem(final CharacterFacade character) { super("in_appHairColor", BiographyField.HAIR_COLOR, character); //$NON-NLS-1$ setTextFieldHandler(new TextFieldHandler(new JTextField(), character.getHairColorRef()) { @Override protected void textChanged(String text) { character.setHairColor(text); } }); } } private static class EyeColorItem extends BioItem { public EyeColorItem(final CharacterFacade character) { super("in_appEyeColor", BiographyField.EYE_COLOR, character); //$NON-NLS-1$ setTextFieldHandler(new TextFieldHandler(new JTextField(), character.getEyeColorRef()) { @Override protected void textChanged(String text) { character.setEyeColor(text); } }); } } private static class HeightItem extends BioItem { public HeightItem(final CharacterFacade character) { super("in_height", BiographyField.HEIGHT, character); //$NON-NLS-1$ setTrailingLabel(character.getDataSet().getGameMode().getHeightUnit()); setFormattedFieldHandler(new FormattedFieldHandler(new JFormattedTextField(), character.getHeightRef()) { @Override protected void valueChanged(int value) { character.setHeight(value); } }); } } private static class WeightItem extends BioItem { public WeightItem(final CharacterFacade character) { super("in_weight", BiographyField.WEIGHT, character); //$NON-NLS-1$ setTrailingLabel(character.getDataSet().getGameMode().getWeightUnit()); setFormattedFieldHandler(new FormattedFieldHandler(new JFormattedTextField(), character.getWeightRef()) { @Override protected void valueChanged(int value) { character.setWeight(value); } }); } } private static class RegionItem extends BioItem { public RegionItem(final CharacterFacade character) { super("in_region", BiographyField.REGION, character); //$NON-NLS-1$ final JTextField regionField = new JTextField(); regionField.setEditable(false); setTextFieldHandler(new TextFieldHandler(regionField, character .getDescriptionFacade() .getBiographyField(BiographyField.REGION)) { @Override protected void textChanged(String text) { // Ignored for a non-editable field. } }); } } private static class HairStyleItem extends BiographyFieldBioItem { public HairStyleItem(final CharacterFacade character) { super("in_style", BiographyField.HAIR_STYLE, character); //$NON-NLS-1$ } } /** * The Class {@code BiographyFieldBioItem} manages a row displaying a * textual biography item and allowing it to be edited and suppressed * from output. */ private static class BiographyFieldBioItem extends BioItem { public BiographyFieldBioItem(final String titleKey, final BiographyField field, final CharacterFacade character) { super(titleKey, field, character); setTextFieldHandler(new TextFieldHandler(new JTextField(), character.getDescriptionFacade().getBiographyField(field)) { @Override protected void textChanged(String text) { character.getDescriptionFacade().setBiographyField(field, text); } }); } public BiographyFieldBioItem(final BiographyField field, final CharacterFacade character) { super(field.getIl8nKey(), field, character); setTextFieldHandler(new TextFieldHandler(new JTextField(), character.getDescriptionFacade().getBiographyField(field)) { @Override protected void textChanged(String text) { character.getDescriptionFacade().setBiographyField(field, text); } }); } } private static abstract class BioItem implements ActionListener, ItemListener { private final JLabel label = new JLabel(); private final JCheckBox checkbox = new JCheckBox(); private JComboBox combobox = null; private JTextField textField = null; private JLabel trailinglabel = null; private final BiographyField bioField; private final CharacterFacade character; private TextFieldHandler textFieldHandler; private FormattedFieldHandler formattedFieldHandler; protected BioItem(String text, BiographyField bioField, CharacterFacade character) { this.bioField = bioField; this.character = character; if (text.startsWith("in_")) //$NON-NLS-1$ { label.setText(LanguageBundle.getString(text) + ":"); //$NON-NLS-1$ } else { label.setText(text); } label.setHorizontalAlignment(SwingConstants.RIGHT); if (character != null) { checkbox.setSelected(character.getExportBioField(bioField)); } } public void addComponents(JPanel panel) { GridBagConstraints gbc = new GridBagConstraints(); gbc.anchor = GridBagConstraints.PAGE_START; gbc.gridwidth = 1; gbc.fill = GridBagConstraints.BOTH; panel.add(checkbox, gbc); gbc.insets = new Insets(1, 2, 1, 2); panel.add(label, gbc); int numComponents = 0; numComponents += textField != null ? 1 : 0; numComponents += combobox != null ? 1 : 0; numComponents += trailinglabel != null ? 1 : 0; switch (numComponents) { case 3: gbc.weightx = 0.3333; break; case 2: gbc.weightx = 0.5; break; default: gbc.weightx = 1.0; break; } if (combobox != null) { panel.add(combobox, gbc); } if (trailinglabel == null) { gbc.gridwidth = GridBagConstraints.REMAINDER; } if (textField != null) { panel.add(textField, gbc); } gbc.gridwidth = GridBagConstraints.REMAINDER; if (trailinglabel != null) { panel.add(trailinglabel, gbc); } else if (numComponents < 2) { //We need a filler component so just use the lightweight Box panel.add(Box.createHorizontalGlue(), gbc); } } protected void setTextFieldHandler(TextFieldHandler handler) { if (textField != null) { throw new IllegalStateException("The TextField has already been set"); //$NON-NLS-1$ } this.textField = handler.getTextField(); textFieldHandler = handler; } protected void setFormattedFieldHandler(FormattedFieldHandler handler) { if (textField != null) { throw new IllegalStateException("The TextField has already been set"); //$NON-NLS-1$ } this.textField = handler.getFormattedTextField(); formattedFieldHandler = handler; } protected void setComboBoxModel(CharacterComboBoxModel<?> model) { if (combobox != null) { throw new IllegalStateException("The CharacterComboBoxModel has already been set"); //$NON-NLS-1$ } this.combobox = new JComboBox(model); combobox.setPreferredSize(new Dimension(10, templateTextField.getPreferredSize().height)); } /** * @param text The text to be displayed in a label after the entry fields. */ protected void setTrailingLabel(String text) { if (trailinglabel != null) { throw new IllegalStateException("The trailing label has already been set"); //$NON-NLS-1$ } this.trailinglabel = new JLabel(text); } public void setVisible(boolean visible) { label.setVisible(visible); checkbox.setVisible(visible); if (combobox != null) { combobox.setVisible(visible); } if (textField != null) { textField.setVisible(visible); } if (trailinglabel != null) { trailinglabel.setVisible(visible); } } /** * Installs this BioItem by attaching itself to the buttons. * @param parent The pane holding this item. */ public void install(BiographyInfoPane parent) { parent.allButton.addActionListener(this); parent.noneButton.addActionListener(this); checkbox.addItemListener(this); if (textFieldHandler != null) { textFieldHandler.install(); } if (formattedFieldHandler != null) { formattedFieldHandler.install(); } } /** * Uninstalls this BioItem by removing its listeners from the buttons. * @param parent The pane holding this item. */ public void uninstall(BiographyInfoPane parent) { parent.allButton.removeActionListener(this); parent.noneButton.removeActionListener(this); checkbox.removeItemListener(this); if (textFieldHandler != null) { textFieldHandler.uninstall(); } if (formattedFieldHandler != null) { formattedFieldHandler.uninstall(); } } @Override public void actionPerformed(ActionEvent e) { if (ALL_COMMAND.equals(e.getActionCommand())) { checkbox.setSelected(true); character.setExportBioField(bioField, true); } else if (NONE_COMMAND.equals(e.getActionCommand())) { checkbox.setSelected(false); character.setExportBioField(bioField, false); } } @Override public void itemStateChanged(ItemEvent e) { boolean selected = e.getStateChange() == ItemEvent.SELECTED; character.setExportBioField(bioField, selected); } } /** * The Class {@code AddAction} acts on a user pressing the Add Custom * Details button. */ private class AddCustomAction extends AbstractAction { private final CharacterFacade character; public AddCustomAction(CharacterFacade character) { super(LanguageBundle.getString("in_descAddDetail")); //$NON-NLS-1$ this.character = character; } @Override public void actionPerformed(ActionEvent e) { // Build list of choices List<BiographyField> availFields = new ArrayList<>(Arrays.asList(BiographyField.values())); availFields.removeAll(defaultBioFieds); for (BiographyField field : character.getDescriptionFacade() .getCustomBiographyFields()) { availFields.remove(field); } if (availFields.isEmpty()) { JOptionPane .showMessageDialog( JOptionPane.getFrameForComponent(addCustomItemButton), LanguageBundle.getString("in_descNoMoreDetails"), //$NON-NLS-1$ Constants.APPLICATION_NAME, JOptionPane.INFORMATION_MESSAGE); return; } String fieldNames[] = new String[availFields.size()]; int i = 0; for (BiographyField biographyField : availFields) { fieldNames[i++] = LanguageBundle.getString(biographyField.getIl8nKey()); } // Show dialog to choose fields String s = (String) JOptionPane.showInputDialog( JOptionPane.getFrameForComponent(addCustomItemButton), LanguageBundle.getString("in_descAddFieldMsg"), //$NON-NLS-1$ LanguageBundle.getString("in_descAddFieldTitle"), //$NON-NLS-1$ JOptionPane.QUESTION_MESSAGE, null, fieldNames, fieldNames[0]); // Check if a selection was made if (StringUtils.isEmpty(s)) { return; } // Add the chosen field to the character for (BiographyField field : availFields) { if (s.equals( LanguageBundle.getString(field.getIl8nKey()))) { character.getDescriptionFacade().addCustomBiographyField(field); break; } } } } }