/* * CampaignHistoryInfoPane.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:57:40 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.Rectangle; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.text.NumberFormat; import java.util.ArrayList; import java.util.List; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JFormattedTextField; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextArea; import javax.swing.JTextField; import javax.swing.ScrollPaneConstants; import javax.swing.Scrollable; import javax.swing.SwingUtilities; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.math.NumberUtils; import pcgen.cdom.base.Constants; import pcgen.facade.core.CharacterFacade; import pcgen.facade.core.ChronicleEntryFacade; import pcgen.facade.core.DescriptionFacade; import pcgen.gui2.tabs.CharacterInfoTab; import pcgen.gui2.tabs.TabTitle; import pcgen.gui2.tabs.models.TextFieldListener; import pcgen.gui2.tools.Icons; /** * The CampaignHistoryInfoPane displays a set of chronicles that the user can fill in for his * character. * @author Connor Petty <cpmeister@users.sourceforge.net> */ public class CampaignHistoryInfoPane extends JPanel implements CharacterInfoTab { private static final String ADD_COMMAND = "ADD"; private static final String ALL_COMMAND = "ALL"; private static final String NONE_COMMAND = "NONE"; private final TabTitle title = new TabTitle("Campaign History", null); private final JPanel chroniclesPane; private final JButton addButton; private final JButton allButton; private final JButton noneButton; public CampaignHistoryInfoPane() { this.addButton = new JButton(); this.allButton = new JButton(); this.noneButton = new JButton(); this.chroniclesPane = new ChroniclesPane(); initComponents(); } private void initComponents() { setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); addButton.setText("Add Next Chronicle"); addButton.setActionCommand(ADD_COMMAND); allButton.setText("All"); allButton.setActionCommand(ALL_COMMAND); noneButton.setText("None"); noneButton.setActionCommand(NONE_COMMAND); Box hbox = Box.createHorizontalBox(); hbox.add(Box.createRigidArea(new Dimension(5, 0))); hbox.add(new JLabel("Check an item to include on your Character Sheet")); hbox.add(Box.createRigidArea(new Dimension(5, 0))); hbox.add(allButton); hbox.add(Box.createRigidArea(new Dimension(3, 0))); hbox.add(noneButton); hbox.add(Box.createHorizontalGlue()); add(Box.createVerticalStrut(5)); add(hbox); add(Box.createVerticalStrut(10)); JScrollPane pane = new JScrollPane(chroniclesPane) { @Override public Dimension getMaximumSize() { Dimension size = getPreferredSize(); size.width = Integer.MAX_VALUE; return size; } @Override public boolean isValidateRoot() { return false; } }; pane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); pane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS); add(pane); add(Box.createVerticalStrut(10)); addButton.setAlignmentX((float) 0.5); add(addButton); add(Box.createVerticalStrut(5)); add(Box.createVerticalGlue()); } @Override public ModelMap createModels(CharacterFacade character) { ModelMap models = new ModelMap(); models.put(ChronicleHandler.class, new ChronicleHandler(character)); return models; } @Override public void restoreModels(ModelMap models) { models.get(ChronicleHandler.class).install(); } @Override public void storeModels(ModelMap models) { models.get(ChronicleHandler.class).uninstall(); } @Override public TabTitle getTabTitle() { return title; } /** * A ChronicleHandler manages the set of chronicles for a character and handles the * addButton, allButton, and noneButton button presses. */ private class ChronicleHandler implements ActionListener { private final List<ChroniclePane> chronicles; private final DescriptionFacade descFacade; public ChronicleHandler(CharacterFacade character) { descFacade = character.getDescriptionFacade(); chronicles = new ArrayList<>(); for (ChronicleEntryFacade entry : descFacade.getChronicleEntries()) { chronicles.add(new ChroniclePane(this, entry)); } } /** * Creates a new chronicle entry for the character. */ private ChroniclePane createNewChronicleEntry() { ChronicleEntryFacade entry = descFacade.createChronicleEntry(); ChroniclePane pane = new ChroniclePane(this, entry); return pane; } /** * Installs this ChronicleHandler by attaching itself to the buttons and changes * the components of the chroniclesPane to the ones contained in this handler. */ public void install() { addButton.addActionListener(this); allButton.addActionListener(this); noneButton.addActionListener(this); chroniclesPane.removeAll(); for (ChroniclePane chroniclePane : chronicles) { //since these components are not part of the UI tree make sure that they //use the current LAF SwingUtilities.updateComponentTreeUI(chroniclePane); chroniclesPane.add(chroniclePane); } updateChroniclesPane(); } /** * Uninstalls this ChronicleHandler by removing its listeners from the buttons. */ public void uninstall() { addButton.removeActionListener(this); allButton.removeActionListener(this); noneButton.removeActionListener(this); } private void updateChroniclesPane() { boolean notempty = !chronicles.isEmpty(); allButton.setEnabled(notempty); noneButton.setEnabled(notempty); chroniclesPane.revalidate(); } @Override public void actionPerformed(ActionEvent e) { if (ADD_COMMAND.equals(e.getActionCommand())) { ChroniclePane pane = createNewChronicleEntry(); chronicles.add(pane); chroniclesPane.add(pane); updateChroniclesPane(); } else if (ALL_COMMAND.equals(e.getActionCommand())) { for (ChroniclePane chroniclePane : chronicles) { chroniclePane.setSelected(true); } } else if (NONE_COMMAND.equals(e.getActionCommand())) { for (ChroniclePane chroniclePane : chronicles) { chroniclePane.setSelected(false); } } } /** * Deletes a chronicle from this character and updates the display */ public void deleteChroniclePane(ChroniclePane pane, ChronicleEntryFacade entry) { descFacade.removeChronicleEntry(entry); chroniclesPane.remove(pane); chronicles.remove(pane); updateChroniclesPane(); } } /** * The ChroniclePane is a JPanel which displays and handles a single chronicle entry. */ private static class ChroniclePane extends JPanel implements ActionListener { private JCheckBox checkBox = new JCheckBox(); private JButton deleteButton = new JButton(); private JTextField campaignField = new JTextField(); private JTextField adventureField = new JTextField(); private JTextField partyField = new JTextField(); private JTextField dateField = new JTextField(); private JFormattedTextField xpField = new JFormattedTextField(NumberFormat.getIntegerInstance()); private JTextField gmField = new JTextField(); private JTextArea textArea = new JTextArea() { @Override public Dimension getMinimumSize() { Dimension minSize = super.getMinimumSize(); Dimension prefSize = getPreferredSize(); minSize.height = prefSize.height; return minSize; } }; private ChronicleHandler handler; private ChronicleEntryFacade entry; /** * Creates a bare ChroniclePane that initializes the layout of its components. This is meant * only for components that want to know the size of a ChroniclePane without fully * initializing it. */ public ChroniclePane() { super(new GridBagLayout()); setBorder(BorderFactory.createEmptyBorder(2, 0, 5, 5)); initComponents(); } public ChroniclePane(ChronicleHandler handler, final ChronicleEntryFacade entry) { this(); this.handler = handler; this.entry = entry; populateComponents(); textArea.setLineWrap(true); textArea.setWrapStyleWord(true); textArea.setRows(5); partyField.setColumns(10); dateField.setColumns(6); xpField.setColumns(6); gmField.setColumns(10); deleteButton.setMinimumSize(new Dimension(20, 20)); deleteButton.setPreferredSize(new Dimension(20, 20)); deleteButton.setFocusable(false); deleteButton.setMargin(new Insets(0, 0, 0, 0)); deleteButton.setIcon(Icons.CloseX9.getImageIcon()); deleteButton.addActionListener(this); } public void setSelected(boolean select) { checkBox.setSelected(select); entry.setOutputEntry(select); } @Override public void actionPerformed(ActionEvent e) { if (fieldsNotBlank()) { int ret = JOptionPane.showConfirmDialog(this, "<html>This chronicle has been written in." + "<br>Are you sure you want to delete it?</html>", Constants.APPLICATION_NAME, JOptionPane.YES_NO_OPTION); if (ret != JOptionPane.YES_OPTION) {//The user has not agreed so exit out return; } } handler.deleteChroniclePane(this, entry); } /** * Checks all the fields to see if the user has entered any information into this * chronicle entry. * @return true if the fields are empty, false otherwise */ private boolean fieldsNotBlank() { return StringUtils.isNotBlank(campaignField.getText()) || StringUtils.isNotBlank(adventureField.getText()) || StringUtils.isNotBlank(adventureField.getText()) || StringUtils.isNotBlank(partyField.getText()) || StringUtils.isNotBlank(dateField.getText()) || !NumberUtils.INTEGER_ZERO.equals(xpField.getValue()) || StringUtils.isNotBlank(gmField.getText()) || StringUtils.isNotBlank(textArea.getText()); } private void initComponents() { GridBagConstraints gbc = new GridBagConstraints(); gbc.insets = new Insets(0, 4, 0, 0); gbc.gridheight = 1; gbc.anchor = GridBagConstraints.BASELINE; add(checkBox, gbc); gbc.anchor = GridBagConstraints.EAST; gbc.insets = new Insets(0, 10, 0, 0); GridBagConstraints gbc2 = new GridBagConstraints(); gbc2.fill = GridBagConstraints.HORIZONTAL; gbc2.gridwidth = 3; gbc2.insets = new Insets(1, 4, 1, 0); add(new JLabel("Campaign:"), gbc); add(campaignField, gbc2); add(new JLabel("Adventure:"), gbc); gbc2.gridwidth = GridBagConstraints.REMAINDER; add(adventureField, gbc2); add(new JLabel(), gbc); add(new JLabel("Party Name:"), gbc); gbc2.gridwidth = 1; gbc2.weightx = 0.35; add(partyField, gbc2); add(new JLabel("Date:"), gbc); gbc2.weightx = 0.15; add(dateField, gbc2); add(new JLabel("XP Gained:"), gbc); gbc2.weightx = 0.05; add(xpField, gbc2); add(new JLabel("GM:"), gbc); gbc2.gridwidth = GridBagConstraints.REMAINDER; gbc2.weightx = 0.45; add(gmField, gbc2); gbc.fill = GridBagConstraints.VERTICAL; gbc.insets = new Insets(0, 0, 2, 0); add(deleteButton, gbc); gbc.gridwidth = GridBagConstraints.REMAINDER; gbc.fill = GridBagConstraints.BOTH; gbc.insets = new Insets(0, 10, 0, 0); add(textArea, gbc); } /** * Place the values from the entry into the fields. */ private void populateComponents() { checkBox.setSelected(entry.isOutputEntry()); campaignField.setText(entry.getCampaign()); adventureField.setText(entry.getAdventure()); partyField.setText(entry.getParty()); dateField.setText(entry.getDate()); xpField.setValue(entry.getXpField()); gmField.setText(entry.getGmField()); textArea.setText(entry.getChronicle()); // Listeners to write any entered values back to the character ActionListener actionListener = new ActionListener() { @Override public void actionPerformed(ActionEvent actionEvent) { entry.setOutputEntry(checkBox.getModel().isSelected()); } }; checkBox.addActionListener(actionListener); campaignField.getDocument().addDocumentListener( new TextFieldListener(campaignField) { @Override protected void textChanged(String text) { entry.setCampaign(text); } }); adventureField.getDocument().addDocumentListener( new TextFieldListener(adventureField) { @Override protected void textChanged(String text) { entry.setAdventure(text); } }); partyField.getDocument().addDocumentListener( new TextFieldListener(partyField) { @Override protected void textChanged(String text) { entry.setParty(text); } }); dateField.getDocument().addDocumentListener( new TextFieldListener(dateField) { @Override protected void textChanged(String text) { entry.setDate(text); } }); xpField.addPropertyChangeListener("value", new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { entry.setXpField(((Number) xpField.getValue()).intValue()); } }); gmField.getDocument().addDocumentListener( new TextFieldListener(gmField) { @Override protected void textChanged(String text) { entry.setGmField(text); } }); textArea.getDocument().addDocumentListener( new TextFieldListener(textArea) { @Override protected void textChanged(String text) { entry.setChronicle(text); } }); } } /** * The ChroniclesPane is just a JPanel that implements Scrollable. This controls the ammount * that this component is scrolled within its parent JScrollPane. By default we set this scroll * distance to a single ChroniclePane's height such that one tick of the mouse scroll will * scroll a single chronicle entry. */ private static class ChroniclesPane extends JPanel implements Scrollable { private ChroniclePane dummyPane = new ChroniclePane(); public ChroniclesPane() { setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); } @Override public Dimension getPreferredScrollableViewportSize() { return getPreferredSize(); } @Override public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) { return 100; } @Override public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) { return dummyPane.getPreferredSize().height + 10; } @Override public boolean getScrollableTracksViewportWidth() { return true; } @Override public boolean getScrollableTracksViewportHeight() { return false; } } }