/*
* 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.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.beans.PropertyChangeEvent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.swing.Action;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import net.schweerelos.parrot.model.Filter;
import net.schweerelos.parrot.model.NodeWrapper;
import net.schweerelos.parrot.model.ParrotModel;
import net.schweerelos.parrot.model.Filter.Mode;
import net.schweerelos.parrot.model.filters.IntervalChainBasedFilter;
import net.schweerelos.parrot.model.filters.TimeBasedFilter;
import net.schweerelos.parrot.timeline.IntervalListener;
import net.schweerelos.timeline.model.IntervalChain;
import net.schweerelos.timeline.model.Timeline;
import net.schweerelos.timeline.ui.ColorKeys;
import net.schweerelos.timeline.ui.TimelinePanel;
import org.joda.time.Interval;
@SuppressWarnings("serial")
public class TimelineNavigator extends AbstractNavigatorPanel {
private static final String INTERVAL_LABEL_DATE_FORMAT = "d MMM yyyy";
private static final Color SELECTED_OUTLINE = UIConstants.ACCENT_MEDIUM;
private static final Color LABEL = UIConstants.ENVIRONMENT_MEDIUM;
private static final Color BACKGROUND_ODD = UIConstants.ENVIRONMENT_LIGHTEST;
private static final Color BACKGROUND = Color.WHITE;
private static final Color INTERVAL_FILL = UIConstants.SECOND_ACCENT_LIGHT;
private static final Color INTERVAL_OUTLINE = UIConstants.SECOND_ACCENT_MEDIUM;
private static final Color HISTOGRAM_FILL = UIConstants.THIRD_ACCENT_LIGHT;
private static final String NAME = "Timeline";
private static final String ACCELERATOR_KEY = "T";
private TimelinePanel<NodeWrapper> timelinePanel;
private Filter lastHighlightFilter;
private Filter lastRestrictingFilter;
private IntervalListener intervalListener;
private ListSelectionListener selectionListener;
private JLabel intervalLabel;
public TimelineNavigator() {
super();
setLayout(new GridBagLayout());
GridBagConstraints constraints = new GridBagConstraints();
constraints.gridwidth = GridBagConstraints.REMAINDER;
Map<ColorKeys, Color> colors = new HashMap<ColorKeys, Color>();
colors.put(ColorKeys.Background, BACKGROUND);
colors.put(ColorKeys.BackgroundOdd, BACKGROUND_ODD);
colors.put(ColorKeys.Label, LABEL);
colors.put(ColorKeys.LabelOdd, LABEL);
colors.put(ColorKeys.SelectedOutline, SELECTED_OUTLINE);
colors.put(ColorKeys.IntervalFill, INTERVAL_FILL);
colors.put(ColorKeys.IntervalOutline, INTERVAL_OUTLINE);
colors.put(ColorKeys.HistogramFill, HISTOGRAM_FILL);
constraints.weightx = 1;
constraints.weighty = 0;
constraints.insets.bottom = 5;
constraints.fill = GridBagConstraints.HORIZONTAL;
constraints.anchor = GridBagConstraints.LINE_START;
intervalLabel = new JLabel(" ");
intervalLabel.setFont(intervalLabel.getFont().deriveFont(Font.PLAIN));
add(intervalLabel, constraints);
constraints.weightx = 0;
constraints.weighty = 1;
constraints.insets.bottom = 17;
constraints.fill = GridBagConstraints.BOTH;
timelinePanel = new TimelinePanel<NodeWrapper>(colors);
JScrollPane scrollPane = new JScrollPane(timelinePanel);
scrollPane.setAutoscrolls(true);
scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
add(scrollPane, constraints);
constraints.fill = GridBagConstraints.NONE;
constraints.anchor = GridBagConstraints.BASELINE_LEADING;
constraints.gridwidth = 1;
constraints.weighty = 0;
constraints.weightx = 0;
constraints.insets.bottom = 0;
constraints.insets.right = 5;
add(new JButton(timelinePanel.getZoomInAction()), constraints);
constraints.weightx = 1;
constraints.insets.right = 0;
add(new JButton(timelinePanel.getZoomOutAction()), constraints);
}
@Override
protected void deactivateFilters() {
if (getModel() == null) {
return;
}
if (lastHighlightFilter != null) {
removeFilter(lastHighlightFilter);
}
if (lastRestrictingFilter != null) {
removeFilter(lastRestrictingFilter);
}
}
@Override
protected void activateFilters() {
if (getModel() == null) {
return;
}
if (lastHighlightFilter != null) {
applyFilter(lastHighlightFilter);
}
if (lastRestrictingFilter != null) {
applyFilter(lastRestrictingFilter);
}
}
@Override
public void setModel(ParrotModel model) {
super.setModel(model);
if (model == null) {
timelinePanel.clearAll();
return;
}
if (selectionListener != null) {
timelinePanel.removeListSelectionListener(selectionListener);
}
if (intervalListener != null) {
timelinePanel.getModel().removeIntervalListener(intervalListener);
}
final Timeline<NodeWrapper> timeline = new Timeline<NodeWrapper>(model
.getTimedThings());
intervalListener = new TimelineIntervalListener();
timeline.addIntervalListener(intervalListener);
timeline.addIntervalListener(new IntervalListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
updateIntervalLabel(timeline);
}
});
}
});
timelinePanel.setModel(timeline);
updateIntervalLabel(timeline);
selectionListener = new SelectionListener();
timelinePanel.addListSelectionListener(selectionListener);
}
@Override
public List<Action> getActionsForNode(NodeWrapper currentNode) {
ArrayList<Action> result = new ArrayList<Action>();
// // TODO #5 show node in timeline
// if (currentNode.isOntResource()) {
// OntResource ontResource = currentNode.getOntResource();
// if (TimedThingsHelper.isTimedThing(ontResource, getModel())) {
// Action showInTimeline = new AbstractAction("Show in timeline") {
// @Override
// public void actionPerformed(ActionEvent e) {
// // TODO #5 show node in timeline
// }
// };
// showInTimeline.setEnabled(false);
// result.add(showInTimeline);
// }
// }
return result;
}
@Override
public List<Action> getActionsForType(NodeWrapper type) {
// timeline doesn't deal with types
return Collections.emptyList();
}
@Override
public String getNavigatorName() {
return NAME;
}
@Override
public String getAcceleratorKey() {
return ACCELERATOR_KEY;
}
private void updateIntervalLabel(final Timeline<NodeWrapper> timeline) {
StringBuilder intervalText = new StringBuilder();
intervalText.append("<html>");
intervalText.append(timeline.getIncrementMode().toString());
intervalText.append(" between ");
intervalText.append(timeline.getStart().toString(INTERVAL_LABEL_DATE_FORMAT));
intervalText.append(" and ");
intervalText.append(timeline.getEnd().toString(INTERVAL_LABEL_DATE_FORMAT));
intervalText.append("</html>");
intervalLabel.setText(intervalText.toString());
}
private final class TimelineIntervalListener implements IntervalListener {
@Override
public void propertyChange(PropertyChangeEvent evt) {
final Interval interval = (Interval) evt.getNewValue();
new SwingWorker<Void, Void>() {
@Override
protected Void doInBackground() throws Exception {
TimeBasedFilter filter = new TimeBasedFilter(interval,
getModel());
filter.setMode(Mode.RESTRICT);
if (lastRestrictingFilter != null) {
removeFilter(lastRestrictingFilter);
}
lastRestrictingFilter = filter;
applyFilter(lastRestrictingFilter);
return null;
}
@Override
protected void done() {
revalidate();
}
}.execute();
}
}
private final class SelectionListener implements ListSelectionListener {
@Override
public void valueChanged(ListSelectionEvent e) {
if (e.getValueIsAdjusting() || getModel() == null) {
return;
}
final IntervalChain<NodeWrapper> chain = timelinePanel
.getSelections();
new SwingWorker<Void, Void>() {
@Override
protected Void doInBackground() throws Exception {
IntervalChainBasedFilter filter = new IntervalChainBasedFilter(
chain, getModel());
filter.setMode(Mode.HIGHLIGHT);
if (lastHighlightFilter != null) {
removeFilter(lastHighlightFilter);
}
lastHighlightFilter = filter;
applyFilter(lastHighlightFilter);
return null;
}
}.execute();
}
}
}