/** * This file is protected by Copyright. * Please refer to the COPYRIGHT file distributed with this source distribution. * * This file is part of REDHAWK IDE. * * 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. * */ package gov.redhawk.ui.port.nxmplot; import gov.redhawk.ui.port.nxmplot.PlotSettings.PlotMode; import gov.redhawk.ui.port.nxmplot.preferences.PlotPreferences; import gov.redhawk.ui.port.nxmplot.preferences.Preference; import java.awt.Color; import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import nxm.sys.inc.Commandable; import nxm.sys.inc.MessageHandler; import nxm.sys.lib.Command; import nxm.sys.lib.Message; import nxm.sys.lib.Position; import nxm.sys.lib.Table; import nxm.sys.libg.DragBox; import nxm.sys.libg.Layer; import nxm.sys.libg.MColor; import nxm.sys.libg.MPlot; import nxm.sys.prim.plot; import org.eclipse.core.runtime.ISafeRunnable; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.ListenerList; import org.eclipse.core.runtime.Status; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.util.IPropertyChangeListener; import org.eclipse.jface.util.PropertyChangeEvent; import org.eclipse.jface.util.SafeRunnable; import org.eclipse.swt.widgets.Composite; import org.eclipse.ui.statushandlers.StatusManager; /** * @noextend This class is not intended to be subclassed by SDK clients. */ public abstract class AbstractNxmPlotWidget extends Composite { /** index to create unique results name. */ private static final AtomicInteger NAME_INDEX = new AtomicInteger(); /** index to create unique pipe name. */ private static final AtomicInteger PIPE_NAME_INDEX = new AtomicInteger(); private IPreferenceStore store = Preference.initStoreFromWorkbench(PlotPreferences.getAllPreferences()); private final IPropertyChangeListener listener = new IPropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent event) { if (isInitialized()) { handlePropertyChange(event); } } }; /** key=sourcePipeId, value=IPlotSession */ private final Map<String, IPlotSession> inputSessions = Collections.synchronizedMap(new LinkedHashMap<String, IPlotSession>()); /** * This class handles all plot clicks and mouse moves and forwards the relevant ones to * all registered IPlotWidgetListener listeners */ private class PlotMessageHandler implements MessageHandler { private final ListenerList<IPlotWidgetListener> plotListeners = new ListenerList<IPlotWidgetListener>(ListenerList.IDENTITY); @Override public int processMessage(Message msg) { int retVal = Commandable.NORMAL; if ("MARK".equals(msg.name) && msg.info == 1) { //left click final Position p = (Position) msg.data; final Object[] listeners = this.plotListeners.getListeners(); for (final Object obj : listeners) { final IPlotWidgetListener pl = (IPlotWidgetListener) obj; pl.click(p.x, p.y, p.t); } } else if ("MARK".equals(msg.name) && msg.info == 0) { //mouse move final Position p = (Position) msg.data; final Object[] listeners = this.plotListeners.getListeners(); for (final Object obj : listeners) { final IPlotWidgetListener pl = (IPlotWidgetListener) obj; pl.motion(p.x, p.y, p.t); } } else if ("ZOOM".equals(msg.name)) { //left-click drag Double[] values = getValues(msg.data); if (values != null) { final Object[] listeners = this.plotListeners.getListeners(); for (final Object obj : listeners) { final IPlotWidgetListener pl = (IPlotWidgetListener) obj; pl.zoomX(values[0], values[1], values[2], values[3], msg.data); // SUPPRESS CHECKSTYLE MagicNumber } } } else if ("ZOOMIN".equals(msg.name)) { //mousewheel Double[] values = getValues(msg.data); if (values != null) { final Object[] listeners = this.plotListeners.getListeners(); for (final Object obj : listeners) { final IPlotWidgetListener pl = (IPlotWidgetListener) obj; pl.zoomIn(values[0], values[1], values[2], values[3], msg.data); // SUPPRESS CHECKSTYLE MagicNumber } } } else if ("DRAGBOX".equals(msg.name)) { //right-click drag final DragBox d = (DragBox) msg.data; final Object[] listeners = this.plotListeners.getListeners(); for (final Object obj : listeners) { final IPlotWidgetListener pl = (IPlotWidgetListener) obj; pl.dragBox(d.getXMin(), d.getYMin(), d.getXMax(), d.getYMax()); } } else if ("UNZOOM".equals(msg.name)) { //right-click Double[] values = getValues(msg.data); if (values != null) { final Object[] listeners = this.plotListeners.getListeners(); for (final Object obj : listeners) { final IPlotWidgetListener pl = (IPlotWidgetListener) obj; pl.unzoom(values[0], values[1], values[2], values[3], msg.data); // SUPPRESS CHECKSTYLE MagicNumber } } } else if ("ZOOMOUT".equals(msg.name)) { //mousewheel Double[] values = getValues(msg.data); if (values != null) { final Object[] listeners = this.plotListeners.getListeners(); for (final Object obj : listeners) { final IPlotWidgetListener pl = (IPlotWidgetListener) obj; pl.zoomOut(values[0], values[1], values[2], values[3], msg.data); // SUPPRESS CHECKSTYLE MagicNumber } } } else if ("PANXY".equals(msg.name)) { //middle drag final double[] d = (double[]) msg.data; final Object[] listeners = this.plotListeners.getListeners(); for (final Object obj : listeners) { final IPlotWidgetListener pl = (IPlotWidgetListener) obj; pl.pan(d[0], d[2], d[1], d[3]); // SUPPRESS CHECKSTYLE MagicNumber } } else { if ("ERROR".equals(msg.name)) { StatusManager.getManager().handle( new Status(IStatus.ERROR, PlotActivator.PLUGIN_ID, "PlotMessageHandler got error message: " + msg.toString("+FROM")), StatusManager.LOG); } retVal = Commandable.NOOP; } return retVal; } private Double[] getValues(Object obj) { if (obj instanceof DragBox) { DragBox box = (DragBox) obj; return new Double[] { box.getXMin(), box.getYMin(), box.getXMax(), box.getYMax() }; } else if (obj instanceof Table) { Table table = (Table) obj; Double[] values = new Double[4]; // SUPPRESS CHECKSTYLE MagicNumber values[0] = table.getD("X1"); // SUPPRESS CHECKSTYLE MagicNumber values[1] = table.getD("Y1"); // SUPPRESS CHECKSTYLE MagicNumber values[2] = table.getD("X2"); // SUPPRESS CHECKSTYLE MagicNumber values[3] = table.getD("Y2"); // SUPPRESS CHECKSTYLE MagicNumber return values; } return null; } public void addPlotListener(final IPlotWidgetListener listener) { this.plotListeners.add(listener); } public void removePlotListener(final IPlotWidgetListener listener) { this.plotListeners.remove(listener); } } private final PlotMessageHandler plotMessageHandler = new PlotMessageHandler(); private final ListenerList<MessageHandler> messageHandlers = new ListenerList<MessageHandler>(ListenerList.IDENTITY); private PlotType plotType; public AbstractNxmPlotWidget(final Composite parent, int style) { super(parent, style); addMessageHandler(this.plotMessageHandler); store.addPropertyChangeListener(listener); } public void addPlotListener(final IPlotWidgetListener listener) { this.plotMessageHandler.addPlotListener(listener); } public void removePlotListener(final IPlotWidgetListener listener) { this.plotMessageHandler.removePlotListener(listener); } public void addMessageHandler(final MessageHandler handler) { this.messageHandlers.add(handler); } public void removeMessageHandler(final MessageHandler handler) { this.messageHandlers.remove(handler); } protected int fireProcessMessage(final Message msg) { final int[] retVal = { Commandable.NORMAL }; final Object[] listeners = this.messageHandlers.getListeners(); for (final Object obj : listeners) { SafeRunnable.run(new ISafeRunnable() { @Override public void run() throws Exception { retVal[0] = ((MessageHandler) obj).processMessage(msg); } @Override public void handleException(final Throwable exception) { } }); } return retVal[0]; } public abstract String addDataFeature(Number xStart, Number xEnd, String color); /** * @since 4.4 */ public abstract String addFeatureByTypeMask(Number xStart, Number xEnd, Number yStart, Number yEnd, String typeMask, String color); /** * @since 4.0 */ public String addDragboxFeature(Number xmin, Number ymin, Number xmax, Number ymax, String color) { return null; } /** * @since 4.0 */ public void removeFeature(String featureid) { } /** * This method initializes the plot by calling the plot command and any other NextMidas commands necessary for plotting. * This method should be called once before any other commands are invoked. * Any additional calls will be ignored. * @param plotSwitches Switches to send to the plot command (MUST start with '/' if not empty or null). * @param plotArgs Arguments to send to the plot command */ public final void initPlot(String plotSwitches, String plotArgs) { setPlotSwitches(plotSwitches); setPlotArgs(plotArgs); PlotType type = null; if (plotArgs != null) { String upperCase = plotArgs.toUpperCase(); for (PlotType t : PlotType.values()) { if (upperCase.contains("TYPE=" + t)) { type = t; break; } } } if (type != null) { setPlotType(type); } else { setPlotType(PlotType.RASTER); } internalInitPlot(plotSwitches, plotArgs); } /** * @since 4.4 */ public void setPlotArgs(String plotArgs) { PlotPreferences.LAUNCH_ARGS.setValue(store, plotArgs); PlotPreferences.LAUNCH_ARGS_OVERRIDE.setValue(store, true); } /** * @since 4.4 */ public String getPlotArgs() { return PlotPreferences.LAUNCH_ARGS.getValue(store); } /** * @since 4.4 */ public boolean isSetPlotArgs() { return PlotPreferences.LAUNCH_ARGS_OVERRIDE.getValue(store); } /** * @since 4.4 */ public void unsetPlotArgs() { PlotPreferences.LAUNCH_ARGS.setToDefault(store); PlotPreferences.LAUNCH_ARGS_OVERRIDE.setValue(store, false); } /** * @since 4.4 */ public void setPlotSwitches(String plotSwitches) { PlotPreferences.LAUNCH_SWITCHES.setValue(store, plotSwitches); PlotPreferences.LAUNCH_SWITCHES_OVERRIDE.setValue(store, true); } /** * @since 4.4 */ public String getPlotSwitches() { return PlotPreferences.LAUNCH_SWITCHES.getValue(store); } /** * @since 4.4 */ public boolean isSetPlotSwitches() { return PlotPreferences.LAUNCH_SWITCHES_OVERRIDE.getValue(store); } /** * @since 4.4 */ public void unsetPlotSwitches() { PlotPreferences.LAUNCH_SWITCHES.setToDefault(store); PlotPreferences.LAUNCH_SWITCHES_OVERRIDE.setValue(store, false); } /** * @since 4.2 */ protected abstract void internalInitPlot(String plotSwtiches, String plotArgs); /** * @since 4.4 */ public abstract boolean isInitialized(); /** * Runs a command headlessly within the NmSession. TODO: renamed runServerCommand(..) * <p> * <b> DO NOT RUN PLOT OR SEND PLOT MESSAGES HERE</b> * <p> * This should be used to run source commands and additional filtering or processing commands before plotting. * @param command headless nm command to run */ public abstract void runHeadlessCommand(String command); /** * Runs a command headlessly within the NmSession (the server side for RAP). * <p> * <b> DO NOT RUN PLOT OR SEND PLOT MESSAGES HERE</b> * <p> * This should be used to run source commands and additional filtering or processing commands before plotting. * @param command headless nm command to run * @return Command that was executed * @since 4.4 */ public abstract Command runHeadlessCommandWithResult(String command); /** * Runs a command on the client's NeXtMidas session. * <p> * <b> DO NOT RUN PLOT OR SEND PLOT MESSAGES HERE</b> * <p> * This should be used to run source commands and additional filtering or processing commands before plotting. * @param command nm command to run on client machine */ public abstract void runClientCommand(String command); /** * Runs a command within the global NeXtMidas session * <p> * <b> DO NOT RUN PLOT OR SEND PLOT MESSAGES HERE</b> * <p> * This should be used to run source commands and additional filtering or processing commands before plotting. * @param command nm command to run in global NeXtMidas session * @return Command that was executed * @since 4.4 */ public abstract Command runGlobalCommand(String command); /** * @since 4.2 */ public final void addSource(String sourcePipeId, String qualifiers, IPlotSession session) { internalAddSource(sourcePipeId, qualifiers); inputSessions.put(sourcePipeId, session); } /** * Add a source to the plot (e.g. open file on PLOT). * @param sourcePipeId the pipe ID to add to this plot * @param pipeQualifiers pipe qualifiers. Can be null. * @since 4.2 */ protected abstract void internalAddSource(String sourcePipeId, String pipeQualifiers); /** * @return An unmodifiable list of the current plot sources */ public abstract Set<String> getSources(); /** * Removes all plot sources */ public void clearSources() { synchronized (inputSessions) { for (IPlotSession session : inputSessions.values()) { session.dispose(); } inputSessions.clear(); } } /** * Remove a pipe source from the plot * @param sourcePipeId remove a piped source from being plotted */ public void removeSource(String sourcePipeId) { IPlotSession session = inputSessions.remove(sourcePipeId); if (session != null) { session.dispose(); } else { // PASS // TODO // sendPlotMessage("CLOSEFILE", 0, sourcePipeId); } } /** * Send the plot a message. * @param msgName */ public abstract void sendPlotMessage(String msgName, int info, Object data); /** * Configure plot settings. * @param configuration */ public abstract void configurePlot(Map<String, String> configuration); /** * Creates a unique name to be used for pipes for variables within the shared NeXtMidas session * @return A new unique name for a pipe * @since 4.0 */ public static String createUniqueName() { return AbstractNxmPlotWidget.createUniqueName(true); } /** * Creates a unique name to be used for pipes or commands for variables within the shared NeXtMidas session * @return A new unique name * @since 4.0 */ public static String createUniqueName(boolean pipe) { if (pipe) { return "_UNIQUE_PIPE" + AbstractNxmPlotWidget.PIPE_NAME_INDEX.incrementAndGet(); } else { return "UNIQUE_NAME" + AbstractNxmPlotWidget.NAME_INDEX.incrementAndGet(); } } /** * @since 4.4, was added in 4.2 as protected */ public PlotMessageHandler getPlotMessageHandler() { return plotMessageHandler; } /** <b>INTERNAL USE ONLY</b> * @noreference This method is not intended to be referenced by clients. * Send the a message to specified command running on server/processing side. * @param cmdID ID of command to send message to * @param msgName name of message * @param info info of message (normally 0) * @param data data of message * @param quals quals of message * @since 4.2 */ public abstract void sendMessageToCommand(String cmdID, String msgName, int info, Object data, Object quals); /** * FOR INTERNAL USE ONLY. * @return null, unless implemented in subclass (e.g. RCPNxmPlotWidget) that have direct reference to the PLOT command. * @since 4.4 * @noreference This method is not intended to be referenced by clients. */ @Nullable public plot getPlot() { return null; } /** * Get a copy of current Plot Settings * @since 4.2 */ public PlotSettings getPlotSettings() { return new PlotSettings(store); } /** * @since 4.2 */ @Override public void dispose() { super.dispose(); clearSources(); store.removePropertyChangeListener(listener); } /** * @since 4.4 */ public double getMinValue() { return PlotPreferences.MIN.getValue(store); } /** * @since 4.4 */ public void setMinValue(double minValue) { PlotPreferences.MIN.setValue(store, minValue); PlotPreferences.MIN_OVERRIDE.setValue(store, true); } /** * @since 4.4 */ public void unsetMinValue() { PlotPreferences.MIN.setToDefault(store); PlotPreferences.MIN_OVERRIDE.setValue(store, false); } /** * @since 4.4 */ public boolean isSetMinValue() { return PlotPreferences.MIN_OVERRIDE.getValue(store); } /** * @since 4.4 */ public double getMaxValue() { return PlotPreferences.MAX.getValue(store); } /** * @since 4.4 */ public void setMaxValue(double maxValue) { PlotPreferences.MAX.setValue(store, maxValue); PlotPreferences.MAX_OVERRIDE.setValue(store, true); } /** * @since 4.4 */ public void unsetMaxValue() { PlotPreferences.MAX.setToDefault(store); PlotPreferences.MAX_OVERRIDE.setValue(store, false); } /** * @since 4.4 */ public boolean isSetMaxValue() { return PlotPreferences.MAX_OVERRIDE.getValue(store); } /** * @since 4.4 */ public PlotType getPlotType() { return this.plotType; } /** * @since 4.4 */ public void setPlotType(PlotType plotType) { this.plotType = plotType; if (isInitialized()) { sendPlotMessage("SET.PlotType", 0, plotType.toString()); } } /** * @since 4.4 */ public PlotMode getPlotMode() { return PlotMode.valueOf(PlotPreferences.MODE.getValue(store)); } /** * @since 4.4 */ public void setPlotMode(PlotMode plotMode) { PlotPreferences.MODE.setValue(store, plotMode.toString()); } /** * @since 4.4 */ public boolean isEnablePlotMenu() { return PlotPreferences.ENABLE_CONFIGURE_MENU_USING_MOUSE.getValue(store); } /** * @since 4.4 */ public void setEnablePlotMenu(boolean enablePlotMenu) { PlotPreferences.ENABLE_CONFIGURE_MENU_USING_MOUSE.setValue(store, enablePlotMenu); } /** * @noreference This method is not intended to be referenced by clients. * @param custom plot settings to apply * @since 4.2 */ public void applySettings(PlotSettings settings) { if (settings != null) { Double minVal = settings.getMinValue(); if (minVal != null) { setMinValue(minVal); } Double maxVal = settings.getMaxValue(); if (maxVal != null) { setMaxValue(maxVal); } PlotType newPlotType = settings.getPlotType(); if (newPlotType != null) { setPlotType(newPlotType); } final PlotMode plotMode = settings.getPlotMode(); if (plotMode != getPlotMode()) { setPlotMode(plotMode); } final Boolean enablePlotMenu = settings.getEnablePlotMenu(); if (enablePlotMenu != null) { setEnablePlotMenu(enablePlotMenu); } } } /** * @since 4.4 */ public IPreferenceStore getPreferenceStore() { return this.store; } /** * @since 4.4 */ protected void handlePropertyChange(PropertyChangeEvent event) { if (PlotPreferences.ENABLE_CONFIGURE_MENU_USING_MOUSE.isEvent(event)) { updateMenu(); } if (PlotPreferences.LAUNCH_ARGS.isEvent(event) || PlotPreferences.LAUNCH_ARGS_OVERRIDE.isEvent(event)) { updateLaunchArgs(); } if (PlotPreferences.LAUNCH_SWITCHES.isEvent(event) || PlotPreferences.LAUNCH_SWITCHES_OVERRIDE.isEvent(event)) { updateLaunchSwitches(); } if (PlotPreferences.MIN.isEvent(event) || PlotPreferences.MIN_OVERRIDE.isEvent(event) || PlotPreferences.MAX.isEvent(event) || PlotPreferences.MAX_OVERRIDE.isEvent(event)) { updateScale(); } if (PlotPreferences.MODE.isEvent(event)) { updatePlotMode(); } } private void updateLaunchSwitches() { // TODO Auto-generated method stub } private void updateLaunchArgs() { // TODO Auto-generated method stub } private void updatePlotMode() { if (getPlotMode().toModeString() != null && !getPlotMode().toModeString().isEmpty()) { sendPlotMessage("SET.MODE", 0, getPlotMode().toModeString()); // TODO: remove this line once nxm342 is released sendPlotMessage("SET.ComplexMode", 0, getPlotMode().toModeString()); // requires nxm342, otherwise fails silently } else { sendPlotMessage("SET.ComplexMode", 0, ""); // set plot mode to auto (requires nxm342) } } private void updateMenu() { String newValue = (isEnablePlotMenu()) ? "-NoMiddleMouse" : "+NoMiddleMouse"; sendPlotMessage("SET.MW.EventFilter", 0, newValue); } private void updateScale() { final boolean isRaster = PlotType.RASTER.equals(getPlotType()); String plotScaleSetting; String plotMinProperty = null; String plotMaxProperty = null; if (isSetMinValue()) { if (isRaster) { plotMinProperty = "SET.Z1"; } else { plotMinProperty = "SET.Y1"; } plotScaleSetting = "-AutoMin"; } else { plotScaleSetting = "+AutoMin"; } if (isSetMaxValue()) { if (isRaster) { plotMaxProperty = "SET.Z2"; } else { plotMaxProperty = "SET.Y2"; } plotScaleSetting += "|-AutoMax"; } else { plotScaleSetting += "|+AutoMax"; } sendPlotMessage("SET.SCALE", 0, plotScaleSetting); // should set scale setting before setting min/max if (plotMinProperty != null) { sendPlotMessage(plotMinProperty, 0, getMinValue()); } if (plotMaxProperty != null) { sendPlotMessage(plotMaxProperty, 0, getMaxValue()); } } /** * @since 4.4 */ public void setStore(IPreferenceStore store) { if (store == null) { store = Preference.initStoreFromWorkbench(PlotPreferences.getAllPreferences()); } if (this.store != null) { this.store.removePropertyChangeListener(listener); } this.store = store; this.store.addPropertyChangeListener(listener); } /** * @since 5.0 */ public void setPropertyOnLayer(String sourcePipeId, String properyName, int info, String data) { sendPlotMessage("SET.LAYERS." + sourcePipeId + "." + properyName, info, data); } /** * Get line color for specified source/layer on PLOT. * @since 5.0 */ public Color getLineColor(@NonNull String sourcePipeId) { Color retColor = null; plot plot = getPlot(); if (plot != null) { // have local PLOT (i.e. RcpNxmPlotWidget) MPlot mplot = plot.MP; if (mplot != null) { Object lay = mplot.getLayers().get(sourcePipeId); if (lay instanceof Layer) { retColor = ((Layer) lay).getColor(); } } } if (retColor == null) { // probably have remote PLOT (i.e. RapNxmPlotWidget) retColor = getDefaultLineColor(sourcePipeId); // use default colors here } return retColor; } /** set the line color of the specified sourcePipeId on the line plot. * @since 5.0 */ public void setLineColor(@NonNull String sourcePipeId, @NonNull Color color) { String colorStr = MColor.toString(color); setPropertyOnLayer(sourcePipeId, "COLOR", 0, colorStr); } /** Get PLOT's default line color for specified source. * @noreference This method is not intended to be referenced by clients. */ public Color getDefaultLineColor(@NonNull String sourcePipeId) { Color retColor = null; int index = 1; // try to figure out the source's layer index for (String key : inputSessions.keySet()) { if (sourcePipeId.equals(key)) { break; } index++; } retColor = MColor.getColorByIndex(index); // <-- PLOT/Layer1D uses this as default color return retColor; } }