/********************************************************************************
* *
* (c) Copyright 2010 Verizon Communications USA and The Open University UK *
* *
* This software is freely distributed in accordance with *
* the GNU Lesser General Public (LGPL) license, version 3 or later *
* as published by the Free Software Foundation. *
* For details see LGPL: http://www.fsf.org/licensing/licenses/lgpl.html *
* and GPL: http://www.fsf.org/licensing/licenses/gpl-3.0.html *
* *
* This software is provided by the copyright holders and contributors "as is" *
* and any express or implied warranties, including, but not limited to, the *
* implied warranties of merchantability and fitness for a particular purpose *
* are disclaimed. In no event shall the copyright owner or contributors be *
* liable for any direct, indirect, incidental, special, exemplary, or *
* consequential damages (including, but not limited to, procurement of *
* substitute goods or services; loss of use, data, or profits; or business *
* interruption) however caused and on any theory of liability, whether in *
* contract, strict liability, or tort (including negligence or otherwise) *
* arising in any way out of the use of this software, even if advised of the *
* possibility of such damage. *
* *
********************************************************************************/
package com.compendium.ui.movie;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.List;
import java.util.Vector;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.border.EmptyBorder;
import javax.swing.border.LineBorder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.compendium.LanguageProperties;
import com.compendium.ProjectCompendium;
import com.compendium.core.datamodel.NodePosition;
import com.compendium.core.datamodel.NodePositionTime;
import com.compendium.core.datamodel.NodeSummary;
import com.compendium.core.datamodel.TimeMapView;
import com.compendium.ui.UIButton;
import com.compendium.ui.UIButtonPanel;
import com.compendium.ui.UIImageButton;
import com.compendium.ui.UIImages;
import com.compendium.ui.UINode;
import com.compendium.ui.dialogs.UINodeContentDialog;
import com.compendium.ui.panels.UITimeMilliSecondPanel;
/**
* This panel manages node time periods.
*
* @author Michelle Bachler
*/
public class UINodeTimeViewPanel extends JPanel implements PropertyChangeListener {
/**
* class's own logger
*/
final Logger log = LoggerFactory.getLogger(getClass());
/** The background colour when the calling timespan is known**/
private static final Color SELECTED_BACKGROUND_COLOUR = new Color(255,255,128);
/** The background colour of a time span property set**/
private static final Color BACKGROUND_COLOUR = new Color(233, 234, 253);
/** The parent dialog that this panel is in.*/
private UINodeContentDialog oParentDialog = null;
/** The current node this is the contents for - if in a map.*/
private UINode oUINode = null;
/** The current node data this is the contents for.*/
private NodeSummary oNode = null;
private TimeMapView oTimeMapView = null;
private Vector vtTimes = new Vector();
/** The main panel for all the rows of times.*/
private JPanel mainPanel = null;
private boolean localChange = true;
private GridBagLayout layout = null;
private GridBagConstraints cons = null;
private NodePositionTime oCallingSpan = null;
/** The button to close the parent dialog.*/
private UIButton pbClose = null;
/** The panel to scroll to if span known.*/
private JPanel oScrollToPanel = null;
/**
* Constructor.
* @param parent the parent frame for the dialog this panel is in.
* @param uinode the current node this is the contents for - if in a map.
* @param tabbedPane the parent dialog this panel is in.
* @param span the originating span that called this popup (so can highlight it)
*/
public UINodeTimeViewPanel(JFrame parent, UINode uinode, TimeMapView oView, UINodeContentDialog tabbedPane, NodePositionTime span) {
super();
oParentDialog = tabbedPane;
oUINode = uinode;
NodePosition pos = uinode.getNodePosition();
pos.addPropertyChangeListener(this);
oNode = oUINode.getNode();
oTimeMapView = oView;
this.oTimeMapView.addPropertyChangeListener(this);
this.oCallingSpan = span;
init();
}
/**
* Constructor.
* @param parent, the parent frame for the dialog this panel is in.
* @param uinode com.compendium.ui.UINode, the current node this is the contents for - if in a map.
* @param tabbedPane, the parent dialog this panel is in.
*/
public UINodeTimeViewPanel(JFrame parent, UINode uinode, TimeMapView oView, UINodeContentDialog tabbedPane) {
super();
oParentDialog = tabbedPane;
oUINode = uinode;
NodePosition pos = uinode.getNodePosition();
pos.addPropertyChangeListener(this);
oNode = oUINode.getNode();
oTimeMapView = oView;
this.oTimeMapView.addPropertyChangeListener(this);
init();
}
private void init() {
mainPanel = new JPanel();
setLayout(new BorderLayout());
JScrollPane scroll = new JScrollPane(mainPanel, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
add(scroll, BorderLayout.CENTER);
add(createButtonPanel(), BorderLayout.SOUTH);
this.addComponentListener(new ComponentAdapter() {
public void componentShown(ComponentEvent e) {
if (oScrollToPanel != null) {
oScrollToPanel.scrollRectToVisible(oScrollToPanel.getBounds());
}
}
});
refreshTimes();
setSize(mainPanel.getPreferredSize().width+30, oParentDialog.getPreferredSize().height);
setPreferredSize(new Dimension(mainPanel.getPreferredSize().width+30, oParentDialog.getPreferredSize().height));
}
//*********************** PROPERTY CHANGE LISTENER *************************/
/**
* Handles property change events for the TimeMapView else calls super.
* @param evt, the associated PropertyChangeEvent object.
*/
public void propertyChange(PropertyChangeEvent evt) {
//log.info("property change="+evt.getPropertyName());
String prop = evt.getPropertyName();
Object source = evt.getSource();
//Object oldvalue = evt.getOldValue();
Object newvalue = evt.getNewValue();
if (source instanceof TimeMapView) {
if (newvalue instanceof NodePositionTime) {
NodePositionTime newtime = (NodePositionTime)newvalue;
if (newtime.getNode().getId() == oNode.getId()) {
if (prop.equals(TimeMapView.TIME_ADDED_PROPERTY)) {
vtTimes.add(newvalue);
refreshTimes();
} else if (prop.equals(TimeMapView.TIME_CHANGED_PROPERTY)) {
int count = vtTimes.size();
for (int i=0; i< count; i++) {
NodePositionTime nextTime = (NodePositionTime)vtTimes.elementAt(i);
if (nextTime.getId().equals(newtime.getId())) {
vtTimes.remove(nextTime);
vtTimes.add(newvalue);
break;
}
}
if (!localChange) {
refreshTimes();
} else {
localChange = false;
}
}
}
}
if (prop.equals(TimeMapView.TIME_REMOVED_PROPERTY)) {
String id = (String)newvalue;
int count = vtTimes.size();
for (int i=0; i< count; i++) {
NodePositionTime nextTime = (NodePositionTime)vtTimes.elementAt(i);
if (nextTime.getId().equals(id)) {
vtTimes.remove(nextTime);
break;
}
}
refreshTimes();
}
} else if (source instanceof NodePosition) {
// if the user moves the node then on mouse release, update the relevant time span
if (prop.equals(NodePosition.POSITION_PROPERTY)) {
refreshTimes();
}
}
}
public void focusNode() {
Hashtable times = oTimeMapView.getTimesForNode(oNode.getId());
vtTimes = new Vector(times.size());
for (Enumeration e = times.elements(); e.hasMoreElements();) {
NodePositionTime nextTime = (NodePositionTime)e.nextElement();
vtTimes.add(nextTime);
}
}
public synchronized void refreshTimes() {
mainPanel.removeAll();
layout = null;
oScrollToPanel = null;
layout = new GridBagLayout();
mainPanel.setLayout(layout);
cons = new GridBagConstraints();
cons.fill = GridBagConstraints.HORIZONTAL;
cons.anchor = GridBagConstraints.NORTHWEST;
cons.gridwidth = GridBagConstraints.REMAINDER;
cons.weightx = 2;
cons.ipady = 4;
Hashtable times = oTimeMapView.getTimesForNode(oNode.getId());
vtTimes = new Vector(times.size());
for (Enumeration e = times.elements(); e.hasMoreElements();) {
NodePositionTime nextTime = (NodePositionTime)e.nextElement();
vtTimes.add(nextTime);
}
Object[] sa = new Object[vtTimes.size()];
vtTimes.copyInto(sa);
List l = Arrays.asList(sa);
Collections.sort(l, new Comparator() {
public int compare(Object o1, Object o2) {
NodePositionTime data1 = (NodePositionTime)o1;
NodePositionTime data2 = (NodePositionTime)o2;
long s1 = data1.getTimeToShow();
long s2 = data2.getTimeToShow();
return (new Double(s1).compareTo(new Double(s2)));
}
});
vtTimes.removeAllElements();
vtTimes.addAll(l);
int count = vtTimes.size();
for (int i=0; i<count; i++) {
final NodePositionTime nextTime = (NodePositionTime)vtTimes.elementAt(i);
JPanel next = createRow(nextTime);
layout.setConstraints(next, cons);
mainPanel.add(next);
}
JButton button = new JButton(LanguageProperties.getString(LanguageProperties.MOVIE_BUNDLE, "UINodeTimeViewPanel.newSpan")); //$NON-NLS-1$
button.setToolTipText(LanguageProperties.getString(LanguageProperties.MOVIE_BUNDLE, "UINodeTimeViewPanel.newSpanTip")); //$NON-NLS-1$
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
try {
int count = vtTimes.size();
long biggestTime = 0;
for (int i=0; i<count; i++) {
final NodePositionTime nextTime = (NodePositionTime)vtTimes.elementAt(i);
if (nextTime.getTimeToHide() > biggestTime) {
biggestTime = nextTime.getTimeToHide();
}
}
Point p = oUINode.getLocation();
UITimeLinesController controller = ((UIMovieMapViewFrame)oUINode.getViewPane().getViewFrame()).getController();
long milliDefaultSpanLength = controller.getDefaultNodeTimeSpanLength();
oTimeMapView.addNodeTime(oNode.getId(), biggestTime+1000, biggestTime+milliDefaultSpanLength, p.x, p.y);
} catch(Exception ex) {
log.error("Error...", ex);
}
}
});
cons.weighty = 2;
cons.fill = GridBagConstraints.NONE;
cons.insets = new Insets(0,5,0,0);
layout.setConstraints(button, cons);
mainPanel.add(button);
//mainPanel.add(Box.createRigidArea(new Dimension(20, 400)));
mainPanel.validate();
repaint();
}
private JPanel createRow(NodePositionTime time) {
final NodePositionTime nextTime = time;
final NodeSummary oNode = nextTime.getNode();
//final String fsViewTimeNodeID = nextTime.getId();
final JLabel durationValue = new JLabel(""); //$NON-NLS-1$
JPanel durationPanel = new JPanel();
JPanel mainPanel = new JPanel(new BorderLayout());
mainPanel.setBorder(new EmptyBorder(5,5,5,5));
JPanel timePanel = new JPanel();
timePanel.setBorder(new LineBorder(Color.darkGray, 1));
GridBagLayout gb = new GridBagLayout();
GridBagConstraints gc = new GridBagConstraints();
timePanel.setLayout(gb);
gc.insets = new Insets(3,3,3,3);
gc.anchor = GridBagConstraints.WEST;
gc.weightx=1;
gc.weighty=1;
mainPanel.add(timePanel, BorderLayout.CENTER);
final UITimeMilliSecondPanel timePanelIn = new UITimeMilliSecondPanel(LanguageProperties.getString(LanguageProperties.MOVIE_BUNDLE, "UINodeTimeViewPanel.ShowAt")+": ", false, true); //$NON-NLS-1$
final UITimeMilliSecondPanel timePanelOut = new UITimeMilliSecondPanel(" "+LanguageProperties.getString(LanguageProperties.MOVIE_BUNDLE, "UINodeTimeViewPanel.hideAt")+": ", false, true); //$NON-NLS-1$
JPanel positionPanel = new JPanel();
if (oCallingSpan != null
&& time.getId().equals(oCallingSpan.getId())
&& vtTimes.size() > 1) {
timePanel.setBackground(SELECTED_BACKGROUND_COLOUR);
timePanelIn.setBackground(SELECTED_BACKGROUND_COLOUR);
timePanelOut.setBackground(SELECTED_BACKGROUND_COLOUR);
positionPanel.setBackground(SELECTED_BACKGROUND_COLOUR);
durationPanel.setBackground(SELECTED_BACKGROUND_COLOUR);
oScrollToPanel = mainPanel;
} else {
timePanel.setBackground(BACKGROUND_COLOUR);
timePanelIn.setBackground(BACKGROUND_COLOUR);
timePanelOut.setBackground(BACKGROUND_COLOUR);
durationPanel.setBackground(BACKGROUND_COLOUR);
positionPanel.setBackground(BACKGROUND_COLOUR);
}
//timePanelIn.setBorder(new LineBorder(Color.black, 1));
timePanelIn.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
long show = timePanelIn.getMilliSeconds();
if (nextTime.getTimeToShow() != show) {
boolean overlap = checkForOverlap(nextTime.getId(), show, nextTime.getTimeToHide());
if (overlap) {
ProjectCompendium.APP.displayError(LanguageProperties.getString(LanguageProperties.MOVIE_BUNDLE, "UINodeTimeViewPanel.errorOverlappingTime")); //$NON-NLS-1$
timePanelIn.setSeconds(nextTime.getTimeToShow());
} else if (show >= nextTime.getTimeToHide()) {
ProjectCompendium.APP.displayError(LanguageProperties.getString(LanguageProperties.MOVIE_BUNDLE, "UINodeTimeViewPanel.errorStartTime")); //$NON-NLS-1$
timePanelIn.setSeconds(nextTime.getTimeToShow());
} else {
nextTime.setTimeToShow(show);
localChange = true;
try {
NodePositionTime pos = oTimeMapView.updateNodeTime(nextTime.getId(),
oNode.getId(),
nextTime.getTimeToShow(),
nextTime.getTimeToHide(),
nextTime.getXPos(),
nextTime.getYPos());
durationValue.setText(formatDuration(nextTime.getTimeToHide()-nextTime.getTimeToShow()));
} catch (Exception ex) {
log.info("error:"+ex.getLocalizedMessage()); //$NON-NLS-1$
log.error("Error...", ex);
}
}
}
}
});
//timePanelOut.setBorder(new LineBorder(Color.black, 1));
timePanelOut.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
long hide = timePanelOut.getMilliSeconds();
if (nextTime.getTimeToHide() != hide) {
boolean overlap = checkForOverlap(nextTime.getId(), nextTime.getTimeToShow(), hide);
if (overlap) {
ProjectCompendium.APP.displayError(LanguageProperties.getString(LanguageProperties.MOVIE_BUNDLE, "UINodeTimeViewPanel.errorOverLappingTime")); //$NON-NLS-1$
timePanelOut.setSeconds(nextTime.getTimeToHide());
} else if (hide <= nextTime.getTimeToShow()) {
ProjectCompendium.APP.displayError(LanguageProperties.getString(LanguageProperties.MOVIE_BUNDLE, "UINodeTimeViewPanel.errorStopTime")); //$NON-NLS-1$
timePanelOut.setSeconds(nextTime.getTimeToHide());
} else {
nextTime.setTimeToHide(hide);
localChange = true;
try {
NodePositionTime pos = oTimeMapView.updateNodeTime(nextTime.getId(),
oNode.getId(),
nextTime.getTimeToShow(),
nextTime.getTimeToHide(),
nextTime.getXPos(),
nextTime.getYPos());
durationValue.setText(formatDuration(nextTime.getTimeToHide()-nextTime.getTimeToShow()));
} catch (Exception ex) {
log.info(LanguageProperties.getString(LanguageProperties.MOVIE_BUNDLE, "UINodeTimeViewPanel.error")+ex.getLocalizedMessage()); //$NON-NLS-1$
log.error("Error...", ex);
}
}
}
}
});
timePanelIn.setMilliSeconds(nextTime.getTimeToShow());
timePanelOut.setMilliSeconds(nextTime.getTimeToHide());
//timePanel.add(timePanelIn);
//timePanel.add(timePanelOut);
int x = nextTime.getXPos();
int y = nextTime.getYPos();
final JLabel label = new JLabel("x:"+x+" y:"+y); //$NON-NLS-1$ //$NON-NLS-2$
JButton button = new JButton(LanguageProperties.getString(LanguageProperties.MOVIE_BUNDLE, "UINodeTimeViewPanel.setButton")); //$NON-NLS-1$
button.setToolTipText(LanguageProperties.getString(LanguageProperties.MOVIE_BUNDLE, "UINodeTimeViewPanel.setButtonTip")); //$NON-NLS-1$
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
Point p = oUINode.getLocation();
try {
oTimeMapView.updateNodeTime(nextTime.getId(), nextTime.getNode().getId(), nextTime.getTimeToShow(), nextTime.getTimeToHide(), p.x, p.y);
} catch(Exception ex) {
log.error("Error...", ex);
}
}
});
UIImageButton delete = new UIImageButton(UIImages.get(UIImages.DELETE_ICON));
delete.setToolTipText(LanguageProperties.getString(LanguageProperties.MOVIE_BUNDLE, "UINodeTimeViewPanel.deleteButtonTip")); //$NON-NLS-1$
delete.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
try {
oTimeMapView.deleteNodeTime(nextTime.getId(), oNode.getId());
} catch(Exception ex) {
log.error("Error...", ex);
}
}
});
//positionPanel.setBorder(new LineBorder(Color.black, 1));
positionPanel.add(label);
positionPanel.add(button);
positionPanel.setPreferredSize(new Dimension(positionPanel.getPreferredSize().width + 6, timePanelOut.getPreferredSize().height));
JLabel durationLabel = new JLabel(LanguageProperties.getString(LanguageProperties.MOVIE_BUNDLE, "UINodeTimeViewPanel.visibleDuration")+": "); //$NON-NLS-1$
durationValue.setText(formatDuration(nextTime.getTimeToHide()-nextTime.getTimeToShow()));
durationPanel.add(durationLabel);
durationPanel.add(durationValue);
gc.gridy = 0;
gc.gridx = 0;
gc.gridwidth = 1;
gb.setConstraints(timePanelIn, gc);
timePanel.add(timePanelIn);
gc.gridy = 0;
gc.gridx = 1;
gc.gridwidth = 1;
gb.setConstraints(positionPanel, gc);
timePanel.add(positionPanel);
gc.gridy = 0;
gc.gridx = 2;
gc.gridwidth = GridBagConstraints.REMAINDER;
gb.setConstraints(delete, gc);
timePanel.add(delete);
gc.gridy = 1;
gc.gridx = 0;
gc.gridwidth = 1;
gb.setConstraints(timePanelOut, gc);
timePanel.add(timePanelOut);
gc.gridy = 1;
gc.gridx = 1;
gc.gridwidth = 2;
gb.setConstraints(durationPanel, gc);
timePanel.add(durationPanel);
//timePanel.add(positionPanel);
////timePanel.add(button);
//timePanel.add(delete);
return mainPanel;
}
/**
* Take the given time in milliseconds and return a string representing the time.
* @param duration
* @return
*/
private String formatDuration(long duration) {
String format = ""; //$NON-NLS-1$
long minutes = 0;
long hours = 0;
long seconds = duration/1000;
long milliseconds = duration%1000;
if (seconds > 59) {
minutes = seconds/60;
seconds = seconds%60;
if (minutes > 59) {
hours = minutes/60;
minutes = minutes%60;
}
}
long hours10 = hours / 10;
hours = hours % 10;
long minutes10 = minutes / 10;
minutes = minutes % 10;
long seconds10 = seconds / 10;
seconds = seconds % 10;
String milli = String.valueOf(milliseconds);
if (milli.length() == 3) {
milli = "0"+milli; //$NON-NLS-1$
} else if (milli.length() == 2) {
milli = "00"+milli; //$NON-NLS-1$
} else if (milli.length() == 1) {
milli = "000"+milli; //$NON-NLS-1$
}
format = new String ( "" + hours10 + hours + ":" + minutes10 + minutes + ":" + seconds10 + seconds + "." + milli ); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
return format;
}
/**
* Check to see if the passed time is inside another span or another span is inside it
* In which case there is a overlap.
* @param newTime the time to check
* @return true if there is an overlap with another time span, else false;
*/
private boolean checkForOverlap(String id, long start, long stop) {
int count = vtTimes.size();
boolean overlap = false;
for (int i=0; i<count; i++) {
NodePositionTime time = (NodePositionTime)vtTimes.elementAt(i);
if (!id.equals(time.getId())) {
overlap = time.checkForOverlap(start, stop);
if (overlap) break;
}
}
return overlap;
}
/**
* Create and return the button panel.
*/
private UIButtonPanel createButtonPanel() {
UIButtonPanel oButtonPanel = new UIButtonPanel();
pbClose = new UIButton(LanguageProperties.getString(LanguageProperties.MOVIE_BUNDLE, "UINodeTimeViewPanel.closeButton")); //$NON-NLS-1$
pbClose.setMnemonic(LanguageProperties.getString(LanguageProperties.MOVIE_BUNDLE, "UINodeTimeViewPanel.closeButtonMnemonic").charAt(0));
pbClose.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
oParentDialog.onCancel();
}
});
oButtonPanel.addButton(pbClose);
UIButton pbHelp = new UIButton(LanguageProperties.getString(LanguageProperties.MOVIE_BUNDLE, "UINodeTimeViewPanel.helpButton")); //$NON-NLS-1$
pbHelp.setMnemonic(LanguageProperties.getString(LanguageProperties.MOVIE_BUNDLE, "UINodeTimeViewPanel.helpButtonMnemonic").charAt(0));
ProjectCompendium.APP.mainHB.enableHelpOnButton(pbHelp, "node.times", ProjectCompendium.APP.mainHS); //$NON-NLS-1$
oButtonPanel.addHelpButton(pbHelp);
return oButtonPanel;
}
/**
* Set the default button for the parent dialog to be this panel's default button.
*/
public void setDefaultButton() {
oParentDialog.getRootPane().setDefaultButton(pbClose);
}
/**
* Save the time data.
*/
public void onUpdate() {
int count = vtTimes.size();
for (int i=0; i<count; i++) {
NodePositionTime nextTime = (NodePositionTime)vtTimes.elementAt(i);
try {
NodePositionTime pos = oTimeMapView.updateNodeTime(nextTime.getId(),
oNode.getId(),
nextTime.getTimeToShow(),
nextTime.getTimeToHide(),
nextTime.getXPos(),
nextTime.getYPos());
} catch (Exception ex) {
log.info("error "+": "+ex.getLocalizedMessage()); //$NON-NLS-1$
log.error("Error...", ex);
}
}
}
}