/* * Copyright (C) 2011 Jason von Nieda <jason@vonnieda.org> * * This file is part of OpenPnP. * * OpenPnP 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. * * OpenPnP 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 OpenPnP. If not, see * <http://www.gnu.org/licenses/>. * * For more information about OpenPnP visit http://openpnp.org */ package org.openpnp.gui; import java.awt.AWTEvent; import java.awt.BorderLayout; import java.awt.Component; import java.awt.EventQueue; import java.awt.FlowLayout; import java.awt.Font; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; import java.awt.event.ComponentListener; import java.awt.event.KeyEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.HashMap; import java.util.Map; import java.util.prefs.Preferences; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.BoxLayout; import javax.swing.ButtonGroup; import javax.swing.JButton; import javax.swing.JCheckBoxMenuItem; 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.JSplitPane; import javax.swing.JTabbedPane; import javax.swing.JTextPane; import javax.swing.KeyStroke; import javax.swing.SwingUtilities; import javax.swing.UIManager; import javax.swing.border.BevelBorder; import javax.swing.border.EmptyBorder; import javax.swing.border.TitledBorder; import org.openpnp.gui.components.CameraPanel; import org.openpnp.gui.components.nav.FxNavigationView; import org.openpnp.gui.importer.BoardImporter; import org.openpnp.gui.importer.EagleBoardImporter; import org.openpnp.gui.importer.EagleMountsmdUlpImporter; import org.openpnp.gui.importer.KicadPosImporter; import org.openpnp.gui.importer.NamedCSVImporter; import org.openpnp.gui.importer.SolderPasteGerberImporter; import org.openpnp.gui.support.HeadCellValue; import org.openpnp.gui.support.LengthCellValue; import org.openpnp.gui.support.MessageBoxes; import org.openpnp.gui.support.OSXAdapter; import org.openpnp.gui.support.RotationCellValue; import org.openpnp.model.Configuration; import org.openpnp.model.LengthUnit; import org.pmw.tinylog.Logger; /** * The main window of the application. */ @SuppressWarnings("serial") public class MainFrame extends JFrame { private static final String PREF_WINDOW_X = "MainFrame.windowX"; private static final int PREF_WINDOW_X_DEF = 0; private static final String PREF_WINDOW_Y = "MainFrame.windowY"; private static final int PREF_WINDOW_Y_DEF = 0; private static final String PREF_WINDOW_WIDTH = "MainFrame.windowWidth"; private static final int PREF_WINDOW_WIDTH_DEF = 1024; private static final String PREF_WINDOW_HEIGHT = "MainFrame.windowHeight"; private static final int PREF_WINDOW_HEIGHT_DEF = 768; private static final String PREF_DIVIDER_POSITION = "MainFrame.dividerPosition"; private static final int PREF_DIVIDER_POSITION_DEF = -1; /* * TODO define accelerators and mnemonics * openJobMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, * Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); */ private final Configuration configuration; // TODO: Really should switch to some kind of DI model, but this will do // for now. private static MainFrame mainFrame; private MachineControlsPanel machineControlsPanel; private PartsPanel partsPanel; private PackagesPanel packagesPanel; private FeedersPanel feedersPanel; private JobPanel jobPanel; private CameraPanel cameraPanel; private MachineSetupPanel machineSetupPanel; private Component navigationPanel; public static MainFrame get() { return mainFrame; } public MachineControlsPanel getMachineControls() { return machineControlsPanel; } public PartsPanel getPartsTab() { return partsPanel; } public PackagesPanel getPackagesTab() { return packagesPanel; } public FeedersPanel getFeedersTab() { return feedersPanel; } public JobPanel getJobTab() { return jobPanel; } public CameraPanel getCameraViews() { return cameraPanel; } public MachineSetupPanel getMachineSetupTab() { return machineSetupPanel; } private JPanel contentPane; private JLabel lblStatus; private JTabbedPane tabs; private JSplitPane splitPaneTopBottom; private TitledBorder panelInstructionsBorder; private JPanel panelInstructions; private JPanel panelInstructionActions; private JPanel panel_1; private JButton btnInstructionsNext; private JButton btnInstructionsCancel; private JTextPane lblInstructions; private JPanel panel_2; private JTabbedPane camerasAndNavTabbedPane; private JPanel panel_3; private JPanel logPanel; private JMenuBar menuBar; private JMenu mnImport; private JMenu mnScripts; public JTabbedPane getTabs() { return tabs; } private Preferences prefs = Preferences.userNodeForPackage(MainFrame.class); private ActionListener instructionsCancelActionListener; private ActionListener instructionsProceedActionListener; public MainFrame(Configuration configuration) { mainFrame = this; this.configuration = configuration; LengthCellValue.setConfiguration(configuration); RotationCellValue.setConfiguration(configuration); HeadCellValue.setConfiguration(configuration); setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); // Get handlers for Mac application menu in place. boolean macOsXMenus = registerForMacOSXEvents(); addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { quit(); } }); if (prefs.getInt(PREF_WINDOW_WIDTH, 50) < 50) { prefs.putInt(PREF_WINDOW_WIDTH, PREF_WINDOW_WIDTH_DEF); } if (prefs.getInt(PREF_WINDOW_HEIGHT, 50) < 50) { prefs.putInt(PREF_WINDOW_HEIGHT, PREF_WINDOW_HEIGHT_DEF); } setBounds(prefs.getInt(PREF_WINDOW_X, PREF_WINDOW_X_DEF), prefs.getInt(PREF_WINDOW_Y, PREF_WINDOW_Y_DEF), prefs.getInt(PREF_WINDOW_WIDTH, PREF_WINDOW_WIDTH_DEF), prefs.getInt(PREF_WINDOW_HEIGHT, PREF_WINDOW_HEIGHT_DEF)); jobPanel = new JobPanel(configuration, this, machineControlsPanel); partsPanel = new PartsPanel(configuration, this); packagesPanel = new PackagesPanel(configuration, this); feedersPanel = new FeedersPanel(configuration, this); machineSetupPanel = new MachineSetupPanel(); cameraPanel = new CameraPanel(); machineControlsPanel = new MachineControlsPanel(configuration); menuBar = new JMenuBar(); setJMenuBar(menuBar); // File ////////////////////////////////////////////////////////////////////// JMenu mnFile = new JMenu("File"); menuBar.add(mnFile); mnFile.add(new JMenuItem(jobPanel.newJobAction)); mnFile.add(new JMenuItem(jobPanel.openJobAction)); mnFile.add(jobPanel.mnOpenRecent); mnFile.addSeparator(); mnFile.add(new JMenuItem(jobPanel.saveJobAction)); mnFile.add(new JMenuItem(jobPanel.saveJobAsAction)); // File -> Import ////////////////////////////////////////////////////////////////////// mnFile.addSeparator(); mnImport = new JMenu("Import Board"); mnFile.add(mnImport); if (!macOsXMenus) { mnFile.addSeparator(); mnFile.add(new JMenuItem(quitAction)); } // Edit ////////////////////////////////////////////////////////////////////// JMenu mnEdit = new JMenu("Edit"); menuBar.add(mnEdit); mnEdit.add(new JMenuItem(jobPanel.newBoardAction)); mnEdit.add(new JMenuItem(jobPanel.addBoardAction)); mnEdit.add(new JMenuItem(jobPanel.removeBoardAction)); mnEdit.addSeparator(); mnEdit.add(new JMenuItem(jobPanel.captureToolBoardLocationAction)); // View ////////////////////////////////////////////////////////////////////// JMenu mnView = new JMenu("View"); menuBar.add(mnView); ButtonGroup buttonGroup = new ButtonGroup(); JMenu mnUnits = new JMenu("System Units"); mnView.add(mnUnits); JMenuItem menuItem; menuItem = new JCheckBoxMenuItem(inchesUnitSelected); buttonGroup.add(menuItem); if (configuration.getSystemUnits() == LengthUnit.Inches) { menuItem.setSelected(true); } mnUnits.add(menuItem); menuItem = new JCheckBoxMenuItem(millimetersUnitSelected); buttonGroup.add(menuItem); if (configuration.getSystemUnits() == LengthUnit.Millimeters) { menuItem.setSelected(true); } mnUnits.add(menuItem); // Job Control ////////////////////////////////////////////////////////////////////// JMenu mnJob = new JMenu("Job Control"); menuBar.add(mnJob); mnJob.add(new JMenuItem(jobPanel.startPauseResumeJobAction)); mnJob.add(new JMenuItem(jobPanel.stepJobAction)); mnJob.add(new JMenuItem(jobPanel.stopJobAction)); // Machine ////////////////////////////////////////////////////////////////////// JMenu mnCommands = new JMenu("Machine"); menuBar.add(mnCommands); mnCommands.add(new JMenuItem(machineControlsPanel.homeAction)); mnCommands.addSeparator(); mnCommands.add(new JMenuItem(machineControlsPanel.startStopMachineAction)); // Scripts ///////////////////////////////////////////////////////////////////// mnScripts = new JMenu("Scripts"); menuBar.add(mnScripts); // Help ///////////////////////////////////////////////////////////////////// if (!macOsXMenus) { JMenu mnHelp = new JMenu("Help"); menuBar.add(mnHelp); mnHelp.add(new JMenuItem(aboutAction)); } contentPane = new JPanel(); contentPane.setBorder(new EmptyBorder(5, 5, 5, 5)); setContentPane(contentPane); contentPane.setLayout(new BorderLayout(0, 0)); splitPaneTopBottom = new JSplitPane(); splitPaneTopBottom.setBorder(null); splitPaneTopBottom.setOrientation(JSplitPane.VERTICAL_SPLIT); splitPaneTopBottom.setContinuousLayout(true); contentPane.add(splitPaneTopBottom, BorderLayout.CENTER); JPanel panelTop = new JPanel(); splitPaneTopBottom.setLeftComponent(panelTop); panelTop.setLayout(new BorderLayout(0, 0)); JPanel panelLeftColumn = new JPanel(); panelTop.add(panelLeftColumn, BorderLayout.WEST); FlowLayout flowLayout = (FlowLayout) panelLeftColumn.getLayout(); flowLayout.setVgap(0); flowLayout.setHgap(0); JPanel panel = new JPanel(); panelLeftColumn.add(panel); panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); machineControlsPanel.setBorder(new TitledBorder(null, "Machine Controls", TitledBorder.LEADING, TitledBorder.TOP, null, null)); panel.add(machineControlsPanel); // Add global hotkeys for the arrow keys final Map<KeyStroke, Action> hotkeyActionMap = new HashMap<>(); int mask = KeyEvent.CTRL_DOWN_MASK; hotkeyActionMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, mask), machineControlsPanel.getJogControlsPanel().yPlusAction); hotkeyActionMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, mask), machineControlsPanel.getJogControlsPanel().yMinusAction); hotkeyActionMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, mask), machineControlsPanel.getJogControlsPanel().xMinusAction); hotkeyActionMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, mask), machineControlsPanel.getJogControlsPanel().xPlusAction); hotkeyActionMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_QUOTE, mask), machineControlsPanel.getJogControlsPanel().zPlusAction); hotkeyActionMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_SLASH, mask), machineControlsPanel.getJogControlsPanel().zMinusAction); hotkeyActionMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_COMMA, mask), machineControlsPanel.getJogControlsPanel().cPlusAction); hotkeyActionMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_PERIOD, mask), machineControlsPanel.getJogControlsPanel().cMinusAction); hotkeyActionMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_MINUS, mask), machineControlsPanel.getJogControlsPanel().lowerIncrementAction); hotkeyActionMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_EQUALS, mask), machineControlsPanel.getJogControlsPanel().raiseIncrementAction); Toolkit.getDefaultToolkit().getSystemEventQueue().push(new EventQueue() { @Override protected void dispatchEvent(AWTEvent event) { if (event instanceof KeyEvent) { KeyStroke ks = KeyStroke.getKeyStrokeForEvent((KeyEvent) event); Action action = hotkeyActionMap.get(ks); if (action != null && action.isEnabled()) { action.actionPerformed(null); return; } } super.dispatchEvent(event); } }); JPanel panelCameraAndInstructions = new JPanel(new BorderLayout()); panelTop.add(panelCameraAndInstructions, BorderLayout.CENTER); panelInstructions = new JPanel(); panelInstructions.setVisible(false); panelInstructions.setBorder(panelInstructionsBorder = new TitledBorder(null, "Instructions", TitledBorder.LEADING, TitledBorder.TOP, null, null)); panelCameraAndInstructions.add(panelInstructions, BorderLayout.SOUTH); panelInstructions.setLayout(new BorderLayout(0, 0)); panelInstructionActions = new JPanel(); panelInstructionActions.setAlignmentY(Component.BOTTOM_ALIGNMENT); panelInstructions.add(panelInstructionActions, BorderLayout.EAST); panelInstructionActions.setLayout(new BorderLayout(0, 0)); panel_2 = new JPanel(); FlowLayout flowLayout_2 = (FlowLayout) panel_2.getLayout(); flowLayout_2.setVgap(0); flowLayout_2.setHgap(0); panelInstructionActions.add(panel_2, BorderLayout.SOUTH); btnInstructionsCancel = new JButton("Cancel"); btnInstructionsCancel.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent arg0) { if (instructionsCancelActionListener != null) { instructionsCancelActionListener.actionPerformed(arg0); } } }); panel_2.add(btnInstructionsCancel); btnInstructionsNext = new JButton("Next"); btnInstructionsNext.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent arg0) { if (instructionsProceedActionListener != null) { instructionsProceedActionListener.actionPerformed(arg0); } } }); panel_2.add(btnInstructionsNext); panel_1 = new JPanel(); panelInstructions.add(panel_1, BorderLayout.CENTER); panel_1.setLayout(new BorderLayout(0, 0)); lblInstructions = new JTextPane(); lblInstructions.setFont(new Font("Lucida Grande", Font.PLAIN, 14)); lblInstructions.setBackground(UIManager.getColor("Panel.background")); lblInstructions.setContentType("text/html"); lblInstructions.setEditable(false); panel_1.add(lblInstructions); cameraPanel.setBorder(new TitledBorder(null, "Cameras", TitledBorder.LEADING, TitledBorder.TOP, null, null)); boolean javaFxAvailable = false; try { Class.forName("javafx.scene.Scene"); javaFxAvailable = true; } catch (Throwable e) { Logger.warn("JavaFX is not installed. The optional navigation feature will not be available."); } if (javaFxAvailable) { navigationPanel = new FxNavigationView(); camerasAndNavTabbedPane = new JTabbedPane(JTabbedPane.TOP); camerasAndNavTabbedPane.addTab("Cameras", null, cameraPanel, null); camerasAndNavTabbedPane.addTab("Navigation", null, navigationPanel, null); panelCameraAndInstructions.add(camerasAndNavTabbedPane, BorderLayout.CENTER); } else { panelCameraAndInstructions.add(cameraPanel, BorderLayout.CENTER); } tabs = new JTabbedPane(JTabbedPane.TOP); splitPaneTopBottom.setRightComponent(tabs); lblStatus = new JLabel(" "); lblStatus.setBorder(new BevelBorder(BevelBorder.LOWERED, null, null, null, null)); contentPane.add(lblStatus, BorderLayout.SOUTH); splitPaneTopBottom .setDividerLocation(prefs.getInt(PREF_DIVIDER_POSITION, PREF_DIVIDER_POSITION_DEF)); splitPaneTopBottom.addPropertyChangeListener("dividerLocation", new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { prefs.putInt(PREF_DIVIDER_POSITION, splitPaneTopBottom.getDividerLocation()); } }); tabs.addTab("Job", null, jobPanel, null); tabs.addTab("Parts", null, partsPanel, null); tabs.addTab("Packages", null, packagesPanel, null); tabs.addTab("Feeders", null, feedersPanel, null); tabs.addTab("Machine Setup", null, machineSetupPanel, null); LogPanel logPanel = new LogPanel(); tabs.addTab("Log", null, logPanel, null); registerBoardImporters(); addComponentListener(componentListener); try { configuration.load(); configuration.getScripting().setMenu(mnScripts); } catch (Exception e) { e.printStackTrace(); MessageBoxes.errorBox(this, "Configuration Load Error", "There was a problem loading the configuration. The reason was:<br/><br/>" + e.getMessage() + "<br/><br/>" + "Please check your configuration files and try again. They are located at: " + configuration.getConfigurationDirectory().getAbsolutePath() + "<br/><br/>" + "If you would like to start with a fresh configuration, just delete the entire directory at the location above.<br/><br/>" + "OpenPnP will now exit."); System.exit(1); } } private void registerBoardImporters() { registerBoardImporter(EagleBoardImporter.class); registerBoardImporter(EagleMountsmdUlpImporter.class); registerBoardImporter(KicadPosImporter.class); registerBoardImporter(NamedCSVImporter.class); registerBoardImporter(SolderPasteGerberImporter.class); } /** * Register a BoardImporter with the system, causing it to gain a menu location in the * File->Import menu. * * @param boardImporterClass */ public void registerBoardImporter(final Class<? extends BoardImporter> boardImporterClass) { final BoardImporter boardImporter; try { boardImporter = boardImporterClass.newInstance(); } catch (Exception e) { throw new Error(e); } JMenuItem menuItem = new JMenuItem(new AbstractAction() { { putValue(NAME, boardImporter.getImporterName()); putValue(SHORT_DESCRIPTION, boardImporter.getImporterDescription()); } @Override public void actionPerformed(ActionEvent e) { jobPanel.importBoard(boardImporterClass); } }); mnImport.add(menuItem); } public void showInstructions(String title, String instructions, boolean showCancelButton, boolean showProceedButton, String proceedButtonText, ActionListener cancelActionListener, ActionListener proceedActionListener) { panelInstructionsBorder.setTitle(title); lblInstructions.setText(instructions); btnInstructionsCancel.setVisible(showCancelButton); btnInstructionsNext.setVisible(showProceedButton); btnInstructionsNext.setText(proceedButtonText); instructionsCancelActionListener = cancelActionListener; instructionsProceedActionListener = proceedActionListener; panelInstructions.setVisible(true); doLayout(); panelInstructions.repaint(); } public void hideInstructions() { panelInstructions.setVisible(false); doLayout(); } public boolean registerForMacOSXEvents() { if ((System.getProperty("os.name").toLowerCase().startsWith("mac os x"))) { try { // Generate and register the OSXAdapter, passing it a hash of // all the methods we wish to // use as delegates for various // com.apple.eawt.ApplicationListener methods OSXAdapter.setQuitHandler(this, getClass().getDeclaredMethod("quit", (Class[]) null)); OSXAdapter.setAboutHandler(this, getClass().getDeclaredMethod("about", (Class[]) null)); // OSXAdapter.setPreferencesHandler(this, getClass() // .getDeclaredMethod("preferences", (Class[]) null)); // OSXAdapter.setFileHandler( // this, // getClass().getDeclaredMethod("loadImageFile", // new Class[] { String.class })); } catch (Exception e) { System.err.println("Error while loading the OSXAdapter:"); e.printStackTrace(); } return true; } return false; } public void about() { AboutDialog dialog = new AboutDialog(this); dialog.setSize(750, 550); dialog.setLocationRelativeTo(null); dialog.setModal(true); dialog.setVisible(true); } public boolean quit() { try { Preferences.userRoot().flush(); } catch (Exception e) { } // Save the configuration try { configuration.save(); } catch (Exception e) { String message = "There was a problem saving the configuration. The reason was:\n\n" + e.getMessage() + "\n\nDo you want to quit without saving?"; message = message.replaceAll("\n", "<br/>"); message = message.replaceAll("\r", ""); message = "<html><body width=\"400\">" + message + "</body></html>"; int result = JOptionPane.showConfirmDialog(this, message, "Configuration Save Error", JOptionPane.YES_NO_OPTION); if (result != JOptionPane.YES_OPTION) { return false; } } if (!jobPanel.checkForModifications()) { return false; } // Attempt to stop the machine on quit try { configuration.getMachine().setEnabled(false); } catch (Exception e) { e.printStackTrace(); } // Attempt to stop the machine on quit try { configuration.getMachine().close(); } catch (Exception e) { e.printStackTrace(); } System.exit(0); return true; } public void setStatus(String status) { SwingUtilities.invokeLater(() -> { lblStatus.setText(status); }); } public void showTab(String title) { int index = tabs.indexOfTab(title); tabs.setSelectedIndex(index); } private ComponentListener componentListener = new ComponentAdapter() { @Override public void componentMoved(ComponentEvent e) { prefs.putInt(PREF_WINDOW_X, getLocation().x); prefs.putInt(PREF_WINDOW_Y, getLocation().y); } @Override public void componentResized(ComponentEvent e) { prefs.putInt(PREF_WINDOW_WIDTH, getSize().width); prefs.putInt(PREF_WINDOW_HEIGHT, getSize().height); } }; private Action inchesUnitSelected = new AbstractAction(LengthUnit.Inches.name()) { @Override public void actionPerformed(ActionEvent arg0) { configuration.setSystemUnits(LengthUnit.Inches); MessageBoxes.infoBox("Notice", "Please restart OpenPnP for the changes to take effect."); } }; private Action millimetersUnitSelected = new AbstractAction(LengthUnit.Millimeters.name()) { @Override public void actionPerformed(ActionEvent arg0) { configuration.setSystemUnits(LengthUnit.Millimeters); MessageBoxes.infoBox("Notice", "Please restart OpenPnP for the changes to take effect."); } }; private Action quitAction = new AbstractAction("Exit") { @Override public void actionPerformed(ActionEvent arg0) { quit(); } }; private Action aboutAction = new AbstractAction("About") { @Override public void actionPerformed(ActionEvent arg0) { about(); } }; }