extracted quick help interface to a separate file, moved plugin-specific quick help from quickhelp.txt to the plugins themselves

This commit is contained in:
Fredrik Osterlind 2012-06-01 11:50:51 +02:00
parent 1a10fa9d58
commit e96a375e33
10 changed files with 187 additions and 119 deletions

View file

@ -51,7 +51,7 @@ import org.jdom.Element;
import se.sics.cooja.ClassDescription;
import se.sics.cooja.GUI;
import se.sics.cooja.GUI.HasQuickHelp;
import se.sics.cooja.HasQuickHelp;
import se.sics.cooja.Mote;
import se.sics.cooja.MotePlugin;
import se.sics.cooja.PluginType;

View file

@ -48,6 +48,7 @@ import javax.swing.JTextField;
import se.sics.cooja.ClassDescription;
import se.sics.cooja.GUI;
import se.sics.cooja.HasQuickHelp;
import se.sics.cooja.Mote;
import se.sics.cooja.MotePlugin;
import se.sics.cooja.PluginType;
@ -63,7 +64,7 @@ import se.sics.mspsim.cli.LineOutputStream;
@ClassDescription("Msp CLI")
@PluginType(PluginType.MOTE_PLUGIN)
@SupportedArguments(motes = {MspMote.class})
public class MspCLI extends VisPlugin implements MotePlugin {
public class MspCLI extends VisPlugin implements MotePlugin, HasQuickHelp {
private static final long serialVersionUID = 2833218439838209672L;
@ -222,4 +223,13 @@ public class MspCLI extends VisPlugin implements MotePlugin {
return mspMote;
}
public String getQuickHelp() {
return
"<b>MSPSim's Command Line Interface</b>" +
"<br><br>help<br><i>lists available commands</i>" +
"<br><br>info CC2420<br><i>shows radio chip details</i>" +
"<br><br>log CC2420 > mylog.txt<br><i>logs radio chip details to file</i>" +
"<br><br>stacktrace<br><i>shows current stacktrace</i>";
}
}

View file

@ -1,74 +1,3 @@
se.sics.cooja.plugins.Visualizer = \
<b>Visualizer</b> \
<p>The visualizer shows the positions of simulated motes as viewed from above (XY-plane). \
It is possible to zoom (CRTL+Mouse drag) and pan (Shift+Mouse drag) the current view. Motes can be moved by dragging them (ALT+Mouse drag). \
Mouse right-click a mote or unoccupied space for a popup menu with more options. \
<p>The visualizer supports "visualizer skins". \
Each skin provides some specific information, such as ongoing simulated radio traffic, or the IP addresses of motes. \
Multiple skins can be active at the same time. \
Click the upper "Select visualizer skin" button to select or deselect skins. \
<p><b>Useful skins</b> \
<br>Mote IDs: prints the unique mote IDs inside motes. \
<br>Log output: prints the last printf message above motes. \
<br>Radio traffic: displays inter-mote radio communication. \
<br>Radio environment (UDGM): enables configurating the UDGM radio medium. \
<p><b>Tip</b><br> \
Right-click visualizer to show the popup menu, and click "Hide window decorations".
se.sics.cooja.plugins.LogListener = \
<b>Log Listener</b>\
<p>Listens to log output from all simulated motes. \
Right-click the main area for a popup menu with more options. \
<p>You may filter shown logs by entering regular expressions in the bottom text field. \
Filtering is performed on both the Mote and the Data columns.\
<p><b>Filter examples:</b> \
<br><br>Hello<br><i>logs containing the string 'Hello'</i>\
<br><br>^Contiki<br><i>logs starting with 'Contiki'</i>\
<br><br>^[CR]<br><i>logs starting either a C or an R</i>\
<br><br>Hello$<br><i>logs ending with 'Hello'</i>\
<br><br>^ID:[2-5]$<br><i>logs from motes 2 to 5</i>\
<br><br>^ID:[2-5] Contiki<br><i>logs from motes 2 to 5 starting with 'Contiki'</i>
se.sics.cooja.plugins.TimeLine = \
<b>Timeline</b>\
<p>The timeline arranges historical simulation events into a graphical timeline. \
The timeline can for example be used to overview the behavior of complex power-saving MAC protocols.\
<p>Events appear as colored rectangles in the timeline. For more information about a particular event, hover the mouse above it.\
<p>The checkboxes in the left pane control what event types are shown in the timeline. \
Currently, four event types are supported (see below). Note that the control pane can be hidden to save space. \
<p>All simulated motes are by default added to the timeline, however, any unwanted motes can be removed by mouse clicking the node ID (left).\
<p>To display a vertical time marker on the timeline, press and hold the mouse on the time ruler (top).\
<p>For more options, such as zooming and saving raw data to file, right-click the mouse for a popup menu.\
<p><b>Radio RX/TX</b>\
<br>Shows radio connection events. Transmissions are painted blue, receptions are green, and interfered radios are red.\
<p><b>Radio ON/OFF</b>\
<br>Shows whether the mote radio is on or off. Turned on radios are indicated with gray color.\
<p><b>LEDs</b>\
<br>Shows LED state: red, green, and blue. (Assumes all mote types have exactly three LEDs.)\
<p><b>Watchpoints</b>\
<br>Shows triggered watchpoints, currently only supported by MSPSim-based motes. To add watchpoints, use the Msp Code Watcher plugin.
se.sics.cooja.plugins.SimControl = \
<b>Control Panel</b>\
<p>The control panel controls the simulation. \
<p><i>Start</i> starts the simulation. \
<p><i>Pause</i> stops the simulation. \
<p>The keyboard shortcut for starting and pausing the simulation is <i>Ctrl+S</i>. \
<p><i>Step</i> runs the simulation for one millisecond. \
<p><i>Reload</i> reloads and restarts the simulation. \
<p>Writing simulation time in milliseconds in the <i>Stop at</i> field causes the simulation to pause at the given time. \
<p>Simulation speed is controlled via the bottom slider. \
If the slider value is zero, simulation runs at full speed. \
<p>Setting the slider to <i>Real time</i>, simulation speed is capped to not run faster than real time. \
The <i>Real time</i> slider value is to the right of <i>No simulation delay</i>: click on the slider button and press the right arrow key on the keyboard. \
se.sics.cooja.mspmote.plugins.MspCLI = \
<b>MSPSim's Command Line Interface</b>\
<br><br>help<br><i>lists available commands</i>\
<br><br>info CC2420<br><i>shows radio chip details</i>\
<br><br>log CC2420 > mylog.txt<br><i>logs radio chip details to file</i>\
<br><br>stacktrace<br><i>shows current stacktrace</i>
KEYBOARD_SHORTCUTS = \
<b>Keyboard shortcuts</b><br>\
<br><i>Ctrl+N:</i> New simulation\
@ -79,3 +8,8 @@ KEYBOARD_SHORTCUTS = \
<br>\
<br><i>F1:</i> Toggle quick help
GETTING_STARTED = \
<b>Getting started</b><br>\
<br>\
<br><i>F1:</i> Toggle quick help</i>

View file

@ -391,7 +391,6 @@ public class GUI extends Observable {
BorderFactory.createEmptyBorder(0, 3, 0, 0)
));
quickHelpScroll.setVisible(false);
loadQuickHelp("KEYBOARD_SHORTCUTS");
loadQuickHelp("GETTING_STARTED");
// Load default and overwrite with user settings (if any)
@ -4217,14 +4216,6 @@ public class GUI extends Observable {
}
}
public interface HasQuickHelp {
/**
* @return Quick help. May be HTML formatted, but must not include the
* document html-tags.
*/
public String getQuickHelp();
}
/**
* Load quick help for given object or identifier. Note that this method does not
* show the quick help pane.

View file

@ -0,0 +1,38 @@
/*
* Copyright (c) 2012, Swedish Institute of Computer Science.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the Institute nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
package se.sics.cooja;
public interface HasQuickHelp {
/**
* @return Quick help. May be HTML formatted, but must not include the
* document html-tags.
*/
public String getQuickHelp();
}

