/*
* AdvancedSourceSelectionPanel.java
* Copyright 2009 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 Feb 21, 2009, 7:15:03 PM
*/
package pcgen.gui2.sources;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import javax.swing.AbstractAction;
import javax.swing.Box;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTree;
import javax.swing.SwingConstants;
import javax.swing.border.EmptyBorder;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import org.apache.commons.lang3.StringUtils;
import pcgen.facade.core.CampaignFacade;
import pcgen.facade.core.GameModeDisplayFacade;
import pcgen.facade.core.GameModeFacade;
import pcgen.facade.core.SourceSelectionFacade;
import pcgen.facade.util.event.ListEvent;
import pcgen.facade.util.event.ListListener;
import pcgen.facade.util.DefaultListFacade;
import pcgen.facade.util.ListFacade;
import pcgen.facade.util.ListFacades;
import pcgen.gui2.PCGenFrame;
import pcgen.gui2.UIPropertyContext;
import pcgen.gui2.filter.FilterBar;
import pcgen.gui2.filter.FilteredTreeViewTable;
import pcgen.gui2.filter.SearchFilterPanel;
import pcgen.gui2.tools.FlippingSplitPane;
import pcgen.gui2.tools.Icons;
import pcgen.gui2.tools.InfoPane;
import pcgen.gui2.tools.InfoPaneLinkAction;
import pcgen.gui2.util.FacadeComboBoxModel;
import pcgen.gui2.util.TreeColumnCellRenderer;
import pcgen.gui2.util.table.DynamicTableColumnModel;
import pcgen.gui2.util.table.TableCellUtilities;
import pcgen.gui2.util.treeview.DataView;
import pcgen.gui2.util.treeview.DataViewColumn;
import pcgen.gui2.util.treeview.DefaultDataViewColumn;
import pcgen.gui2.util.treeview.TreeView;
import pcgen.gui2.util.treeview.TreeViewModel;
import pcgen.gui2.util.treeview.TreeViewPath;
import pcgen.system.FacadeFactory;
import pcgen.system.LanguageBundle;
import pcgen.util.Comparators;
import pcgen.util.Logging;
/**
*
* @author Connor Petty <cpmeister@users.sourceforge.net>
*/
class AdvancedSourceSelectionPanel extends JPanel
implements ListSelectionListener, ListListener<CampaignFacade>, ActionListener
{
private static final UIPropertyContext context = UIPropertyContext
.createContext("advancedSourceSelectionPanel"); //$NON-NLS-1$
private static final String PROP_SELECTED_GAME = "selectedGame"; //$NON-NLS-1$
private static final String PROP_SELECTED_SOURCES = "selectedSources."; //$NON-NLS-1$
private final FilteredTreeViewTable<Object, CampaignFacade> availableTable;
private final FilteredTreeViewTable<Object, CampaignFacade> selectedTable;
private SourceTreeViewModel availTreeViewModel;
private SourceTreeViewModel selTreeViewModel;
private InfoPane infoPane;
private GameModeFacade gameMode;
private JComboBox gameModeList;
private InfoPaneLinkAction linkAction;
private final JButton unloadAllButton;
private final JButton addButton;
private final JButton removeButton;
private DefaultListFacade<CampaignFacade> selectedCampaigns;
private final PCGenFrame frame;
public AdvancedSourceSelectionPanel(PCGenFrame frame)
{
this.frame = frame;
this.availableTable = new FilteredTreeViewTable<>();
this.selectedTable = new FilteredTreeViewTable<>();
this.selectedCampaigns = new DefaultListFacade<>();
this.availTreeViewModel = new SourceTreeViewModel();
this.selTreeViewModel = new SourceTreeViewModel(selectedCampaigns);
this.gameModeList = new JComboBox();
this.unloadAllButton = new JButton();
this.addButton = new JButton();
this.removeButton = new JButton();
this.infoPane = new InfoPane(LanguageBundle.getString("in_src_info")); //$NON-NLS-1$
this.linkAction = new InfoPaneLinkAction(infoPane);
initComponents();
initDefaults();
selectedCampaigns.addListListener(this);
}
private void initComponents()
{
FlippingSplitPane mainPane = new FlippingSplitPane(JSplitPane.VERTICAL_SPLIT, "advSrcMain");
FlippingSplitPane topPane = new FlippingSplitPane("advSrcTop");
topPane.setResizeWeight(0.6);
JPanel panel = new JPanel(new BorderLayout());
panel.add(new JLabel(LanguageBundle.getString("in_src_gameLabel")), BorderLayout.WEST); //$NON-NLS-1$
FacadeComboBoxModel<GameModeDisplayFacade> gameModes = new FacadeComboBoxModel<>();
gameModes.setListFacade(FacadeFactory.getGameModeDisplays());
gameModeList.setModel(gameModes);
gameModeList.addActionListener(this);
panel.add(gameModeList, BorderLayout.CENTER);
FilterBar<Object, CampaignFacade> bar = new FilterBar<>(false);
bar.add(panel, BorderLayout.WEST);
bar.addDisplayableFilter(new SearchFilterPanel());
panel = new JPanel(new BorderLayout());
panel.add(bar, BorderLayout.NORTH);
availableTable.setDisplayableFilter(bar);
availableTable.setTreeViewModel(availTreeViewModel);
availableTable.getSelectionModel().addListSelectionListener(this);
availableTable.setTreeCellRenderer(new CampaignRenderer());
((DynamicTableColumnModel) availableTable.getColumnModel())
.getAvailableColumns()
.get(2)
.setCellRenderer(
new TableCellUtilities.AlignRenderer(SwingConstants.CENTER));
JScrollPane pane = new JScrollPane(availableTable);
pane.setPreferredSize(new Dimension(600, 310));
panel.add(pane, BorderLayout.CENTER);
Box box = Box.createHorizontalBox();
unloadAllButton.setAction(new UnloadAllAction());
box.add(unloadAllButton);
box.add(Box.createHorizontalGlue());
addButton.setHorizontalTextPosition(SwingConstants.LEADING);
addButton.setAction(new AddAction());
box.add(addButton);
box.add(Box.createHorizontalStrut(5));
box.setBorder(new EmptyBorder(0, 0, 5, 0));
panel.add(box, BorderLayout.SOUTH);
topPane.setLeftComponent(panel);
JPanel selPanel = new JPanel(new BorderLayout());
FilterBar<Object, CampaignFacade> filterBar = new FilterBar<>();
filterBar.addDisplayableFilter(new SearchFilterPanel());
selectedTable.setDisplayableFilter(filterBar);
selectedTable.setTreeViewModel(selTreeViewModel);
selectedTable.getSelectionModel().addListSelectionListener(this);
selectedTable.setTreeCellRenderer(new CampaignRenderer());
((DynamicTableColumnModel) selectedTable.getColumnModel())
.getAvailableColumns()
.get(2)
.setCellRenderer(
new TableCellUtilities.AlignRenderer(SwingConstants.CENTER));
JScrollPane scrollPane = new JScrollPane(selectedTable);
scrollPane.setPreferredSize(new Dimension(300, 350));
selPanel.add(scrollPane, BorderLayout.CENTER);
box = Box.createHorizontalBox();
box.add(Box.createHorizontalStrut(5));
removeButton.setAction(new RemoveAction());
box.add(removeButton);
box.add(Box.createHorizontalGlue());
box.setBorder(new EmptyBorder(0, 0, 5, 0));
selPanel.add(box, BorderLayout.SOUTH);
topPane.setRightComponent(selPanel);
mainPane.setTopComponent(topPane);
linkAction.install();
infoPane.setPreferredSize(new Dimension(800, 150));
mainPane.setBottomComponent(infoPane);
mainPane.setResizeWeight(0.7);
setLayout(new BorderLayout());
add(mainPane, BorderLayout.CENTER);
}
private void initDefaults()
{
String defaultGame =
context.initProperty(PROP_SELECTED_GAME, "");
GameModeDisplayFacade modeDisplay = null;
if (StringUtils.isNotEmpty(defaultGame))
{
for (int i = 0; i < gameModeList.getModel().getSize(); i++)
{
GameModeDisplayFacade game =
(GameModeDisplayFacade) gameModeList.getModel()
.getElementAt(i);
if (defaultGame.equals(game.getGameMode().toString()))
{
gameModeList.setSelectedIndex(i);
modeDisplay = game;
}
}
}
if (modeDisplay == null && gameModeList.getModel().getSize() > 0)
{
gameModeList.setSelectedIndex(0);
modeDisplay = (GameModeDisplayFacade) gameModeList.getSelectedItem();
}
}
/**
* Add the user's previously selected sources for this gamemode
* to the selected list.
* @param mode The game mode being selected
*/
private void selectDefaultSources(GameModeFacade mode)
{
if (mode != null)
{
List<String> sourceNames;
String defaultSelectedSources =
context.initProperty(
PROP_SELECTED_SOURCES + mode.toString(), ""); //$NON-NLS-1$
if (defaultSelectedSources == null || "".equals(defaultSelectedSources))
{
sourceNames = mode.getDefaultDataSetList();
}
else
{
sourceNames = Arrays.asList(defaultSelectedSources.split("\\|")); //$NON-NLS-1$
}
for (String name : sourceNames)
{
for (CampaignFacade camp : FacadeFactory
.getSupportedCampaigns(mode))
{
if (name.equals(camp.toString()))
{
selectedCampaigns.addElement(camp);
break;
}
}
}
}
}
public GameModeFacade getSelectedGameMode()
{
return gameMode;
}
public List<CampaignFacade> getSelectedCampaigns()
{
return selectedCampaigns.getContents();
}
public void setSourceSelection(SourceSelectionFacade sources)
{
if (sources == null || sources.getGameMode() == null)
{
Logging.errorPrint("Invalid source selection " + sources
+ "- ignoring.");
return;
}
GameModeFacade selectedGame = sources.getGameMode().get();
for (int i = 0; i < gameModeList.getModel().getSize(); i++)
{
GameModeDisplayFacade gmdf = (GameModeDisplayFacade) gameModeList.getModel().getElementAt(i);
if (gmdf.getGameMode() == selectedGame)
{
gameModeList.setSelectedItem(gmdf);
}
}
List<CampaignFacade> wrap = new ArrayList<>(ListFacades.wrap(sources.getCampaigns()));
wrap.sort(Comparators.toStringIgnoreCaseCollator());
selectedCampaigns.setContents(wrap);
}
private void setSelectedGameMode(GameModeDisplayFacade elementAt)
{
this.gameMode = elementAt.getGameMode();
context.setProperty(PROP_SELECTED_GAME, gameMode.toString());
selectedCampaigns.clearContents();
availTreeViewModel.setGameModel(elementAt.getGameMode());
selectDefaultSources(gameMode);
}
private void setSelectedCampaign(CampaignFacade source)
{
infoPane.setText(FacadeFactory.getCampaignInfoFactory().getHTMLInfo(
source, selectedCampaigns.getContents()));
}
@Override
public void valueChanged(ListSelectionEvent e)
{
if (!e.getValueIsAdjusting())
{
if (e.getSource() == availableTable.getSelectionModel())
{
int selectedRow = availableTable.getSelectedRow();
if (selectedRow != -1)
{
final Object data = availableTable.getModel().getValueAt(selectedRow, 0);
if (data != null && data instanceof CampaignFacade)
{
setSelectedCampaign((CampaignFacade) data);
}
}
}
else if (e.getSource() == selectedTable.getSelectionModel())
{
int selectedRow = selectedTable.getSelectedRow();
if (selectedRow != -1)
{
final Object data = selectedTable.getModel().getValueAt(selectedRow, 0);
if (data != null && data instanceof CampaignFacade)
{
setSelectedCampaign((CampaignFacade) data);
}
}
}
}
}
void refreshDisplay()
{
availableTable.refreshModelData();
selectedTable.refreshModelData();
}
@Override
public void actionPerformed(ActionEvent e)
{
// Signal from the game mode conbo that the selection has changed.
setSelectedGameMode((GameModeDisplayFacade) gameModeList.getSelectedItem());
}
@Override
public void elementAdded(ListEvent<CampaignFacade> e)
{
// Refresh displayed rows now that the selection has changed
availableTable.updateDisplay();
}
@Override
public void elementRemoved(ListEvent<CampaignFacade> e)
{
// Refresh displayed rows now that the selection has changed
availableTable.updateDisplay();
}
@Override
public void elementsChanged(ListEvent<CampaignFacade> e)
{
// Refresh displayed rows now that the selection has changed
availableTable.updateDisplay();
}
@Override
public void elementModified(ListEvent<CampaignFacade> e)
{
availableTable.updateDisplay();
}
/**
* Save the selected sources for the current game mode so they can be
* restored next time we start.
*/
void rememberSelectedSources()
{
String sources = StringUtils.join(getSelectedCampaigns(), "|"); //$NON-NLS-1$
context.setProperty(PROP_SELECTED_SOURCES+gameMode.toString(), sources);
}
private class AddAction extends AbstractAction
{
public AddAction()
{
super(LanguageBundle.getString("in_addSelected")); //$NON-NLS-1$
putValue(SMALL_ICON, Icons.Forward16.getImageIcon());
availableTable.addActionListener(this);
}
@Override
public void actionPerformed(ActionEvent e)
{
List<Object> list = availableTable.getSelectedData();
if (list != null && !list.isEmpty())
{
for (Object obj : list)
{
if (obj instanceof CampaignFacade)
{
CampaignFacade camp = (CampaignFacade) obj;
if (selectedCampaigns.containsElement(camp))
{
// Already in the list - ignore
continue;
}
selectedCampaigns.addElement(camp);
if (!FacadeFactory.passesPrereqs(selectedCampaigns.getContents()))
{
String prereqDesc =
FacadeFactory.getCampaignInfoFactory()
.getRequirementsHTMLString(camp,
selectedCampaigns.getContents());
JOptionPane.showMessageDialog(AdvancedSourceSelectionPanel.this,
LanguageBundle.getFormattedString("in_src_badComboMsg", //$NON-NLS-1$
prereqDesc),
LanguageBundle.getString("in_src_badComboTitle"), //$NON-NLS-1$
JOptionPane.INFORMATION_MESSAGE);
selectedCampaigns.removeElement(camp);
}
}
}
rememberSelectedSources();
}
}
}
private class RemoveAction extends AbstractAction
{
public RemoveAction()
{
super(LanguageBundle.getString("in_removeSelected")); //$NON-NLS-1$
putValue(SMALL_ICON, Icons.Back16.getImageIcon());
selectedTable.addActionListener(this);
}
@Override
public void actionPerformed(ActionEvent e)
{
List<Object> list = selectedTable.getSelectedData();
if (list != null && !list.isEmpty())
{
for (Object obj : list)
{
if (obj instanceof CampaignFacade)
{
selectedCampaigns.removeElement((CampaignFacade) obj);
}
}
rememberSelectedSources();
}
}
}
private class UnloadAllAction extends AbstractAction
{
public UnloadAllAction()
{
super(LanguageBundle.getString("in_src_unloadAll")); //$NON-NLS-1$
}
@Override
public void actionPerformed(ActionEvent e)
{
frame.unloadSources();
availableTable.refreshModelData();
selectedTable.refreshModelData();
}
}
private class SourceTreeViewModel
implements TreeViewModel<CampaignFacade>, DataView<CampaignFacade>, ListListener<CampaignFacade>
{
private ListFacade<TreeView<CampaignFacade>> views =
new DefaultListFacade<>(Arrays.asList(SourceTreeView.values()));
private DefaultListFacade<CampaignFacade> model;
private ListFacade<CampaignFacade> baseModel = null;
private final List<DefaultDataViewColumn> columns;
private final boolean isAvailModel;
public SourceTreeViewModel()
{
this.model = new DefaultListFacade<>();
this.isAvailModel = true;
columns =
Arrays.asList(new DefaultDataViewColumn("in_src_bookType", String.class, true),
new DefaultDataViewColumn("in_src_status", String.class, true),
new DefaultDataViewColumn("in_src_loaded", String.class, false));
}
public SourceTreeViewModel( DefaultListFacade<CampaignFacade> model)
{
this.model = model;
this.isAvailModel = false;
columns =
Arrays.asList(new DefaultDataViewColumn("in_src_bookType", String.class, false),
new DefaultDataViewColumn("in_src_status", String.class, false),
new DefaultDataViewColumn("in_src_loaded", String.class, false));
}
@Override
public ListFacade<? extends TreeView<CampaignFacade>> getTreeViews()
{
return views;
}
@Override
public int getDefaultTreeViewIndex()
{
return isAvailModel ? 2 : 0;
}
@Override
public DataView<CampaignFacade> getDataView()
{
return this;
}
@Override
public ListFacade<CampaignFacade> getDataModel()
{
return model;
}
@Override
public Object getData(CampaignFacade obj, int column)
{
SourceSelectionFacade sourceFacade
= frame.getCurrentSourceSelectionRef().get();
boolean isLoaded
= sourceFacade != null
&& sourceFacade.getCampaigns().containsElement(obj);
switch (column)
{
case 0:
return obj.getBookTypes();
case 1:
return obj.getStatus();
case 2:
return isLoaded ? LanguageBundle.getString("in_yes") : LanguageBundle
.getString("in_no");
default:
return null;
}
}
@Override
public void setData(Object value, CampaignFacade element, int column)
{
}
@Override
public List<? extends DataViewColumn> getDataColumns()
{
return columns;
}
public void setGameModel(GameModeFacade gameMode)
{
if (baseModel != null)
{
baseModel.removeListListener(this);
}
baseModel = FacadeFactory.getSupportedCampaigns(gameMode);
model.setContents(ListFacades.wrap(baseModel));
baseModel.addListListener(this);
}
@Override
public void elementAdded(ListEvent<CampaignFacade> e)
{
model.addElement(e.getIndex(), e.getElement());
}
@Override
public void elementRemoved(ListEvent<CampaignFacade> e)
{
model.removeElement(e.getIndex());
}
@Override
public void elementsChanged(ListEvent<CampaignFacade> e)
{
model.setContents(ListFacades.wrap(baseModel));
}
@Override
public void elementModified(ListEvent<CampaignFacade> e)
{
}
@Override
public String getPrefsKey()
{
return isAvailModel ? "SourceAvail" : "SourceSelected"; //$NON-NLS-1$//$NON-NLS-2$
}
}
private static enum SourceTreeView implements TreeView<CampaignFacade>
{
NAME("in_nameLabel"), //$NON-NLS-1$
PUBLISHER_NAME("in_src_pubName"), //$NON-NLS-1$
PUBLISHER_SETTING_NAME("in_src_pubSetName"), //$NON-NLS-1$
PUBLISHER_FORMAT_SETTING_NAME("in_src_pubFmtSetName"); //$NON-NLS-1$
private final String name;
private SourceTreeView(String name)
{
this.name = LanguageBundle.getString(name);
}
@Override
public String getViewName()
{
return name;
}
@Override
public List<TreeViewPath<CampaignFacade>> getPaths(CampaignFacade pobj)
{
String publisher = pobj.getPublisher();
if (publisher == null)
{
publisher = LanguageBundle.getString("in_other"); //$NON-NLS-1$
}
String setting = pobj.getSetting();
String format = pobj.getFormat();
switch (this)
{
case NAME:
return Collections
.singletonList(new TreeViewPath<>(pobj));
case PUBLISHER_FORMAT_SETTING_NAME:
if (format != null && setting != null)
{
return Collections
.singletonList(new TreeViewPath<>(
pobj, publisher, format, setting));
}
if (format != null)
{
return Collections
.singletonList(new TreeViewPath<>(
pobj, publisher, format));
}
case PUBLISHER_SETTING_NAME:
if (setting != null)
{
return Collections
.singletonList(new TreeViewPath<>(
pobj, publisher, setting));
}
case PUBLISHER_NAME:
return Collections
.singletonList(new TreeViewPath<>(pobj,
publisher));
default:
throw new InternalError();
}
}
}
/**
* The Class <code>CampaignRenderer</code> displays the tree cells of the
* source table.
*/
private class CampaignRenderer extends TreeColumnCellRenderer
{
/**
* Create a new renderer for the campaign names for a game mode. The
* names will be coloured to show if they are qualified or not.
*/
public CampaignRenderer()
{
setTextNonSelectionColor(UIPropertyContext.getQualifiedColor());
}
@Override
public Component getTreeCellRendererComponent(JTree tree, Object value,
boolean sel, boolean expanded, boolean leaf, int row, boolean focus)
{
super.getTreeCellRendererComponent(tree, value, sel, expanded,
leaf, row, focus);
Object campaignObj = ((DefaultMutableTreeNode) value).getUserObject();
if (campaignObj instanceof CampaignFacade)
{
CampaignFacade campaign = (CampaignFacade) campaignObj;
List<CampaignFacade> testCampaigns = selectedCampaigns.getContents();
testCampaigns.add(campaign);
if (!FacadeFactory.passesPrereqs(testCampaigns))
{
setForeground(UIPropertyContext.getNotQualifiedColor());
}
}
return this;
}
}
}