/*
* 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.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.ExecutionException;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.ComboBoxModel;
import javax.swing.DefaultComboBoxModel;
import javax.swing.DefaultListCellRenderer;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.SwingWorker;
import net.schweerelos.parrot.model.NodeWrapper;
import net.schweerelos.parrot.model.filters.Chain;
import net.schweerelos.parrot.model.filters.ChainLink;
import org.apache.log4j.Logger;
@SuppressWarnings("serial")
public class ChainLinkView extends JPanel {
private static final String ANY_TYPE = "any";
private static final Object ANY_INSTANCE = "any";
private static final String REMOVE_ICON = "images/remove.png";
private static final Color COLOR_BORDER = UIConstants.ENVIRONMENT_SHADOW_DARK;
private JButton removeButton;
private JComboBox instanceCombo;
private JComboBox typesCombo;
private ToStringComparator comparator = new ToStringComparator();
private ToStringListCellRenderer renderer = new ToStringListCellRenderer(30);
private ChainLink link;
private Chain chain;
public ChainLinkView(Chain chain, ChainLink link) {
this.chain = chain;
this.link = link;
typesCombo = new JComboBox();
typesCombo.setToolTipText("Type");
typesCombo.setModel(getTypesForInstance(link.getInstance()));
if (link.hasType()) {
typesCombo.setSelectedItem(link.getType());
} else {
typesCombo.setSelectedItem(ANY_TYPE);
}
typesCombo.setRenderer(renderer);
typesCombo.setFont(typesCombo.getFont().deriveFont(Font.PLAIN));
instanceCombo = new JComboBox();
instanceCombo.setToolTipText("Item");
instanceCombo.setModel(getInstancesForType(link.getType()));
if (link.hasInstance()) {
instanceCombo.setSelectedItem(link.getInstance());
} else {
instanceCombo.setSelectedItem(ANY_INSTANCE);
}
instanceCombo.setRenderer(renderer);
instanceCombo.setFont(instanceCombo.getFont().deriveFont(Font.PLAIN));
typesCombo.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
Object selectedType = typesCombo.getSelectedItem();
if (selectedType instanceof NodeWrapper) {
NodeWrapper type = (NodeWrapper) selectedType;
getChainLink().setType(type);
} else if (selectedType.toString().equals(ANY_TYPE)) {
getChainLink().setType(null);
}
// ignore otherwise
}
});
instanceCombo.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
Object selectedInstance = instanceCombo.getSelectedItem();
if (selectedInstance instanceof NodeWrapper) {
NodeWrapper instance = (NodeWrapper) selectedInstance;
getChainLink().setInstance(instance);
} else if (selectedInstance.toString().equals(ANY_INSTANCE)) {
getChainLink().setInstance(null);
}
// ignore otherwise
}
});
setBorder(BorderFactory.createLineBorder(COLOR_BORDER, 1));
setLayout(new GridBagLayout());
GridBagConstraints constraints = new GridBagConstraints();
constraints.gridwidth = 1;
constraints.gridheight = 1;
constraints.gridx = 0;
constraints.anchor = GridBagConstraints.LINE_START;
constraints.fill = GridBagConstraints.HORIZONTAL;
constraints.weightx = 1.0;
constraints.insets = new Insets(6, 6, 0, 0);
add(typesCombo, constraints);
constraints.anchor = GridBagConstraints.FIRST_LINE_START;
constraints.weighty = 1.0;
constraints.insets = new Insets(5, 6, 5, 0);
add(instanceCombo, constraints);
constraints.gridx = 1;
constraints.gridy = 0;
constraints.gridheight = GridBagConstraints.REMAINDER;
constraints.anchor = GridBagConstraints.CENTER;
constraints.fill = GridBagConstraints.NONE;
constraints.weightx = 0;
constraints.insets = new Insets(6, 5, 5, 5);
removeButton = new JButton();
removeButton.setPreferredSize(new Dimension(16, 16));
add(removeButton, constraints);
link.addPropertyChangeListener(new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent pce) {
if (pce.getPropertyName().equals(ChainLink.INSTANCE_PROPERTY)) {
if (pce.getNewValue() == null) {
// TODO adjust type combo accordingly?
} else if (pce.getNewValue() instanceof NodeWrapper) {
final NodeWrapper instance = (NodeWrapper) pce
.getNewValue();
instanceCombo.setSelectedItem(instance);
// did it work? it won't if instance isn't in
// the combo box model yet
if (!instanceCombo.getSelectedItem().equals(instance)) {
new OneInstanceModelMaker(instance).execute();
}
new TypesForInstanceModelMaker(instance).execute();
}
} else if (pce.getPropertyName()
.equals(ChainLink.TYPE_PROPERTY)) {
if (pce.getNewValue() == null) {
// TODO adjust instance combo accordingly?
} else if (pce.getNewValue() instanceof NodeWrapper) {
final NodeWrapper type = (NodeWrapper) pce
.getNewValue();
typesCombo.setSelectedItem(type);
// did it work? it won't if type isn't in the combo box
// model yet
if (!typesCombo.getSelectedItem().equals(type)) {
// it didn't work, we need to add type to the combo
// box model first
new OneTypeModelMaker(type).execute();
}
new InstancesForTypeModelMaker(type).execute();
}
}
}
});
}
private ComboBoxModel getTypesForInstance(NodeWrapper instance) {
List<Object> typesList = new ArrayList<Object>();
if (instance == null) {
typesList.addAll(chain.getPossibleTypes(link));
Collections.sort(typesList, comparator);
} else {
if (!instance.isType()) {
typesList.addAll(chain.getPossibleTypesForIndividual(instance));
Collections.sort(typesList, comparator);
}
}
typesList.add(0, ANY_TYPE);
return new DefaultComboBoxModel(typesList.toArray());
}
private ComboBoxModel getInstancesForType(NodeWrapper type) {
List<Object> instanceList = new ArrayList<Object>();
if (type != null) {
if (type.isType()) {
instanceList.addAll(chain.getPossibleInstancesForType(link));
Collections.sort(instanceList, comparator);
} else {
instanceList.add(type);
}
}
instanceList.add(0, ANY_INSTANCE);
return new DefaultComboBoxModel(instanceList.toArray());
}
public void setRemoveAction(Action removeAction) {
removeButton.setAction(removeAction);
removeButton.setIcon(new ImageIcon(REMOVE_ICON));
removeButton.setText(null);
removeButton.setContentAreaFilled(false);
removeButton.setIconTextGap(0);
removeButton.setBorderPainted(false);
}
public void setCanChange(boolean canChange) {
typesCombo.setEnabled(canChange);
instanceCombo.setEnabled(canChange);
}
public ChainLink getChainLink() {
return link;
}
@Override
public Dimension getMinimumSize() {
return getLayout().minimumLayoutSize(this);
}
@Override
public Dimension getPreferredSize() {
return getLayout().preferredLayoutSize(this);
}
private final class TypesForInstanceModelMaker extends
SwingWorker<ComboBoxModel, Void> {
private final NodeWrapper instance;
private TypesForInstanceModelMaker(NodeWrapper instance) {
this.instance = instance;
}
@Override
protected ComboBoxModel doInBackground() throws Exception {
return getTypesForInstance(instance);
}
@Override
protected void done() {
try {
typesCombo.setModel(get());
typesCombo.setSelectedItem(ANY_TYPE);
} catch (InterruptedException e) {
Logger logger = Logger.getLogger(ChainLinkView.this.getClass());
logger.warn("problem setting instances model", e);
} catch (ExecutionException e) {
Logger logger = Logger.getLogger(ChainLinkView.this.getClass());
logger.warn("problem setting instances model", e);
}
}
}
private final class InstancesForTypeModelMaker extends
SwingWorker<ComboBoxModel, Void> {
private final NodeWrapper type;
private InstancesForTypeModelMaker(NodeWrapper type) {
this.type = type;
}
@Override
protected ComboBoxModel doInBackground() throws Exception {
return getInstancesForType(type);
}
@Override
protected void done() {
try {
instanceCombo.setModel(get());
// does that mean we have to ignore the
// event that this
// generates?
instanceCombo.setSelectedItem(ANY_INSTANCE);
} catch (InterruptedException e) {
Logger logger = Logger.getLogger(ChainLinkView.this.getClass());
logger.warn("problem setting instances model", e);
} catch (ExecutionException e) {
Logger logger = Logger.getLogger(ChainLinkView.this.getClass());
logger.warn("problem setting instances model", e);
}
}
}
private final class OneTypeModelMaker extends
SwingWorker<ComboBoxModel, Void> {
private final NodeWrapper type;
private OneTypeModelMaker(NodeWrapper type) {
this.type = type;
}
@Override
protected ComboBoxModel doInBackground() throws Exception {
List<Object> typeModel = new ArrayList<Object>();
typeModel.add(ANY_TYPE);
typeModel.add(type);
return new DefaultComboBoxModel(typeModel.toArray());
}
@Override
protected void done() {
try {
typesCombo.setModel(get());
// now try again
typesCombo.setSelectedItem(type);
} catch (InterruptedException e) {
Logger logger = Logger.getLogger(ChainLinkView.this.getClass());
logger.warn("problem setting types model", e);
} catch (ExecutionException e) {
Logger logger = Logger.getLogger(ChainLinkView.this.getClass());
logger.warn("problem setting types model", e);
}
}
}
private final class OneInstanceModelMaker extends
SwingWorker<ComboBoxModel, Void> {
private final NodeWrapper instance;
private OneInstanceModelMaker(NodeWrapper instance) {
this.instance = instance;
}
@Override
protected ComboBoxModel doInBackground() throws Exception {
// it didn't work, we need to add instance
// to the combo box model first
List<Object> instanceModel = new ArrayList<Object>();
instanceModel.add(ANY_INSTANCE);
instanceModel.add(instance);
return new DefaultComboBoxModel(instanceModel.toArray());
}
@Override
protected void done() {
try {
instanceCombo.setModel(get());
// now try again
instanceCombo.setSelectedItem(instance);
} catch (InterruptedException e) {
Logger logger = Logger.getLogger(ChainLinkView.this.getClass());
logger.warn("problem setting types model", e);
} catch (ExecutionException e) {
Logger logger = Logger.getLogger(ChainLinkView.this.getClass());
logger.warn("problem setting types model", e);
}
}
}
private static final class ToStringComparator implements Comparator<Object> {
@Override
public int compare(Object o1, Object o2) {
return o1.toString().compareTo(o2.toString());
}
}
private static final class ToStringListCellRenderer extends
DefaultListCellRenderer {
private boolean shorten = false;
private int maxCharacters = 0;
public ToStringListCellRenderer() {
// leave fields at default value
}
public ToStringListCellRenderer(int maxCharacters) {
if (maxCharacters >= 0) {
this.shorten = true;
this.maxCharacters = maxCharacters;
}
}
@Override
public Component getListCellRendererComponent(JList list, Object value,
int index, boolean isSelected, boolean cellHasFocus) {
String toStringValue = value.toString();
if (shorten && toStringValue.length() > maxCharacters) {
int lastSpaceAt = toStringValue.lastIndexOf(" ", maxCharacters);
// if there is a space character not too far before the cut-off point,
// cut off at the space instead
if (lastSpaceAt < maxCharacters - 1 && maxCharacters - lastSpaceAt < Math.max(maxCharacters / 4.0, 5)) {
toStringValue = toStringValue.substring(0, lastSpaceAt) + " ...";
} else {
toStringValue = toStringValue.substring(0, maxCharacters) + "...";
}
}
return super.getListCellRendererComponent(list, toStringValue,
index, isSelected, cellHasFocus);
}
}
}