radio logger update: autosizing columns + support for new microsecond resolution + some minor fixes

This commit is contained in:
nifi 2009-06-12 14:12:59 +00:00
parent be03c380c5
commit b2c0df08af
2 changed files with 450 additions and 40 deletions

View file

@ -0,0 +1,340 @@
/*
* Copyright (c) 2009, 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.
*
* $Id: TableColumnAdjuster.java,v 1.1 2009/06/12 14:12:59 nifi Exp $
*
* -----------------------------------------------------------------
*
* Author : Niclas Finne
* Created : 2009-05-20
* Updated : $Date: 2009/06/12 14:12:59 $
* $Revision: 1.1 $
*/
package se.sics.cooja.dialogs;
import java.awt.Component;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.JTable;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import javax.swing.table.TableModel;
/**
* This code was originally based on a Java tip from Rob Camick at
* Java Tips Weblog, http://tips4java.wordpress.com.
*/
public class TableColumnAdjuster implements PropertyChangeListener, TableModelListener {
private final JTable table;
private int spacing;
private boolean isColumnHeaderIncluded;
private boolean isColumnDataIncluded;
private boolean isOnlyAdjustLarger;
private boolean isDynamicAdjustment;
/*
* Specify the table and use default spacing
*/
public TableColumnAdjuster(JTable table) {
this(table, 6);
}
/*
* Specify the table and spacing
*/
public TableColumnAdjuster(JTable table, int spacing) {
this.table = table;
this.spacing = spacing;
setColumnHeaderIncluded(true);
setColumnDataIncluded(true);
setOnlyAdjustLarger(true);
setDynamicAdjustment(false);
}
public void packColumns() {
TableColumnModel tcm = table.getColumnModel();
for (int i = 0, n = tcm.getColumnCount(); i < n; i++) {
packColumn(i);
}
}
public void packColumn(final int column) {
adjustColumn(column, false);
}
/*
* Adjust the widths of all the columns in the table
*/
public void adjustColumns() {
TableColumnModel tcm = table.getColumnModel();
for (int i = 0, n = tcm.getColumnCount(); i < n; i++) {
adjustColumn(i, isOnlyAdjustLarger);
}
}
/*
* Adjust the width of the specified column in the table
*/
public void adjustColumn(int column) {
adjustColumn(column, isOnlyAdjustLarger);
}
private void adjustColumn(int column, boolean onlyAdjustLarger) {
int viewColumn = table.convertColumnIndexToView(column);
if (viewColumn < 0) {
return;
}
TableColumn tableColumn = table.getColumnModel().getColumn(viewColumn);
if (! tableColumn.getResizable()) {
return;
}
int columnHeaderWidth = getColumnHeaderWidth(tableColumn, column);
int columnDataWidth = getColumnDataWidth(tableColumn, column);
int preferredWidth = Math.max(columnHeaderWidth, columnDataWidth);
updateTableColumn(tableColumn, preferredWidth, onlyAdjustLarger);
}
/*
* Calculate the width based on the column name
*/
private int getColumnHeaderWidth(TableColumn tableColumn, int column) {
if (! isColumnHeaderIncluded) return 0;
Object value = tableColumn.getHeaderValue();
TableCellRenderer renderer = tableColumn.getHeaderRenderer();
if (renderer == null) {
renderer = table.getTableHeader().getDefaultRenderer();
}
Component c = renderer.getTableCellRendererComponent(table, value, false, false, -1, column);
return c.getPreferredSize().width;
}
/*
* Calculate the width based on the widest cell renderer for the
* given column.
*/
private int getColumnDataWidth(TableColumn tableColumn, int column) {
if (! isColumnDataIncluded) return 0;
int preferredWidth = 0;
int maxWidth = tableColumn.getMaxWidth();
for (int row = 0, n = table.getRowCount(); row < n; row++) {
preferredWidth = Math.max(preferredWidth, getCellDataWidth(row, column));
// We've exceeded the maximum width, no need to check other rows
if (preferredWidth >= maxWidth) {
break;
}
}
return preferredWidth;
}
/*
* Get the preferred width for the specified cell
*/
private int getCellDataWidth(int row, int column) {
// Invoke the renderer for the cell to calculate the preferred width
TableCellRenderer cellRenderer = table.getCellRenderer(row, column);
Object value = table.getModel().getValueAt(row, column);
Component c = cellRenderer.getTableCellRendererComponent(table, value, false, false, row, column);
int width = c.getPreferredSize().width + table.getIntercellSpacing().width;
return width;
}
/*
* Update the TableColumn with the newly calculated width
*/
private void updateTableColumn(TableColumn tableColumn, int width, boolean onlyAdjustLarger) {
int currentWidth = tableColumn.getWidth();
width += spacing;
// Don't shrink the column width if onlyAdjustLarger is set
if (width != currentWidth && (!onlyAdjustLarger || width > currentWidth)) {
table.getTableHeader().setResizingColumn(tableColumn);
tableColumn.setWidth(width);
tableColumn.setPreferredWidth(width);
}
}
private void adjustColumnsForNewRows(int firstRow, int lastRow) {
TableColumnModel tcm = table.getColumnModel();
for (int column = 0, n = tcm.getColumnCount(); column < n; column++) {
int viewColumn = table.convertColumnIndexToView(column);
if (viewColumn < 0) {
continue;
}
TableColumn tableColumn = tcm.getColumn(viewColumn);
if (! tableColumn.getResizable()) {
continue;
}
// Find max width for the new rows (only adjust if wider)
int width = 0;
for (int row = firstRow; row <= lastRow; row++) {
int w = getCellDataWidth(row, column);
if (w > width) {
width = w;
}
}
updateTableColumn(tableColumn, width, true);
}
}
/*
* Indicates whether to include the header in the width calculation
*/
public void setColumnHeaderIncluded(boolean isColumnHeaderIncluded) {
this.isColumnHeaderIncluded = isColumnHeaderIncluded;
}
/*
* Indicates whether to include the model data in the width calculation
*/
public void setColumnDataIncluded(boolean isColumnDataIncluded) {
this.isColumnDataIncluded = isColumnDataIncluded;
}
/*
* Indicates whether columns can only be increased in size
*/
public void setOnlyAdjustLarger(boolean isOnlyAdjustLarger) {
this.isOnlyAdjustLarger = isOnlyAdjustLarger;
}
/*
* Indicate whether changes to the model should cause the width to be
* dynamically recalculated.
*/
public void setDynamicAdjustment(boolean isDynamicAdjustment) {
if (this.isDynamicAdjustment != isDynamicAdjustment) {
this.isDynamicAdjustment = isDynamicAdjustment;
if (isDynamicAdjustment) {
table.addPropertyChangeListener(this);
table.getModel().addTableModelListener(this);
} else {
table.removePropertyChangeListener(this);
table.getModel().removeTableModelListener(this);
}
}
}
//
// Implement the PropertyChangeListener
//
public void propertyChange(PropertyChangeEvent e) {
// When the TableModel changes we need to update the listeners
// and column widths
if ("model".equals(e.getPropertyName())) {
if (this.isDynamicAdjustment) {
TableModel model = (TableModel)e.getOldValue();
model.removeTableModelListener(this);
model = (TableModel)e.getNewValue();
model.addTableModelListener(this);
}
adjustColumns();
}
}
//
// Implement the TableModelListener
//
public void tableChanged(final TableModelEvent e) {
if (e.getType() == TableModelEvent.INSERT) {
adjustColumnsForNewRows(e.getFirstRow(), e.getLastRow());
} else if (e.getType() == TableModelEvent.UPDATE) {
int column = e.getColumn();
int lastRow = e.getLastRow();
// Last row might be set higher than the row count if all rows have
// been updated
if (lastRow >= table.getRowCount()) {
lastRow = table.getRowCount() - 1;
}
if (column == TableModelEvent.ALL_COLUMNS) {
// All columns have been updated
if (isOnlyAdjustLarger) {
int firstRow = e.getFirstRow();
if (firstRow >= 0) {
// Handle the rows as new rows since they should only increase
// width as needed
adjustColumnsForNewRows(firstRow, lastRow);
}
} else {
// Could be an increase or decrease so check all rows, all columns
adjustColumns();
}
} else if (isOnlyAdjustLarger) {
// Only need to worry about an increase in width for these cells
int viewColumn = table.convertColumnIndexToView(column);
if (viewColumn < 0) {
// Column is not visible
} else {
TableColumn tableColumn = table.getColumnModel().getColumn(viewColumn);
if (tableColumn.getResizable()) {
int firstRow = e.getFirstRow();
int width = 0;
if (firstRow < 0) {
// Header changed
width = getColumnHeaderWidth(tableColumn, column);
} else {
for (int row = e.getFirstRow(); row <= lastRow; row++) {
int w = getCellDataWidth(row, column);
if (w > width) {
width = w;
}
}
}
updateTableColumn(tableColumn, width, true);
}
}
} else {
// Could be an increase or decrease so check all rows
adjustColumn(column, false);
}
} else {
// Some rows have been deleted.
if (!isOnlyAdjustLarger) {
adjustColumns();
}
}
}
}

