package glug.gui;
import glug.gui.mousecursor.FineCrosshairMouseCursorFactory;
import glug.gui.timelinecursor.TimelineCursor;
import glug.model.ThreadModel;
import glug.model.ThreadedSystem;
import glug.model.time.LogInstant;
import glug.model.time.LogInterval;
import org.joda.time.Interval;
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import static java.lang.Math.max;
import static java.lang.Math.min;
import static java.lang.System.currentTimeMillis;
import static java.util.Collections.sort;
public class ThreadedSystemViewComponent extends TimelineComponent {
private static final long serialVersionUID = 1L;
private ThreadedSystem threadedSystem;
private final UIThreadScale threadScale;
private final UILogTimeScale uiLogTimeScale;
private final SwingHtmlStyleThreadReporter htmlStyleReporter;
private final ThreadPainter threadPainter;
private static final Comparator<ThreadModel> niceThreadOrderer = new Comparator<ThreadModel>() {
@Override
public int compare(ThreadModel t1, ThreadModel t2) {
return NiceOrderBasedOnNumericThreadIdComparator.INSTANCE.compare(t1.getThreadId(), t2.getThreadId());
}
};
public ThreadedSystemViewComponent(UITimeScale timeScale, UIThreadScale threadScale, ThreadedSystem threadedSystem, TimelineCursor timelineCursor) {
super(timeScale, timelineCursor);
this.threadScale = threadScale;
this.threadedSystem = threadedSystem;
uiLogTimeScale = new UILogTimeScale(timeScale);
threadPainter = new ThreadPainter(uiLogTimeScale,threadScale);
htmlStyleReporter = new SwingHtmlStyleThreadReporter();
setCursor(new FineCrosshairMouseCursorFactory().createFineCrosshairMouseCursor());
turnOnToolTips();
timelineCursor.install(this);
//setDoubleBuffered(true); // benefit?
}
private void turnOnToolTips() {
ToolTipManager toolTipManager = ToolTipManager.sharedInstance();
makeResponsive(toolTipManager);
toolTipManager.registerComponent(this);
}
private void makeResponsive(ToolTipManager toolTipManager) {
toolTipManager.setInitialDelay(20);
toolTipManager.setReshowDelay(10);
toolTipManager.setDismissDelay(10000);
}
@Override
protected int getPreferredHeight() {
return threadScale.fullModelToViewLength();
}
@Override
protected void paintPopulatedComponent(Graphics2D graphics2D) {
Rectangle clipBounds = graphics2D.getClipBounds();
LogInterval visibleInterval = visibleIntervalFor(clipBounds);
List<ThreadModel> fullThreadList = fullThreadList();
getTimelineCursor().paintHighlightOn(this, graphics2D);
paint(fullThreadList, minThreadIndexFor(clipBounds, fullThreadList), maxThreadIndexFor(clipBounds, fullThreadList), visibleInterval, graphics2D);
getTimelineCursor().paintCursorOn(this, graphics2D);
}
private List<ThreadModel> fullThreadList() {
List<ThreadModel> fullThreadList = new ArrayList<ThreadModel>(threadedSystem.getThreads());
sort(fullThreadList, niceThreadOrderer);
return fullThreadList;
}
protected LogInterval visibleIntervalFor(Rectangle clipBounds) {
Interval interval = uiTimeScale.viewToModel(clipBounds);
return new LogInterval(new LogInstant(interval.getStart().getMillis()-1,0),new LogInstant(interval.getEnd().getMillis()+1,Integer.MAX_VALUE));
}
private int maxThreadIndexFor(Rectangle clipBounds, List<ThreadModel> fullThreadList) {
return min(clipBounds.y+clipBounds.height,fullThreadList.size()-1);
}
private int minThreadIndexFor(Rectangle clipBounds, List<ThreadModel> fullThreadList) {
return min(max(threadScale.viewToModelThreadIndex(clipBounds.y),0),fullThreadList.size()-1);
}
private void paint(List<ThreadModel> threads, int minThreadIndex, int maxThreadIndex, LogInterval visibleInterval, Graphics2D g) {
long startRenderTime = currentTimeMillis();
for (int threadIndex = minThreadIndex ; threadIndex<=maxThreadIndex;++threadIndex) {
ThreadModel threadModel = threads.get(threadIndex);
threadPainter.paintThread(threadModel, threadIndex, visibleInterval, g);
long expiredDuration = currentTimeMillis()-startRenderTime;
if (expiredDuration>100) {
// System.out.println("Abandoning painting after "+expiredDuration+" ms");
repaint(visibleInterval, threadIndex+1, maxThreadIndex);
return;
}
}
//System.out.println("duration =" + (currentTimeMillis()-startRenderTime));
}
private LogInstant instantFor(int graphicsX) {
return new LogInstant(uiTimeScale.viewToModel(graphicsX), 0);
}
private void repaint(LogInterval logInterval, int minThreadIndex, int maxThreadIndex) {
repaint(boundsFor(logInterval, minThreadIndex, maxThreadIndex));
}
private Rectangle boundsFor(LogInterval logInterval, int threadSetStartIndex, int threadSetEndIndex) {
int x= uiLogTimeScale.modelToView(logInterval.getStart());
int width=uiLogTimeScale.modelToView(logInterval.getEnd()) - x;
int threadSetStartY = threadScale.modelThreadIndexToView(threadSetStartIndex);
int threadSetEndY = threadScale.modelThreadIndexToView(threadSetEndIndex+1);
return new Rectangle(x,threadSetStartY, width, threadSetEndY - threadSetStartY);
}
@Override
public String getToolTipText(MouseEvent event) {
if (!containsData()) {
return null;
}
ThreadModel thread = threadFor(event.getPoint());
if (thread == null) {
return null;
}
LogInstant instant = instantFor(event.getX());
return htmlStyleReporter.htmlSyledReportFor(thread, instant);
}
private ThreadModel threadFor(Point point) {
List<ThreadModel> threads = fullThreadList();
int threadIndex = threadScale.viewToModelThreadIndex(point);
if (threadIndex >= 0 && threadIndex < threads.size()) {
return threads.get(threadIndex);
}
return null;
}
@Override
public boolean containsData() {
return !threadedSystem.getThreads().isEmpty();
}
}