564 lines
20 KiB
Java
564 lines
20 KiB
Java
/*******************************************************************************
|
|
* Copyright (c) 2015 MINRES Technologies GmbH and others.
|
|
* 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:
|
|
* MINRES Technologies GmbH - initial API and implementation
|
|
*******************************************************************************/
|
|
package com.minres.scviewer.database.swt.internal;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.HashMap;
|
|
import java.util.LinkedList;
|
|
import java.util.List;
|
|
import java.util.Map.Entry;
|
|
|
|
import org.eclipse.swt.SWT;
|
|
import org.eclipse.swt.events.ControlAdapter;
|
|
import org.eclipse.swt.events.ControlEvent;
|
|
import org.eclipse.swt.events.PaintEvent;
|
|
import org.eclipse.swt.events.PaintListener;
|
|
import org.eclipse.swt.events.SelectionAdapter;
|
|
import org.eclipse.swt.events.SelectionEvent;
|
|
import org.eclipse.swt.graphics.Color;
|
|
import org.eclipse.swt.graphics.GC;
|
|
import org.eclipse.swt.graphics.Point;
|
|
import org.eclipse.swt.graphics.RGB;
|
|
import org.eclipse.swt.graphics.Rectangle;
|
|
import org.eclipse.swt.graphics.Transform;
|
|
import org.eclipse.swt.widgets.Canvas;
|
|
import org.eclipse.swt.widgets.Composite;
|
|
import org.eclipse.swt.widgets.Display;
|
|
import org.eclipse.swt.widgets.Event;
|
|
import org.eclipse.swt.widgets.ScrollBar;
|
|
import org.eclipse.wb.swt.SWTResourceManager;
|
|
|
|
import com.google.common.collect.Lists;
|
|
import com.minres.scviewer.database.ITx;
|
|
import com.minres.scviewer.database.IWaveform;
|
|
import com.minres.scviewer.database.RelationType;
|
|
import com.minres.scviewer.database.swt.Constants;
|
|
import com.minres.scviewer.database.ui.IWaveformViewer;
|
|
import com.minres.scviewer.database.ui.TrackEntry;
|
|
import com.minres.scviewer.database.ui.WaveformColors;
|
|
|
|
public class WaveformCanvas extends Canvas{
|
|
|
|
Color[] colors = new Color[WaveformColors.values().length];
|
|
|
|
private int trackHeight = 50;
|
|
|
|
private long scaleFactor = 1000000L; // 1ns
|
|
|
|
String unit="ns";
|
|
|
|
private int level = 12;
|
|
|
|
private long maxTime;
|
|
|
|
protected Point origin; /* original size */
|
|
|
|
protected Transform transform;
|
|
|
|
protected int rulerHeight=40;
|
|
|
|
protected List<IPainter> painterList;
|
|
|
|
ITx currentSelection;
|
|
|
|
private List<SelectionAdapter> selectionListeners;
|
|
|
|
private RulerPainter rulerPainter;
|
|
|
|
private TrackAreaPainter trackAreaPainter;
|
|
|
|
private ArrowPainter arrowPainter;
|
|
|
|
private List<CursorPainter> cursorPainters;
|
|
|
|
HashMap<IWaveform, IWaveformPainter> wave2painterMap;
|
|
/**
|
|
* Constructor for ScrollableCanvas.
|
|
*
|
|
* @param parent
|
|
* the parent of this control.super(parent, style | SWT.DOUBLE_BUFFERED | SWT.NO_BACKGROUND | SWT.NO_REDRAW_RESIZE | SWT.V_SCROLL | SWT.H_SCROLL);
|
|
* @param style
|
|
* the style of this control.
|
|
*/
|
|
public WaveformCanvas(final Composite parent, int style) {
|
|
super(parent, style | SWT.DOUBLE_BUFFERED | SWT.NO_BACKGROUND | SWT.NO_REDRAW_RESIZE | SWT.V_SCROLL | SWT.H_SCROLL);
|
|
addControlListener(new ControlAdapter() { /* resize listener. */
|
|
public void controlResized(ControlEvent event) {
|
|
syncScrollBars();
|
|
}
|
|
});
|
|
addPaintListener(new PaintListener() { /* paint listener. */
|
|
public void paintControl(final PaintEvent event) {
|
|
paint(event.gc);
|
|
}
|
|
});
|
|
painterList = new LinkedList<IPainter>();
|
|
origin = new Point(0, 0);
|
|
transform = new Transform(getDisplay());
|
|
selectionListeners = new LinkedList<>();
|
|
cursorPainters= new ArrayList<>();
|
|
wave2painterMap=new HashMap<>();
|
|
|
|
initScrollBars();
|
|
initColors(null);
|
|
// order is important: it is bottom to top
|
|
trackAreaPainter=new TrackAreaPainter(this);
|
|
painterList.add(trackAreaPainter);
|
|
rulerPainter=new RulerPainter(this);
|
|
painterList.add(rulerPainter);
|
|
arrowPainter=new ArrowPainter(this, IWaveformViewer.NEXT_PREV_IN_STREAM);
|
|
painterList.add(arrowPainter);
|
|
CursorPainter cp = new CursorPainter(this, scaleFactor * 10, cursorPainters.size()-1);
|
|
painterList.add(cp);
|
|
cursorPainters.add(cp);
|
|
CursorPainter marker = new CursorPainter(this, scaleFactor * 100, cursorPainters.size()-1);
|
|
painterList.add(marker);
|
|
cursorPainters.add(marker);
|
|
wave2painterMap=new HashMap<>();
|
|
// fall back initialization
|
|
colors[WaveformColors.LINE.ordinal()] = SWTResourceManager.getColor(SWT.COLOR_RED);
|
|
colors[WaveformColors.LINE_HIGHLITE.ordinal()] = SWTResourceManager.getColor(SWT.COLOR_CYAN);
|
|
colors[WaveformColors.TRACK_BG_EVEN.ordinal()] = SWTResourceManager.getColor(SWT.COLOR_BLACK);
|
|
colors[WaveformColors.TRACK_BG_ODD.ordinal()] = SWTResourceManager.getColor(40, 40, 40);
|
|
colors[WaveformColors.TRACK_BG_HIGHLITE.ordinal()] = SWTResourceManager.getColor(40, 40, 80);
|
|
colors[WaveformColors.TX_BG.ordinal()] = SWTResourceManager.getColor(SWT.COLOR_GREEN);
|
|
colors[WaveformColors.TX_BG_HIGHLITE.ordinal()] = SWTResourceManager.getColor(SWT.COLOR_DARK_GREEN);
|
|
colors[WaveformColors.TX_BORDER.ordinal()] = SWTResourceManager.getColor(SWT.COLOR_RED);
|
|
colors[WaveformColors.SIGNAL0.ordinal()] = SWTResourceManager.getColor(SWT.COLOR_GREEN);
|
|
colors[WaveformColors.SIGNAL1.ordinal()] = SWTResourceManager.getColor(SWT.COLOR_GREEN);
|
|
colors[WaveformColors.SIGNALZ.ordinal()] = SWTResourceManager.getColor(SWT.COLOR_DARK_YELLOW);
|
|
colors[WaveformColors.SIGNALX.ordinal()] = SWTResourceManager.getColor(255, 51, 51);
|
|
colors[WaveformColors.SIGNALU.ordinal()] = SWTResourceManager.getColor(SWT.COLOR_YELLOW);
|
|
colors[WaveformColors.SIGNAL_TEXT.ordinal()] = SWTResourceManager.getColor(SWT.COLOR_WHITE);
|
|
colors[WaveformColors.SIGNAL_REAL.ordinal()] = SWTResourceManager.getColor(SWT.COLOR_YELLOW);
|
|
colors[WaveformColors.SIGNAL_NAN.ordinal()] = SWTResourceManager.getColor(SWT.COLOR_RED);
|
|
colors[WaveformColors.CURSOR.ordinal()] = SWTResourceManager.getColor(SWT.COLOR_RED);
|
|
colors[WaveformColors.CURSOR_DRAG.ordinal()] = SWTResourceManager.getColor(SWT.COLOR_GRAY);
|
|
colors[WaveformColors.CURSOR_TEXT.ordinal()] = SWTResourceManager.getColor(SWT.COLOR_WHITE);
|
|
colors[WaveformColors.MARKER.ordinal()] = SWTResourceManager.getColor(SWT.COLOR_DARK_GRAY);
|
|
colors[WaveformColors.MARKER_TEXT.ordinal()] = SWTResourceManager.getColor(SWT.COLOR_WHITE);
|
|
colors[WaveformColors.REL_ARROW.ordinal()] = SWTResourceManager.getColor(SWT.COLOR_MAGENTA);
|
|
colors[WaveformColors.REL_ARROW_HIGHLITE.ordinal()] = SWTResourceManager.getColor(255, 128, 255);
|
|
|
|
}
|
|
|
|
public long getXOffset() {
|
|
return -origin.x;
|
|
}
|
|
|
|
public void addCursoPainter(CursorPainter cursorPainter){
|
|
painterList.add(cursorPainter);
|
|
cursorPainters.add(cursorPainter);
|
|
}
|
|
|
|
public void initColors(HashMap<WaveformColors, RGB> colourMap) {
|
|
Display d = getDisplay();
|
|
if (colourMap != null) {
|
|
for (WaveformColors c : WaveformColors.values()) {
|
|
if (colourMap.containsKey(c))
|
|
colors[c.ordinal()] = new Color(d, colourMap.get(c));
|
|
}
|
|
redraw();
|
|
}
|
|
}
|
|
|
|
public void setHighliteRelation(RelationType relationType){
|
|
if(arrowPainter!=null){
|
|
boolean redraw = arrowPainter.getHighlightType()!=relationType;
|
|
arrowPainter.setHighlightType(relationType);
|
|
if(redraw) redraw();
|
|
}
|
|
}
|
|
|
|
public Point getOrigin() {
|
|
return origin;
|
|
}
|
|
|
|
public int getWidth() {
|
|
return getClientArea().width;
|
|
}
|
|
public void setOrigin(Point origin) {
|
|
setOrigin(origin.x, origin.y);
|
|
}
|
|
|
|
public void setOrigin(int x, int y) {
|
|
checkWidget();
|
|
ScrollBar hBar = getHorizontalBar();
|
|
hBar.setSelection(-x);
|
|
x = -hBar.getSelection();
|
|
ScrollBar vBar = getVerticalBar();
|
|
vBar.setSelection(-y);
|
|
y = -vBar.getSelection();
|
|
origin.x = x;
|
|
origin.y = y;
|
|
syncScrollBars();
|
|
}
|
|
|
|
public long getMaxTime() {
|
|
return maxTime;
|
|
}
|
|
|
|
public void setMaxTime(long maxTime) {
|
|
this.maxTime = maxTime;
|
|
syncScrollBars();
|
|
}
|
|
|
|
public int getTrackHeight() {
|
|
return trackHeight;
|
|
}
|
|
|
|
public void setTrackHeight(int trackHeight) {
|
|
this.trackHeight = trackHeight;
|
|
syncScrollBars();
|
|
}
|
|
|
|
public int getZoomLevel() {
|
|
return level;
|
|
}
|
|
|
|
public int getMaxZoomLevel(){
|
|
return Constants.unitMultiplier.length*Constants.unitString.length-1;
|
|
}
|
|
|
|
public void setZoomLevel(int level) {
|
|
long oldScaleFactor=scaleFactor;
|
|
if(level<Constants.unitMultiplier.length*Constants.unitString.length){
|
|
this.level = level;
|
|
this.scaleFactor = (long) Math.pow(10, level/2);
|
|
if(level%2==1) this.scaleFactor*=3;
|
|
ITx tx = arrowPainter.getTx();
|
|
arrowPainter.setTx(null);
|
|
/*
|
|
* xc = tc/oldScaleFactor
|
|
* xoffs = xc+origin.x
|
|
* xcn = tc/newScaleFactor
|
|
* t0n = (xcn-xoffs)*scaleFactor
|
|
*/
|
|
long tc=cursorPainters.get(0).getTime(); // cursor time
|
|
long xc=tc/oldScaleFactor; // cursor total x-offset
|
|
long xoffs=xc+origin.x; // cursor offset relative to left border
|
|
long xcn=tc/scaleFactor; // new total x-offset
|
|
long originX=xcn-xoffs;
|
|
if(originX>0) {
|
|
origin.x=(int) -originX; // new cursor time offset relative to left border
|
|
}else {
|
|
origin.x=0;
|
|
}
|
|
syncScrollBars();
|
|
arrowPainter.setTx(tx);
|
|
redraw();
|
|
}
|
|
}
|
|
|
|
public long getScaleFactor() {
|
|
return scaleFactor;
|
|
}
|
|
|
|
public long getScaleFactorPow10() {
|
|
int scale = level/Constants.unitMultiplier.length;
|
|
double res = Math.pow(1000, scale);
|
|
return (long) res;
|
|
}
|
|
|
|
public String getUnitStr(){
|
|
return Constants.unitString[level/Constants.unitMultiplier.length];
|
|
}
|
|
|
|
public int getUnitMultiplier(){
|
|
return Constants.unitMultiplier[level%Constants.unitMultiplier.length];
|
|
}
|
|
|
|
public long getTimeForOffset(int xOffset){
|
|
return (xOffset-origin.x) * scaleFactor;
|
|
}
|
|
|
|
public void addPainter(IPainter painter) {
|
|
painterList.add(painter);
|
|
redraw();
|
|
}
|
|
|
|
public void removePainter(IPainter painter) {
|
|
painterList.remove(painter);
|
|
redraw();
|
|
}
|
|
|
|
public void clearAllWaveformPainter() {
|
|
clearAllWaveformPainter(true);
|
|
}
|
|
|
|
void clearAllWaveformPainter(boolean update) {
|
|
trackAreaPainter.getTrackVerticalOffset().clear();
|
|
wave2painterMap.clear();
|
|
if(update) syncScrollBars();
|
|
}
|
|
|
|
public void addWaveformPainter(IWaveformPainter painter) {
|
|
addWaveformPainter(painter, true);
|
|
}
|
|
|
|
void addWaveformPainter(IWaveformPainter painter, boolean update) {
|
|
trackAreaPainter.addTrackPainter(painter);
|
|
wave2painterMap.put(painter.getTrackEntry().waveform, painter);
|
|
if(update) syncScrollBars();
|
|
}
|
|
|
|
public List<CursorPainter> getCursorPainters() {
|
|
return cursorPainters;
|
|
}
|
|
|
|
/**
|
|
* Dispose the garbage here
|
|
*/
|
|
public void dispose() {
|
|
transform.dispose();
|
|
for (WaveformColors c : WaveformColors.values())
|
|
colors[c.ordinal()].dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
/* Initialize the scrollbar and register listeners. */
|
|
private void initScrollBars() {
|
|
ScrollBar horizontal = getHorizontalBar();
|
|
horizontal.setEnabled(false);
|
|
horizontal.setVisible(true);
|
|
horizontal.addSelectionListener(new SelectionAdapter() {
|
|
public void widgetSelected(SelectionEvent event) {
|
|
if (painterList.size() == 0)
|
|
return;
|
|
setOrigin(-((ScrollBar) event.widget).getSelection(), origin.y);
|
|
}
|
|
});
|
|
ScrollBar vertical = getVerticalBar();
|
|
vertical.setEnabled(false);
|
|
vertical.setVisible(true);
|
|
vertical.addSelectionListener(new SelectionAdapter() {
|
|
public void widgetSelected(SelectionEvent event) {
|
|
if (painterList.size() == 0)
|
|
return;
|
|
setOrigin(origin.x, -((ScrollBar) event.widget).getSelection());
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Synchronize the scrollbar with the image. If the transform is out of
|
|
* range, it will correct it. This function considers only following factors
|
|
* :<b> transform, image size, client area</b>.
|
|
*/
|
|
public void syncScrollBars() {
|
|
if (painterList.size() == 0) {
|
|
redraw();
|
|
return;
|
|
}
|
|
int height = trackAreaPainter.getHeight(); // incl. Ruler
|
|
long width = maxTime / scaleFactor;
|
|
Rectangle clientArea=getClientArea();
|
|
ScrollBar horizontal = getHorizontalBar();
|
|
horizontal.setIncrement((int) (getClientArea().width / 100));
|
|
horizontal.setPageIncrement(getClientArea().width);
|
|
int clientWidthw = clientArea.width;
|
|
if (width > clientWidthw) { /* image is wider than client area */
|
|
horizontal.setMinimum(0);
|
|
horizontal.setMaximum((int)width);
|
|
horizontal.setEnabled(true);
|
|
if (((int) -origin.x) > horizontal.getMaximum() - clientWidthw) {
|
|
origin.x = -horizontal.getMaximum() + clientWidthw;
|
|
}
|
|
} else { /* image is narrower than client area */
|
|
horizontal.setEnabled(false);
|
|
}
|
|
horizontal.setThumb(clientWidthw);
|
|
horizontal.setSelection(-origin.x);
|
|
|
|
ScrollBar vertical = getVerticalBar();
|
|
vertical.setIncrement((int) (getClientArea().height / 100));
|
|
vertical.setPageIncrement((int) (getClientArea().height));
|
|
int clientHeighth = clientArea.height;
|
|
if (height > clientHeighth) { /* image is higher than client area */
|
|
vertical.setMinimum(0);
|
|
vertical.setMaximum(height);
|
|
vertical.setEnabled(true);
|
|
if (((int) -origin.y) > vertical.getMaximum() - clientHeighth) {
|
|
origin.y = -vertical.getMaximum() + clientHeighth;
|
|
}
|
|
} else { /* image is less higher than client area */
|
|
vertical.setMaximum((int) (clientHeighth));
|
|
vertical.setEnabled(false);
|
|
}
|
|
vertical.setThumb(clientHeighth);
|
|
vertical.setSelection(-origin.y);
|
|
redraw();
|
|
fireSelectionEvent();
|
|
|
|
}
|
|
|
|
/* Paint function */
|
|
private void paint(GC gc) {
|
|
Rectangle clientRect = getClientArea(); /* Canvas' painting area */
|
|
// clientRect.x = -origin.x;
|
|
clientRect.y = -origin.y;
|
|
// reset the transform
|
|
transform.identity();
|
|
// shift the content
|
|
// DO NOT SHIFT HORIZONTALLY, the range is WAY TOO BIG for float!!!
|
|
transform.translate(0, origin.y);
|
|
gc.setTransform(transform);
|
|
gc.setClipping(clientRect);
|
|
if (painterList.size() > 0 ) {
|
|
for (IPainter painter : painterList)
|
|
painter.paintArea(gc, clientRect);
|
|
} else {
|
|
gc.fillRectangle(clientRect);
|
|
initScrollBars();
|
|
}
|
|
}
|
|
|
|
public List<Object> getClicked(Point point) {
|
|
LinkedList<Object> result=new LinkedList<>();
|
|
for (IPainter p : Lists.reverse(painterList)) {
|
|
if (p instanceof TrackAreaPainter) {
|
|
int y = point.y - origin.y;
|
|
int x = point.x - origin.x;
|
|
Entry<Integer, IWaveformPainter> entry = trackAreaPainter.getTrackVerticalOffset().floorEntry(y);
|
|
if (entry != null) {
|
|
if (entry.getValue() instanceof StreamPainter) {
|
|
ITx tx = ((StreamPainter) entry.getValue()).getClicked(new Point(x, y - entry.getKey()));
|
|
if(tx!=null)
|
|
result.add(tx);
|
|
}
|
|
result.add(entry.getValue().getTrackEntry());
|
|
}
|
|
} else if (p instanceof CursorPainter) {
|
|
if (Math.abs(point.x - origin.x - ((CursorPainter) p).getTime()/scaleFactor) < 2) {
|
|
result.add(p);
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
public List<Object> getEntriesAtPosition(IWaveform iWaveform, int i) {
|
|
LinkedList<Object> result=new LinkedList<>();
|
|
int x = i - origin.x;
|
|
for(IPainter p: wave2painterMap.values()){
|
|
if (p instanceof StreamPainter && ((StreamPainter)p).getStream()==iWaveform) {
|
|
result.add(((StreamPainter) p).getClicked(new Point(x, trackHeight/2)));
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
public void setSelected(ITx currentSelection) {
|
|
this.currentSelection = currentSelection;
|
|
if (currentSelection != null)
|
|
reveal(currentSelection);
|
|
arrowPainter.setTx(currentSelection);
|
|
redraw();
|
|
}
|
|
|
|
public void reveal(ITx tx) {
|
|
int lower = (int) (tx.getBeginTime() / scaleFactor);
|
|
int higher = (int) (tx.getEndTime() / scaleFactor);
|
|
Point size = getSize();
|
|
size.x -= getVerticalBar().getSize().x + 2;
|
|
size.y -= getHorizontalBar().getSize().y;
|
|
if (lower < -origin.x) {
|
|
setOrigin(-lower, origin.y);
|
|
} else if (higher > (size.x - origin.x)) {
|
|
setOrigin(size.x - higher, origin.y);
|
|
}
|
|
for (IWaveformPainter painter : wave2painterMap.values()) {
|
|
if (painter instanceof StreamPainter && ((StreamPainter) painter).getStream() == tx.getStream()) {
|
|
int top = painter.getVerticalOffset() + trackHeight * tx.getConcurrencyIndex();
|
|
int bottom = top + trackHeight;
|
|
if (top < -origin.y) {
|
|
setOrigin(origin.x, -(top-trackHeight));
|
|
} else if (bottom > (size.y - origin.y)) {
|
|
setOrigin(origin.x, size.y - bottom);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void reveal(IWaveform waveform) {
|
|
for (IWaveformPainter painter : wave2painterMap.values()) {
|
|
TrackEntry te = painter.getTrackEntry();
|
|
if(te.waveform == waveform) {
|
|
Point size = getSize();
|
|
//size.x -= getVerticalBar().getSize().x + 2;
|
|
size.y -=+rulerHeight;
|
|
ScrollBar sb = getHorizontalBar();
|
|
if((sb.getStyle()&SWT.SCROLLBAR_OVERLAY)!=0 && sb.isVisible()) // TODO: check on other platform than MacOSX
|
|
size.y-= getHorizontalBar().getSize().y;
|
|
int top = te.vOffset;
|
|
int bottom = top + trackHeight;
|
|
if (top < -origin.y) {
|
|
setOrigin(origin.x, -(top-trackHeight));
|
|
} else if (bottom > (size.y - origin.y)) {
|
|
setOrigin(origin.x, size.y - bottom);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void reveal(long time) {
|
|
int scaledTime = (int) (time / scaleFactor);
|
|
Point size = getSize();
|
|
size.x -= getVerticalBar().getSize().x + 2;
|
|
size.y -= getHorizontalBar().getSize().y;
|
|
if (scaledTime < -origin.x) {
|
|
setOrigin(-scaledTime+10, origin.y);
|
|
} else if (scaledTime > (size.x - origin.x)) {
|
|
setOrigin(size.x - scaledTime-30, origin.y);
|
|
}
|
|
}
|
|
|
|
public int getRulerHeight() {
|
|
return rulerHeight;
|
|
}
|
|
|
|
public void setRulerHeight(int rulerHeight) {
|
|
this.rulerHeight = rulerHeight;
|
|
}
|
|
|
|
public void addSelectionListener(SelectionAdapter selectionAdapter) {
|
|
selectionListeners.add(selectionAdapter);
|
|
}
|
|
|
|
public void removeSelectionListener(SelectionAdapter selectionAdapter) {
|
|
selectionListeners.remove(selectionAdapter);
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
protected void fireSelectionEvent() {
|
|
Event e = new Event();
|
|
e.widget = this;
|
|
e.detail=SWT.SELECTED;
|
|
e.type=SWT.Selection;
|
|
SelectionEvent ev = new SelectionEvent(e);
|
|
ev.x = origin.x;
|
|
ev.y = origin.y;
|
|
for (SelectionAdapter a : selectionListeners) {
|
|
a.widgetSelected(ev);
|
|
}
|
|
}
|
|
|
|
long getMaxVisibleTime() {
|
|
return (getClientArea().width+origin.x)*scaleFactor;
|
|
}
|
|
|
|
long getOriginTime() {
|
|
return origin.x * scaleFactor;
|
|
}
|
|
}
|