/* -*- tab-width: 4 -*- * * Electric(tm) VLSI Design System * * File: WaveformWindow.java * * Copyright (c) 2004 Sun Microsystems and Static Free Software * * Electric(tm) is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * Electric(tm) is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Electric(tm); see the file COPYING. If not, write to * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, * Boston, Mass 02111-1307, USA. */ package com.sun.electric.tool.user.waveform; import com.sun.electric.Main; import com.sun.electric.database.geometry.PolyBase; import com.sun.electric.database.hierarchy.Cell; import com.sun.electric.database.hierarchy.Export; import com.sun.electric.database.hierarchy.HierarchyEnumerator; import com.sun.electric.database.hierarchy.Nodable; import com.sun.electric.database.id.CellId; import com.sun.electric.database.network.Global; import com.sun.electric.database.network.Netlist; import com.sun.electric.database.network.Network; import com.sun.electric.database.prototype.PortProto; import com.sun.electric.database.text.Pref; import com.sun.electric.database.text.TextUtils; import com.sun.electric.database.topology.ArcInst; import com.sun.electric.database.topology.Connection; import com.sun.electric.database.topology.Geometric; import com.sun.electric.database.topology.NodeInst; import com.sun.electric.database.variable.CodeExpression; import com.sun.electric.database.variable.TextDescriptor; import com.sun.electric.database.variable.VarContext; import com.sun.electric.technology.technologies.Generic; import com.sun.electric.tool.Job; import com.sun.electric.tool.io.FileType; import com.sun.electric.tool.io.input.EpicAnalysis; import com.sun.electric.tool.io.input.NewEpicAnalysis; import com.sun.electric.tool.io.input.Simulate; import com.sun.electric.tool.io.output.PNG; import com.sun.electric.tool.io.output.Spice; import com.sun.electric.tool.ncc.NccCrossProbing; import com.sun.electric.tool.ncc.result.NccResult; import com.sun.electric.tool.simulation.AnalogAnalysis; import com.sun.electric.tool.simulation.AnalogSignal; import com.sun.electric.tool.simulation.Analysis; import com.sun.electric.tool.simulation.DigitalSignal; import com.sun.electric.tool.simulation.Signal; import com.sun.electric.tool.simulation.Simulation; import com.sun.electric.tool.simulation.Stimuli; import com.sun.electric.tool.simulation.Waveform; import com.sun.electric.tool.user.ActivityLogger; import com.sun.electric.tool.user.HighlightListener; import com.sun.electric.tool.user.Highlighter; import com.sun.electric.tool.user.Resources; import com.sun.electric.tool.user.User; import com.sun.electric.tool.user.dialogs.OpenFile; import com.sun.electric.tool.user.ui.EditWindow; import com.sun.electric.tool.user.ui.ElectricPrinter; import com.sun.electric.tool.user.ui.ExplorerTree; import com.sun.electric.tool.user.ui.ExplorerTreeModel; import com.sun.electric.tool.user.ui.WindowContent; import com.sun.electric.tool.user.ui.WindowFrame; import java.awt.Color; import java.awt.Component; import java.awt.Cursor; import java.awt.Dimension; import java.awt.Font; import java.awt.Graphics2D; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.Point; import java.awt.Rectangle; import java.awt.datatransfer.DataFlavor; import java.awt.dnd.DnDConstants; import java.awt.dnd.DropTarget; import java.awt.dnd.DropTargetDragEvent; import java.awt.dnd.DropTargetDropEvent; import java.awt.dnd.DropTargetEvent; import java.awt.dnd.DropTargetListener; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ComponentEvent; import java.awt.event.ComponentListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.awt.font.FontRenderContext; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.awt.print.PageFormat; import java.awt.print.PrinterJob; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.*; import java.net.URL; import java.net.URLConnection; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Stack; import java.util.prefs.Preferences; import javax.print.attribute.standard.ColorSupported; import javax.swing.AbstractCellEditor; import javax.swing.BoxLayout; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JSplitPane; import javax.swing.JTable; import javax.swing.SwingUtilities; import javax.swing.Timer; import javax.swing.event.TableModelEvent; import javax.swing.table.DefaultTableModel; import javax.swing.table.TableCellEditor; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableColumn; import javax.swing.table.TableColumnModel; import javax.swing.table.TableModel; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.MutableTreeNode; import javax.swing.tree.TreeNode; import javax.swing.tree.TreePath; import com.sun.electric.tool.io.*; import java.io.*; /** * This class defines the a screenful of Panels that make up a waveform display. */ public class WaveformWindow implements WindowContent, PropertyChangeListener { /** minimum height of an Analog panel */ private static final int MINANALOGPANELSIZE = 30; /** minimum height of a Digital panel */ private static final int MINDIGITALPANELSIZE = 20; public static final boolean USETABLES = true; /** the window that this lives in */ private WindowFrame wf; /** the cell being simulated */ private Stimuli sd; /** the signal on all X axes (null for time) */ private Signal xAxisSignalAll; /** the top-level panel of the waveform window. */ private JPanel overall; /** the "lock X axis" button. */ private JButton xAxisLockButton; /** the "refresh" button. */ private JButton refresh; /** the "show points" button. */ private JButton showPoints; /** the "grow panel" button for widening. */ private JButton growPanel; /** the "shrink panel" button for narrowing. */ private JButton shrinkPanel; /** the list of panels. */ private JComboBox signalNameList; /** mapping from analysis to entries in "SIGNALS" tree*/private Map<Analysis,TreePath> treePathFromAnalysis = new HashMap<Analysis,TreePath>(); /** true if rebuilding the list of panels */ private boolean rebuildingSignalNameList = false; /** the main scroll of all panels. */ private JScrollPane scrollAll; /** the split between signal names and traces. */ private JSplitPane split; /** left panel: the signal names */ private JPanel left; /** right panel: the signal traces */ private JPanel right; /** the table with panels and labels */ private WaveTable table; /** the table editor for left and right halves */ private WaveCellEditor leftSideColumn, rightSideColumn; /** the table with panels and labels */ private TableModel tableModel; /** labels for the text at the top */ private JLabel mainPos, extPos, delta, diskLabel; /** buttons for centering the X-axis cursors. */ private JButton centerMain, centerExt; /** a list of panels in this window */ private List<Panel> wavePanels; /** a list of sweep signals in this window */ private List<SweepSignal> sweepSignals; /** the main horizontal ruler for all panels. */ private HorizRuler mainHorizRulerPanel; /** true if the main horizontal ruler is logarithmic */ private boolean mainHorizRulerPanelLogarithmic; /** the VCR timer, when running */ private Timer vcrTimer; /** true to run VCR backwards */ private boolean vcrPlayingBackwards = false; /** time the VCR last advanced */ private long vcrLastAdvance; /** speed of the VCR (in screen pixels) */ private int vcrAdvanceSpeed = 3; /** current "main" x-axis cursor */ private double mainXPosition; /** current "extension" x-axis cursor */ private double extXPosition; /** default range along horozintal axis */ private double minXPosition, maxXPosition; /** true if the X axis is the same in each panel */ private boolean xAxisLocked; /** the sweep signal that is highlighted */ private int highlightedSweep = -1; /** display mode (0=lines, 1=lines&points, 2=points) */ private int linePointMode; /** true to show a grid (analog only) */ private boolean showGrid; /** the actual screen coordinates of the waveform */ private int screenLowX, screenHighX; /** a listener for redraw requests */ private WaveComponentListener wcl; /** The highlighter for this waveform window. */ private Highlighter highlighter; /** 0: color display, 1: color printing, 2: B&W printing */ private int nowPrinting; /** lock for crossprobing */ private static boolean freezeWaveformHighlighting = false; /** lock for crossprobing */ private static boolean freezeEditWindowHighlighting = false; /** The global listener for all waveform windows. */ private static WaveformWindowHighlightListener waveHighlighter = new WaveformWindowHighlightListener(); /** Font for all text in the window */ private static Font waveWindowFont; /** For rendering text */ private static FontRenderContext waveWindowFRC; /** The colors of signal lines */ private static Color offStrengthColor, nodeStrengthColor, gateStrengthColor, powerStrengthColor; /** The background color */ private static Color backgroundColor; /** drop target (for drag and drop) */ public static WaveFormDropTarget waveformDropTarget = new WaveFormDropTarget(); private static final ImageIcon iconAddPanel = Resources.getResource(WaveformWindow.class, "ButtonSimAddPanel.gif"); private static final ImageIcon iconLockXAxes = Resources.getResource(WaveformWindow.class, "ButtonSimLockTime.gif"); private static final ImageIcon iconUnLockXAxes = Resources.getResource(WaveformWindow.class, "ButtonSimUnLockTime.gif"); private static final ImageIcon iconRefresh = Resources.getResource(WaveformWindow.class, "ButtonSimRefresh.gif"); private static final ImageIcon iconLineOnPointOn = Resources.getResource(WaveformWindow.class, "ButtonSimLineOnPointOn.gif"); private static final ImageIcon iconLineOnPointOff = Resources.getResource(WaveformWindow.class, "ButtonSimLineOnPointOff.gif"); private static final ImageIcon iconLineOffPointOn = Resources.getResource(WaveformWindow.class, "ButtonSimLineOffPointOn.gif"); private static final ImageIcon iconToggleGrid = Resources.getResource(WaveformWindow.class, "ButtonSimGrid.gif"); private static final ImageIcon iconGrowPanel = Resources.getResource(WaveformWindow.class, "ButtonSimGrow.gif"); private static final ImageIcon iconShrinkPanel = Resources.getResource(WaveformWindow.class, "ButtonSimShrink.gif"); private static final ImageIcon iconVCRRewind = Resources.getResource(WaveformWindow.class, "ButtonVCRRewind.gif"); private static final ImageIcon iconVCRPlayBackward = Resources.getResource(WaveformWindow.class, "ButtonVCRPlayBackward.gif"); private static final ImageIcon iconVCRStop = Resources.getResource(WaveformWindow.class, "ButtonVCRStop.gif"); private static final ImageIcon iconVCRPlay = Resources.getResource(WaveformWindow.class, "ButtonVCRPlay.gif"); private static final ImageIcon iconVCRToEnd = Resources.getResource(WaveformWindow.class, "ButtonVCRToEnd.gif"); private static final ImageIcon iconVCRFaster = Resources.getResource(WaveformWindow.class, "ButtonVCRFaster.gif"); private static final ImageIcon iconVCRSlower = Resources.getResource(WaveformWindow.class, "ButtonVCRSlower.gif"); private static final Cursor resizeRowCursor = Cursor.getPredefinedCursor(Cursor.N_RESIZE_CURSOR); private static final Cursor resizeColumnCursor = Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR); /** * Constructor creates a Waveform window in a given WindowFrame with given Stimuli data. * @param sd the Stimuli data to show in the window. * @param wf the WindowFrame in which to place the window. */ public WaveformWindow(Stimuli sd, WindowFrame wf) { // initialize the structure this.wf = wf; this.sd = sd; sd.setWaveformWindow(this); resetSweeps(); wavePanels = new ArrayList<Panel>(); xAxisLocked = true; linePointMode = 0; nowPrinting = 0; showGrid = false; xAxisSignalAll = null; mainHorizRulerPanelLogarithmic = false; // compute static fields used in graphics waveWindowFont = new Font(User.getDefaultFont(), Font.PLAIN, 12); waveWindowFRC = new FontRenderContext(null, false, false); offStrengthColor = new Color(User.getColor(User.ColorPrefType.WAVE_OFF_STRENGTH)); nodeStrengthColor = new Color(User.getColor(User.ColorPrefType.WAVE_NODE_STRENGTH)); gateStrengthColor = new Color(User.getColor(User.ColorPrefType.WAVE_GATE_STRENGTH)); powerStrengthColor = new Color(User.getColor(User.ColorPrefType.WAVE_POWER_STRENGTH)); highlighter = new Highlighter(Highlighter.SELECT_HIGHLIGHTER, wf); Highlighter.addHighlightListener(waveHighlighter); // the total panel in the waveform window overall = new OnePanel(null, this); overall.setLayout(new GridBagLayout()); wcl = new WaveComponentListener(overall); overall.addComponentListener(wcl); // a drop target for the overall waveform window new DropTarget(overall, DnDConstants.ACTION_LINK, waveformDropTarget, true); // the table that holds the waveform panels if (USETABLES) { tableModel = new WaveTableModel(); table = new WaveTable(tableModel, this); new TableMouseListener(table); table.getTableHeader().setPreferredSize(new Dimension(1, 1)); TableColumn column1 = table.getColumnModel().getColumn(0); column1.setPreferredWidth(100); TableColumn column2 = table.getColumnModel().getColumn(1); column2.setPreferredWidth(500); leftSideColumn = new WaveCellEditor(table, 0); rightSideColumn = new WaveCellEditor(table, 1); int height = User.getWaveformDigitalPanelHeight(); if (sd.isAnalog()) height = User.getWaveformAnalogPanelHeight(); table.setRowHeight(height); scrollAll = new JScrollPane(table); // a drop target for the table new DropTarget(table, DnDConstants.ACTION_LINK, WaveformWindow.waveformDropTarget, true); } else { // the left half has signal names; the right half has waveforms left = new JPanel(); left.setLayout(new BoxLayout(left, BoxLayout.Y_AXIS)); right = new JPanel(); right.setLayout(new BoxLayout(right, BoxLayout.Y_AXIS)); // the main part of the waveform window: a split-pane between names and waveforms, put into a scrollpane split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, left, right); split.setResizeWeight(0.1); split.addPropertyChangeListener(this); scrollAll = new JScrollPane(split); } GridBagConstraints gbc = new GridBagConstraints(); gbc.gridx = 0; gbc.gridy = 2; gbc.gridwidth = 11; gbc.gridheight = 1; gbc.weightx = 0; gbc.weighty = 1; gbc.anchor = GridBagConstraints.CENTER; gbc.fill = GridBagConstraints.BOTH; overall.add(scrollAll, gbc); if (sd.isAnalog()) { // the top part of the waveform window: status information JButton addPanel = new JButton(iconAddPanel); addPanel.setBorderPainted(false); addPanel.setDefaultCapable(false); addPanel.setToolTipText("Create new waveform panel"); Dimension minWid = new Dimension(iconAddPanel.getIconWidth()+4, iconAddPanel.getIconHeight()+4); addPanel.setMinimumSize(minWid); addPanel.setPreferredSize(minWid); gbc = new GridBagConstraints(); gbc.gridx = 0; gbc.gridy = 0; gbc.anchor = GridBagConstraints.CENTER; overall.add(addPanel, gbc); addPanel.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { makeNewPanel(null); } }); showPoints = new JButton(iconLineOnPointOff); showPoints.setBorderPainted(false); showPoints.setDefaultCapable(false); showPoints.setToolTipText("Toggle display of vertex points and lines"); minWid = new Dimension(iconLineOnPointOff.getIconWidth()+4, iconLineOnPointOff.getIconHeight()+4); showPoints.setMinimumSize(minWid); showPoints.setPreferredSize(minWid); gbc = new GridBagConstraints(); gbc.gridx = 1; gbc.gridy = 0; gbc.anchor = GridBagConstraints.CENTER; overall.add(showPoints, gbc); showPoints.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { toggleShowPoints(); } }); JButton toggleGrid = new JButton(iconToggleGrid); toggleGrid.setBorderPainted(false); toggleGrid.setDefaultCapable(false); toggleGrid.setToolTipText("Toggle display of a grid"); minWid = new Dimension(iconToggleGrid.getIconWidth()+4, iconToggleGrid.getIconHeight()+4); toggleGrid.setMinimumSize(minWid); toggleGrid.setPreferredSize(minWid); gbc = new GridBagConstraints(); gbc.gridx = 0; gbc.gridy = 1; gbc.anchor = GridBagConstraints.CENTER; overall.add(toggleGrid, gbc); toggleGrid.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { toggleGridPoints(); } }); } refresh = new JButton(iconRefresh); refresh.setBorderPainted(false); refresh.setDefaultCapable(false); refresh.setToolTipText("Reread stimuli data file and update waveforms"); Dimension minWid = new Dimension(iconRefresh.getIconWidth()+4, iconRefresh.getIconHeight()+4); refresh.setMinimumSize(minWid); refresh.setPreferredSize(minWid); gbc = new GridBagConstraints(); gbc.gridx = 1; gbc.gridy = 1; gbc.anchor = GridBagConstraints.CENTER; overall.add(refresh, gbc); refresh.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { refreshData(); } }); xAxisLockButton = new JButton(iconLockXAxes); xAxisLockButton.setBorderPainted(false); xAxisLockButton.setDefaultCapable(false); xAxisLockButton.setToolTipText("Lock all panels horizontally"); minWid = new Dimension(iconLockXAxes.getIconWidth()+4, iconLockXAxes.getIconHeight()+4); xAxisLockButton.setMinimumSize(minWid); xAxisLockButton.setPreferredSize(minWid); gbc = new GridBagConstraints(); gbc.gridx = 2; gbc.gridy = 0; gbc.anchor = GridBagConstraints.CENTER; overall.add(xAxisLockButton, gbc); xAxisLockButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { togglePanelXAxisLock(); } }); signalNameList = new JComboBox(); signalNameList.setToolTipText("Show or hide waveform panels"); signalNameList.setLightWeightPopupEnabled(false); gbc = new GridBagConstraints(); gbc.gridx = 3; gbc.gridy = 0; gbc.gridwidth = 5; gbc.gridheight = 1; gbc.anchor = GridBagConstraints.CENTER; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.insets = new Insets(0, 0, 0, 0); overall.add(signalNameList, gbc); signalNameList.addItem("Panel 1"); signalNameList.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { togglePanelName(); } }); growPanel = new JButton(iconGrowPanel); growPanel.setBorderPainted(false); growPanel.setDefaultCapable(false); growPanel.setToolTipText("Increase minimum panel height"); minWid = new Dimension(iconGrowPanel.getIconWidth()+4, iconGrowPanel.getIconHeight()+4); growPanel.setMinimumSize(minWid); growPanel.setPreferredSize(minWid); gbc = new GridBagConstraints(); gbc.gridx = 8; gbc.gridy = 0; gbc.anchor = GridBagConstraints.CENTER; overall.add(growPanel, gbc); growPanel.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { growPanels(1.25); } }); shrinkPanel = new JButton(iconShrinkPanel); shrinkPanel.setBorderPainted(false); shrinkPanel.setDefaultCapable(false); shrinkPanel.setToolTipText("Decrease minimum panel height"); minWid = new Dimension(iconShrinkPanel.getIconWidth()+4, iconShrinkPanel.getIconHeight()+4); shrinkPanel.setMinimumSize(minWid); shrinkPanel.setPreferredSize(minWid); gbc = new GridBagConstraints(); gbc.gridx = 9; gbc.gridy = 0; gbc.anchor = GridBagConstraints.CENTER; overall.add(shrinkPanel, gbc); shrinkPanel.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { growPanels(0.8); } }); // the X axis section that shows the value of the main and extension cursors JPanel xAxisLabelPanel = new JPanel(); xAxisLabelPanel.setLayout(new GridBagLayout()); gbc = new GridBagConstraints(); gbc.gridx = 10; gbc.gridy = 0; gbc.weightx = 1; gbc.weighty = 0; gbc.anchor = GridBagConstraints.CENTER; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.insets = new Insets(0, 4, 0, 4); overall.add(xAxisLabelPanel, gbc); mainPos = new JLabel("Main:", JLabel.RIGHT); mainPos.setToolTipText("The main (dashed) X axis cursor"); gbc = new GridBagConstraints(); gbc.gridx = 0; gbc.gridy = 0; gbc.weightx = 0.2; gbc.weighty = 0; gbc.anchor = GridBagConstraints.CENTER; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.insets = new Insets(0, 0, 0, 0); xAxisLabelPanel.add(mainPos, gbc); centerMain = new JButton("Center"); centerMain.setToolTipText("Center the main (dashed) X axis cursor"); gbc = new GridBagConstraints(); gbc.gridx = 1; gbc.gridy = 0; gbc.anchor = GridBagConstraints.WEST; gbc.insets = new Insets(2, 4, 2, 0); xAxisLabelPanel.add(centerMain, gbc); centerMain.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { centerCursor(true); } }); extPos = new JLabel("Ext:", JLabel.RIGHT); extPos.setToolTipText("The extension (dotted) X axis cursor"); gbc = new GridBagConstraints(); gbc.gridx = 2; gbc.gridy = 0; gbc.weightx = 0.2; gbc.weighty = 0; gbc.anchor = GridBagConstraints.CENTER; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.insets = new Insets(0, 0, 0, 0); xAxisLabelPanel.add(extPos, gbc); centerExt = new JButton("Center"); centerExt.setToolTipText("Center the extension (dotted) X axis cursor"); gbc = new GridBagConstraints(); gbc.gridx = 3; gbc.gridy = 0; gbc.anchor = GridBagConstraints.WEST; gbc.insets = new Insets(2, 4, 2, 0); xAxisLabelPanel.add(centerExt, gbc); centerExt.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { centerCursor(false); } }); delta = new JLabel("Delta:", JLabel.CENTER); delta.setToolTipText("X distance between cursors"); gbc = new GridBagConstraints(); gbc.gridx = 4; gbc.gridy = 0; gbc.weightx = 0.2; gbc.weighty = 0; gbc.anchor = GridBagConstraints.CENTER; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.insets = new Insets(0, 0, 0, 0); xAxisLabelPanel.add(delta, gbc); // the name of the waveform disk file if (sd.getFileURL() != null) { String fileName = TextUtils.getFileNameWithoutExtension(sd.getFileURL()); String ext = TextUtils.getExtension(sd.getFileURL()); if (ext.length() > 0) fileName += "." + ext; diskLabel = new JLabel("File: " + fileName, JLabel.CENTER); diskLabel.setToolTipText("The disk file that is being displayed"); gbc = new GridBagConstraints(); gbc.gridx = 5; gbc.gridy = 0; gbc.weightx = 0.4; gbc.weighty = 0; gbc.anchor = GridBagConstraints.CENTER; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.insets = new Insets(0, 10, 0, 0); xAxisLabelPanel.add(diskLabel, gbc); } // add VCR controls JButton vcrButtonRewind = new JButton(iconVCRRewind); vcrButtonRewind.setBorderPainted(false); vcrButtonRewind.setDefaultCapable(false); vcrButtonRewind.setToolTipText("Rewind main X axis cursor to start"); minWid = new Dimension(iconVCRRewind.getIconWidth()+4, iconVCRRewind.getIconHeight()+4); vcrButtonRewind.setMinimumSize(minWid); vcrButtonRewind.setPreferredSize(minWid); gbc = new GridBagConstraints(); gbc.gridx = 3; gbc.gridy = 1; gbc.anchor = GridBagConstraints.CENTER; overall.add(vcrButtonRewind, gbc); vcrButtonRewind.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { vcrClickRewind(); } }); JButton vcrButtonPlayBackwards = new JButton(iconVCRPlayBackward); vcrButtonPlayBackwards.setBorderPainted(false); vcrButtonPlayBackwards.setDefaultCapable(false); vcrButtonPlayBackwards.setToolTipText("Play main X axis cursor backwards"); minWid = new Dimension(iconVCRPlayBackward.getIconWidth()+4, iconVCRPlayBackward.getIconHeight()+4); vcrButtonPlayBackwards.setMinimumSize(minWid); vcrButtonPlayBackwards.setPreferredSize(minWid); gbc = new GridBagConstraints(); gbc.gridx = 4; gbc.gridy = 1; gbc.anchor = GridBagConstraints.CENTER; overall.add(vcrButtonPlayBackwards, gbc); vcrButtonPlayBackwards.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { vcrClickPlayBackwards(); } }); JButton vcrButtonStop = new JButton(iconVCRStop); vcrButtonStop.setBorderPainted(false); vcrButtonStop.setDefaultCapable(false); vcrButtonStop.setToolTipText("Stop moving main X axis cursor"); minWid = new Dimension(iconVCRStop.getIconWidth()+4, iconVCRStop.getIconHeight()+4); vcrButtonStop.setMinimumSize(minWid); vcrButtonStop.setPreferredSize(minWid); gbc = new GridBagConstraints(); gbc.gridx = 5; gbc.gridy = 1; gbc.anchor = GridBagConstraints.CENTER; overall.add(vcrButtonStop, gbc); vcrButtonStop.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { vcrClickStop(); } }); JButton vcrButtonPlay = new JButton(iconVCRPlay); vcrButtonPlay.setBorderPainted(false); vcrButtonPlay.setDefaultCapable(false); vcrButtonPlay.setToolTipText("Play main X axis cursor"); minWid = new Dimension(iconVCRPlay.getIconWidth()+4, iconVCRPlay.getIconHeight()+4); vcrButtonPlay.setMinimumSize(minWid); vcrButtonPlay.setPreferredSize(minWid); gbc = new GridBagConstraints(); gbc.gridx = 6; gbc.gridy = 1; gbc.anchor = GridBagConstraints.CENTER; overall.add(vcrButtonPlay, gbc); vcrButtonPlay.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { vcrClickPlay(); } }); JButton vcrButtonToEnd = new JButton(iconVCRToEnd); vcrButtonToEnd.setBorderPainted(false); vcrButtonToEnd.setDefaultCapable(false); vcrButtonToEnd.setToolTipText("Move main X axis cursor to end"); minWid = new Dimension(iconVCRToEnd.getIconWidth()+4, iconVCRToEnd.getIconHeight()+4); vcrButtonToEnd.setMinimumSize(minWid); vcrButtonToEnd.setPreferredSize(minWid); gbc = new GridBagConstraints(); gbc.gridx = 7; gbc.gridy = 1; gbc.anchor = GridBagConstraints.CENTER; overall.add(vcrButtonToEnd, gbc); vcrButtonToEnd.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { vcrClickToEnd(); } }); JButton vcrButtonFaster = new JButton(iconVCRFaster); vcrButtonFaster.setBorderPainted(false); vcrButtonFaster.setDefaultCapable(false); vcrButtonFaster.setToolTipText("Move main X axis cursor faster"); minWid = new Dimension(iconVCRFaster.getIconWidth()+4, iconVCRFaster.getIconHeight()+4); vcrButtonFaster.setMinimumSize(minWid); vcrButtonFaster.setPreferredSize(minWid); gbc = new GridBagConstraints(); gbc.gridx = 8; gbc.gridy = 1; gbc.anchor = GridBagConstraints.CENTER; overall.add(vcrButtonFaster, gbc); vcrButtonFaster.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { vcrClickFaster(); } }); JButton vcrButtonSlower = new JButton(iconVCRSlower); vcrButtonSlower.setBorderPainted(false); vcrButtonSlower.setDefaultCapable(false); vcrButtonSlower.setToolTipText("Move main X axis cursor slower"); minWid = new Dimension(iconVCRSlower.getIconWidth()+4, iconVCRSlower.getIconHeight()+4); vcrButtonSlower.setMinimumSize(minWid); vcrButtonSlower.setPreferredSize(minWid); gbc = new GridBagConstraints(); gbc.gridx = 9; gbc.gridy = 1; gbc.anchor = GridBagConstraints.CENTER; overall.add(vcrButtonSlower, gbc); vcrButtonSlower.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { vcrClickSlower(); } }); backgroundColor = vcrButtonSlower.getBackground(); // the single horizontal ruler panel (when the X axes are locked) if (xAxisLocked) { addMainHorizRulerPanel(); } // set bounds of the window from extent of the data Rectangle2D dataBounds = sd.getBounds(); if (dataBounds != null) { double lowTime = dataBounds.getMinX(); double highTime = dataBounds.getMaxX(); double timeRange = highTime - lowTime; setMainXPositionCursor(timeRange*0.2 + lowTime); setExtensionXPositionCursor(timeRange*0.8 + lowTime); setDefaultHorizontalRange(lowTime, highTime); } } private class WaveTable extends JTable { private WaveformWindow ww; WaveTable(TableModel model, WaveformWindow ww) { super(model); this.ww = ww; } } private class WaveTableModel extends DefaultTableModel { public int getColumnCount() { return 2; } public int getRowCount() { int numVisPanels = 0; for(Panel wp : wavePanels) { if (!wp.isHidden()) numVisPanels++; } return numVisPanels; } public Object getValueAt(int row, int col) { int rowNo = 0; for(Panel panel : wavePanels) { if (panel.isHidden()) continue; if (rowNo == row) { if (col == 0) return panel.getLeftHalf(); return panel.getRightHalf(); } rowNo++; } return null; } public void setValueAt(Object obj, int row, int col) {} public Class<?> getColumnClass(int column) { return JPanel.class; } } static class WaveCellEditor extends AbstractCellEditor implements TableCellRenderer, TableCellEditor { private Component lastOne; public WaveCellEditor(JTable table, int column) { super(); TableColumnModel columnModel = table.getColumnModel(); columnModel.getColumn(column).setCellRenderer(this); columnModel.getColumn(column).setCellEditor(this); } public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { return (Component)table.getValueAt(row, column); } public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { lastOne = (Component)table.getValueAt(row, column); return lastOne; } public Object getCellEditorValue() { return lastOne; } } private class TableMouseListener implements MouseListener, MouseMotionListener { private int mouseXOffset, mouseYOffset, resizingRow; private TableColumn resizingColumn; private JTable table; public TableMouseListener(JTable table) { this.table = table; table.addMouseListener(this); table.addMouseMotionListener(this); } private TableColumn getResizingColumn(Point p) { int column = table.columnAtPoint(p); if (column == -1) return null; int row = table.rowAtPoint(p); if (row == -1) return null; Rectangle r = table.getCellRect(row, column, true); r.grow(-3, 0); if (r.contains(p)) return null; int midPoint = r.x + r.width / 2; int columnIndex = (p.x < midPoint) ? column - 1 : column; if (columnIndex == -1) return null; return table.getTableHeader().getColumnModel().getColumn(columnIndex); } private int getResizingRow(Point p) { int row = table.rowAtPoint(p); if (row == -1) return -1; int col = table.columnAtPoint(p); if (col == -1) return -1; Rectangle r = table.getCellRect(row, col, true); r.grow(0, -3); if (r.contains(p)) return -1; int midPoint = r.y + r.height / 2; int rowIndex = (p.y < midPoint) ? row - 1 : row; return rowIndex; } public void mouseClicked(MouseEvent e) { // forward event to the panel contents forwardEventToPanel(e); } public void mouseEntered(MouseEvent e) { // forward event to the panel contents forwardEventToPanel(e); } public void mouseExited(MouseEvent e) { // forward event to the panel contents forwardEventToPanel(e); } public void mousePressed(MouseEvent e) { table.getTableHeader().setResizingColumn(null); // figure out if a divider was hit Point p = e.getPoint(); resizingColumn = getResizingColumn(p); if (resizingColumn != null) { resizingRow = -1; table.getTableHeader().setResizingColumn(resizingColumn); mouseXOffset = p.x - resizingColumn.getWidth(); return; } resizingRow = getResizingRow(p); if (resizingRow >= 0) { mouseYOffset = p.y - table.getRowHeight(resizingRow); return; } // forward event to the panel contents forwardEventToPanel(e); } public void mouseMoved(MouseEvent e) { if (getResizingColumn(e.getPoint()) != null) { table.setCursor(resizeColumnCursor); return; } if (getResizingRow(e.getPoint()) >= 0) { table.setCursor(resizeRowCursor); return; } table.setCursor(null); // forward event to the panel contents forwardEventToPanel(e); } public void mouseDragged(MouseEvent e) { if (resizingColumn != null) { resizingColumn.setWidth(e.getX() - mouseXOffset); if (mainHorizRulerPanel != null) mainHorizRulerPanel.repaint(); return; } if (resizingRow >= 0) { int newHeight = e.getY() - mouseYOffset; if (newHeight > 0) { table.setRowHeight(resizingRow, newHeight); return; } } // forward event to the panel contents forwardEventToPanel(e); } public void mouseReleased(MouseEvent e) { if (resizingColumn != null) { table.getTableHeader().setResizingColumn(null); return; } // forward event to the panel contents forwardEventToPanel(e); } public void forwardEventToPanel(MouseEvent e) { Point p = e.getPoint(); int row = table.rowAtPoint(p); int col = table.columnAtPoint(p); if (row < 0 || col < 0) return; JPanel panel = (JPanel)table.getValueAt(row, col); // so clicks are felt inside the panels MouseEvent panelEvent = SwingUtilities.convertMouseEvent(table, e, panel); panel.dispatchEvent(panelEvent); // This is necessary so that when a button is pressed and released // it gets rendered properly. Otherwise, the button may still appear // pressed down when it has been released. table.repaint(); } } public void stopEditing() { leftSideColumn.stopCellEditing(); rightSideColumn.stopCellEditing(); } public void reloadTable() { table.tableChanged(new TableModelEvent(tableModel)); } // ************************************* REQUIRED IMPLEMENTATION METHODS ************************************* /** * Method to get rid of this WaveformWindow. Called by WindowFrame when * that windowFrame gets closed. */ public void finished() { for(Panel wp : wavePanels) { wp.finished(); } overall.removeComponentListener(wcl); highlighter.delete(); if (sd != null) sd.finished(); } public void fullRepaint() { repaint(); } public void repaint() { for(Panel wp : wavePanels) { wp.repaintContents(); } if (mainHorizRulerPanel != null) mainHorizRulerPanel.repaint(); } public static void exportSimulationDataAsCSV(String file) { if (file == null) // cancel operation for example return; WindowFrame current = WindowFrame.getCurrentWindowFrame(); WindowContent content = current.getContent(); if (!(content instanceof WaveformWindow)) { System.out.println("Must select a Waveform window first"); return; } WaveformWindow ww = (WaveformWindow)content; try { PrintWriter pw = new PrintWriter(new OutputStreamWriter(new FileOutputStream(file))); for(Panel wp : ww.wavePanels) wp.dumpDataCSV(pw); pw.close(); System.out.println("Exported Waveform in CSV format file '" + file + "'"); } catch (Exception e) { throw new RuntimeException(e); } } public static void plotSimulationData(String file, String format) { WindowFrame current = WindowFrame.getCurrentWindowFrame(); WindowContent content = current.getContent(); if (!(content instanceof WaveformWindow)) { System.out.println("Must select a Waveform window first"); return; } WaveformWindow ww = (WaveformWindow)content; try { String commands = ""; double min = Double.MAX_VALUE; double max = Double.MIN_VALUE; //double sr = 1; int numPanels = 0; int maxWidth = 0; int height = 0; for(Panel wp : ww.wavePanels) { numPanels++; min = Math.min(min, wp.convertXScreenToData(0)); max = Math.max(max, wp.convertXScreenToData(wp.getSz().width)); maxWidth = Math.max(wp.getSz().width, maxWidth); height += wp.getSz().height; } System.out.println("plotting: maxWidth="+maxWidth+", height="+height); if (file!=null) { commands += "set terminal "+format+" size 9 , "+(((double)height*9)/maxWidth)+"; "; commands += "set output \""+file+"\"; "; } else { commands += "set terminal aqua size "+(maxWidth+100)+" "+(height+100)+"; "; } commands += "unset colorbox; "; commands += "set multiplot; "; commands += "set xrange [\""+min+"\":\""+max+"\"]; "; commands += "set format x \"\"; "; System.out.println("Running: gnuplot for "+numPanels+" panels"); ExecProcess ep = new ExecProcess(new String[] { "gnuplot" }, null); ep.redirectStdout(System.out); ep.redirectStderr(System.out); ep.start(); PrintWriter pw = new PrintWriter(new OutputStreamWriter(new ExecProcess.MultiOutputStream(new OutputStream[] { System.out, ep.getStdin() }))); pw.println(commands); pw.println("set label 1 \"Voltage (Volts)\" at 1,1 left"); pw.println(); int whichPanel = 0; double ypos = 1.0; for(Panel wp : ww.wavePanels) { if (whichPanel==numPanels-1) { pw.println("set xlabel \"time (in seconds)\"; "); pw.println("unset format; "); } pw.println("set size "+ (((double)wp.getSz().width)/maxWidth)+ ","+ (((double)wp.getSz().height)/height)+"; "); ypos -= (((double)wp.getSz().height)/height); pw.println("set origin 0, "+ypos); pw.flush(); pw.print("plot "); wp.dumpDataForGnuplot(pw, min, max, ","); pw.println(); pw.flush(); whichPanel++; } pw.println("unset multiplot; "); pw.println("quit;"); pw.flush(); pw.close(); System.out.println("gnuplot finished."); } catch (Exception e) { System.out.println("ERROR: Unable to run 'gnuplot': " + e); } } /** * Method to initialize for a new text search. * @param search the string to locate. * @param caseSensitive true to match only where the case is the same. * @param regExp true if the search string is a regular expression. * @param whatToSearch a collection of text types to consider. * @param codeRestr a restriction on types of Code to consider (null to consider all Code values). * @param unitRestr a restriction on types of Units to consider (null to consider all Unit values). * @param highlightedOnly true to search only in the highlighted area. */ public void initTextSearch(String search, boolean caseSensitive, boolean regExp, Set<TextUtils.WhatToSearch> whatToSearch, CodeExpression.Code codeRestr, TextDescriptor.Unit unitRestr, boolean highlightedOnly) { System.out.println("Text search not implemented for waveform windows"); } /** * Method to find the next occurrence of a string. * @param reverse true to find in the reverse direction. * @return true if something was found. */ public boolean findNextText(boolean reverse) { return false; } /** * Method to replace the text that was just selected with findNextText(). * @param replace the new text to replace. */ public void replaceText(String replace) {} /** * Method to replace all selected text. * @param replace the new text to replace everywhere. */ public void replaceAllText(String replace) {} /** * Method to export directly PNG file. * @param ep printable object. * @param filePath */ public void writeImage(ElectricPrinter ep, String filePath) { BufferedImage img = getPrintImage(ep); PNG.writeImage(img, filePath); } private int oldBackground, oldForeground; private boolean changedColors = false; /** * Method to intialize for printing. * @param ep the ElectricPrinter object. * @param pageFormat information about the print job. * @return true if no erros were found during initialization. */ public boolean initializePrinting(ElectricPrinter ep, PageFormat pageFormat) { oldForeground = User.getColor(User.ColorPrefType.WAVE_FOREGROUND); oldBackground = User.getColor(User.ColorPrefType.WAVE_BACKGROUND); User.setColor(User.ColorPrefType.WAVE_FOREGROUND, 0); User.setColor(User.ColorPrefType.WAVE_BACKGROUND, 0xFFFFFF); changedColors = true; PrinterJob pj = ep.getPrintJob(); if (pj == null) return false; // error ColorSupported cs = pj.getPrintService().getAttribute(ColorSupported.class); if (cs == null) return false; // error nowPrinting = 1; if (cs.getValue() == 0) nowPrinting = 2; Dimension oldSize = ep.getOldSize(); int pageWid = (int)pageFormat.getImageableWidth() * ep.getDesiredDPI() / 72; int pageHei = (int)pageFormat.getImageableHeight() * ep.getDesiredDPI() / 72; double scaleX = (double)pageWid / (double)oldSize.width; double scaleY = (double)pageHei / (double)oldSize.height; double scale = Math.min(scaleX, scaleY); pageWid = (int)(oldSize.width * scale); pageHei = (int)(oldSize.height * scale); overall.setSize(pageWid, pageHei); overall.validate(); redrawAllPanels(); overall.repaint(); return true; } /** * Method to print window using offscreen canvas. * @param ep printable object. * @return the image to print (null on error). */ public BufferedImage getPrintImage(ElectricPrinter ep) { BufferedImage bImage = ep.getBufferedImage(); Dimension sz = getPanel().getSize(); if (bImage == null) { bImage = (BufferedImage)(overall.createImage(sz.width, sz.height)); ep.setBufferedImage(bImage); } Graphics2D g2d = (Graphics2D)ep.getGraphics(); if (g2d == null) { g2d = bImage.createGraphics(); } // scale if there was an old image size Dimension szOld = ep.getOldSize(); if (szOld != null) { double scaleX = (double)sz.width / (double)szOld.width; double scaleY = (double)sz.height / (double)szOld.height; double gSX = (double)szOld.width / (double)szOld.height; double gSY = gSX * scaleY / scaleX; g2d.translate(ep.getPageFormat().getImageableX(), ep.getPageFormat().getImageableY()); g2d.scale(72.0 / ep.getDesiredDPI() / gSX, 72.0 / ep.getDesiredDPI() / gSY); } overall.paint(g2d); // if (mainHorizRulerPanel != null) // mainHorizRulerPanel.paint(g2d); if (changedColors) { User.setColor(User.ColorPrefType.WAVE_FOREGROUND, oldForeground); User.setColor(User.ColorPrefType.WAVE_BACKGROUND, oldBackground); changedColors = false; } nowPrinting = 0; return bImage; } /** * Method to pan along X or Y according to fixed amount of ticks * @param direction 0 for horizontal, 1 for vertical. * @param panningAmounts an array of distances, indexed by the current panning distance index. * @param ticks the number of steps to take (usually 1 or -1). */ public void panXOrY(int direction, double[] panningAmounts, int ticks) { // determine the panel extent double hRange = maxXPosition - minXPosition; double vRange = -1; double vRangeAny = -1; for(Panel wp : wavePanels) { vRangeAny = wp.getYAxisRange(); if (wp.isSelected()) { hRange = wp.getMaxXAxis() - wp.getMinXAxis(); vRange = wp.getYAxisRange(); break; } } if (vRange < 0) vRange = vRangeAny; double distance = ticks * panningAmounts[User.getPanningDistance()]; for(Panel wp : wavePanels) { if (direction == 0) { // pan horizontally if (!xAxisLocked && !wp.isSelected()) continue; double low = wp.getMinXAxis() - hRange * distance; double high = wp.getMaxXAxis() - hRange * distance; wp.setXAxisRange(low, high); } else { // pan vertically if (!wp.isSelected()) continue; double low = wp.getYAxisLowValue() - vRange * distance; double high = wp.getYAxisHighValue() - vRange * distance; wp.setYAxisRange(low, high); } wp.repaintWithRulers(); } } /** * Method to shift the panels so that the current cursor location becomes the center. */ public void centerCursor() { Panel pan = Panel.getCurrentPanel(); if (pan == null) return; int x = Panel.getCurrentXPos(); if (x < 0) return; Dimension dim = pan.getSize(); double startX = pan.convertXScreenToData(x); double endX = pan.convertXScreenToData(dim.width/2); double dXValue = endX - startX; for(Iterator<Panel> it = getPanels(); it.hasNext(); ) { Panel wp = it.next(); if (!isXAxisLocked() && wp != pan) continue; wp.setXAxisRange(wp.getMinXAxis() - dXValue, wp.getMaxXAxis() - dXValue); wp.repaintWithRulers(); } } /** * Method to set the window title. */ public void setWindowTitle() { if (wf == null) return; String title = ""; if (sd.getEngine() != null) title = "Simulation of "; else title = "Waveforms of "; if (sd != null && sd.getDataType() != null) { if (sd.getEngine() != null) title = sd.getDataType().getName() + " simulation of "; else title = sd.getDataType().getName() + " of "; } wf.setTitle(wf.composeTitle(sd.getCell(), title, 0)); } /** * Method to return the top-level JPanel for this WaveformWindow. * The actual WaveformWindow object is below the top level, surrounded by scroll bars and other display artifacts. * @return the top-level JPanel for this WaveformWindow. */ public JPanel getPanel() { return overall; } public void setCursor(Cursor cursor) { overall.setCursor(cursor); if (USETABLES) { table.setCursor(cursor); } else { split.setCursor(cursor); right.setCursor(cursor); } for (JPanel p : wavePanels) { p.setCursor(cursor); } } public void setCell(Cell cell, VarContext context, WindowFrame.DisplayAttributes displayAttributes) { sd.setCell(cell); setWindowTitle(); } /** * Method to return the cell that is shown in this window. * @return the cell that is shown in this window. */ public Cell getCell() { return sd.getCell(); } /** * Method to return the stimulus information associated with this WaveformWindow. * @return the stimulus information associated with this WaveformWindow. */ public Stimuli getSimData() { return sd; } public void bottomScrollChanged(int e) {} public void rightScrollChanged(int e) {} // ************************************* WINDOW CONTROL ************************************* /** * Method to return the associated schematics or layout window for this WaveformWindow. * @return the other window that is cross-linked to this. * Returns null if none can be found. */ private WindowFrame findSchematicsWindow() { Cell cell = getCell(); if (cell == null) return null; // look for the original cell to highlight it for(Iterator<WindowFrame> it = WindowFrame.getWindows(); it.hasNext(); ) { WindowFrame wf = it.next(); if (wf.getContent().getCell() != cell) continue; if (wf.getContent() instanceof EditWindow) return wf; } return null; } /** * Method to return the waveform window associated with a Cell. * There may be multiple such windows, and the most recently used is returned. * @param cell the Cell whose waveform window is desired. * @return the waveform window that is linked to a cell. * Returns null if none can be found. */ public static WaveformWindow findWaveformWindow(Cell cell) { // look for the original cell to highlight it WaveformWindow found = null; int bestClock = 0; for(Iterator<WindowFrame> it = WindowFrame.getWindows(); it.hasNext(); ) { WindowFrame wf = it.next(); if (wf.getContent().getCell() != cell) continue; if (wf.getContent() instanceof WaveformWindow) { WaveformWindow ww = (WaveformWindow)wf.getContent(); if (found == null || wf.getUsageClock() > bestClock) { found = ww; bestClock = wf.getUsageClock(); } } } return found; } /** * Method to return the WindowFrame in which this WaveformWindow lives. * @return the WindowFrame in which this WaveformWindow lives. */ public WindowFrame getWindowFrame() { return wf; } public int getScreenLowX() { return screenLowX; } public int getScreenHighX() { return screenHighX; } public void setScreenXSize(int lowX, int highX) { screenLowX = lowX; screenHighX = highX; } public JPanel getSignalNamesPanel() { return left; } public JPanel getSignalTracesPanel() { return right; } public JTable getWaveformTable() { return table; } public static Color getBackgroundColor() { return backgroundColor; } // ************************************* CONTROL OF PANELS IN THE WINDOW ************************************* /** * Method to create a new panel with an X range similar to others on the display. * @return the newly created Panel. */ public Panel makeNewPanel(Analysis analysis) { // determine panel's analysis type Analysis.AnalysisType analysisType; if (analysis != null) analysisType = analysis.getAnalysisType(); else { analysisType = Analysis.ANALYSIS_SIGNALS; if (sd.isAnalog()) { if (sd.getNumAnalyses() > 0) analysisType = sd.getAnalyses().next().getAnalysisType(); if (xAxisLocked && xAxisSignalAll != null) { AnalogSignal as = (AnalogSignal)xAxisSignalAll; analysisType = as.getAnalysis().getAnalysisType(); } } } int panelSize = User.getWaveformDigitalPanelHeight(); if (sd.isAnalog()) panelSize = User.getWaveformAnalogPanelHeight(); // determine the X and Y ranges Rectangle2D bounds = null; double leftEdge, rightEdge; Analysis an = sd.findAnalysis(analysisType); if (an != null) { bounds = an.getBounds(); leftEdge = an.getLeftEdge(); rightEdge = an.getRightEdge(); } else { bounds = sd.getBounds(); leftEdge = sd.getLeftEdge(); rightEdge = sd.getRightEdge(); } double lowValue = bounds.getMinY(); double highValue = bounds.getMaxY(); int vertAxisPos = -1; if (xAxisLocked && wavePanels.size() > 0) { Panel aPanel = wavePanels.get(0); leftEdge = aPanel.getMinXAxis(); rightEdge = aPanel.getMaxXAxis(); vertAxisPos = aPanel.getVertAxisPos(); } int [] rowHeights = null; if (USETABLES) { int rows = table.getRowCount(); rowHeights = new int[rows+1]; for(int i=0; i<rows; i++) rowHeights[i] = table.getRowHeight(i); rowHeights[rows] = panelSize; } // create the new panel Panel panel = new Panel(this, sd.isAnalog(), analysisType); // set the X and Y ranges panel.setXAxisRange(leftEdge, rightEdge); if (analysisType != null) panel.setYAxisRange(lowValue, highValue); if (vertAxisPos > 0) panel.setVertAxisPos(vertAxisPos); // show and return the panel panel.makeSelectedPanel(-1, -1); getPanel().validate(); if (getMainHorizRuler() != null) getMainHorizRuler().repaint(); if (USETABLES) { table.tableChanged(new TableModelEvent(tableModel)); for(int i=0; i<rowHeights.length; i++) table.setRowHeight(i, rowHeights[i]); } return panel; } /** * Method to return the number of Panels in this WaveformWindow. * @return the number of Panels in this WaveformWindow. */ public int getNumPanels() { return wavePanels.size(); } /** * Method to return a Panel in this window. * @param index the panel number to get. * @return a Panel in this window. */ public Panel getPanel(int index) { return wavePanels.get(index); } /** * Method to return the index of a Panel in this window. * @param panel the Panel to find. * @return the index of that Panel in this window. */ public int getPanelIndex(Panel panel) { return wavePanels.indexOf(panel); } /** * Method to add a Panel to this window. */ public void addPanel(Panel panel) { wavePanels.add(panel); } /** * Method to add a Panel to this window. */ public void addPanel(Panel panel, int index) { wavePanels.add(index, panel); } /** * Method to remove a Panel from this window. */ public void removePanel(Panel panel) { wavePanels.remove(panel); } /** * Method to return an Iterator over the Panel in this window. * @return an Iterator over the Panel in this window. */ public Iterator<Panel> getPanels() { return wavePanels.iterator(); } /** * Method to return the current printing mode. * @return 0: color display (default), 1: color printing, 2: B&W printing */ public int getPrintingMode() { return nowPrinting; } /** * Method to return a Panel, given its number. * @param panelNumber the number of the desired Panel. * @return the Panel with that number (null if not found). */ private Panel getPanelFromNumber(int panelNumber) { for(Panel wp : wavePanels) { if (wp.getPanelNumber() == panelNumber) return wp; } return null; } private void togglePanelName() { if (rebuildingSignalNameList) return; String panelName = (String)signalNameList.getSelectedItem(); int spacePos = panelName.indexOf(' '); if (spacePos >= 0) panelName = panelName.substring(spacePos+1); int index = TextUtils.atoi(panelName); // toggle its state for(Panel wp : wavePanels) { if (wp.getPanelNumber() == index) { if (wp.isHidden()) { showPanel(wp); } else { hidePanel(wp); } break; } } } public void validatePanel() { overall.validate(); } public void rebuildPanelList() { rebuildingSignalNameList = true; signalNameList.removeAllItems(); boolean hasSignals = false; for(Panel wp : wavePanels) { signalNameList.addItem("Panel " + Integer.toString(wp.getPanelNumber()) + (wp.isHidden() ? " (HIDDEN)" : "")); hasSignals = true; } if (hasSignals) signalNameList.setSelectedIndex(0); rebuildingSignalNameList = false; } public void redrawAllPanels() { if (mainHorizRulerPanel != null) mainHorizRulerPanel.repaint(); for(Panel wp : wavePanels) { wp.repaintContents(); } if (USETABLES) { table.repaint(); } else { left.repaint(); right.repaint(); } } public void repaintAllPanels() { if (USETABLES) { table.repaint(); } else { right.repaint(); } } /** * Method called when a Panel is to be closed. * @param wp the Panel to close. */ public void closePanel(Panel wp) { if (USETABLES) { int rows = wavePanels.size(); int [] rowHeights = new int[rows]; int closedPanelIndex = wavePanels.indexOf(wp); int validPanels = 0; int visRow = 0; for(int i=0; i<rows; i++) { if (wavePanels.get(i).isHidden()) continue; int rowHeight = table.getRowHeight(visRow++); if (i == closedPanelIndex) continue; rowHeights[validPanels++] = rowHeight; } stopEditing(); reloadTable(); wavePanels.remove(wp); for(int i=0; i<validPanels; i++) table.setRowHeight(i, rowHeights[i]); } else { left.remove(wp.getLeftHalf()); right.remove(wp.getRightHalf()); wavePanels.remove(wp); } rebuildPanelList(); overall.validate(); redrawAllPanels(); } /** * Method called when a Panel is to be hidden. * @param wp the Panel to hide. */ public void hidePanel(Panel wp) { if (wp.isHidden()) return; if (USETABLES) { int rows = wavePanels.size(); int [] rowHeights = new int[rows]; int hiddenPanelIndex = wavePanels.indexOf(wp); int validPanels = 0; int visRow = 0; for(int i=0; i<rows; i++) { if (wavePanels.get(i).isHidden()) continue; int rowHeight = table.getRowHeight(visRow++); if (i == hiddenPanelIndex) continue; rowHeights[validPanels++] = rowHeight; } wp.setHidden(true); stopEditing(); reloadTable(); for(int i=0; i<validPanels; i++) table.setRowHeight(i, rowHeights[i]); } else { wp.setHidden(true); left.remove(wp.getLeftHalf()); right.remove(wp.getRightHalf()); } rebuildPanelList(); overall.validate(); redrawAllPanels(); } /** * Method called when a Panel is to be shown. * @param wp the Panel to show. */ public void showPanel(Panel wp) { if (!wp.isHidden()) return; if (USETABLES) { int rows = wavePanels.size(); int [] rowHeights = new int[rows]; int openedPanelIndex = wavePanels.indexOf(wp); int validPanels = 0; int visRow = 0; int openedPanelHeightIndex = -1; int averageHeight = 0; int numAverages = 0; for(int i=0; i<rows; i++) { if (i == openedPanelIndex) { openedPanelHeightIndex = validPanels; int height = User.getWaveformDigitalPanelHeight(); if (sd.isAnalog()) height = User.getWaveformAnalogPanelHeight(); rowHeights[validPanels++] = height; continue; } if (wavePanels.get(i).isHidden()) continue; int rowHeight = table.getRowHeight(visRow++); rowHeights[validPanels++] = rowHeight; averageHeight += rowHeight; numAverages++; } wp.setHidden(false); stopEditing(); reloadTable(); if (numAverages != 0) rowHeights[openedPanelHeightIndex] = averageHeight / numAverages; for(int i=0; i<validPanels; i++) table.setRowHeight(i, rowHeights[i]); } else { wp.setHidden(false); left.add(wp.getLeftHalf()); right.add(wp.getRightHalf()); } rebuildPanelList(); overall.validate(); redrawAllPanels(); } /** * Method called to grow or shrink the panels vertically. */ public void growPanels(double scale) { // if minimum doesn't apply to this display, stop now int minHeight; if (sd.isAnalog()) { int origPanelSize = User.getWaveformAnalogPanelHeight(); int newPanelSize = (int)(origPanelSize * scale); if (newPanelSize < MINANALOGPANELSIZE) newPanelSize = MINANALOGPANELSIZE; if (origPanelSize == newPanelSize) return; User.setWaveformAnalogPanelHeight(newPanelSize); minHeight = MINANALOGPANELSIZE; } else { int origPanelSize = User.getWaveformDigitalPanelHeight(); int newPanelSize = (int)(origPanelSize * scale); if (newPanelSize < MINDIGITALPANELSIZE) newPanelSize = MINDIGITALPANELSIZE; if (origPanelSize == newPanelSize) return; User.setWaveformDigitalPanelHeight(newPanelSize); minHeight = MINDIGITALPANELSIZE; } // resize the panels if (USETABLES) { for(int i=0; i<table.getRowCount(); i++) { int rowHeight = table.getRowHeight(i); int newRowHeight = (int)(rowHeight*scale); if (newRowHeight < minHeight) newRowHeight = minHeight; table.setRowHeight(i, newRowHeight); } } else { for(Panel wp : wavePanels) { Dimension sz = wp.getSize(); sz.height = (int)(sz.height * scale); if (sz.height < minHeight) sz.height = minHeight; wp.setSize(sz.width, sz.height); wp.setMinimumSize(sz); wp.setPreferredSize(sz); sz = wp.getLeftHalf().getSize(); sz.height = (int)(sz.height * scale); if (sz.height < minHeight) sz.height = minHeight; wp.getLeftHalf().setPreferredSize(sz); wp.getLeftHalf().setMinimumSize(sz); wp.getLeftHalf().setSize(sz.width, sz.height); sz = wp.getRightHalf().getSize(); sz.height = (int)(sz.height * scale); if (sz.height < minHeight) sz.height = minHeight; wp.getRightHalf().setPreferredSize(sz); wp.getRightHalf().setMinimumSize(sz); wp.getRightHalf().setSize(sz.width, sz.height); } } overall.validate(); redrawAllPanels(); } /** * Method called to delete the highlighted signal from its Panel. * @param wp the Panel with the signal to be deleted. */ public void deleteSignalFromPanel(Panel wp) { boolean found = true; while (found) { found = false; for(WaveSignal ws : wp.getSignals()) { if (!ws.isHighlighted()) continue; wp.removeHighlightedSignal(ws, true); wp.removeSignal(ws.getButton()); found = true; break; } } if (wp.getSignalButtons() != null) { wp.getSignalButtons().validate(); wp.getSignalButtons().repaint(); } wp.repaintContents(); saveSignalOrder(); } /** * Method called to delete all signals from a Panel. * @param wp the Panel to clear. */ public void deleteAllSignalsFromPanel(Panel wp) { wp.clearHighlightedSignals(); wp.getSignalButtons().removeAll(); wp.getSignalButtons().validate(); wp.getSignalButtons().repaint(); wp.removeAllSignals(); wp.repaintContents(); saveSignalOrder(); } // ************************************* THE HORIZONTAL RULER ************************************* public HorizRuler getMainHorizRuler() { return mainHorizRulerPanel; } public Signal getXAxisSignalAll() { return xAxisSignalAll; } public void setXAxisSignalAll(Signal sig) { xAxisSignalAll = sig; } private void addMainHorizRulerPanel() { mainHorizRulerPanel = new HorizRuler(null, this); mainHorizRulerPanel.setToolTipText("One X axis ruler applies to all signals when the X axes are locked"); GridBagConstraints gbc = new GridBagConstraints(); gbc.gridx = 10; gbc.gridy = 1; gbc.weightx = 1; gbc.weighty = 0; gbc.anchor = GridBagConstraints.CENTER; gbc.fill = GridBagConstraints.BOTH; overall.add(mainHorizRulerPanel, gbc); } private void removeMainHorizRulerPanel() { overall.remove(mainHorizRulerPanel); mainHorizRulerPanel = null; } // ************************************* SWEEP CONTROL ************************************* private void resetSweeps() { sweepSignals = new ArrayList<SweepSignal>(); for(Iterator<Analysis> it = sd.getAnalyses(); it.hasNext(); ) { Analysis an = it.next(); if (!(an instanceof AnalogAnalysis)) continue; AnalogAnalysis aa = (AnalogAnalysis)an; int maxNum = aa.getNumSweeps(); if (maxNum <= 1) continue; for (int i = 0; i < maxNum; i++) { Object obj = aa.getSweep(i); new SweepSignal(obj, this, an); } } } public int addSweep(SweepSignal ss) { if (sweepSignals.add(ss)) return sweepSignals.size()-1; return -1; } public void setIncludeInAllSweeps(List<SweepSignal> sweeps, boolean include) { for(int i=0; i<sweeps.size(); i++) { SweepSignal ss = sweeps.get(i); boolean update = (i == sweeps.size()-1); ss.setIncluded(include, update); } } /** * Method to check whether this particular sweep is included. * @return true if the sweep is included */ public boolean isSweepSignalIncluded(AnalogAnalysis an, int index) { Object sweep = an.getSweep(index); for (SweepSignal ss : sweepSignals) { if (ss.getObject() == sweep) return ss.isIncluded(); } return true; // in case no sweep, always true } public int getHighlightedSweep() { return highlightedSweep; } public void setHighlightedSweep(int sweep) { highlightedSweep = sweep; } // ************************************* VCR CONTROL ************************************* private void tick() { // see if it is time to advance the VCR long curtime = System.currentTimeMillis(); if (curtime - vcrLastAdvance < 100) return; vcrLastAdvance = curtime; if (wavePanels.size() == 0) return; Panel wp = wavePanels.iterator().next(); int xValueScreen = wp.convertXDataToScreen(mainXPosition); Rectangle2D bounds = sd.getBounds(); if (vcrPlayingBackwards) { int newXValueScreen = xValueScreen - vcrAdvanceSpeed; double newXValue = wp.convertXScreenToData(newXValueScreen); double lowXValue = bounds.getMinX(); if (newXValue <= lowXValue) { newXValue = lowXValue; vcrClickStop(); } setMainXPositionCursor(newXValue); } else { int newXValueScreen = xValueScreen + vcrAdvanceSpeed; double newXValue = wp.convertXScreenToData(newXValueScreen); double highXValue = bounds.getMaxX(); if (newXValue >= highXValue) { newXValue = highXValue; vcrClickStop(); } setMainXPositionCursor(newXValue); } redrawAllPanels(); } public void vcrClickRewind() { vcrClickStop(); Rectangle2D bounds = sd.getBounds(); double lowXValue = bounds.getMinX(); setMainXPositionCursor(lowXValue); redrawAllPanels(); } public void vcrClickPlayBackwards() { if (vcrTimer == null) { ActionListener taskPerformer = new ActionListener() { public void actionPerformed(ActionEvent evt) { tick(); } }; vcrTimer = new Timer(100, taskPerformer); vcrLastAdvance = System.currentTimeMillis(); vcrTimer.start(); } vcrPlayingBackwards = true; } /** * Method to stop the auto-playing in the simulation window. */ public void vcrClickStop() { if (vcrTimer == null) return; vcrTimer.stop(); vcrTimer = null; } public void vcrClickPlay() { if (vcrTimer == null) { ActionListener taskPerformer = new ActionListener() { public void actionPerformed(ActionEvent evt) { tick(); } }; vcrTimer = new Timer(100, taskPerformer); vcrLastAdvance = System.currentTimeMillis(); vcrTimer.start(); } vcrPlayingBackwards = false; } public void vcrClickToEnd() { vcrClickStop(); Rectangle2D bounds = sd.getBounds(); double highXValue = bounds.getMaxX(); setMainXPositionCursor(highXValue); redrawAllPanels(); } public void vcrClickFaster() { int j = vcrAdvanceSpeed / 4; if (j <= 0) j = 1; vcrAdvanceSpeed += j; } public void vcrClickSlower() { int j = vcrAdvanceSpeed / 4; if (j <= 0) j = 1; vcrAdvanceSpeed -= j; if (vcrAdvanceSpeed <= 0) vcrAdvanceSpeed = 1; } // ************************************* HIGHLIGHTING ************************************* /** * Method to remove all highlighting from waveform window. */ public void clearHighlighting() { // look at all signal names in the cell for(Panel wp : wavePanels) { // look at all traces in this panel boolean changed = false; for(WaveSignal ws : wp.getSignals()) { if (ws.isHighlighted()) changed = true; ws.setHighlighted(false); } if (changed) wp.repaintContents(); } } /** * Method to return a List of highlighted simulation signals. * @return a List of highlighted simulation signals. */ public List<Signal> getHighlightedNetworkNames() { List<Signal> highlightedSignals = new ArrayList<Signal>(); // look at all signal names in the cell for(Panel wp : wavePanels) { // look at all traces in this panel for(WaveSignal ws : wp.getSignals()) { if (ws.isHighlighted()) highlightedSignals.add(ws.getSignal()); } } // also include what is in the SIGNALS tree ExplorerTree sigTree = wf.getExplorerTab(); Object nodeInfo = sigTree.getCurrentlySelectedObject(0); if (nodeInfo != null && nodeInfo instanceof Signal) { Signal sig = (Signal)nodeInfo; highlightedSignals.add(sig); } return highlightedSignals; } /** * Method to get a Set of currently highlighted networks in this WaveformWindow. */ public Set<Network> getHighlightedNetworks() { // make empty set Set<Network> nets = new HashSet<Network>(); // if no cell in the window, stop now Cell cell = sd.getCell(); if (cell == null) return nets; Netlist netlist = cell.getNetlist(); if (netlist == null) { System.out.println("Sorry, a deadlock aborted crossprobing (network information unavailable). Please try again"); return nets; } // look at all signal names in the cell for(Panel wp : wavePanels) { // look at all traces in this panel for(WaveSignal ws : wp.getSignals()) { Network net = findNetwork(netlist, ws.getSignal().getSignalName()); if (net != null) nets.add(net); } } // also include what is in the SIGNALS tree ExplorerTree sigTree = wf.getExplorerTab(); Object nodeInfo = sigTree.getCurrentlySelectedObject(0); if (nodeInfo != null && nodeInfo instanceof Signal) { Signal sig = (Signal)nodeInfo; Network net = findNetwork(netlist, sig.getSignalName()); if (net != null) nets.add(net); } return nets; } /** * Get the highlighter for this window content. * @return the highlighter */ public Highlighter getHighlighter() { return highlighter; } // ************************************* PRINTING ************************************* /** * Method to get a list of polygons describing the waveform window. * @return a List of PolyBase objects that describe this window. */ public List<PolyBase> getPolysForPrinting() { int offY = 0; List<PolyBase> override = new ArrayList<PolyBase>(); HorizRuler mainHR = getMainHorizRuler(); if (mainHR != null) { List<PolyBase> horizPolys = mainHR.getPolysForPrinting(getPanels().next()); for(PolyBase poly : horizPolys) { Point2D [] pts = poly.getPoints(); for(int i=0; i<pts.length; i++) { poly.setPoint(i, pts[i].getX(), pts[i].getY() + offY); } override.add(poly); } offY += mainHR.getHeight(); } for(Iterator<Panel> it = getPanels(); it.hasNext(); ) { Panel panel = it.next(); HorizRuler hr = panel.getHorizRuler(); if (hr != null) { offY += hr.getHeight(); List<PolyBase> horizPolys = hr.getPolysForPrinting(panel); for(PolyBase poly : horizPolys) { Point2D [] pts = poly.getPoints(); for(int i=0; i<pts.length; i++) { poly.setPoint(i, pts[i].getX(), pts[i].getY() + offY); } override.add(poly); } offY += hr.getHeight(); } List<PolyBase> panelList = panel.getPolysForPrinting(); for(PolyBase poly : panelList) { Point2D [] pts = poly.getPoints(); for(int i=0; i<pts.length; i++) { poly.setPoint(i, pts[i].getX(), pts[i].getY() + offY); } override.add(poly); } offY += panel.getHeight(); if (hr == null) offY += 20; } return override; } // ************************************* THE EXPLORER TREE ************************************* public List<MutableTreeNode> loadExplorerTrees() { TreePath rootPath = new TreePath(ExplorerTreeModel.rootNode); ArrayList<MutableTreeNode> nodes = new ArrayList<MutableTreeNode>(); treePathFromAnalysis.clear(); for(Iterator<Analysis> it = sd.getAnalyses(); it.hasNext(); ) { Analysis an = it.next(); if (an.getAnalysisType() == Analysis.ANALYSIS_SIGNALS) { nodes.add(getSignalsForExplorer(an, rootPath, "SIGNALS")); } else if (an.getAnalysisType() == Analysis.ANALYSIS_TRANS) { nodes.add(getSignalsForExplorer(an, rootPath, "TRANS SIGNALS")); nodes.add(getSweepsForExplorer(an, "TRANS SWEEPS")); } else if (an.getAnalysisType() == Analysis.ANALYSIS_AC) { nodes.add(getSignalsForExplorer(an, rootPath, "AC SIGNALS")); nodes.add(getSweepsForExplorer(an, "AC SWEEPS")); } else if (an.getAnalysisType() == Analysis.ANALYSIS_DC) { nodes.add(getSignalsForExplorer(an, rootPath, "DC SIGNALS")); nodes.add(getSweepsForExplorer(an, "DC SWEEPS")); } else if (an.getAnalysisType() == Analysis.ANALYSIS_MEAS) { nodes.add(getSignalsForExplorer(an, rootPath, "MEASUREMENTS")); } } // clean possible nulls while (nodes.remove(null)); return nodes; } private DefaultMutableTreeNode getSignalsForExplorer(Analysis an, TreePath parentPath, String analysis) { List<Signal> signals = an.getSignals(); if (signals.size() == 0) return null; if (an instanceof EpicAnalysis) { DefaultMutableTreeNode analysisNode = ((EpicAnalysis)an).getSignalsForExplorer(analysis); treePathFromAnalysis.put(an, parentPath.pathByAddingChild(analysisNode)); return analysisNode; } else if (an instanceof NewEpicAnalysis) { DefaultMutableTreeNode analysisNode = ((NewEpicAnalysis)an).getSignalsForExplorer(analysis); treePathFromAnalysis.put(an, parentPath.pathByAddingChild(analysisNode)); return analysisNode; } DefaultMutableTreeNode signalsExplorerTree = new DefaultMutableTreeNode(analysis); TreePath analysisPath = parentPath.pathByAddingChild(signalsExplorerTree); treePathFromAnalysis.put(an, analysisPath); Map<String,TreePath> contextMap = new HashMap<String,TreePath>(); contextMap.put("", analysisPath); Collections.sort(signals, new SignalsByName()); // add branches first char separatorChar = sd.getSeparatorChar(); for(Signal sSig : signals) { // if (!(sSig instanceof TimedSignal)) continue; if (sSig.getSignalContext() != null) makeContext(sSig.getSignalContext(), contextMap, separatorChar); } String delim = Simulation.getSpiceExtractedNetDelimiter(); // make a list of signal names with "#" in them Set<String> sharpSet = new HashSet<String>(); for(Signal sSig : signals) { // if (!(sSig instanceof TimedSignal)) continue; String sigName = sSig.getSignalName(); int hashPos = sigName.indexOf(delim); if (hashPos > 0) { String nodeName = sSig.getSignalContext(); if (nodeName == null) nodeName = ""; else nodeName += separatorChar; nodeName += sigName.substring(0, hashPos); sharpSet.add(nodeName); } } // add all signals to the tree for(Signal sSig : signals) { // if (!(sSig instanceof TimedSignal)) continue; TreePath thisTree = analysisPath; String nodeName = sSig.getSignalContext(); String nodeNameStr = nodeName; if (nodeNameStr == null) nodeNameStr = ""; else nodeNameStr += separatorChar; String sigName = sSig.getSignalName(); int hashPos = sigName.indexOf(delim); if (hashPos > 0) { // force a branch with the proper name nodeName = nodeNameStr + sigName.substring(0, hashPos+1); } else { // if this is the pure name of a hash set, force a branch String pureSharpName = nodeNameStr + sigName; if (sharpSet.contains(pureSharpName)) nodeName = pureSharpName + delim; } if (nodeName != null) thisTree = makeContext(nodeName, contextMap, separatorChar); DefaultMutableTreeNode sigLeaf = new DefaultMutableTreeNode(sSig); ((DefaultMutableTreeNode)thisTree.getLastPathComponent()).add(sigLeaf); } return signalsExplorerTree; } /** * Recursive method to locate and create branches in the Signal Explorer tree. * @param branchName the name of a branch to find/create. * The name has dots in it to separate levels of the hierarchy. * @param contextMap a HashMap of branch names to tree paths. * @return the tree path for the requested branch name. */ private TreePath makeContext(String branchName, Map<String,TreePath> contextMap, char separatorChar) { TreePath branchTree = contextMap.get(branchName); if (branchTree != null) return branchTree; // split the branch name into a leaf and parent String parent = ""; String leaf = branchName; int dotPos = leaf.lastIndexOf(separatorChar); if (dotPos >= 0) { parent = leaf.substring(0, dotPos); leaf = leaf.substring(dotPos+1); } TreePath parentBranch = makeContext(parent, contextMap, separatorChar); DefaultMutableTreeNode thisTree = new DefaultMutableTreeNode(leaf); ((DefaultMutableTreeNode)parentBranch.getLastPathComponent()).add(thisTree); TreePath thisPath = parentBranch.pathByAddingChild(thisTree); contextMap.put(branchName, thisPath); return thisPath; } /** * Class to sort signals by their name */ private static class SignalsByName implements Comparator<Signal> { public int compare(Signal s1, Signal s2) { return TextUtils.STRING_NUMBER_ORDER.compare(s1.getFullName(), s2.getFullName()); } } private DefaultMutableTreeNode getSweepsForExplorer(Analysis an, String analysis) { DefaultMutableTreeNode sweepsExplorerTree = null; boolean first = true; for(SweepSignal ss : sweepSignals) { if (ss.getAnalysis() != an) continue; if (first) { first = false; sweepsExplorerTree = new DefaultMutableTreeNode(analysis); } sweepsExplorerTree.add(new DefaultMutableTreeNode(ss)); } return sweepsExplorerTree; } // ************************************* SIGNALS ************************************* private Signal findSignal(String name, Analysis<Signal> an) { for(Signal sSig : an.getSignals()) { String sigName = sSig.getFullName(); if (sigName.equals(name)) return sSig; } return null; } /** * Method to add a selection to the waveform display. * @param h a Highlighter of what is selected. * @param context the context of these networks * (a string to prepend to them to get the actual simulation signal name). * @param newPanel true to create new panels for each signal. */ public void showSignals(Highlighter h, VarContext context, boolean newPanel) { List<Signal> found = findSelectedSignals(h, context); showSignals(found, newPanel); } /** * Method to add a list of signals to the waveform display. * @param found the signals to add. * @param newPanel true to create new panels for each signal. */ public void showSignals(List<Signal> found, boolean newPanel) { // determine the current panel Panel wp = null; for(Panel oWp : wavePanels) { if (oWp.isSelected()) { wp = oWp; break; } } if (!sd.isAnalog()) newPanel = true; if (!newPanel && wp == null) { System.out.println("No current waveform panel to add signals"); return; } boolean added = false; for(Signal sSig : found) { // add the signal if (newPanel) { wp = makeNewPanel(sSig.getAnalysis()); boolean isAnalog = false; if (sSig instanceof AnalogSignal) isAnalog = true; if (isAnalog) { AnalogSignal as = (AnalogSignal)sSig; Rectangle2D rangeBounds = as.getBounds(); double lowValue = rangeBounds.getMinY(); double highValue = rangeBounds.getMaxY(); double range = highValue - lowValue; if (range == 0) range = 2; double rangeExtra = range / 10; wp.setYAxisRange(lowValue - rangeExtra, highValue + rangeExtra); wp.makeSelectedPanel(-1, -1); newPanel = false; } if (!xAxisLocked) { wp.setXAxisRange(sSig.getLeftEdge(), sSig.getRightEdge()); } } else { // make sure the analysis type is correct if (wp.wrongPanelType(sSig)) break; } // check if signal already in panel boolean alreadyPlotted = false; for(WaveSignal ws : wp.getSignals()) { String name = ws.getSignal().getFullName(); if (name.equals(sSig.getFullName())) { alreadyPlotted = true; // add it again, this will increment colors WaveSignal.addSignalToPanel(ws.getSignal(), wp, null); } } if (!alreadyPlotted) { new WaveSignal(wp, sSig); } added = true; wp.repaintContents(); } if (added) { overall.validate(); saveSignalOrder(); } } /** * Method to remove a set of Networks from the waveform display. * @param nets the Set of Networks to remove. * @param context the context of these networks * (a string to prepend to them to get the actual simulation signal name). */ public void removeSignals(Set<Network> nets, VarContext context) { for(Network net : nets) { String netName = getSpiceNetName(context, net); for(Iterator<Analysis> aIt = sd.getAnalyses(); aIt.hasNext(); ) { Analysis an = aIt.next(); Signal sSig = an.findSignalForNetwork(netName); if (sSig == null) continue; boolean found = true; while (found) { found = false; for(Iterator<Panel> pIt = getPanels(); pIt.hasNext(); ) { Panel wp = pIt.next(); if (wp.getAnalysisType() != an.getAnalysisType()) continue; for(WaveSignal ws : wp.getSignals()) { if (ws.getSignal() != sSig) continue; wp.removeHighlightedSignal(ws, true); wp.removeSignal(ws.getButton()); wp.getSignalButtons().validate(); wp.getSignalButtons().repaint(); wp.repaintContents(); found = true; break; } if (found) break; } } } } } public static String getSpiceNetName(Network net) { return Spice.getSafeNetName(net.getName(), Simulation.getSpiceEngine()); } /** * Get the spice net name associated with the network and the context. * If the network is null, a String describing only the context is returned. * @param context the context * @param net the network, or null * @return a String describing the unique, global spice name for the network, * or a String describing the context if net is null */ public static String getSpiceNetName(VarContext context, Network net) { return getSpiceNetName(context, net, false, false); } /** * Get the spice net name associated with the network and the context. * If the network is null, a String describing only the context is returned. * @param context the context * @param net the network, or null * @param assuraRCXFormat return net assuming Assura RCX flat netlist format * @param starRCXTFormat return net assuming Star RCXT flat netlist format * @return a String describing the unique, global spice name for the network, * or a String describing the context if net is null */ public static String getSpiceNetName(VarContext context, Network net, boolean assuraRCXFormat, boolean starRCXTFormat) { boolean isGlobal = false; if (net != null) { Netlist netlist = net.getNetlist(); while (net.isExported() && (context != VarContext.globalContext)) { // net is exported, find net in parent Network tempnet = getNetworkInParent(net, context.getNodable()); if (tempnet == null) break; net = tempnet; context = context.pop(); } // searching in globals // Code taken from NCC netlist = net.getNetlist(); Global.Set globNets = netlist.getGlobals(); for (int i=0; i<globNets.size(); i++) { Global g = globNets.get(i); Network netG = netlist.getNetwork(g); if (netG == net) { isGlobal = true; break; } } } // create net name String contextStr = context.getInstPath("."); if (assuraRCXFormat) { contextStr = "x"+context.getInstPath("/x"); } if (starRCXTFormat) { contextStr = context.getInstPath("/"); } contextStr = TextUtils.canonicString(contextStr); if (net == null) return contextStr; if (context == VarContext.globalContext || isGlobal) return getSpiceNetName(net); else if (assuraRCXFormat) return contextStr + "/" + getSpiceNetName(net); else if (starRCXTFormat) return contextStr + "/" + getSpiceNetName(net); else return contextStr + "." + getSpiceNetName(net); } /** * Get the Network in the childNodable's parent that corresponds to the Network * inside the childNodable. * @param childNetwork the network in the childNodable * @return the network in the parent that connects to the * specified network, or null if no such network. * null on error. */ public static Network getNetworkInParent(Network childNetwork, Nodable childNodable) { if (childNodable == null || childNetwork == null) return null; if (!childNodable.isCellInstance()) return null; Cell childCell = (Cell)childNodable.getProto(); if (childCell.contentsView() != null) childCell = childCell.contentsView(); // find export on network boolean found = false; Export export = null; int i = 0; for (Iterator<PortProto> it = childCell.getPorts(); it.hasNext(); ) { export = (Export)it.next(); for (i=0; i<export.getNameKey().busWidth(); i++) { Netlist netlist = childCell.getNetlist(); if (netlist == null) { System.out.println("Sorry, a deadlock aborted crossprobing (network information unavailable). Please try again"); return null; } Network net = netlist.getNetwork(export, i); if (net == childNetwork) { found = true; break; } } if (found) break; } if (!found) return null; // find corresponding port on icon Export pp = (Export)childNodable.getProto().findPortProto(export.getNameKey()); // find corresponding network in parent Cell parentCell = childNodable.getParent(); //if (childNodable instanceof NodeInst) childNodable = Netlist.getNodableFor((NodeInst)childNodable, 0); Netlist netlist = parentCell.getNetlist(); if (netlist == null) { System.out.println("Sorry, a deadlock aborted crossprobing (network information unavailable). Please try again"); return null; } Network parentNet = netlist.getNetwork(childNodable, pp, i); return parentNet; } /** * Method to locate a simulation signal in the waveform. * @param sSig the Signal to locate. * @return the displayed WaveSignal where it is in the waveform window. * Returns null if the signal is not being displayed. */ public WaveSignal findDisplayedSignal(Signal sSig) { for(Panel wp : wavePanels) { WaveSignal ws = wp.findWaveSignal(sSig); if (ws != null) return ws; } return null; } // ************************************* THE X AXIS ************************************* public double getMainXPositionCursor() { return mainXPosition; } public void setMainXPositionCursor(double value) { mainXPosition = value; String amount = TextUtils.convertToEngineeringNotation(mainXPosition, "s"); mainPos.setText("Main: " + amount); String diff = TextUtils.convertToEngineeringNotation(Math.abs(mainXPosition - extXPosition), "s"); delta.setText("Delta: " + diff); updateAssociatedLayoutWindow(); } public double getExtensionXPositionCursor() { return extXPosition; } public void setExtensionXPositionCursor(double value) { extXPosition = value; String amount = TextUtils.convertToEngineeringNotation(extXPosition, "s"); extPos.setText("Ext: " + amount); String diff = TextUtils.convertToEngineeringNotation(Math.abs(mainXPosition - extXPosition), "s"); delta.setText("Delta: " + diff); } /** * Method to set the X range in all panels. * @param minXPosition the low X value. * @param maxXPosition the high X value. */ public void setDefaultHorizontalRange(double minXPosition, double maxXPosition) { this.minXPosition = minXPosition; this.maxXPosition = maxXPosition; } public double getLowDefaultHorizontalRange() { return minXPosition; } public double getHighDefaultHorizontalRange() { return maxXPosition; } /** * Method to set the zoom extents for this waveform window. * @param lowVert the low value of the vertical axis (for the given panel only). * @param highVert the high value of the vertical axis (for the given panel only). * @param lowHoriz the low value of the horizontal axis (for the given panel only unless X axes are locked). * @param highHoriz the high value of the horizontal axis (for the given panel only unless X axes are locked). * @param thePanel the panel being zoomed. */ public void setZoomExtents(double lowVert, double highVert, double lowHoriz, double highHoriz, Panel thePanel) { for(Panel wp : wavePanels) { boolean changed = false; if (wp == thePanel) { wp.setYAxisRange(lowVert, highVert); changed = true; } if (xAxisLocked || wp == thePanel) { if (wp.getMinXAxis() < wp.getMaxXAxis()) { wp.setXAxisRange(Math.min(lowHoriz, highHoriz), Math.max(lowHoriz, highHoriz)); } else { wp.setXAxisRange(Math.max(lowHoriz, highHoriz), Math.min(lowHoriz, highHoriz)); } changed = true; } if (changed) wp.repaintWithRulers(); } } /** * Method called to toggle the lock on the horizontal axes. */ public void togglePanelXAxisLock() { xAxisLocked = ! xAxisLocked; if (xAxisLocked) { // X axes now locked: add main ruler, remove individual rulers xAxisLockButton.setIcon(iconLockXAxes); addMainHorizRulerPanel(); double minXPosition = 0, maxXPosition = 0; int vertAxis = 0; boolean first = true; for(Panel wp : wavePanels) { wp.removeHorizRulerPanel(); if (first) { first = false; minXPosition = wp.getMinXAxis(); maxXPosition = wp.getMaxXAxis(); vertAxis = wp.getVertAxisPos(); } else { if (minXPosition < maxXPosition) { minXPosition = Math.min(minXPosition, wp.getMinXAxis()); maxXPosition = Math.max(maxXPosition, wp.getMaxXAxis()); } else { minXPosition = Math.max(minXPosition, wp.getMinXAxis()); maxXPosition = Math.min(maxXPosition, wp.getMaxXAxis()); } wp.setVertAxisPos(vertAxis); } } // force all panels to be at the same X position for(Panel wp : wavePanels) { wp.setXAxisRange(minXPosition, maxXPosition); } } else { // X axes are unlocked: put a ruler in each panel, remove main ruler xAxisLockButton.setIcon(iconUnLockXAxes); for(Panel wp : wavePanels) { wp.addHorizRulerPanel(); } removeMainHorizRulerPanel(); } overall.validate(); overall.repaint(); } public boolean isXAxisLocked() { return xAxisLocked; } // ************************************* CROSS-PROBING ************************************* /** * Method to crossprobe from an EditWindow to this WaveformWindow. * @param wnd the EditWindow that changed. * @param which the Highlighter in that window with current selection. */ private void crossProbeEditWindowToWaveform(EditWindow wnd, Highlighter which) { // make sure the windows are associated with each other Locator loc = new Locator(wnd, this); if (loc.getWaveformWindow() != this) return; freezeEditWindowHighlighting = true; // start by removing all highlighting in the waveform for(Panel wp : wavePanels) { wp.clearHighlightedSignals(); } // also clear "Signals" tree highlighting ExplorerTree tree = wf.getExplorerTab(); tree.setSelectionPath(null); tree.clearCurrentlySelectedObjects(); // find the signal to show in the waveform window List<Signal> found = findSelectedSignals(which, loc.getContext()); // show it in every panel boolean foundSignal = false; for(Panel wp : wavePanels) { for(WaveSignal ws : wp.getSignals()) { for(Signal sSig : found) { if (ws.getSignal() == sSig) { wp.addHighlightedSignal(ws, false); foundSignal = true; } } } } if (foundSignal) repaint(); // show only one in the "Signals" tree Collections.sort(found, new SignalsByName()); for(Signal sSig : found) { TreePath treePath = treePathFromSignal(sSig); if (treePath != null) { tree.setSelectionPath(treePath); break; } } freezeEditWindowHighlighting = false; } private TreePath treePathFromSignal(Signal sig) { Analysis an = sig.getAnalysis(); TreePath treePath = treePathFromAnalysis.get(an); if (treePath == null) return null; String fullName = sig.getFullName(); char separator = an.getStimuli().getSeparatorChar(); int sBeg = 0; while (sBeg < fullName.length()) { int sEnd = fullName.indexOf(separator, sBeg); if (sEnd < 0) sEnd = fullName.length(); String s = fullName.substring(sBeg, sEnd); TreeNode parentNode = (TreeNode)treePath.getLastPathComponent(); TreeNode child = findChild(parentNode, s); if (child == null) return null; treePath = treePath.pathByAddingChild(child); sBeg = sEnd + 1; } return sBeg == fullName.length() + 1 ? treePath : null; } private static TreeNode findChild(TreeNode parent, String name) { for (int i = 0, numChilds = parent.getChildCount(); i < numChilds; i++) { TreeNode child = parent.getChildAt(i); String s; if (child instanceof DefaultMutableTreeNode) { DefaultMutableTreeNode node = (DefaultMutableTreeNode)child; Object o = node.getUserObject(); if (o instanceof Signal) s = ((Signal)o).getSignalName(); else s = o.toString(); } else { s = child.toString(); } if (name.equals(s)) return child; } return null; } /** * Method to return a list of signals that are selected in an EditWindow. * @param h a Highlighter with a selection in an EditWindow. * @param context the VarContext of that window. * @return a List of Signal objects in this WaveformWindow. */ private List<Signal> findSelectedSignals(Highlighter h, VarContext context) { List<Signal> found = new ArrayList<Signal>(); // special case if a current source is selected List<Geometric> highlightedObjects = h.getHighlightedEObjs(true, true); if (highlightedObjects.size() == 1) { // if a node is highlighted that has current measured on it, use that Geometric geom = highlightedObjects.get(0); if (geom instanceof NodeInst) { NodeInst ni = (NodeInst)geom; String nodeName = "I(v" + ni.getName(); for(Iterator<Analysis> it = sd.getAnalyses(); it.hasNext(); ) { Analysis an = it.next(); Signal sSig = an.findSignalForNetworkQuickly(nodeName); if (sSig != null) { found.add(sSig); return found; } } } } // convert all networks to signals Set<Network> nets = h.getHighlightedNetworks(); found.addAll(findSelectedSignals(nets, context, false)); Collections.sort(found, new CompSignals()); return found; } private List<Signal> findSelectedSignals(Set<Network> nets, VarContext context, boolean sort) { Cell topContext = sd.getCell(); List<Signal> found = new ArrayList<Signal>(); for(Network net : nets) { String netName = getSpiceNetName(context, net); for(Iterator<Analysis> aIt = sd.getAnalyses(); aIt.hasNext(); ) { Analysis an = aIt.next(); Signal sSig = an.findSignalForNetworkQuickly(netName); if (sSig == null) { // try prepending the top-level cell name to the signal name if (topContext == null) topContext = net.getParent(); String nameWithCell = topContext.getName() + "." + netName; sSig = an.findSignalForNetworkQuickly(nameWithCell); } if (sSig == null) { // when cross-probing extracted layout, hierarchy delimiter is '/x' instead of '.' String temp = getSpiceNetName(context, net, true, false); sSig = an.findSignalForNetworkQuickly(temp); } if (sSig == null) { // when cross-probing extracted layout, hierarchy delimiter is '/' instead of '.' String temp = getSpiceNetName(context, net, false, true); sSig = an.findSignalForNetworkQuickly(temp); } if (sSig == null) { // try prepending the top-level cell name and setting the hierarchy delimiter as '/' instead of '.' if (topContext == null) topContext = net.getParent(); String temp = getSpiceNetName(context, net, false, true); String nameWithCell = topContext.getName() + "." + temp; sSig = an.findSignalForNetworkQuickly(nameWithCell); } if (sSig == null) { // check for equivalent layout net name // search up hierarchy for cell with NCC equiv info Cell cell = net.getParent(); NccResult result = NccCrossProbing.getResults(cell); if (result == null) { for (VarContext checkContext = context; checkContext != VarContext.globalContext; checkContext = checkContext.pop()) { cell = checkContext.getNodable().getParent(); result = NccCrossProbing.getResults(cell); if (result != null) break; } } if (result != null) { HierarchyEnumerator.NetNameProxy proxy = result.getEquivalence().findEquivalentNet(context, net); if (proxy != null) { String otherName = getSpiceNetName(proxy.getContext(), proxy.getNet()); System.out.println("Mapped "+netName+" to "+otherName); sSig = an.findSignalForNetworkQuickly(otherName); } } } if (sSig != null) { List<Signal> signalGroup = an.getSignalsFromExtractedNet(sSig); for (Signal s : signalGroup) found.add(s); } // else System.out.println("Can't find net "+netName+" in cell "+context.getInstPath(".")); } } if (sort) Collections.sort(found, new CompSignals()); return found; } /** Test signal lookup */ public List<Signal> findAllSignals(Cell cell, VarContext context, boolean sort, boolean recurse) { Set<Network> nets = new HashSet<Network>(); List<Signal> found = new ArrayList<Signal>(); for (Iterator<Network> it = cell.getNetlist().getNetworks(); it.hasNext(); ) { nets.add(it.next()); } found.addAll(findSelectedSignals(nets, context, false)); if (recurse) { for (Iterator<Nodable> it = cell.getNetlist().getNodables(); it.hasNext(); ) { Nodable no = it.next(); if (!(no.getProto() instanceof Cell)) continue; Cell subCell = (Cell)no.getProto(); VarContext subContext = context.push(no); found.addAll(findAllSignals(subCell, subContext, false, true)); } } if (sort) Collections.sort(found, new CompSignals()); return found; } private static class CompSignals implements Comparator<Signal> { public int compare(Signal s1, Signal s2) { return TextUtils.STRING_NUMBER_ORDER.compare(s1.getFullName(), s2.getFullName()); } } private static Network findNetwork(Netlist netlist, String name) { // Should really use extended code, found in "simspicerun.cpp:sim_spice_signalname()" for(Iterator<Network> nIt = netlist.getNetworks(); nIt.hasNext(); ) { Network net = nIt.next(); if (getSpiceNetName(net).equalsIgnoreCase(name)) return net; } // try converting "@" in network names for(Iterator<Network> nIt = netlist.getNetworks(); nIt.hasNext(); ) { Network net = nIt.next(); String convertedName = getSpiceNetName(net).replace('@', '_'); if (convertedName.equalsIgnoreCase(name)) return net; } // try ignoring "_" in signal names for(Iterator<Network> nIt = netlist.getNetworks(); nIt.hasNext(); ) { Network net = nIt.next(); String netName = getSpiceNetName(net); if (netName.length() != name.length()) continue; boolean matches = true; for(int i=0; i<netName.length(); i++) { char netChar = netName.charAt(i); char nameChar = name.charAt(i); if (nameChar == '_') { if (TextUtils.isLetterOrDigit(netChar)) { matches = false; break; } } else { if (TextUtils.canonicChar(nameChar) != TextUtils.canonicChar(netChar)) { matches = false; break; } } } if (matches) return net; } return null; } /** * Method called when signal waveforms change, and equivalent should be shown in the edit window. */ public void crossProbeWaveformToEditWindow() { // check double-crossprobe locks if (freezeEditWindowHighlighting) return; freezeWaveformHighlighting = true; // highlight the net in any associated edit windows for(Iterator<WindowFrame> wIt = WindowFrame.getWindows(); wIt.hasNext(); ) { WindowFrame wfr = wIt.next(); if (!(wfr.getContent() instanceof EditWindow)) continue; EditWindow wnd = (EditWindow)wfr.getContent(); Locator loc = new Locator(wnd, this); if (loc.getWaveformWindow() != this) continue; VarContext context = loc.getContext(); Cell cell = wnd.getCell(); if (cell == null) continue; Highlighter hl = wnd.getHighlighter(); Netlist netlist = cell.getNetlist(); if (netlist == null) { System.out.println("Sorry, a deadlock aborted crossprobing (network information unavailable). Please try again"); freezeWaveformHighlighting = false; return; } hl.clear(); for(Panel wp : wavePanels) { for(WaveSignal ws : wp.getSignals()) { if (!ws.isHighlighted()) continue; String want = ws.getSignal().getFullName(); Stack<Nodable> upNodables = new Stack<Nodable>(); Network net = null; Cell subCell = cell; VarContext subContext = context; for (;;) { String contextStr = getSpiceNetName(subContext, null); if (contextStr.length() > 0) { boolean matches = false; contextStr += "."; String altContextStr = contextStr; if (sd.getCell() != null) altContextStr = sd.getCell().getName().toLowerCase() + "." + contextStr; if (want.startsWith(contextStr)) matches = true; else { contextStr = contextStr.replace('@', '_'); if (want.startsWith(contextStr)) matches = true; } if (!matches) { if (want.startsWith(altContextStr)) matches = true; else { altContextStr = altContextStr.replace('@', '_'); if (want.startsWith(altContextStr)) matches = true; } if (matches) contextStr = altContextStr; } if (!matches) { if (subContext == VarContext.globalContext) break; subCell = subContext.getNodable().getParent(); upNodables.push(subContext.getNodable()); subContext = subContext.pop(); continue; } } String desired = want.substring(contextStr.length()); net = findNetwork(netlist, desired); if (net != null) { // found network while (!upNodables.isEmpty()) { Nodable no = upNodables.pop(); net = HierarchyEnumerator.getNetworkInChild(net, no); if (net == null) break; } if (net != null) hl.addNetwork(net, subCell); break; } // see if this name is really a current source if (desired.startsWith("I(v")) { NodeInst ni = subCell.findNode(desired.substring(3)); if (ni != null) hl.addElectricObject(ni, subCell); } if (subContext == VarContext.globalContext) break; subCell = subContext.getNodable().getParent(); upNodables.push(subContext.getNodable()); subContext = subContext.pop(); } } } // also highlight anything selected in the "SIGNALS" tree String contextStr = context.getInstPath("."); Cell topContext = sd.getCell(); String altContextStr = contextStr; if (topContext != null) { altContextStr = topContext.getName().toLowerCase(); if (contextStr.length() > 0) altContextStr += "." + contextStr; } ExplorerTree sigTree = wf.getExplorerTab(); Object nodeInfo = sigTree.getCurrentlySelectedObject(0); if (nodeInfo != null && nodeInfo instanceof Signal) { Signal sig = (Signal)nodeInfo; if (sig.getSignalContext() == null || sig.getSignalContext().equals(contextStr) || sig.getSignalContext().equals(altContextStr)) { String desired = sig.getSignalName(); Network net = findNetwork(netlist, desired); if (net != null) { if (net.getParent() == cell) hl.addNetwork(net, cell); } else { // see if this name is really a current source if (desired.startsWith("I(v")) { NodeInst ni = cell.findNode(desired.substring(3)); if (ni != null) hl.addElectricObject(ni, cell); } } } } hl.finished(); } freezeWaveformHighlighting = false; } private Map<Network,Integer> netValues; /** * Method to update associated layout windows when the main cursor changes. */ private void updateAssociatedLayoutWindow() { // this only works for digital simulation if (sd.isAnalog()) return; // make sure there is a layout/schematic window being simulated WindowFrame oWf = findSchematicsWindow(); if (oWf == null) return; EditWindow schemWnd = (EditWindow)oWf.getContent(); boolean crossProbeChanged = schemWnd.hasCrossProbeData(); schemWnd.clearCrossProbeLevels(); Cell cell = getCell(); Netlist netlist = cell.getNetlist(); if (netlist == null) { System.out.println("Sorry, a deadlock aborted crossprobing (network information unavailable). Please try again"); return; } // reset all values on networks netValues = new HashMap<Network,Integer>(); // assign values from simulation window traces to networks for(Panel wp : wavePanels) { if (wp.isHidden()) continue; for(WaveSignal ws : wp.getSignals()) { DigitalSignal ds = (DigitalSignal)ws.getSignal(); List<DigitalSignal> bussedSignals = ds.getBussedSignals(); if (bussedSignals != null) { // a digital bus trace for(Signal subSig : bussedSignals) { DigitalSignal subDS = (DigitalSignal)subSig; putValueOnTrace(subDS, cell, netValues, netlist); } } else { // single signal putValueOnTrace(ds, cell, netValues, netlist); } } } // light up any simulation-probe objects for(Iterator<NodeInst> it = cell.getNodes(); it.hasNext(); ) { NodeInst ni = it.next(); if (ni.getProto() != Generic.tech().simProbeNode) continue; Network net = null; for(Iterator<Connection> cIt = ni.getConnections(); cIt.hasNext(); ) { Connection con = cIt.next(); net = netlist.getNetwork(con.getArc(), 0); break; } if (net == null) continue; Integer state = netValues.get(net); if (state == null) continue; Color col = getHighlightColor(state.intValue()); schemWnd.addCrossProbeBox(ni.getBounds(), col); crossProbeChanged = true; netValues.remove(net); } // redraw all arcs in the layout/schematic window for(Iterator<ArcInst> it = cell.getArcs(); it.hasNext(); ) { ArcInst ai = it.next(); int width = netlist.getBusWidth(ai); for(int i=0; i<width; i++) { Network net = netlist.getNetwork(ai, i); Integer state = netValues.get(net); if (state == null) continue; Color col = getHighlightColor(state.intValue()); schemWnd.addCrossProbeLine(ai.getHeadLocation(), ai.getTailLocation(), col); crossProbeChanged = true; } } // if anything changed, queue the window for redisplay if (crossProbeChanged) schemWnd.repaint(); } /** * Method to convert a digital state to a color. * The color is used when showing cross-probed levels in the EditWindow. * The colors used to be user-selectable, but are not yet. * @param state the digital state from the Waveform Window. * @return the color to display in the EditWindow. */ private Color getHighlightColor(int state) { // determine trace color switch (state & Stimuli.LOGIC) { case Stimuli.LOGIC_LOW: return new Color(User.getColor(User.ColorPrefType.WAVE_CROSS_LOW)); case Stimuli.LOGIC_HIGH: return new Color(User.getColor(User.ColorPrefType.WAVE_CROSS_HIGH)); case Stimuli.LOGIC_X: return new Color(User.getColor(User.ColorPrefType.WAVE_CROSS_UNDEF)); case Stimuli.LOGIC_Z: return new Color(User.getColor(User.ColorPrefType.WAVE_CROSS_FLOAT)); } return Color.RED; } private void putValueOnTrace(DigitalSignal ds, Cell cell, Map<Network,Integer> netValues, Netlist netlist) { // set simulation value on the network in the associated layout/schematic window Network net = findNetwork(netlist, ds.getSignalName()); if (net == null) return; // find the proper data for the main cursor int numEvents = ds.getNumEvents(); int state = Stimuli.LOGIC_X; for(int i=numEvents-1; i>=0; i--) { double xValue = ds.getTime(i); if (xValue <= mainXPosition) { state = ds.getState(i) & Stimuli.LOGIC; break; } } netValues.put(net, new Integer(state)); } /** * Method called when the main or extension cursors should be centered. * @param main true for the main cursor, false for the extension cursor. */ public void centerCursor(boolean main) { boolean havePanel = false; double lowXValue = 0, highXValue = 0; for(Panel wp : wavePanels) { double low = wp.getMinXAxis(); double high = wp.getMaxXAxis(); if (havePanel) { lowXValue = Math.max(lowXValue, low); highXValue = Math.min(highXValue, high); } else { lowXValue = low; highXValue = high; havePanel = true; } } if (!havePanel) return; double center = (lowXValue + highXValue) / 2; if (main) setMainXPositionCursor(center); else setExtensionXPositionCursor(center); for(Panel wp : wavePanels) { wp.repaintWithRulers(); } } // ************************************* STIMULI CONTROL ************************************* /** * Method to update the Simulation data for this waveform window. * When new data is read from disk, this is used. * @param sd new simulation data for this window. */ public void setSimData(Stimuli sd) { if (this.sd != null) this.sd.finished(); this.sd = sd; // reset all bounds calculations for(Iterator<Analysis> it = sd.getAnalyses(); it.hasNext(); ) { Analysis an = it.next(); an.setBoundsDirty(); } // reload the sweeps resetSweeps(); // adjust the overall X axis signal (if it is not time) String oldXAxisSignalAllName = null; if (xAxisSignalAll != null) { oldXAxisSignalAllName = xAxisSignalAll.getFullName(); xAxisSignalAll = null; } List<Panel> panelList = new ArrayList<Panel>(); for(Panel wp : wavePanels) panelList.add(wp); for(Panel wp : panelList) { Analysis<Signal> an = sd.findAnalysis(wp.getAnalysisType()); boolean redoPanel = false; // adjust the panel's X axis signal (if it is not time) if (wp.getXAxisSignal() != null) { String oldSigName = wp.getXAxisSignal().getFullName(); wp.setXAxisSignal(null); for(Signal newSs : an.getSignals()) { String newSigName = newSs.getFullName(); if (!newSigName.equals(oldSigName)) continue; wp.setXAxisSignal(newSs); break; } if (wp.getXAxisSignal() == null) { System.out.println("Could not find X axis signal " + oldSigName + " in the new data"); redoPanel = true; } } if (oldXAxisSignalAllName != null) { for(Signal newSs : an.getSignals()) { String newSigName = newSs.getFullName(); if (!newSigName.equals(oldXAxisSignalAllName)) continue; xAxisSignalAll = newSs; break; } } // adjust all signals inside the panel for(WaveSignal ws : wp.getSignals()) { Signal ss = ws.getSignal(); if (ss instanceof DigitalSignal && ((DigitalSignal)ss).getBussedSignals() != null) { List<DigitalSignal> inBus = ((DigitalSignal)ss).getBussedSignals(); for(int b=0; b<inBus.size(); b++) { DigitalSignal subDS = inBus.get(b); String oldSigName = subDS.getFullName(); DigitalSignal newBus = null; for(Signal newSs : an.getSignals() ) { String newSigName = newSs.getFullName(); if (!newSigName.equals(oldSigName)) continue; newBus = (DigitalSignal)newSs; break; } if (newBus == null) { inBus.remove(b); b--; System.out.println("Could not find signal " + oldSigName + " in the new data"); redoPanel = true; continue; } inBus.set(b, newBus); } } else { // single signal: find the name in the new list String oldSigName = ss.getFullName(); ws.setSignal(null); for(Signal newSs : an.getSignals()) { String newSigName = newSs.getFullName(); if (!newSigName.equals(oldSigName)) continue; ws.setSignal(newSs); break; } if (ws.getSignal() == null) { System.out.println("Could not find signal " + oldSigName + " in the new data"); redoPanel = true; } } } while (redoPanel) { redoPanel = false; for(WaveSignal ws : wp.getSignals()) { Signal s = ws.getSignal(); if (s == null || (s instanceof DigitalSignal && ((DigitalSignal)s).getBussedSignals() != null && ((DigitalSignal)s).getBussedSignals().size() == 0)) { redoPanel = true; if (wp.getSignalButtons() != null) wp.removeSignal(ws.getButton()); break; } } } if (wp.getNumSignals() == 0) { // removed all signals: delete the panel wp.getWaveWindow().closePanel(wp); } else { if (wp.getSignalButtons() != null) { wp.getSignalButtons().validate(); wp.getSignalButtons().repaint(); } wp.repaintContents(); } } if (oldXAxisSignalAllName != null && xAxisSignalAll == null) System.out.println("Could not find main X axis signal " + oldXAxisSignalAllName + " in the new data"); wf.wantToRedoSignalTree(); if (sd.getEngine() != null) System.out.println("Simulation data reloaded from circuit"); else System.out.println("Simulation data reloaded from disk"); } public static WaveformWindow getCurrentWaveformWindow() { WindowFrame current = WindowFrame.getCurrentWindowFrame(); WindowContent content = current.getContent(); if (!(content instanceof WaveformWindow)) { System.out.println("Must select a Waveform window first"); return null; } return (WaveformWindow)content; } /** * Method to write the simulation data as a tab-separated file. */ public static void exportSimulationData() { WindowFrame current = WindowFrame.getCurrentWindowFrame(); WindowContent content = current.getContent(); if (!(content instanceof WaveformWindow)) { System.out.println("Must select a Waveform window first"); return; } WaveformWindow ww = (WaveformWindow)content; String configurationFileName = OpenFile.chooseOutputFile(FileType.TEXT, "Waveform Export File", "wavedata.txt"); if (configurationFileName == null) return; try { PrintWriter printWriter = new PrintWriter(new BufferedWriter(new FileWriter(configurationFileName))); List<Signal> dumpSignals = new ArrayList<Signal>(); List<Integer> dumpSweeps = new ArrayList<Integer>(); List<Waveform> dumpWaveforms = new ArrayList<Waveform>(); for(Panel wp : ww.wavePanels) { if (wp.isHidden()) continue; Signal signalInX = ww.xAxisSignalAll; if (!ww.xAxisLocked) signalInX = wp.getXAxisSignal(); if (signalInX != null) addSignalSweep(signalInX, -1, dumpSignals, dumpSweeps, dumpWaveforms); for(WaveSignal ws : wp.getSignals()) { Signal sig = ws.getSignal(); if (sig instanceof AnalogSignal) { AnalogSignal as = (AnalogSignal)sig; AnalogAnalysis an = as.getAnalysis(); int numSweeps = as.getNumSweeps(); if (numSweeps <= 1) { addSignalSweep(sig, -1, dumpSignals, dumpSweeps, dumpWaveforms); } else { for (int s = 0; s < numSweeps; s++) { if (!ww.isSweepSignalIncluded(an, s)) continue; addSignalSweep(sig, s, dumpSignals, dumpSweeps, dumpWaveforms); } } } else { addSignalSweep(sig, -1, dumpSignals, dumpSweeps, dumpWaveforms); } } } int numEntries = dumpSignals.size() + 1; String [] entries = new String[numEntries]; entries[0] = "TIME"; for(int i=1; i<numEntries; i++) { Signal sig = dumpSignals.get(i-1); entries[i] = sig.getFullName(); int s = dumpSweeps.get(i-1).intValue(); if (s >= 0) { AnalogAnalysis an = (AnalogAnalysis)sig.getAnalysis(); Object sweepObj = an.getSweep(s); entries[i] += "/S=" + sweepObj; } } for(int i=0; i<numEntries; i++) { if (i > 0) printWriter.print("\t"); printWriter.print(entries[i]); } printWriter.println(); double result[] = new double[3]; for(int j=0; ; j++) { // get signal values for this iteration boolean haveData = false; entries[0] = null; for(int i=1; i<numEntries; i++) { entries[i] = ""; Signal sig = dumpSignals.get(i-1); if (sig instanceof AnalogSignal) { Waveform waveform = dumpWaveforms.get(i - 1); if (j < waveform.getNumEvents()) { waveform.getEvent(j, result); if (entries[0] == null) entries[0] = "" + result[0]; entries[i] = "" + result[1]; haveData = true; } } else if (sig instanceof DigitalSignal) { DigitalSignal ds = (DigitalSignal)sig; if (j < ds.getNumEvents()) { if (entries[0] == null) entries[0] = "" + ds.getTime(j); entries[i] = "" + ds.getState(j); haveData = true; } } } if (!haveData) break; if (entries[0] == null) entries[0] = ""; for(int i=0; i<numEntries; i++) { if (i > 0) printWriter.print("\t"); printWriter.print(entries[i]); } printWriter.println(); } printWriter.close(); } catch (IOException e) { System.out.println("Error writing configuration"); return; } System.out.println("Wrote " + configurationFileName); } private static void addSignalSweep(Signal sig, int s, List<Signal> dumpSignals, List<Integer> dumpSweeps, List<Waveform> waveforms) { for(int i=0; i<dumpSignals.size(); i++) { if (dumpSignals.get(i) == sig && dumpSweeps.get(i).intValue() == s) return; } dumpSignals.add(sig); dumpSweeps.add(new Integer(s)); Waveform waveform = null; if (sig instanceof AnalogSignal) waveform = ((AnalogSignal)sig).getWaveform(s == -1 ? 0 : s); waveforms.add(waveform); } /** * Method to refresh simulation data by menu in ToolMenu. This would allow to attach a KeyBinding */ public static void refreshSimulationData() { WindowFrame current = WindowFrame.getCurrentWindowFrame(); WindowContent content = current.getContent(); if (!(content instanceof WaveformWindow)) { System.out.println("Nothing to refresh in non Waveform window"); return; // nothing to do } ((WaveformWindow)content).refreshData(); } /** * Method to clear all panels from the waveform window. */ public static void clearSimulationData() { WindowFrame current = WindowFrame.getCurrentWindowFrame(); WindowContent content = current.getContent(); if (!(content instanceof WaveformWindow)) { System.out.println("Nothing to refresh in non Waveform window"); return; // nothing to do } WaveformWindow ww = (WaveformWindow)content; // clear the display ww.clearAllPanels(); ww.saveSignalOrder(); } /** * Method to remove all panels from the display. */ private void clearAllPanels() { List<Panel> closeList = new ArrayList<Panel>(); for(Panel wp : wavePanels) closeList.add(wp); for(Panel wp : closeList) { closePanel(wp); } } /** * Method to refresh the simulation data from disk. */ private void refreshData() { // // if there is no stimuli file, simulator is built-in: update it // if (sd.getFileURL() == null) // { // sd.getEngine().update(); // return; // } // if there is a simulation engine (i.e. IRISM, ALS) ask simulator to reload circuit if (sd.getEngine() != null) { sd.getEngine().refresh(); return; } // there is no simulation engine (i.e. external Spice, Verilog) reload external data Simulate.plotSimulationResults(sd.getDataType(), sd.getCell(), sd.getFileURL(), this); } /** * Method to save the waveform window configuration to a disk file. */ public static void saveConfiguration() { Cell cell = WindowFrame.needCurCell(); if (cell == null) return; WaveformWindow ww = findWaveformWindow(cell); if (ww == null) { System.out.println("There is no waveform window to save"); return; } String configurationFileName = OpenFile.chooseOutputFile(FileType.TEXT, "Waveform Configuration File", "waveform.txt"); if (configurationFileName == null) return; try { PrintWriter printWriter = new PrintWriter(new BufferedWriter(new FileWriter(configurationFileName))); for(int i=0; i<ww.wavePanels.size(); i++) { Panel wp = ww.wavePanels.get(i); if (wp.isHidden()) continue; boolean first = true; for(WaveSignal ws : wp.getSignals()) { String sigName = ws.getSignal().getFullName(); if (first) { // header first = false; String analysisName = ""; if (wp.getAnalysisType() != null) analysisName = " " + wp.getAnalysisType(); String log = ""; if (wp.isPanelLogarithmicHorizontally()) log = " xlog"; if (wp.isPanelLogarithmicVertically()) log += " ylog"; if (i > 0) printWriter.println(); printWriter.println("panel" + analysisName + log); printWriter.println("zoom " + wp.getYAxisLowValue() + " " + wp.getYAxisHighValue() + " " + wp.getMinXAxis() + " " + wp.getMaxXAxis()); Signal signalInX = ww.xAxisSignalAll; if (!ww.xAxisLocked) signalInX = wp.getXAxisSignal(); if (signalInX != null) printWriter.println("x-axis " + signalInX.getFullName()); } Color color = ws.getColor(); printWriter.println("signal " + sigName + " " + color.getRed() + "," + color.getGreen() + "," + color.getBlue()); } } printWriter.close(); } catch (IOException e) { System.out.println("Error writing configuration"); return; } System.out.println("Wrote " + configurationFileName); } /** * Method to restore the waveform window configuration from a disk file. */ public static void restoreConfiguration() { Cell cell = WindowFrame.needCurCell(); if (cell == null) return; WaveformWindow ww = findWaveformWindow(cell); if (ww == null) { System.out.println("There is no waveform window to restore"); return; } String configurationFileName = OpenFile.chooseInputFile(FileType.TEXT, "Waveform Configuration File"); if (configurationFileName == null) return; // clear the display ww.clearAllPanels(); // read the file URL url = TextUtils.makeURLToFile(configurationFileName); Panel curPanel = null; Analysis.AnalysisType oneType = null; try { URLConnection urlCon = url.openConnection(); InputStreamReader is = new InputStreamReader(urlCon.getInputStream()); LineNumberReader lineReader = new LineNumberReader(is); for(;;) { String buf = lineReader.readLine(); if (buf == null) break; String [] keywords = buf.split(" "); if (keywords.length == 0) continue; if (keywords[0].equals("panel")) { Analysis.AnalysisType analysisType = null; boolean xLog = false, yLog = false; for(int i=1; i<keywords.length; i++) { if (keywords[i].equals("xlog")) xLog = true; else if (keywords[i].equals("ylog")) yLog = true; else { analysisType = Analysis.AnalysisType.findAnalysisType(keywords[i]); if (analysisType != null) { if (oneType == null) oneType = analysisType; if (oneType != analysisType && ww.isXAxisLocked()) ww.togglePanelXAxisLock(); } } } curPanel = new Panel(ww, ww.getSimData().isAnalog(), analysisType); if (xLog) { if (ww.isXAxisLocked()) ww.togglePanelXAxisLock(); curPanel.setPanelLogarithmicHorizontally(true); } if (yLog) curPanel.setPanelLogarithmicVertically(true); continue; } if (keywords[0].equals("zoom")) { if (curPanel == null) continue; double lowYValue = TextUtils.atof(keywords[1]); double highYValue = TextUtils.atof(keywords[2]); double lowXValue = TextUtils.atof(keywords[3]); double highXValue = TextUtils.atof(keywords[4]); curPanel.setXAxisRange(lowXValue, highXValue); if (curPanel.isAnalog()) curPanel.setYAxisRange(lowYValue, highYValue); continue; } if (keywords[0].equals("x-axis")) { if (curPanel == null) continue; Stimuli sd = ww.getSimData(); Analysis an = sd.getAnalyses().next(); if (curPanel.getAnalysisType() != null) an = sd.findAnalysis(curPanel.getAnalysisType()); if (an == null) continue; Signal sig = an.findSignalForNetwork(keywords[1]); if (sig == null) continue; if (ww.isXAxisLocked()) ww.togglePanelXAxisLock(); curPanel.setXAxisSignal(sig); continue; } if (keywords[0].equals("signal")) { if (curPanel == null) continue; Stimuli sd = ww.getSimData(); Analysis an = sd.getAnalyses().next(); if (curPanel.getAnalysisType() != null) an = sd.findAnalysis(curPanel.getAnalysisType()); if (an == null) continue; Signal sig = an.findSignalForNetwork(keywords[1]); if (sig == null) continue; String [] colorNames = keywords[2].split(","); int red = TextUtils.atoi(colorNames[0]); int green = TextUtils.atoi(colorNames[1]); int blue = TextUtils.atoi(colorNames[2]); Color color = new Color(red, green, blue); WaveSignal ws = new WaveSignal(curPanel, sig); ws.setColor(color); continue; } } lineReader.close(); for(Panel panel : ww.wavePanels) { panel.repaintWithRulers(); } ww.saveSignalOrder(); } catch (IOException e) { System.out.println("Error reading " + configurationFileName); return; } } private static Map<CellId,String> savedSignalOrder = new HashMap<CellId,String>(); /** * Method to save the signal ordering on the cell. */ public void saveSignalOrder() { Cell cell = getCell(); if (cell == null) return; StringBuffer sb = new StringBuffer(); for(Panel wp : wavePanels) { boolean first = true; for(WaveSignal ws : wp.getSignals()) { String sigName = ws.getSignal().getFullName(); if (first) { // header begins with a tab sb.append("\t" + wp.getAnalysisType()); Signal signalInX = xAxisSignalAll; if (!xAxisLocked) signalInX = wp.getXAxisSignal(); first = false; if (signalInX != null) sb.append("(" + signalInX.getFullName() + ")"); } sb.append("\t"); sb.append(sigName); Color color = ws.getColor(); sb.append(" {" + color.getRed() + "," + color.getGreen() + "," + color.getBlue() + "}"); } sb.append("\n"); } savedSignalOrder.put(cell.getId(), sb.toString()); } /** * Method called when the program exits to preserve signal ordering in cells. */ public static void preserveSignalOrder() { Pref.delayPrefFlushing(); for(Map.Entry<CellId,String> e: savedSignalOrder.entrySet()) { CellId cellId = e.getKey(); String savedOrder = e.getValue(); Preferences libPrefs = Pref.getLibraryPreferences(cellId.libId); String key = "SavedSignalsForCell" + cellId.cellName.getName(); if (savedOrder.length() == 0) libPrefs.remove(key); else libPrefs.put(key, savedOrder); } Pref.resumePrefFlushing(); } /** * Method to get the saved signal information for a cell. * @param cell the Cell to query. * @return a list of strings, one per waveform window panel, with tab-separated signal names in that panel. * Returns an empty array if nothing is saved. */ public static String [] getSignalOrder(Cell cell) { CellId cellId = cell.getId(); String savedOrder = savedSignalOrder.get(cellId); if (savedOrder == null) { Preferences libPrefs = Pref.getLibraryPreferences(cellId.libId); String key = "SavedSignalsForCell" + cellId.cellName.getName(); savedOrder = libPrefs.get(key, ""); if (savedOrder.length() == 0) return new String[0]; } // convert a single string into an array of strings List<String> panels = new ArrayList<String>(); int startPos = 0; for(;;) { int endCh = savedOrder.indexOf('\n', startPos); if (endCh < 0) break; String panel = savedOrder.substring(startPos, endCh); panels.add(panel); startPos = endCh + 1; } String [] ret = new String[panels.size()]; int i=0; for(String s : panels) ret[i++] = s; return ret; } // ************************************* DISPLAY CONTROL ************************************* public Font getFont() { return waveWindowFont; } public FontRenderContext getFontRenderContext() { return waveWindowFRC; } public Color getOffStrengthColor() { return offStrengthColor; } public Color getNodeStrengthColor() { return nodeStrengthColor; } public Color getGateStrengthColor() { return gateStrengthColor; } public Color getPowerStrengthColor() { return powerStrengthColor; } /** * Method called to toggle the display of vertex points. */ public void toggleShowPoints() { linePointMode = (linePointMode+1) % 3; switch (linePointMode) { case 0: showPoints.setIcon(iconLineOnPointOff); break; case 1: showPoints.setIcon(iconLineOnPointOn); break; case 2: showPoints.setIcon(iconLineOffPointOn); break; } for(Panel wp : wavePanels) { wp.repaintWithRulers(); } } /** * Method to return the drawing mode for analog waves. * @return the drawing mode for analog waves. * 0 means draw lines only; 1 means draw lines and points; 2 means draw points only. */ public int getLinePointMode() { return linePointMode;} /** * Method called to toggle the display of a grid. */ public void toggleGridPoints() { showGrid = !showGrid; for(Panel wp : wavePanels) { wp.repaintWithRulers(); } } public boolean isShowGrid() { return showGrid; } /** * Method to add a signal to the display. * Called when the user double-clicks on the signal in the explorer tree. * @param sig the Signal to add to the display */ public void addSignal(Signal sig) { if (sig instanceof AnalogSignal) { // add analog signal on top of current panel AnalogSignal as = (AnalogSignal)sig; boolean found = false; for(Panel wp : wavePanels) { if (wp.isSelected()) { if (wp.wrongPanelType(as)) return; WaveSignal.addSignalToPanel(sig, wp, null); if (getMainHorizRuler() != null) getMainHorizRuler().repaint(); found = true; break; } } if (!found) { // create a new panel for the signal Panel wp = makeNewPanel(as.getAnalysis()); Rectangle2D rangeBounds = as.getBounds(); double lowValue = rangeBounds.getMinY(); double highValue = rangeBounds.getMaxY(); double range = highValue - lowValue; if (range == 0) range = 2; double rangeExtra = range / 10; wp.setYAxisRange(lowValue - rangeExtra, highValue + rangeExtra); wp.makeSelectedPanel(-1, -1); if (!xAxisLocked) wp.setXAxisRange(as.getLeftEdge(), as.getRightEdge()); WaveSignal.addSignalToPanel(sig, wp, null); if (getMainHorizRuler() != null) getMainHorizRuler().repaint(); } } else { // add digital signal in new panel Panel wp = makeNewPanel(sig.getAnalysis()); new WaveSignal(wp, sig); overall.validate(); wp.repaintContents(); } saveSignalOrder(); } /** * Method called when "delete" command (or key) is given. * If a control point is selected, delete it. * If a single signal of an analog window is selected, remove it. */ public void deleteSelectedSignals() { for(Panel wp : wavePanels) { if (!wp.isSelected()) continue; boolean removedSingleStimuli = false; for(WaveSignal ws : wp.getSignals()) { if (ws.getSelectedControlPoints() != null) { if (sd.getEngine() != null) { if (sd.getEngine().removeSelectedStimuli()) removedSingleStimuli = true; } } } if (wp.getAnalysisType() != null && !removedSingleStimuli) deleteSignalFromPanel(wp); break; } } /** * Method to make the waveform window/panel fill in X only. */ public static void fillInX() { WindowFrame current = WindowFrame.getCurrentWindowFrame(); WindowContent content = current.getContent(); if (!(content instanceof WaveformWindow)) { System.out.println("Must select a Waveform window first"); return; } WaveformWindow ww = (WaveformWindow)content; ww.fillWaveform(1); } /** * Method to make the waveform window/panel fill in Y only. */ public static void fillInY() { WindowFrame current = WindowFrame.getCurrentWindowFrame(); WindowContent content = current.getContent(); if (!(content instanceof WaveformWindow)) { System.out.println("Must select a Waveform window first"); return; } WaveformWindow ww = (WaveformWindow)content; ww.fillWaveform(2); } /** * Method to make the waveform window/panel fill in X and Y. */ public void fillScreen() { fillWaveform(3); } /** * Method to make the stimuli fill the waveform window. * @param how what to fill: 1=X, 2=Y, 3=both. */ private void fillWaveform(int how) { // accumulate bounds for all displayed panels double leftEdge=0, rightEdge=0; for(Panel wp : wavePanels) { if (wp.getXAxisSignal() != null) continue; for(WaveSignal ws : wp.getSignals()) { Rectangle2D bounds = ws.getSignal().getBounds(); if (leftEdge == rightEdge) { leftEdge = bounds.getMinX(); rightEdge = bounds.getMaxX(); } else { leftEdge = Math.min(leftEdge, bounds.getMinX()); rightEdge = Math.max(rightEdge, bounds.getMaxX()); } } // Analysis an = sd.findAnalysis(wp.getAnalysisType()); // if (leftEdge == rightEdge) // { // leftEdge = an.getLeftEdge(); // rightEdge = an.getRightEdge(); // } else // { // if (leftEdge < rightEdge) // { // leftEdge = Math.min(leftEdge, an.getLeftEdge()); // rightEdge = Math.max(rightEdge, an.getRightEdge()); // } else // { // leftEdge = Math.max(leftEdge, an.getLeftEdge()); // rightEdge = Math.min(rightEdge, an.getRightEdge()); // } // } } // if there is an overriding signal on the X axis, use its bounds if (xAxisLocked && xAxisSignalAll != null) { leftEdge = xAxisSignalAll.getLeftEdge(); rightEdge = xAxisSignalAll.getRightEdge(); } for(Panel wp : wavePanels) { if (!xAxisLocked) { if (!wp.isSelected()) continue; // when time is not locked, compute bounds for this panel only Analysis an = sd.findAnalysis(wp.getAnalysisType()); leftEdge = an.getLeftEdge(); rightEdge = an.getRightEdge(); if (wp.getXAxisSignal() != null) { leftEdge = wp.getXAxisSignal().getLeftEdge(); rightEdge = wp.getXAxisSignal().getRightEdge(); } } Rectangle2D yBounds = null; for(WaveSignal ws : wp.getSignals()) { Rectangle2D sigBounds = ws.getSignal().getBounds(); if (yBounds == null) { yBounds = new Rectangle2D.Double(sigBounds.getMinX(), sigBounds.getMinY(), sigBounds.getWidth(), sigBounds.getHeight()); } else { Rectangle2D.union(yBounds, sigBounds, yBounds); } } if (yBounds == null) { Analysis an = sd.findAnalysis(wp.getAnalysisType()); Rectangle2D anBounds = an.getBounds(); if (anBounds != null) yBounds = new Rectangle2D.Double(anBounds.getMinX(), anBounds.getMinY(), anBounds.getWidth(), anBounds.getHeight()); } boolean repaint = false; if (leftEdge != rightEdge) { if (wp.getMinXAxis() != leftEdge || wp.getMaxXAxis() != rightEdge) { // only if X filling is requested if ((how&1) != 0) { wp.setXAxisRange(leftEdge, rightEdge); repaint = true; } } } if (yBounds != null) { double lowValue = yBounds.getMinY(); double highValue = yBounds.getMaxY(); double valueRange = (highValue - lowValue) / 8; if (valueRange == 0) valueRange = 0.5; lowValue -= valueRange; highValue += valueRange; if (wp.getYAxisLowValue() != lowValue || wp.getYAxisHighValue() != highValue) { // only if Y filling is requested if ((how&2) != 0) { wp.setYAxisRange(lowValue, highValue); repaint = true; } } } if (repaint) { wp.repaintWithRulers(); } } } public void zoomOutContents() { for(Panel wp : wavePanels) { if (!xAxisLocked && !wp.isSelected()) continue; boolean timeInXAxis = true; if (xAxisLocked) { if (xAxisSignalAll != null) timeInXAxis = false; } else { if (wp.getXAxisSignal() != null) timeInXAxis = false; } double range = wp.getMaxXAxis() - wp.getMinXAxis(); wp.setXAxisRange(wp.getMinXAxis() - range/2, wp.getMaxXAxis() + range/2); if (wp.getMinXAxis() < 0 && timeInXAxis) { wp.setXAxisRange(0, wp.getMaxXAxis() - wp.getMinXAxis()); } wp.repaintWithRulers(); } } public void zoomInContents() { for(Panel wp : wavePanels) { if (!xAxisLocked && !wp.isSelected()) continue; double range = wp.getMaxXAxis() - wp.getMinXAxis(); wp.setXAxisRange(wp.getMinXAxis() + range/4, wp.getMaxXAxis() - range/4); wp.repaintWithRulers(); } } public void focusOnHighlighted() { if (mainXPosition == extXPosition) return; double maxXPosition, minXPosition; if (mainXPosition > extXPosition) { double size = (mainXPosition-extXPosition) / 20.0; maxXPosition = mainXPosition + size; minXPosition = extXPosition - size; } else { double size = (extXPosition-mainXPosition) / 20.0; maxXPosition = extXPosition + size; minXPosition = mainXPosition - size; } for(Panel wp : wavePanels) { if (!xAxisLocked && !wp.isSelected()) continue; if (wp.getMinXAxis() != minXPosition || wp.getMaxXAxis() != maxXPosition) { if (wp.getMinXAxis() > wp.getMaxXAxis()) wp.setXAxisRange(maxXPosition, minXPosition); else wp.setXAxisRange(minXPosition, maxXPosition); wp.repaintWithRulers(); } } } public boolean isWaveWindowLogarithmic() { return mainHorizRulerPanelLogarithmic; } public void setWaveWindowLogarithmic(boolean logarithmic) { mainHorizRulerPanelLogarithmic = logarithmic; mainHorizRulerPanel.repaint(); } // ************************************* DRAG AND DROP CLASSES ************************************* /** * This class extends JPanel so that components of the Waveform window can be identified by the Drag and Drop system * and by the key binding manager. */ public static class OnePanel extends JPanel { Panel panel; WaveformWindow ww; public OnePanel(Panel panel, WaveformWindow ww) { super(); this.panel = panel; this.ww = ww; } public Panel getPanel() { return panel; } public WaveformWindow getWaveformWindow() { return ww; } } private static class WaveFormDropTarget implements DropTargetListener { public void dragEnter(DropTargetDragEvent e) { e.acceptDrag(e.getDropAction()); } public void dragOver(DropTargetDragEvent e) { e.acceptDrag(e.getDropAction()); } public void dropActionChanged(DropTargetDragEvent e) { e.acceptDrag(e.getDropAction()); } public void dragExit(DropTargetEvent e) {} /** * Entry point when something is dropped onto the waveform window. * The data that is transferred is always a string with these values: * * "PANEL #" means an entire panel has been dragged (user has rearranged panels) * Drag originates in Panel.DragLabel.dragGestureRecognized(). * * "TRANS sig" / "DC sig" / "AC sig" / "MEASUREMENT sig" means * a transient/DC/AC/measurement signal has been dragged (from the Explorer tree) * Drag originates in ExplorerTree.dragGestureRecognized(). * Multiple signals may be combined, separated by Newline. * * "PANEL # MOVEBUTTON sig" / "PANEL # COPYBUTTON sig" means a signal has been * moved or copied (from one panel to another). * Drag originates in DragButton.dragGestureRecognized(). */ public void drop(DropTargetDropEvent dtde) { // get information about the drop (such as the signal name) Object data = null; try { dtde.acceptDrop(DnDConstants.ACTION_LINK); data = dtde.getTransferable().getTransferData(DataFlavor.stringFlavor); if (data == null) { dtde.dropComplete(false); return; } } catch (Throwable t) { ActivityLogger.logException(t); dtde.dropComplete(false); return; } if (!(data instanceof String)) { dtde.dropComplete(false); return; } String sigNameData = (String)data; String [] sigNames = sigNameData.split("\n"); Analysis.AnalysisType analysisType = null; for(int i=0; i<sigNames.length; i++) { Analysis.AnalysisType anAnalysisType = Analysis.ANALYSIS_SIGNALS; String aSigName = sigNames[i]; if (aSigName.startsWith("TRANS ")) { sigNames[i] = aSigName.substring(6); anAnalysisType = Analysis.ANALYSIS_TRANS; } else if (aSigName.startsWith("MEASUREMENT ")) { sigNames[i] = aSigName.substring(12); anAnalysisType = Analysis.ANALYSIS_MEAS; } else if (aSigName.startsWith("AC ")) { sigNames[i] = aSigName.substring(3); anAnalysisType = Analysis.ANALYSIS_AC; } else if (aSigName.startsWith("DC ")) { sigNames[i] = aSigName.substring(3); anAnalysisType = Analysis.ANALYSIS_DC; } if (analysisType == null) analysisType = anAnalysisType; else { if (analysisType != anAnalysisType) { Job.getUserInterface().showErrorMessage("All signals must be the same type", "Incorrect Signal Selection"); dtde.dropComplete(false); return; } } } if (analysisType == null) { dtde.dropComplete(false); return; } // see if the signal was dropped onto a ruler panel (setting x-axis) DropTarget dt = (DropTarget)dtde.getSource(); if (dt.getComponent() instanceof HorizRuler) { // dragged a signal to the ruler panel: make sure only one signal was selected if (sigNames.length != 1) { Job.getUserInterface().showErrorMessage("Only one signal can be dragged to a ruler", "Too Much Selected"); dtde.dropComplete(false); return; } HorizRuler hr = (HorizRuler)dt.getComponent(); Panel panel = hr.getPanel(); WaveformWindow ww = hr.getWaveformWindow(); // find the signal that was dragged Signal sSig = null; if (sigNames[0].startsWith("PANEL ")) { // get signal when dragged from inside the waveform window int sigPos = Math.max(sigNames[0].indexOf("MOVEBUTTON "), sigNames[0].indexOf("COPYBUTTON ")); if (sigPos >= 0) { // dragging from waveform window signal to horizontal ruler int panelNumber = TextUtils.atoi(sigNames[0].substring(6)); Panel sourcePanel = ww.getPanelFromNumber(panelNumber); analysisType = sourcePanel.getAnalysisType(); String signalName = sigNames[0].substring(sigPos + 11); for(WaveSignal ws : sourcePanel.getSignals()) { if (!ws.getSignal().getFullName().equals(signalName)) continue; sSig = ws.getSignal(); break; } } } else { Analysis an = ww.getSimData().findAnalysis(analysisType); if (an == null) { System.out.println("Cannot find " + analysisType + " data"); dtde.dropComplete(true); return; } sSig = ww.findSignal(sigNames[0], an); } if (sSig != null) { if (panel == null) { // dropped signal onto main time ruler: make sure it is the right type boolean warn = false; for(Panel wp : ww.wavePanels) { if (wp.getAnalysisType() != analysisType && wp.getNumSignals() > 0) warn = true; } if (warn) { String warning = "The waveform window is not showing " + analysisType + " data. Remove all traces and convert panels to show " + analysisType + " data?"; int response = JOptionPane.showConfirmDialog(Main.getCurrentJFrame(), warning); if (response != JOptionPane.YES_OPTION) { dtde.dropComplete(true); return; } for(Panel wp : ww.wavePanels) ww.deleteAllSignalsFromPanel(wp); } ww.xAxisSignalAll = sSig; for(Panel wp : ww.wavePanels) { wp.setAnalysisType(analysisType); wp.setXAxisRange(sSig.getLeftEdge(), sSig.getRightEdge()); } ww.redrawAllPanels(); } else { // dropped signal onto a single panel's time ruler if (panel.getAnalysisType() != analysisType) { JOptionPane.showMessageDialog(Main.getCurrentJFrame(), "Cannot drop a " + analysisType + " signal onto the horizontal ruler of a " + panel.getAnalysisType() + " panel. " + "First convert the panel with the popup in the upper-left.", "Error Displaying Signals", JOptionPane.ERROR_MESSAGE); dtde.dropComplete(true); return; } panel.setXAxisSignal(sSig); panel.setXAxisRange(sSig.getLeftEdge(), sSig.getRightEdge()); panel.repaintContents(); } hr.repaint(); ww.saveSignalOrder(); } dtde.dropComplete(false); return; } // determine which panel was the target of the drop WaveformWindow ww = null; Panel panel = null; if (dt.getComponent() instanceof Panel) { panel = (Panel)dt.getComponent(); ww = panel.getWaveWindow(); } if (dt.getComponent() instanceof OnePanel) { OnePanel op = (OnePanel)dt.getComponent(); ww = op.getWaveformWindow(); panel = op.getPanel(); } if (dt.getComponent() instanceof WaveTable) { WaveTable table = (WaveTable)dt.getComponent(); ww = table.ww; int row = table.rowAtPoint(dtde.getLocation()); if (row != -1) { Object obj = ww.tableModel.getValueAt(row, 0); OnePanel op = (OnePanel)obj; panel = op.getPanel(); } } if (panel == null) { dtde.dropComplete(false); return; } // see if rearranging the waveform window if (sigNames[0].startsWith("PANEL ")) { // rearranging signals and panels int panelNumber = TextUtils.atoi(sigNames[0].substring(6)); Panel sourcePanel = ww.getPanelFromNumber(panelNumber); if (sourcePanel == panel) { // moved to same panel dtde.dropComplete(false); return; } // see if a signal button was grabbed int sigMovePos = sigNames[0].indexOf("MOVEBUTTON "); int sigCopyPos = sigNames[0].indexOf("COPYBUTTON "); if (!panel.isAnalog()) sigMovePos = sigCopyPos = -1; if (sigMovePos < 0 && sigCopyPos < 0) { // moving the entire panel if (USETABLES) { ww.stopEditing(); ww.wavePanels.remove(sourcePanel); int destIndex = ww.wavePanels.indexOf(panel); if (dtde.getLocation().y > panel.getBounds().height/2) destIndex++; ww.wavePanels.add(destIndex, sourcePanel); ww.reloadTable(); ww.table.repaint(); } else { ww.left.remove(sourcePanel.getLeftHalf()); ww.right.remove(sourcePanel.getRightHalf()); int destIndex = 0; Component [] lefts = ww.left.getComponents(); for(destIndex=0; destIndex < lefts.length; destIndex++) { if (lefts[destIndex] == panel.getLeftHalf()) break; } if (dtde.getLocation().y > panel.getBounds().height/2) destIndex++; ww.left.add(sourcePanel.getLeftHalf(), destIndex); ww.right.add(sourcePanel.getRightHalf(), destIndex); } ww.getPanel().validate(); dtde.dropComplete(true); ww.saveSignalOrder(); return; } // moving/copying a signal (analog only) int sigPos = Math.max(sigMovePos, sigCopyPos); String signalName = sigNames[0].substring(sigPos + 11); Signal sSig = null; Color oldColor = null; for(WaveSignal ws : sourcePanel.getSignals()) { if (!ws.getSignal().getFullName().equals(signalName)) continue; sSig = ws.getSignal(); if (panel.wrongPanelType(sSig)) { dtde.dropComplete(true); return; } oldColor = ws.getColor(); if (sigCopyPos < 0) { sourcePanel.removeHighlightedSignal(ws, true); sourcePanel.removeSignal(ws.getButton()); } break; } if (sSig != null) { sourcePanel.getSignalButtons().validate(); sourcePanel.getSignalButtons().repaint(); sourcePanel.repaintContents(); WaveSignal.addSignalToPanel(sSig, panel, oldColor); } ww.saveSignalOrder(); dtde.dropComplete(true); return; } // not rearranging: dropped a signal onto a panel Analysis an = ww.getSimData().findAnalysis(analysisType); for(int i=0; i<sigNames.length; i++) { Signal sSig = ww.findSignal(sigNames[i], an); if (sSig == null) { dtde.dropComplete(false); return; } // digital signals are always added in new panels if (sSig instanceof DigitalSignal) panel = null; if (panel != null) { // overlay this signal onto an existing panel AnalogSignal as = (AnalogSignal)sSig; if (panel.wrongPanelType(as)) { dtde.dropComplete(true); return; } WaveSignal.addSignalToPanel(sSig, panel, null); panel.makeSelectedPanel(-1, -1); continue; } // add this signal in a new panel panel = ww.makeNewPanel(sSig.getAnalysis()); panel.fitToSignal(sSig); new WaveSignal(panel, sSig); } ww.overall.validate(); panel.repaintContents(); panel.getWaveWindow().saveSignalOrder(); dtde.dropComplete(true); } } // ************************************* CLASS TO ASSOCIATE WAVEFORM WINDOWS WITH EDIT WINDOWS ************************************* /** * Class to find the WaveformWindow associated with the cell in a given EditWindow. * May have to climb the hierarchy to find the top-level cell that is being simulated. */ public static class Locator { private WaveformWindow ww; private VarContext context; /** * The constructor takes an EditWindow and locates the associated WaveformWindow. * It may have to climb the hierarchy to find it. * @param wnd the EditWindow that is being simulated. */ public Locator(EditWindow wnd) { Cell cellInWindow = wnd.getCell(); VarContext curContext = wnd.getVarContext(); ww = null; Stack<Nodable> contextStack = new Stack<Nodable>(); for(;;) { ww = WaveformWindow.findWaveformWindow(cellInWindow); if (ww != null) break; Nodable no = curContext.getNodable(); if (no == null) break; contextStack.push(no); cellInWindow = no.getParent(); curContext = curContext.pop(); //context = no.getName() + "." + context; } context = VarContext.globalContext; while (!contextStack.isEmpty()) { context = context.push(contextStack.pop()); } } /** * The constructor takes an EditWindow and a WaveformWindow and determines whether they are associated. * It may have to climb the hierarchy to find out. * @param wnd the EditWindow that is being simulated. * @param wantWW the WaveformWindow that is being associated. */ public Locator(EditWindow wnd, WaveformWindow wantWW) { Cell cellInWindow = wnd.getCell(); VarContext curContext = wnd.getVarContext(); ww = null; Stack<Nodable> contextStack = new Stack<Nodable>(); for(;;) { if (wantWW.getCell() == cellInWindow) { ww = wantWW; break; } Nodable no = curContext.getNodable(); if (no == null) break; contextStack.push(no); cellInWindow = no.getParent(); curContext = curContext.pop(); } context = VarContext.globalContext; while (!contextStack.isEmpty()) { context = context.push(contextStack.pop()); } } /** * Method to return the WaveformWindow found by this locator class. * @return the WaveformWindow associated with the EditWindow given to the contructor. * Returns null if no WaveformWindow could be found. */ public WaveformWindow getWaveformWindow() { return ww; } /** * Method to return the context of all signals in the EditWindow given to the constructor. * @return the context to prepend to all signals in the EditWindow. * If the EditWindow is directly associated with a WaveformWindow, returns "". */ public VarContext getContext() { return context; } } // ************************************* HIGHLIGHT LISTENER FOR ALL WAVEFORM WINDOWS ************************************* private static class WaveformWindowHighlightListener implements HighlightListener { /** * Method to highlight waveform signals corresponding to circuit networks that are highlighted. * Method is called when any edit window changes its highlighting. */ public void highlightChanged(Highlighter which) { // if this is a response to crossprobing from waveform to schematic, stop now if (freezeWaveformHighlighting) return; // find the EditWindow that this change comes from WindowFrame highWF = which.getWindowFrame(); if (highWF == null) return; if (!(highWF.getContent() instanceof EditWindow)) return; EditWindow wnd = (EditWindow)highWF.getContent(); // loop through all windows, looking for waveform windows if (which == wnd.getHighlighter()) { for(Iterator<WindowFrame> wIt = WindowFrame.getWindows(); wIt.hasNext(); ) { WindowFrame wf = wIt.next(); if (!(wf.getContent() instanceof WaveformWindow)) continue; WaveformWindow ww = (WaveformWindow)wf.getContent(); ww.crossProbeEditWindowToWaveform(wnd, which); } } } /** * Called when by a Highlighter when it loses focus. The argument * is the Highlighter that has gained focus (may be null). * @param highlighterGainedFocus the highlighter for the current window (may be null). */ public void highlighterLostFocus(Highlighter highlighterGainedFocus) {} } // ************************************* HELPER CLASS FOR WAVEFORM WINDOW ************************************* private static class WaveComponentListener implements ComponentListener { private JPanel panel; public WaveComponentListener(JPanel panel) { this.panel = panel; } public void componentHidden(ComponentEvent e) {} public void componentMoved(ComponentEvent e) {} public void componentResized(ComponentEvent e) { panel.repaint(); } public void componentShown(ComponentEvent e) {} } public void propertyChange(PropertyChangeEvent e) { if (!USETABLES) { if (e.getPropertyName().equals("dividerLocation")) { if (mainHorizRulerPanel != null) mainHorizRulerPanel.repaint(); } } } }