View file

@ -26,21 +26,36 @@
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE. * SUCH DAMAGE.
* *
* $Id: RadioLogger.java,v 1.19 2009/05/26 14:25:29 fros4943 Exp $ * $Id: RadioLogger.java,v 1.20 2009/06/12 14:12:59 nifi Exp $
*/ */
package se.sics.cooja.plugins; package se.sics.cooja.plugins;
import java.awt.Font; import java.awt.Font;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent; import java.awt.event.MouseEvent;
import java.util.*; import java.util.ArrayList;
import javax.swing.*; import java.util.Collection;
import java.util.Observable;
import java.util.Observer;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.AbstractTableModel; import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableColumn;
import org.apache.log4j.Logger;
import org.jdom.Element; import org.jdom.Element;
import se.sics.cooja.ClassDescription;
import se.sics.cooja.*; import se.sics.cooja.ConvertedRadioPacket;
import se.sics.cooja.GUI;
import se.sics.cooja.Mote;
import se.sics.cooja.PluginType;
import se.sics.cooja.RadioConnection;
import se.sics.cooja.RadioMedium;
import se.sics.cooja.RadioPacket;
import se.sics.cooja.Simulation;
import se.sics.cooja.VisPlugin;
import se.sics.cooja.dialogs.TableColumnAdjuster;
import se.sics.cooja.interfaces.MoteID; import se.sics.cooja.interfaces.MoteID;
import se.sics.cooja.interfaces.Radio; import se.sics.cooja.interfaces.Radio;
import se.sics.cooja.util.StringUtils; import se.sics.cooja.util.StringUtils;
@ -54,7 +69,8 @@ import se.sics.cooja.util.StringUtils;
@ClassDescription("Radio Logger") @ClassDescription("Radio Logger")
@PluginType(PluginType.SIM_PLUGIN) @PluginType(PluginType.SIM_PLUGIN)
public class RadioLogger extends VisPlugin { public class RadioLogger extends VisPlugin {
private static Logger logger = Logger.getLogger(RadioLogger.class);
private static final long serialVersionUID = -6927091711697081353L;
private final static int COLUMN_TIME = 0; private final static int COLUMN_TIME = 0;
private final static int COLUMN_FROM = 1; private final static int COLUMN_FROM = 1;
@ -68,9 +84,9 @@ public class RadioLogger extends VisPlugin {
"Data" "Data"
}; };
private Simulation simulation; private final Simulation simulation;
private final JTable dataTable;
private ArrayList<RadioConnectionLog> connections = new ArrayList<RadioConnectionLog>(); private ArrayList<RadioConnectionLog> connections = new ArrayList<RadioConnectionLog>();
private JTable dataTable = null;
private RadioMedium radioMedium; private RadioMedium radioMedium;
private Observer radioMediumObserver; private Observer radioMediumObserver;
@ -80,6 +96,9 @@ public class RadioLogger extends VisPlugin {
radioMedium = simulation.getRadioMedium(); radioMedium = simulation.getRadioMedium();
final AbstractTableModel model = new AbstractTableModel() { final AbstractTableModel model = new AbstractTableModel() {
private static final long serialVersionUID = 1692207305977527004L;
public String getColumnName(int col) { public String getColumnName(int col) {
return COLUMN_NAMES[col]; return COLUMN_NAMES[col];
} }
@ -95,15 +114,21 @@ public class RadioLogger extends VisPlugin {
public Object getValueAt(int row, int col) { public Object getValueAt(int row, int col) {
RadioConnectionLog conn = connections.get(row); RadioConnectionLog conn = connections.get(row);
if (col == COLUMN_TIME) { if (col == COLUMN_TIME) {
return conn.startTime; return Long.toString(conn.startTime / Simulation.MILLISECOND);
} else if (col == COLUMN_FROM) { } else if (col == COLUMN_FROM) {
return getMoteID(conn.connection.getSource().getMote()); return getMoteID(conn.connection.getSource().getMote());
} else if (col == COLUMN_TO) { } else if (col == COLUMN_TO) {
Radio[] dests = conn.connection.getDestinations(); Radio[] dests = conn.connection.getDestinations();
if (dests.length == 0) {
return "-";
}
if (dests.length == 1) { if (dests.length == 1) {
return getMoteID(dests[0].getMote()); return getMoteID(dests[0].getMote());
} }
return "[" + dests.length + " motes]"; if (dests.length == 2) {
return getMoteID(dests[0].getMote()) + ',' + getMoteID(dests[1].getMote());
}
return "[" + dests.length + " d]";
} else if (col == COLUMN_DATA) { } else if (col == COLUMN_DATA) {
if (conn.data == null) { if (conn.data == null) {
prepareDataString(connections.get(row)); prepareDataString(connections.get(row));
@ -131,13 +156,15 @@ public class RadioLogger extends VisPlugin {
return false; return false;
} }
public Class getColumnClass(int c) { public Class<?> getColumnClass(int c) {
return getValueAt(0, c).getClass(); return getValueAt(0, c).getClass();
} }
}; };
dataTable = new JTable(model) { dataTable = new JTable(model) {
private static final long serialVersionUID = -2199726885069809686L;
public String getToolTipText(MouseEvent e) { public String getToolTipText(MouseEvent e) {
java.awt.Point p = e.getPoint(); java.awt.Point p = e.getPoint();
int rowIndex = rowAtPoint(p); int rowIndex = rowAtPoint(p);
@ -148,18 +175,26 @@ public class RadioLogger extends VisPlugin {
if (realColumnIndex == COLUMN_TIME) { if (realColumnIndex == COLUMN_TIME) {
return return
"<html>" + "<html>" +
"Start time: " + conn.startTime + "Start time (us): " + conn.startTime +
"<br>" + "<br>" +
"End time: " + conn.endTime + "End time (us): " + conn.endTime +
"<br><br>" + "<br><br>" +
"Duration: " + (conn.endTime - conn.startTime) + "Duration (us): " + (conn.endTime - conn.startTime) +
"</html>"; "</html>";
} else if (realColumnIndex == COLUMN_FROM) { } else if (realColumnIndex == COLUMN_FROM) {
return conn.connection.getSource().getMote().toString(); return conn.connection.getSource().getMote().toString();
} else if (realColumnIndex == COLUMN_TO) { } else if (realColumnIndex == COLUMN_TO) {
Radio[] dests = conn.connection.getDestinations();
if (dests.length == 0) {
return "No destinations";
}
StringBuilder tip = new StringBuilder(); StringBuilder tip = new StringBuilder();
tip.append("<html>"); tip.append("<html>");
Radio[] dests = conn.connection.getDestinations(); if (dests.length == 1) {
tip.append("One destination:<br>");
} else {
tip.append(dests.length).append(" destinations:<br>");
}
for (Radio radio: dests) { for (Radio radio: dests) {
tip.append(radio.getMote()).append("<br>"); tip.append(radio.getMote()).append("<br>");
} }
@ -177,38 +212,73 @@ public class RadioLogger extends VisPlugin {
// Set data column width greedy // Set data column width greedy
dataTable.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN); dataTable.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN);
dataTable.getColumnModel().getColumn(COLUMN_TIME).setPreferredWidth(130);
// dataTable.getColumnModel().getColumn(COLUMN_TIME).setResizable(false);
dataTable.getColumnModel().getColumn(COLUMN_FROM).setPreferredWidth(90);
dataTable.getColumnModel().getColumn(COLUMN_TO).setPreferredWidth(150);
dataTable.getColumnModel().getColumn(COLUMN_DATA).setPreferredWidth(1500);
dataTable.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
dataTable.setFont(new Font("Monospaced", Font.PLAIN, 12)); dataTable.setFont(new Font("Monospaced", Font.PLAIN, 12));
simulation.getRadioMedium().addRadioMediumObserver(radioMediumObserver = new Observer() { JPopupMenu popupMenu = new JPopupMenu();
public void update(Observable obs, Object obj) { JMenuItem clearItem = new JMenuItem("Clear");
RadioConnection[] conns = radioMedium.getLastTickConnections(); clearItem.addActionListener(new ActionListener() {
if (conns == null) {
return; public void actionPerformed(ActionEvent e) {
int size = connections.size();
if (size > 0) {
connections.clear();
model.fireTableRowsDeleted(0, size - 1);
setTitle("Radio Logger: " + dataTable.getRowCount() + " packets");
}
} }
for (RadioConnection conn : conns) {
RadioConnectionLog loggedConn = new RadioConnectionLog();
loggedConn.startTime = conn.getStartTime();
loggedConn.endTime = simulation.getSimulationTime();
loggedConn.connection = conn;
loggedConn.packet = conn.getSource().getLastPacketTransmitted();
connections.add(loggedConn);
}
model.fireTableRowsInserted(connections.size() - conns.length + 1, connections.size());
setTitle("Radio Logger: " + connections.size() + " packets");
}
}); });
popupMenu.add(clearItem);
dataTable.setComponentPopupMenu(popupMenu);
add(new JScrollPane(dataTable)); add(new JScrollPane(dataTable));
TableColumnAdjuster adjuster = new TableColumnAdjuster(dataTable);
adjuster.setDynamicAdjustment(true);
adjuster.packColumns();
radioMedium.addRadioMediumObserver(radioMediumObserver = new Observer() {
public void update(Observable obs, Object obj) {
RadioConnection[] conns = radioMedium.getLastTickConnections();
if (conns == null || conns.length == 0) {
return;
}
final RadioConnectionLog[] logged = new RadioConnectionLog[conns.length];
for (int i = 0, n = logged.length; i < n; i++) {
RadioConnectionLog loggedConn = new RadioConnectionLog();
loggedConn.startTime = conns[i].getStartTime();
loggedConn.endTime = simulation.getSimulationTime();
loggedConn.connection = conns[i];
loggedConn.packet = conns[i].getSource().getLastPacketTransmitted();
logged[i] = loggedConn;
}
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
int lastSize = connections.size();
// Check if the last row is visible
boolean isVisible = false;
int rowCount = dataTable.getRowCount();
if (rowCount > 0) {
Rectangle lastRow = dataTable.getCellRect(rowCount - 1, 0, true);
Rectangle visible = dataTable.getVisibleRect();
isVisible = visible.y <= lastRow.y && visible.y + visible.height >= lastRow.y + lastRow.height;
}
for(RadioConnectionLog log: logged) {
connections.add(log);
}
if (connections.size() > lastSize) {
model.fireTableRowsInserted(lastSize, connections.size() - 1);
}
if (isVisible) {
dataTable.scrollRectToVisible(dataTable.getCellRect(dataTable.getRowCount() - 1, 0, true));
}
setTitle("Radio Logger: " + dataTable.getRowCount() + " packets");
}
});
}
});
setSize(500, 300); setSize(500, 300);
try { try {
setSelected(true); setSelected(true);