/*
* Copyright (C) 2011 Andrea Schweer
*
* This file is part of the Digital Parrot.
*
* The Digital Parrot is free software; you can redistribute it and/or modify
* it under the terms of the Eclipse Public License as published by the Eclipse
* Foundation or its Agreement Steward, either version 1.0 of the License, or
* (at your option) any later version.
*
* The Digital Parrot 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 Eclipse Public License for
* more details.
*
* You should have received a copy of the Eclipse Public License along with the
* Digital Parrot. If not, see http://www.eclipse.org/legal/epl-v10.html.
*
*/
package net.schweerelos.parrot.ui;
import java.awt.FlowLayout;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;
import net.schweerelos.parrot.model.Filter;
import net.schweerelos.parrot.model.NodeWrapper;
import net.schweerelos.parrot.model.ParrotModel;
import net.schweerelos.parrot.model.filters.Chain;
import net.schweerelos.parrot.model.filters.ChainLink;
import net.schweerelos.parrot.model.filters.HighlightChainFilter;
import net.schweerelos.parrot.model.filters.RestrictToChainFilter;
import com.hp.hpl.jena.ontology.OntResource;
@SuppressWarnings("serial")
public class ChainNavigator extends AbstractNavigatorPanel implements
PickListener {
private static final String CLEAR_ICON = "images/clear.png";
private static final String PLUS_ONE_ICON = "images/chain-plus-one.png";
private static final String NAME = "Connections";
private static final String ACCELERATOR_KEY = "C";
private Chain chain;
private RestrictToChainFilter restrictingFilter;
private HighlightChainFilter highlightingFilter;
private JPanel chainPanel;
private ClearChainAction clearAction;
private Action oneOutAction;
private PropertyChangeListener lastLinkListener;
private Map<ChainLink, ChainLinkView> linkViews = new TreeMap<ChainLink, ChainLinkView>(
ChainLink.getCloneComparator());
private Collection<NodeWrapper> selectedNodes;
public ChainNavigator() {
super();
setLayout(new GridBagLayout());
chainPanel = new JPanel();
chainPanel.setLayout(new FlowLayout(FlowLayout.LEADING));
GridBagConstraints constraints = new GridBagConstraints();
constraints.anchor = GridBagConstraints.LINE_START;
constraints.fill = GridBagConstraints.HORIZONTAL;
constraints.weightx = 1;
constraints.gridheight = 2;
JScrollPane chainScrollPane = new JScrollPane(chainPanel);
chainScrollPane
.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
chainScrollPane
.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
chainScrollPane.setBorder(BorderFactory.createEmptyBorder());
add(chainScrollPane, constraints);
clearAction = new ClearChainAction();
constraints.anchor = GridBagConstraints.LINE_END;
constraints.fill = GridBagConstraints.HORIZONTAL;
constraints.weightx = 0;
constraints.gridheight = 1;
constraints.gridx = 1;
add(new JButton(clearAction), constraints);
oneOutAction = new AbstractAction("Next", new ImageIcon(PLUS_ONE_ICON)) {
@Override
public void actionPerformed(ActionEvent e) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
if (chain != null) {
chain.add(null, null);
}
}
});
}
};
oneOutAction.putValue(Action.LONG_DESCRIPTION,
"Show all direct neighbours of the last chain link");
add(new JButton(oneOutAction), constraints);
}
@Override
public void setModel(ParrotModel model) {
super.setModel(model);
if (model == null) {
return;
}
chain = new Chain(model);
// make sure the 'clear' action is in the right state
// (whenever the last link itself changes)
lastLinkListener = new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
clearAction.setEnabled(chain.canClear());
}
};
// initially listen to the last link
if (chain.getLastLink() != null) {
chain.getLastLink().addPropertyChangeListener(lastLinkListener);
}
// make sure the 'clear' action is in the right state
// and make sure @code{lastLinkListener} is listening to the right link
// and make sure the right link views are being shown
// (whenever the chain changes)
chain.addPropertyChangeListener(new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent pce) {
// make sure the 'clear' action is in the right
// state
clearAction.setEnabled(chain.canClear());
oneOutAction.setEnabled(chain.canAddAnyAny());
String propName = pce.getPropertyName();
if (propName.equals(Chain.LAST_LINK_PROPERTY)) {
// make sure @code{lastLinkListener} is listening to
// the right link
if (pce.getOldValue() != null
&& pce.getOldValue() instanceof ChainLink) {
((ChainLink) pce.getOldValue())
.removePropertyChangeListener(lastLinkListener);
}
if (pce.getNewValue() != null
&& pce.getNewValue() instanceof ChainLink) {
((ChainLink) pce.getNewValue())
.addPropertyChangeListener(lastLinkListener);
}
} else if (propName.equals(Chain.SIZE_PROPERTY)
|| propName.equals(Chain.CONTENTS_PROPERTY)) {
if (restrictingFilter != null
&& restrictingFilter.sameChain(chain)) {
// nothing has changed
// -> ignore
return;
}
Filter oldRestrictingFilter = restrictingFilter;
Filter oldHighlightingFilter = highlightingFilter;
if (chain.isEmpty()) {
restrictingFilter = null;
highlightingFilter = null;
} else {
restrictingFilter = new RestrictToChainFilter(chain);
highlightingFilter = new HighlightChainFilter(
restrictingFilter);
}
replaceFilter(oldRestrictingFilter, restrictingFilter);
replaceFilter(oldHighlightingFilter, highlightingFilter);
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
updateChainPanel();
}
});
}
}
});
// initially put 'clear' action into the right state
clearAction.setEnabled(chain.canClear());
oneOutAction.setEnabled(chain.canAddAnyAny());
// initially show all links in the chain
updateChainPanel();
}
@Override
protected void activateFilters() {
if (getModel() == null) {
return;
}
if (restrictingFilter != null) {
applyFilter(restrictingFilter);
} else {
if (selectedNodes != null && !selectedNodes.isEmpty()) {
NodeWrapper firstSelectedNode = selectedNodes.iterator().next();
if (firstSelectedNode != null) {
highlightingFilter = null;
chain.expandOrRestart(firstSelectedNode);
}
}
}
if (highlightingFilter != null) {
applyFilter(highlightingFilter);
}
}
@Override
protected void deactivateFilters() {
if (getModel() == null) {
return;
}
if (restrictingFilter != null) {
removeFilter(restrictingFilter);
}
if (highlightingFilter != null) {
removeFilter(highlightingFilter);
}
}
@Override
public List<Action> getActionsForNode(final NodeWrapper currentNode) {
List<Action> actions = new ArrayList<Action>();
if (!currentNode.isOntResource()) {
// don't deal with literals etc
return actions;
} else {
OntResource res = currentNode.getOntResource();
if (res.isProperty()) {
// don't deal with properties
return actions;
}
}
return actions;
}
@Override
public List<Action> getActionsForType(NodeWrapper type) {
if (!type.isType()) {
return getActionsForNode(type);
}
List<Action> actions = new ArrayList<Action>();
return actions;
}
@Override
public String getNavigatorName() {
return NAME;
}
@Override
public String getAcceleratorKey() {
return ACCELERATOR_KEY;
}
private ChainLinkView createLinkComponent(final ChainLink link) {
final ChainLinkView chainComponent = new ChainLinkView(chain, link);
final RemoveFromChainAction removeAction = new RemoveFromChainAction(
link);
removeAction.setEnabled(chain.canRemove(link));
chainComponent.setCanChange(chain.canChange(link));
chain.addPropertyChangeListener(new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent pce) {
removeAction.setEnabled(chain.canRemove(link));
chainComponent.setCanChange(chain.canChange(link));
}
});
chainComponent.setRemoveAction(removeAction);
return chainComponent;
}
@Override
public void picked(NodeWrapper node) {
if (!isVisible()) {
// ignore
return;
}
// only react to selection of individuals (and not, eg, properties)
if (node.isType() || !node.isOntResource()
|| !node.getOntResource().isIndividual()) {
return;
}
chain.expandOrRestart(node);
}
@Override
public void setSelectedNodes(Collection<NodeWrapper> selected) {
selectedNodes = selected;
}
@Override
public boolean tellSelectionWhenShown() {
return true;
}
private void updateChainPanel() {
chainPanel.removeAll();
Map<ChainLink, ChainLinkView> newLinkViews = new HashMap<ChainLink, ChainLinkView>();
for (ChainLink link : chain.getLinks()) {
ChainLinkView view;
if (linkViews.containsKey(link)) {
view = linkViews.get(link);
} else {
view = createLinkComponent(link);
}
newLinkViews.put(link, view);
view.setCanChange(chain.canChange(link));
chainPanel.add(view);
}
linkViews.clear();
linkViews = newLinkViews;
revalidate();
repaint();
}
private final class ClearChainAction extends AbstractAction {
public ClearChainAction() {
super("Clear", new ImageIcon(CLEAR_ICON));
super.putValue(Action.LONG_DESCRIPTION,
"Remove all items from the chain");
}
@Override
public void actionPerformed(ActionEvent arg0) {
if (!chain.canClear()) {
return;
}
removeFilter(restrictingFilter);
if (highlightingFilter != null) {
removeFilter(highlightingFilter);
}
chain.clear();
}
}
private final class RemoveFromChainAction extends AbstractAction {
private final ChainLink link;
private RemoveFromChainAction(ChainLink link) {
super("Remove from chain");
this.link = link;
putValue(Action.MNEMONIC_KEY, KeyEvent.VK_R);
}
@Override
public void actionPerformed(ActionEvent e) {
if (!chain.canRemove(link)) {
return;
}
chain.remove(link);
}
}
}