View file

@ -84,13 +84,14 @@ import org.jdom.Element;
import se.sics.cooja.ClassDescription;
import se.sics.cooja.GUI;
import se.sics.cooja.HasQuickHelp;
import se.sics.cooja.Mote;
import se.sics.cooja.Plugin;
import se.sics.cooja.PluginType;
import se.sics.cooja.Simulation;
import se.sics.cooja.VisPlugin;
import se.sics.cooja.SimEventCentral.LogOutputEvent;
import se.sics.cooja.SimEventCentral.LogOutputListener;
import se.sics.cooja.Simulation;
import se.sics.cooja.VisPlugin;
import se.sics.cooja.dialogs.TableColumnAdjuster;
import se.sics.cooja.dialogs.UpdateAggregator;
import se.sics.cooja.util.ArrayQueue;
@ -103,7 +104,7 @@ import se.sics.cooja.util.ArrayQueue;
*/
@ClassDescription("Log Listener")
@PluginType(PluginType.SIM_STANDARD_PLUGIN)
public class LogListener extends VisPlugin {
public class LogListener extends VisPlugin implements HasQuickHelp {
private static final long serialVersionUID = 3294595371354857261L;
private static Logger logger = Logger.getLogger(LogListener.class);
@ -124,7 +125,7 @@ public class LogListener extends VisPlugin {
private boolean formatTimeString = false;
private boolean hasHours = false;
private final JTable logTable;
private TableRowSorter<TableModel> logFilter;
private ArrayQueue<LogData> logs = new ArrayQueue<LogData>();
@ -147,9 +148,9 @@ public class LogListener extends VisPlugin {
private boolean hideDebug = false;
private JCheckBoxMenuItem hideDebugCheckbox;
private JCheckBoxMenuItem appendCheckBox;
private static final int UPDATE_INTERVAL = 250;
private UpdateAggregator<LogData> logUpdateAggregator = new UpdateAggregator<LogData>(UPDATE_INTERVAL) {
private Runnable scroll = new Runnable() {
@ -187,7 +188,7 @@ public class LogListener extends VisPlugin {
}
}
};
/**
* @param simulation Simulation
* @param gui GUI
@ -394,7 +395,7 @@ public class LogListener extends VisPlugin {
}
});
logTable.setComponentPopupMenu(popupMenu);
/* Fetch log output history */
@ -515,7 +516,7 @@ public class LogListener extends VisPlugin {
}
private void updateTitle() {
setTitle("Log Listener listening on "
setTitle("Log Listener listening on "
+ simulation.getEventCentral().getLogOutputObservationsCount() + " log interfaces");
}
@ -655,7 +656,7 @@ public class LogListener extends VisPlugin {
return;
}
}
});
});
}
private class LogData {
@ -726,8 +727,8 @@ public class LogListener extends VisPlugin {
PrintWriter outStream = new PrintWriter(new FileWriter(saveFile));
for(LogData data : logs) {
outStream.println(
data.getTime() + "\t" +
data.getID() + "\t" +
data.getTime() + "\t" +
data.getID() + "\t" +
data.ev.getMessage());
}
outStream.close();
@ -817,7 +818,7 @@ public class LogListener extends VisPlugin {
}
}
};
private Action timeLineAction = new AbstractAction("Timeline") {
private static final long serialVersionUID = -6358463434933029699L;
public void actionPerformed(ActionEvent e) {
@ -833,7 +834,7 @@ public class LogListener extends VisPlugin {
if (!(p instanceof TimeLine)) {
continue;
}
/* Select simulation time */
TimeLine plugin = (TimeLine) p;
plugin.trySelectTime(time);
@ -856,7 +857,7 @@ public class LogListener extends VisPlugin {
if (!(p instanceof RadioLogger)) {
continue;
}
/* Select simulation time */
RadioLogger plugin = (RadioLogger) p;
plugin.trySelectTime(time);
@ -891,7 +892,7 @@ public class LogListener extends VisPlugin {
model.fireTableRowsDeleted(0, size - 1);
}
}
private Action copyAction = new AbstractAction("Selected") {
private static final long serialVersionUID = -8433490108577001803L;
@ -953,4 +954,20 @@ public class LogListener extends VisPlugin {
}
};
public String getQuickHelp() {
return
"<b>Log Listener</b>" +
"<p>Listens to log output from all simulated motes. " +
"Right-click the main area for a popup menu with more options. " +
"<p>You may filter shown logs by entering regular expressions in the bottom text field. " +
"Filtering is performed on both the Mote and the Data columns." +
"<p><b>Filter examples:</b> " +
"<br><br>Hello<br><i>logs containing the string 'Hello'</i>" +
"<br><br>^Contiki<br><i>logs starting with 'Contiki'</i>" +
"<br><br>^[CR]<br><i>logs starting either a C or an R</i>" +
"<br><br>Hello$<br><i>logs ending with 'Hello'</i>" +
"<br><br>^ID:[2-5]$<br><i>logs from motes 2 to 5</i>" +
"<br><br>^ID:[2-5] Contiki<br><i>logs from motes 2 to 5 starting with 'Contiki'</i>";
}
}

View file

@ -51,13 +51,13 @@ import org.jdom.Element;
import se.sics.cooja.ClassDescription;
import se.sics.cooja.GUI;
import se.sics.cooja.HasQuickHelp;
import se.sics.cooja.Mote;
import se.sics.cooja.MoteInterface;
import se.sics.cooja.MotePlugin;
import se.sics.cooja.PluginType;
import se.sics.cooja.Simulation;
import se.sics.cooja.VisPlugin;
import se.sics.cooja.GUI.HasQuickHelp;
/**
* Mote Interface Viewer views information about a specific mote interface.
@ -208,7 +208,7 @@ public class MoteInterfaceViewer extends VisPlugin implements HasQuickHelp, Mote
setSelectedInterface(element.getText());
} else if (element.getName().equals("scrollpos")) {
String[] scrollPos = element.getText().split(",");
final Point pos = new Point(Integer.parseInt(scrollPos[0]), Integer.parseInt(scrollPos[1]));
final Point pos = new Point(Integer.parseInt(scrollPos[0]), Integer.parseInt(scrollPos[1]));
EventQueue.invokeLater(new Runnable() {
public void run() {
mainScrollPane.getViewport().setViewPosition(pos);
@ -222,7 +222,7 @@ public class MoteInterfaceViewer extends VisPlugin implements HasQuickHelp, Mote
public String getQuickHelp() {
String help = "<b>" + GUI.getDescriptionOf(this) + "</b>";
help += "<p>Lists mote interfaces, and allows mote inspection and interaction via mote interface visualizers.";
MoteInterface intf = selectedMoteInterface;
if (intf != null) {
if (intf instanceof HasQuickHelp) {

View file

@ -31,19 +31,41 @@
package se.sics.cooja.plugins;
import java.awt.*;
import java.awt.event.*;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.text.NumberFormat;
import java.util.*;
import javax.swing.*;
import java.util.Observable;
import java.util.Observer;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFormattedTextField;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.event.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.apache.log4j.Logger;
import se.sics.cooja.*;
import se.sics.cooja.ClassDescription;
import se.sics.cooja.GUI;
import se.sics.cooja.HasQuickHelp;
import se.sics.cooja.PluginType;
import se.sics.cooja.Simulation;
import se.sics.cooja.TimeEvent;
import se.sics.cooja.VisPlugin;
/**
* Control panel for starting and pausing the current simulation.
@ -53,7 +75,7 @@ import se.sics.cooja.*;
*/
@ClassDescription("Control Panel")
@PluginType(PluginType.SIM_STANDARD_PLUGIN)
public class SimControl extends VisPlugin {
public class SimControl extends VisPlugin implements HasQuickHelp {
private static final long serialVersionUID = 8452253637624664192L;
private static Logger logger = Logger.getLogger(SimControl.class);
@ -84,7 +106,7 @@ public class SimControl extends VisPlugin {
/* Update current time label when simulation is running */
if (simulation.isRunning()) {
updateLabelTimer.start();
updateLabelTimer.start();
}
/* Container */
@ -197,9 +219,9 @@ public class SimControl extends VisPlugin {
smallPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 10, 5));
sliderDelay = new JSlider(
JSlider.HORIZONTAL,
SLIDE_MIN,
SLIDE_MAX,
JSlider.HORIZONTAL,
SLIDE_MIN,
SLIDE_MAX,
convertTimeToSlide(simulation.getDelayTime()));
sliderDelay.addChangeListener(new ChangeListener() {
public void stateChanged(ChangeEvent e) {
@ -285,9 +307,9 @@ public class SimControl extends VisPlugin {
return Integer.MIN_VALUE;
}
if (slide <= 0) {
return slide-2; /* Ignore special cases */
return slide-2; /* Ignore special cases */
}
return slide;
return slide;
}
private int convertTimeToSlide(int time) {
@ -341,7 +363,7 @@ public class SimControl extends VisPlugin {
+ " ms");
long systemTimeDiff = System.currentTimeMillis() - lastSystemTimeTimestamp;
if(systemTimeDiff > 1000) {
long simulationTimeDiff = simulation.getSimulationTimeMillis() - lastSimulationTimeTimestamp;
@ -382,4 +404,19 @@ public class SimControl extends VisPlugin {
simulation.getGUI().reloadCurrentSimulation(simulation.isRunning());
}
};
public String getQuickHelp() {
return "<b>Control Panel</b>" +
"<p>The control panel controls the simulation. " +
"<p><i>Start</i> starts the simulation. " +
"<p><i>Pause</i> stops the simulation. " +
"<p>The keyboard shortcut for starting and pausing the simulation is <i>Ctrl+S</i>. " +
"<p><i>Step</i> runs the simulation for one millisecond. " +
"<p><i>Reload</i> reloads and restarts the simulation. " +
"<p>Writing simulation time in milliseconds in the <i>Stop at</i> field causes the simulation to pause at the given time. " +
"<p>Simulation speed is controlled via the bottom slider. " +
"If the slider value is zero, simulation runs at full speed. " +
"<p>Setting the slider to <i>Real time</i>, simulation speed is capped to not run faster than real time. " +
"The <i>Real time</i> slider value is to the right of <i>No simulation delay</i>: click on the slider button and press the right arrow key on the keyboard. ";
}
}

View file

@ -86,6 +86,7 @@ import org.jdom.Element;
import se.sics.cooja.ClassDescription;
import se.sics.cooja.GUI;
import se.sics.cooja.HasQuickHelp;
import se.sics.cooja.Mote;
import se.sics.cooja.Plugin;
import se.sics.cooja.PluginType;
@ -107,7 +108,7 @@ import se.sics.cooja.motes.AbstractEmulatedMote;
*/
@ClassDescription("Timeline")
@PluginType(PluginType.SIM_STANDARD_PLUGIN)
public class TimeLine extends VisPlugin {
public class TimeLine extends VisPlugin implements HasQuickHelp {
private static final long serialVersionUID = -883154261246961973L;
public static final int LED_PIXEL_HEIGHT = 2;
public static final int EVENT_PIXEL_HEIGHT = 4;
@ -242,7 +243,7 @@ public class TimeLine extends VisPlugin {
JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
timelineScrollPane.getHorizontalScrollBar().setUnitIncrement(50);
timelineMoteRuler = new MoteRuler();
timelineScrollPane.setRowHeaderView(timelineMoteRuler);
timelineScrollPane.setBackground(Color.WHITE);
@ -2386,4 +2387,25 @@ public class TimeLine extends VisPlugin {
}
});
public String getQuickHelp() {
return
"<b>Timeline</b>" +
"<p>The timeline arranges historical simulation events into a graphical timeline. " +
"The timeline can for example be used to overview the behavior of complex power-saving MAC protocols." +
"<p>Events appear as colored rectangles in the timeline. For more information about a particular event, hover the mouse above it." +
"<p>The checkboxes in the left pane control what event types are shown in the timeline. " +
"Currently, four event types are supported (see below). Note that the control pane can be hidden to save space. " +
"<p>All simulated motes are by default added to the timeline, however, any unwanted motes can be removed by mouse clicking the node ID (left)." +
"<p>To display a vertical time marker on the timeline, press and hold the mouse on the time ruler (top)." +
"<p>For more options, such as zooming and saving raw data to file, right-click the mouse for a popup menu." +
"<p><b>Radio RX/TX</b>" +
"<br>Shows radio connection events. Transmissions are painted blue, receptions are green, and interfered radios are red." +
"<p><b>Radio ON/OFF</b>" +
"<br>Shows whether the mote radio is on or off. Turned on radios are indicated with gray color." +
"<p><b>LEDs</b>" +
"<br>Shows LED state: red, green, and blue. (Assumes all mote types have exactly three LEDs.)" +
"<p><b>Watchpoints</b>" +
"<br>Shows triggered watchpoints, currently only supported by MSPSim-based motes. To add watchpoints, use the Msp Code Watcher plugin.";
}
}

View file

@ -71,7 +71,6 @@ import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JButton;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
@ -89,6 +88,7 @@ import org.jdom.Element;
import se.sics.cooja.ClassDescription;
import se.sics.cooja.GUI;
import se.sics.cooja.GUI.MoteRelation;
import se.sics.cooja.HasQuickHelp;
import se.sics.cooja.Mote;
import se.sics.cooja.MoteInterface;
import se.sics.cooja.PluginType;
@ -127,7 +127,7 @@ import se.sics.cooja.plugins.skins.UDGMVisualizerSkin;
*/
@ClassDescription("Simulation visualizer")
@PluginType(PluginType.SIM_STANDARD_PLUGIN)
public class Visualizer extends VisPlugin {
public class Visualizer extends VisPlugin implements HasQuickHelp {
private static final long serialVersionUID = 1L;
private static Logger logger = Logger.getLogger(Visualizer.class);
@ -1434,5 +1434,24 @@ public class Visualizer extends VisPlugin {
}
return true;
}
}
public String getQuickHelp() {
return
"<b>Visualizer</b> " +
"<p>The visualizer shows the positions of simulated motes as viewed from above (XY-plane). " +
"It is possible to zoom (CRTL+Mouse drag) and pan (Shift+Mouse drag) the current view. Motes can be moved by dragging them (ALT+Mouse drag). " +
"Mouse right-click a mote or unoccupied space for a popup menu with more options. " +
"<p>The visualizer supports \"visualizer skins\". " +
"Each skin provides some specific information, such as ongoing simulated radio traffic, or the IP addresses of motes. " +
"Multiple skins can be active at the same time. " +
"Click the upper \"Select visualizer skin\" button to select or deselect skins. " +
"<p><b>Useful skins</b> " +
"<br>Mote IDs: prints the unique mote IDs inside motes. " +
"<br>Log output: prints the last printf message above motes. " +
"<br>Radio traffic: displays inter-mote radio communication. " +
"<br>Radio environment (UDGM): enables configurating the UDGM radio medium. " +
"<p><b>Tip</b><br> " +
"Right-click visualizer to show the popup menu, and click \"Hide window decorations\".";
};
}