/**
* Copyright (c) 2014 Matthias Jaenicke <matthias.jaenicke@student.kit.edu>,
* Matthias Plappert <undkc@student.kit.edu>,
* Julien Duman <uncyc@student.kit.edu>,
* Christian Dreher <uaeef@student.kit.edu>,
* Wasilij Beskorovajnov <uajkm@student.kit.edu> and
* Aydin Tekin <aydin.tekin@student.kit.edu>
*
* Released under the MIT license (refer to LICENSE.md)
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package edu.kit.iks.Cryptographics;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.Stroke;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.geom.Rectangle2D;
import java.util.Iterator;
import java.util.List;
import javax.swing.JPanel;
import org.xnap.commons.i18n.I18n;
import edu.kit.iks.CryptographicsLib.AbstractVisualizationInfo;
import edu.kit.iks.CryptographicsLib.Configuration;
import edu.kit.iks.CryptographicsLib.VisualizationButton;
/**
* An instance of this class represents the view of a timeline
*
* @author Christian Dreher
*/
public class TimelineView extends JPanel implements ComponentListener {
/**
* The stroke width used for painting the timeline.
*/
private static final float TIMELINE_STROKE_WIDTH = 3.0f;
/**
* The stroke width used for painting the connection between marker and name label.
*/
private static final float LABEL_STROKE_WIDTH = 2.0f;
/**
* The radius of a marker.
*/
private static final int MARKER_RADIUS = 20;
/**
* The padding between elements.
*/
private static final int PADDING = 10;
/**
* The color of the timeline stroke.
*/
private static final Color TIMELINE_STROKE_COLOR = Color.BLACK;
/**
* The color of the stroke between the label and the marker.
*/
private static final Color LABEL_STROKE_COLOR = Color.GRAY;
/**
* The color of the name label.
*/
private static final Color NAME_TEXT_COLOR = Color.BLACK;
/**
* The color of the year label.
*/
private static final Color YEAR_TEXT_COLOR = Color.GRAY;
/**
* Serial Version UID
*/
private static final long serialVersionUID = -4974243564527826198L;
/**
* Localization instance
*/
private static I18n i18n = Configuration.getInstance().getI18n(TimelineView.class);
/**
* Buttons to open the popover of a specific procedure to eventually
* start their visualization
*/
private VisualizationButton[] buttons;
/**
* The list of all visualization infos.
*/
private List<AbstractVisualizationInfo> visualizationInfos;
/**
* Gets the buttons symbolizing a procedure displayed on the timeline
*
* @return Array of all timeline-buttons
*/
public VisualizationButton[] getButtons() {
return buttons;
}
/**
* Constructor initializing a new instance of {TimelineView} with
* given {visualizationInfos}
*
* @param visualizationInfos List of all {VisualizationInfo}-instances
*/
public TimelineView(List<AbstractVisualizationInfo> visualizationInfos) {
super(null);
this.addComponentListener(this);
this.setPreferredSize(new Dimension(0, 200));
this.visualizationInfos = visualizationInfos;
// Initialize buttons. These buttons are not visible and positioned without a layout manager.
// They simply fulfill the purpose of providing a clickable area.
final Dimension size = new Dimension(MARKER_RADIUS * 2, MARKER_RADIUS * 2);
this.buttons = new VisualizationButton[visualizationInfos.size()];
int j = 0;
for (Iterator<AbstractVisualizationInfo> i = visualizationInfos.iterator(); i.hasNext();) {
AbstractVisualizationInfo visualizationInfo = i.next();
VisualizationButton button = new VisualizationButton(visualizationInfo);
button.setText("");
button.setSize(size);
button.setOpaque(false);
button.setContentAreaFilled(false);
button.setBorderPainted(false);
this.add(button);
this.buttons[j] = button;
j++;
}
this.layoutButtons();
this.validate();
}
/**
* Paints the timeline and all markers
*/
@Override
protected void paintComponent(Graphics g) {
// Configure graphics context.
Graphics2D g2 = (Graphics2D)g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
this.paintTimeline(g2);
// Draw markers for all visualization infos.
for (Iterator<AbstractVisualizationInfo> i = this.visualizationInfos.iterator(); i.hasNext();) {
AbstractVisualizationInfo visualizationInfo = i.next();
this.paintMarker(g2, visualizationInfo);
}
}
/**
* Paints the timeline.
* @param g2 The graphics context
*/
private void paintTimeline(Graphics2D g2) {
final Stroke stroke = new BasicStroke(TIMELINE_STROKE_WIDTH);
final int baseline = (int)(this.getSize().getHeight() / 2);
// Paint.
g2.setColor(TIMELINE_STROKE_COLOR);
g2.setStroke(stroke);
g2.drawLine(0, baseline, (int)this.getSize().getWidth(), baseline);
}
/**
* Paints the markers and the related labels.
* @param g2 The graphics context
* @param visualizationInfo The visualization info that is represented by the marker
*/
private void paintMarker(Graphics2D g2, AbstractVisualizationInfo visualizationInfo) {
final int radius = MARKER_RADIUS;
final int padding = PADDING;
final int baseline = this.getSize().height / 2;
// Calculate marker position.
int markerX = (int)(this.getSize().width * visualizationInfo.getTimelineOffset());
int markerY = baseline;
// Calculate name label position.
FontMetrics nameMetrics = g2.getFontMetrics(g2.getFont());
Rectangle2D nameRect = nameMetrics.getStringBounds(visualizationInfo.getName(), g2);
int nameX = markerX - (int)(nameRect.getWidth() / 2);
int nameY = -(int)nameRect.getY();
// Calculate year label position.
String year;
if (visualizationInfo.getYear() >= 0) {
year = new Integer(visualizationInfo.getYear()).toString();
} else {
year = String.format(i18n.tr("{0} B.C.", Math.abs(visualizationInfo.getYear())));
}
FontMetrics yearMetrics = g2.getFontMetrics(g2.getFont());
Rectangle2D yearRect = yearMetrics.getStringBounds(year, g2);
int yearX = markerX - (int)(yearRect.getWidth() / 2);
int yearY = baseline + radius + padding - (int)yearRect.getY();
// Draw name label.
g2.setColor(LABEL_STROKE_COLOR);
g2.setStroke(new BasicStroke(LABEL_STROKE_WIDTH));
g2.drawLine(markerX, markerY - radius - padding, markerX, nameY + (int)nameRect.getHeight() + (int)nameRect.getY() + padding);
g2.setColor(NAME_TEXT_COLOR);
g2.drawString(visualizationInfo.getName(), nameX, nameY);
// Draw year label.
g2.setColor(YEAR_TEXT_COLOR);
g2.drawString(year, yearX, yearY);
g2.setColor(visualizationInfo.getDifficultyColor());
g2.fillOval(markerX - radius, markerY - radius, radius * 2, radius * 2);
g2.setColor(TIMELINE_STROKE_COLOR);
g2.setStroke(new BasicStroke(TIMELINE_STROKE_WIDTH));
g2.drawOval(markerX - radius, markerY - radius, radius * 2, radius * 2);
}
/**
* Manually layouts the buttons. We cannot use a layout manager for this since we need precise
* placement of the buttons.
*/
private void layoutButtons() {
for (VisualizationButton button : this.buttons) {
Point location = this.getLocation(button.getVisualizationInfo());
Dimension size = button.getSize();
location.x -= size.width / 2;
location.y -= size.height / 2;
button.setLocation(location);
}
}
/**
* Calculates the correct location from a given visualization info.
* @param visualizationInfo the visualization info
* @return the location of that visualization info on the timeline
*/
private Point getLocation(AbstractVisualizationInfo visualizationInfo) {
int x = (int)(this.getSize().getWidth() * visualizationInfo.getTimelineOffset());
int y = (int)(this.getSize().getHeight() / 2);
return new Point(x, y);
}
@Override
public void componentResized(ComponentEvent e) {
// If the view resizes, re-layout the buttons.
this.layoutButtons();
}
@Override
public void componentMoved(ComponentEvent e) {
// Unused
}
@Override
public void componentShown(ComponentEvent e) {
// Unused
}
@Override
public void componentHidden(ComponentEvent e) {
// Unused
}
}