/*
* 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.timeline.ui;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Insets;
import java.awt.LayoutManager;
import java.util.ArrayList;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
public class TimelineLayout implements LayoutManager {
private int minimumHorizontalGap = 5;
private int verticalGap = 5;
private int intervalHeight = 10;
private int bottomMargin = 12;
private int numRows;
public TimelineLayout(int minimumHorizontalGap, int verticalGap, int intervalHeight, int bottomMargin) {
this.minimumHorizontalGap = minimumHorizontalGap;
this.verticalGap = verticalGap;
this.intervalHeight = intervalHeight;
this.bottomMargin = bottomMargin;
}
@Override
public void addLayoutComponent(String name, Component comp) {
// we're not associating components with names
// -> don't need to do anything here
}
@Override
public void layoutContainer(Container parent) {
TimelinePanel<?> timeline = null;
if (parent instanceof TimelinePanel) {
timeline = (TimelinePanel<?>) parent;
}
if (timeline == null) {
// do nothing if parent isn't a timelinePanel
return;
}
List<Row> rows = new ArrayList<Row>();
Component[] components = timeline.getComponents();
for (int i = 0; i < components.length; i++) {
Component component = components[i];
IntervalView view = null;
if (component instanceof IntervalView) {
view = (IntervalView) component;
}
if (view == null) {
// do nothing if component isn't an intervalView
continue;
}
int startX = timeline.convertDateToXCoord(view.getInterval().getStart());
int endX = timeline.convertDateToXCoord(view.getInterval().getEnd());
int width = endX - startX;
int rowIndex = 0;
boolean foundRow = false;
while (!foundRow) {
Row row;
if (rowIndex < rows.size()) {
row = rows.get(rowIndex);
} else {
row = new Row();
rows.add(row);
}
try {
row.add(startX, endX);
foundRow = true;
} catch (TakenException te) {
rowIndex++;
}
}
int startY = convertRowToY(rowIndex);
int height = intervalHeight;
view.setBounds(startX, startY, width, height);
}
numRows = rows.size();
}
private int convertRowToY(int row) {
int minY = 22; // TODO calculate based on maxAscent of timeline's font
return minY + row * (intervalHeight + verticalGap);
}
@Override
public Dimension minimumLayoutSize(Container parent) {
Insets insets = parent.getInsets();
int width = insets.left + 8 + insets.right;
// TODO calculate minY based on maxAscent of timeline's font
int height = insets.top + 22 + numRows * (intervalHeight + verticalGap) + bottomMargin + insets.bottom;
return new Dimension(width, height);
}
@Override
public Dimension preferredLayoutSize(Container parent) {
Dimension minimumSize = minimumLayoutSize(parent);
int width = Math.max(360, minimumSize.width);
int height = Math.max(150, minimumSize.height);
return new Dimension(width, height);
}
@Override
public void removeLayoutComponent(Component comp) {
// we're not caching anything -> don't need to do anything here
}
private class Row {
SortedSet<Range> taken = new TreeSet<Range>();
boolean canFit(int from, int to) {
if (taken.isEmpty()) {
return true;
}
Range[] takenArray = (Range[]) taken.toArray(new Range[taken.size()]);
if (to + minimumHorizontalGap <= takenArray[0].from) {
return true;
}
for (int i = 0; i < takenArray.length; i++) {
Range range = takenArray[i];
if (range.to + minimumHorizontalGap <= from) {
if (i + 1 < takenArray.length) {
Range nextRange = takenArray[i+1];
if (to + minimumHorizontalGap <= nextRange.from) {
return true;
}
} else {
return true;
}
}
}
// couldn't find a fitting space
return false;
}
void add(int from, int to) throws TakenException {
if (canFit(from, to)) {
taken.add(new Range(from, to));
} else {
throw new TakenException("range from " + from + " to " + to + " is taken");
}
}
}
private class Range implements Comparable<Range> {
private int from;
private int to;
Range(int from, int to) {
this.from = from;
this.to = to;
}
@Override
public int compareTo(Range o) {
Integer thisFrom = new Integer(from);
Integer otherFrom = new Integer(o.from);
return thisFrom.compareTo(otherFrom);
}
}
private class TakenException extends Exception {
private static final long serialVersionUID = 1L;
TakenException(String message) {
super(message);
}
TakenException(String message, Throwable cause) {
super(message, cause);
}
}
}