blob: 2bfc7d9e35b4067f9ac89bce558bffb47f9332d9 [file] [log] [blame]
/*****************************************************************************
* Copyright (c) 2007, 2018 Intel Corporation, Ericsson
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Intel Corporation - Initial API and implementation
* Ruslan A. Scherbakov, Intel - Initial API and implementation
* Alvaro Sanchez-Leon - Updated for TMF
* Patrick Tasse - Refactoring
* Marc-Andre Laperle - Add time zone preference
*****************************************************************************/
package org.eclipse.tracecompass.tmf.ui.widgets.timegraph.widgets;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.events.MouseMoveListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.tracecompass.common.core.format.LongToPercentFormat;
import org.eclipse.tracecompass.common.core.math.SaturatedArithmetic;
import org.eclipse.tracecompass.internal.tmf.ui.Messages;
import org.eclipse.tracecompass.tmf.core.signal.TmfSignalHandler;
import org.eclipse.tracecompass.tmf.core.signal.TmfSignalManager;
import org.eclipse.tracecompass.tmf.core.signal.TmfTimestampFormatUpdateSignal;
import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimePreferences;
import org.eclipse.tracecompass.tmf.ui.views.FormatTimeUtils;
import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.widgets.Utils.TimeFormat;
import com.google.common.collect.ImmutableList;
/**
* Implementation of the scale for the time graph view.
*
* This goes above the "gantt chart" area.
*
* @version 1.0
* @author Alvaro Sanchez-Leon
* @author Patrick Tasse
*/
public class TimeGraphScale extends TimeGraphBaseControl implements
MouseListener, MouseMoveListener {
private static final int BASE_10 = 10;
private static final int TICK_HEIGHT = 4;
private static final int DECORATOR_HEIGHT = 4;
private static final int MIN_SECOND_FACTOR = 20;
private static final int SECOND_FACTOR = 30;
private static final int MAX_SECOND_FACTOR = 30;
private static final int MIN_MINUTE_FACTOR = 10;
private static final int MINUTE_FACTOR = 15;
private static final int MAX_MINUTE_FACTOR = 30;
private static final int MIN_HOUR_FACTOR = 3;
private static final int HOUR_FACTOR = 6;
private static final int MAX_HOUR_FACTOR = 12;
private static final int MAX_DAY_FACTOR = 10;
private static final int MAX_MONTH_FACTOR = 6;
private static final int MONTH_FACTOR = 6;
private static final int MIN_MONTH_FACTOR = 3;
private static final long MICROSEC_IN_NS = 1000;
private static final long MILLISEC_IN_NS = 1000000;
private static final long SEC_IN_NS = 1000000000;
private static final long MIN_IN_NS = 60 * SEC_IN_NS;
private static final long HOUR_IN_NS = 60 * MIN_IN_NS;
private static final long DAY_IN_NS = 24 * HOUR_IN_NS;
private static final long MONTH_IN_NS = 31 * DAY_IN_NS; // upper limit
private static final long YEAR_IN_NS = 366 * DAY_IN_NS; // upper limit
private static final double LOG10_1 = Math.log10(1);
private static final double LOG10_2 = Math.log10(2);
private static final double LOG10_3 = Math.log10(3);
private static final double LOG10_5 = Math.log10(5);
private static final Calendar GREGORIAN_CALENDAR = Calendar.getInstance();
private static final TimeDraw TIMEDRAW_NANOSEC = new TimeDrawNanosec();
private static final TimeDraw TIMEDRAW_MICROSEC = new TimeDrawMicrosec();
private static final TimeDraw TIMEDRAW_MILLISEC = new TimeDrawMillisec();
private static final TimeDraw TIMEDRAW_SEC = new TimeDrawSec();
private static final TimeDraw TIMEDRAW_ABS_NANOSEC = new TimeDrawAbsNanoSec();
private static final TimeDraw TIMEDRAW_ABS_MICROSEC = new TimeDrawAbsMicroSec();
private static final TimeDraw TIMEDRAW_ABS_MILLISEC = new TimeDrawAbsMillisec();
private static final TimeDraw TIMEDRAW_ABS_SEC = new TimeDrawAbsSec();
private static final TimeDraw TIMEDRAW_ABS_MIN = new TimeDrawAbsMin();
private static final TimeDraw TIMEDRAW_ABS_HRS = new TimeDrawAbsHrs();
private static final TimeDraw TIMEDRAW_ABS_DAY = new TimeDrawAbsDay();
private static final TimeDraw TIMEDRAW_ABS_MONTH = new TimeDrawAbsMonth();
private static final TimeDraw TIMEDRAW_ABS_YEAR = new TimeDrawAbsYear();
private static final TimeDraw TIMEDRAW_NUMBER = new TimeDrawNumber();
private static final TimeDraw TIMEDRAW_CYCLES = new TimeDrawCycles();
private static final TimeDraw TIMEDRAW_PERCENT = new TimeDrawPercent();
private static final int DRAG_EXTERNAL = -1;
private static final int NO_BUTTON = 0;
private static final int LEFT_BUTTON = 1;
private ITimeDataProvider fTimeProvider;
private int fDragState = NO_BUTTON;
private int fDragX0 = 0;
private int fDragX = 0;
private long fTime0bak;
private long fTime1bak;
private int fHeight;
private int fDigitWidth;
private final int fAlign;
/**
* Standard constructor
*
* @param parent
* The parent composite object
* @param colors
* The color scheme to use
*/
public TimeGraphScale(Composite parent, TimeGraphColorScheme colors) {
this(parent, colors, SWT.TOP);
}
/**
* Standard constructor
*
* @param parent
* The parent composite object
* @param colors
* The color scheme to use
* @param style
* The style to add to the view, can be {@link SWT#TOP} or
* {@link SWT#BOTTOM}
* @since 3.3
*/
public TimeGraphScale(Composite parent, TimeGraphColorScheme colors, int style) {
super(parent, colors, SWT.NO_BACKGROUND | SWT.NO_FOCUS | SWT.DOUBLE_BUFFERED);
TmfSignalManager.register(this);
addMouseListener(this);
addMouseMoveListener(this);
TimeDraw.updateTimeZone();
addDisposeListener((e) -> {
TmfSignalManager.deregister(TimeGraphScale.this);
});
GC gc = new GC(parent.getDisplay());
fDigitWidth = gc.getCharWidth('0');
gc.dispose();
fAlign = (style & SWT.BOTTOM) == 0 ? SWT.TOP : SWT.BOTTOM;
}
/**
* Assign the time provider for this scale
*
* @param timeProvider
* The provider to use
*/
public void setTimeProvider(ITimeDataProvider timeProvider) {
fTimeProvider = timeProvider;
}
/**
* Get the time provider used by this scale
*
* @return The time provider
*/
public ITimeDataProvider getTimeProvider() {
return fTimeProvider;
}
@Override
public Point computeSize(int wHint, int hHint, boolean changed) {
return super.computeSize(wHint, fHeight, changed);
}
/**
* Set the height of the scale
*
* @param height
* The height to use
*/
public void setHeight(int height) {
if (fHeight != height) {
fHeight = height;
getParent().layout(new Control[] { this });
}
}
/**
* Set the drag range to paint decorators
*
* @param begin
* The begin x-coordinate
* @param end
* The end x-coordinate
*/
public void setDragRange(int begin, int end) {
if (NO_BUTTON == fDragState || DRAG_EXTERNAL == fDragState) {
fDragX0 = begin - fTimeProvider.getNameSpace();
fDragX = end - fTimeProvider.getNameSpace();
if (begin >= 0 || end >= 0) {
fDragState = DRAG_EXTERNAL;
} else {
fDragState = NO_BUTTON;
}
}
redraw();
}
/**
* Get the list of visible tick times of the time axis.
*
* @return the list of visible tick times
*/
private List<Long> getTickTimeList() {
List<Long> tickTimeList = new ArrayList<>();
if (fTimeProvider == null) {
return tickTimeList;
}
long time0 = fTimeProvider.getTime0();
long time1 = fTimeProvider.getTime1();
int timeSpace = fTimeProvider.getTimeSpace();
if (time1 <= time0 || timeSpace < 2) {
return tickTimeList;
}
int numDigits = calculateDigits(time0, time1);
int labelWidth = fDigitWidth * numDigits;
double pixelsPerNanoSec = (timeSpace <= RIGHT_MARGIN) ? 0 : (double) (timeSpace - RIGHT_MARGIN) / (time1 - time0);
long timeDelta = calcTimeDelta(labelWidth, pixelsPerNanoSec);
long time;
if (fTimeProvider != null && fTimeProvider.getTimeFormat() == TimeFormat.CALENDAR) {
time = floorToCalendar(time0, timeDelta);
} else {
time = (time0 / timeDelta) * timeDelta;
}
while (time <= time1) {
if (time >= time0) {
tickTimeList.add(time);
}
if (pixelsPerNanoSec == 0 || time > Long.MAX_VALUE - timeDelta || timeDelta == 0) {
break;
}
if (fTimeProvider != null && fTimeProvider.getTimeFormat() == TimeFormat.CALENDAR) {
if (timeDelta >= YEAR_IN_NS) {
long millis = time / MILLISEC_IN_NS;
GREGORIAN_CALENDAR.setTime(new Date(millis));
GREGORIAN_CALENDAR.add(Calendar.YEAR, (int) (timeDelta / YEAR_IN_NS));
millis = GREGORIAN_CALENDAR.getTimeInMillis();
time = millis * MILLISEC_IN_NS;
} else if (timeDelta >= MONTH_IN_NS) {
long millis = time / MILLISEC_IN_NS;
GREGORIAN_CALENDAR.setTime(new Date(millis));
GREGORIAN_CALENDAR.add(Calendar.MONTH, (int) (timeDelta / MONTH_IN_NS));
millis = GREGORIAN_CALENDAR.getTimeInMillis();
time = millis * MILLISEC_IN_NS;
} else if (timeDelta >= DAY_IN_NS) {
long millis = time / MILLISEC_IN_NS;
GREGORIAN_CALENDAR.setTime(new Date(millis));
GREGORIAN_CALENDAR.add(Calendar.DAY_OF_MONTH, (int) (timeDelta / DAY_IN_NS));
millis = GREGORIAN_CALENDAR.getTimeInMillis();
time = millis * MILLISEC_IN_NS;
} else {
time += timeDelta;
}
} else {
time += timeDelta;
}
}
return tickTimeList;
}
/**
* Get the list of visible ticks of the time axis.
*
* @return the list of visible tick x-coordinates
* @since 2.0
*/
public List<Integer> getTickList() {
double pixelsPerNanoSec = calcPixelsPerNanoSec();
long time0 = fTimeProvider.getTime0();
int namespace = fTimeProvider.getNameSpace();
List<Long> tickTimeList = getTickTimeList();
List<Integer> tickList = new ArrayList<>(tickTimeList.size());
for (long time : tickTimeList) {
int x = (int) (Math.floor((time - time0) * pixelsPerNanoSec));
tickList.add(namespace + x);
}
return tickList;
}
private double calcPixelsPerNanoSec() {
long time0 = fTimeProvider.getTime0();
long time1 = fTimeProvider.getTime1();
int timeSpace = fTimeProvider.getTimeSpace();
return (timeSpace <= RIGHT_MARGIN) ? 0 : (double) (timeSpace - RIGHT_MARGIN) / (time1 - time0);
}
private long calcTimeDelta(int width, double pixelsPerNanoSec) {
long timeDelta;
double minDelta = (pixelsPerNanoSec == 0) ? YEAR_IN_NS : width / pixelsPerNanoSec;
long unit = 1;
if (fTimeProvider != null && fTimeProvider.getTimeFormat() == TimeFormat.CALENDAR) {
if (minDelta > MAX_MONTH_FACTOR * MONTH_IN_NS) {
unit = YEAR_IN_NS;
} else if (minDelta > MIN_MONTH_FACTOR * MONTH_IN_NS) {
unit = MONTH_FACTOR * MONTH_IN_NS;
} else if (minDelta > MAX_DAY_FACTOR * DAY_IN_NS) {
unit = MONTH_IN_NS;
} else if (minDelta > MAX_HOUR_FACTOR * HOUR_IN_NS) {
unit = DAY_IN_NS;
} else if (minDelta > MIN_HOUR_FACTOR * HOUR_IN_NS) {
unit = HOUR_FACTOR * HOUR_IN_NS;
} else if (minDelta > MAX_MINUTE_FACTOR * MIN_IN_NS) {
unit = HOUR_IN_NS;
} else if (minDelta > MIN_MINUTE_FACTOR * MIN_IN_NS) {
unit = MINUTE_FACTOR * MIN_IN_NS;
} else if (minDelta > MAX_SECOND_FACTOR * SEC_IN_NS) {
unit = MIN_IN_NS;
} else if (minDelta > MIN_SECOND_FACTOR * SEC_IN_NS) {
unit = SECOND_FACTOR * SEC_IN_NS;
} else if (minDelta <= 1) {
timeDelta = 1;
return timeDelta;
}
}
double log = Math.log10(minDelta / unit);
long pow10 = (long) log;
double remainder = log - pow10;
if (remainder < LOG10_1) {
timeDelta = (long) Math.pow(BASE_10, pow10) * unit;
} else if (remainder < LOG10_2) {
timeDelta = 2 * (long) Math.pow(BASE_10, pow10) * unit;
} else if (remainder < LOG10_3 && unit >= HOUR_IN_NS && unit < YEAR_IN_NS) {
timeDelta = 3 * (long) Math.pow(BASE_10, pow10) * unit;
} else if (remainder < LOG10_5) {
timeDelta = 5 * (long) Math.pow(BASE_10, pow10) * unit;
} else {
timeDelta = 10 * (long) Math.pow(BASE_10, pow10) * unit;
}
if (timeDelta <= 0) {
timeDelta = 1;
}
return timeDelta;
}
TimeDraw getTimeDraw(long timeDelta) {
TimeDraw timeDraw;
if (fTimeProvider != null) {
switch (fTimeProvider.getTimeFormat()) {
case CALENDAR:
if (timeDelta >= YEAR_IN_NS) {
timeDraw = TIMEDRAW_ABS_YEAR;
} else if (timeDelta >= MONTH_IN_NS) {
timeDraw = TIMEDRAW_ABS_MONTH;
} else if (timeDelta >= DAY_IN_NS) {
timeDraw = TIMEDRAW_ABS_DAY;
} else if (timeDelta >= HOUR_IN_NS) {
timeDraw = TIMEDRAW_ABS_HRS;
} else if (timeDelta >= MIN_IN_NS) {
timeDraw = TIMEDRAW_ABS_MIN;
} else if (timeDelta >= SEC_IN_NS) {
timeDraw = TIMEDRAW_ABS_SEC;
} else if (timeDelta >= MILLISEC_IN_NS) {
timeDraw = TIMEDRAW_ABS_MILLISEC;
} else if (timeDelta >= MICROSEC_IN_NS) {
timeDraw = TIMEDRAW_ABS_MICROSEC;
} else {
timeDraw = TIMEDRAW_ABS_NANOSEC;
}
return timeDraw;
case NUMBER:
return TIMEDRAW_NUMBER;
case CYCLES:
return TIMEDRAW_CYCLES;
case PERCENTAGE:
return TIMEDRAW_PERCENT;
case RELATIVE:
default:
}
}
if (timeDelta >= SEC_IN_NS) {
timeDraw = TIMEDRAW_SEC;
} else if (timeDelta >= MILLISEC_IN_NS) {
timeDraw = TIMEDRAW_MILLISEC;
} else if (timeDelta >= MICROSEC_IN_NS) {
timeDraw = TIMEDRAW_MICROSEC;
} else {
timeDraw = TIMEDRAW_NANOSEC;
}
return timeDraw;
}
@Override
void paint(Rectangle rect, PaintEvent e) {
if (fTimeProvider == null) {
return;
}
GC gc = e.gc;
gc.fillRectangle(rect);
long time0 = fTimeProvider.getTime0();
long time1 = fTimeProvider.getTime1();
gc.setBackground(getColorScheme().getColor(TimeGraphColorScheme.TOOL_BACKGROUND));
gc.setForeground(getColorScheme().getColor(TimeGraphColorScheme.TOOL_FOREGROUND));
Rectangle timeDrawArea = new Rectangle(0, 0, 0, 0);
Utils.init(timeDrawArea, rect);
// draw bottom border and erase all other area
if (fAlign == SWT.TOP) {
gc.drawLine(rect.x, rect.y + rect.height - 1, rect.x + rect.width - 1,
rect.y + rect.height - 1);
} else {
gc.drawLine(rect.x, 0, rect.x + rect.width - 1, 0);
timeDrawArea.y = 1;
}
timeDrawArea.height--;
gc.fillRectangle(timeDrawArea);
if (time1 <= time0) {
return;
}
int numDigits = calculateDigits(time0, time1);
int labelWidth = fDigitWidth * numDigits;
double pixelsPerNanoSec = calcPixelsPerNanoSec();
long timeDelta = calcTimeDelta(labelWidth, pixelsPerNanoSec);
TimeDraw timeDraw = getTimeDraw(timeDelta);
// draw range decorators
if (DRAG_EXTERNAL == fDragState) {
int x1 = fDragX0;
int x2 = fDragX;
drawRangeDecorators(timeDrawArea, gc, x1, x2, fAlign);
} else {
long selectionBegin = fTimeProvider.getSelectionBegin();
long selectionEnd = fTimeProvider.getSelectionEnd();
int x1 = (int) ((selectionBegin - time0) * pixelsPerNanoSec);
int x2 = (int) ((selectionEnd - time0) * pixelsPerNanoSec);
drawRangeDecorators(timeDrawArea, gc, x1, x2, fAlign);
}
// draw time scale ticks
timeDrawArea.y = fAlign == SWT.TOP ? rect.y : rect.y + TICK_HEIGHT + DECORATOR_HEIGHT;
timeDrawArea.height = rect.height - TICK_HEIGHT - DECORATOR_HEIGHT;
timeDrawArea.width = labelWidth;
int tickTop = fAlign == SWT.TOP ? rect.y + rect.height - TICK_HEIGHT : rect.y;
for (final long time : getTickTimeList()) {
int x = SaturatedArithmetic.add(rect.x, (int) (Math.floor((time - time0) * pixelsPerNanoSec)));
gc.drawLine(x, tickTop, x, tickTop + TICK_HEIGHT);
timeDrawArea.x = x;
if (x + timeDrawArea.width <= rect.x + rect.width) {
timeDraw.draw(gc, time, timeDrawArea);
}
}
}
private static void drawRangeDecorators(Rectangle rect, GC gc, int x1, int x2, int align) {
int y1 = align == SWT.TOP ? rect.y + rect.height - TICK_HEIGHT - DECORATOR_HEIGHT : TICK_HEIGHT;
int y2 = y1 + DECORATOR_HEIGHT;
int ym = y1 + DECORATOR_HEIGHT / 2;
if (x1 >= rect.x) {
// T1
gc.drawLine(x1 - 2, y1, x1 - 2, y2);
gc.drawLine(x1 - 3, y1, x1 - 1, y1);
gc.drawLine(x1 + 1, y1, x1 + 1, y2);
}
if (x2 >= rect.x && Math.abs(x2 - x1 - 2) > 3) {
// T of T2
gc.drawLine(x2 - 2, y1, x2 - 2, y2);
gc.drawLine(x2 - 3, y1, x2 - 1, y1);
}
if (x2 >= rect.x && Math.abs(x2 - x1 + 3) > 3) {
// 2 of T2
gc.drawLine(x2 + 1, y1, x2 + 3, y1);
gc.drawLine(x2 + 3, y1, x2 + 3, ym);
gc.drawLine(x2 + 1, ym, x2 + 3, ym);
gc.drawLine(x2 + 1, ym, x2 + 1, y2);
gc.drawLine(x2 + 1, y2, x2 + 3, y2);
}
}
private static long floorToCalendar(long time, long timeDelta) {
long ret = time;
if (timeDelta >= YEAR_IN_NS) {
GREGORIAN_CALENDAR.setTime(new Date(ret / MILLISEC_IN_NS));
int year = GREGORIAN_CALENDAR.get(Calendar.YEAR);
int yearDelta = (int) (timeDelta / YEAR_IN_NS);
year = (year / yearDelta) * yearDelta;
GREGORIAN_CALENDAR.set(Calendar.YEAR, year);
GREGORIAN_CALENDAR.set(Calendar.MONTH, 0); // January 1st of year
GREGORIAN_CALENDAR.set(Calendar.DAY_OF_MONTH, 1);
GREGORIAN_CALENDAR.set(Calendar.HOUR_OF_DAY, 0);
GREGORIAN_CALENDAR.set(Calendar.MINUTE, 0);
GREGORIAN_CALENDAR.set(Calendar.SECOND, 0);
GREGORIAN_CALENDAR.set(Calendar.MILLISECOND, 0);
ret = GREGORIAN_CALENDAR.getTimeInMillis() * MILLISEC_IN_NS;
} else if (timeDelta >= MONTH_IN_NS) {
GREGORIAN_CALENDAR.setTime(new Date(ret / MILLISEC_IN_NS));
int month = GREGORIAN_CALENDAR.get(Calendar.MONTH);
int monthDelta = (int) (timeDelta / MONTH_IN_NS);
month = (month / monthDelta) * monthDelta;
GREGORIAN_CALENDAR.set(Calendar.MONTH, month);
GREGORIAN_CALENDAR.set(Calendar.DAY_OF_MONTH, 1); // 1st of month
GREGORIAN_CALENDAR.set(Calendar.HOUR_OF_DAY, 0);
GREGORIAN_CALENDAR.set(Calendar.MINUTE, 0);
GREGORIAN_CALENDAR.set(Calendar.SECOND, 0);
GREGORIAN_CALENDAR.set(Calendar.MILLISECOND, 0);
ret = GREGORIAN_CALENDAR.getTimeInMillis() * MILLISEC_IN_NS;
} else {
long offset = GREGORIAN_CALENDAR.getTimeZone().getOffset(ret / MILLISEC_IN_NS) * MILLISEC_IN_NS;
ret += offset;
ret = (ret / timeDelta) * timeDelta;
ret -= offset;
}
return ret;
}
private int calculateDigits(long time0, long time1) {
int numDigits;
long timeRange = time1 - time0;
TimeFormat timeFormat = fTimeProvider.getTimeFormat();
if (timeFormat == TimeFormat.CALENDAR) {
// Calculate the number of digits to represent the time provided
if (timeRange < 10000L) {
// HH:mm:ss.SSSSSSSSS
numDigits = 18;
} else if (timeRange < 10000000L) {
// HH:mm:ss.SSSSSS
numDigits = 15;
} else if (timeRange < 10000000000L) {
// HH:mm:ss.SSS
numDigits = 12;
} else {
// HH:mm:ss
numDigits = 8;
}
} else {
long sec = time1 / SEC_IN_NS;
numDigits = Long.toString(sec).length();
int thousandGroups = (numDigits - 1) / 3;
numDigits += thousandGroups;
numDigits += 12; // .000 000 000
if (timeFormat == TimeFormat.CYCLES) {
numDigits += Messages.Utils_ClockCyclesUnit.length();
} else if (fTimeProvider.getTimeFormat() == TimeFormat.RELATIVE) {
numDigits += 2; // " s"
}
}
return numDigits;
}
@Override
public void mouseDown(MouseEvent e) {
getParent().setFocus();
if (fDragState == NO_BUTTON && null != fTimeProvider) {
if (LEFT_BUTTON == e.button && e.x > 0) {
setCapture(true);
fDragState = LEFT_BUTTON;
}
fDragX = e.x;
fDragX0 = e.x;
fTime0bak = fTimeProvider.getTime0();
fTime1bak = fTimeProvider.getTime1();
}
}
@Override
public void mouseUp(MouseEvent e) {
if (e.button == LEFT_BUTTON && fDragState == LEFT_BUTTON) {
setCapture(false);
fDragState = NO_BUTTON;
// Notify time provider to check the need for listener notification
if (fDragX != fDragX0 && fTimeProvider.getTime0() != fTimeProvider.getTime1()) {
fTimeProvider.setStartFinishTimeNotify(fTimeProvider.getTime0(), fTimeProvider.getTime1());
}
}
}
@Override
public void mouseMove(MouseEvent e) {
if (fDragX0 < 0 || fDragState == NO_BUTTON || fTimeProvider == null) {
return;
}
if (LEFT_BUTTON == fDragState) {
if (e.x > 0 && fDragX != e.x) {
fDragX = e.x;
if (fTimeProvider.getTime0() == fTimeProvider.getTime1()) {
return;
}
long interval = (long) ((fTime1bak - fTime0bak) * ((double) fDragX0 / fDragX));
if (interval == Long.MAX_VALUE) {
fTimeProvider.setStartFinishTimeNotify(fTime0bak, Long.MAX_VALUE);
} else {
long time1 = fTime0bak + (long) ((fTime1bak - fTime0bak) * ((double) fDragX0 / fDragX));
fTimeProvider.setStartFinishTimeNotify(fTime0bak, time1);
}
}
}
}
@Override
public void mouseDoubleClick(MouseEvent e) {
if (e.button == 1 && null != fTimeProvider && fTimeProvider.getTime0() != fTimeProvider.getTime1() && (e.stateMask & SWT.BUTTON_MASK) == 0) {
fTimeProvider.resetStartFinishTime();
fTime0bak = fTimeProvider.getTime0();
fTime1bak = fTimeProvider.getTime1();
}
}
/**
* Update the display to use the updated timestamp format
*
* @param signal
* the incoming signal
*/
@TmfSignalHandler
public void timestampFormatUpdated(TmfTimestampFormatUpdateSignal signal) {
TimeDraw.updateTimeZone();
FormatTimeUtils.updateTimeZone();
redraw();
}
}
abstract class TimeDraw {
protected static final long MICROSEC_IN_NS = 1000;
protected static final long MILLISEC_IN_NS = 1000000;
protected static final long MILLISEC_IN_US = 1000;
protected static final long SEC_IN_NS = 1000000000;
protected static final long SEC_IN_MS = 1000;
private static final String S = ""; //$NON-NLS-1$
private static final String S0 = "0"; //$NON-NLS-1$
private static final String S00 = "00"; //$NON-NLS-1$
protected static final long PAD_1000 = 1000;
protected static final SimpleDateFormat SEC_FORMAT_HEADER = new SimpleDateFormat("yyyy MMM dd");//$NON-NLS-1$
protected static final SimpleDateFormat SEC_FORMAT = new SimpleDateFormat("HH:mm:ss"); //$NON-NLS-1$
protected static final SimpleDateFormat MIN_FORMAT_HEADER = new SimpleDateFormat("yyyy MMM dd"); //$NON-NLS-1$
protected static final SimpleDateFormat MIN_FORMAT = new SimpleDateFormat("HH:mm"); //$NON-NLS-1$
protected static final SimpleDateFormat HOURS_FORMAT_HEADER = new SimpleDateFormat("yyyy"); //$NON-NLS-1$
protected static final SimpleDateFormat HOURS_FORMAT = new SimpleDateFormat("MMM dd HH:mm"); //$NON-NLS-1$
protected static final SimpleDateFormat DAY_FORMAT_HEADER = new SimpleDateFormat("yyyy"); //$NON-NLS-1$
protected static final SimpleDateFormat DAY_FORMAT = new SimpleDateFormat("MMM dd"); //$NON-NLS-1$
protected static final SimpleDateFormat MONTH_FORMAT = new SimpleDateFormat("yyyy MMM"); //$NON-NLS-1$
protected static final SimpleDateFormat YEAR_FORMAT = new SimpleDateFormat("yyyy"); //$NON-NLS-1$
protected static final List<SimpleDateFormat> formats;
static {
ImmutableList.Builder<SimpleDateFormat> formatArrayBuilder = ImmutableList.<SimpleDateFormat> builder();
formatArrayBuilder.add(SEC_FORMAT);
formatArrayBuilder.add(SEC_FORMAT_HEADER);
formatArrayBuilder.add(MIN_FORMAT);
formatArrayBuilder.add(MIN_FORMAT_HEADER);
formatArrayBuilder.add(HOURS_FORMAT);
formatArrayBuilder.add(HOURS_FORMAT_HEADER);
formatArrayBuilder.add(DAY_FORMAT);
formatArrayBuilder.add(DAY_FORMAT_HEADER);
formatArrayBuilder.add(MONTH_FORMAT);
formatArrayBuilder.add(YEAR_FORMAT);
formats = formatArrayBuilder.build();
}
/**
* Updates the timezone using the preferences.
*/
public static void updateTimeZone() {
final TimeZone timeZone = TmfTimePreferences.getTimeZone();
for (SimpleDateFormat sdf : formats) {
synchronized (sdf) {
sdf.setTimeZone(timeZone);
}
}
}
static String sep(long n) {
StringBuilder retVal = new StringBuilder();
String s = Long.toString(n);
for (int i = 0; i < s.length(); i++) {
int pos = s.length() - i - 1;
retVal.append(s.charAt(i));
if (pos % 3 == 0 && pos != 0) {
retVal.append(' ');
}
}
return retVal.toString();
}
static String pad(long n) {
String s;
if (n < 10) {
s = S00;
} else if (n < 100) {
s = S0;
} else {
s = S;
}
return s + n;
}
public abstract int draw(GC gc, long time, Rectangle rect);
/**
* Override to draw absolute time header. This is for the time information not
* shown in the draw of each tick
*
* @param gc
* Graphics context
* @param nanosec
* time in nanosec
* @param absHeaderRect
* Header rectangle
*/
public void drawAbsHeader(GC gc, long nanosec, Rectangle absHeaderRect) {
}
protected void drawAbsHeader(GC gc, long nanosec, Rectangle rect, SimpleDateFormat dateFormat) {
String header;
synchronized (dateFormat) {
header = dateFormat.format(new Date(nanosec / MILLISEC_IN_NS));
}
int headerwidth = gc.stringExtent(header).x + 4;
if (headerwidth <= rect.width) {
rect.x += (rect.width - headerwidth);
Utils.drawText(gc, header, rect, true);
}
}
}
class TimeDrawSec extends TimeDraw {
@Override
public int draw(GC gc, long nanosec, Rectangle rect) {
long sec = nanosec / SEC_IN_NS;
return Utils.drawText(gc, sep(sec) + " s", rect, true); //$NON-NLS-1$
}
}
class TimeDrawMillisec extends TimeDraw {
@Override
public int draw(GC gc, long nanosec, Rectangle rect) {
long millisec = nanosec / MILLISEC_IN_NS;
long ms = millisec % PAD_1000;
long sec = millisec / SEC_IN_MS;
return Utils.drawText(gc, sep(sec) + "." + pad(ms) + " s", rect, true); //$NON-NLS-1$ //$NON-NLS-2$
}
}
class TimeDrawMicrosec extends TimeDraw {
@Override
public int draw(GC gc, long nanosec, Rectangle rect) {
long microsec = nanosec / MICROSEC_IN_NS;
long us = microsec % PAD_1000;
long millisec = microsec / MILLISEC_IN_US;
long ms = millisec % PAD_1000;
long sec = millisec / SEC_IN_MS;
return Utils.drawText(gc, sep(sec) + "." + pad(ms) + " " + pad(us) + " s", rect, true); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
}
class TimeDrawNanosec extends TimeDraw {
@Override
public int draw(GC gc, long nanosec, Rectangle rect) {
long ns = nanosec % PAD_1000;
long microsec = nanosec / MICROSEC_IN_NS;
long us = microsec % PAD_1000;
long millisec = microsec / MILLISEC_IN_US;
long ms = millisec % PAD_1000;
long sec = millisec / SEC_IN_MS;
return Utils.drawText(gc, sep(sec) + "." + pad(ms) + " " + pad(us) + " " + pad(ns) + " s", rect, true); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
}
}
class TimeDrawAbsYear extends TimeDraw {
@Override
public int draw(GC gc, long nanosec, Rectangle rect) {
String stime;
synchronized (YEAR_FORMAT) {
stime = YEAR_FORMAT.format(new Date(nanosec / MILLISEC_IN_NS));
}
return Utils.drawText(gc, stime, rect, true);
}
}
class TimeDrawAbsMonth extends TimeDraw {
@Override
public int draw(GC gc, long nanosec, Rectangle rect) {
String stime;
synchronized (MONTH_FORMAT) {
stime = MONTH_FORMAT.format(new Date(nanosec / MILLISEC_IN_NS));
}
return Utils.drawText(gc, stime, rect, true);
}
}
class TimeDrawAbsDay extends TimeDraw {
@Override
public int draw(GC gc, long nanosec, Rectangle rect) {
String stime;
synchronized (DAY_FORMAT) {
stime = DAY_FORMAT.format(new Date(nanosec / MILLISEC_IN_NS));
}
return Utils.drawText(gc, stime, rect, true);
}
@Override
public void drawAbsHeader(GC gc, long nanosec, Rectangle rect) {
drawAbsHeader(gc, nanosec, rect, DAY_FORMAT_HEADER);
}
}
class TimeDrawAbsHrs extends TimeDraw {
@Override
public int draw(GC gc, long nanosec, Rectangle rect) {
String stime;
synchronized (HOURS_FORMAT) {
stime = HOURS_FORMAT.format(new Date(nanosec / MILLISEC_IN_NS));
}
return Utils.drawText(gc, stime, rect, true);
}
@Override
public void drawAbsHeader(GC gc, long nanosec, Rectangle rect) {
drawAbsHeader(gc, nanosec, rect, HOURS_FORMAT_HEADER);
}
}
class TimeDrawAbsMin extends TimeDraw {
@Override
public int draw(GC gc, long nanosec, Rectangle rect) {
String stime;
synchronized (MIN_FORMAT) {
stime = MIN_FORMAT.format(new Date(nanosec / MILLISEC_IN_NS));
}
return Utils.drawText(gc, stime, rect, true);
}
@Override
public void drawAbsHeader(GC gc, long nanosec, Rectangle rect) {
drawAbsHeader(gc, nanosec, rect, MIN_FORMAT_HEADER);
}
}
class TimeDrawAbsSec extends TimeDraw {
@Override
public int draw(GC gc, long nanosec, Rectangle rect) {
String stime;
synchronized (SEC_FORMAT) {
stime = SEC_FORMAT.format(new Date(nanosec / MILLISEC_IN_NS));
}
return Utils.drawText(gc, stime, rect, true);
}
@Override
public void drawAbsHeader(GC gc, long nanosec, Rectangle rect) {
drawAbsHeader(gc, nanosec, rect, SEC_FORMAT_HEADER);
}
}
class TimeDrawAbsMillisec extends TimeDraw {
@Override
public int draw(GC gc, long nanosec, Rectangle rect) {
String stime;
synchronized (SEC_FORMAT) {
stime = SEC_FORMAT.format(new Date(nanosec / MILLISEC_IN_NS));
}
String ns = FormatTimeUtils.formatNs(nanosec, FormatTimeUtils.Resolution.MILLISEC);
return Utils.drawText(gc, stime + "." + ns, rect, true); //$NON-NLS-1$
}
@Override
public void drawAbsHeader(GC gc, long nanosec, Rectangle rect) {
drawAbsHeader(gc, nanosec, rect, SEC_FORMAT_HEADER);
}
}
class TimeDrawAbsMicroSec extends TimeDraw {
@Override
public int draw(GC gc, long nanosec, Rectangle rect) {
String stime;
synchronized (SEC_FORMAT) {
stime = SEC_FORMAT.format(new Date(nanosec / MILLISEC_IN_NS));
}
String micr = FormatTimeUtils.formatNs(nanosec, FormatTimeUtils.Resolution.MICROSEC);
return Utils.drawText(gc, stime + "." + micr, rect, true); //$NON-NLS-1$
}
@Override
public void drawAbsHeader(GC gc, long nanosec, Rectangle rect) {
drawAbsHeader(gc, nanosec, rect, SEC_FORMAT_HEADER);
}
}
class TimeDrawAbsNanoSec extends TimeDraw {
@Override
public int draw(GC gc, long nanosec, Rectangle rect) {
String stime;
synchronized (SEC_FORMAT) {
stime = SEC_FORMAT.format(new Date(nanosec / MILLISEC_IN_NS));
}
String ns = FormatTimeUtils.formatNs(nanosec, FormatTimeUtils.Resolution.NANOSEC);
return Utils.drawText(gc, stime + "." + ns, rect, true); //$NON-NLS-1$
}
@Override
public void drawAbsHeader(GC gc, long nanosec, Rectangle rect) {
drawAbsHeader(gc, nanosec, rect, SEC_FORMAT_HEADER);
}
}
class TimeDrawNumber extends TimeDraw {
@Override
public int draw(GC gc, long time, Rectangle rect) {
String stime = NumberFormat.getInstance().format(time);
return Utils.drawText(gc, stime, rect, true);
}
}
class TimeDrawCycles extends TimeDraw {
@Override
public int draw(GC gc, long time, Rectangle rect) {
String stime = FormatTimeUtils.formatTime(time, FormatTimeUtils.TimeFormat.CYCLES, FormatTimeUtils.Resolution.SECONDS);
return Utils.drawText(gc, stime, rect, true);
}
}
class TimeDrawPercent extends TimeDraw {
@Override
public int draw(GC gc, long time, Rectangle rect) {
String stime = LongToPercentFormat.getInstance().format(time);
return Utils.drawText(gc, stime, rect, true);
}
}