From b2c0df08af999cefbc5699de3473e9a3f6050579 Mon Sep 17 00:00:00 2001 From: nifi Date: Fri, 12 Jun 2009 14:12:59 +0000 Subject: [PATCH] radio logger update: autosizing columns + support for new microsecond resolution + some minor fixes --- .../cooja/dialogs/TableColumnAdjuster.java | 340 ++++++++++++++++++ .../se/sics/cooja/plugins/RadioLogger.java | 150 +++++--- 2 files changed, 450 insertions(+), 40 deletions(-) create mode 100644 tools/cooja/java/se/sics/cooja/dialogs/TableColumnAdjuster.java diff --git a/tools/cooja/java/se/sics/cooja/dialogs/TableColumnAdjuster.java b/tools/cooja/java/se/sics/cooja/dialogs/TableColumnAdjuster.java new file mode 100644 index 000000000..adf7c4825 --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/dialogs/TableColumnAdjuster.java @@ -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(); + } + } + } + +} diff --git a/tools/cooja/java/se/sics/cooja/plugins/RadioLogger.java b/tools/cooja/java/se/sics/cooja/plugins/RadioLogger.java index c3185f6d8..16308a8dd 100644 --- a/tools/cooja/java/se/sics/cooja/plugins/RadioLogger.java +++ b/tools/cooja/java/se/sics/cooja/plugins/RadioLogger.java @@ -26,21 +26,36 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * 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; 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.util.*; -import javax.swing.*; +import java.util.ArrayList; +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.TableCellEditor; -import javax.swing.table.TableColumn; -import org.apache.log4j.Logger; import org.jdom.Element; - -import se.sics.cooja.*; +import se.sics.cooja.ClassDescription; +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.Radio; import se.sics.cooja.util.StringUtils; @@ -54,7 +69,8 @@ import se.sics.cooja.util.StringUtils; @ClassDescription("Radio Logger") @PluginType(PluginType.SIM_PLUGIN) 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_FROM = 1; @@ -68,9 +84,9 @@ public class RadioLogger extends VisPlugin { "Data" }; - private Simulation simulation; + private final Simulation simulation; + private final JTable dataTable; private ArrayList connections = new ArrayList(); - private JTable dataTable = null; private RadioMedium radioMedium; private Observer radioMediumObserver; @@ -80,6 +96,9 @@ public class RadioLogger extends VisPlugin { radioMedium = simulation.getRadioMedium(); final AbstractTableModel model = new AbstractTableModel() { + + private static final long serialVersionUID = 1692207305977527004L; + public String getColumnName(int col) { return COLUMN_NAMES[col]; } @@ -95,15 +114,21 @@ public class RadioLogger extends VisPlugin { public Object getValueAt(int row, int col) { RadioConnectionLog conn = connections.get(row); if (col == COLUMN_TIME) { - return conn.startTime; + return Long.toString(conn.startTime / Simulation.MILLISECOND); } else if (col == COLUMN_FROM) { return getMoteID(conn.connection.getSource().getMote()); } else if (col == COLUMN_TO) { Radio[] dests = conn.connection.getDestinations(); + if (dests.length == 0) { + return "-"; + } if (dests.length == 1) { 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) { if (conn.data == null) { prepareDataString(connections.get(row)); @@ -131,13 +156,15 @@ public class RadioLogger extends VisPlugin { return false; } - public Class getColumnClass(int c) { + public Class getColumnClass(int c) { return getValueAt(0, c).getClass(); } }; dataTable = new JTable(model) { + private static final long serialVersionUID = -2199726885069809686L; + public String getToolTipText(MouseEvent e) { java.awt.Point p = e.getPoint(); int rowIndex = rowAtPoint(p); @@ -148,18 +175,26 @@ public class RadioLogger extends VisPlugin { if (realColumnIndex == COLUMN_TIME) { return "" + - "Start time: " + conn.startTime + + "Start time (us): " + conn.startTime + "
" + - "End time: " + conn.endTime + + "End time (us): " + conn.endTime + "

" + - "Duration: " + (conn.endTime - conn.startTime) + + "Duration (us): " + (conn.endTime - conn.startTime) + ""; } else if (realColumnIndex == COLUMN_FROM) { return conn.connection.getSource().getMote().toString(); } else if (realColumnIndex == COLUMN_TO) { + Radio[] dests = conn.connection.getDestinations(); + if (dests.length == 0) { + return "No destinations"; + } StringBuilder tip = new StringBuilder(); tip.append(""); - Radio[] dests = conn.connection.getDestinations(); + if (dests.length == 1) { + tip.append("One destination:
"); + } else { + tip.append(dests.length).append(" destinations:
"); + } for (Radio radio: dests) { tip.append(radio.getMote()).append("
"); } @@ -177,38 +212,73 @@ public class RadioLogger extends VisPlugin { // Set data column width greedy 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)); - simulation.getRadioMedium().addRadioMediumObserver(radioMediumObserver = new Observer() { - public void update(Observable obs, Object obj) { - RadioConnection[] conns = radioMedium.getLastTickConnections(); - if (conns == null) { - return; - } + JPopupMenu popupMenu = new JPopupMenu(); + JMenuItem clearItem = new JMenuItem("Clear"); + clearItem.addActionListener(new ActionListener() { - 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); + 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"); } - 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)); + 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); try { setSelected(true);