SCViewer/plugins/com.minres.scviewer.databas.../src/com/minres/scviewer/database/ui/swt/internal/WaveformCanvas.java

544 lines
19 KiB
Java
Raw Normal View History

2015-01-20 18:50:15 +01:00
/*******************************************************************************
2021-01-09 14:26:49 +01:00
* Copyright (c) 2015-2021 MINRES Technologies GmbH and others.
2015-01-20 18:50:15 +01:00
* 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
*******************************************************************************/
2020-07-15 21:42:10 +02:00
package com.minres.scviewer.database.ui.swt.internal;
2015-01-20 18:50:15 +01:00
import java.util.ArrayList;
2021-01-14 23:14:22 +01:00
import java.util.Arrays;
2015-01-20 18:50:15 +01:00
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map.Entry;
2021-01-14 23:14:22 +01:00
import java.util.Optional;
2015-01-20 18:50:15 +01:00
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.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.GC;
2020-07-11 19:36:28 +02:00
import org.eclipse.swt.graphics.Image;
2015-01-20 18:50:15 +01:00
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Event;
2015-01-20 18:50:15 +01:00
import org.eclipse.swt.widgets.ScrollBar;
import com.google.common.collect.Lists;
2021-02-27 13:26:07 +01:00
import com.minres.scviewer.database.EventEntry;
2021-01-14 23:14:22 +01:00
import com.minres.scviewer.database.IEvent;
2015-01-20 18:50:15 +01:00
import com.minres.scviewer.database.IWaveform;
import com.minres.scviewer.database.RelationType;
2020-11-28 14:47:43 +01:00
import com.minres.scviewer.database.tx.ITx;
2021-01-14 23:14:22 +01:00
import com.minres.scviewer.database.tx.ITxEvent;
2020-11-28 19:41:00 +01:00
import com.minres.scviewer.database.ui.IWaveformStyleProvider;
2020-07-11 19:36:28 +02:00
import com.minres.scviewer.database.ui.IWaveformView;
import com.minres.scviewer.database.ui.TrackEntry;
2020-07-15 21:42:10 +02:00
import com.minres.scviewer.database.ui.swt.Constants;
2015-01-20 18:50:15 +01:00
2020-06-20 17:58:26 +02:00
public class WaveformCanvas extends Canvas {
2020-07-11 19:36:28 +02:00
private boolean doubleBuffering = true;
2020-11-28 19:41:00 +01:00
IWaveformStyleProvider styleProvider;
private long scaleFactor = 1000000L; // 1ns
2015-07-10 13:40:50 +02:00
String unit="ns";
private int level = 12;
private long maxTime;
protected Point origin; /* original size */
2020-06-20 17:58:26 +02:00
2015-07-10 13:40:50 +02:00
protected int rulerHeight=40;
protected List<IPainter> painterList;
ITx currentSelection;
private List<SelectionAdapter> selectionListeners;
private RulerPainter rulerPainter;
private TrackAreaPainter trackAreaPainter;
2015-11-13 23:44:44 +01:00
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.
*/
2020-11-28 19:41:00 +01:00
public WaveformCanvas(final Composite parent, int style, IWaveformStyleProvider styleProvider) {
2020-06-20 17:58:26 +02:00
super(parent, style | SWT.DOUBLE_BUFFERED | SWT.NO_BACKGROUND | SWT.V_SCROLL | SWT.H_SCROLL);
2020-11-28 19:41:00 +01:00
this.styleProvider=styleProvider;
addControlListener(new ControlAdapter() { /* resize listener. */
@Override
public void controlResized(ControlEvent event) {
syncScrollBars();
}
});
addPaintListener((final PaintEvent event) -> paint(event.gc));
painterList = new LinkedList<>();
origin = new Point(0, 0);
selectionListeners = new LinkedList<>();
cursorPainters= new ArrayList<>();
wave2painterMap=new HashMap<>();
initScrollBars();
// order is important: it is bottom to top
trackAreaPainter=new TrackAreaPainter(this);
painterList.add(trackAreaPainter);
2020-07-11 19:36:28 +02:00
arrowPainter=new ArrowPainter(this, IWaveformView.NEXT_PREV_IN_STREAM);
2015-11-13 23:44:44 +01:00
painterList.add(arrowPainter);
2020-10-17 13:13:34 +02:00
rulerPainter=new RulerPainter(this);
painterList.add(rulerPainter);
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<>();
}
public void addCursoPainter(CursorPainter cursorPainter){
painterList.add(cursorPainter);
cursorPainters.add(cursorPainter);
}
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();
}
2015-07-10 13:40:50 +02:00
public int getZoomLevel() {
return level;
}
public int getMaxZoomLevel(){
return Constants.UNIT_MULTIPLIER.length*Constants.UNIT_STRING.length-1;
}
2015-07-10 13:40:50 +02:00
public void setZoomLevel(int level) {
2020-03-13 21:06:53 +01:00
long tc=cursorPainters.get(0).getTime(); // cursor time
setZoomLevel(level, tc);
}
public void setZoomLevel(int level, long centerTime) {
2021-03-25 22:07:05 +01:00
if(level<0) {
level = findFitZoomLevel();
if(level<0) level = 0;
}
//FIXME: keep center if zoom-out and cursor is not in view
2021-02-17 21:24:25 +01:00
long xc=centerTime/this.scaleFactor; // cursor total x-offset
if(level<Constants.UNIT_MULTIPLIER.length*Constants.UNIT_STRING.length){
2021-02-17 21:24:25 +01:00
this.scaleFactor = (long) Math.pow(10, level>>1);
if(level%2==1) this.scaleFactor*=3;
2015-11-13 23:44:44 +01:00
ITx tx = arrowPainter.getTx();
arrowPainter.setTx(null);
/*
* xc = tc/oldScaleFactor
* xoffs = xc+origin.x
* xcn = tc/newScaleFactor
* t0n = (xcn-xoffs)*scaleFactor
*/
long xoffs=xc+origin.x; // cursor offset relative to left border
long xcn=centerTime/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);
2015-11-13 23:44:44 +01:00
redraw();
this.level = level;
}
}
2021-03-25 22:07:05 +01:00
private int findFitZoomLevel() {
//get area actually capable of displaying data, i.e. area of the receiver which is capable of displaying data
Rectangle clientArea = getClientArea();
long clientAreaWidth = clientArea.width;
//try to find existing zoomlevel where scaleFactor*clientAreaWidth >= maxTime, if one is found set it as new zoomlevel
int magnitude_factor=1;
for(int magnitude=0; magnitude<Constants.UNIT_STRING.length; magnitude++) {
for (int multiplier=0; multiplier<Constants.UNIT_MULTIPLIER.length; multiplier++){
int tempLevel = magnitude*Constants.UNIT_MULTIPLIER.length+multiplier;
long scaleFactor = Constants.UNIT_MULTIPLIER[multiplier]*magnitude_factor;
if(scaleFactor*clientAreaWidth >= maxTime)
return tempLevel;
}
magnitude_factor*=1000;
}
return -1;
}
public long getScaleFactor() {
return scaleFactor;
}
public long getScaleFactorPow10() {
int scale = level/Constants.UNIT_MULTIPLIER.length;
double res = Math.pow(1000, scale);
return (long) res;
}
2015-07-10 13:40:50 +02:00
public String getUnitStr(){
return Constants.UNIT_STRING[level/Constants.UNIT_MULTIPLIER.length];
2015-07-10 13:40:50 +02:00
}
2015-07-10 13:40:50 +02:00
public int getUnitMultiplier(){
return Constants.UNIT_MULTIPLIER[level%Constants.UNIT_MULTIPLIER.length];
2015-07-10 13:40:50 +02:00
}
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.trackVerticalOffset.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;
}
/* Initialize the scrollbar and register listeners. */
private void initScrollBars() {
ScrollBar horizontal = getHorizontalBar();
horizontal.setEnabled(false);
horizontal.setVisible(true);
horizontal.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent event) {
if (painterList.isEmpty())
return;
setOrigin(-((ScrollBar) event.widget).getSelection(), origin.y);
}
});
ScrollBar vertical = getVerticalBar();
vertical.setEnabled(false);
vertical.setVisible(true);
vertical.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent event) {
if (painterList.isEmpty())
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.isEmpty()) {
redraw();
return;
}
int height = trackAreaPainter.getHeight(); // incl. Ruler
long width = maxTime / scaleFactor;
Rectangle clientArea=getClientArea();
ScrollBar horizontal = getHorizontalBar();
horizontal.setIncrement(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 (-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(getClientArea().height / 100);
vertical.setPageIncrement(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 ( -origin.y > vertical.getMaximum() - clientHeighth) {
origin.y = -vertical.getMaximum() + clientHeighth;
}
} else { /* image is less higher than client area */
vertical.setMaximum(clientHeighth);
vertical.setEnabled(false);
}
vertical.setThumb(clientHeighth);
vertical.setSelection(-origin.y);
redraw();
fireSelectionEvent();
}
/* Paint function */
private void paint(GC gc) {
Point pt = getSize();
if(pt.x==0 || pt.y==0) return;
Rectangle clientRect = getClientArea(); /* Canvas' painting area */
2020-07-11 19:36:28 +02:00
GC thisGc = gc;
Image dBackingImg = null;
2020-07-11 19:36:28 +02:00
if(doubleBuffering) {
dBackingImg = new Image(getDisplay(), pt.x, pt.y);
thisGc = new GC(dBackingImg);
2020-07-11 19:36:28 +02:00
thisGc.setBackground(gc.getBackground());
thisGc.setForeground(gc.getForeground());
thisGc.setFont(gc.getFont());
}
Projection p = new Projection(thisGc);
2020-06-20 17:58:26 +02:00
p.setTranslation(origin);
if (!painterList.isEmpty() ) {
for (IPainter painter : painterList)
2020-06-20 17:58:26 +02:00
painter.paintArea(p, clientRect);
} else {
gc.fillRectangle(clientRect);
initScrollBars();
}
2020-07-11 19:36:28 +02:00
if(doubleBuffering) {
gc.drawImage(dBackingImg, 0, 0);
if(dBackingImg!=null) dBackingImg.dispose();
2020-07-11 19:36:28 +02:00
thisGc.dispose();
}
}
2020-03-21 06:28:28 +01:00
public List<Object> getElementsAt(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.trackVerticalOffset.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(IWaveformPainter p: wave2painterMap.values()){
if (p instanceof StreamPainter && ((StreamPainter)p).getStream()==iWaveform) {
2020-11-28 19:41:00 +01:00
result.add(((StreamPainter) p).getClicked(new Point(x, styleProvider.getTrackHeight()/2)));
}
}
return result;
}
public void setSelected(ITx currentSelection) {
this.currentSelection = currentSelection;
if (currentSelection != null)
reveal(currentSelection);
2015-11-13 23:44:44 +01:00
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()) {
2021-02-27 13:26:07 +01:00
EventEntry entry = tx.getStream().getEvents().floorEntry(tx.getBeginTime());
Optional<IEvent> res = Arrays.stream(entry.events).filter(e -> ((ITxEvent)e).getTransaction().equals(tx)).findFirst();
2021-01-14 23:14:22 +01:00
if(res.isPresent()) {
int top = painter.getVerticalOffset() + styleProvider.getTrackHeight() * ((ITxEvent)res.get()).getRowIndex();
int bottom = top + styleProvider.getTrackHeight();
if (top < -origin.y) {
setOrigin(origin.x, -(top-styleProvider.getTrackHeight()));
} 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.y -=+rulerHeight;
ScrollBar sb = getHorizontalBar();
if((sb.getStyle()&SWT.SCROLLBAR_OVERLAY)!=0 && sb.isVisible())
size.y-= getHorizontalBar().getSize().y;
int top = te.vOffset;
2020-11-28 19:41:00 +01:00
int bottom = top + styleProvider.getTrackHeight();
if (top < -origin.y) {
2020-11-28 19:41:00 +01:00
setOrigin(origin.x, -(top-styleProvider.getTrackHeight()));
} 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);
}
}
2015-07-10 13:40:50 +02:00
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;
}
2020-03-13 21:06:53 +01:00
long getMinVisibleTime() {
return origin.x * scaleFactor;
}
2020-11-28 19:41:00 +01:00
public void setStyleProvider(IWaveformStyleProvider styleProvider) {
this.styleProvider=styleProvider;
redraw();
}
2015-01-20 18:50:15 +01:00
}