[cooja] plugins/Visualizer: Multi-mote selection functionality

This adds multi-mote selection capabilities to the visualizer plugin:

- [Ctrl + Mouse Drag]: Rectangular selection of multiple motes
- [Ctrl + Mouse Click]: Add/Remove motes from current selection
- Mouse Drag on any selected Mote: Move all currently selected motes

Note: This changes previous behaviour of using Ctrl key.
This commit is contained in:
Enrico Joerns 2014-04-10 17:23:28 +02:00
parent a7dbf46b8e
commit 22ac769781
4 changed files with 298 additions and 173 deletions

View file

@ -33,6 +33,7 @@ import java.awt.Color;
import java.awt.FontMetrics; import java.awt.FontMetrics;
import java.awt.Graphics; import java.awt.Graphics;
import java.awt.Point; import java.awt.Point;
import java.util.Set;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
@ -72,20 +73,23 @@ public class MRMVisualizerSkin implements VisualizerSkin {
} }
public Color[] getColorOf(Mote mote) { public Color[] getColorOf(Mote mote) {
Mote selectedMote = visualizer.getSelectedMote(); if (visualizer.getSelectedMotes().contains(mote)) {
if (mote == selectedMote) {
return new Color[] { Color.CYAN }; return new Color[] { Color.CYAN };
} }
return null; return null;
} }
public void paintBeforeMotes(Graphics g) { public void paintBeforeMotes(Graphics g) {
final Mote selectedMote = visualizer.getSelectedMote(); Set<Mote> selectedMotes = visualizer.getSelectedMotes();
if (simulation == null if (simulation == null || selectedMotes == null || selectedMotes.isEmpty()) {
|| selectedMote == null
|| selectedMote.getInterfaces().getRadio() == null) {
return; return;
} }
final Mote selectedMote = visualizer.getSelectedMotes().iterator().next();
if (selectedMote.getInterfaces().getRadio() == null) {
return;
}
final Position sPos = selectedMote.getInterfaces().getPosition(); final Position sPos = selectedMote.getInterfaces().getPosition();
/* Paint transmission and interference range for selected mote */ /* Paint transmission and interference range for selected mote */

View file

@ -30,10 +30,12 @@
package org.contikios.cooja.plugins; package org.contikios.cooja.plugins;
import java.awt.BasicStroke;
import java.awt.BorderLayout; import java.awt.BorderLayout;
import java.awt.Color; import java.awt.Color;
import java.awt.Cursor; import java.awt.Cursor;
import java.awt.Dimension; import java.awt.Dimension;
import java.awt.Event;
import java.awt.Graphics; import java.awt.Graphics;
import java.awt.Graphics2D; import java.awt.Graphics2D;
import java.awt.Point; import java.awt.Point;
@ -55,7 +57,7 @@ import java.awt.event.ItemListener;
import java.awt.event.KeyEvent; import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter; import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent; import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener; import java.awt.event.MouseMotionAdapter;
import java.awt.event.MouseWheelEvent; import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener; import java.awt.event.MouseWheelListener;
import java.awt.geom.AffineTransform; import java.awt.geom.AffineTransform;
@ -64,9 +66,14 @@ import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Observable; import java.util.Observable;
import java.util.Observer; import java.util.Observer;
import java.util.Set;
import javax.swing.AbstractAction; import javax.swing.AbstractAction;
import javax.swing.Action; import javax.swing.Action;
@ -74,7 +81,6 @@ import javax.swing.JCheckBoxMenuItem;
import javax.swing.JMenu; import javax.swing.JMenu;
import javax.swing.JMenuBar; import javax.swing.JMenuBar;
import javax.swing.JMenuItem; import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel; import javax.swing.JPanel;
import javax.swing.JPopupMenu; import javax.swing.JPopupMenu;
import javax.swing.JSeparator; import javax.swing.JSeparator;
@ -128,6 +134,7 @@ import org.contikios.cooja.plugins.skins.UDGMVisualizerSkin;
* @see #registerVisualizerSkin(Class) * @see #registerVisualizerSkin(Class)
* @see UDGMVisualizerSkin * @see UDGMVisualizerSkin
* @author Fredrik Osterlind * @author Fredrik Osterlind
* @author Enrico Jorns
*/ */
@ClassDescription("Network") @ClassDescription("Network")
@PluginType(PluginType.SIM_STANDARD_PLUGIN) @PluginType(PluginType.SIM_STANDARD_PLUGIN)
@ -149,20 +156,34 @@ public class Visualizer extends VisPlugin implements HasQuickHelp {
private AffineTransform viewportTransform; private AffineTransform viewportTransform;
public int resetViewport = 0; public int resetViewport = 0;
/* Actions: move motes, pan view, and zoom view */ private static final int SELECT_MASK = Event.CTRL_MASK;
private boolean panning = false; private static final int MOVE_MASK = Event.SHIFT_MASK;
private Position panningPosition = null; /* Panning start position */
private boolean zooming = false; enum MotesActionState {
private double zoomStart = 0;
private Position zoomingPosition = null; /* Zooming center position */ UNKNWON,
private Point zoomingPixel = null; /* Zooming center pixel */ SELECT_PRESS,
private boolean moving = false; DEFAULT_PRESS,
private Point mouseDownPixel = null; /* Records position of mouse down to differentiate a click from a move */ PAN_PRESS,
private Mote movedMote = null; PANNING,
public Mote clickedMote = null; MOVING,
// rectangular select
SELECTING
}
/* All selected motes */
public Set<Mote> selectedMotes = new HashSet<>();
/* Mote that was under curser while mouse press */
Mote cursorMote;
MotesActionState mouseActionState = MotesActionState.UNKNWON;
/* Position where mouse button was pressed */
Position pressedPos;
private Set<Mote> movedMotes = null;
private long moveStartTime = -1; private long moveStartTime = -1;
private boolean moveConfirm; private static final Cursor MOVE_CURSOR = new Cursor(Cursor.MOVE_CURSOR);
private Cursor moveCursor = new Cursor(Cursor.MOVE_CURSOR); private Selection selection;
/* Visualizers */ /* Visualizers */
private static ArrayList<Class<? extends VisualizerSkin>> visualizerSkins = private static ArrayList<Class<? extends VisualizerSkin>> visualizerSkins =
@ -277,6 +298,7 @@ public class Visualizer extends VisPlugin implements HasQuickHelp {
}); });
zoomMenu.add(resetViewportItem); zoomMenu.add(resetViewportItem);
selection = new Selection();
/* Main canvas */ /* Main canvas */
canvas = new JPanel() { canvas = new JPanel() {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@ -296,6 +318,7 @@ public class Visualizer extends VisPlugin implements HasQuickHelp {
for (VisualizerSkin skin: currentSkins) { for (VisualizerSkin skin: currentSkins) {
skin.paintAfterMotes(g); skin.paintAfterMotes(g);
} }
selection.drawSelection(g);
} }
}; };
canvas.setBackground(Color.WHITE); canvas.setBackground(Color.WHITE);
@ -378,18 +401,16 @@ public class Visualizer extends VisPlugin implements HasQuickHelp {
}); });
/* Popup menu */ /* Popup menu */
canvas.addMouseMotionListener(new MouseMotionListener() { canvas.addMouseMotionListener(new MouseMotionAdapter() {
public void mouseMoved(MouseEvent e) { @Override
handleMouseMove(e, false);
}
public void mouseDragged(MouseEvent e) { public void mouseDragged(MouseEvent e) {
handleMouseMove(e, false); handleMouseDrag(e, false);
} }
}); });
canvas.addMouseListener(new MouseAdapter() { canvas.addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent e) { public void mousePressed(MouseEvent e) {
if (e.isPopupTrigger()) { if (e.isPopupTrigger()) {
handlePopupRequest(e.getPoint().x, e.getPoint().y); handlePopupRequest(e.getPoint());
return; return;
} }
@ -399,19 +420,13 @@ public class Visualizer extends VisPlugin implements HasQuickHelp {
} }
public void mouseReleased(MouseEvent e) { public void mouseReleased(MouseEvent e) {
if (e.isPopupTrigger()) { if (e.isPopupTrigger()) {
handlePopupRequest(e.getPoint().x, e.getPoint().y); handlePopupRequest(e.getPoint());
return; return;
} }
handleMouseMove(e, true); if (SwingUtilities.isLeftMouseButton(e)) {
handleMouseRelease(e);
} }
public void mouseClicked(MouseEvent e) {
if (e.isPopupTrigger()) {
handlePopupRequest(e.getPoint().x, e.getPoint().y);
}
handleMouseMove(e, true);
repaint();
} }
}); });
canvas.addMouseWheelListener(new MouseWheelListener() { canvas.addMouseWheelListener(new MouseWheelListener() {
@ -645,11 +660,11 @@ public class Visualizer extends VisPlugin implements HasQuickHelp {
visualizerSkins.remove(skin); visualizerSkins.remove(skin);
} }
private void handlePopupRequest(final int x, final int y) { private void handlePopupRequest(Point point) {
JPopupMenu menu = new JPopupMenu(); JPopupMenu menu = new JPopupMenu();
/* Mote specific actions */ /* Mote specific actions */
final Mote[] motes = findMotesAtPosition(x, y); final Mote[] motes = findMotesAtPosition(point.x, point.y);
if (motes != null && motes.length > 0) { if (motes != null && motes.length > 0) {
menu.add(new JSeparator()); menu.add(new JSeparator());
@ -709,8 +724,8 @@ public class Visualizer extends VisPlugin implements HasQuickHelp {
/* Show menu */ /* Show menu */
menu.setLocation(new Point( menu.setLocation(new Point(
canvas.getLocationOnScreen().x + x, canvas.getLocationOnScreen().x + point.x,
canvas.getLocationOnScreen().y + y)); canvas.getLocationOnScreen().y + point.y));
menu.setInvoker(canvas); menu.setInvoker(canvas);
menu.setVisible(true); menu.setVisible(true);
} }
@ -826,43 +841,169 @@ public class Visualizer extends VisPlugin implements HasQuickHelp {
private void handleMousePress(MouseEvent mouseEvent) { private void handleMousePress(MouseEvent mouseEvent) {
int x = mouseEvent.getX(); int x = mouseEvent.getX();
int y = mouseEvent.getY(); int y = mouseEvent.getY();
clickedMote = null;
if (mouseEvent.isControlDown()) { pressedPos = transformPixelToPosition(mouseEvent.getPoint());
/* Zoom */
zooming = true; // this is the state we have from pressing button
zoomingPixel = new Point(x, y); final Mote[] foundMotes = findMotesAtPosition(x, y);
zoomingPosition = transformPixelToPosition(zoomingPixel); if (foundMotes == null) {
zoomStart = viewportTransform.getScaleX(); cursorMote = null;
return; }
else {
// select top mote
cursorMote = foundMotes[foundMotes.length - 1];
} }
final Mote[] motes = findMotesAtPosition(x, y); int modifiers = mouseEvent.getModifiers();
if (mouseEvent.isShiftDown() ||
(!mouseEvent.isAltDown() && (motes == null || motes.length == 0))) { /* translate input */
/* No motes clicked or shift pressed: We should pan */ if ((modifiers & SELECT_MASK) != 0) {
panning = true; mouseActionState = MotesActionState.SELECT_PRESS;
panningPosition = transformPixelToPosition(x, y); }
return; else if ((modifiers & MOVE_MASK) != 0) {
// only move viewport
mouseActionState = MotesActionState.PAN_PRESS;
}
else {
if (foundMotes == null) {
// move viewport
selectedMotes.clear();
}
else {
// if this mote was not selected before, assume a new selection
if (!selectedMotes.contains(cursorMote)) {
selectedMotes.clear();
selectedMotes.add(cursorMote);
}
}
mouseActionState = MotesActionState.DEFAULT_PRESS;
}
repaint();
} }
if (motes != null && motes.length > 0) { Map<Mote, double[]> moveStartPositions = new HashMap<>();
/* One of the clicked motes should be moved */
mouseDownPixel = new Point(x, y); private void handleMouseDrag(MouseEvent e, boolean stop) {
clickedMote = motes[0]; Position currPos = transformPixelToPosition(e.getPoint());
beginMoveRequest(motes[0], false, false);
switch (mouseActionState) {
case DEFAULT_PRESS:
if (cursorMote == null) {
mouseActionState = MotesActionState.PANNING;
}
else {
mouseActionState = MotesActionState.MOVING;
// save start position
for (Mote m : selectedMotes) {
Position pos = m.getInterfaces().getPosition();
moveStartPositions.put(m, new double[]{
pos.getXCoordinate(),
pos.getYCoordinate(),
pos.getZCoordinate()});
}
}
break;
case MOVING:
canvas.setCursor(MOVE_CURSOR);
for (Mote moveMote : selectedMotes) {
moveMote.getInterfaces().getPosition().setCoordinates(
moveStartPositions.get(moveMote)[0]
+ (currPos.getXCoordinate() - pressedPos.getXCoordinate()),
moveStartPositions.get(moveMote)[1]
+ (currPos.getYCoordinate() - pressedPos.getYCoordinate()),
moveStartPositions.get(moveMote)[2]
);
repaint();
}
break;
case PAN_PRESS:
mouseActionState = MotesActionState.PANNING;
break;
case PANNING:
/* The current mouse position should correspond to where panning started */
viewportTransform.translate(
currPos.getXCoordinate() - pressedPos.getXCoordinate(),
currPos.getYCoordinate() - pressedPos.getYCoordinate()
);
repaint();
break;
case SELECT_PRESS:
mouseActionState = MotesActionState.SELECTING;
selection.setEnabled(true);
break;
case SELECTING:
int pressedX = transformToPixelX(pressedPos.getXCoordinate());
int pressedY = transformToPixelY(pressedPos.getYCoordinate());
int currX = transformToPixelX(currPos.getXCoordinate());
int currY = transformToPixelY(currPos.getYCoordinate());
int startX = pressedX < currX ? pressedX : currX;
int startY = pressedY < currY ? pressedY : currY;
int width = Math.abs(pressedX - currX);
int height = Math.abs(pressedY - currY);
selection.setSelection(startX, startY, width, height);
selectedMotes.clear();
selectedMotes.addAll(Arrays.asList(findMotesInRange(startX, startY, width, height)));
repaint();
break;
} }
} }
private void beginMoveRequest(Mote moteToMove, boolean withTiming, boolean confirm) { private void handleMouseRelease(MouseEvent mouseEvent) {
switch (mouseActionState) {
case PAN_PRESS:
// ignore
break;
case SELECT_PRESS:
if (cursorMote == null) {
/* Click on free canvas deselects all mote */
selectedMotes.clear();
}
else {
/* toggle selection for mote */
if (selectedMotes.contains(cursorMote)) {
selectedMotes.remove(cursorMote);
}
else {
selectedMotes.add(cursorMote);
}
}
break;
case DEFAULT_PRESS:
if (cursorMote == null) {
/* Click on free canvas deselects all mote */
selectedMotes.clear();
}
else {
/* Click on mote selects single mote */
selectedMotes.clear();
selectedMotes.add(cursorMote);
}
break;
case MOVING:
/* Release stops moving */
canvas.setCursor(Cursor.getDefaultCursor());
break;
case SELECTING:
/* Release stops moving */
selection.setEnabled(false);
repaint();
break;
}
repaint();
}
private void beginMoveRequest(Mote motesToMove, boolean withTiming, boolean confirm) {
if (withTiming) { if (withTiming) {
moveStartTime = System.currentTimeMillis(); moveStartTime = System.currentTimeMillis();
} else { } else {
moveStartTime = -1; moveStartTime = -1;
} }
moving = true; mouseActionState = MotesActionState.DEFAULT_PRESS;
moveConfirm = confirm; selectedMotes.clear();
movedMote = moteToMove; selectedMotes.add(motesToMove);
repaint(); repaint();
} }
@ -888,107 +1029,37 @@ public class Visualizer extends VisPlugin implements HasQuickHelp {
repaint(); repaint();
} }
private void handleMouseMove(MouseEvent e, boolean stop) { /**
int x = e.getX(); * Returns all motes in rectangular range
int y = e.getY(); *
* @param startX
/* Panning */ * @param startY
if (panning) { * @param width
if (panningPosition == null || stop) { * @param height
panning = false; * @return All motes in range
return; */
} public Mote[] findMotesInRange(int startX, int startY, int width, int height) {
List<Mote> motes = new LinkedList<>();
/* The current mouse position should correspond to where panning started */ for (Mote m : simulation.getMotes()) {
Position moved = transformPixelToPosition(x,y); Position pos = m.getInterfaces().getPosition();
viewportTransform.translate( int moteX = transformToPixelX(pos.getXCoordinate());
moved.getXCoordinate() - panningPosition.getXCoordinate(), int moteY = transformToPixelY(pos.getYCoordinate());
moved.getYCoordinate() - panningPosition.getYCoordinate() if (moteX > startX && moteX < startX + width
); && moteY > startY && moteY < startY + height) {
repaint(); motes.add(m);
return;
}
/* Zooming */
if (zooming) {
if (zoomingPosition == null || zoomingPixel == null || stop) {
zooming = false;
return;
}
/* The zooming start pixel should correspond to the zooming center position */
/* The current mouse position should correspond to where panning started */
double zoomFactor = 1.0 + Math.abs((double) zoomingPixel.y - y)/100.0;
double newZoom = (zoomingPixel.y - y)>0?zoomStart*zoomFactor: zoomStart/zoomFactor;
if (newZoom < 0.00001) {
newZoom = 0.00001;
}
viewportTransform.setToScale(
newZoom,
newZoom
);
Position moved = transformPixelToPosition(zoomingPixel);
viewportTransform.translate(
moved.getXCoordinate() - zoomingPosition.getXCoordinate(),
moved.getYCoordinate() - zoomingPosition.getYCoordinate()
);
repaint();
return;
}
/* Moving */
if (moving) {
if(x != mouseDownPixel.x || y != mouseDownPixel.y) {
Position newPos = transformPixelToPosition(x, y);
if (!stop) {
canvas.setCursor(moveCursor);
movedMote.getInterfaces().getPosition().setCoordinates(
newPos.getXCoordinate(),
newPos.getYCoordinate(),
movedMote.getInterfaces().getPosition().getZCoordinate()
);
repaint();
return;
}
/* Restore cursor */
canvas.setCursor(Cursor.getDefaultCursor());
/* Move mote */
if (moveStartTime < 0 || System.currentTimeMillis() - moveStartTime > 300) {
if (moveConfirm) {
String options[] = {"Yes", "Cancel"};
int returnValue = JOptionPane.showOptionDialog(Visualizer.this,
"Move mote to" +
"\nX=" + newPos.getXCoordinate() +
"\nY=" + newPos.getYCoordinate() +
"\nZ=" + movedMote.getInterfaces().getPosition().getZCoordinate(),
"Move mote?",
JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE,
null, options, options[0]);
moving = returnValue == JOptionPane.YES_OPTION;
}
if (moving) {
movedMote.getInterfaces().getPosition().setCoordinates(
newPos.getXCoordinate(),
newPos.getYCoordinate(),
movedMote.getInterfaces().getPosition().getZCoordinate()
);
repaint();
} }
} }
} Mote[] motesArr = new Mote[motes.size()];
return motes.toArray(motesArr);
moving = false;
movedMote = null;
repaint();
}
} }
/** /**
* Returns all motes at given position. * Returns all motes at given position.
* *
* If multiple motes were found on at a position the motes are returned
* in the order they are painted on screen.
* First mote in array is the bottom mote, last mote is the top mote.
*
* @param clickedX * @param clickedX
* X coordinate * X coordinate
* @param clickedY * @param clickedY
@ -1066,7 +1137,7 @@ public class Visualizer extends VisPlugin implements HasQuickHelp {
int x = pixelCoord.x; int x = pixelCoord.x;
int y = pixelCoord.y; int y = pixelCoord.y;
if (mote == movedMote) { if (mote == movedMotes) {
g.setColor(MOVE_COLOR); g.setColor(MOVE_COLOR);
g.fillOval(x - MOTE_RADIUS, y - MOTE_RADIUS, 2 * MOTE_RADIUS, g.fillOval(x - MOTE_RADIUS, y - MOTE_RADIUS, 2 * MOTE_RADIUS,
2 * MOTE_RADIUS); 2 * MOTE_RADIUS);
@ -1292,8 +1363,8 @@ public class Visualizer extends VisPlugin implements HasQuickHelp {
/** /**
* @return Selected mote * @return Selected mote
*/ */
public Mote getSelectedMote() { public Set<Mote> getSelectedMotes() {
return clickedMote; return selectedMotes;
} }
public Collection<Element> getConfigXML() { public Collection<Element> getConfigXML() {
@ -1585,4 +1656,44 @@ public class Visualizer extends VisPlugin implements HasQuickHelp {
"Multiple views can be active at the same time. " + "Multiple views can be active at the same time. " +
"Use the View menu to select views. "; "Use the View menu to select views. ";
}; };
private class Selection {
private int x;
private int y;
private int width;
private int height;
private boolean enable;
public void setSelection(int x, int y, int width, int height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
public void setEnabled(boolean enable) {
this.enable = enable;
}
public void drawSelection(Graphics g) {
/* only draw if enabled */
if (!enable) {
return;
}
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(new Color(64, 64, 64, 10));
g2d.fillRect(x, y, width, height);
BasicStroke dashed
= new BasicStroke(1.0f,
BasicStroke.CAP_BUTT,
BasicStroke.JOIN_MITER,
10.0f, new float[]{5.0f}, 0.0f);
g2d.setColor(Color.BLACK);
g2d.setStroke(dashed);
g2d.drawRect(x, y, width, height);
}
}
} }

View file

@ -34,6 +34,7 @@ import java.awt.Color;
import java.awt.FontMetrics; import java.awt.FontMetrics;
import java.awt.Graphics; import java.awt.Graphics;
import java.awt.Point; import java.awt.Point;
import java.util.Set;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
@ -74,18 +75,22 @@ public class DGRMVisualizerSkin implements VisualizerSkin {
} }
public Color[] getColorOf(Mote mote) { public Color[] getColorOf(Mote mote) {
Mote selectedMote = visualizer.getSelectedMote(); if (visualizer.getSelectedMotes().contains(mote)) {
if (mote == selectedMote) {
return new Color[] { Color.CYAN }; return new Color[] { Color.CYAN };
} }
return null; return null;
} }
public void paintBeforeMotes(Graphics g) { public void paintBeforeMotes(Graphics g) {
Mote selectedMote = visualizer.getSelectedMote(); Set<Mote> selectedMotes = visualizer.getSelectedMotes();
if (simulation == null if (simulation == null
|| selectedMote == null || selectedMotes == null
|| selectedMote.getInterfaces().getRadio() == null) { || selectedMotes.isEmpty()) {
return;
}
final Mote selectedMote = visualizer.getSelectedMotes().iterator().next();
if (selectedMote.getInterfaces().getRadio() == null) {
return; return;
} }

View file

@ -36,6 +36,7 @@ import java.awt.FontMetrics;
import java.awt.Graphics; import java.awt.Graphics;
import java.awt.Point; import java.awt.Point;
import java.beans.PropertyVetoException; import java.beans.PropertyVetoException;
import java.util.Set;
import javax.swing.BorderFactory; import javax.swing.BorderFactory;
import javax.swing.Box; import javax.swing.Box;
@ -259,8 +260,7 @@ public class UDGMVisualizerSkin implements VisualizerSkin {
@Override @Override
public Color[] getColorOf(Mote mote) { public Color[] getColorOf(Mote mote) {
Mote selectedMote = visualizer.getSelectedMote(); if (visualizer.getSelectedMotes().contains(mote)) {
if (mote == selectedMote) {
return new Color[] { Color.CYAN }; return new Color[] { Color.CYAN };
} }
return null; return null;
@ -268,10 +268,15 @@ public class UDGMVisualizerSkin implements VisualizerSkin {
@Override @Override
public void paintBeforeMotes(Graphics g) { public void paintBeforeMotes(Graphics g) {
Mote selectedMote = visualizer.getSelectedMote(); Set<Mote> selectedMotes = visualizer.getSelectedMotes();
if (simulation == null if (simulation == null
|| selectedMote == null || selectedMotes == null
|| selectedMote.getInterfaces().getRadio() == null) { || selectedMotes.isEmpty()) {
return;
}
final Mote selectedMote = visualizer.getSelectedMotes().iterator().next();
if (selectedMote.getInterfaces().getRadio() == null) {
return; return;
} }