Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.github.rinde.rinsim.pdptw.common.TimeLinePanel Maven / Gradle / Ivy
/*
* Copyright (C) 2011-2016 Rinde van Lon, iMinds-DistriNet, KU Leuven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.rinde.rinsim.pdptw.common;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Verify.verify;
import static com.google.common.base.Verify.verifyNotNull;
import static com.google.common.collect.Lists.newArrayList;
import java.math.RoundingMode;
import java.util.List;
import javax.annotation.Nullable;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.PaletteData;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.ScrollBar;
import org.joda.time.Period;
import org.joda.time.format.PeriodFormatter;
import org.joda.time.format.PeriodFormatterBuilder;
import com.github.rinde.rinsim.core.model.DependencyProvider;
import com.github.rinde.rinsim.core.model.Model.AbstractModelVoid;
import com.github.rinde.rinsim.core.model.ModelBuilder.AbstractModelBuilder;
import com.github.rinde.rinsim.core.model.pdp.PDPModel;
import com.github.rinde.rinsim.core.model.pdp.PDPModel.PDPModelEventType;
import com.github.rinde.rinsim.core.model.pdp.PDPModelEvent;
import com.github.rinde.rinsim.core.model.pdp.Parcel;
import com.github.rinde.rinsim.core.model.time.TickListener;
import com.github.rinde.rinsim.core.model.time.TimeLapse;
import com.github.rinde.rinsim.event.Event;
import com.github.rinde.rinsim.event.Listener;
import com.github.rinde.rinsim.ui.renderers.PanelRenderer;
import com.github.rinde.rinsim.util.TimeWindow;
import com.google.auto.value.AutoValue;
import com.google.common.base.Optional;
import com.google.common.math.DoubleMath;
/**
* Time line panel is an UI element that shows a real time visualization of
* parcels and their time windows in a simulation.
* @author Rinde van Lon
*/
public final class TimeLinePanel extends AbstractModelVoid implements
PanelRenderer, TickListener {
static final PeriodFormatter FORMATTER = new PeriodFormatterBuilder()
.minimumPrintedDigits(2)
.printZeroAlways()
.appendHours()
.appendLiteral(":")
.appendMinutes()
.toFormatter();
static final int PANEL_PX = 200;
static final int MARGIN_PX = 2;
static final int BAR_HEIGHT_PX = 22;
static final int WIDTH_PX = 700;
static final int FONT_SIZE = 10;
static final long TIME_PER_PIXEL = 15000;
static final int V_THUMB_SIZE = 5;
static final int H_THUMB_SIZE = 20;
static final int SCROLL_INCR = 2;
static final int SCROLL_PAGE_INCR = 20;
private static final boolean IS_MAC_OR_WINDOWS;
static {
final String name = System.getProperty("os.name").toLowerCase();
IS_MAC_OR_WINDOWS = name.contains("win") || name.contains("mac");
}
Optional canvas;
Optional barCanvas;
Point origin = new Point(0, 0);
long currentTime;
final PDPModel pdpModel;
TimeLinePanel(PDPModel pm) {
pdpModel = pm;
canvas = Optional.absent();
barCanvas = Optional.absent();
}
@Override
public void initializePanel(Composite parent) {
final TimelineBar timelineBar = new TimelineBar(parent.getDisplay());
final Timeline timeline = new Timeline(parent.getDisplay());
pdpModel.getEventAPI().addListener(new Listener() {
@Override
public void handleEvent(Event e) {
if (e.getEventType() == PDPModelEventType.NEW_PARCEL) {
verify(e instanceof PDPModelEvent);
final PDPModelEvent event = (PDPModelEvent) e;
timeline.addParcel(new ParcelInfo(event.time,
verifyNotNull(event.parcel)));
}
}
}, PDPModelEventType.NEW_PARCEL);
final GridLayout layout = new GridLayout(1, false);
layout.marginHeight = MARGIN_PX;
layout.marginWidth = MARGIN_PX;
layout.verticalSpacing = 0;
parent.setLayout(layout);
barCanvas = Optional.of(new Canvas(parent, SWT.NONE));
final GridData barData = new GridData(SWT.FILL, SWT.TOP, true, false);
barData.minimumHeight = BAR_HEIGHT_PX;
barData.heightHint = BAR_HEIGHT_PX;
barCanvas.get().setLayoutData(barData);
barCanvas.get().addPaintListener(new PaintListener() {
@Override
public void paintControl(@Nullable PaintEvent e) {
assert e != null;
timelineBar.update(timeline.getWidth());
e.gc.setBackground(
e.display.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND));
e.gc.fillRectangle(0, 0,
barCanvas.get().getClientArea().width,
barCanvas.get().getClientArea().height);
e.gc.drawImage(timelineBar.contents, origin.x, 0);
e.gc.setForeground(e.display.getSystemColor(SWT.COLOR_RED));
e.gc.drawLine(origin.x + (int) (currentTime / TIME_PER_PIXEL),
FONT_SIZE, origin.x + (int) (currentTime / TIME_PER_PIXEL),
barCanvas.get().getClientArea().height);
}
});
canvas = Optional.of(new Canvas(parent, SWT.DOUBLE_BUFFERED | SWT.NONE
| SWT.V_SCROLL | SWT.H_SCROLL));
final ScrollBar hBar = canvas.get().getHorizontalBar();
final ScrollBar vBar = canvas.get().getVerticalBar();
canvas.get().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
canvas.get().addPaintListener(new PaintListener() {
@Override
public void paintControl(@Nullable PaintEvent e) {
assert e != null;
final int timeX = DoubleMath.roundToInt(origin.x + currentTime
/ TIME_PER_PIXEL,
RoundingMode.HALF_UP);
final int height = timeline.getHeight();
timeline.update(timeX);
final boolean shouldScroll = timeline.getHeight() > height
&& vBar.getMaximum() == vBar.getSelection() + vBar.getThumb();
e.gc.setBackground(
e.display.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND));
e.gc.fillRectangle(0, 0,
canvas.get().getClientArea().width,
canvas.get().getClientArea().height);
e.gc.drawImage(timeline.contents.get(), origin.x, origin.y);
e.gc.setForeground(e.display.getSystemColor(SWT.COLOR_RED));
e.gc.drawLine(timeX, 0, timeX, canvas.get().getClientArea().height);
hBar.setMaximum(timeline.getWidth() == 0 ? 1 : timeline.getWidth()
+ H_THUMB_SIZE);
vBar.setMaximum(timeline.getHeight() + V_THUMB_SIZE);
hBar.setThumb(Math.min(timeline.getWidth() + H_THUMB_SIZE, canvas.get()
.getClientArea().width));
vBar.setThumb(Math.min(timeline.getHeight() + V_THUMB_SIZE, canvas
.get().getClientArea().height));
// if view is currently scrolled down, automatically scroll down when
// view is expanded downward (similar to the behavior of a terminal)
if (shouldScroll) {
vBar.setSelection(vBar.getMaximum());
final int vSelection = vBar.getSelection();
final int destY = -vSelection - origin.y;
canvas.get().scroll(0, destY, 0, 0, timeline.getWidth(),
timeline.getHeight(), false);
origin.y = -vSelection;
}
}
});
hBar.setIncrement(SCROLL_INCR);
hBar.setPageIncrement(SCROLL_PAGE_INCR);
hBar.addListener(SWT.Selection, new org.eclipse.swt.widgets.Listener() {
@Override
public void handleEvent(@Nullable org.eclipse.swt.widgets.Event e) {
final int hSelection = hBar.getSelection();
final int destX = -hSelection - origin.x;
canvas.get().scroll(destX, 0, 0, 0, timeline.getWidth(),
timeline.getHeight(), false);
barCanvas.get().scroll(destX, 0, 0, 0,
timelineBar.contents.getBounds().width,
timelineBar.contents.getBounds().height, false);
origin.x = -hSelection;
}
});
vBar.setIncrement(SCROLL_INCR);
vBar.setPageIncrement(SCROLL_PAGE_INCR);
vBar.addListener(SWT.Selection, new org.eclipse.swt.widgets.Listener() {
@Override
public void handleEvent(@Nullable org.eclipse.swt.widgets.Event e) {
final int vSelection = vBar.getSelection();
final int destY = -vSelection - origin.y;
canvas.get().scroll(0, destY, 0, 0, timeline.getWidth(),
timeline.getHeight(), false);
origin.y = -vSelection;
}
});
canvas.get().redraw();
barCanvas.get().redraw();
}
@Override
public int preferredSize() {
return PANEL_PX;
}
@Override
public int getPreferredPosition() {
return SWT.TOP;
}
@Override
public String getName() {
return "Timeline";
}
@Override
public void tick(TimeLapse timeLapse) {}
@Override
public void afterTick(TimeLapse timeLapse) {
currentTime = timeLapse.getStartTime();
}
@Override
public void render() {
checkState(canvas.isPresent());
if (canvas.get().isDisposed()) {
return;
}
canvas.get().getDisplay().syncExec(new Runnable() {
@Override
public void run() {
if (!canvas.get().isDisposed()) {
canvas.get().redraw();
barCanvas.get().redraw();
}
}
});
}
static Image createNewTransparentImg(Display d, int w, int h) {
final Color bg = d.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND);
final PaletteData palette = new PaletteData(new RGB[] {bg.getRGB()});
final ImageData sourceData = new ImageData(w, h, 1, palette);
if (IS_MAC_OR_WINDOWS) {
sourceData.transparentPixel = 0;
}
return new Image(d, sourceData);
}
/**
* @return A new {@link Builder}.
*/
public static Builder builder() {
return new AutoValue_TimeLinePanel_Builder();
}
/**
* Builder for {@link TimeLinePanel}.
* @author Rinde van Lon
*/
@AutoValue
public abstract static class Builder extends
AbstractModelBuilder {
Builder() {
setDependencies(PDPModel.class);
}
@Override
public TimeLinePanel build(DependencyProvider dependencyProvider) {
return new TimeLinePanel(dependencyProvider.get(PDPModel.class));
}
}
static class ParcelInfo {
final long eventTime;
final Parcel parcel;
ParcelInfo(long time, Parcel p) {
eventTime = time;
parcel = p;
}
}
static class TimelineBar {
static final int LARGE_TICK_HEIGHT = 10;
static final int SMALL_TICK_HEIGHT = 5;
static final int LARGE_TICK_DIST = 40;
static final int SMALL_TICK_DIST = 8;
static final int TL_BAR_HEIGHT_PX = 20;
static final int ADDITIONAL_WIDTH = 30;
protected final Display display;
protected Image contents;
protected Font font;
TimelineBar(Display d) {
display = d;
contents = createNewTransparentImg(display, WIDTH_PX, TL_BAR_HEIGHT_PX);
font = new Font(display, "arial", FONT_SIZE, SWT.NORMAL);
drawTimeline();
}
void update(int width) {
if (contents.getBounds().width < width) {
contents.dispose();
contents = createNewTransparentImg(display, width + ADDITIONAL_WIDTH,
TL_BAR_HEIGHT_PX);
drawTimeline();
}
}
final void drawTimeline() {
final GC gc = new GC(contents);
gc.setBackground(display.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND));
gc.fillRectangle(0, 0, contents.getBounds().width,
contents.getBounds().height);
gc.setAdvanced(true);
gc.setTextAntialias(SWT.ON);
for (int i = 0; i < contents.getBounds().width; i += SMALL_TICK_DIST) {
final int height = i % LARGE_TICK_DIST == 0 ? LARGE_TICK_HEIGHT
: SMALL_TICK_HEIGHT;
if (i % LARGE_TICK_DIST == 0) {
final String time = FORMATTER
.print(new Period(0L, TIME_PER_PIXEL * i));
gc.setFont(font);
final Point size = gc.textExtent(time);
gc.drawText(time, i - size.x / 2, 0, true);
}
gc.drawLine(i, TL_BAR_HEIGHT_PX - height, i, TL_BAR_HEIGHT_PX);
}
gc.dispose();
}
}
static class Timeline {
static final int ROW_HEIGHT = 15;
static final int VERTICAL_DIST = 40;
static final long HOUR = 60 * 60 * 1000;
static final int START_HEIGHT = 100;
static final int ADDITIONAL_HEIGHT_FACTOR = 10;
static final int BAR_H = 12;
static final int HALF_BAR_H = BAR_H / 2;
static final int BAR_START_OFFSET_Y = 1;
static final int BAR_END_OFFSET_Y = BAR_H + BAR_START_OFFSET_Y;
static final int BAR_MIDDLE_OFFSET_Y = 8;
final Display display;
Optional contents;
List parcels;
List newParcels;
Color lineColor;
Color pickupColor;
Color deliveryColor;
Color gridColor;
int width;
private int height;
Timeline(Display d) {
display = d;
parcels = newArrayList();
newParcels = newArrayList();
lineColor = d.getSystemColor(SWT.COLOR_WIDGET_BORDER);
pickupColor = d.getSystemColor(SWT.COLOR_BLUE);
deliveryColor = d.getSystemColor(SWT.COLOR_DARK_RED);
gridColor = d.getSystemColor(SWT.COLOR_GRAY);
contents = Optional.absent();
}
void ensureImg() {
if (!contents.isPresent()) {
contents = Optional.of(createNewTransparentImg(display, WIDTH_PX,
START_HEIGHT));
final GC gc = new GC(contents.get());
drawVerticals(gc, WIDTH_PX, START_HEIGHT);
gc.dispose();
} else {
final boolean wViolation = width > contents.get().getBounds().width;
final boolean hViolation = height > contents.get().getBounds().height;
if (wViolation || hViolation) {
final int newWidth = Math
.max(width, contents.get().getBounds().width)
+ (wViolation ? (int) (HOUR / TIME_PER_PIXEL) : 0);
final int newHeight = contents.get().getBounds().height
+ (hViolation ? ADDITIONAL_HEIGHT_FACTOR * ROW_HEIGHT : 0);
final Image newContents = createNewTransparentImg(display, newWidth,
newHeight);
// copy previous image to new image
final GC gc = new GC(newContents);
// draw vertical grid lines
drawVerticals(gc, newWidth, newHeight);
gc.drawImage(contents.get(), 0, 0);
gc.dispose();
contents.get().dispose();
contents = Optional.of(newContents);
}
}
}
void drawVerticals(GC gc, int w, int h) {
for (int i = 0; i < w; i += VERTICAL_DIST) {
gc.setForeground(gridColor);
gc.drawLine(i, 0, i, h);
}
}
void addParcel(final ParcelInfo p) {
newParcels.add(p);
width = Math.max(width,
(int) (p.parcel.getDeliveryTimeWindow().end() / TIME_PER_PIXEL));
}
void update(int timeX) {
if (display.isDisposed()) {
return;
}
final int oldHeight = height;
// make copy to avoid concurrency problems
final List copyNewParcels;
synchronized (newParcels) {
copyNewParcels = newArrayList(newParcels);
newParcels.clear();
}
parcels.addAll(copyNewParcels);
height = parcels.size() * ROW_HEIGHT;
width = Math.max(width, timeX);
ensureImg();
for (int i = 0; i < copyNewParcels.size(); i++) {
drawParcel(copyNewParcels.get(i), oldHeight + i * ROW_HEIGHT);
}
}
void drawParcel(ParcelInfo p, int y) {
final TimeWindow pi = p.parcel.getPickupTimeWindow();
final TimeWindow de = p.parcel.getDeliveryTimeWindow();
final int startX = (int) (p.eventTime / TIME_PER_PIXEL);
final int startPickX = (int) (pi.begin() / TIME_PER_PIXEL);
final int endPickX = (int) (pi.end() / TIME_PER_PIXEL);
final int startDelX = (int) (de.begin() / TIME_PER_PIXEL);
final int endDelX = (int) (de.end() / TIME_PER_PIXEL);
final GC gc = new GC(contents.get());
gc.setForeground(lineColor);
gc.drawLine(startX, y + BAR_START_OFFSET_Y, startX, y
+ BAR_END_OFFSET_Y);
gc.drawLine(startX, y + BAR_MIDDLE_OFFSET_Y, startPickX, y
+ BAR_MIDDLE_OFFSET_Y);
gc.setBackground(pickupColor);
gc.fillRectangle(startPickX, y + 2,
Math.max(endPickX - startPickX, 1), HALF_BAR_H);
gc.drawRectangle(startPickX, y + 2,
Math.max(endPickX - startPickX, 1), HALF_BAR_H);
gc.drawLine(endPickX, y + BAR_MIDDLE_OFFSET_Y, startDelX, y
+ BAR_MIDDLE_OFFSET_Y);
gc.setBackground(deliveryColor);
gc.fillRectangle(startDelX, y + BAR_MIDDLE_OFFSET_Y,
Math.max(endDelX - startDelX, 1), HALF_BAR_H);
gc.drawRectangle(startDelX, y + BAR_MIDDLE_OFFSET_Y,
Math.max(endDelX - startDelX, 1), HALF_BAR_H);
gc.dispose();
}
int getHeight() {
return height;
}
int getWidth() {
return width;
}
void dispose() {
contents.get().dispose();
}
}
}