/* * funCKit - functional Circuit Kit * Copyright (C) 2013 Lukas Elsner <open@mindrunner.de> * Copyright (C) 2013 Peter Dahlberg <catdog2@tuxzone.org> * Copyright (C) 2013 Julian Stier <mail@julian-stier.de> * Copyright (C) 2013 Sebastian Vetter <mail@b4sti.eu> * Copyright (C) 2013 Thomas Poxrucker <poxrucker_t@web.de> * Copyright (C) 2013 Alexander Treml <alex.treml@directbox.com> * * This program 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. * * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. */ package de.sep2011.funckit.view; import static de.sep2011.funckit.util.Log.gl; import java.awt.Color; import java.awt.Frame; import java.awt.Graphics; import java.awt.Rectangle; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.HierarchyBoundsListener; import java.awt.event.HierarchyEvent; import java.util.ArrayList; import java.util.List; import javax.swing.JApplet; import javax.swing.JButton; import javax.swing.JDialog; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JProgressBar; import javax.swing.SwingUtilities; import javax.swing.Timer; import javax.swing.WindowConstants; import net.miginfocom.swing.MigLayout; import de.sep2011.funckit.Application; import de.sep2011.funckit.FunCKit; import de.sep2011.funckit.controller.Controller; import de.sep2011.funckit.controller.listener.project.SaveFileActionListener; import de.sep2011.funckit.controller.listener.simulation.SimulationForwardButtonListener; import de.sep2011.funckit.model.graphmodel.Circuit; import de.sep2011.funckit.model.sessionmodel.Project; import de.sep2011.funckit.model.sessionmodel.SessionModel; import de.sep2011.funckit.model.sessionmodel.Settings; import de.sep2011.funckit.observer.ProjectInfo; import de.sep2011.funckit.observer.ProjectObserver; import de.sep2011.funckit.observer.SessionModelInfo; import de.sep2011.funckit.observer.SessionModelObserver; import de.sep2011.funckit.observer.SettingsInfo; import de.sep2011.funckit.observer.SettingsObserver; import de.sep2011.funckit.util.FunckitGuiUtil; import de.sep2011.funckit.util.internationalization.Language; import de.sep2011.funckit.validator.LiveCheckValidatorFactory; import de.sep2011.funckit.validator.Result; /** * View is an overall mediator system, encapsulating graphical user interface * components from an outer glance at the view-part of the application. It * serves as session model observer to react on changes in that model part and * delegate that change information to subsystems of the GUI. */ public class View implements SessionModelObserver, SettingsObserver, ProjectObserver { private FunckitRootPane mainRootPane; private FunckitFrame mainFrame; private JDialog progressDialog; private JProgressBar progressBar; private JLabel progressLabel; private boolean progressCanceled; private SessionModel session; private Controller controller; private Timer simulationTimer; private String windowTitle; private JApplet applet; /** * Initializes view object with given parameters. * * @param windowTitle * @param session * @param controller * @param applet The applet to use, can be null for a standalone application */ public View(String windowTitle, SessionModel session, Controller controller, JApplet applet) { initialize(windowTitle, session, controller, applet); } private void initialize(String windowTitle, SessionModel session, Controller controller, JApplet applet) { this.windowTitle = windowTitle; this.applet = applet; assert session != null; assert controller != null; this.session = session; this.controller = controller; this.simulationTimer = new Timer(0, new SimulationForwardButtonListener(this, controller)); mainRootPane = new FunckitRootPane(this); if (applet == null) { createAndShowMainWindow(); } initProgressBar(); session.addObserver(this); for (Project pro : session.getProjects()) { pro.addObserver(this); if (pro.hasSimulation() && (pro == session.getCurrentProject())) { simulationTimer.setDelay(pro.getTimerDelay()); if (pro.isSimulationPaused()) { simulationTimer.stop(); } else { simulationTimer.start(); } } } getSessionModel().getSettings().addObserver(this); } private void createAndShowMainWindow() { Rectangle frameBounds = this.session.getSettings().get( Settings.WINDOW_BOUNDS, Rectangle.class); if (frameBounds == null) { mainFrame = new FunckitFrame(windowTitle, this, mainRootPane); } else { mainFrame = new FunckitFrame(windowTitle, frameBounds, this, mainRootPane); } mainFrame.setVisible(true); } private void initProgressBar() { progressDialog = new JDialog(mainFrame, Language.tr("view.progressBar"), true); progressDialog.setUndecorated(true); progressDialog.setResizable(false); // progressDialog.setSize(300, 50); progressDialog.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); progressBar = new JProgressBar(); progressBar.setIndeterminate(true); progressBar.setStringPainted(true); JButton cancelButton = new JButton(Language.tr("view.progressBar.cancelButton")); cancelButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { progressCanceled = true; } }); progressLabel = new JLabel(); progressDialog.setLayout(new MigLayout()); progressDialog.add(progressBar); progressDialog.add(cancelButton, "wrap"); progressDialog.add(progressLabel); progressDialog.pack(); if(mainFrame != null ) { mainFrame.getContentPane().addHierarchyBoundsListener(new HierarchyBoundsListener() { @Override public void ancestorResized(HierarchyEvent e) { FunckitGuiUtil.centerDialogInWindow(progressDialog, mainFrame); } @Override public void ancestorMoved(HierarchyEvent e) { FunckitGuiUtil.centerDialogInWindow(progressDialog, mainFrame); } }); /* grey out the Frame */ mainFrame.setGlassPane(new JPanel() { /** * */ private static final long serialVersionUID = 550111350341542349L; { setOpaque(false); } @Override public void paintComponent(Graphics g) { super.paintComponent(g); Color ppColor = new Color(88, 88, 88, 100); // r,g,b,alpha g.setColor(ppColor); g.fillRect(0, 0, getSize().width, getSize().height); } }); } } public void openNewProject(Circuit circuit) { NewProjectDialog newProjectFrame = new NewProjectDialog(this, circuit); newProjectFrame.setLocationRelativeTo(mainFrame); newProjectFrame.setVisible(true); } public void setStatusText(String text) { mainRootPane.setStatusText(text); } /** * Gets the {@link Timer} used for the * {@link de.sep2011.funckit.model.simulationmodel.Simulation}. * * @return the {@link Timer} used for the * {@link de.sep2011.funckit.model.simulationmodel.Simulation}. */ public Timer getSimulationTimer() { return simulationTimer; } public FunckitRootPane getMainRootPane() { return mainRootPane; } /** * @return the main frame, can be null if no main frame */ public FunckitFrame getMainFrame() { return mainFrame; } public void prepareProgress(String label) { progressLabel.setText(label); progressLabel.revalidate(); progressLabel.repaint(); progressDialog.pack(); progressBar.setValue(0); progressBar.setIndeterminate(true); progressCanceled = false; if (mainFrame != null) { mainFrame.getGlassPane().setVisible(true); FunckitGuiUtil.centerDialogInWindow(progressDialog, mainFrame); } } public void showProgress() { // workaround for displaying the wrong label text because of missing // repaint. SwingUtilities.invokeLater(new Runnable() { @Override public void run() { progressLabel.repaint(); } }); progressDialog.setVisible(true); } public void setProgress(int value) { progressBar.setIndeterminate(false); progressBar.setValue(value); } public void hideProgress() { mainRootPane.getGlassPane().setVisible(false); progressDialog.setVisible(false); } public void setProgressMax(int max) { progressBar.setMaximum(max); } public boolean isProgressCanceled() { return progressCanceled; } public void setProgressCanceled(boolean canceled) { this.progressCanceled = canceled; } /** * returns the current active {@link EditPanel}, null if no * {@link EditPanel} is active. * * @return the current active {@link EditPanel}, null if no * {@link EditPanel} is active */ public EditPanel getCurrentActiveEditPanel() { java.awt.Component ac = mainRootPane.getTabbedPane().getSelectedComponent(); if (ac instanceof EditPanelScrollPane) { return ((EditPanelScrollPane) ac).getEditPanel(); } return null; } public List<EditPanel> getOpenEditPanels() { java.awt.Component comps[] = mainRootPane.getTabbedPane().getComponents(); List<EditPanel> eps = new ArrayList<EditPanel>(comps.length); for (java.awt.Component ac : comps) { if (ac instanceof EditPanelScrollPane) { eps.add(((EditPanelScrollPane) ac).getEditPanel()); } } return eps; } /** * Delegates exiting application event. */ public void exit() { if (askForSave()) { if (mainFrame != null) { mainFrame.setVisible(false); mainFrame.dispose(); } progressDialog.setVisible(false); progressDialog.dispose(); simulationTimer.stop(); } } /** * Returns current session model object. May never be null. * * @return Current session model. */ public SessionModel getSessionModel() { return session; } /** * Current mediating controller object. May never be null. * * @return mediating controller object. May never be null. */ public Controller getController() { return controller; } @Override public void sessionModelChanged(SessionModel source, SessionModelInfo i) { if (i.isPrepareExit()) { session.getSettings().set(Settings.WINDOW_BOUNDS, mainRootPane.getBounds()); exit(); } if (i.hasCurrentProjectChanged()) { if (this.getSessionModel().getSettings().getBoolean(Settings.REALTIME_VALIDATION)) { LiveCheckValidatorFactory liveFactory = new LiveCheckValidatorFactory(); List<Result> results = liveFactory.getValidator().validate( i.getChangedProject().getCircuit()); // NOTE: the notifying circuit has to be the main circuit of the // current project. this.getSessionModel().getCurrentProject().setCheckResults(results); } /* update simulation timer */ if (i.getChangedProject().hasSimulation() && (i.getChangedProject() == session.getCurrentProject())) { simulationTimer.setDelay(i.getChangedProject().getTimerDelay()); if (i.getChangedProject().isSimulationPaused()) { simulationTimer.stop(); } else { simulationTimer.start(); } } else { simulationTimer.stop(); } } if (i.hasProjectAdded()) { i.getChangedProject().addObserver(this); } if (i.hasProjectRemoved()) { i.getChangedProject().deleteObserver(this); } } private boolean askForSave() { boolean askForSave = false; for (Project p : controller.getSessionModel().getProjects()) { if (p.isModified()) { askForSave = true; break; } } if (askForSave) { int ret = askForSaveUnsavedProjects(); if (ret == JOptionPane.YES_OPTION) { for (Project p : controller.getSessionModel().getProjects()) { if (p.isModified()) { controller.getSessionModel().setCurrentProject(p); if (!SaveFileActionListener.saveProject(p, this)) { return false; } } } } else if (ret == JOptionPane.CANCEL_OPTION) { return false; } } return true; } /** * Rebuilds the view by starting a new {@link Application} instance. */ public void rebuildView() { if (askForSave()) { if (mainFrame != null) { mainFrame.setVisible(false); mainFrame.dispose(); } simulationTimer.stop(); gl().info("Restating Application"); FunCKit.start(); } } public void showErrorMessage(String title, String message) { JOptionPane.showMessageDialog(null, message, title, JOptionPane.ERROR_MESSAGE); } public void showWarningMessage(String title, String message) { JOptionPane.showMessageDialog(null, message, title, JOptionPane.WARNING_MESSAGE); } public int showWarningOptionDialog(String title, String message) { return JOptionPane.showOptionDialog(getMainRootPane(), message, title, JOptionPane.OK_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE, null, null, null); } /** * Shows dialog for saving unsaved projects and returns its result depending * on users choice. * * @return {@link JOptionPane#YES_OPTION} to confirm saving unsaved * projects, {@link JOptionPane#NO_OPTION} to deny. */ public int askForSaveUnsavedProjects() { if (mainFrame != null) { mainFrame.setExtendedState(Frame.NORMAL); mainFrame.toFront(); } return JOptionPane.showOptionDialog(getMainRootPane(), Language.tr("view.saveUnsavedProjects.message"), Language.tr("view.saveUnsavedProjects.title"), JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE, null, null, null); } public void showValidatorResults(List<Result> results) { StringBuilder builder = new StringBuilder(); builder.append("<html><table>"); for (Result result : results) { if (!result.isPassed()) { builder.append("<tr>"); builder.append("<td>"); builder.append(result.getMessage()); builder.append("</td>"); builder.append("</tr>"); } } builder.append("</table></html>"); showErrorMessage(Language.tr("view.validationFailedDialog.title"), builder.toString()); } @Override public void settingsChanged(Settings source, SettingsInfo i) { } @Override public void projectChanged(Project source, ProjectInfo i) { if ((i.isSimulationControlStateModified() || i.isSimulationChanged()) && source.hasSimulation() && source == session.getCurrentProject()) { simulationTimer.setDelay(source.getTimerDelay()); if (source.isSimulationPaused()) { simulationTimer.stop(); } else { simulationTimer.start(); } } else if (i.isSimulationChanged() && !source.hasSimulation()) { simulationTimer.stop(); } if (i.isCircuitChanged() && source.getCircuit() == null) { simulationTimer.stop(); } } public JApplet getApplet() { return applet; } }