/******************************************************************************* * 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.cburch.logisim.gui.main; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Container; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GraphicsConfiguration; import java.awt.GraphicsDevice; import java.awt.GraphicsEnvironment; import java.awt.IllegalComponentStateException; import java.awt.Point; import java.awt.Rectangle; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.awt.geom.Ellipse2D; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import javax.swing.JFrame; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JTabbedPane; import javax.swing.WindowConstants; import javax.swing.border.EmptyBorder; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import com.cburch.draw.toolbar.Toolbar; import com.cburch.draw.toolbar.ToolbarModel; import com.cburch.logisim.Main; //for version name import com.cburch.logisim.circuit.Circuit; import com.cburch.logisim.circuit.CircuitEvent; import com.cburch.logisim.circuit.CircuitListener; import com.cburch.logisim.comp.Component; import com.cburch.logisim.data.AttributeEvent; import com.cburch.logisim.data.AttributeSet; import com.cburch.logisim.data.Direction; import com.cburch.logisim.file.LibraryEvent; import com.cburch.logisim.file.LibraryListener; import com.cburch.logisim.gui.appear.AppearanceView; import com.cburch.logisim.gui.generic.AttrTable; import com.cburch.logisim.gui.generic.AttrTableModel; import com.cburch.logisim.gui.generic.BasicZoomModel; import com.cburch.logisim.gui.generic.CanvasPane; import com.cburch.logisim.gui.generic.CardPanel; import com.cburch.logisim.gui.generic.LFrame; import com.cburch.logisim.gui.generic.RegTabContent; import com.cburch.logisim.gui.generic.ZoomControl; import com.cburch.logisim.gui.generic.ZoomModel; import com.cburch.logisim.gui.menu.LogisimMenuBar; import com.cburch.logisim.prefs.AppPreferences; import com.cburch.logisim.proj.Project; import com.cburch.logisim.proj.ProjectActions; import com.cburch.logisim.proj.ProjectEvent; import com.cburch.logisim.proj.ProjectListener; import com.cburch.logisim.proj.Projects; import com.cburch.logisim.std.hdl.VhdlSimulatorConsole; import com.cburch.logisim.std.hdl.VhdlSimulatorListener; import com.cburch.logisim.tools.Tool; import com.cburch.logisim.util.HorizontalSplitPane; import com.cburch.logisim.util.JFileChoosers; import com.cburch.logisim.util.LocaleListener; import com.cburch.logisim.util.LocaleManager; import com.cburch.logisim.util.StringUtil; import com.cburch.logisim.util.VerticalSplitPane; public class Frame extends LFrame implements LocaleListener { class MyProjectListener implements ProjectListener, LibraryListener, CircuitListener, PropertyChangeListener, ChangeListener { public void attributeListChanged(AttributeEvent e) { } @Override public void circuitChanged(CircuitEvent event) { if (event.getAction() == CircuitEvent.ACTION_SET_NAME) { computeTitle(); } } private void enableSave() { Project proj = getProject(); boolean ok = proj.isFileDirty(); getRootPane().putClientProperty("windowModified", Boolean.valueOf(ok)); } @Override public void libraryChanged(LibraryEvent e) { if (e.getAction() == LibraryEvent.SET_NAME) { computeTitle(); } else if (e.getAction() == LibraryEvent.DIRTY_STATE) { enableSave(); } } @Override public void projectChanged(ProjectEvent event) { int action = event.getAction(); if (action == ProjectEvent.ACTION_SET_FILE) { computeTitle(); proj.setTool(proj.getOptions().getToolbarData().getFirstTool()); placeToolbar(); } else if (action == ProjectEvent.ACTION_SET_CURRENT) { setEditorView(EDIT_LAYOUT); if (appearance != null) { appearance.setCircuit(proj, proj.getCircuitState()); } viewAttributes(proj.getTool()); computeTitle(); } else if (action == ProjectEvent.ACTION_SET_TOOL) { if (attrTable == null) { return; // for startup } Tool oldTool = (Tool) event.getOldData(); Tool newTool = (Tool) event.getData(); if (getEditorView().equals(EDIT_LAYOUT)) { viewAttributes(oldTool, newTool, false); } } } @Override public void propertyChange(PropertyChangeEvent event) { if (AppPreferences.TOOLBAR_PLACEMENT.isSource(event)) { placeToolbar(); } } @Override public void stateChanged(ChangeEvent event) { Object source = event.getSource(); if (source == explorerPane) { firePropertyChange(EXPLORER_VIEW, "???", getExplorerView()); } else if (source == mainPanel) { firePropertyChange(EDITOR_VIEW, "???", getEditorView()); } } } class MyWindowListener extends WindowAdapter { @Override public void windowClosing(WindowEvent e) { if (confirmClose(Strings.get("confirmCloseTitle"))) { layoutCanvas.closeCanvas(); Frame.this.dispose(); } } @Override public void windowOpened(WindowEvent e) { layoutCanvas.computeSize(true); } } private class VhdlSimState extends JPanel implements VhdlSimulatorListener { private static final long serialVersionUID = 1L; Ellipse2D.Double circle; Color color; private int margin = 5; public VhdlSimState() { int radius = 15; circle = new Ellipse2D.Double(margin, margin, radius, radius); setOpaque(false); color = Color.GRAY; this.setBorder(new EmptyBorder(margin, margin, margin, margin)); } public Dimension getPreferredSize() { Rectangle bounds = circle.getBounds(); return new Dimension(bounds.width + 2 * margin, bounds.height + 2 * margin); } public void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2 = (Graphics2D) g; g2.setColor(color); g2.fill(circle); } @Override public void stateChanged() { switch (proj.getVhdlSimulator().getState()) { case DISABLED: color = Color.GRAY; break; case ENABLED: color = Color.RED; break; case STARTING: color = Color.ORANGE; break; case RUNNING: color = new Color(40, 180, 40); break; } this.repaint(); // this.setText("VHDL Sim : " + // proj.getSimulator().getCircuitState().getVhdlSimulator().getState()); } } private static Point getInitialLocation() { String s = AppPreferences.WINDOW_LOCATION.get(); if (s == null) { return null; } int comma = s.indexOf(','); if (comma < 0) { return null; } try { int x = Integer.parseInt(s.substring(0, comma)); int y = Integer.parseInt(s.substring(comma + 1)); while (isProjectFrameAt(x, y)) { x += 20; y += 20; } Rectangle desired = new Rectangle(x, y, 50, 50); int gcBestSize = 0; Point gcBestPoint = null; GraphicsEnvironment ge; ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); for (GraphicsDevice gd : ge.getScreenDevices()) { for (GraphicsConfiguration gc : gd.getConfigurations()) { Rectangle gcBounds = gc.getBounds(); if (gcBounds.intersects(desired)) { Rectangle inter = gcBounds.intersection(desired); int size = inter.width * inter.height; if (size > gcBestSize) { gcBestSize = size; int x2 = Math.max( gcBounds.x, Math.min(inter.x, inter.x + inter.width - 50)); int y2 = Math.max( gcBounds.y, Math.min(inter.y, inter.y + inter.height - 50)); gcBestPoint = new Point(x2, y2); } } } } if (gcBestPoint != null) { if (isProjectFrameAt(gcBestPoint.x, gcBestPoint.y)) { gcBestPoint = null; } } return gcBestPoint; } catch (Throwable t) { return null; } } private static boolean isProjectFrameAt(int x, int y) { for (Project current : Projects.getOpenProjects()) { Frame frame = current.getFrame(); if (frame != null) { Point loc = frame.getLocationOnScreen(); int d = Math.abs(loc.x - x) + Math.abs(loc.y - y); if (d <= 3) { return true; } } } return false; } private static final long serialVersionUID = 1L; public static final String EDITOR_VIEW = "editorView"; public static final String EXPLORER_VIEW = "explorerView"; public static final String EDIT_LAYOUT = "layout"; public static final String EDIT_APPEARANCE = "appearance"; public static final String VIEW_TOOLBOX = "toolbox"; public static final String VIEW_SIMULATION = "simulation"; public static final String VIEW_TRACKER = "tracker"; private static final double[] ZOOM_OPTIONS = { 20, 50, 75, 100, 133, 150, 200, 250, 300, 400 }; private Project proj; private MyProjectListener myProjectListener = new MyProjectListener(); // GUI elements shared between views private LogisimMenuBar menubar; private MenuListener menuListener; private Toolbar toolbar; private HorizontalSplitPane leftRegion, rightRegion; private VerticalSplitPane mainRegion; private JPanel mainPanelSuper; private CardPanel mainPanel; // left-side elements private Toolbar projectToolbar; private CardPanel explorerPane; private Toolbox toolbox; private SimulationExplorer simExplorer; private AttrTable attrTable; private VhdlSimState vhdlSimState; private ZoomControl zoom; // for the Layout view private LayoutToolbarModel layoutToolbarModel; private Canvas layoutCanvas; private VhdlSimulatorConsole vhdlSimulatorConsole; private ZoomModel layoutZoomModel; private LayoutEditHandler layoutEditHandler; private AttrTableSelectionModel attrTableSelectionModel; // for the Appearance view private AppearanceView appearance; private Double lastFraction = AppPreferences.WINDOW_RIGHT_SPLIT.get(); public Frame(Project proj) { this.proj = proj; setBackground(Color.white); setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); addWindowListener(new MyWindowListener()); proj.addProjectListener(myProjectListener); proj.addLibraryListener(myProjectListener); proj.addCircuitListener(myProjectListener); computeTitle(); // set up elements for the Layout view layoutToolbarModel = new LayoutToolbarModel(this, proj); layoutCanvas = new Canvas(proj); layoutZoomModel = new BasicZoomModel(AppPreferences.LAYOUT_SHOW_GRID, AppPreferences.LAYOUT_ZOOM, ZOOM_OPTIONS); layoutCanvas.getGridPainter().setZoomModel(layoutZoomModel); layoutEditHandler = new LayoutEditHandler(this); attrTableSelectionModel = new AttrTableSelectionModel(proj, this); // set up menu bar and toolbar menubar = new LogisimMenuBar(this, proj); menuListener = new MenuListener(this, menubar); menuListener.setEditHandler(layoutEditHandler); setJMenuBar(menubar); toolbar = new Toolbar(layoutToolbarModel); // set up the left-side components ToolbarModel projectToolbarModel = new ExplorerToolbarModel(this, menuListener); projectToolbar = new Toolbar(projectToolbarModel); toolbox = new Toolbox(proj, menuListener); simExplorer = new SimulationExplorer(proj, menuListener); explorerPane = new CardPanel(); explorerPane.addView(VIEW_TOOLBOX, toolbox); explorerPane.addView(VIEW_SIMULATION, simExplorer); explorerPane.setView(VIEW_TOOLBOX); attrTable = new AttrTable(this); JTabbedPane tabPane = attrTable.getTabPane(); RegTabContent regPanel = new RegTabContent(this); tabPane.addTab("Registers", regPanel); vhdlSimState = new VhdlSimState(); vhdlSimState.stateChanged(); proj.getVhdlSimulator().addVhdlSimStateListener(vhdlSimState); zoom = new ZoomControl(layoutZoomModel); // set up the central area CanvasPane canvasPane = new CanvasPane(layoutCanvas); mainPanelSuper = new JPanel(new BorderLayout()); canvasPane.setZoomModel(layoutZoomModel); mainPanel = new CardPanel(); mainPanel.addView(EDIT_LAYOUT, canvasPane); mainPanel.setView(EDIT_LAYOUT); mainPanelSuper.add(mainPanel, BorderLayout.CENTER); // set up the contents, split down the middle, with the canvas // on the right and a split pane on the left containing the // explorer and attribute values. JPanel explPanel = new JPanel(new BorderLayout()); explPanel.add(projectToolbar, BorderLayout.NORTH); explPanel.add(explorerPane, BorderLayout.CENTER); JPanel attrPanel = new JPanel(new BorderLayout()); attrPanel.add(attrTable, BorderLayout.CENTER); JPanel attrFooter = new JPanel(new BorderLayout()); attrFooter.add(zoom, BorderLayout.LINE_END); attrPanel.add(attrFooter, BorderLayout.SOUTH); leftRegion = new HorizontalSplitPane(explPanel, attrPanel, AppPreferences.WINDOW_LEFT_SPLIT.get().doubleValue()); vhdlSimulatorConsole = new VhdlSimulatorConsole(proj); rightRegion = new HorizontalSplitPane(mainPanelSuper, vhdlSimulatorConsole, 1.0); mainRegion = new VerticalSplitPane(leftRegion, rightRegion, AppPreferences.WINDOW_MAIN_SPLIT.get().doubleValue()); getContentPane().add(mainRegion, BorderLayout.CENTER); computeTitle(); this.setSize(AppPreferences.WINDOW_WIDTH.get().intValue(), AppPreferences.WINDOW_HEIGHT.get().intValue()); Point prefPoint = getInitialLocation(); if (prefPoint != null) { this.setLocation(prefPoint); } this.setExtendedState(AppPreferences.WINDOW_STATE.get().intValue()); menuListener.register(mainPanel); KeyboardToolSelection.register(toolbar); proj.setFrame(this); if (proj.getTool() == null) { proj.setTool(proj.getOptions().getToolbarData().getFirstTool()); } mainPanel.addChangeListener(myProjectListener); explorerPane.addChangeListener(myProjectListener); AppPreferences.TOOLBAR_PLACEMENT .addPropertyChangeListener(myProjectListener); placeToolbar(); ((MenuListener.EnabledListener) projectToolbarModel) .menuEnableChanged(menuListener); LocaleManager.addLocaleListener(this); } private void computeTitle() { String s; Circuit circuit = proj.getCurrentCircuit(); String name = proj.getLogisimFile().getName(); if (circuit != null) { s = StringUtil.format(Strings.get("titleCircFileKnown"), circuit.getName(), name); } else { s = StringUtil.format(Strings.get("titleFileKnown"), name); } this.setTitle(s + " (v " + Main.VERSION_NAME + ")"); myProjectListener.enableSave(); } public boolean confirmClose() { return confirmClose(Strings.get("confirmCloseTitle")); } // returns true if user is OK with proceeding public boolean confirmClose(String title) { String message = StringUtil.format( Strings.get("confirmDiscardMessage"), proj.getLogisimFile() .getName()); if (!proj.isFileDirty()) { return true; } toFront(); String[] options = { Strings.get("saveOption"), Strings.get("discardOption"), Strings.get("cancelOption") }; int result = JOptionPane.showOptionDialog(this, message, title, 0, JOptionPane.QUESTION_MESSAGE, null, options, options[0]); boolean ret; if (result == 0) { ret = ProjectActions.doSave(proj); } else if (result == 1) { // Close the current project dispose(); ret = true; } else { ret = false; } return ret; } public Canvas getCanvas() { return layoutCanvas; } public String getEditorView() { return mainPanel.getView(); } public String getExplorerView() { return explorerPane.getView(); } public Project getProject() { return proj; } public VhdlSimulatorConsole getVhdlSimulatorConsole() { return vhdlSimulatorConsole; } public ZoomModel getZoomModel() { return layoutZoomModel; } @Override public void localeChanged() { computeTitle(); } private void placeToolbar() { String loc = AppPreferences.TOOLBAR_PLACEMENT.get(); Container contents = getContentPane(); contents.remove(toolbar); mainPanelSuper.remove(toolbar); if (AppPreferences.TOOLBAR_HIDDEN.equals(loc)) { ; // don't place value anywhere } else if (AppPreferences.TOOLBAR_DOWN_MIDDLE.equals(loc)) { toolbar.setOrientation(Toolbar.VERTICAL); mainPanelSuper.add(toolbar, BorderLayout.WEST); } else { // it is a BorderLayout constant Object value = BorderLayout.NORTH; for (Direction dir : Direction.cardinals) { if (dir.toString().equals(loc)) { if (dir == Direction.EAST) { value = BorderLayout.EAST; } else if (dir == Direction.SOUTH) { value = BorderLayout.SOUTH; } else if (dir == Direction.WEST) { value = BorderLayout.WEST; } else { value = BorderLayout.NORTH; } } } contents.add(toolbar, value); boolean vertical = value == BorderLayout.WEST || value == BorderLayout.EAST; toolbar.setOrientation(vertical ? Toolbar.VERTICAL : Toolbar.HORIZONTAL); } contents.validate(); } public void savePreferences() { AppPreferences.TICK_FREQUENCY.set(Double.valueOf(proj.getSimulator() .getTickFrequency())); AppPreferences.LAYOUT_SHOW_GRID.setBoolean(layoutZoomModel .getShowGrid()); AppPreferences.LAYOUT_ZOOM.set(Double.valueOf(layoutZoomModel .getZoomFactor())); if (appearance != null) { ZoomModel aZoom = appearance.getZoomModel(); AppPreferences.APPEARANCE_SHOW_GRID.setBoolean(aZoom.getShowGrid()); AppPreferences.APPEARANCE_ZOOM.set(Double.valueOf(aZoom .getZoomFactor())); } int state = getExtendedState() & ~JFrame.ICONIFIED; AppPreferences.WINDOW_STATE.set(Integer.valueOf(state)); Dimension dim = getSize(); AppPreferences.WINDOW_WIDTH.set(Integer.valueOf(dim.width)); AppPreferences.WINDOW_HEIGHT.set(Integer.valueOf(dim.height)); Point loc; try { loc = getLocationOnScreen(); } catch (IllegalComponentStateException e) { loc = Projects.getLocation(this); } if (loc != null) { AppPreferences.WINDOW_LOCATION.set(loc.x + "," + loc.y); } AppPreferences.WINDOW_LEFT_SPLIT.set(Double.valueOf(leftRegion .getFraction())); if (Double.valueOf(rightRegion.getFraction()) < 1.0) AppPreferences.WINDOW_RIGHT_SPLIT.set(Double.valueOf(rightRegion .getFraction())); AppPreferences.WINDOW_MAIN_SPLIT.set(Double.valueOf(mainRegion .getFraction())); AppPreferences.DIALOG_DIRECTORY .set(JFileChoosers.getCurrentDirectory()); } void setAttrTableModel(AttrTableModel value) { attrTable.setAttrTableModel(value); if (value instanceof AttrTableToolModel) { Tool tool = ((AttrTableToolModel) value).getTool(); toolbox.setHaloedTool(tool); layoutToolbarModel.setHaloedTool(tool); } else { toolbox.setHaloedTool(null); layoutToolbarModel.setHaloedTool(null); } if (value instanceof AttrTableComponentModel) { Circuit circ = ((AttrTableComponentModel) value).getCircuit(); Component comp = ((AttrTableComponentModel) value).getComponent(); layoutCanvas.setHaloedComponent(circ, comp); } else { layoutCanvas.setHaloedComponent(null, null); } } public void setEditorView(String view) { String curView = mainPanel.getView(); if (curView.equals(view)) { return; } if (view.equals(EDIT_APPEARANCE)) { // appearance view AppearanceView app = appearance; if (app == null) { app = new AppearanceView(); app.setCircuit(proj, proj.getCircuitState()); mainPanel.addView(EDIT_APPEARANCE, app.getCanvasPane()); appearance = app; } toolbar.setToolbarModel(app.getToolbarModel()); app.getAttrTableDrawManager(attrTable).attributesSelected(); zoom.setZoomModel(app.getZoomModel()); menuListener.setEditHandler(app.getEditHandler()); mainPanel.setView(view); app.getCanvas().requestFocus(); } else { // layout view toolbar.setToolbarModel(layoutToolbarModel); zoom.setZoomModel(layoutZoomModel); menuListener.setEditHandler(layoutEditHandler); viewAttributes(proj.getTool(), true); mainPanel.setView(view); layoutCanvas.requestFocus(); } } public void setExplorerView(String view) { explorerPane.setView(view); } public void setVhdlSimulatorConsoleStatus(boolean visible) { if (visible) { rightRegion.setFraction(lastFraction); } else { lastFraction = rightRegion.getFraction(); rightRegion.setFraction(1); } } void viewAttributes(Tool newTool) { viewAttributes(null, newTool, false); } private void viewAttributes(Tool newTool, boolean force) { viewAttributes(null, newTool, force); } private void viewAttributes(Tool oldTool, Tool newTool, boolean force) { AttributeSet newAttrs; if (newTool == null) { newAttrs = null; if (!force) { return; } } else { newAttrs = newTool.getAttributeSet(layoutCanvas); } if (newAttrs == null) { AttrTableModel oldModel = attrTable.getAttrTableModel(); boolean same = oldModel instanceof AttrTableToolModel && ((AttrTableToolModel) oldModel).getTool() == oldTool; if (!force && !same && !(oldModel instanceof AttrTableCircuitModel)) { return; } } if (newAttrs == null) { Circuit circ = proj.getCurrentCircuit(); if (circ != null) { setAttrTableModel(new AttrTableCircuitModel(proj, circ)); } else if (force) { setAttrTableModel(null); } } else if (newAttrs instanceof SelectionAttributes) { setAttrTableModel(attrTableSelectionModel); } else { setAttrTableModel(new AttrTableToolModel(proj, newTool)); } } public void viewComponentAttributes(Circuit circ, Component comp) { if (comp == null) { setAttrTableModel(null); } else { setAttrTableModel(new AttrTableComponentModel(proj, circ, comp)); } } }