/*
* ChooserDialog.java
* Copyright James Dempsey, 2012
*
* 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 06/01/2012 9:23:01 AM
*
* $Id$
*/
package pcgen.gui2.dialog;
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Frame;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.swing.Box;
import javax.swing.BoxLayout;
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.JSplitPane;
import javax.swing.JTextField;
import javax.swing.ListSelectionModel;
import javax.swing.WindowConstants;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import org.apache.commons.lang3.StringUtils;
import pcgen.cdom.base.Constants;
import pcgen.core.chooser.InfoWrapper;
import pcgen.facade.core.ChooserFacade;
import pcgen.facade.core.ChooserFacade.ChooserTreeViewType;
import pcgen.facade.core.InfoFacade;
import pcgen.facade.core.InfoFactory;
import pcgen.facade.util.event.ReferenceEvent;
import pcgen.facade.util.event.ReferenceListener;
import pcgen.facade.util.DefaultListFacade;
import pcgen.facade.util.DelegatingListFacade;
import pcgen.facade.util.ListFacade;
import pcgen.gui2.UIPropertyContext;
import pcgen.gui2.tools.Icons;
import pcgen.gui2.tools.InfoPane;
import pcgen.gui2.util.FacadeListModel;
import pcgen.gui2.util.JListEx;
import pcgen.gui2.util.JTreeViewTable;
import pcgen.gui2.util.treeview.DataView;
import pcgen.gui2.util.treeview.DataViewColumn;
import pcgen.gui2.util.treeview.TreeView;
import pcgen.gui2.util.treeview.TreeViewModel;
import pcgen.gui2.util.treeview.TreeViewPath;
import pcgen.system.LanguageBundle;
import pcgen.system.PropertyContext;
/**
* The Class {@code ChooserDialog} provides a general dialog to allow the
* user to select from a number of predefined choices. A ChooserFacade instance
* must be supplied, this defines the choices available, the text to be displayed
* on screen and the actions to be taken when the user confirms their choices. The
* chooser is generally displayed via a call to UIDelgate.showGeneralChooser.
* <p>
* This class is based heavily on Connor Petty's LanguageChooserDialog class.
*
* <br>
*
* @author James Dempsey <jdempsey@users.sourceforge.net>
*/
@SuppressWarnings("serial")
public class ChooserDialog extends JDialog implements ActionListener, ReferenceListener<Integer>, ListSelectionListener
{
private final ChooserFacade chooser;
private final JTreeViewTable<InfoFacade> availTable;
private final JTextField availInput;
private final JLabel remainingLabel;
private final GeneralTreeViewModel treeViewModel;
private final FacadeListModel<InfoFacade> listModel;
private final JListEx list;
private final InfoPane infoPane;
private boolean committed;
/**
* Create a new instance of ChooserDialog for selecting from the data supplied in the chooserFacade.
* @param frame The window we are opening relative to.
* @param chooser The definition of what should be displayed.
*/
public ChooserDialog(Frame frame, ChooserFacade chooser)
{
super(frame, true);
this.chooser = chooser;
if (chooser.isUserInput())
{
this.availTable = null;
this.availInput = new JTextField(20);
}
else
{
this.availTable = new JTreeViewTable<>();
this.availInput = null;
}
this.remainingLabel = new JLabel();
this.treeViewModel = new GeneralTreeViewModel();
this.list = new JListEx();
this.listModel = new FacadeListModel<>();
this.infoPane = new InfoPane();
treeViewModel.setDelegate(chooser.getAvailableList());
listModel.setListFacade(chooser.getSelectedList());
chooser.getRemainingSelections().addReferenceListener(this);
overridePrefs();
initComponents();
pack();
}
@Override
public void setVisible(boolean b)
{
//
// Only do this if 1 entry and can add...
//
ListFacade<InfoFacade> availableList = chooser.getAvailableList();
if ((availableList != null) && (availableList.getSize() == 1)
&& (listModel.getSize() == 0) && b && !chooser.isUserInput())
{
final int method = UIPropertyContext.getSingleChoiceAction();
if (method != Constants.CHOOSER_SINGLE_CHOICE_METHOD_NONE)
{
chooser.addSelected(availableList.getElementAt(0));
if (method == Constants.CHOOSER_SINGLE_CHOICE_METHOD_SELECT_EXIT)
{
chooser.commit();
committed = true;
return;
}
}
}
super.setVisible(b);
}
/**
* We don't want some things recalled in preferences (e.g. tree sorting) as they
* aren't the same for all choose data. Ensure we put out desired values in first.
*/
private void overridePrefs()
{
UIPropertyContext baseContext = UIPropertyContext.createContext("tablePrefs");
PropertyContext context =
baseContext.createChildContext(
treeViewModel.getDataView().getPrefsKey());
final String VIEW_INDEX_PREFS_KEY = "viewIdx";
context.setInt(VIEW_INDEX_PREFS_KEY, treeViewModel.getDefaultTreeViewIndex());
}
private void initComponents()
{
setTitle(chooser.getName());
setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
addWindowListener(new WindowAdapter()
{
@Override
public void windowClosed(WindowEvent e)
{
//detach listeners from the chooser
treeViewModel.setDelegate(null);
listModel.setListFacade(null);
chooser.getRemainingSelections().removeReferenceListener(ChooserDialog.this);
}
});
Container pane = getContentPane();
pane.setLayout(new BorderLayout());
JSplitPane split = new JSplitPane();
JPanel leftPane = new JPanel(new BorderLayout());
if (availTable != null)
{
availTable.setAutoCreateRowSorter(true);
availTable.setTreeViewModel(treeViewModel);
availTable.getRowSorter().toggleSortOrder(0);
availTable.addActionListener(this);
leftPane.add(new JScrollPane(availTable), BorderLayout.CENTER);
}
else
{
availInput.addActionListener(this);
Dimension maxDim = new Dimension(Integer.MAX_VALUE, availInput.getPreferredSize().height);
availInput.setMaximumSize(maxDim);
JPanel availPanel = new JPanel();
availPanel.setLayout(new BoxLayout(availPanel, BoxLayout.PAGE_AXIS));
availPanel.add(Box.createRigidArea(new Dimension(10, 30)));
availPanel.add(Box.createVerticalGlue());
availPanel.add(new JLabel(LanguageBundle.getString("in_uichooser_value")));
availPanel.add(availInput);
availPanel.add(Box.createVerticalGlue());
leftPane.add(availPanel, BorderLayout.WEST);
}
JPanel buttonPane1 = new JPanel(new FlowLayout());
JButton addButton = new JButton(chooser.getAddButtonName());
addButton.setActionCommand("ADD");
addButton.addActionListener(this);
buttonPane1.add(addButton);
buttonPane1.add(new JLabel(Icons.Forward16.getImageIcon()));
leftPane.add(buttonPane1, BorderLayout.SOUTH);
split.setLeftComponent(leftPane);
JPanel rightPane = new JPanel(new BorderLayout());
JPanel labelPane = new JPanel(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridwidth = GridBagConstraints.REMAINDER;
labelPane.add(new JLabel(chooser.getSelectionCountName()), new GridBagConstraints());
remainingLabel.setText(chooser.getRemainingSelections().get().toString());
labelPane.add(remainingLabel, gbc);
labelPane.add(new JLabel(chooser.getSelectedTableTitle()), gbc);
rightPane.add(labelPane, BorderLayout.NORTH);
list.setModel(listModel);
list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
list.addActionListener(this);
rightPane.add(new JScrollPane(list), BorderLayout.CENTER);
JPanel buttonPane2 = new JPanel(new FlowLayout());
buttonPane2.add(new JLabel(Icons.Back16.getImageIcon()));
JButton removeButton = new JButton(chooser.getRemoveButtonName());
removeButton.setActionCommand("REMOVE");
removeButton.addActionListener(this);
buttonPane2.add(removeButton);
rightPane.add(buttonPane2, BorderLayout.SOUTH);
split.setRightComponent(rightPane);
if (chooser.isInfoAvailable())
{
JSplitPane infoSplit = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
infoSplit.setTopComponent(split);
infoSplit.setBottomComponent(infoPane);
infoSplit.setResizeWeight(0.8);
pane.add(infoSplit, BorderLayout.CENTER);
if (availTable != null)
{
availTable.getSelectionModel().addListSelectionListener(this);
}
}
else
{
pane.add(split, BorderLayout.CENTER);
}
JPanel bottomPane = new JPanel(new FlowLayout());
JButton button = new JButton(LanguageBundle.getString("in_ok")); //$NON-NLS-1$
button.setMnemonic(LanguageBundle.getMnemonic("in_mn_ok")); //$NON-NLS-1$
button.setActionCommand("OK");
button.addActionListener(this);
bottomPane.add(button);
button = new JButton(LanguageBundle.getString("in_cancel")); //$NON-NLS-1$
button.setMnemonic(LanguageBundle.getMnemonic("in_mn_cancel")); //$NON-NLS-1$
button.setActionCommand("CANCEL");
button.addActionListener(this);
bottomPane.add(button);
pane.add(bottomPane, BorderLayout.SOUTH);
}
@Override
public void referenceChanged(ReferenceEvent<Integer> e)
{
remainingLabel.setText(e.getNewReference().toString());
}
@Override
public void valueChanged(ListSelectionEvent e)
{
if (availTable != null && !e.getValueIsAdjusting())
{
if (e.getSource() == availTable.getSelectionModel()
&& availTable.getSelectedObject() instanceof InfoFacade)
{
InfoFacade target = (InfoFacade) availTable.getSelectedObject();
InfoFactory factory = chooser.getInfoFactory();
if (factory != null && target!=null)
{
infoPane.setText(factory.getHTMLInfo(target));
}
}
}
}
@Override
public void actionPerformed(ActionEvent e)
{
if (availTable != null
&& (e.getActionCommand().equals("ADD") || e.getSource() == availTable))
{
List<Object> data = availTable.getSelectedData();
if (!data.isEmpty())
{
for (Object object : data)
{
if (object instanceof InfoFacade)
{
chooser.addSelected((InfoFacade) object);
}
}
}
return;
}
if (availInput != null
&& (e.getActionCommand().equals("ADD") || e.getSource() == availInput))
{
String data = availInput.getText();
if (StringUtils.isNotBlank(data))
{
chooser.addSelected(new InfoWrapper(data));
}
availInput.setText("");
return;
}
if (e.getActionCommand().equals("REMOVE") || e.getSource() == list)
{
Object value = list.getSelectedValue();
if (value != null && value instanceof InfoFacade)
{
chooser.removeSelected((InfoFacade) value);
if (availInput != null)
{
availInput.setText(value.toString());
}
}
return;
}
if (e.getActionCommand().equals("OK"))
{
if (chooser.isRequireCompleteSelection()
&& chooser.getRemainingSelections().get() > 0)
{
JOptionPane.showMessageDialog(this,
LanguageBundle.getFormattedString("in_chooserRequireComplete", //$NON-NLS-1$
chooser.getRemainingSelections().get()),
chooser.getName(),
JOptionPane.INFORMATION_MESSAGE);
return;
}
else
{
chooser.commit();
}
}
else
{
chooser.rollback();
}
committed = e.getActionCommand().equals("OK");
dispose();
}
/**
* Returns the means by which the dialog was closed.
* @return the committed status, false for cancelled, true for OKed.
*/
public boolean isCommitted()
{
return committed;
}
private class GeneralTreeViewModel extends DelegatingListFacade<InfoFacade> implements TreeViewModel<InfoFacade>,
DataView<InfoFacade>
{
@Override
public ListFacade<? extends TreeView<InfoFacade>> getTreeViews()
{
DefaultListFacade<TreeView<InfoFacade>> views =
new DefaultListFacade<>();
views.addElement(new ChooserTreeView(ChooserTreeViewType.NAME,
chooser.getAvailableTableTitle(), chooser));
views.addElement(new ChooserTreeView(ChooserTreeViewType.TYPE_NAME,
chooser.getAvailableTableTypeNameTitle(), chooser));
return views;
}
@Override
public int getDefaultTreeViewIndex()
{
return chooser.getDefaultView().ordinal();
}
@Override
public DataView<InfoFacade> getDataView()
{
return this;
}
@Override
public ListFacade<InfoFacade> getDataModel()
{
return this;
}
@Override
public Object getData(InfoFacade element, int column)
{
return null;
}
@Override
public void setData(Object value, InfoFacade element, int column)
{
}
@Override
public List<? extends DataViewColumn> getDataColumns()
{
return Collections.emptyList();
}
@Override
public String getPrefsKey()
{
return chooser.getAvailableTableTypeNameTitle();
}
}
private final class ChooserTreeView implements TreeView<InfoFacade>
{
private String viewName;
private final ChooserFacade chooser;
private final ChooserTreeViewType viewType;
private ChooserTreeView(ChooserTreeViewType viewType, String name, ChooserFacade chooser)
{
this.viewType = viewType;
this.viewName = name;
this.chooser = chooser;
}
@Override
public String getViewName()
{
return viewName;
}
@Override
public List<TreeViewPath<InfoFacade>> getPaths(InfoFacade pobj)
{
switch (viewType)
{
case TYPE_NAME:
List<TreeViewPath<InfoFacade>> paths = new ArrayList<>();
for(String type : chooser.getBranchNames(pobj))
{
paths.add(new TreeViewPath<>(pobj, type));
}
if (!paths.isEmpty())
{
return paths;
}
// Otherwise treat as a name entry
case NAME:
return Collections.singletonList(new TreeViewPath<>(pobj));
default:
throw new InternalError();
}
}
}
}