/*******************************************************************************
* This file is part of logisim-evolution.
*
* logisim-evolution 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.
*
* logisim-evolution 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 logisim-evolution. If not, see <http://www.gnu.org/licenses/>.
*
* Original code by Carl Burch (http://www.cburch.com), 2011.
* Subsequent modifications by :
* + Haute École Spécialisée Bernoise
* http://www.bfh.ch
* + Haute École du paysage, d'ingénierie et d'architecture de Genève
* http://hepia.hesge.ch/
* + Haute École d'Ingénierie et de Gestion du Canton de Vaud
* http://www.heig-vd.ch/
* The project is currently maintained by :
* + REDS Institute - HEIG-VD
* Yverdon-les-Bains, Switzerland
* http://reds.heig-vd.ch
*******************************************************************************/
package com.hepia.logisim.chronogui;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.AdjustmentEvent;
import java.awt.event.AdjustmentListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.util.prefs.Preferences;
import javax.swing.JButton;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JToolBar;
import javax.swing.SwingUtilities;
import com.cburch.logisim.circuit.Simulator;
import com.cburch.logisim.file.Options;
import com.cburch.logisim.gui.generic.LFrame;
import com.cburch.logisim.gui.log.LogFrame;
import com.cburch.logisim.instance.StdAttr;
import com.cburch.logisim.proj.Project;
import com.cburch.logisim.std.wiring.Clock;
import com.cburch.logisim.util.Icons;
import com.hepia.logisim.chronodata.ChronoData;
import com.hepia.logisim.chronodata.ChronoDataWriter;
import com.hepia.logisim.chronodata.ChronoModelEventHandler;
import com.hepia.logisim.chronodata.NoSysclkException;
import com.hepia.logisim.chronodata.SignalDataBus;
import com.hepia.logisim.chronodata.TimelineParam;
import java.io.File;
/**
* Main chronogram JFrame Creates the chronogram
*/
public class ChronoFrame extends LFrame implements KeyListener, ActionListener,
WindowListener {
JFileChooser fc;
/**
* Listener to the button, the scrollbars, splitPane divider
*/
private class MyListener implements ActionListener, AdjustmentListener {
private ChronoFrame chronoFrame;
public MyListener(ChronoFrame cf) {
chronoFrame = cf;
}
/**
* Load or export button event handler
*/
@Override
public void actionPerformed(ActionEvent e) {
// load a chronogram from a file
if ("load".equals(e.getActionCommand())) {
final JFileChooser fc = new JFileChooser();
int returnVal = fc.showOpenDialog(chronoFrame);
if (returnVal == JFileChooser.APPROVE_OPTION) {
chronoFrame
.loadFile(fc.getSelectedFile().getAbsolutePath());
}
// export a chronogram to a file
} else if ("export".equals(e.getActionCommand())) {
final JFileChooser fc = new JFileChooser();
int returnVal = fc.showSaveDialog(chronoFrame);
if (returnVal == JFileChooser.APPROVE_OPTION) {
chronoFrame.exportFile(fc.getSelectedFile().getAbsolutePath());
}
}
else if ("exportImg".equals(e.getActionCommand())) {
fc = new JFileChooser();
int returnVal = fc.showSaveDialog(ChronoFrame.this);
if (returnVal == JFileChooser.APPROVE_OPTION) {
File file = fc.getSelectedFile();
//add .png to the filename if the user forgot
if (!fc.getSelectedFile().getAbsolutePath().endsWith(".png")) {
file = new File(fc.getSelectedFile() + ".png");
}
exportImage(file);
}
} else if ("play".equals(e.getActionCommand())) {
if (simulator.isRunning()) {
((JButton) e.getSource()).setIcon(Icons
.getIcon("simplay.png"));
} else {
((JButton) e.getSource()).setIcon(Icons
.getIcon("simstop.png"));
}
simulator.setIsRunning(!simulator.isRunning());
} else if ("step".equals(e.getActionCommand())) {
simulator.step();
} else if ("tplay".equals(e.getActionCommand())) {
if (simulator.isTicking()) {
((JButton) e.getSource()).setIcon(Icons
.getIcon("simtplay.png"));
} else {
((JButton) e.getSource()).setIcon(Icons
.getIcon("simtstop.png"));
}
simulator.setIsTicking(!simulator.isTicking());
} else if ("tstep".equals(e.getActionCommand())) {
simulator.tick();
} else if ("tmainstep".equals(e.getActionCommand())) {
tickMain();
}
}
/**
* rightScroll horizontal movement
*/
@Override
public void adjustmentValueChanged(AdjustmentEvent e) {
if (rightPanel != null) {
rightPanel.adjustmentValueChanged(e.getValue());
}
}
}
private static final long serialVersionUID = 1L;
private Simulator simulator;
private Project project;
private ChronoData chronogramData;
private JPanel mainPanel;
private Preferences prefs;
private LogFrame logFrame;
// top bar
private JPanel topBar;
private JButton chooseFileButton;
private JButton exportDataInFile;
private JButton exportDataToImage;
private JLabel statusLabel;
// split pane
private RightPanel rightPanel;
private LeftPanel leftPanel;
private CommonPanelParam commonPanelParam;
private JScrollPane leftScroll;
private JScrollPane rightScroll;
private JSplitPane mainSplitPane;
private TimelineParam timelineParam;
// event managers
private MyListener myListener = new MyListener(this);
private DrawAreaEventManager mDrawAreaEventManager;
private DrawAreaManager mDrawAreaManager;
// graphical
private int dividerLocation = 353;
// mode
private boolean realTimeMode;
private ChronoModelEventHandler chronoModelEventHandler;
// menu
private JMenuBar winMenuBar;
private JCheckBoxMenuItem ontopItem;
private JMenuItem close;
/**
* Offline mode ChronoFrame constructor
*/
public ChronoFrame(Project prj) {
realTimeMode = false;
commonPanelParam = new CommonPanelParam(20, 38);
createMainStructure();
}
/**
* Real time mode ChronoFrame constructor
*/
public ChronoFrame(Project prj, LogFrame logFrame) {
realTimeMode = true;
this.logFrame = logFrame;
timelineParam = logFrame.getTimelineParam();
commonPanelParam = new CommonPanelParam(20, 38);
project = prj;
simulator = prj.getSimulator();
chronogramData = new ChronoData();
try {
chronoModelEventHandler = new ChronoModelEventHandler(this,
logFrame.getModel(), prj);
createMainStructure();
fillMainSPlitPane();
if (chronogramData.size() == 0) {
statusLabel.setText(Strings.get("SimStatusNoSignal"));
} else {
statusLabel.setText(Strings.get("SimStatusCurrentScheme"));
}
} catch (NoSysclkException ex) {
createMainStructure();
statusLabel.setText(Strings.get("SimStatusNoSysclk"));
}
}
@Override
public void actionPerformed(ActionEvent ae) {
Object src = ae.getSource();
if (src == ontopItem) {
ae.paramString();
setAlwaysOnTop(ontopItem.getState());
} else if (src == close) {
setVisible(false);
}
}
/**
* Creates the main panels for the chronogram
*/
private void createMainStructure() {
mDrawAreaEventManager = new DrawAreaEventManager();
mDrawAreaManager = new DrawAreaManager(this);
mDrawAreaEventManager.addDrawAreaListener(mDrawAreaManager);
mainPanel = new JPanel();
mainPanel.setLayout(new BorderLayout());
mainPanel.setFocusable(true);
mainPanel.requestFocus();
mainPanel.addKeyListener(this);
addWindowListener(this);
// menu bar
winMenuBar = new JMenuBar();
JMenu windowMenu = new JMenu("Window");
windowMenu.setMnemonic('W');
ontopItem = new JCheckBoxMenuItem("Set on top", true);
ontopItem.addActionListener(this);
close = new JMenuItem("Close");
close.addActionListener(this);
windowMenu.add(ontopItem);
windowMenu.addSeparator();
windowMenu.add(close);
winMenuBar.add(windowMenu);
winMenuBar.setFocusable(false);
setJMenuBar(winMenuBar);
//setAlwaysOnTop(true);
// top bar
Dimension buttonSize = new Dimension(150, 25);
topBar = new JPanel();
topBar.setLayout(new FlowLayout(FlowLayout.LEFT));
// external file
chooseFileButton = new JButton(Strings.get("ButtonLoad"));
chooseFileButton.setActionCommand("load");
chooseFileButton.addActionListener(myListener);
chooseFileButton.setPreferredSize(buttonSize);
chooseFileButton.setFocusable(false);
// export
exportDataInFile = new JButton(Strings.get("ButtonExport"));
exportDataInFile.setActionCommand("export");
exportDataInFile.addActionListener(myListener);
exportDataInFile.setPreferredSize(buttonSize);
exportDataInFile.setFocusable(false);
exportDataToImage = new JButton(Strings.get("Export as image"));
exportDataToImage.setActionCommand("exportImg");
exportDataToImage.addActionListener(myListener);
exportDataToImage.setPreferredSize(buttonSize);
exportDataToImage.setFocusable(false);
// Toolbar
JToolBar bar = new JToolBar();
bar.setFocusable(false);
JButton playButton;
if (simulator != null && simulator.isRunning()) {
playButton = new JButton(Icons.getIcon("simstop.png"));
} else {
playButton = new JButton(Icons.getIcon("simplay.png"));
}
playButton.setActionCommand("play");
playButton.addActionListener(myListener);
playButton.setToolTipText("Start/Stop simulation");
playButton.setFocusable(false);
bar.add(playButton);
JButton stepButton = new JButton(Icons.getIcon("simstep.png"));
stepButton.setActionCommand("step");
stepButton.addActionListener(myListener);
stepButton.setToolTipText("Simulate one step");
stepButton.setFocusable(false);
bar.add(stepButton);
JButton tplayButton;
if (simulator != null && simulator.isTicking()) {
tplayButton = new JButton(Icons.getIcon("simtstop.png"));
} else {
tplayButton = new JButton(Icons.getIcon("simtplay.png"));
}
tplayButton.setActionCommand("tplay");
tplayButton.addActionListener(myListener);
tplayButton.setToolTipText("Start/Stop 'sysclk' tick");
tplayButton.setFocusable(false);
bar.add(tplayButton);
JButton tstepButton = new JButton(Icons.getIcon("simtstep.png"));
tstepButton.setActionCommand("tstep");
tstepButton.addActionListener(myListener);
tstepButton.setToolTipText("Step one 'sysclk' tick");
tstepButton.setFocusable(false);
bar.add(tstepButton);
JButton tmainstepButton = new JButton(Icons.getIcon("clock.gif"));
tmainstepButton.setActionCommand("tmainstep");
tmainstepButton.addActionListener(myListener);
tmainstepButton.setToolTipText("Step one 'clk' tick");
tmainstepButton.setFocusable(false);
if (chronogramData.get("clk") == null) {
tmainstepButton.setEnabled(false);
tmainstepButton
.setToolTipText("Please create a clock named 'clk' to enable this function");
}
bar.add(tmainstepButton);
mainPanel.add(BorderLayout.NORTH, bar);
statusLabel = new JLabel();
topBar.add(chooseFileButton);
topBar.add(exportDataInFile);
topBar.add(exportDataToImage);
topBar.add(new JLabel(Strings.get("SimStatusName")));
topBar.add(statusLabel);
mainPanel.add(BorderLayout.SOUTH, topBar);
// split pane
mainSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
mainPanel.add(BorderLayout.CENTER, mainSplitPane);
setTitle(Strings.get("ChronoTitle") + ": "
+ project.getLogisimFile().getDisplayName());
setResizable(true);
setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
setSize(new Dimension(1024, 768));
setContentPane(mainPanel);
prefs = Preferences.userRoot().node(this.getClass().getName());
setLocation(prefs.getInt("X", 0), prefs.getInt("Y", 0));
setSize(prefs.getInt("W", getSize().width),
prefs.getInt("H", getSize().height));
setVisible(true);
}
/**
* Popup an error message
*
* @param err
* Error message
*/
public void errorMessage(String err) {
JOptionPane.showMessageDialog(null, err);
}
/**
* Export the current chronogram to file
*/
public void exportFile(String file) {
ChronoDataWriter.export(file, timelineParam, chronogramData);
}
public void exportImage(File file) {
ImageExporter ie = new ImageExporter(this, chronogramData, this.getCommonPanelParam().getHeaderHeight());
ie.createImage(file);
}
/**
* Fill the splitPane with the two panels (SignalName and SignalDraw)
*/
private void fillMainSPlitPane() {
mainSplitPane.setDividerSize(5);
// ===Left Side===
leftPanel = new LeftPanel(this, mDrawAreaEventManager);
leftScroll = new JScrollPane(leftPanel);
// ===Right Side===
// keep scroolbar position
int scrollBarCursorPos = rightScroll == null ? 0 : rightScroll
.getHorizontalScrollBar().getValue();
if (rightPanel == null) {
rightPanel = new RightPanel(this, mDrawAreaEventManager);
} else {
rightPanel = new RightPanel(rightPanel);
}
rightScroll = new JScrollPane(rightPanel);
rightScroll.getHorizontalScrollBar().addAdjustmentListener(myListener);
// Synchronize the two scrollbars
leftScroll.getVerticalScrollBar().setModel(
rightScroll.getVerticalScrollBar().getModel());
mainSplitPane.setLeftComponent(leftScroll);
mainSplitPane.setRightComponent(rightScroll);
// right scrollbar position
rightScroll.getHorizontalScrollBar().setValue(scrollBarCursorPos);
mDrawAreaManager.drawVerticalMouseClicked();
rightScroll.getHorizontalScrollBar().setValue(scrollBarCursorPos);
// keep leftpanel signal value up to date
mDrawAreaManager.refreshSignalsValues();
mainSplitPane.setDividerLocation(dividerLocation);
}
// accessors
public ChronoData getChronoData() {
return chronogramData;
}
public CommonPanelParam getCommonPanelParam() {
return commonPanelParam;
}
public int getDividerLocation() {
return dividerLocation;
}
public LeftPanel getLeftPanel() {
return leftPanel;
}
public int getNbrOfTick() {
return chronogramData.get("sysclk").getSignalValues().size();
}
public Project getProject() {
return project;
}
public RightPanel getRightPanel() {
return rightPanel;
}
public TimelineParam getTimelineParam() {
return timelineParam;
}
public int getVisibleSignalsWidth() {
return mainSplitPane.getRightComponent().getWidth();
}
public boolean isRealTimeMode() {
return realTimeMode;
}
@Override
public void keyPressed(KeyEvent ke) {
int keyCode = ke.getKeyCode();
if (keyCode == KeyEvent.VK_F2) {
tickMain();
// if(ke.getSource() instanceof JTable){
// ((JTable)(ke.getSource())).getInputMap()
// }
}
}
@Override
public void keyReleased(KeyEvent ke) {
// throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public void keyTyped(KeyEvent ke) {
// throw new UnsupportedOperationException("Not supported yet.");
}
/**
* Load the chronogram from the log file
*/
public void loadFile(String logFile) {
try {
ChronoData tmp = new ChronoData(logFile, this);
if (tmp != null) {
realTimeMode = false;
chronogramData = tmp;
fillMainSPlitPane();
statusLabel.setText(Strings.get("InputFileLoaded") + logFile);
}
} catch (NoSysclkException ex) {
errorMessage(Strings.get("InputFileNoSysclk"));
} catch (Exception ex) {
errorMessage(ex.toString());
}
}
/**
* Repaint all signals
*
* @param force
* If true, calls SwingUtilities.updateComponentTreeUI(this);
*/
public void repaintAll(boolean force) {
rightPanel.repaintAll();
if (this.isRealTimeMode()) {
Runnable refreshScroll = new Runnable() {
@Override
public void run() {
// keeps the scrolling bar at the top right, to follow the
// last added data
rightScroll.getHorizontalScrollBar().setValue(
rightPanel.getSignalWidth());
SwingUtilities.updateComponentTreeUI(ChronoFrame.this);
}
};
SwingUtilities.invokeLater(refreshScroll);
}
if (force) {
SwingUtilities.updateComponentTreeUI(this);
}
}
public void setScrollbarPosition(int pos) {
rightScroll.getHorizontalScrollBar().setValue(pos);
}
public void setTimelineParam(TimelineParam timelineParam) {
this.timelineParam = timelineParam;
}
private void tickMain() {
int ticks = 0;
for (com.cburch.logisim.comp.Component clock : project.getLogisimFile()
.getMainCircuit().getClocks()) {
if (clock.getAttributeSet().getValue(StdAttr.LABEL)
.contentEquals("clk")) {
if (project.getOptions().getAttributeSet()
.getValue(Options.ATTR_TICK_MAIN)
.equals(Options.TICK_MAIN_HALF_PERIOD)) {
if (project.getCircuitState().getValue(clock.getLocation())
.toIntValue() == 0) {
ticks = clock.getAttributeSet()
.getValue(Clock.ATTR_LOW);
} else {
ticks = clock.getAttributeSet().getValue(
Clock.ATTR_HIGH);
}
} else {
ticks = clock.getAttributeSet().getValue(Clock.ATTR_LOW)
+ clock.getAttributeSet().getValue(Clock.ATTR_HIGH);
}
break;
}
}
simulator.tickMain(ticks);
}
/**
* Switch bus between signle bus view or detailed signals view
*
* @param choosenBus
* Bus to expand or contract
* @param expand
* true: expand, false:contract
*/
public void toggleBusExpand(SignalDataBus choosenBus, boolean expand) {
if (expand) {
chronogramData.expandBus(choosenBus);
} else {
chronogramData.contractBus(choosenBus);
}
fillMainSPlitPane();
}
@Override
public void windowActivated(WindowEvent we) {
}
@Override
public void windowClosed(WindowEvent we) {
}
@Override
public void windowClosing(WindowEvent we) {
logFrame.getModel().removeModelListener(chronoModelEventHandler);
}
@Override
public void windowDeactivated(WindowEvent e) {
prefs.putInt("X", getX());
prefs.putInt("Y", getY());
prefs.putInt("W", getWidth());
prefs.putInt("H", getHeight());
}
@Override
public void windowDeiconified(WindowEvent we) {
}
@Override
public void windowIconified(WindowEvent we) {
}
@Override
public void windowOpened(WindowEvent we) {
}
}