/******************************************************************************* * Copyright (c) 2016 Ericsson * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v1.0 which * accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Patrick Tasse - Initial API and implementation *******************************************************************************/ package org.eclipse.tracecompass.tmf.ui.markers; import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import org.apache.commons.lang3.math.Fraction; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.swt.graphics.RGBA; import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.IMarkerEvent; import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.IMarkerEventSource; import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.MarkerEvent; /** * Marker event source that produces periodic markers. * * @since 2.0 */ @NonNullByDefault public class PeriodicMarkerEventSource implements IMarkerEventSource { /** * Reference marker time and index */ public static class Reference { /** Reference marker index 0 at time 0 */ public static final Reference ZERO = new Reference(0L, 0); private final long time; private final long index; /** * Constructor * * @param time * the reference marker time in time units * @param index * the reference marker index */ public Reference(long time, int index) { this.time = time; this.index = index; } /** * Constructor * * @param time * the reference marker time in time units * @param index * the reference marker index * @since 2.3 */ public Reference(long time, long index) { this.time = time; this.index = index; } @Override public String toString() { return String.format("[%d, %d]", time, index); //$NON-NLS-1$ } } private final String fCategory; private final Reference fReference; private final double fPeriod; private final long fPeriodInteger; private @Nullable Fraction fPeriodFraction; private final long fRollover; private final RGBA fColor; private final @Nullable RGBA fOddColor; private final boolean fForeground; /** * Constructs a periodic marker event source with line markers at period * boundaries. * <p> * The markers will have the given category and color. The reference defines * the marker with the given index to be at the specified time. * * @param category * the marker category * @param reference * the reference marker time and index * @param period * the period in time units * @param rollover * the number of periods before the index rolls-over to 0, or 0 * for no roll-over * @param color * the marker color * @param foreground * true if the marker is drawn in foreground, and false otherwise */ public PeriodicMarkerEventSource(String category, Reference reference, double period, long rollover, RGBA color, boolean foreground) { this(category, reference, period, rollover, foreground, color, null); } /** * Constructs a periodic marker event source with alternating shading * markers. * <p> * The markers will have the given category. Periods with even index will be * shaded with the even color. Periods with odd index will be shaded with * the odd color. The reference defines the marker with the given index to * be at the specified time. * * @param category * the marker category * @param reference * the reference marker time and index * @param period * the period in time units * @param rollover * the number of periods before the index rolls-over to 0, or 0 * for no roll-over * @param evenColor * the even marker color * @param oddColor * the odd marker color * @param foreground * true if the marker is drawn in foreground, and false otherwise */ public PeriodicMarkerEventSource(String category, Reference reference, double period, long rollover, RGBA evenColor, RGBA oddColor, boolean foreground) { this(category, reference, period, rollover, foreground, evenColor, oddColor); } /* Private constructor. The order of parameters is changed to make it unique. */ private PeriodicMarkerEventSource(String category, Reference reference, double period, long rollover, boolean foreground, RGBA evenColor, @Nullable RGBA oddColor) { if (period <= 0) { throw new IllegalArgumentException("period cannot be less than or equal to zero"); //$NON-NLS-1$ } if (rollover < 0) { throw new IllegalArgumentException("rollover cannot be less than zero"); //$NON-NLS-1$ } fCategory = category; fReference = reference; fPeriod = period; fPeriodInteger = (long) period; try { fPeriodFraction = Fraction.getFraction(fPeriod - fPeriodInteger); } catch (ArithmeticException e) { /* can't convert to fraction, use floating-point arithmetic */ fPeriodFraction = null; } fRollover = rollover; fColor = evenColor; fOddColor = oddColor; fForeground = foreground; } @Override public List<String> getMarkerCategories() { return Arrays.asList(fCategory); } @Override public List<IMarkerEvent> getMarkerList(String category, long startTime, long endTime, long resolution, IProgressMonitor monitor) { if (startTime > endTime) { return Collections.emptyList(); } List<IMarkerEvent> markers = new ArrayList<>(); /* Subtract 1.5 periods to ensure previous marker is included */ long time = startTime - Math.max(Math.round(1.5 * fPeriod), resolution); Reference reference = adjustReference(fReference, time); IMarkerEvent markerEvent = null; while (true) { long index = Math.round((time - reference.time) / fPeriod) + reference.index; time = Math.round((index - reference.index) * fPeriod) + reference.time; long duration = (fOddColor == null) ? 0 : Math.round((index + 1 - reference.index) * fPeriod) + reference.time - time; long labelIndex = index; if (fRollover != 0) { labelIndex %= fRollover; if (labelIndex < 0) { labelIndex += fRollover; } } /* Add previous marker if current is visible */ if ((time >= startTime || time + duration > startTime) && markerEvent != null) { markers.add(markerEvent); } RGBA color = (fOddColor == null) ? fColor : (index % 2 == 0) ? fColor : fOddColor; markerEvent = new MarkerEvent(null, time, duration, fCategory, color, getMarkerLabel(labelIndex), fForeground); if (time > endTime) { /* The next marker out of range is included */ markers.add(markerEvent); break; } time += Math.max(Math.round(fPeriod), resolution); } return markers; } /* * Adjust to a reference that is closer to the start time, to avoid rounding * errors in floating point calculations with large numbers. */ private Reference adjustReference(Reference baseReference, long time) { long offsetIndex = (long) ((time - baseReference.time) / fPeriod); long offsetTime = 0; Fraction fraction = fPeriodFraction; if (fraction != null) { /* * If period = int num/den, find an offset index that is an exact * multiple of den and calculate index * period = (index * int) + * (index / den * num), all exact calculations. */ offsetIndex = offsetIndex - offsetIndex % fraction.getDenominator(); offsetTime = offsetIndex * fPeriodInteger + offsetIndex / fraction.getDenominator() * fraction.getNumerator(); } else { /* * Couldn't compute fractional part as fraction, use simple * multiplication but with possible rounding error. */ offsetTime = Math.round(offsetIndex * fPeriod); } Reference reference = new Reference(baseReference.time + offsetTime, baseReference.index + offsetIndex); return reference; } /** * Get the marker label for the given marker index. * <p> * This method can be overridden by clients. * * @param index * the marker index * @return the marker label */ public String getMarkerLabel(long index) { return checkNotNull(Long.toString(index)); } }