| /***************************************************************************** |
| * 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); |
| } |
| } |