/* SignalPlot.java created 2007-09-21 * */ package org.signalml.app.view.signal; import static java.lang.String.format; import static org.signalml.app.util.i18n.SvarogI18n._; import static org.signalml.app.util.i18n.SvarogI18n._R; import java.awt.AlphaComposite; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Component; import java.awt.Dimension; import java.awt.Font; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Point; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.geom.GeneralPath; import java.awt.geom.Point2D; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.InvalidClassException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.SortedSet; import javax.swing.DefaultBoundedRangeModel; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JPopupMenu; import javax.swing.JViewport; import javax.swing.Scrollable; import javax.swing.SwingConstants; import javax.swing.SwingUtilities; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import org.apache.log4j.Logger; import org.signalml.app.config.ApplicationConfiguration; import org.signalml.app.document.TagDocument; import org.signalml.app.document.signal.SignalDocument; import org.signalml.app.model.components.ChannelPlotOptionsModel; import org.signalml.app.model.components.ChannelsPlotOptionsModel; import org.signalml.app.view.common.dialogs.errors.Dialogs; import org.signalml.app.view.tag.TagAttributesRenderer; import org.signalml.app.view.tag.TagPaintMode; import org.signalml.app.view.tag.TagRenderer; import org.signalml.app.view.tag.comparison.TagDifferenceRenderer; import org.signalml.domain.montage.Montage; import org.signalml.domain.montage.MontageMismatchException; import org.signalml.domain.montage.SourceChannel; import org.signalml.domain.montage.system.ChannelFunction; import org.signalml.domain.signal.SignalProcessingChain; import org.signalml.domain.signal.raw.RawSignalSampleSource; import org.signalml.domain.signal.samplesource.ChangeableMultichannelSampleSource; import org.signalml.domain.signal.samplesource.MultichannelSampleSource; import org.signalml.domain.signal.samplesource.OriginalMultichannelSampleSource; import org.signalml.domain.tag.StyledTagSet; import org.signalml.domain.tag.TagDifference; import org.signalml.domain.tag.TagDifferenceSet; import org.signalml.exception.SanityCheckException; import org.signalml.plugin.export.SignalMLException; import org.signalml.plugin.export.signal.ExportedSignalSelection; import org.signalml.plugin.export.signal.ExportedTagDocument; import org.signalml.plugin.export.signal.ExportedTagStyle; import org.signalml.plugin.export.signal.SignalSelection; import org.signalml.plugin.export.signal.SignalSelectionType; import org.signalml.plugin.export.signal.Tag; import org.signalml.plugin.export.signal.TagStyle; import org.signalml.plugin.export.signal.tagStyle.TagAttributeValue; import org.signalml.plugin.export.signal.tagStyle.TagAttributes; import org.signalml.plugin.export.view.ExportedSignalPlot; import org.signalml.util.Util; /** SignalPlot * * * @author Michal Dobaczewski © 2007-2008 CC Otwarte Systemy Komputerowe Sp. z o.o. * based on code Copyright (C) 2003 Dobieslaw Ircha <dircha@eranet.pl> Artur Biesiadowski <abies@adres.pl> Piotr J. Durka <Piotr-J.Durka@fuw.edu.pl> */ public class SignalPlot extends JComponent implements PropertyChangeListener, ChangeListener, Scrollable, ExportedSignalPlot { private static final long serialVersionUID = 1L; protected static final Logger logger = Logger.getLogger(SignalPlot.class); private static final Dimension MINIMUM_SIZE = new Dimension(0,0); private SignalProcessingChain signalChain; private SignalDocument document; private Montage localMontage; private TagRenderer tagRenderer; /** * Renderer capable of rendering the list of visible tag attributes on * a tag. */ private TagAttributesRenderer tagAttributesRenderer; private TagDifferenceRenderer tagDifferenceRenderer; private float samplingFrequency; private double voltageZoomFactor; private double voltageZoomFactorRatio; private double timeZoomFactor; // equiv to "samplesPerPixel" private boolean antialiased; private boolean clamped; private boolean offscreenChannelsDrawn; private boolean tagToolTipsVisible; private boolean optimizeSignalDisplaying; private boolean pageLinesVisible; private boolean blockLinesVisible; private boolean channelLinesVisible; private double pixelPerSecond; private double pixelPerBlock; private double pixelPerPage; private int pixelPerChannel; private double pixelPerValue; private int[] sampleCount; private int maxSampleCount; private int channelCount; private double[] samples; private int[] channelLevel; private int clampLimit; private int pageCount; private int wholePageCount; private float pageSize; private int blockCount; private float blockSize; private float maxTime; private int blocksPerPage; private final long firstSampleTimestamp; private GeneralPath generalPath = new GeneralPath(GeneralPath.WIND_EVEN_ODD,50000); private SignalPlotColumnHeader signalPlotColumnHeader = null; private SignalPlotRowHeader signalPlotRowHeader = null; private SignalPlotCorner signalPlotCorner = null; private JLabel signalPlotTitleLabel = null; private JLabel signalPlotSynchronizationLabel = null; private DefaultBoundedRangeModel timeScaleRangeModel; private DefaultBoundedRangeModel valueScaleRangeModel; private DefaultBoundedRangeModel channelHeightRangeModel; private ChannelsPlotOptionsModel channelsPlotOptionsModel; // the plot must be aware of its own viewport to draw fixed-position elements properly private JViewport viewport; private SignalPlotPopupProvider popupMenuProvider; private SignalView view; private SignalPlot masterPlot; private boolean horizontalLock; private boolean verticalLock; private float horizontalTimeLead; private float verticalValueLead; private int horizontalPixelLead; private int verticalPixelLead; private boolean compensationEnabled = true; private boolean ignoreSliderEvents = false; private ArrayList<PositionedTag> tempTagList; private int tempTagCnt; private boolean tempComparing; private TagDocument[] tempComparedTags; private Point tempViewportLocation; private Dimension tempViewportSize; private Dimension tempPlotSize; private ArrayList<SortedSet<Tag>> tempTagsToDrawList = new ArrayList<SortedSet<Tag>>(); private TagPaintMode tagPaintMode; private SignalColor signalColor; private boolean signalXOR; private Rectangle tempBounds = new Rectangle(); /* ***************** ***************** ***************** */ /* ***************** ***************** ***************** */ /* ***************** ***************** ***************** */ /* ***************** ***************** ***************** */ /* ***************** ***************** ***************** */ /* Initialization & setup */ public SignalPlot(SignalDocument document, SignalView view, SignalPlot masterPlot) throws SignalMLException { super(); this.document = document; this.view = view; this.masterPlot = masterPlot; setBackground(Color.WHITE); setFocusable(true); if (document.getSampleSource() instanceof RawSignalSampleSource) { double timestamp = ((RawSignalSampleSource) document.getSampleSource()).getFirstSampleTimestamp(); firstSampleTimestamp = Math.round(timestamp); } else { firstSampleTimestamp = 0; } signalChain = SignalProcessingChain.createFilteredChain(document.getSampleSource()); Montage montage = document.getMontage(); if (montage != null) signalChain.applyMontageDefinition(montage); signalChain.addPropertyChangeListener(this); document.addPropertyChangeListener(this); ApplicationConfiguration config = view.getApplicationConfig(); if (masterPlot == null) { timeScaleRangeModel = new DefaultBoundedRangeModel(); valueScaleRangeModel = new DefaultBoundedRangeModel(); channelHeightRangeModel = new DefaultBoundedRangeModel(); pixelPerChannel = 80; voltageZoomFactor = 0.95; timeZoomFactor = 0.5; antialiased = config.isAntialiased(); clamped = config.isClamped(); offscreenChannelsDrawn = config.isOffscreenChannelsDrawn(); optimizeSignalDisplaying = config.isOptimizeSignalDisplay(); pageLinesVisible = config.isPageLinesVisible(); blockLinesVisible = config.isBlockLinesVisible(); channelLinesVisible = config.isChannelLinesVisible(); tagPaintMode = config.getTagPaintMode(); signalColor = config.getSignalColor(); signalXOR = config.isSignalXOR(); signalPlotCorner = new MasterSignalPlotCorner(this); } else { timeScaleRangeModel = masterPlot.getTimeScaleRangeModel(); valueScaleRangeModel = masterPlot.getValueScaleRangeModel(); channelHeightRangeModel = masterPlot.getChannelHeightRangeModel(); pixelPerChannel = masterPlot.getPixelPerChannel(); voltageZoomFactor = masterPlot.getVoltageZoomFactor(); timeZoomFactor = masterPlot.getTimeZoomFactor(); antialiased = masterPlot.isAntialiased(); clamped = masterPlot.isClamped(); offscreenChannelsDrawn = masterPlot.isOffscreenChannelsDrawn(); optimizeSignalDisplaying = masterPlot.isOptimizeSignalDisplaying(); pageLinesVisible = masterPlot.isPageLinesVisible(); blockLinesVisible = masterPlot.isBlockLinesVisible(); channelLinesVisible = masterPlot.isChannelLinesVisible(); tagPaintMode = masterPlot.getTagPaintMode(); signalColor = masterPlot.getSignalColor(); signalXOR = masterPlot.isSignalXOR(); SlaveSignalPlotCorner slaveSignalPlotCorner = new SlaveSignalPlotCorner(this); slaveSignalPlotCorner.setSlavePlotSettingsPopupDialog(view.getSlavePlotSettingsPopupDialog()); signalPlotCorner = slaveSignalPlotCorner; } signalPlotColumnHeader = new SignalPlotColumnHeader(this); signalPlotRowHeader = new SignalPlotRowHeader(this); channelsPlotOptionsModel = new ChannelsPlotOptionsModel(this); signalPlotRowHeader.setChannelOptionsPopupDialog(view.getChannelOptionsPopupDialog()); if (masterPlot == null) { setTagToolTipsVisible(config.isTagToolTipsVisible()); setOptimizeSignalDisplaying(config.isOptimizeSignalDisplay()); } else { setTagToolTipsVisible(masterPlot.isTagToolTipsVisible()); setOptimizeSignalDisplaying(masterPlot.isOptimizeSignalDisplaying()); } addMouseListener(new MouseAdapter() { @Override public void mousePressed(MouseEvent e) { if (SignalPlot.this.view.getApplicationConfig().isRightClickPagesForward() && SwingUtilities.isRightMouseButton(e)) { if (e.isShiftDown()) { maybeShowPopupMenu(e); } else { pageForward(); } } else { maybeShowPopupMenu(e); } } @Override public void mouseReleased(MouseEvent e) { if (!SignalPlot.this.view.getApplicationConfig().isRightClickPagesForward() || e.isShiftDown()) { maybeShowPopupMenu(e); } } private void maybeShowPopupMenu(MouseEvent e) { if (e.isPopupTrigger()) { JPopupMenu popupMenu = getPlotPopupMenu(); if (popupMenu != null) { popupMenu.show(e.getComponent(),e.getX(),e.getY()); } } } }); } private double condMaxValue(double mv) { double result = Math.min(2000.0, mv); if (Math.abs(result) < 0.000001) result = 2000.0; return result; } /** * Returns source channel for given montage channel. * @param index index of montage channel. * @return SourceChannel for given montage channel. */ public SourceChannel getSourceChannelFor(int index) { return document.getMontage().getSourceChannelForMontageChannel(index); } public void initialize() throws SignalMLException { calculateParameters(); if (masterPlot == null) { calculateVoltageZoomFactorRatio(); ApplicationConfiguration config = view.getApplicationConfig(); // update models timeScaleRangeModel.setRangeProperties((int)(timeZoomFactor*1000), 0, (int)(config.getMinTimeScale()*1000), (int)(config.getMaxTimeScale()*1000), false); valueScaleRangeModel.setRangeProperties(100, 0, config.getMinValueScale(), config.getMaxValueScale(), false); channelHeightRangeModel.setRangeProperties(pixelPerChannel, 0, config.getMinChannelHeight(), config.getMaxChannelHeight(), false); timeScaleRangeModel.addChangeListener(this); valueScaleRangeModel.addChangeListener(this); valueScaleRangeModel.addChangeListener(this.channelsPlotOptionsModel); channelHeightRangeModel.addChangeListener(this); } else { samples = new double[1024]; masterPlot.addPropertyChangeListener(this); } this.channelsPlotOptionsModel.reset(channelCount); calculateParameters(); } /** * Calculates and returns ZoomFactorRatio for given 'index' channel. * If 'index' == -1 then returns 'global' ZoomFactorRatio defined by * EEG channel's type maxValue (if exists) or MAX from all channels` types * maxValues. * @param index an index of a channel for which calculations will be made * @return voltage zoom ratio for given channel (or globally for all channels) */ public double getVoltageZoomFactorRatioFor(int index) { double v; if (index == -1) v = ChannelFunction.EEG.getMaxValue(); //global voltage scale is for EEG by default else v = this.getSourceChannelFor(index).getFunction().getMaxValue(); return ((1.0 / (condMaxValue(v) * 2)) * 0.95) / 100; } /** * Recalculates the voltageZoomFactorRatio according to the maximum * value assumed from the signal. */ protected void calculateVoltageZoomFactorRatio() { voltageZoomFactorRatio = this.getVoltageZoomFactorRatioFor(-1); voltageZoomFactor = voltageZoomFactorRatio * 100; } private void calculateParameters() { if (document == null) { return; } samplingFrequency = signalChain.getSamplingFrequency(); pageSize = document.getPageSize(); blockSize = document.getBlockSize(); blocksPerPage = document.getBlocksPerPage(); pixelPerSecond = samplingFrequency * timeZoomFactor; pixelPerPage = pixelPerSecond * pageSize; pixelPerBlock = pixelPerPage / blocksPerPage; channelCount = signalChain.getChannelCount(); if (this.channelsPlotOptionsModel.getChannelCount() != channelCount) this.channelsPlotOptionsModel.reset(channelCount); sampleCount = new int[channelCount]; int i, j, k; maxSampleCount = 0; for (i=0; i<channelCount; i++) { sampleCount[i] = signalChain.getSampleCount(i); if (maxSampleCount < sampleCount[i]) { maxSampleCount = sampleCount[i]; } } maxTime = maxSampleCount / samplingFrequency; pageCount = (int) Math.ceil(maxTime / pageSize); blockCount = (int) Math.ceil(maxTime / blockSize); if (pageCount != ((int) Math.floor(maxTime / pageSize))) { wholePageCount = pageCount-1; } else { wholePageCount = pageCount; } pixelPerValue = pixelPerChannel * voltageZoomFactor; clampLimit = (pixelPerChannel / 2) - 2; channelLevel = new int[channelCount]; j = 0; int prevVisibleLevel = 0, prevVisibleIndex = -1, invisibleCount = 0; for (i=0; i<channelCount; i++) { ChannelPlotOptionsModel channelModel = this.channelsPlotOptionsModel.getModelAt(i); //recalculate channel levels if (!channelModel.getVisible()) { invisibleCount ++; } else { //determine positions of last invisibleCount channels if (invisibleCount > 0) { if (prevVisibleIndex == -1) for (k=1; k<=invisibleCount; k++) channelLevel[prevVisibleIndex+k] = prevVisibleLevel + k*((pixelPerChannel/2) / (invisibleCount+1)); else for (k=1; k<=invisibleCount; k++) channelLevel[prevVisibleIndex+k] = prevVisibleLevel + k*(pixelPerChannel / (invisibleCount+1)); invisibleCount = 0; } //determine position of the current i-th channel channelLevel[i] = j * pixelPerChannel + pixelPerChannel / 2; j++; prevVisibleLevel = channelLevel[i]; prevVisibleIndex = i; } } for (k=1; k<=invisibleCount; k++) channelLevel[prevVisibleIndex+k] = prevVisibleLevel + k*((pixelPerChannel/2) / (invisibleCount+1)); if (signalPlotColumnHeader != null) { signalPlotColumnHeader.reset(); } if (signalPlotRowHeader != null) { signalPlotRowHeader.reset(); } } public void destroy() { setVisible(false); document.removePropertyChangeListener(this); document = null; signalChain.removePropertyChangeListener(this); signalChain.destroy(); signalChain = null; view = null; signalPlotColumnHeader = null; signalPlotRowHeader = null; signalPlotCorner = null; viewport = null; timeScaleRangeModel = null; valueScaleRangeModel = null; channelHeightRangeModel = null; if (masterPlot != null) { masterPlot.removePropertyChangeListener(this); masterPlot = null; } } /* ***************** ***************** ***************** */ /* ***************** ***************** ***************** */ /* ***************** ***************** ***************** */ /* ***************** ***************** ***************** */ /* ***************** ***************** ***************** */ /* JComponent & Scrollable implementations */ private void prepareToPaintTags() { List<TagDocument> tagDocuments = document.getTagDocuments(); tempTagCnt = tagDocuments.size(); if (tempTagCnt == 0) { return; } if (tagRenderer == null) { tagRenderer = new TagRenderer(); } if (tagAttributesRenderer == null) { tagAttributesRenderer = new TagAttributesRenderer(); } tempViewportLocation = viewport.getViewPosition(); tempViewportSize = viewport.getExtentSize(); tempPlotSize = getSize(); tempComparing = view.isComparingTags(); tempComparedTags = view.getComparedTags(); if (tempComparing && tagDifferenceRenderer == null) { tagDifferenceRenderer = new TagDifferenceRenderer(); } } private void useTagPaintMode(Graphics2D g) { switch (tagPaintMode) { case XOR : g.setXORMode(Color.WHITE); break; case ALPHA_50 : g.setComposite(AlphaComposite.SrcOver.derive(0.5F)); break; case ALPHA_80 : g.setComposite(AlphaComposite.SrcOver.derive(0.8F)); break; case OVERLAY : default : g.setComposite(AlphaComposite.SrcOver); break; } } // note - this relies on class-local variables (optimization), see prepareToPaintTags private void paintTagOrTagSelection(Graphics2D g, Tag tag, int tagNumber, boolean active, boolean selected, boolean selectionOnly) { SignalSelectionType type = tag.getType(); if (type == SignalSelectionType.PAGE) { return; } else if (tag.getChannel() != -1 && !isChannelVisible(tag.getChannel())) { return; } Component rendererComponent; Component attributesRendererComponent; if (selectionOnly) { rendererComponent = tagRenderer.getTagSelectionRendererComponent(); } else { rendererComponent = tagRenderer.getTagRendererComponent(tag.getStyle(), active, selected); } attributesRendererComponent = tagAttributesRenderer.getTagAttributesRendererComponent(tag); if (type == SignalSelectionType.BLOCK) { Rectangle tagBounds = getPixelBlockTagBounds(tag, tag.isMarker(), tempTagCnt, tagNumber, tempViewportLocation, tempViewportSize, tempPlotSize, tempComparing, tempBounds); rendererComponent.setBounds(tagBounds); rendererComponent.paint(g.create(tagBounds.x, tagBounds.y, tagBounds.width, tagBounds.height)); attributesRendererComponent.setBounds(tagBounds); attributesRendererComponent.paint(g.create(tagBounds.x, tagBounds.y, 400, tagBounds.height)); } else if (type == SignalSelectionType.CHANNEL) { Rectangle[] tagBoundsArr = getPixelChannelTagBounds(tag, tag.isMarker(), tempTagCnt, tagNumber, tempComparing); for (int i=0; i<tagBoundsArr.length; i++) { if (tagBoundsArr[i].intersects(g.getClipBounds())) { rendererComponent.setBounds(tagBoundsArr[i]); rendererComponent.paint(g.create(tagBoundsArr[i].x, tagBoundsArr[i].y, tagBoundsArr[i].width, tagBoundsArr[i].height)); attributesRendererComponent.setBounds(tagBoundsArr[i]); attributesRendererComponent.paint(g.create(tagBoundsArr[i].x, tagBoundsArr[i].y, 400, tagBoundsArr[i].height)); } } } else { throw new SanityCheckException("Bad tag type"); } } // note - this relies on class-local variables (optimization), see prepareToPaintTags private void paintTagDifference(Graphics2D g, TagDifference tagDifference) { if (!tempComparing) { return; } SignalSelectionType type = tagDifference.getType(); if (type == SignalSelectionType.PAGE) { return; } Component rendererComponent = tagDifferenceRenderer.getTagDifferenceRendererComponent(tagDifference.getDifferenceType()); if (type == SignalSelectionType.BLOCK) { Rectangle tagBounds = getPixelBlockTagBounds(tagDifference, false, tempTagCnt, 2, tempViewportLocation, tempViewportSize, tempPlotSize, true, tempBounds); rendererComponent.setBounds(tagBounds); rendererComponent.paint(g.create(tagBounds.x, tagBounds.y, tagBounds.width, tagBounds.height)); } else if (type == SignalSelectionType.CHANNEL) { Rectangle[] tagBoundsArr = getPixelChannelTagBounds(tagDifference, false, tempTagCnt, 2, true); for (int i=0; i<tagBoundsArr.length; i++) { if (tagBoundsArr[i].intersects(g.getClipBounds())) { rendererComponent.setBounds(tagBoundsArr[i]); rendererComponent.paint(g.create(tagBoundsArr[i].x, tagBoundsArr[i].y, tagBoundsArr[i].width, tagBoundsArr[i].height)); } } } else { throw new SanityCheckException("Bad tag difference type"); } } // note - this relies on class-local variables (optimization), see prepareToPaintTags private void paintBlockAndChannelTags(Graphics2D g, PositionedTag tagSelection) { // note - this doesn't paint the selected tag, see paintSelectedBlockOrChannelTag List<TagDocument> tagDocuments = document.getTagDocuments(); StyledTagSet tagSet; SortedSet<Tag> tagsToDraw; Tag highlightedTag = (tagSelection != null ? tagSelection.tag : null); Rectangle clip = g.getClipBounds(); float start = (float)(clip.x / pixelPerSecond); float end = (float)((clip.x+clip.width) / pixelPerSecond); boolean active; boolean showActivity = (tempTagCnt > 1); useTagPaintMode(g); tempTagsToDrawList.clear(); // draw block tags first int cnt = 0; for (TagDocument tagDocument : tagDocuments) { if (tempComparing && tagDocument != tempComparedTags[0] && tagDocument != tempComparedTags[1]) { // in comparing mode paint only the compared tags continue; } active = showActivity && (tagDocument == document.getActiveTag()); tagSet = tagDocument.getTagSet(); tagsToDraw = tagSet.getTagsBetween(start, end); tempTagsToDrawList.add(tagsToDraw); for (Tag tag : tagsToDraw) { if (tag == highlightedTag) { continue; } if (tag.getType() == SignalSelectionType.BLOCK) { paintTagOrTagSelection(g, tag, cnt, active, false, false); } } cnt++; } // draw channel tags second cnt = 0; for (TagDocument tagDocument : tagDocuments) { if (tempComparing && tagDocument != tempComparedTags[0] && tagDocument != tempComparedTags[1]) { // in comparing mode paint only the compared tags continue; } active = showActivity && (tagDocument == document.getActiveTag()); tagsToDraw = tempTagsToDrawList.get(cnt); for (Tag tag : tagsToDraw) { if (tag == highlightedTag) { continue; } if (tag.getType() == SignalSelectionType.CHANNEL) { paintTagOrTagSelection(g, tag, cnt, active, false, false); } } cnt++; } // page tags are drawn in the column header g.setComposite(AlphaComposite.SrcOver); // differences go here if (tempComparing) { TagDifferenceSet differenceSet = view.getDifferenceSet(); if (differenceSet != null) { SortedSet<TagDifference> differencesToDraw = differenceSet.getDifferencesBetween(start,end); for (TagDifference difference : differencesToDraw) { paintTagDifference(g, difference); } } } } // note - this relies on class-local variables (optimization), see prepareToPaintTags private void paintSelectedBlockOrChannelTag(Graphics2D g, PositionedTag tagSelection, boolean selectionOnly) { if (tagSelection == null) { return; } TagDocument tagDocument = document.getTagDocuments().get(tagSelection.tagPositionIndex); if (tempComparing && tagDocument != tempComparedTags[0] && tagDocument != tempComparedTags[1]) { // in comparing mode paint only the compared tags return; } SignalSelectionType type = tagSelection.tag.getType(); if (type == SignalSelectionType.BLOCK || type == SignalSelectionType.CHANNEL) { boolean active = (tempTagCnt > 1) && (document.getTagDocuments().get(tagSelection.tagPositionIndex) == document.getActiveTag()); useTagPaintMode(g); paintTagOrTagSelection(g, tagSelection.tag, tagSelection.tagPositionIndex, active, false, selectionOnly); g.setComposite(AlphaComposite.SrcOver); } } /* * For given top x-value (in pixels) computes start channel index. * @param topBoundary top x-value (in pixels) of the plot */ public int computePaintStartChannel(int topBoundary) { int startChannel = 0; int prevChannelsPix = 0; while (prevChannelsPix <= topBoundary && startChannel<channelCount) { if (this.getChannelsPlotOptionsModel().getModelAt(startChannel).getVisible()) prevChannelsPix += pixelPerChannel; startChannel++; } startChannel--; return startChannel; } @Override protected void paintComponent(Graphics gOrig) { int i; Graphics2D g = (Graphics2D)gOrig; Rectangle clip = g.getClipBounds(); g.setColor(getBackground()); g.fillRect(clip.x,clip.y,clip.width,clip.height); int clipEndX = clip.x + clip.width - 1; int clipEndY = clip.y + clip.height - 1; prepareToPaintTags(); PositionedTag tagSelection = view.getTagSelection(this); if (tempTagCnt > 0) { paintBlockAndChannelTags(g, tagSelection); } if (blockLinesVisible && pixelPerBlock > 4) { // this draws block boundaries int startBlock = (int) Math.floor(clip.x / pixelPerBlock); if (startBlock == 0) { startBlock++; } int endBlock = (int) Math.ceil(clipEndX / pixelPerBlock); g.setColor(Color.GRAY); for (i=startBlock; i <= endBlock; i++) { g.drawLine((int)(i * pixelPerBlock), clip.y, (int)(i * pixelPerBlock), clipEndY); } } if (pageLinesVisible && pixelPerPage > 4) { // this draws page boundaries int startPage = (int) Math.floor(clip.x / pixelPerPage); if (startPage == 0) { startPage++; } int endPage = (int) Math.ceil(clipEndX / pixelPerPage); g.setColor(Color.RED); for (i=startPage; i <= endPage; i++) { g.drawLine((int)(i * pixelPerPage), clip.y, (int)(i * pixelPerPage), clipEndY); } } int channel, visibleCount; int startChannel = this.computePaintStartChannel(clip.y); int maxNumberOfChannels = (int) Math.min(channelCount, Math.ceil(((double)(clip.height - 1)) / pixelPerChannel)); if (channelLinesVisible && pixelPerChannel > 10) { g.setColor(Color.BLUE); visibleCount = 0; channel=startChannel; while (visibleCount < maxNumberOfChannels && channel<channelCount) { if (isChannelVisible(channel)) { visibleCount ++; g.drawLine(clip.x, channelLevel[channel], clipEndX, channelLevel[channel]); } channel++; } } // draw the highlighted tag as is if (tempTagCnt > 0 && tagSelection != null) { paintSelectedBlockOrChannelTag(g, tagSelection, false); } if (!clamped) { if (offscreenChannelsDrawn) { // draw all startChannel = 0; maxNumberOfChannels = channelCount; } else { // determine on screen channels // NOTE: not the channels within the clip, the channels within the viewport Point viewportPoint = viewport.getViewPosition(); Dimension viewportSize = viewport.getExtentSize(); startChannel = this.computePaintStartChannel(viewportPoint.y); maxNumberOfChannels = (int) Math.min(channelCount, Math.ceil(((double)(viewportSize.height-1)) / pixelPerChannel)); } } if (antialiased) { g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON); } g.setColor(signalColor.getColor()); if (signalXOR) { g.setXORMode(Color.WHITE); } else { g.setComposite(AlphaComposite.SrcOver); } int firstSample, lastSample, length; double realX, x, y; double lastX = 0; double lastY = 0; visibleCount = 0; channel=startChannel; while (visibleCount < maxNumberOfChannels && channel<channelCount) { if (!isChannelVisible(channel)) { channel++; continue; } visibleCount ++; // those must be offset by one to get correct partial redraw // offset again by one, this time in terms of samples firstSample = (int) Math.max(0, Math.floor((clip.x-1) / timeZoomFactor) - 1); lastSample = (int) Math.min(sampleCount[channel] - 1, Math.ceil((clipEndX+1) / timeZoomFactor) + 1); if (lastSample < firstSample) { continue; } length = 1 + lastSample - firstSample; if (samples == null || samples.length < length) { samples = new double[length]; } try { signalChain.getSamples(channel, samples, firstSample, length, 0); } catch (RuntimeException ex) { logger.error(format("failed to read %d samples starting at %d, till %d, channel %d", length, firstSample, lastSample, sampleCount[channel])); setVisible(false); throw ex; } realX = firstSample * timeZoomFactor; double pixelPerValueForChannel= channelsPlotOptionsModel.getPixelsPerValue(channel); y = samples[0] * pixelPerValueForChannel; if (clamped) { if (y > clampLimit) { y = channelLevel[channel] - clampLimit; } else if (y < -clampLimit) { y = channelLevel[channel] + clampLimit; } else { y = channelLevel[channel] - y; } } else { y = channelLevel[channel] - y; } generalPath.reset(); if (!antialiased) { x = StrictMath.floor(realX + 0.5); y = StrictMath.floor(y + 0.5); generalPath.moveTo(x, y); lastX = x; lastY = y; } else { generalPath.moveTo(realX, y); } int sampleSkip = 1; if (optimizeSignalDisplaying) { //optimize signal display displays at most two sample for each pixel. sampleSkip = (int) (1/timeZoomFactor); sampleSkip /= 2; if (sampleSkip < 1) sampleSkip = 1; } //if the user selects a piece of the signal, we shouldn't break the rule //that we always take the sampleSkip's sample! int startingFrom = sampleSkip - firstSample % sampleSkip; if (firstSample % sampleSkip == 0) startingFrom = 0; if (optimizeSignalDisplaying) { //in each step we want to display the same samples even though few new were added OriginalMultichannelSampleSource source = signalChain.getSource(); if (source instanceof ChangeableMultichannelSampleSource) { ChangeableMultichannelSampleSource changeableSource = (ChangeableMultichannelSampleSource) source; long addedSamples = changeableSource.getAddedSamplesCount(); int changeableCorrection = 0; if (addedSamples % sampleSkip != 0) changeableCorrection = (int) (sampleSkip - addedSamples % sampleSkip); startingFrom += changeableCorrection; if (startingFrom >= sampleSkip) startingFrom -= sampleSkip; } } for (i=startingFrom; i<length; i += sampleSkip) { y = samples[i] * pixelPerValueForChannel; if (clamped) { if (y > clampLimit) { y = channelLevel[channel] - clampLimit; } else if (y < -clampLimit) { y = channelLevel[channel] + clampLimit; } else { y = channelLevel[channel] - y; } } else { y = channelLevel[channel] - y; } realX = ((firstSample+i) * timeZoomFactor); if (antialiased) { generalPath.lineTo(realX, y); } else { // if not antialiased then round to integer in order to prevent aliasing affects // (which cause slave plots to display the signal slightly differently) // expand Math.round for performance, StrictMath.floor is native x = StrictMath.floor(realX + 0.5); y = StrictMath.floor(y + 0.5); if (x != lastX || y != lastY) { generalPath.lineTo(x, y); } lastX = x; lastY = y; } } g.draw(generalPath); channel++; } if (signalXOR) { g.setComposite(AlphaComposite.SrcOver); } // finally draw the highlighted tags selection outline if (tempTagCnt > 0 && tagSelection != null) { paintSelectedBlockOrChannelTag(g, tagSelection, true); } SignalSelection signalSelection = view.getSignalSelection(this); if (signalSelection != null) { g.setColor(Color.BLUE); g.setStroke(new BasicStroke(3.0F,BasicStroke.CAP_BUTT,BasicStroke.JOIN_MITER, 10F, new float[] {5,5}, 0F)); Rectangle r = getPixelSelectionBounds(signalSelection, tempBounds); r = r.intersection(new Rectangle(new Point(0,0), getSize())); g.drawRect(r.x+1,r.y+1,r.width-2,r.height-2); // draw the selection completely _inside_ the selected area (pen width of 3 must be compenstated) } } public long getFirstSampleTimestamp() { return this.view.isDisplayClockTime() ? firstSampleTimestamp : 0; } @Override public Dimension getPreferredSize() { return new Dimension((int)(maxSampleCount*timeZoomFactor),this.channelsPlotOptionsModel.getVisibleChannelsCount()*pixelPerChannel); } @Override public Dimension getMinimumSize() { if (masterPlot == null) { return new Dimension((int)(maxSampleCount*timeZoomFactor),pixelPerChannel); } else { return MINIMUM_SIZE; } } @Override public Dimension getMaximumSize() { return getPreferredSize(); } public JPopupMenu getPlotPopupMenu() { if (view.isToolEngaged()) { return null; } if (popupMenuProvider == null) { return null; } return popupMenuProvider.getPlotPopupMenu(); } @Override public boolean isDoubleBuffered() { return true; } @Override public Dimension getPreferredScrollableViewportSize() { return getPreferredSize(); } @Override public boolean getScrollableTracksViewportHeight() { return false; } @Override public boolean getScrollableTracksViewportWidth() { return false; } @Override public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) { switch (orientation) { case SwingConstants.VERTICAL : return pixelPerChannel; case SwingConstants.HORIZONTAL : default : if (direction > 0) { return getPageForwardSkip(viewport.getViewPosition()); } else { return -getPageBackwardSkip(viewport.getViewPosition()); } } } @Override public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) { switch (orientation) { case SwingConstants.VERTICAL : return pixelPerChannel/8; case SwingConstants.HORIZONTAL : default : if (direction > 0) { return getBlockForwardSkip(viewport.getViewPosition()); } else { return -getBlockBackwardSkip(viewport.getViewPosition()); } } } @Override public int getPageForwardSkip(Point position) { int currentPage = (int) Math.floor(position.x / pixelPerPage); int pageOffset = position.x - ((int)(currentPage*pixelPerPage)); if (pageOffset > pixelPerPage / 2) { // this trick prevent innacuracies caused by rounding currentPage++; pageOffset = position.x - ((int)(currentPage*pixelPerPage)); } currentPage++; return (pageOffset + ((int)(currentPage*pixelPerPage))) - position.x; } @Override public int getPageBackwardSkip(Point position) { int currentPage = (int) Math.floor(position.x / pixelPerPage); int pageOffset = position.x - ((int)(currentPage*pixelPerPage)); if (pageOffset < pixelPerPage / 2) { // this trick prevent innacuracies caused by rounding currentPage--; pageOffset = position.x - ((int)(currentPage*pixelPerPage)); } currentPage--; return (pageOffset + ((int)(currentPage*pixelPerPage))) - position.x; } @Override public int getBlockForwardSkip(Point position) { int currentBlock = (int) Math.floor(position.x / pixelPerBlock); int blockOffset = position.x - ((int)(currentBlock*pixelPerBlock)); if (blockOffset > pixelPerBlock / 2) { // this trick prevent innacuracies caused by rounding currentBlock++; blockOffset = position.x - ((int)(currentBlock*pixelPerBlock)); } currentBlock++; return (blockOffset + ((int)(currentBlock*pixelPerBlock))) - position.x; } @Override public int getBlockBackwardSkip(Point position) { int currentBlock = (int) Math.floor(position.x / pixelPerBlock); int blockOffset = position.x - ((int)(currentBlock*pixelPerBlock)); if (blockOffset < pixelPerBlock / 2) { // this trick prevent innacuracies caused by rounding currentBlock--; blockOffset = position.x - ((int)(currentBlock*pixelPerBlock)); } currentBlock--; return (blockOffset + ((int)(currentBlock*pixelPerBlock))) - position.x; } @Override public void pageForward() { Point position = viewport.getViewPosition(); position.x += getPageForwardSkip(position); position.x = Math.max(0, Math.min(getSize().width - viewport.getExtentSize().width, position.x)); viewport.setViewPosition(position); } @Override public void pageBackward() { Point position = viewport.getViewPosition(); position.x += getPageBackwardSkip(position); position.x = Math.max(0, Math.min(getSize().width - viewport.getExtentSize().width, position.x)); viewport.setViewPosition(position); } public void snapPageToView() { Dimension extent = viewport.getExtentSize(); Point position = viewport.getViewPosition(); int currentPage = (int) Math.floor(position.x / pixelPerPage); if (masterPlot == null) { double timeZoomFactor = ((double) extent.width) / (samplingFrequency*pageSize); setTimeZoomFactor(timeZoomFactor); } // viewport needs to be validated after change, so that getSize returns a valid value viewport.validate(); Dimension size = getSize(); position.x = (int)(currentPage * pixelPerPage); position.x = Math.min(size.width-extent.width, position.x); boolean oldHorizontalLock = horizontalLock; boolean oldVerticalLock = verticalLock; try { // prevent re-synchronization horizontalLock = false; verticalLock = false; viewport.setViewPosition(position); } finally { horizontalLock = oldHorizontalLock; verticalLock = oldVerticalLock; } if (horizontalLock && masterPlot != null) { // reset alignment Point masterPosition = masterPlot.getViewport().getViewPosition(); horizontalTimeLead = toTimeSpace(position) - masterPlot.toTimeSpace(masterPosition); horizontalPixelLead = position.x - masterPosition.x; } } @Override public String getToolTipText(MouseEvent event) { if (!tagToolTipsVisible) { return null; } Point p = event.getPoint(); tempTagList = getTagsAtPoint(p, tempTagList); if (tempTagList.isEmpty()) { return null; } String locationMessage = _R("T: {0}, V:{1} [P: {2}, B: {3}, C: {4}]", toTimeSpace(p), toValueSpace(p), toPageSpace(p), toBlockSpace(p), signalChain.getLabel(toChannelSpace(p))); return getTagListToolTip(locationMessage, tempTagList); } /* ***************** ***************** ***************** */ /* ***************** ***************** ***************** */ /* ***************** ***************** ***************** */ /* ***************** ***************** ***************** */ /* ***************** ***************** ***************** */ /* Listener implementations */ public void reset() { calculateParameters(); revalidateAndRepaintAll(); } public void revalidateAndRepaintAll() { if (signalPlotColumnHeader != null) { signalPlotColumnHeader.revalidate(); signalPlotColumnHeader.repaint(); } if (signalPlotRowHeader != null) { signalPlotRowHeader.revalidate(); signalPlotRowHeader.repaint(); } revalidate(); repaint(); } public void updateScales(double timeZoomFactor, double voltageZoomFactor, int pixelPerChannel, boolean compensate) { Point viewportPoint = null; Dimension viewportSize = null; Point p = null; Dimension plotSize = null; Point2D.Float p2 = null; if (compensate) { viewportPoint = viewport.getViewPosition(); viewportSize = viewport.getExtentSize(); p = new Point(viewportPoint.x + viewportSize.width/2, viewportPoint.y + viewportSize.height/2); p2 = toSignalSpace(p); } if (timeZoomFactor >= 0) { setTimeZoomFactor(timeZoomFactor); } if (voltageZoomFactor >= 0) { setVoltageZoomFactor(voltageZoomFactor); } if (pixelPerChannel >= 0) { setPixelPerChannel(pixelPerChannel); } if (compensate) { // viewport needs to be validated after change, so that getSize returns a valid value viewport.validate(); plotSize = getSize(); Point newP = toPixelSpace(p2); newP.x = newP.x - viewportSize.width/2; newP.y = newP.y - viewportSize.height/2; newP.x = Math.max(0, Math.min(plotSize.width - viewportSize.width, newP.x)); newP.y = Math.max(0, Math.min(plotSize.height - viewportSize.height, newP.y)); viewport.setViewPosition(newP); } } @Override public void propertyChange(PropertyChangeEvent evt) { Object source = evt.getSource(); String name = evt.getPropertyName(); if (source == document) { if (name.equals(SignalDocument.MONTAGE_PROPERTY)) { Montage oldMontage = (Montage) evt.getOldValue(); Montage newMontage = (Montage) evt.getNewValue(); this.channelsPlotOptionsModel.resetOnlyWhatsNecessary(oldMontage, newMontage); this.setLocalMontage(newMontage); reset(); } } else if (masterPlot != null && source == masterPlot) { if (TIME_ZOOM_FACTOR_PROPERTY.equals(name)) { updateScales(masterPlot.getTimeZoomFactor(), -1, -1, compensationEnabled); } else if (VOLTAGE_ZOOM_FACTOR_PROPERTY.equals(name)) { updateScales(-1, masterPlot.getVoltageZoomFactor(), -1, false); } else if (PIXEL_PER_CHANNEL_PROPERTY.equals(name)) { updateScales(-1, -1, masterPlot.getPixelPerChannel(), compensationEnabled); } } else if (OriginalMultichannelSampleSource.CALIBRATION_PROPERTY.equals(name)) { calculateVoltageZoomFactorRatio(); reset(); } } @Override public void stateChanged(ChangeEvent e) { Object source = e.getSource(); if (source == viewport) { PositionedTag pTag = view.getTagSelection(this); if (pTag != null) { if (!isTagOnScreen(pTag)) { view.clearTagSelection(); } } SignalSelection selection = view.getSignalSelection(this); if (selection != null) { if (!isSelectionOnScreen(selection)) { view.clearSignalSelection(); } } } else if (!ignoreSliderEvents) { if (source == timeScaleRangeModel) { double timeZoomFactor = ((double) timeScaleRangeModel.getValue()) / 1000F; updateScales(timeZoomFactor, -1, -1, compensationEnabled); } else if (source == valueScaleRangeModel) { double voltageZoomFactor = (valueScaleRangeModel.getValue()) * voltageZoomFactorRatio; //this.channelsPlotOptionsModel.globalScaleChanged(valueScaleRangeModel.getValue()); updateScales(-1, voltageZoomFactor, -1, false); } else if (source == channelHeightRangeModel) { updateScales(-1, -1, channelHeightRangeModel.getValue(), compensationEnabled); } } } /* ***************** ***************** ***************** */ /* ***************** ***************** ***************** */ /* ***************** ***************** ***************** */ /* ***************** ***************** ***************** */ /* ***************** ***************** ***************** */ /* Conversions */ @Override public Point2D.Float toSignalSpace(Point p) { float time = (float)((p.x) / pixelPerSecond); float value = (float)((p.y) / pixelPerValue); return new Point2D.Float(time,value); } /** * Returns the distance (in the signal space) between two points (in pixels). * @param p1 point 1 (pixels) * @param p2 point 2 (pixels) * @return the distance between two points (signal space) */ public Point2D.Float getDistanceInSignalSpace(Point p1, Point p2) { Point pixelSize = new Point( Math.abs(p1.x - p2.x), Math.abs(p1.y - p2.y) ); int channel = toChannelSpace(p1); double pixelPerValueForChannel= channelsPlotOptionsModel.getPixelsPerValue(channel); float value = (float)((pixelSize.y) / pixelPerValueForChannel); float time = (float)((pixelSize.x) / pixelPerSecond); return new Point2D.Float(time,value); } /** * Distance is controversial when it has different scaling for each point. * @param p1 point 1 * @param p2 point 2 * @return true if for each point we have a different scaling. */ public boolean isDistanceControversial(Point p1, Point p2) { int channel1 = Math.min(toChannelSpace(p1), toChannelSpace(p2)); int channel2 = Math.max(toChannelSpace(p1), toChannelSpace(p2)); double p1PixelPerValueForChannel= channelsPlotOptionsModel.getPixelsPerValue(channel1); for (int channel = channel1; channel <= channel2; channel++) { double p2PixelPerValueForChannel= channelsPlotOptionsModel.getPixelsPerValue(channel); if (p1PixelPerValueForChannel != p2PixelPerValueForChannel) return true; } return false; } @Override public Point toPixelSpace(Point2D.Float p) { int x = (int) Math.round(p.getX() * pixelPerSecond); int y = (int) Math.round(p.getY() * pixelPerValue); return new Point(x,y); } @Override public int toPageSpace(Point p) { return (int) Math.max(0, Math.min(pageCount-1, Math.floor(p.x / pixelPerPage))); } @Override public int toBlockSpace(Point p) { return (int) Math.max(0, Math.min(blockCount-1, Math.floor(p.x / pixelPerBlock))); } @Override public float toTimeSpace(Point p) { return Math.max(0, Math.min(maxTime, (float)((p.x) / pixelPerSecond))); } @Override public int toSampleSpace(Point p) { return Math.max(0, Math.min(maxSampleCount-1, (int)((p.x) / timeZoomFactor))); } @Override public float toValueSpace(Point p) { int channel = toChannelSpace(p); int y = channelLevel[channel] - p.y; return Math.round((y) / pixelPerValue); } @Override public int timeToPixel(double time) { return (int) Math.round((time) * pixelPerSecond); } @Override public int channelToPixel(int channel) { int invisibleChannels = 0; for (int i = 0; i < channel; i++) if (!isChannelVisible(i)) invisibleChannels++; return ((channel-invisibleChannels) * pixelPerChannel); } public int getInvisibleChannelsBeforeChannel(int channel) { int numberOfInvisibleChannels = 0; for (int i = 0; i < channel + numberOfInvisibleChannels && i < getChannelCount(); i++) { if (!isChannelVisible(i)) numberOfInvisibleChannels++; } return numberOfInvisibleChannels; } protected boolean isChannelVisible(int channel) { return this.channelsPlotOptionsModel.getModelAt(channel).getVisible(); } @Override public int toChannelSpace(Point p) { int channel = (int) Math.max(0, Math.min(channelCount-1, Math.floor(p.y / pixelPerChannel))); int numberOfInvisibleChannels = 0; for (int i = 0; i <= channel + numberOfInvisibleChannels && i < getChannelCount(); i++) { if (!isChannelVisible(i)) numberOfInvisibleChannels++; } return channel + numberOfInvisibleChannels; } public Rectangle getPixelSelectionBounds(SignalSelection selection, Rectangle useRect) { double position = selection.getPosition(); double length = selection.getLength(); SignalSelectionType type = selection.getType(); int selLeft = (int) Math.floor(selection.getPosition() * pixelPerSecond); int selRight = (int) Math.ceil(selection.getLength() * pixelPerSecond); int selTop; int selBottom; if (type == SignalSelectionType.PAGE) { selLeft = (int)((position / pageSize) * pixelPerPage); selRight = (int)(((position + length) / pageSize) * pixelPerPage); } else if (type == SignalSelectionType.BLOCK) { selLeft = (int)((position / blockSize) * pixelPerBlock); selRight = (int)(((position + length) / blockSize) * pixelPerBlock); } else { selLeft = (int) Math.round(position * pixelPerSecond); selRight = (int) Math.round((position+length) * pixelPerSecond) - 1; } int selChannel = selection.getChannel(); if (selChannel == SignalSelection.CHANNEL_NULL) { selTop = 0; selBottom = getSize().height - 1; } else { selChannel -= getInvisibleChannelsBeforeChannel(selChannel); selTop = selChannel*pixelPerChannel; selBottom = selTop + pixelPerChannel - 1; } Rectangle rect; if (useRect == null) { rect = new Rectangle(); } else { rect = useRect; } rect.x = selLeft; rect.y = selTop; rect.width = selRight-selLeft; rect.height = selBottom-selTop; return rect; } private boolean isSelectionOnScreen(SignalSelection selection) { return viewport.getViewRect().intersects(getPixelSelectionBounds(selection, tempBounds)); } public Rectangle getPixelBlockTagBounds(SignalSelection tag, boolean marker,int tagCnt, int tagNumber, Point viewportPoint, Dimension viewportSize, Dimension plotSize, boolean comparing, Rectangle useRect) { Rectangle rect = getTagSelectionRectangle(tag, marker, tagCnt, useRect); if (rect.x > 0 && blockLinesVisible && pixelPerBlock > 4) { int linePosition = (int)((int)((tag.getPosition() / blockSize)) * pixelPerBlock); if (linePosition == rect.x) { rect.x++; // block tags are drawn only inside the block } } if (tagCnt > 1) { int height = (plotSize.height <= viewportSize.height ? plotSize.height : viewportSize.height); if (comparing) { // 0 - top, 1 - bottom, 2 - comparison int tagHeight = (height-COMPARISON_STRIP_HEIGHT) / 2; if (tagNumber == 0) { rect.y = viewportPoint.y; rect.height = tagHeight; } else if (tagNumber == 1) { rect.y = viewportPoint.y + COMPARISON_STRIP_HEIGHT + tagHeight; rect.height = tagHeight; } else { rect.y = viewportPoint.y + tagHeight + COMPARISON_STRIP_MARGIN; rect.height = COMPARISON_STRIP_HEIGHT - (2 * COMPARISON_STRIP_MARGIN); } } else { float pixerPerTag = ((float) height) / tagCnt; rect.y = viewportPoint.y + (int)((tagNumber) * pixerPerTag); int endY = viewportPoint.y + (int)((tagNumber+1) * pixerPerTag); rect.height = endY - rect.y; } } else { rect.y = 0; rect.height = plotSize.height; } return rect; } public Rectangle getTagSelectionRectangle(SignalSelection tag, boolean marker, int tagCnt, Rectangle useRect) { Rectangle rect; if (useRect == null) { rect = new Rectangle(); } else { rect = useRect; } if (marker) { int rWidth = pixelPerChannel / (3 * tagCnt); // 1/3 of the height for this tag if (rWidth > 50) { rWidth = 50; } else if (rWidth < 5) { rWidth = 5; } rect.x = (int)(tag.getPosition() * pixelPerSecond) - rWidth/2; rect.width = rWidth; } else { rect.x = (int)(tag.getPosition() * pixelPerSecond); rect.width = (int)(tag.getLength() * pixelPerSecond); } return rect; } public Rectangle getPixelChannelTagBoundsInChannel(SignalSelection tag, boolean marker, int tagCnt, int tagNumber, int channel, boolean comparing, Rectangle useRect) { Rectangle rect = getTagSelectionRectangle(tag, marker, tagCnt, useRect); int invisibleChannels = getInvisibleChannelsBeforeChannel(channel); int channelOffset = (channel-invisibleChannels) * pixelPerChannel; if (comparing) { // 0 - top, 1 - bottom, 2 - comparison int tagHeight = (pixelPerChannel-COMPARISON_STRIP_HEIGHT) / 2; if (tagNumber == 0) { rect.y = channelOffset; rect.height = tagHeight; } else if (tagNumber == 1) { rect.y = channelOffset + COMPARISON_STRIP_HEIGHT + tagHeight; rect.height = tagHeight; } else { rect.y = channelOffset + tagHeight + COMPARISON_STRIP_MARGIN; rect.height = COMPARISON_STRIP_HEIGHT - (2 * COMPARISON_STRIP_MARGIN); } } else { float pixelPerTag = ((float) pixelPerChannel) / tagCnt; rect.y = channelOffset + (int)((tagNumber) * pixelPerTag); if (channelLinesVisible && (tagCnt % 2 == 0) && (tagNumber == (tagCnt/2))) { // avoid obscuring channel line rect.y++; } int endY = channelOffset + (int)((tagNumber+1) * pixelPerTag); rect.height = endY - rect.y; } return rect; } public Rectangle[] getPixelChannelTagBounds(SignalSelection tag, boolean marker, int tagCnt, int tagNumber, boolean comparing) { if (tag.getChannel() == -1) { Rectangle[] rects = new Rectangle[1]; rects[0] = getPixelBlockTagBounds(tag, marker, tagCnt, tagNumber, tempViewportLocation, tempViewportSize, tempPlotSize, tempComparing, tempBounds); return rects; }; int[] channels = signalChain.getDependantChannelIndices(tag.getChannel()); Rectangle[] rects = new Rectangle[channels.length]; for (int i=0; i<channels.length; i++) { rects[i] = getPixelChannelTagBoundsInChannel(tag, marker, tagCnt, tagNumber, channels[i], comparing, null); } return rects; } private boolean isTagOnScreen(PositionedTag pTag) { Rectangle viewRect = viewport.getViewRect(); SignalSelectionType type = pTag.tag.getType(); if (type.isBlock()) { return getPixelBlockTagBounds(pTag.tag, pTag.tag.isMarker(), document.getTagDocuments().size(), pTag.tagPositionIndex, viewRect.getLocation(), viewRect.getSize(), getSize(), view.isComparingTags(), tempBounds).intersects(viewRect); } else if (type.isChannel()) { Rectangle[] bounds = getPixelChannelTagBounds(pTag.tag, pTag.tag.isMarker(), document.getTagDocuments().size(), pTag.tagPositionIndex, view.isComparingTags()); for (int i=0; i<bounds.length; i++) { if (viewRect.intersects(bounds[i])) { return true; } } return false; } else if (type.isPage()) { Rectangle bounds = signalPlotColumnHeader.getPixelPageTagBounds(pTag.tag, document.getTagDocuments().size(), pTag.tagPositionIndex, view.isComparingTags(), tempBounds); // we assume the selected page tag to be always visible in vertical direction, only horizontal is checked bounds.y = viewRect.y; bounds.height = 1; return bounds.intersects(viewRect); } else { throw new SanityCheckException("Bad tag type"); } } /* ***************** ***************** ***************** */ /* ***************** ***************** ***************** */ /* ***************** ***************** ***************** */ /* ***************** ***************** ***************** */ /* ***************** ***************** ***************** */ /* Local montage support */ public Montage getLocalMontage() { return localMontage; } public void setLocalMontage(Montage localMontage) { this.setLocalMontage(localMontage, false); } public void setLocalMontage(Montage localMontage, boolean withoutFilters) { if (this.localMontage != localMontage) { this.localMontage = localMontage; updateSignalPlotTitleLabel(); try { Montage m = null; if (localMontage == null) m = document.getMontage(); else m = localMontage; if (withoutFilters) signalChain.applyMontageDefinitionWithoutfilters(m); else signalChain.applyMontageDefinition(m); } catch (MontageMismatchException ex) { logger.error("Failed to set montage", ex); Dialogs.showExceptionDialog(this, ex); return; } if (view.getSignalSelection(this) != null) { view.clearSignalSelection(); } if (view.getTagSelection(this) != null) { view.clearTagSelection(); } reset(); // clone plots must revalidate to compensate for possible label length chnage // which might have resized the row header for (SignalPlot plot : view.getPlots()) { if (plot != this) { plot.revalidateAndRepaintAll(); } } } } /* ***************** ***************** ***************** */ /* ***************** ***************** ***************** */ /* ***************** ***************** ***************** */ /* ***************** ***************** ***************** */ /* ***************** ***************** ***************** */ /* Selection support */ public void repaintSelectionBounds(SignalSelection signalSelection) { Rectangle selectionBounds = getPixelSelectionBounds(signalSelection, tempBounds); selectionBounds.grow(3,3); repaint(selectionBounds); } @Override public SignalSelection getPageSelection(int fromPage, int toPage) { if (fromPage > toPage) { int temp = toPage; toPage = fromPage; fromPage = temp; } SignalSelection selection = new SignalSelection( SignalSelectionType.PAGE, fromPage * pageSize, ((toPage+1)-fromPage) * pageSize ); return selection; } @Override public SignalSelection getBlockSelection(int fromBlock, int toBlock) { if (fromBlock > toBlock) { int temp = toBlock; toBlock = fromBlock; fromBlock = temp; } SignalSelection selection = new SignalSelection( SignalSelectionType.BLOCK, fromBlock * blockSize, ((toBlock+1)-fromBlock) * blockSize ); return selection; } @Override public SignalSelection getChannelSelection(float fromPosition, float toPosition, int channel) { if (fromPosition > toPosition) { float temp = toPosition; toPosition = fromPosition; fromPosition = temp; } return new SignalSelection(SignalSelectionType.CHANNEL, fromPosition, toPosition-fromPosition, channel); } /** * Transforms a given {@link SignalSelection}to a marker selection type. * The result of the transformation is a one-sample wide signal selection * positioned at the beginning of the original signal selection. * @param selection the selection to be transformed * @return the result of the transformation - a one-sample wide signal * selection position at the beginning of the given signal selection. */ protected SignalSelection transformToMarkerSelection(SignalSelection selection) { double startPosition = selection.getPosition(); int sampleAtPoint = (int)(startPosition * samplingFrequency); float newStartPosition = sampleAtPoint / samplingFrequency; return getChannelSelection(newStartPosition, newStartPosition + 1/samplingFrequency, selection.getChannel()); } /* ***************** ***************** ***************** */ /* ***************** ***************** ***************** */ /* ***************** ***************** ***************** */ /* ***************** ***************** ***************** */ /* ***************** ***************** ***************** */ /* Tagging support */ public void repaintTagBounds(PositionedTag tag, int tagCnt) { SignalSelectionType type = tag.tag.getStyle().getType(); if (type == SignalSelectionType.PAGE) { Rectangle tagBounds; tagBounds = signalPlotColumnHeader.getPixelPageTagBounds(tag.tag, tagCnt, tag.tagPositionIndex, view.isComparingTags(), tempBounds); tagBounds.grow(3,3); signalPlotColumnHeader.repaint(tagBounds); } else if (type == SignalSelectionType.BLOCK) { Rectangle tagBounds; tagBounds = getPixelBlockTagBounds(tag.tag, tag.tag.isMarker(), tagCnt, tag.tagPositionIndex, viewport.getViewPosition(), viewport.getExtentSize(), getSize(), view.isComparingTags(), tempBounds); tagBounds.grow(3,3); repaint(tagBounds); } else if (type == SignalSelectionType.CHANNEL) { Rectangle[] tagBounds; tagBounds = getPixelChannelTagBounds(tag.tag, tag.tag.isMarker(), tagCnt, tag.tagPositionIndex, view.isComparingTags()); for (int i=0; i<tagBounds.length; i++) { tagBounds[i].grow(3,3); repaint(tagBounds[i]); } } } public void tagSelection(TagDocument tagDocument, TagStyle style, SignalSelection selection, boolean selectNew) { SignalSelectionType type = selection.getType(); if (type == SignalSelectionType.PAGE) { tagPageSelection(tagDocument, style, selection, selectNew); } else if (type == SignalSelectionType.BLOCK) { tagBlockSelection(tagDocument, style, selection, selectNew); } else { tagChannelSelection(tagDocument, style, selection, selectNew); } } public void eraseTagsFromSelection(TagDocument tagDocument, SignalSelection selection) { logger.debug("Erasing tags from [" + selection.toString() + "]"); if (selection.getType().isChannel()) { // channel must be converted to source channel SignalSelection corrSelection; int channel = selection.getChannel(); if (channel != SignalSelection.CHANNEL_NULL) channel = signalChain.getDocumentChannelIndex(channel); corrSelection = new SignalSelection(SignalSelectionType.CHANNEL, selection.getPosition(), selection.getLength(), channel); tagDocument.getTagSet().eraseTags(corrSelection); } else { tagDocument.getTagSet().eraseTags(selection); } tagDocument.invalidate(); } public void tagPageSelection(TagDocument tagDocument, TagStyle style, SignalSelection selection, boolean selectNew) { if (!style.getType().isPage()) { throw new SanityCheckException("Not a page style"); } if (!selection.getType().isPage()) { throw new SanityCheckException("Not a page selection"); } Tag tag = null; int startPage = selection.getStartSegment(pageSize); int endPage = selection.getEndSegment(pageSize); for (int page=startPage; page<endPage; page++) { tag = new Tag(style, page*pageSize, pageSize, SignalSelection.CHANNEL_NULL, null); logger.debug("Adding page tag [" + tag.toString() + "]"); tagDocument.getTagSet().replaceSameTypeTags(tag); } tagDocument.invalidate(); if (selectNew && tag != null) { view.setTagSelection(this, new PositionedTag(tag, document.getTagDocuments().indexOf(tagDocument))); } } public void tagBlockSelection(TagDocument tagDocument, TagStyle style, SignalSelection selection, boolean selectNew) { if (!style.getType().isBlock()) { throw new SanityCheckException("Not a block style"); } if (!selection.getType().isBlock()) { throw new SanityCheckException("Not a block selection"); } Tag tag = null; int startBlock = selection.getStartSegment(blockSize); int endBlock = selection.getEndSegment(blockSize); for (int block=startBlock; block<endBlock; block++) { tag = new Tag(style, block*blockSize, blockSize, SignalSelection.CHANNEL_NULL, null); logger.debug("Adding block tag [" + tag.toString() + "]"); tagDocument.getTagSet().replaceSameTypeTags(tag); } tagDocument.invalidate(); if (selectNew && tag != null) { view.setTagSelection(this, new PositionedTag(tag, document.getTagDocuments().indexOf(tagDocument))); } } public void tagChannelSelection(TagDocument tagDocument, TagStyle style, SignalSelection selection, boolean selectNew) { if (!style.getType().isChannel()) { throw new SanityCheckException("Not a channel style"); } if (!selection.getType().isChannel()) { throw new SanityCheckException("Not a channel selection"); } if (style.isMarker()) selection = transformToMarkerSelection(selection); int channel = selection.getChannel(); if (channel != Tag.CHANNEL_NULL) channel = signalChain.getDocumentChannelIndex(channel); Tag tag = new Tag(style,selection.getPosition(), selection.getLength(), channel, null); logger.debug("Adding channel tag [" + tag.toString() + "]"); tagDocument.getTagSet().mergeSameTypeChannelTags(tag); tagDocument.invalidate(); if (selectNew) { view.setTagSelection(this, new PositionedTag(tag, document.getTagDocuments().indexOf(tagDocument))); } } public void selectTagAtPoint(Point point) { tempTagList = getTagsAtPoint(point, tempTagList); if (tempTagList.isEmpty()) { view.clearTagSelection(); return; } Collections.sort(tempTagList); PositionedTag oldSelection = view.getTagSelection(this); if (oldSelection == null) { view.setTagSelection(this,tempTagList.get(0)); } else { int index = -1; int cnt = tempTagList.size(); for (int i=0; i<cnt; i++) { // this must check reference equality if (tempTagList.get(i).getTag() == oldSelection.getTag()) { index = i; break; } } index = (index + 1) % tempTagList.size(); view.setTagSelection(this,tempTagList.get(index)); } } public ArrayList<PositionedTag> getTagsAtPoint(Point point, ArrayList<PositionedTag> list) { List<TagDocument> tagDocuments = document.getTagDocuments(); int tagCnt = tagDocuments.size(); if (list == null) { list = new ArrayList<PositionedTag>(); } else { list.clear(); } if (tagCnt == 0) { return list; } int tagIndex; SortedSet<Tag> tagSet = null; float time = toTimeSpace(point); int viewChannel = toChannelSpace(point); int channel = signalChain.getDocumentChannelIndex(viewChannel); boolean comparing = view.isComparingTags(); TagDocument[] comparedTags = null; if (comparing) { comparedTags = view.getComparedTags(); } int cnt = 0; Rectangle tagBounds; Point viewportPoint = viewport.getViewPosition(); Dimension viewportSize = viewport.getExtentSize(); Dimension plotSize = getSize(); for (TagDocument tagDocument : tagDocuments) { if (comparing && tagDocument != comparedTags[0] && tagDocument != comparedTags[1]) { // in comparing mode scan only the compared tags continue; } tagIndex = tagDocuments.indexOf(tagDocument); // use a 1 s margin to capture any marker tags tagSet = tagDocument.getTagSet().getTagsBetween(time-1, time+1); for (Tag tag : tagSet) { if (!tag.getStyle().isVisible()) continue; if (tag.getStyle().getType() == SignalSelectionType.BLOCK) { if (time >= tag.getPosition() && time < (tag.getPosition() + tag.getLength())) { tagBounds = getPixelBlockTagBounds(tag, tag.isMarker(), tagCnt, cnt, viewportPoint, viewportSize, plotSize, comparing, tempBounds); if (tagBounds.contains(point)) { list.add(new PositionedTag(tag,tagIndex)); } } } else if (tag.getStyle().getType() == SignalSelectionType.CHANNEL) { if (tag.getChannel() == channel) { if (tag.isMarker() || (time >= tag.getPosition() && time < tag.getEndPosition())) { tagBounds = getPixelChannelTagBoundsInChannel(tag, tag.isMarker(), tagCnt, cnt, viewChannel, comparing, tempBounds); if (tagBounds.contains(point)) { list.add(new PositionedTag(tag,tagIndex)); } } } else if (tag.getChannel() == Tag.CHANNEL_NULL) { if (tag.isMarker() || (time >= tag.getPosition() && time < (tag.getPosition() + tag.getLength()))) { tagBounds = getPixelBlockTagBounds(tag, tag.isMarker(), tagCnt, cnt, viewportPoint, viewportSize, plotSize, comparing, tempBounds); if (tagBounds.contains(point)) { list.add(new PositionedTag(tag,tagIndex)); } } } } } cnt++; } return list; } public String getTagMessage(Tag tag) { SignalSelectionType type = tag.getType(); if (type == SignalSelectionType.PAGE || type == SignalSelectionType.BLOCK || (type == SignalSelectionType.CHANNEL && tag.getChannel() == Tag.CHANNEL_NULL)) { return _R("{0} [{1}->{3}]", tag.getStyle().getDescriptionOrName(), tag.getPosition(), tag.getLength(), tag.getPosition()+tag.getLength()); } else { if (tag.isMarker()) { return _R("{0} [{1} in channel {2}]", tag.getStyle().getDescriptionOrName(), tag.getPosition(), signalChain.getPrimaryLabel(tag.getChannel())); } else { return _R("{0} [{1}->{3} in channel {4}]", tag.getStyle().getDescriptionOrName(), tag.getPosition(), tag.getLength(), tag.getPosition()+tag.getLength(), signalChain.getPrimaryLabel(tag.getChannel())); } } } public String getTagToolTip(String title, PositionedTag tag) { if (tempTagList != null) { tempTagList.clear(); } else { tempTagList = new ArrayList<PositionedTag>(); } tempTagList.add(tag); return getTagListToolTip(title, tempTagList); } public String getTagListToolTip(String title, ArrayList<PositionedTag> tags) { StringBuilder buffer = new StringBuilder("<html><head></head><body>"); buffer.append(title).append("<br /> <br />"); String annotation; PositionedTag tagSelection = view.getTagSelection(this); TagAttributes tagAttributes = null; if (tagSelection != null && tags.contains(tagSelection)) { buffer.append("<b>"); buffer.append(getTagMessage(tagSelection.tag)).append("</b><br />"); annotation = tagSelection.tag.getAnnotation(); if (annotation != null && !annotation.isEmpty()) { buffer.append("<div style=\"padding-left: 20px; width: 300px; font-style: italic;\">").append(annotation).append("</div>"); } tagAttributes = tagSelection.tag.getAttributes(); } for (PositionedTag tag : tags) { if (tagSelection != null && tag.tag == tagSelection.tag) { continue; } buffer.append(getTagMessage(tag.tag)).append("<br />"); annotation = tag.tag.getAnnotation(); if (annotation != null && !annotation.isEmpty()) { buffer.append("<div style=\"padding-left: 20px; width: 300px; font-style: italic;\">").append(annotation).append("</div>"); } tagAttributes = tag.tag.getAttributes(); } for (TagAttributeValue attributeValue: tagAttributes.getAttributesList()) { String value = attributeValue.getAttributeValue(); if (value.length() > 15) value = value.substring(0, 15); String code = attributeValue.getAttributeDefinition().getCode(); buffer.append("<div style=\"padding-left: 20px; width: 300px; font-style: italic;\">").append(code).append(": ").append(value).append("</div>"); } buffer.append("</body></html>"); return buffer.toString(); } /* ***************** ***************** ***************** */ /* ***************** ***************** ***************** */ /* ***************** ***************** ***************** */ /* ***************** ***************** ***************** */ /* ***************** ***************** ***************** */ /* Other getters and setters */ public SignalProcessingChain getSignalChain() { return signalChain; } public void setSignalChain(SignalProcessingChain signalChain) { this.signalChain = signalChain; } public OriginalMultichannelSampleSource getSignalSource() { return signalChain.getSource(); } public MultichannelSampleSource getSignalOutput() { return signalChain.getOutput(); } public DefaultBoundedRangeModel getTimeScaleRangeModel() { return timeScaleRangeModel; } public DefaultBoundedRangeModel getValueScaleRangeModel() { return valueScaleRangeModel; } public DefaultBoundedRangeModel getChannelHeightRangeModel() { return channelHeightRangeModel; } public boolean isHorizontalLock() { return horizontalLock; } public void setHorizontalLock(boolean horizontalLock) { if (this.horizontalLock != horizontalLock) { this.horizontalLock = horizontalLock; if (horizontalLock && masterPlot != null) { Point viewportPosition = viewport.getViewPosition(); Point masterViewportPosition = masterPlot.getViewport().getViewPosition(); horizontalTimeLead = toTimeSpace(viewportPosition) - masterPlot.toTimeSpace(masterViewportPosition); horizontalPixelLead = viewportPosition.x - masterViewportPosition.x; } updateSignalPlotTitleLabel(); } } public boolean isVerticalLock() { return verticalLock; } public void setVerticalLock(boolean verticalLock) { if (this.verticalLock != verticalLock) { this.verticalLock = verticalLock; if (verticalLock && masterPlot != null) { Point viewportPosition = viewport.getViewPosition(); Point masterViewportPosition = masterPlot.getViewport().getViewPosition(); verticalValueLead = toValueSpace(viewportPosition) - masterPlot.toValueSpace(masterViewportPosition); verticalPixelLead = viewportPosition.y - masterViewportPosition.y; } updateSignalPlotTitleLabel(); } } public float getHorizontalTimeLead() { return horizontalTimeLead; } public float getVerticalValueLead() { return verticalValueLead; } public void setHorizontalTimeLead(float horizontalTimeLead) { if (this.horizontalTimeLead != horizontalTimeLead) { if (masterPlot != null) { this.horizontalTimeLead = horizontalTimeLead; boolean oldHorizontalLock = horizontalLock; boolean oldVerticalLock = verticalLock; try { horizontalLock = false; verticalLock = false; horizontalPixelLead = (int) Math.round(horizontalTimeLead * pixelPerSecond); Point masterPosition = masterPlot.getViewport().getViewPosition(); Point newPosition = new Point(masterPosition.x + horizontalPixelLead, masterPosition.y); newPosition.x = Math.max(0, Math.min(getSize().width - viewport.getExtentSize().width, newPosition.x)); viewport.setViewPosition(newPosition); } finally { horizontalLock = oldHorizontalLock; verticalLock = oldVerticalLock; } } } } public void setVerticalValueLead(float verticalValueLead) { if (this.verticalValueLead != verticalValueLead) { if (masterPlot != null) { this.verticalValueLead = verticalValueLead; boolean oldHorizontalLock = horizontalLock; boolean oldVerticalLock = verticalLock; try { horizontalLock = false; verticalLock = false; verticalPixelLead = (int) Math.round(verticalValueLead * pixelPerValue); Point masterPosition = masterPlot.getViewport().getViewPosition(); Point newPosition = new Point(masterPosition.x, masterPosition.y + verticalPixelLead); newPosition.y = Math.max(0, Math.min(getSize().height - viewport.getExtentSize().height, newPosition.y)); viewport.setViewPosition(newPosition); } finally { horizontalLock = oldHorizontalLock; verticalLock = oldVerticalLock; } } } } public int getHorizontalPixelLead() { return horizontalPixelLead; } public int getVerticalPixelLead() { return verticalPixelLead; } public TagPaintMode getTagPaintMode() { return tagPaintMode; } public void setTagPaintMode(TagPaintMode tagPaintMode) { if (this.tagPaintMode != tagPaintMode) { this.tagPaintMode = tagPaintMode; repaint(); } } public SignalColor getSignalColor() { return signalColor; } public void setSignalColor(SignalColor signalColor) { if (!Util.equalsWithNulls(this.signalColor, signalColor)) { this.signalColor = signalColor; repaint(); } } public boolean isSignalXOR() { return signalXOR; } public void setSignalXOR(boolean signalXOR) { if (this.signalXOR != signalXOR) { this.signalXOR = signalXOR; repaint(); } } public boolean isPageLinesVisible() { return pageLinesVisible; } public void setPageLinesVisible(boolean pageLinesVisible) { if (this.pageLinesVisible != pageLinesVisible) { this.pageLinesVisible = pageLinesVisible; if (signalPlotColumnHeader != null) { signalPlotColumnHeader.reset(); signalPlotColumnHeader.repaint(); } repaint(); } } public boolean isBlockLinesVisible() { return blockLinesVisible; } public void setBlockLinesVisible(boolean blockLinesVisible) { if (this.blockLinesVisible != blockLinesVisible) { this.blockLinesVisible = blockLinesVisible; repaint(); } } public boolean isChannelLinesVisible() { return channelLinesVisible; } public void setChannelLinesVisible(boolean channelLinesVisible) { if (this.channelLinesVisible != channelLinesVisible) { this.channelLinesVisible = channelLinesVisible; repaint(); } } public double getVoltageZoomFactor() { return voltageZoomFactor; } public double getVoltageZoomFactorRatio() { return voltageZoomFactorRatio; } public void setVoltageZoomFactor(double voltageZoomFactor) { if (this.voltageZoomFactor != voltageZoomFactor) { double oldValue = this.voltageZoomFactor; this.voltageZoomFactor = voltageZoomFactor; calculateParameters(); if (masterPlot == null) { try { ignoreSliderEvents = true; int rangeModelValue = (int)(voltageZoomFactor / voltageZoomFactorRatio); if (rangeModelValue > valueScaleRangeModel.getMaximum()) { valueScaleRangeModel.setMaximum(rangeModelValue); } if (rangeModelValue < valueScaleRangeModel.getMinimum()) { valueScaleRangeModel.setMinimum(rangeModelValue); } valueScaleRangeModel.setValue(rangeModelValue); //todo mati - rebuild gui in side panel } finally { ignoreSliderEvents = false; } } if (signalPlotRowHeader != null) { signalPlotRowHeader.repaint(); } repaint(); firePropertyChange(VOLTAGE_ZOOM_FACTOR_PROPERTY, oldValue, voltageZoomFactor); } } @Override public double getTimeZoomFactor() { return timeZoomFactor; } public void setTimeZoomFactor(double timeZoomFactor) { if (this.timeZoomFactor != timeZoomFactor) { double oldValue = this.timeZoomFactor; this.timeZoomFactor = timeZoomFactor; calculateParameters(); if (horizontalLock) { horizontalPixelLead = (int) Math.round(horizontalTimeLead * pixelPerSecond); } if (masterPlot == null) { try { ignoreSliderEvents = true; int rangeModelValue = (int)(timeZoomFactor*1000); if (rangeModelValue > timeScaleRangeModel.getMaximum()) { timeScaleRangeModel.setMaximum(rangeModelValue); } if (rangeModelValue < timeScaleRangeModel.getMinimum()) { timeScaleRangeModel.setMinimum(rangeModelValue); } timeScaleRangeModel.setValue(rangeModelValue); } finally { ignoreSliderEvents = false; } } if (signalPlotColumnHeader != null) { signalPlotColumnHeader.revalidate(); signalPlotColumnHeader.repaint(); } revalidate(); repaint(); firePropertyChange(TIME_ZOOM_FACTOR_PROPERTY, oldValue, timeZoomFactor); } } @Override public int getPixelPerChannel() { return pixelPerChannel; } public void setPixelPerChannel(int pixelPerChannel) { if (this.pixelPerChannel != pixelPerChannel) { if (verticalLock) { verticalPixelLead = (int) Math.round(verticalValueLead * pixelPerValue); } int oldValue = this.pixelPerChannel; this.pixelPerChannel = pixelPerChannel; calculateParameters(); if (masterPlot == null) { try { ignoreSliderEvents = true; if (pixelPerChannel > channelHeightRangeModel.getMaximum()) { channelHeightRangeModel.setMaximum(pixelPerChannel); } if (pixelPerChannel < channelHeightRangeModel.getMinimum()) { channelHeightRangeModel.setMinimum(pixelPerChannel); } channelHeightRangeModel.setValue(pixelPerChannel); } finally { ignoreSliderEvents = false; } } if (signalPlotRowHeader != null) { signalPlotRowHeader.revalidate(); signalPlotRowHeader.repaint(); } revalidate(); repaint(); firePropertyChange(PIXEL_PER_CHANNEL_PROPERTY, oldValue, pixelPerChannel); } } @Override public boolean isAntialiased() { return antialiased; } public void setAntialiased(boolean antialiased) { this.antialiased = antialiased; repaint(); } public boolean isCompensationEnabled() { return compensationEnabled; } public void setCompensationEnabled(boolean compensationEnabled) { this.compensationEnabled = compensationEnabled; } public boolean isClamped() { return clamped; } public void setClamped(boolean clamped) { this.clamped = clamped; repaint(); } public boolean isOffscreenChannelsDrawn() { return offscreenChannelsDrawn; } public void setOffscreenChannelsDrawn(boolean offscreenChannelsDrawn) { this.offscreenChannelsDrawn = offscreenChannelsDrawn; repaint(); } public boolean isTagToolTipsVisible() { return tagToolTipsVisible; } public void setTagToolTipsVisible(boolean tagToolTipsVisible) { if (this.tagToolTipsVisible != tagToolTipsVisible) { this.tagToolTipsVisible = tagToolTipsVisible; if (tagToolTipsVisible) { setToolTipText(""); signalPlotColumnHeader.setToolTipText(""); } else { setToolTipText(null); signalPlotColumnHeader.setToolTipText(null); } } } public boolean isOptimizeSignalDisplaying() { return optimizeSignalDisplaying; } public void setOptimizeSignalDisplaying(boolean optimizeSignalDisplaying) { this.optimizeSignalDisplaying = optimizeSignalDisplaying; repaint(); } @Override public double getPixelPerSecond() { return pixelPerSecond; } @Override public double getPixelPerBlock() { return pixelPerBlock; } @Override public double getPixelPerPage() { return pixelPerPage; } @Override public double getPixelPerValue() { return pixelPerValue; } public double getPixelPerValue(int channel) { return channelsPlotOptionsModel.getPixelsPerValue(channel); } @Override public int getChannelCount() { return channelCount; } @Override public int getPageCount() { return pageCount; } public int getWholePageCount() { return wholePageCount; } @Override public int getBlockCount() { return blockCount; } @Override public float getMaxTime() { return maxTime; } @Override public int getBlocksPerPage() { return blocksPerPage; } @Override public float getPageSize() { return pageSize; } @Override public float getBlockSize() { return blockSize; } @Override public float getSamplingFrequency() { return signalChain.getSamplingFrequency(); } public SignalPlotColumnHeader getSignalPlotColumnHeader() { return signalPlotColumnHeader; } public SignalPlotRowHeader getSignalPlotRowHeader() { return signalPlotRowHeader; } public SignalPlotCorner getSignalPlotCorner() { return signalPlotCorner; } public JLabel getSignalPlotTitleLabel() { if (signalPlotTitleLabel == null) { signalPlotTitleLabel = new JLabel(); signalPlotTitleLabel.setFont(signalPlotTitleLabel.getFont().deriveFont(Font.PLAIN, 10F)); updateSignalPlotTitleLabel(); } return signalPlotTitleLabel; } public void updateSignalPlotTitleLabel() { if (signalPlotTitleLabel != null) { String title; if (masterPlot == null) { title = ""; } else { String montageString; if (localMontage == null) { montageString = _("montage from document"); } else { montageString = _("modified montage"); } String hSynchroString = horizontalLock ? _("on") : _("off"); String vSynchroString = verticalLock ? _("on") : _("off"); title = _R("Auxiliary signal plot ({0}, horizontal synchro {1}, vertical synchro {2})", montageString, hSynchroString, vSynchroString); } signalPlotTitleLabel.setText(title); } } public JLabel getSignalPlotSynchronizationLabel() { if (signalPlotSynchronizationLabel == null) { signalPlotSynchronizationLabel = new JLabel(); signalPlotSynchronizationLabel.setFont(signalPlotSynchronizationLabel.getFont().deriveFont(Font.PLAIN, 10F)); signalPlotSynchronizationLabel.setHorizontalAlignment(SwingConstants.RIGHT); updateSignalPlotSynchronizationLabel(); } return signalPlotSynchronizationLabel; } public void updateSignalPlotSynchronizationLabel() { if (signalPlotSynchronizationLabel != null) { String text; Color color; if (masterPlot == null) { text = ""; color = Color.BLACK; } else { Point viewportPosition = viewport.getViewPosition(); Point masterViewportPosition = masterPlot.getViewport().getViewPosition(); int pixelDiff = viewportPosition.x - masterViewportPosition.x; if (horizontalLock && pixelDiff != horizontalPixelLead) { color = Color.RED; } else { color = Color.BLACK; } if (viewportPosition.x == masterViewportPosition.x) { text = _("synchronized"); } else { float timeDiff = toTimeSpace(viewportPosition) - masterPlot.toTimeSpace(masterViewportPosition); if (timeDiff < 0) { text = _R("trailing by {0,number,#.##}s", -timeDiff); } else { text = _R("leading by {0,number,#.##}s", timeDiff); } } } signalPlotSynchronizationLabel.setForeground(color); signalPlotSynchronizationLabel.setText(text); } } public void synchronizeToMaster() { if (masterPlot != null) { boolean oldHorizontalLock = horizontalLock; boolean oldVerticalLock = verticalLock; try { horizontalLock = false; verticalLock = false; viewport.setViewPosition(masterPlot.getViewport().getViewPosition()); horizontalTimeLead = 0; horizontalPixelLead = 0; verticalValueLead = 0; verticalPixelLead = 0; } finally { horizontalLock = oldHorizontalLock; verticalLock = oldVerticalLock; } } } public JViewport getViewport() { return viewport; } public void setViewport(JViewport viewport) { if (viewport == null) { throw new NullPointerException("No viewport"); } if (this.viewport != viewport) { if (this.viewport != null) { this.viewport.removeChangeListener(this); } this.viewport = viewport; if (viewport != null) { viewport.addChangeListener(this); } synchronizeToMaster(); } } @Override public int getMaxSampleCount() { return maxSampleCount; } @Override public SignalDocument getDocument() { return document; } public int[] getChannelLevel() { return channelLevel; } @Override public SignalView getView() { return view; } public SignalPlotPopupProvider getPopupMenuProvider() { return popupMenuProvider; } public void setPopupMenuProvider(SignalPlotPopupProvider popupMenuProvider) { this.popupMenuProvider = popupMenuProvider; signalPlotColumnHeader.setSignalViewPopupProvider(popupMenuProvider); } @Override public SignalPlot getMasterPlot() { return masterPlot; } public boolean isMaster() { return(masterPlot == null); } /* (non-Javadoc) * @see org.signalml.plugin.export.view.ExportedSignalPlot#tagSelection(org.signalml.plugin.export.signal.ExportedTagDocument, org.signalml.plugin.export.signal.TagStyle, org.signalml.plugin.export.signal.ExportedSignalSelection, boolean) */ @Override public void tagSelection(ExportedTagDocument tagDocument, ExportedTagStyle style, ExportedSignalSelection selection, boolean selectNew) throws InvalidClassException { if (tagDocument instanceof TagDocument) tagSelection((TagDocument) tagDocument, new TagStyle(style), new SignalSelection(selection), selectNew); else throw new InvalidClassException("only document got from SvarogAccess can be used"); } /* (non-Javadoc) * @see org.signalml.plugin.export.view.ExportedSignalPlot#eraseTagsFromSelection(org.signalml.plugin.export.signal.ExportedTagDocument, org.signalml.plugin.export.signal.ExportedSignalSelection) */ @Override public void eraseTagsFromSelection(ExportedTagDocument tagDocument, ExportedSignalSelection selection) throws InvalidClassException { if (tagDocument instanceof TagDocument) eraseTagsFromSelection((TagDocument) tagDocument, new SignalSelection(selection)); else throw new InvalidClassException("only document got from SvarogAccess can be used"); } /* (non-Javadoc) * @see org.signalml.plugin.export.view.ExportedSignalPlot#tagPageSelection(org.signalml.plugin.export.signal.ExportedTagDocument, org.signalml.plugin.export.signal.TagStyle, org.signalml.plugin.export.signal.ExportedSignalSelection, boolean) */ @Override public void tagPageSelection(ExportedTagDocument tagDocument, ExportedTagStyle style, ExportedSignalSelection selection, boolean selectNew) throws InvalidClassException { if (tagDocument instanceof TagDocument) tagPageSelection((TagDocument) tagDocument, new TagStyle(style), new SignalSelection(selection), selectNew); else throw new InvalidClassException("only document got from SvarogAccess can be used"); } /* (non-Javadoc) * @see org.signalml.plugin.export.view.ExportedSignalPlot#tagBlockSelection(org.signalml.plugin.export.signal.ExportedTagDocument, org.signalml.plugin.export.signal.TagStyle, org.signalml.plugin.export.signal.ExportedSignalSelection, boolean) */ @Override public void tagBlockSelection(ExportedTagDocument tagDocument, ExportedTagStyle style, ExportedSignalSelection selection, boolean selectNew) throws InvalidClassException { if (tagDocument instanceof TagDocument) tagBlockSelection((TagDocument) tagDocument, new TagStyle(style), new SignalSelection(selection), selectNew); else throw new InvalidClassException("only document got from SvarogAccess can be used"); } /* (non-Javadoc) * @see org.signalml.plugin.export.view.ExportedSignalPlot#tagChannelSelection(org.signalml.plugin.export.signal.ExportedTagDocument, org.signalml.plugin.export.signal.TagStyle, org.signalml.plugin.export.signal.ExportedSignalSelection, boolean) */ @Override public void tagChannelSelection(ExportedTagDocument tagDocument, ExportedTagStyle style, ExportedSignalSelection selection, boolean selectNew) throws InvalidClassException { if (tagDocument instanceof TagDocument) tagChannelSelection((TagDocument) tagDocument, new TagStyle(style), new SignalSelection(selection), selectNew); else throw new InvalidClassException("only document got from SvarogAccess can be used"); } public ChannelsPlotOptionsModel getChannelsPlotOptionsModel() { return this.channelsPlotOptionsModel; } }