Search in sources :

Example 11 with SpringLayout

use of javax.swing.SpringLayout in project energy3d by concord-consortium.

the class PopupMenuForRack method getPopupMenu.

static JPopupMenu getPopupMenu() {
    if (popupMenuForRack == null) {
        final JMenu trackerMenu = new JMenu("Tracker");
        final JMenuItem miPaste = new JMenuItem("Paste");
        miPaste.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_V, Config.isMac() ? KeyEvent.META_MASK : InputEvent.CTRL_MASK));
        miPaste.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(final ActionEvent e) {
                SceneManager.getTaskManager().update(new Callable<Object>() {

                    @Override
                    public Object call() throws Exception {
                        Scene.getInstance().pasteToPickedLocationOnRack();
                        Scene.getInstance().setEdited(true);
                        return null;
                    }
                });
            }
        });
        final JMenuItem miClear = new JMenuItem("Clear");
        miClear.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (!(selectedPart instanceof Rack)) {
                    return;
                }
                final Rack rack = (Rack) selectedPart;
                SceneManager.getTaskManager().update(new Callable<Object>() {

                    @Override
                    public Object call() {
                        if (rack.isMonolithic()) {
                            rack.setMonolithic(false);
                            rack.draw();
                        } else {
                            Scene.getInstance().removeAllSolarPanels(null);
                            EventQueue.invokeLater(new Runnable() {

                                @Override
                                public void run() {
                                    MainPanel.getInstance().getEnergyButton().setSelected(false);
                                }
                            });
                        }
                        return null;
                    }
                });
            }
        });
        final JMenuItem miFixedTiltAngle = new JMenuItem("Fixed Tilt Angle...");
        miFixedTiltAngle.addActionListener(new ActionListener() {

            // remember the scope selection as the next action will likely be applied to the same scope
            private int selectedScopeIndex = 0;

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (!(selectedPart instanceof Rack)) {
                    return;
                }
                final Rack rack = (Rack) selectedPart;
                if (rack.areMonthlyTiltAnglesSet()) {
                    if (JOptionPane.showConfirmDialog(MainFrame.getInstance(), "<html>Choosing the fixed tilt angle will remove existing settings for seasonal tilt angles.<br>Are you sure?", "Confirm", JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE) == JOptionPane.CANCEL_OPTION) {
                        return;
                    }
                }
                final String partInfo = selectedPart.toString().substring(0, selectedPart.toString().indexOf(')') + 1);
                final String title = "<html>Tilt Angle of " + partInfo + " (&deg;)</html>";
                final String footnote = "<html><hr><font size=2>The tilt angle of a rack is the angle between its surface and the ground surface.<br>The tilt angle must be between -90&deg; and 90&deg;.<hr></html>";
                final JPanel gui = new JPanel(new BorderLayout());
                final JPanel panel = new JPanel();
                panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
                panel.setBorder(BorderFactory.createTitledBorder("Apply to:"));
                final JRadioButton rb1 = new JRadioButton("Only this Rack", true);
                final JRadioButton rb2 = new JRadioButton("All Racks on This Foundation");
                final JRadioButton rb3 = new JRadioButton("All Racks");
                panel.add(rb1);
                panel.add(rb2);
                panel.add(rb3);
                final ButtonGroup bg = new ButtonGroup();
                bg.add(rb1);
                bg.add(rb2);
                bg.add(rb3);
                switch(selectedScopeIndex) {
                    case 0:
                        rb1.setSelected(true);
                        break;
                    case 1:
                        rb2.setSelected(true);
                        break;
                    case 2:
                        rb3.setSelected(true);
                        break;
                }
                gui.add(panel, BorderLayout.CENTER);
                final JTextField inputField = new JTextField(rack.getTiltAngle() + "");
                gui.add(inputField, BorderLayout.SOUTH);
                final Object[] options = new Object[] { "OK", "Cancel", "Apply" };
                final JOptionPane optionPane = new JOptionPane(new Object[] { title, footnote, gui }, JOptionPane.QUESTION_MESSAGE, JOptionPane.YES_NO_CANCEL_OPTION, null, options, options[2]);
                final JDialog dialog = optionPane.createDialog(MainFrame.getInstance(), "Tilt Angle");
                while (true) {
                    inputField.selectAll();
                    inputField.requestFocusInWindow();
                    dialog.setVisible(true);
                    final Object choice = optionPane.getValue();
                    if (choice == options[1] || choice == null) {
                        break;
                    } else {
                        double val = 0;
                        boolean ok = true;
                        try {
                            val = Double.parseDouble(inputField.getText());
                        } catch (final NumberFormatException exception) {
                            JOptionPane.showMessageDialog(MainFrame.getInstance(), inputField.getText() + " is an invalid value!", "Error", JOptionPane.ERROR_MESSAGE);
                            ok = false;
                        }
                        if (ok) {
                            if (val < -90 || val > 90) {
                                JOptionPane.showMessageDialog(MainFrame.getInstance(), "The tilt angle must be between -90 and 90 degrees.", "Range Error", JOptionPane.ERROR_MESSAGE);
                            } else {
                                if (Util.isZero(val - 90)) {
                                    val = 89.999;
                                } else if (Util.isZero(val + 90)) {
                                    val = -89.999;
                                }
                                boolean changed = val != rack.getTiltAngle();
                                if (rb1.isSelected()) {
                                    if (changed) {
                                        final ChangeTiltAngleCommand c = new ChangeTiltAngleCommand(rack);
                                        rack.setTiltAngle(val);
                                        rack.draw();
                                        if (rack.checkContainerIntersection()) {
                                            JOptionPane.showMessageDialog(MainFrame.getInstance(), "The rack cannot be tilted at such an angle as it would cut into the underlying surface.", "Illegal Tilt Angle", JOptionPane.ERROR_MESSAGE);
                                            c.undo();
                                        } else {
                                            SceneManager.getInstance().refresh();
                                            SceneManager.getInstance().getUndoManager().addEdit(c);
                                        }
                                    }
                                    selectedScopeIndex = 0;
                                } else if (rb2.isSelected()) {
                                    final Foundation foundation = rack.getTopContainer();
                                    if (!changed) {
                                        for (final Rack x : foundation.getRacks()) {
                                            if (x.getTiltAngle() != val) {
                                                changed = true;
                                                break;
                                            }
                                        }
                                    }
                                    if (changed) {
                                        final ChangeFoundationRackTiltAngleCommand c = new ChangeFoundationRackTiltAngleCommand(foundation);
                                        foundation.setTiltAngleForRacks(val);
                                        if (foundation.checkContainerIntersectionForRacks()) {
                                            JOptionPane.showMessageDialog(MainFrame.getInstance(), "Racks cannot be tilted at such an angle as one or more would cut into the underlying surface.", "Illegal Tilt Angle", JOptionPane.ERROR_MESSAGE);
                                            c.undo();
                                        } else {
                                            SceneManager.getInstance().getUndoManager().addEdit(c);
                                        }
                                    }
                                    selectedScopeIndex = 1;
                                } else if (rb3.isSelected()) {
                                    if (!changed) {
                                        for (final Rack x : Scene.getInstance().getAllRacks()) {
                                            if (x.getTiltAngle() != val) {
                                                changed = true;
                                                break;
                                            }
                                        }
                                    }
                                    if (changed) {
                                        final ChangeTiltAngleForAllRacksCommand c = new ChangeTiltAngleForAllRacksCommand();
                                        Scene.getInstance().setTiltAngleForAllRacks(val);
                                        if (Scene.getInstance().checkContainerIntersectionForAllRacks()) {
                                            JOptionPane.showMessageDialog(MainFrame.getInstance(), "Racks cannot be tilted at such an angle as one or more would cut into the underlying surface.", "Illegal Tilt Angle", JOptionPane.ERROR_MESSAGE);
                                            c.undo();
                                        } else {
                                            SceneManager.getInstance().getUndoManager().addEdit(c);
                                        }
                                    }
                                    selectedScopeIndex = 2;
                                }
                                if (changed) {
                                    updateAfterEdit();
                                }
                                if (choice == options[0]) {
                                    break;
                                }
                            }
                        }
                    }
                }
            }
        });
        final JMenuItem miSeasonalTiltAngle = new JMenuItem("Seasonally Adjusted Tilt Angles...");
        miSeasonalTiltAngle.addActionListener(new ActionListener() {

            // remember the scope selection as the next action will likely be applied to the same scope
            private int selectedScopeIndex = 0;

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (!(selectedPart instanceof Rack)) {
                    return;
                }
                final String partInfo = selectedPart.toString().substring(0, selectedPart.toString().indexOf(')') + 1);
                final Rack rack = (Rack) selectedPart;
                final String title = "<html>Seasonally Adjusted Tilt Angles of " + partInfo + " (&deg;)</html>";
                final String footnote = "<html><hr><font size=2>The tilt angle of a rack is the angle between its surface and the ground surface.<br>The tilt angle must be between -90&deg; and 90&deg; and may be adjusted seasonally.<hr></html>";
                final JPanel gui = new JPanel(new BorderLayout());
                final JPanel panel = new JPanel();
                panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
                panel.setBorder(BorderFactory.createTitledBorder("Apply to:"));
                final JRadioButton rb1 = new JRadioButton("Only this Rack", true);
                final JRadioButton rb2 = new JRadioButton("All Racks on This Foundation");
                final JRadioButton rb3 = new JRadioButton("All Racks");
                panel.add(rb1);
                panel.add(rb2);
                panel.add(rb3);
                final ButtonGroup bg = new ButtonGroup();
                bg.add(rb1);
                bg.add(rb2);
                bg.add(rb3);
                switch(selectedScopeIndex) {
                    case 0:
                        rb1.setSelected(true);
                        break;
                    case 1:
                        rb2.setSelected(true);
                        break;
                    case 2:
                        rb3.setSelected(true);
                        break;
                }
                gui.add(panel, BorderLayout.CENTER);
                final JPanel inputPanel = new JPanel(new SpringLayout());
                final JTextField[] fields = new JTextField[12];
                for (int i = 0; i < 12; i++) {
                    final JLabel l = new JLabel(AnnualGraph.THREE_LETTER_MONTH[i] + ": ", JLabel.LEFT);
                    inputPanel.add(l);
                    fields[i] = new JTextField(rack.getTiltAngleOfMonth(i) + "", 5);
                    l.setLabelFor(fields[i]);
                    inputPanel.add(fields[i]);
                }
                SpringUtilities.makeCompactGrid(inputPanel, 4, 6, 6, 6, 6, 6);
                gui.add(inputPanel, BorderLayout.SOUTH);
                final Object[] options = new Object[] { "OK", "Cancel", "Apply" };
                final JOptionPane optionPane = new JOptionPane(new Object[] { title, footnote, gui }, JOptionPane.QUESTION_MESSAGE, JOptionPane.YES_NO_CANCEL_OPTION, null, options, options[2]);
                final JDialog dialog = optionPane.createDialog(MainFrame.getInstance(), "Seasonally Adjusted Tilt Angles");
                while (true) {
                    fields[0].selectAll();
                    fields[0].requestFocusInWindow();
                    dialog.setVisible(true);
                    final Object choice = optionPane.getValue();
                    if (choice == options[1] || choice == null) {
                        break;
                    } else {
                        final double[] val = new double[12];
                        boolean ok = true;
                        for (int i = 0; i < 12; i++) {
                            try {
                                val[i] = Double.parseDouble(fields[i].getText());
                            } catch (final NumberFormatException exception) {
                                JOptionPane.showMessageDialog(MainFrame.getInstance(), AnnualGraph.THREE_LETTER_MONTH[i] + ": " + fields[i].getText() + " is an invalid value!", "Error", JOptionPane.ERROR_MESSAGE);
                                ok = false;
                            }
                            if (ok) {
                                if (val[i] < -90 || val[i] > 90) {
                                    JOptionPane.showMessageDialog(MainFrame.getInstance(), AnnualGraph.THREE_LETTER_MONTH[i] + ": The tilt angle must be between -90 and 90 degrees.", "Range Error", JOptionPane.ERROR_MESSAGE);
                                    ok = false;
                                }
                            }
                        }
                        if (ok) {
                            boolean changed = false;
                            for (int i = 0; i < 12; i++) {
                                if (Util.isZero(val[i] - 90)) {
                                    val[i] = 89.999;
                                } else if (Util.isZero(val[i] + 90)) {
                                    val[i] = -89.999;
                                }
                                if (!changed) {
                                    changed = val[i] != rack.getTiltAngleOfMonth(i);
                                }
                            }
                            if (rb1.isSelected()) {
                                if (changed) {
                                    final ChangeMonthlyTiltAnglesCommand c = new ChangeMonthlyTiltAnglesCommand(rack);
                                    rack.setMonthlyTiltAngles(val);
                                    rack.draw();
                                    if (rack.checkContainerIntersection()) {
                                        JOptionPane.showMessageDialog(MainFrame.getInstance(), "The rack cannot be tilted at such an angle as it would cut into the underlying surface.", "Illegal Tilt Angle", JOptionPane.ERROR_MESSAGE);
                                        c.undo();
                                    } else {
                                        SceneManager.getInstance().refresh();
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                }
                                selectedScopeIndex = 0;
                            } else if (rb2.isSelected()) {
                                final Foundation foundation = rack.getTopContainer();
                                if (!changed) {
                                    for (final Rack x : foundation.getRacks()) {
                                        for (int i = 0; i < 12; i++) {
                                            if (x.getTiltAngleOfMonth(i) != val[i]) {
                                                changed = true;
                                                break;
                                            }
                                        }
                                    }
                                }
                                if (changed) {
                                    final ChangeFoundationRackMonthlyTiltAnglesCommand c = new ChangeFoundationRackMonthlyTiltAnglesCommand(foundation);
                                    foundation.setMonthlyTiltAnglesForRacks(val);
                                    if (foundation.checkContainerIntersectionForRacks()) {
                                        JOptionPane.showMessageDialog(MainFrame.getInstance(), "Racks cannot be tilted at such an angle as one or more would cut into the underlying surface.", "Illegal Tilt Angle", JOptionPane.ERROR_MESSAGE);
                                        c.undo();
                                    } else {
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                }
                                selectedScopeIndex = 1;
                            } else if (rb3.isSelected()) {
                                if (!changed) {
                                    for (final Rack x : Scene.getInstance().getAllRacks()) {
                                        for (int i = 0; i < 12; i++) {
                                            if (x.getTiltAngleOfMonth(i) != val[i]) {
                                                changed = true;
                                                break;
                                            }
                                        }
                                    }
                                }
                                if (changed) {
                                    final ChangeMonthlyTiltAnglesForAllRacksCommand c = new ChangeMonthlyTiltAnglesForAllRacksCommand();
                                    Scene.getInstance().setMonthlyTiltAnglesForAllRacks(val);
                                    if (Scene.getInstance().checkContainerIntersectionForAllRacks()) {
                                        JOptionPane.showMessageDialog(MainFrame.getInstance(), "Racks cannot be tilted at such an angle as one or more would cut into the underlying surface.", "Illegal Tilt Angle", JOptionPane.ERROR_MESSAGE);
                                        c.undo();
                                    } else {
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                }
                                selectedScopeIndex = 2;
                            }
                            if (changed) {
                                updateAfterEdit();
                            }
                            if (choice == options[0]) {
                                break;
                            }
                        }
                    }
                }
            }
        });
        final JMenuItem miAzimuth = new JMenuItem("Azimuth...");
        miAzimuth.addActionListener(new ActionListener() {

            // remember the scope selection as the next action will likely be applied to the same scope
            private int selectedScopeIndex = 0;

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (!(selectedPart instanceof Rack)) {
                    return;
                }
                final String partInfo = selectedPart.toString().substring(0, selectedPart.toString().indexOf(')') + 1);
                final Rack rack = (Rack) selectedPart;
                final Foundation foundation = rack.getTopContainer();
                final String title = "<html>Azimuth Angle of " + partInfo + " (&deg;)</html>";
                final String footnote = "<html><hr><font size=2>The azimuth angle is measured clockwise from the true north.<hr></html>";
                final JPanel gui = new JPanel(new BorderLayout());
                final JPanel panel = new JPanel();
                panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
                panel.setBorder(BorderFactory.createTitledBorder("Apply to:"));
                final JRadioButton rb1 = new JRadioButton("Only this Rack", true);
                final JRadioButton rb2 = new JRadioButton("All Racks on this Foundation");
                final JRadioButton rb3 = new JRadioButton("All Racks");
                panel.add(rb1);
                panel.add(rb2);
                panel.add(rb3);
                final ButtonGroup bg = new ButtonGroup();
                bg.add(rb1);
                bg.add(rb2);
                bg.add(rb3);
                switch(selectedScopeIndex) {
                    case 0:
                        rb1.setSelected(true);
                        break;
                    case 1:
                        rb2.setSelected(true);
                        break;
                    case 2:
                        rb3.setSelected(true);
                        break;
                }
                gui.add(panel, BorderLayout.CENTER);
                double a = rack.getRelativeAzimuth() + foundation.getAzimuth();
                if (a > 360) {
                    a -= 360;
                }
                final JTextField inputField = new JTextField(a + "");
                gui.add(inputField, BorderLayout.SOUTH);
                final Object[] options = new Object[] { "OK", "Cancel", "Apply" };
                final JOptionPane optionPane = new JOptionPane(new Object[] { title, footnote, gui }, JOptionPane.QUESTION_MESSAGE, JOptionPane.YES_NO_CANCEL_OPTION, null, options, options[2]);
                final JDialog dialog = optionPane.createDialog(MainFrame.getInstance(), "Azimuth Angle");
                while (true) {
                    inputField.selectAll();
                    inputField.requestFocusInWindow();
                    dialog.setVisible(true);
                    final Object choice = optionPane.getValue();
                    if (choice == options[1] || choice == null) {
                        break;
                    } else {
                        boolean ok = true;
                        try {
                            a = Double.parseDouble(inputField.getText()) - foundation.getAzimuth();
                        } catch (final NumberFormatException exception) {
                            JOptionPane.showMessageDialog(MainFrame.getInstance(), inputField.getText() + " is an invalid value!", "Error", JOptionPane.ERROR_MESSAGE);
                            ok = false;
                        }
                        if (ok) {
                            if (a < 0) {
                                a += 360;
                            }
                            boolean changed = a != rack.getRelativeAzimuth();
                            if (rb1.isSelected()) {
                                if (changed) {
                                    final ChangeAzimuthCommand c = new ChangeAzimuthCommand(rack);
                                    rack.setRelativeAzimuth(a);
                                    rack.draw();
                                    SceneManager.getInstance().refresh();
                                    SceneManager.getInstance().getUndoManager().addEdit(c);
                                }
                                selectedScopeIndex = 0;
                            } else if (rb2.isSelected()) {
                                if (!changed) {
                                    for (final Rack x : foundation.getRacks()) {
                                        if (x.getRelativeAzimuth() != a) {
                                            changed = true;
                                            break;
                                        }
                                    }
                                }
                                if (changed) {
                                    final ChangeFoundationRackAzimuthCommand c = new ChangeFoundationRackAzimuthCommand(foundation);
                                    foundation.setAzimuthForRacks(a);
                                    SceneManager.getInstance().getUndoManager().addEdit(c);
                                }
                                selectedScopeIndex = 1;
                            } else if (rb3.isSelected()) {
                                if (!changed) {
                                    for (final Rack x : Scene.getInstance().getAllRacks()) {
                                        if (x.getRelativeAzimuth() != a) {
                                            changed = true;
                                            break;
                                        }
                                    }
                                }
                                if (changed) {
                                    final ChangeAzimuthForAllRacksCommand c = new ChangeAzimuthForAllRacksCommand();
                                    Scene.getInstance().setAzimuthForAllRacks(a);
                                    SceneManager.getInstance().getUndoManager().addEdit(c);
                                }
                                selectedScopeIndex = 2;
                            }
                            if (changed) {
                                updateAfterEdit();
                            }
                            if (choice == options[0]) {
                                break;
                            }
                        }
                    }
                }
            }
        });
        final JMenuItem miRotate = new JMenuItem("Rotate 90\u00B0");
        miRotate.addActionListener(new ActionListener() {

            // remember the scope selection as the next action will likely be applied to the same scope
            private int selectedScopeIndex = 0;

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (!(selectedPart instanceof Rack)) {
                    return;
                }
                final Rack rack = (Rack) selectedPart;
                final Foundation foundation = rack.getTopContainer();
                final String partInfo = rack.toString().substring(0, selectedPart.toString().indexOf(')') + 1);
                final JPanel gui = new JPanel(new BorderLayout());
                final JPanel scopePanel = new JPanel();
                scopePanel.setLayout(new BoxLayout(scopePanel, BoxLayout.Y_AXIS));
                scopePanel.setBorder(BorderFactory.createTitledBorder("Apply to:"));
                final JRadioButton rb1 = new JRadioButton("Only this Rack", true);
                final JRadioButton rb2 = new JRadioButton("All Racks on this Foundation");
                final JRadioButton rb3 = new JRadioButton("All Racks");
                scopePanel.add(rb1);
                scopePanel.add(rb2);
                scopePanel.add(rb3);
                final ButtonGroup bg = new ButtonGroup();
                bg.add(rb1);
                bg.add(rb2);
                bg.add(rb3);
                switch(selectedScopeIndex) {
                    case 0:
                        rb1.setSelected(true);
                        break;
                    case 1:
                        rb2.setSelected(true);
                        break;
                    case 2:
                        rb3.setSelected(true);
                        break;
                }
                gui.add(scopePanel, BorderLayout.CENTER);
                final Object[] options = new Object[] { "OK", "Cancel", "Apply" };
                final JOptionPane optionPane = new JOptionPane(new Object[] { "Rotate 90\u00B0 for " + partInfo, gui }, JOptionPane.QUESTION_MESSAGE, JOptionPane.YES_NO_CANCEL_OPTION, null, options, options[2]);
                final JDialog dialog = optionPane.createDialog(MainFrame.getInstance(), "Rotate Rack");
                while (true) {
                    dialog.setVisible(true);
                    final Object choice = optionPane.getValue();
                    if (choice == options[1] || choice == null) {
                        break;
                    } else {
                        if (rb1.isSelected()) {
                            double a = rack.getRelativeAzimuth() + 90;
                            if (a > 360) {
                                a -= 360;
                            }
                            final ChangeAzimuthCommand c = new ChangeAzimuthCommand(rack);
                            rack.setRelativeAzimuth(a);
                            rack.draw();
                            SceneManager.getInstance().refresh();
                            SceneManager.getInstance().getUndoManager().addEdit(c);
                            selectedScopeIndex = 0;
                        } else if (rb2.isSelected()) {
                            final ChangeFoundationRackAzimuthCommand c = new ChangeFoundationRackAzimuthCommand(foundation);
                            final List<Rack> racks = foundation.getRacks();
                            for (final Rack r : racks) {
                                double a = r.getRelativeAzimuth() + 90;
                                if (a > 360) {
                                    a -= 360;
                                }
                                r.setRelativeAzimuth(a);
                                r.draw();
                            }
                            SceneManager.getInstance().refresh();
                            SceneManager.getInstance().getUndoManager().addEdit(c);
                            selectedScopeIndex = 1;
                        } else if (rb3.isSelected()) {
                            final ChangeAzimuthForAllRacksCommand c = new ChangeAzimuthForAllRacksCommand();
                            final List<Rack> racks = Scene.getInstance().getAllRacks();
                            for (final Rack r : racks) {
                                double a = r.getRelativeAzimuth() + 90;
                                if (a > 360) {
                                    a -= 360;
                                }
                                r.setRelativeAzimuth(a);
                                r.draw();
                            }
                            SceneManager.getInstance().refresh();
                            SceneManager.getInstance().getUndoManager().addEdit(c);
                            selectedScopeIndex = 2;
                        }
                        updateAfterEdit();
                        if (choice == options[0]) {
                            break;
                        }
                    }
                }
            }
        });
        final JMenuItem miRackWidth = new JMenuItem("Width...");
        miRackWidth.addActionListener(new ActionListener() {

            // remember the scope selection as the next action will likely be applied to the same scope
            private int selectedScopeIndex = 0;

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (!(selectedPart instanceof Rack)) {
                    return;
                }
                final Rack rack = (Rack) selectedPart;
                final Foundation foundation = rack.getTopContainer();
                final String partInfo = rack.toString().substring(0, selectedPart.toString().indexOf(')') + 1);
                final JPanel gui = new JPanel(new BorderLayout());
                final JPanel inputPanel = new JPanel(new SpringLayout());
                inputPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
                gui.add(inputPanel, BorderLayout.CENTER);
                final JLabel label = new JLabel("Width (m): ", JLabel.TRAILING);
                inputPanel.add(label);
                // rack uses width and height, which should have been named length and width
                final JTextField inputField = new JTextField(threeDecimalsFormat.format(rack.getRackHeight()));
                label.setLabelFor(inputField);
                inputPanel.add(inputField);
                SpringUtilities.makeCompactGrid(inputPanel, 1, 2, 6, 6, 6, 6);
                final JPanel scopePanel = new JPanel();
                scopePanel.setLayout(new BoxLayout(scopePanel, BoxLayout.Y_AXIS));
                scopePanel.setBorder(BorderFactory.createTitledBorder("Apply to:"));
                final JRadioButton rb1 = new JRadioButton("Only this Rack", true);
                final JRadioButton rb2 = new JRadioButton("All Racks on this Foundation");
                final JRadioButton rb3 = new JRadioButton("All Racks");
                scopePanel.add(rb1);
                scopePanel.add(rb2);
                scopePanel.add(rb3);
                final ButtonGroup bg = new ButtonGroup();
                bg.add(rb1);
                bg.add(rb2);
                bg.add(rb3);
                switch(selectedScopeIndex) {
                    case 0:
                        rb1.setSelected(true);
                        break;
                    case 1:
                        rb2.setSelected(true);
                        break;
                    case 2:
                        rb3.setSelected(true);
                        break;
                }
                gui.add(scopePanel, BorderLayout.NORTH);
                final Object[] options = new Object[] { "OK", "Cancel", "Apply" };
                final JOptionPane optionPane = new JOptionPane(new Object[] { "Set width for " + partInfo, gui }, JOptionPane.QUESTION_MESSAGE, JOptionPane.YES_NO_CANCEL_OPTION, null, options, options[2]);
                final JDialog dialog = optionPane.createDialog(MainFrame.getInstance(), "Rack Width");
                while (true) {
                    dialog.setVisible(true);
                    final Object choice = optionPane.getValue();
                    if (choice == options[1] || choice == null) {
                        break;
                    } else {
                        double val = 0;
                        boolean ok = true;
                        try {
                            val = Double.parseDouble(inputField.getText());
                        } catch (final NumberFormatException x) {
                            JOptionPane.showMessageDialog(MainFrame.getInstance(), "Invalid input!", "Error", JOptionPane.ERROR_MESSAGE);
                            ok = false;
                        }
                        if (ok) {
                            if (val < 0.5 || val > 50) {
                                JOptionPane.showMessageDialog(MainFrame.getInstance(), "Width must be between 0.5 and 50 m.", "Range Error", JOptionPane.ERROR_MESSAGE);
                            } else {
                                boolean changed = val != rack.getRackHeight();
                                if (rb1.isSelected()) {
                                    if (changed) {
                                        final SetPartSizeCommand c = new SetPartSizeCommand(rack);
                                        rack.setRackHeight(val);
                                        rack.ensureFullSolarPanels(false);
                                        rack.draw();
                                        if (rack.checkContainerIntersection()) {
                                            JOptionPane.showMessageDialog(MainFrame.getInstance(), "This width cannot be set as the rack would cut into the underlying surface.", "Illegal Size", JOptionPane.ERROR_MESSAGE);
                                            c.undo();
                                        } else {
                                            SceneManager.getInstance().refresh();
                                            SceneManager.getInstance().getUndoManager().addEdit(c);
                                        }
                                    }
                                    selectedScopeIndex = 0;
                                } else if (rb2.isSelected()) {
                                    if (!changed) {
                                        for (final Rack x : foundation.getRacks()) {
                                            if (x.getRackHeight() != val) {
                                                changed = true;
                                                break;
                                            }
                                        }
                                    }
                                    if (changed) {
                                        final SetSizeForRacksOnFoundationCommand c = new SetSizeForRacksOnFoundationCommand(foundation);
                                        foundation.setWidthForRacks(val);
                                        if (foundation.checkContainerIntersectionForRacks()) {
                                            JOptionPane.showMessageDialog(MainFrame.getInstance(), "This width cannot be set as one or more racks would cut into the underlying surface.", "Illegal Size", JOptionPane.ERROR_MESSAGE);
                                            c.undo();
                                        } else {
                                            SceneManager.getInstance().getUndoManager().addEdit(c);
                                        }
                                    }
                                    selectedScopeIndex = 1;
                                } else if (rb3.isSelected()) {
                                    if (!changed) {
                                        for (final Rack x : Scene.getInstance().getAllRacks()) {
                                            if (x.getRackHeight() != val) {
                                                changed = true;
                                                break;
                                            }
                                        }
                                    }
                                    if (changed) {
                                        final SetSizeForAllRacksCommand c = new SetSizeForAllRacksCommand();
                                        Scene.getInstance().setWidthForAllRacks(val);
                                        if (Scene.getInstance().checkContainerIntersectionForAllRacks()) {
                                            JOptionPane.showMessageDialog(MainFrame.getInstance(), "This width cannot be set as one or more racks would cut into the underlying surface.", "Illegal Size", JOptionPane.ERROR_MESSAGE);
                                            c.undo();
                                        } else {
                                            SceneManager.getInstance().getUndoManager().addEdit(c);
                                        }
                                    }
                                    selectedScopeIndex = 2;
                                }
                                if (changed) {
                                    updateAfterEdit();
                                }
                                if (choice == options[0]) {
                                    break;
                                }
                            }
                        }
                    }
                }
            }
        });
        final JMenuItem miRackLength = new JMenuItem("Length...");
        miRackLength.addActionListener(new ActionListener() {

            // remember the scope selection as the next action will likely be applied to the same scope
            private int selectedScopeIndex = 0;

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (!(selectedPart instanceof Rack)) {
                    return;
                }
                final Rack rack = (Rack) selectedPart;
                final Foundation foundation = rack.getTopContainer();
                final String partInfo = rack.toString().substring(0, selectedPart.toString().indexOf(')') + 1);
                final JPanel gui = new JPanel(new BorderLayout());
                final JPanel inputPanel = new JPanel(new SpringLayout());
                inputPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
                gui.add(inputPanel, BorderLayout.CENTER);
                final JLabel label = new JLabel("Length (m): ", JLabel.TRAILING);
                inputPanel.add(label);
                // rack uses width and height, which should have been named length and width
                final JTextField inputField = new JTextField(threeDecimalsFormat.format(rack.getRackWidth()));
                label.setLabelFor(inputField);
                inputPanel.add(inputField);
                SpringUtilities.makeCompactGrid(inputPanel, 1, 2, 6, 6, 6, 6);
                final JPanel scopePanel = new JPanel();
                scopePanel.setLayout(new BoxLayout(scopePanel, BoxLayout.Y_AXIS));
                scopePanel.setBorder(BorderFactory.createTitledBorder("Apply to:"));
                final JRadioButton rb1 = new JRadioButton("Only this Rack", true);
                final JRadioButton rb2 = new JRadioButton("All Racks on this Foundation");
                final JRadioButton rb3 = new JRadioButton("All Racks");
                scopePanel.add(rb1);
                scopePanel.add(rb2);
                scopePanel.add(rb3);
                final ButtonGroup bg = new ButtonGroup();
                bg.add(rb1);
                bg.add(rb2);
                bg.add(rb3);
                switch(selectedScopeIndex) {
                    case 0:
                        rb1.setSelected(true);
                        break;
                    case 1:
                        rb2.setSelected(true);
                        break;
                    case 2:
                        rb3.setSelected(true);
                        break;
                }
                gui.add(scopePanel, BorderLayout.NORTH);
                final Object[] options = new Object[] { "OK", "Cancel", "Apply" };
                final JOptionPane optionPane = new JOptionPane(new Object[] { "Set length for " + partInfo, gui }, JOptionPane.QUESTION_MESSAGE, JOptionPane.YES_NO_CANCEL_OPTION, null, options, options[2]);
                final JDialog dialog = optionPane.createDialog(MainFrame.getInstance(), "Rack Length");
                while (true) {
                    dialog.setVisible(true);
                    inputField.selectAll();
                    inputField.requestFocusInWindow();
                    final Object choice = optionPane.getValue();
                    if (choice == options[1] || choice == null) {
                        break;
                    } else {
                        double val = 0;
                        boolean ok = true;
                        try {
                            val = Double.parseDouble(inputField.getText());
                        } catch (final NumberFormatException x) {
                            JOptionPane.showMessageDialog(MainFrame.getInstance(), "Invalid input!", "Error", JOptionPane.ERROR_MESSAGE);
                            ok = false;
                        }
                        if (ok) {
                            if (val < 1 || val > 1000) {
                                JOptionPane.showMessageDialog(MainFrame.getInstance(), "Length must be between 1 and 1000 m.", "Range Error", JOptionPane.ERROR_MESSAGE);
                            } else {
                                boolean changed = val != rack.getRackWidth();
                                if (rb1.isSelected()) {
                                    if (changed) {
                                        final SetPartSizeCommand c = new SetPartSizeCommand(rack);
                                        rack.setRackWidth(val);
                                        rack.ensureFullSolarPanels(false);
                                        rack.draw();
                                        SceneManager.getInstance().refresh();
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 0;
                                } else if (rb2.isSelected()) {
                                    if (!changed) {
                                        for (final Rack x : foundation.getRacks()) {
                                            if (x.getRackWidth() != val) {
                                                changed = true;
                                                break;
                                            }
                                        }
                                    }
                                    if (changed) {
                                        final SetSizeForRacksOnFoundationCommand c = new SetSizeForRacksOnFoundationCommand(foundation);
                                        foundation.setLengthForRacks(val);
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 1;
                                } else if (rb3.isSelected()) {
                                    if (!changed) {
                                        for (final Rack x : Scene.getInstance().getAllRacks()) {
                                            if (x.getRackWidth() != val) {
                                                changed = true;
                                                break;
                                            }
                                        }
                                    }
                                    if (changed) {
                                        final SetSizeForAllRacksCommand c = new SetSizeForAllRacksCommand();
                                        Scene.getInstance().setLengthForAllRacks(val);
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 2;
                                }
                                if (changed) {
                                    updateAfterEdit();
                                }
                                if (choice == options[0]) {
                                    break;
                                }
                            }
                        }
                    }
                }
            }
        });
        final JMenuItem miBaseHeight = new JMenuItem("Base Height...");
        miBaseHeight.addActionListener(new ActionListener() {

            // remember the scope selection as the next action will likely be applied to the same scope
            private int selectedScopeIndex = 0;

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (!(selectedPart instanceof Rack)) {
                    return;
                }
                final String partInfo = selectedPart.toString().substring(0, selectedPart.toString().indexOf(')') + 1);
                final Rack rack = (Rack) selectedPart;
                final Foundation foundation = rack.getTopContainer();
                final String title = "<html>Base Height (m) of " + partInfo + "</html>";
                final String footnote = "<html><hr><font size=2></html>";
                final JPanel panel = new JPanel();
                panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
                panel.setBorder(BorderFactory.createTitledBorder("Apply to:"));
                final JRadioButton rb1 = new JRadioButton("Only this Rack", true);
                final JRadioButton rb2 = new JRadioButton("All Racks on this Foundation");
                final JRadioButton rb3 = new JRadioButton("All Racks");
                panel.add(rb1);
                panel.add(rb2);
                panel.add(rb3);
                final ButtonGroup bg = new ButtonGroup();
                bg.add(rb1);
                bg.add(rb2);
                bg.add(rb3);
                switch(selectedScopeIndex) {
                    case 0:
                        rb1.setSelected(true);
                        break;
                    case 1:
                        rb2.setSelected(true);
                        break;
                    case 2:
                        rb3.setSelected(true);
                        break;
                }
                final JPanel gui = new JPanel(new BorderLayout());
                gui.add(panel, BorderLayout.CENTER);
                final JTextField inputField = new JTextField(rack.getBaseHeight() * Scene.getInstance().getAnnotationScale() + "");
                gui.add(inputField, BorderLayout.SOUTH);
                final Object[] options = new Object[] { "OK", "Cancel", "Apply" };
                final JOptionPane optionPane = new JOptionPane(new Object[] { title, footnote, gui }, JOptionPane.QUESTION_MESSAGE, JOptionPane.YES_NO_CANCEL_OPTION, null, options, options[2]);
                final JDialog dialog = optionPane.createDialog(MainFrame.getInstance(), "Base Height");
                while (true) {
                    inputField.selectAll();
                    inputField.requestFocusInWindow();
                    dialog.setVisible(true);
                    final Object choice = optionPane.getValue();
                    if (choice == options[1] || choice == null) {
                        break;
                    } else {
                        boolean ok = true;
                        double val = 0;
                        try {
                            val = Double.parseDouble(inputField.getText()) / Scene.getInstance().getAnnotationScale();
                        } catch (final NumberFormatException exception) {
                            JOptionPane.showMessageDialog(MainFrame.getInstance(), "Invalid input!", "Error", JOptionPane.ERROR_MESSAGE);
                            ok = false;
                        }
                        if (ok) {
                            boolean changed = val != rack.getBaseHeight();
                            if (rb1.isSelected()) {
                                if (changed) {
                                    final ChangeBaseHeightCommand c = new ChangeBaseHeightCommand(rack);
                                    rack.setBaseHeight(val);
                                    rack.draw();
                                    if (rack.checkContainerIntersection()) {
                                        JOptionPane.showMessageDialog(MainFrame.getInstance(), "The base height cannot be set this low as the rack would cut into the underlying surface.", "Illegal Base Height", JOptionPane.ERROR_MESSAGE);
                                        c.undo();
                                    } else {
                                        SceneManager.getInstance().refresh();
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                }
                                selectedScopeIndex = 0;
                            } else if (rb2.isSelected()) {
                                if (!changed) {
                                    for (final Rack x : foundation.getRacks()) {
                                        if (x.getBaseHeight() != val) {
                                            changed = true;
                                            break;
                                        }
                                    }
                                }
                                if (changed) {
                                    final ChangeFoundationSolarCollectorBaseHeightCommand c = new ChangeFoundationSolarCollectorBaseHeightCommand(foundation, rack.getClass());
                                    foundation.setBaseHeightForRacks(val);
                                    if (foundation.checkContainerIntersectionForRacks()) {
                                        JOptionPane.showMessageDialog(MainFrame.getInstance(), "Base heights cannot be set this low as one or more racks would cut into the underlying surface.", "Illegal Base Height", JOptionPane.ERROR_MESSAGE);
                                        c.undo();
                                    } else {
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                }
                                selectedScopeIndex = 1;
                            } else if (rb3.isSelected()) {
                                if (!changed) {
                                    for (final Rack x : Scene.getInstance().getAllRacks()) {
                                        if (x.getBaseHeight() != val) {
                                            changed = true;
                                            break;
                                        }
                                    }
                                }
                                if (changed) {
                                    final ChangeBaseHeightForAllSolarCollectorsCommand c = new ChangeBaseHeightForAllSolarCollectorsCommand(rack.getClass());
                                    Scene.getInstance().setBaseHeightForAllRacks(val);
                                    if (Scene.getInstance().checkContainerIntersectionForAllRacks()) {
                                        JOptionPane.showMessageDialog(MainFrame.getInstance(), "Base heights cannot be set this low as one or more racks would cut into the underlying surface.", "Illegal Base Height", JOptionPane.ERROR_MESSAGE);
                                        c.undo();
                                    } else {
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                }
                                selectedScopeIndex = 2;
                            }
                            if (changed) {
                                updateAfterEdit();
                            }
                            if (choice == options[0]) {
                                break;
                            }
                        }
                    }
                }
            }
        });
        final JMenuItem miPoleSpacing = new JMenuItem("Pole Settings...");
        miPoleSpacing.addActionListener(new ActionListener() {

            // remember the scope selection as the next action will likely be applied to the same scope
            private int selectedScopeIndex = 0;

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (!(selectedPart instanceof Rack)) {
                    return;
                }
                final Rack rack = (Rack) selectedPart;
                final String partInfo = rack.toString().substring(0, rack.toString().indexOf(')') + 1);
                final String title = "<html>Pole Settings of " + partInfo + "</html>";
                final JPanel gui = new JPanel(new BorderLayout());
                final JPanel inputPanel = new JPanel(new GridLayout(3, 2, 5, 5));
                gui.add(inputPanel, BorderLayout.CENTER);
                inputPanel.add(new JLabel("Distance X (m): "));
                final JTextField dxField = new JTextField(threeDecimalsFormat.format(rack.getPoleDistanceX()));
                inputPanel.add(dxField);
                inputPanel.add(new JLabel("Distance Y (m): "));
                final JTextField dyField = new JTextField(threeDecimalsFormat.format(rack.getPoleDistanceY()));
                inputPanel.add(dyField);
                inputPanel.add(new JLabel("Visible: "));
                final JComboBox<String> visibleComboBox = new JComboBox<String>(new String[] { "Yes", "No" });
                visibleComboBox.setSelectedIndex(rack.isPoleVisible() ? 0 : 1);
                inputPanel.add(visibleComboBox);
                inputPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
                final JPanel scopePanel = new JPanel();
                scopePanel.setLayout(new BoxLayout(scopePanel, BoxLayout.Y_AXIS));
                scopePanel.setBorder(BorderFactory.createTitledBorder("Apply to:"));
                final JRadioButton rb1 = new JRadioButton("Only this Rack", true);
                final JRadioButton rb2 = new JRadioButton("All Racks on this Foundation");
                final JRadioButton rb3 = new JRadioButton("All Racks");
                scopePanel.add(rb1);
                scopePanel.add(rb2);
                scopePanel.add(rb3);
                final ButtonGroup bg = new ButtonGroup();
                bg.add(rb1);
                bg.add(rb2);
                bg.add(rb3);
                switch(selectedScopeIndex) {
                    case 0:
                        rb1.setSelected(true);
                        break;
                    case 1:
                        rb2.setSelected(true);
                        break;
                    case 2:
                        rb3.setSelected(true);
                        break;
                }
                gui.add(scopePanel, BorderLayout.NORTH);
                final Object[] options = new Object[] { "OK", "Cancel", "Apply" };
                final JOptionPane optionPane = new JOptionPane(new Object[] { title, gui }, JOptionPane.QUESTION_MESSAGE, JOptionPane.YES_NO_CANCEL_OPTION, null, options, options[2]);
                final JDialog dialog = optionPane.createDialog(MainFrame.getInstance(), "Pole Settings");
                while (true) {
                    dialog.setVisible(true);
                    final Object choice = optionPane.getValue();
                    if (choice == options[1] || choice == null) {
                        break;
                    } else {
                        boolean ok = true;
                        double dx = 0, dy = 0;
                        try {
                            dx = Double.parseDouble(dxField.getText());
                            dy = Double.parseDouble(dyField.getText());
                        } catch (final NumberFormatException x) {
                            JOptionPane.showMessageDialog(MainFrame.getInstance(), "Invalid input!", "Error", JOptionPane.ERROR_MESSAGE);
                            ok = false;
                        }
                        if (ok) {
                            if (dx < 1 || dx > 50) {
                                JOptionPane.showMessageDialog(MainFrame.getInstance(), "Dx must be between 1 and 50 m.", "Range Error", JOptionPane.ERROR_MESSAGE);
                            } else if (dy < 1 || dy > 50) {
                                JOptionPane.showMessageDialog(MainFrame.getInstance(), "Dy must be between 1 and 50 m.", "Range Error", JOptionPane.ERROR_MESSAGE);
                            } else {
                                final boolean visible = visibleComboBox.getSelectedIndex() == 0;
                                boolean changed = dx != rack.getPoleDistanceX() || dy != rack.getPoleDistanceY();
                                if (rb1.isSelected()) {
                                    if (changed) {
                                        final ChangeRackPoleSettingsCommand c = new ChangeRackPoleSettingsCommand(rack);
                                        rack.setPoleDistanceX(dx);
                                        rack.setPoleDistanceY(dy);
                                        rack.setPoleVisible(visible);
                                        rack.draw();
                                        SceneManager.getInstance().refresh();
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 0;
                                } else if (rb2.isSelected()) {
                                    final Foundation foundation = rack.getTopContainer();
                                    if (!changed) {
                                        for (final Rack x : foundation.getRacks()) {
                                            if (x.getPoleDistanceX() != dx || x.getPoleDistanceY() != dy) {
                                                changed = true;
                                                break;
                                            }
                                        }
                                    }
                                    if (changed) {
                                        final ChangePoleSettingsForRacksOnFoundationCommand c = new ChangePoleSettingsForRacksOnFoundationCommand(foundation);
                                        foundation.setPoleSpacingForRacks(dx, dy, visible);
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 1;
                                } else if (rb3.isSelected()) {
                                    if (!changed) {
                                        for (final Rack x : Scene.getInstance().getAllRacks()) {
                                            if (x.getPoleDistanceX() != dx || x.getPoleDistanceY() != dy) {
                                                changed = true;
                                                break;
                                            }
                                        }
                                    }
                                    if (changed) {
                                        final ChangePoleSettingsForAllRacksCommand c = new ChangePoleSettingsForAllRacksCommand();
                                        Scene.getInstance().setPoleSpacingForAllRacks(dx, dy, visible);
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 2;
                                }
                                if (changed) {
                                    updateAfterEdit();
                                }
                                if (choice == options[0]) {
                                    break;
                                }
                            }
                        }
                    }
                }
            }
        });
        final JMenuItem miSolarPanels = new JMenuItem("Select Solar Panels...");
        miSolarPanels.addActionListener(new ActionListener() {

            private Rack rack;

            private JComboBox<String> modelComboBox;

            private JComboBox<String> sizeComboBox;

            private JComboBox<String> orientationComboBox;

            private JComboBox<String> cellTypeComboBox;

            private JComboBox<String> colorOptionComboBox;

            private JComboBox<String> shadeToleranceComboBox;

            private JTextField cellEfficiencyField;

            private JTextField noctField;

            private JTextField pmaxTcField;

            private double cellEfficiency;

            private double inverterEfficiency;

            private double pmax;

            private double noct;

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (!(selectedPart instanceof Rack)) {
                    return;
                }
                rack = (Rack) selectedPart;
                final int n = rack.getChildren().size();
                if (n > 0 && JOptionPane.showConfirmDialog(MainFrame.getInstance(), "All existing " + n + " solar panels on this rack must be removed before\na new layout can be applied. Do you want to continue?", "Confirmation", JOptionPane.OK_CANCEL_OPTION) == JOptionPane.CANCEL_OPTION) {
                    return;
                }
                final SolarPanel solarPanel = rack.getSolarPanel();
                final JPanel panel = new JPanel(new SpringLayout());
                panel.add(new JLabel("Model"));
                modelComboBox = new JComboBox<String>();
                modelComboBox.addItem("Custom");
                final Map<String, PvModuleSpecs> modules = PvModulesData.getInstance().getModules();
                for (final String key : modules.keySet()) {
                    modelComboBox.addItem(key);
                }
                if (solarPanel.getModelName() != null) {
                    modelComboBox.setSelectedItem(solarPanel.getModelName());
                }
                modelComboBox.addItemListener(new ItemListener() {

                    @Override
                    public void itemStateChanged(final ItemEvent e) {
                        if (e.getStateChange() == ItemEvent.SELECTED) {
                            final boolean isCustom = modelComboBox.getSelectedIndex() == 0;
                            sizeComboBox.setEnabled(isCustom);
                            cellTypeComboBox.setEnabled(isCustom);
                            colorOptionComboBox.setEnabled(isCustom);
                            shadeToleranceComboBox.setEnabled(isCustom);
                            cellEfficiencyField.setEnabled(isCustom);
                            noctField.setEnabled(isCustom);
                            pmaxTcField.setEnabled(isCustom);
                            if (!isCustom) {
                                final PvModuleSpecs specs = modules.get(modelComboBox.getSelectedItem());
                                cellTypeComboBox.setSelectedItem(specs.getCellType());
                                shadeToleranceComboBox.setSelectedItem(specs.getShadeTolerance());
                                cellEfficiencyField.setText(threeDecimalsFormat.format(specs.getCelLEfficiency() * 100));
                                noctField.setText(threeDecimalsFormat.format(specs.getNoct()));
                                pmaxTcField.setText(sixDecimalsFormat.format(specs.getPmaxTc()));
                                final String s = threeDecimalsFormat.format(specs.getNominalWidth()) + "m \u00D7 " + threeDecimalsFormat.format(specs.getNominalLength()) + "m (" + specs.getLayout().width + " \u00D7 " + specs.getLayout().height + " cells)";
                                sizeComboBox.setSelectedItem(s);
                                colorOptionComboBox.setSelectedItem(specs.getColor());
                            }
                        }
                    }
                });
                panel.add(modelComboBox);
                // the following properties should be disabled when the model is not custom
                panel.add(new JLabel("Panel Size:"));
                sizeComboBox = new JComboBox<String>(solarPanelNominalSize.getStrings());
                final PvModuleSpecs specs = solarPanel.getPvModuleSpecs();
                final boolean isCustom = "Custom".equals(specs.getModel());
                final double width = isCustom ? solarPanel.getPanelWidth() : specs.getNominalWidth();
                final double height = isCustom ? solarPanel.getPanelHeight() : specs.getNominalLength();
                final int nItems = sizeComboBox.getItemCount();
                for (int i = 0; i < nItems; i++) {
                    if (Util.isZero(height - solarPanelNominalSize.getNominalHeights()[i]) && Util.isZero(width - solarPanelNominalSize.getNominalWidths()[i])) {
                        sizeComboBox.setSelectedIndex(i);
                    }
                }
                panel.add(sizeComboBox);
                panel.add(new JLabel("Cell Type:"));
                cellTypeComboBox = new JComboBox<String>(new String[] { "Polycrystalline", "Monocrystalline", "Thin Film" });
                cellTypeComboBox.setSelectedIndex(solarPanel.getCellType());
                panel.add(cellTypeComboBox);
                panel.add(new JLabel("Color:"));
                colorOptionComboBox = new JComboBox<String>(new String[] { "Blue", "Black", "Gray" });
                colorOptionComboBox.setSelectedIndex(solarPanel.getColorOption());
                panel.add(colorOptionComboBox);
                panel.add(new JLabel("Solar Cell Efficiency (%):"));
                cellEfficiencyField = new JTextField(threeDecimalsFormat.format(solarPanel.getCellEfficiency() * 100));
                panel.add(cellEfficiencyField);
                panel.add(new JLabel("<html>Nominal Operating Cell Temperature (&deg;C):"));
                noctField = new JTextField(threeDecimalsFormat.format(solarPanel.getNominalOperatingCellTemperature()));
                panel.add(noctField);
                panel.add(new JLabel("<html>Temperature Coefficient of Pmax (%/&deg;C):"));
                pmaxTcField = new JTextField(sixDecimalsFormat.format(solarPanel.getTemperatureCoefficientPmax() * 100));
                panel.add(pmaxTcField);
                panel.add(new JLabel("Shade Tolerance:"));
                shadeToleranceComboBox = new JComboBox<String>(new String[] { "Partial", "High", "None" });
                shadeToleranceComboBox.setSelectedIndex(solarPanel.getShadeTolerance());
                panel.add(shadeToleranceComboBox);
                if (modelComboBox.getSelectedIndex() != 0) {
                    sizeComboBox.setEnabled(false);
                    cellTypeComboBox.setEnabled(false);
                    colorOptionComboBox.setEnabled(false);
                    shadeToleranceComboBox.setEnabled(false);
                    cellEfficiencyField.setEnabled(false);
                    noctField.setEnabled(false);
                    pmaxTcField.setEnabled(false);
                }
                // the following properties are not related to the model
                panel.add(new JLabel("Orientation:"));
                orientationComboBox = new JComboBox<String>(new String[] { "Portrait", "Landscape" });
                orientationComboBox.setSelectedIndex(solarPanel.isRotated() ? 1 : 0);
                panel.add(orientationComboBox);
                panel.add(new JLabel("Inverter Efficiency (%):"));
                final JTextField inverterEfficiencyField = new JTextField(threeDecimalsFormat.format(solarPanel.getInverterEfficiency() * 100));
                panel.add(inverterEfficiencyField);
                SpringUtilities.makeCompactGrid(panel, 10, 2, 6, 6, 6, 6);
                final Object[] options = new Object[] { "OK", "Cancel", "Apply" };
                final JOptionPane optionPane = new JOptionPane(panel, JOptionPane.QUESTION_MESSAGE, JOptionPane.YES_NO_CANCEL_OPTION, null, options, options[2]);
                final JDialog dialog = optionPane.createDialog(MainFrame.getInstance(), "Solar Panels on this Rack");
                while (true) {
                    dialog.setVisible(true);
                    final Object choice = optionPane.getValue();
                    if (choice == options[1] || choice == null) {
                        break;
                    } else {
                        boolean ok = true;
                        if (modelComboBox.getSelectedIndex() == 0) {
                            try {
                                cellEfficiency = Double.parseDouble(cellEfficiencyField.getText());
                                pmax = Double.parseDouble(pmaxTcField.getText());
                                noct = Double.parseDouble(noctField.getText());
                                inverterEfficiency = Double.parseDouble(inverterEfficiencyField.getText());
                            } catch (final NumberFormatException ex) {
                                JOptionPane.showMessageDialog(MainFrame.getInstance(), "Invalid input!", "Error", JOptionPane.ERROR_MESSAGE);
                                ok = false;
                            }
                            if (ok) {
                                if (cellEfficiency < SolarPanel.MIN_SOLAR_CELL_EFFICIENCY_PERCENTAGE || cellEfficiency > SolarPanel.MAX_SOLAR_CELL_EFFICIENCY_PERCENTAGE) {
                                    JOptionPane.showMessageDialog(MainFrame.getInstance(), "Solar cell efficiency must be between " + SolarPanel.MIN_SOLAR_CELL_EFFICIENCY_PERCENTAGE + "% and " + SolarPanel.MAX_SOLAR_CELL_EFFICIENCY_PERCENTAGE + "%.", "Range Error", JOptionPane.ERROR_MESSAGE);
                                } else if (inverterEfficiency < SolarPanel.MIN_INVERTER_EFFICIENCY_PERCENTAGE || inverterEfficiency >= SolarPanel.MAX_INVERTER_EFFICIENCY_PERCENTAGE) {
                                    JOptionPane.showMessageDialog(MainFrame.getInstance(), "Inverter efficiency must be greater than " + SolarPanel.MIN_INVERTER_EFFICIENCY_PERCENTAGE + "% and less than " + SolarPanel.MAX_INVERTER_EFFICIENCY_PERCENTAGE + "%.", "Range Error", JOptionPane.ERROR_MESSAGE);
                                } else if (pmax < -1 || pmax > 0) {
                                    JOptionPane.showMessageDialog(MainFrame.getInstance(), "Temperature coefficient of Pmax must be between -1% and 0% per Celsius degree.", "Range Error", JOptionPane.ERROR_MESSAGE);
                                } else if (noct < 33 || noct > 58) {
                                    JOptionPane.showMessageDialog(MainFrame.getInstance(), "Nominal Cell Operating Temperature must be between 33 and 58 Celsius degrees.", "Range Error", JOptionPane.ERROR_MESSAGE);
                                } else {
                                    setCustomSolarPanels();
                                    if (choice == options[0]) {
                                        break;
                                    }
                                }
                            }
                        } else {
                            try {
                                inverterEfficiency = Double.parseDouble(inverterEfficiencyField.getText());
                            } catch (final NumberFormatException ex) {
                                JOptionPane.showMessageDialog(MainFrame.getInstance(), "Invalid input!", "Error", JOptionPane.ERROR_MESSAGE);
                                ok = false;
                            }
                            if (ok) {
                                if (inverterEfficiency < SolarPanel.MIN_INVERTER_EFFICIENCY_PERCENTAGE || inverterEfficiency >= SolarPanel.MAX_INVERTER_EFFICIENCY_PERCENTAGE) {
                                    JOptionPane.showMessageDialog(MainFrame.getInstance(), "Inverter efficiency must be greater than " + SolarPanel.MIN_INVERTER_EFFICIENCY_PERCENTAGE + "% and less than " + SolarPanel.MAX_INVERTER_EFFICIENCY_PERCENTAGE + "%.", "Range Error", JOptionPane.ERROR_MESSAGE);
                                } else {
                                    setBrandSolarPanels((String) modelComboBox.getSelectedItem());
                                    if (choice == options[0]) {
                                        break;
                                    }
                                }
                            }
                        }
                    }
                }
            }

            private void setBrandSolarPanels(final String modelName) {
                final SolarPanel s = rack.getSolarPanel();
                final boolean changed = !modelName.equals(s.getPvModuleSpecs().getModel()) || Math.abs(inverterEfficiency * 0.01 - s.getInverterEfficiency()) > 0.000001 || ((orientationComboBox.getSelectedIndex() == 1) ^ s.isRotated());
                if (changed) {
                    final SetSolarPanelArrayOnRackByModelCommand command = rack.isMonolithic() ? new SetSolarPanelArrayOnRackByModelCommand(rack) : null;
                    s.setRotated(orientationComboBox.getSelectedIndex() == 1);
                    s.setInverterEfficiency(inverterEfficiency * 0.01);
                    s.setPvModuleSpecs(PvModulesData.getInstance().getModuleSpecs(modelName));
                    SceneManager.getTaskManager().update(new Callable<Object>() {

                        @Override
                        public Object call() {
                            rack.addSolarPanels();
                            if (command != null) {
                                SceneManager.getInstance().getUndoManager().addEdit(command);
                            }
                            return null;
                        }
                    });
                    updateAfterEdit();
                }
            }

            private void setCustomSolarPanels() {
                final SolarPanel s = rack.getSolarPanel();
                final int i = sizeComboBox.getSelectedIndex();
                boolean changed = !"Custom".equals(s.getModelName());
                if (s.getPanelWidth() != solarPanelNominalSize.getNominalWidths()[i]) {
                    changed = true;
                } else if (s.getPanelHeight() != solarPanelNominalSize.getNominalHeights()[i]) {
                    changed = true;
                } else if (s.getNumberOfCellsInX() != solarPanelNominalSize.getCellNx()[i]) {
                    changed = true;
                } else if (s.getNumberOfCellsInY() != solarPanelNominalSize.getCellNy()[i]) {
                    changed = true;
                } else if (s.isRotated() ^ orientationComboBox.getSelectedIndex() == 1) {
                    changed = true;
                } else if (s.getCellType() != cellTypeComboBox.getSelectedIndex()) {
                    changed = true;
                } else if (s.getColorOption() != colorOptionComboBox.getSelectedIndex()) {
                    changed = true;
                } else if (Math.abs(s.getCellEfficiency() - cellEfficiency * 0.01) > 0.000001) {
                    changed = true;
                } else if (Math.abs(s.getInverterEfficiency() - inverterEfficiency * 0.01) > 0.000001) {
                    changed = true;
                } else if (Math.abs(s.getTemperatureCoefficientPmax() - pmax * 0.01) > 0.000001) {
                    changed = true;
                } else if (Math.abs(s.getNominalOperatingCellTemperature() - noct) > 0.000001) {
                    changed = true;
                } else if (s.getShadeTolerance() != shadeToleranceComboBox.getSelectedIndex()) {
                    changed = true;
                }
                if (changed) {
                    s.setModelName("Custom");
                    s.setBrandName("Custom");
                    final SetSolarPanelArrayOnRackCustomCommand command = rack.isMonolithic() ? new SetSolarPanelArrayOnRackCustomCommand(rack) : null;
                    s.setPanelWidth(solarPanelNominalSize.getNominalWidths()[i]);
                    s.setPanelHeight(solarPanelNominalSize.getNominalHeights()[i]);
                    s.setNumberOfCellsInX(solarPanelNominalSize.getCellNx()[i]);
                    s.setNumberOfCellsInY(solarPanelNominalSize.getCellNy()[i]);
                    s.setRotated(orientationComboBox.getSelectedIndex() == 1);
                    s.setCellType(cellTypeComboBox.getSelectedIndex());
                    s.setColorOption(colorOptionComboBox.getSelectedIndex());
                    s.setCellEfficiency(cellEfficiency * 0.01);
                    s.setInverterEfficiency(inverterEfficiency * 0.01);
                    s.setTemperatureCoefficientPmax(pmax * 0.01);
                    s.setNominalOperatingCellTemperature(noct);
                    s.setShadeTolerance(shadeToleranceComboBox.getSelectedIndex());
                    SceneManager.getTaskManager().update(new Callable<Object>() {

                        @Override
                        public Object call() {
                            rack.addSolarPanels();
                            if (command != null) {
                                SceneManager.getInstance().getUndoManager().addEdit(command);
                            }
                            return null;
                        }
                    });
                    updateAfterEdit();
                }
            }
        });
        final JMenu solarPanelMenu = new JMenu("Change Solar Panel Properties");
        final JMenuItem miSolarPanelModel = new JMenuItem("Model...");
        solarPanelMenu.add(miSolarPanelModel);
        miSolarPanelModel.addActionListener(new ActionListener() {

            private String modelName;

            // remember the scope selection as the next action will likely be applied to the same scope
            private int selectedScopeIndex = 0;

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (!(selectedPart instanceof Rack)) {
                    return;
                }
                final Rack r = (Rack) selectedPart;
                final Foundation foundation = r.getTopContainer();
                final SolarPanel s = r.getSolarPanel();
                final String partInfo = r.toString().substring(0, r.toString().indexOf(')') + 1);
                final Map<String, PvModuleSpecs> modules = PvModulesData.getInstance().getModules();
                final String[] models = new String[modules.size() + 1];
                int i = 0;
                models[i] = "Custom";
                for (final String key : modules.keySet()) {
                    models[++i] = key;
                }
                final PvModuleSpecs specs = s.getPvModuleSpecs();
                final JPanel gui = new JPanel(new BorderLayout(5, 5));
                gui.setBorder(BorderFactory.createTitledBorder("Solar Panel Model for " + partInfo));
                final JComboBox<String> typeComboBox = new JComboBox<String>(models);
                typeComboBox.setSelectedItem(specs.getModel());
                modelName = specs.getModel();
                typeComboBox.addItemListener(new ItemListener() {

                    @Override
                    public void itemStateChanged(final ItemEvent e) {
                        if (e.getStateChange() == ItemEvent.SELECTED) {
                            modelName = (String) typeComboBox.getSelectedItem();
                        }
                    }
                });
                gui.add(typeComboBox, BorderLayout.NORTH);
                final JPanel scopePanel = new JPanel();
                scopePanel.setLayout(new BoxLayout(scopePanel, BoxLayout.Y_AXIS));
                scopePanel.setBorder(BorderFactory.createTitledBorder("Apply to:"));
                final JRadioButton rb1 = new JRadioButton("Only this Rack", true);
                final JRadioButton rb2 = new JRadioButton("All Racks on this Foundation");
                final JRadioButton rb3 = new JRadioButton("All Racks");
                scopePanel.add(rb1);
                scopePanel.add(rb2);
                scopePanel.add(rb3);
                final ButtonGroup bg = new ButtonGroup();
                bg.add(rb1);
                bg.add(rb2);
                bg.add(rb3);
                switch(selectedScopeIndex) {
                    case 0:
                        rb1.setSelected(true);
                        break;
                    case 1:
                        rb2.setSelected(true);
                        break;
                    case 2:
                        rb3.setSelected(true);
                        break;
                }
                gui.add(scopePanel, BorderLayout.CENTER);
                final Object[] options = new Object[] { "OK", "Cancel", "Apply" };
                final JOptionPane optionPane = new JOptionPane(gui, JOptionPane.QUESTION_MESSAGE, JOptionPane.YES_NO_CANCEL_OPTION, null, options, options[2]);
                final JDialog dialog = optionPane.createDialog(MainFrame.getInstance(), "Solar Panel Model");
                while (true) {
                    dialog.setVisible(true);
                    final Object choice = optionPane.getValue();
                    if (choice == options[1] || choice == null) {
                        break;
                    } else {
                        boolean changed = !modelName.equals(s.getModelName());
                        if (rb1.isSelected()) {
                            if (changed) {
                                final ChangeSolarPanelModelForRackCommand c = new ChangeSolarPanelModelForRackCommand(r);
                                s.setPvModuleSpecs(PvModulesData.getInstance().getModuleSpecs(modelName));
                                r.ensureFullSolarPanels(false);
                                r.draw();
                                SceneManager.getInstance().refresh();
                                SceneManager.getInstance().getUndoManager().addEdit(c);
                            }
                            selectedScopeIndex = 0;
                        } else if (rb2.isSelected()) {
                            if (!changed) {
                                for (final Rack x : foundation.getRacks()) {
                                    if (!modelName.equals(x.getSolarPanel().getModelName())) {
                                        changed = true;
                                        break;
                                    }
                                }
                            }
                            if (changed) {
                                final ChangeSolarPanelModelForRacksOnFoundationCommand c = new ChangeSolarPanelModelForRacksOnFoundationCommand(foundation);
                                foundation.setSolarPanelModelForRacks(PvModulesData.getInstance().getModuleSpecs(modelName));
                                SceneManager.getInstance().getUndoManager().addEdit(c);
                            }
                            selectedScopeIndex = 1;
                        } else if (rb3.isSelected()) {
                            if (!changed) {
                                for (final Rack x : Scene.getInstance().getAllRacks()) {
                                    if (!modelName.equals(x.getSolarPanel().getModelName())) {
                                        changed = true;
                                        break;
                                    }
                                }
                            }
                            if (changed) {
                                final ChangeSolarPanelModelForAllRacksCommand c = new ChangeSolarPanelModelForAllRacksCommand();
                                Scene.getInstance().setSolarPanelModelForAllRacks(PvModulesData.getInstance().getModuleSpecs(modelName));
                                SceneManager.getInstance().getUndoManager().addEdit(c);
                            }
                            selectedScopeIndex = 2;
                        }
                        if (changed) {
                            updateAfterEdit();
                        }
                        if (choice == options[0]) {
                            break;
                        }
                    }
                }
            }
        });
        solarPanelMenu.addSeparator();
        final JMenuItem miSolarPanelSize = new JMenuItem("Size...");
        solarPanelMenu.add(miSolarPanelSize);
        miSolarPanelSize.addActionListener(new ActionListener() {

            // remember the scope selection as the next action will likely be applied to the same scope
            private int selectedScopeIndex = 0;

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (!(selectedPart instanceof Rack)) {
                    return;
                }
                final Rack r = (Rack) selectedPart;
                final Foundation foundation = r.getTopContainer();
                final SolarPanel s = r.getSolarPanel();
                final String partInfo = r.toString().substring(0, r.toString().indexOf(')') + 1);
                final JPanel gui = new JPanel(new BorderLayout(5, 5));
                gui.setBorder(BorderFactory.createTitledBorder("Solar Panel Size for " + partInfo));
                final JComboBox<String> sizeComboBox = new JComboBox<String>(solarPanelNominalSize.getStrings());
                final PvModuleSpecs specs = s.getPvModuleSpecs();
                final boolean isCustom = "Custom".equals(specs.getModel());
                final double width = isCustom ? s.getPanelWidth() : specs.getNominalWidth();
                final double height = isCustom ? s.getPanelHeight() : specs.getNominalLength();
                final int nItems = sizeComboBox.getItemCount();
                for (int i = 0; i < nItems; i++) {
                    if (Util.isZero(height - solarPanelNominalSize.getNominalHeights()[i]) && Util.isZero(width - solarPanelNominalSize.getNominalWidths()[i])) {
                        sizeComboBox.setSelectedIndex(i);
                    }
                }
                gui.add(sizeComboBox, BorderLayout.NORTH);
                final JPanel scopePanel = new JPanel();
                scopePanel.setLayout(new BoxLayout(scopePanel, BoxLayout.Y_AXIS));
                scopePanel.setBorder(BorderFactory.createTitledBorder("Apply to:"));
                final JRadioButton rb1 = new JRadioButton("Only this Rack", true);
                final JRadioButton rb2 = new JRadioButton("All Racks on this Foundation");
                final JRadioButton rb3 = new JRadioButton("All Racks");
                scopePanel.add(rb1);
                scopePanel.add(rb2);
                scopePanel.add(rb3);
                final ButtonGroup bg = new ButtonGroup();
                bg.add(rb1);
                bg.add(rb2);
                bg.add(rb3);
                switch(selectedScopeIndex) {
                    case 0:
                        rb1.setSelected(true);
                        break;
                    case 1:
                        rb2.setSelected(true);
                        break;
                    case 2:
                        rb3.setSelected(true);
                        break;
                }
                gui.add(scopePanel, BorderLayout.CENTER);
                final Object[] options = new Object[] { "OK", "Cancel", "Apply" };
                final JOptionPane optionPane = new JOptionPane(gui, JOptionPane.QUESTION_MESSAGE, JOptionPane.YES_NO_CANCEL_OPTION, null, options, options[2]);
                final JDialog dialog = optionPane.createDialog(MainFrame.getInstance(), "Solar Panel Size");
                while (true) {
                    dialog.setVisible(true);
                    final Object choice = optionPane.getValue();
                    if (choice == options[1] || choice == null) {
                        break;
                    } else {
                        final int i = sizeComboBox.getSelectedIndex();
                        final double w = solarPanelNominalSize.getNominalWidths()[i];
                        final double h = solarPanelNominalSize.getNominalHeights()[i];
                        final int numberOfCellsInX = solarPanelNominalSize.getCellNx()[i];
                        final int numberOfCellsInY = solarPanelNominalSize.getCellNy()[i];
                        boolean changed = numberOfCellsInX != s.getNumberOfCellsInX() || numberOfCellsInY != s.getNumberOfCellsInY();
                        if (Math.abs(s.getPanelWidth() - w) > 0.000001 || Math.abs(s.getPanelHeight() - h) > 0.000001) {
                            changed = true;
                        }
                        if (rb1.isSelected()) {
                            if (changed) {
                                final ChooseSolarPanelSizeForRackCommand c = new ChooseSolarPanelSizeForRackCommand(r);
                                s.setPanelWidth(w);
                                s.setPanelHeight(h);
                                s.setNumberOfCellsInX(numberOfCellsInX);
                                s.setNumberOfCellsInY(numberOfCellsInY);
                                r.ensureFullSolarPanels(false);
                                r.draw();
                                SceneManager.getInstance().refresh();
                                SceneManager.getInstance().getUndoManager().addEdit(c);
                            }
                            selectedScopeIndex = 0;
                        } else if (rb2.isSelected()) {
                            if (!changed) {
                                for (final Rack x : foundation.getRacks()) {
                                    final SolarPanel p = x.getSolarPanel();
                                    if (numberOfCellsInX != p.getNumberOfCellsInX() || numberOfCellsInY != p.getNumberOfCellsInY()) {
                                        changed = true;
                                        break;
                                    }
                                    if (Math.abs(p.getPanelWidth() - w) > 0.000001 || Math.abs(p.getPanelHeight() - h) > 0.000001) {
                                        changed = true;
                                        break;
                                    }
                                }
                            }
                            if (changed) {
                                final SetSolarPanelSizeForRacksOnFoundationCommand c = new SetSolarPanelSizeForRacksOnFoundationCommand(foundation);
                                foundation.setSolarPanelSizeForRacks(w, h, numberOfCellsInX, numberOfCellsInY);
                                SceneManager.getInstance().getUndoManager().addEdit(c);
                            }
                            selectedScopeIndex = 1;
                        } else if (rb3.isSelected()) {
                            if (!changed) {
                                for (final Rack x : Scene.getInstance().getAllRacks()) {
                                    final SolarPanel p = x.getSolarPanel();
                                    if (numberOfCellsInX != p.getNumberOfCellsInX() || numberOfCellsInY != p.getNumberOfCellsInY()) {
                                        changed = true;
                                        break;
                                    }
                                    if (Math.abs(p.getPanelWidth() - w) > 0.000001 || Math.abs(p.getPanelHeight() - h) > 0.000001) {
                                        changed = true;
                                        break;
                                    }
                                }
                            }
                            if (changed) {
                                final SetSolarPanelSizeForAllRacksCommand c = new SetSolarPanelSizeForAllRacksCommand();
                                Scene.getInstance().setSolarPanelSizeForAllRacks(w, h, numberOfCellsInX, numberOfCellsInY);
                                SceneManager.getInstance().getUndoManager().addEdit(c);
                            }
                            selectedScopeIndex = 2;
                        }
                        if (changed) {
                            updateAfterEdit();
                        }
                        if (choice == options[0]) {
                            break;
                        }
                    }
                }
            }
        });
        final JMenuItem miSolarPanelCellType = new JMenuItem("Cell Type...");
        solarPanelMenu.add(miSolarPanelCellType);
        miSolarPanelCellType.addActionListener(new ActionListener() {

            // remember the scope selection as the next action will likely be applied to the same scope
            private int selectedScopeIndex = 0;

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (!(selectedPart instanceof Rack)) {
                    return;
                }
                final Rack r = (Rack) selectedPart;
                final Foundation foundation = r.getTopContainer();
                final SolarPanel s = r.getSolarPanel();
                final String partInfo = r.toString().substring(0, r.toString().indexOf(')') + 1);
                final JPanel gui = new JPanel(new BorderLayout(5, 5));
                gui.setBorder(BorderFactory.createTitledBorder("Choose Cell Type for " + partInfo));
                final JComboBox<String> cellTypeComboBox = new JComboBox<String>(new String[] { "Polycrystalline", "Monocrystalline", "Thin Film" });
                cellTypeComboBox.setSelectedIndex(s.getCellType());
                gui.add(cellTypeComboBox, BorderLayout.NORTH);
                final JPanel scopePanel = new JPanel();
                scopePanel.setLayout(new BoxLayout(scopePanel, BoxLayout.Y_AXIS));
                scopePanel.setBorder(BorderFactory.createTitledBorder("Apply to:"));
                final JRadioButton rb1 = new JRadioButton("Only this Rack", true);
                final JRadioButton rb2 = new JRadioButton("All Racks on this Foundation");
                final JRadioButton rb3 = new JRadioButton("All Racks");
                scopePanel.add(rb1);
                scopePanel.add(rb2);
                scopePanel.add(rb3);
                final ButtonGroup bg = new ButtonGroup();
                bg.add(rb1);
                bg.add(rb2);
                bg.add(rb3);
                switch(selectedScopeIndex) {
                    case 0:
                        rb1.setSelected(true);
                        break;
                    case 1:
                        rb2.setSelected(true);
                        break;
                    case 2:
                        rb3.setSelected(true);
                        break;
                }
                gui.add(scopePanel, BorderLayout.CENTER);
                final Object[] options = new Object[] { "OK", "Cancel", "Apply" };
                final JOptionPane optionPane = new JOptionPane(gui, JOptionPane.QUESTION_MESSAGE, JOptionPane.YES_NO_CANCEL_OPTION, null, options, options[2]);
                final JDialog dialog = optionPane.createDialog(MainFrame.getInstance(), "Solar Panel Cell Type");
                while (true) {
                    dialog.setVisible(true);
                    final Object choice = optionPane.getValue();
                    if (choice == options[1] || choice == null) {
                        break;
                    } else {
                        final int selectedIndex = cellTypeComboBox.getSelectedIndex();
                        boolean changed = selectedIndex != s.getCellType();
                        if (rb1.isSelected()) {
                            if (changed) {
                                final SetSolarPanelCellTypeForRackCommand c = new SetSolarPanelCellTypeForRackCommand(r);
                                s.setCellType(selectedIndex);
                                r.draw();
                                SceneManager.getInstance().refresh();
                                SceneManager.getInstance().getUndoManager().addEdit(c);
                            }
                            selectedScopeIndex = 0;
                        } else if (rb2.isSelected()) {
                            if (!changed) {
                                for (final Rack x : foundation.getRacks()) {
                                    if (x.getSolarPanel().getCellType() != selectedIndex) {
                                        changed = true;
                                        break;
                                    }
                                }
                            }
                            if (changed) {
                                final SetSolarPanelCellTypeForRacksOnFoundationCommand c = new SetSolarPanelCellTypeForRacksOnFoundationCommand(foundation);
                                foundation.setSolarPanelCellTypeForRacks(selectedIndex);
                                SceneManager.getInstance().getUndoManager().addEdit(c);
                            }
                            selectedScopeIndex = 1;
                        } else if (rb3.isSelected()) {
                            if (!changed) {
                                for (final Rack x : Scene.getInstance().getAllRacks()) {
                                    if (x.getSolarPanel().getCellType() != selectedIndex) {
                                        changed = true;
                                        break;
                                    }
                                }
                            }
                            if (changed) {
                                final SetSolarPanelCellTypeForAllRacksCommand c = new SetSolarPanelCellTypeForAllRacksCommand();
                                Scene.getInstance().setSolarPanelCellTypeForAllRacks(selectedIndex);
                                SceneManager.getInstance().getUndoManager().addEdit(c);
                            }
                            selectedScopeIndex = 2;
                        }
                        if (changed) {
                            updateAfterEdit();
                        }
                        if (choice == options[0]) {
                            break;
                        }
                    }
                }
            }
        });
        final JMenuItem miSolarPanelColor = new JMenuItem("Color...");
        solarPanelMenu.add(miSolarPanelColor);
        miSolarPanelColor.addActionListener(new ActionListener() {

            // remember the scope selection as the next action will likely be applied to the same scope
            private int selectedScopeIndex = 0;

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (!(selectedPart instanceof Rack)) {
                    return;
                }
                final Rack r = (Rack) selectedPart;
                final Foundation foundation = r.getTopContainer();
                final SolarPanel s = r.getSolarPanel();
                final String partInfo = r.toString().substring(0, r.toString().indexOf(')') + 1);
                final JPanel gui = new JPanel(new BorderLayout(5, 5));
                gui.setBorder(BorderFactory.createTitledBorder("Choose Color for " + partInfo));
                final JComboBox<String> colorComboBox = new JComboBox<String>(new String[] { "Blue", "Black", "Gray" });
                colorComboBox.setSelectedIndex(s.getColorOption());
                gui.add(colorComboBox, BorderLayout.NORTH);
                final JPanel scopePanel = new JPanel();
                scopePanel.setLayout(new BoxLayout(scopePanel, BoxLayout.Y_AXIS));
                scopePanel.setBorder(BorderFactory.createTitledBorder("Apply to:"));
                final JRadioButton rb1 = new JRadioButton("Only this Rack", true);
                final JRadioButton rb2 = new JRadioButton("All Racks on this Foundation");
                final JRadioButton rb3 = new JRadioButton("All Racks");
                scopePanel.add(rb1);
                scopePanel.add(rb2);
                scopePanel.add(rb3);
                final ButtonGroup bg = new ButtonGroup();
                bg.add(rb1);
                bg.add(rb2);
                bg.add(rb3);
                switch(selectedScopeIndex) {
                    case 0:
                        rb1.setSelected(true);
                        break;
                    case 1:
                        rb2.setSelected(true);
                        break;
                    case 2:
                        rb3.setSelected(true);
                        break;
                }
                gui.add(scopePanel, BorderLayout.CENTER);
                final Object[] options = new Object[] { "OK", "Cancel", "Apply" };
                final JOptionPane optionPane = new JOptionPane(gui, JOptionPane.QUESTION_MESSAGE, JOptionPane.YES_NO_CANCEL_OPTION, null, options, options[2]);
                final JDialog dialog = optionPane.createDialog(MainFrame.getInstance(), "Solar Panel Color");
                while (true) {
                    dialog.setVisible(true);
                    final Object choice = optionPane.getValue();
                    if (choice == options[1] || choice == null) {
                        break;
                    } else {
                        final int selectedIndex = colorComboBox.getSelectedIndex();
                        boolean changed = selectedIndex != s.getColorOption();
                        if (rb1.isSelected()) {
                            if (changed) {
                                final SetSolarPanelColorForRackCommand c = new SetSolarPanelColorForRackCommand(r);
                                s.setColorOption(selectedIndex);
                                r.draw();
                                SceneManager.getInstance().refresh();
                                SceneManager.getInstance().getUndoManager().addEdit(c);
                            }
                            selectedScopeIndex = 0;
                        } else if (rb2.isSelected()) {
                            if (!changed) {
                                for (final Rack x : foundation.getRacks()) {
                                    if (x.getSolarPanel().getColorOption() != selectedIndex) {
                                        changed = true;
                                        break;
                                    }
                                }
                            }
                            if (changed) {
                                final SetSolarPanelColorForRacksOnFoundationCommand c = new SetSolarPanelColorForRacksOnFoundationCommand(foundation);
                                foundation.setSolarPanelColorForRacks(selectedIndex);
                                SceneManager.getInstance().getUndoManager().addEdit(c);
                            }
                            selectedScopeIndex = 1;
                        } else if (rb3.isSelected()) {
                            if (!changed) {
                                for (final Rack x : Scene.getInstance().getAllRacks()) {
                                    if (x.getSolarPanel().getColorOption() != selectedIndex) {
                                        changed = true;
                                        break;
                                    }
                                }
                            }
                            if (changed) {
                                final SetSolarPanelColorForAllRacksCommand c = new SetSolarPanelColorForAllRacksCommand();
                                Scene.getInstance().setSolarPanelColorForAllRacks(selectedIndex);
                                SceneManager.getInstance().getUndoManager().addEdit(c);
                            }
                            selectedScopeIndex = 2;
                        }
                        if (changed) {
                            updateAfterEdit();
                        }
                        if (choice == options[0]) {
                            break;
                        }
                    }
                }
            }
        });
        final JMenuItem miSolarPanelCellEfficiency = new JMenuItem("Solar Cell Efficiency...");
        solarPanelMenu.add(miSolarPanelCellEfficiency);
        miSolarPanelCellEfficiency.addActionListener(new ActionListener() {

            // remember the scope selection as the next action will likely be applied to the same scope
            private int selectedScopeIndex = 0;

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (!(selectedPart instanceof Rack)) {
                    return;
                }
                final Rack r = (Rack) selectedPart;
                final Foundation foundation = r.getTopContainer();
                final SolarPanel s = r.getSolarPanel();
                final String title = "Set Solar Cell Efficiency (%) for " + r.toString().substring(0, r.toString().indexOf(')') + 1);
                final String footnote = "<html><hr><font size=2>How efficient can a solar panel be for converting light into electricity?<br>The Shockley-Queisser limit is 34%.<br>The theoretical limit for multilayer cells is 86%.<br>As of 2017, the best solar panel in the market has an efficiency of 24%.<br>The highest efficiency you can choose is limited to " + SolarPanel.MAX_SOLAR_CELL_EFFICIENCY_PERCENTAGE + "%.<hr></html>";
                final JPanel gui = new JPanel(new BorderLayout(5, 5));
                final JTextField inputField = new JTextField(threeDecimalsFormat.format(s.getCellEfficiency() * 100));
                gui.add(inputField, BorderLayout.NORTH);
                final JPanel scopePanel = new JPanel();
                scopePanel.setLayout(new BoxLayout(scopePanel, BoxLayout.Y_AXIS));
                scopePanel.setBorder(BorderFactory.createTitledBorder("Apply to:"));
                final JRadioButton rb1 = new JRadioButton("Only this Rack", true);
                final JRadioButton rb2 = new JRadioButton("All Racks on this Foundation");
                final JRadioButton rb3 = new JRadioButton("All Racks");
                scopePanel.add(rb1);
                scopePanel.add(rb2);
                scopePanel.add(rb3);
                final ButtonGroup bg = new ButtonGroup();
                bg.add(rb1);
                bg.add(rb2);
                bg.add(rb3);
                switch(selectedScopeIndex) {
                    case 0:
                        rb1.setSelected(true);
                        break;
                    case 1:
                        rb2.setSelected(true);
                        break;
                    case 2:
                        rb3.setSelected(true);
                        break;
                }
                gui.add(scopePanel, BorderLayout.CENTER);
                final Object[] options = new Object[] { "OK", "Cancel", "Apply" };
                final JOptionPane optionPane = new JOptionPane(new Object[] { title, footnote, gui }, JOptionPane.QUESTION_MESSAGE, JOptionPane.YES_NO_CANCEL_OPTION, null, options, options[2]);
                final JDialog dialog = optionPane.createDialog(MainFrame.getInstance(), "Solar Cell Efficiency");
                while (true) {
                    inputField.selectAll();
                    inputField.requestFocusInWindow();
                    dialog.setVisible(true);
                    final Object choice = optionPane.getValue();
                    if (choice == options[1] || choice == null) {
                        break;
                    } else {
                        boolean ok = true;
                        try {
                            solarCellEfficiencyPercentage = Double.parseDouble(inputField.getText());
                        } catch (final NumberFormatException exception) {
                            JOptionPane.showMessageDialog(MainFrame.getInstance(), inputField.getText() + " is an invalid value!", "Error", JOptionPane.ERROR_MESSAGE);
                            ok = false;
                        }
                        if (ok) {
                            if (solarCellEfficiencyPercentage < SolarPanel.MIN_SOLAR_CELL_EFFICIENCY_PERCENTAGE || solarCellEfficiencyPercentage > SolarPanel.MAX_SOLAR_CELL_EFFICIENCY_PERCENTAGE) {
                                JOptionPane.showMessageDialog(MainFrame.getInstance(), "Solar cell efficiency must be between " + SolarPanel.MIN_SOLAR_CELL_EFFICIENCY_PERCENTAGE + "% and " + SolarPanel.MAX_SOLAR_CELL_EFFICIENCY_PERCENTAGE + "%.", "Range Error", JOptionPane.ERROR_MESSAGE);
                            } else {
                                boolean changed = Math.abs(solarCellEfficiencyPercentage * 0.01 - s.getCellEfficiency()) > 0.000001;
                                if (rb1.isSelected()) {
                                    if (changed) {
                                        final SetSolarCellEfficiencyForRackCommand c = new SetSolarCellEfficiencyForRackCommand(r);
                                        s.setCellEfficiency(solarCellEfficiencyPercentage * 0.01);
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 0;
                                } else if (rb2.isSelected()) {
                                    if (!changed) {
                                        for (final Rack x : foundation.getRacks()) {
                                            if (Math.abs(solarCellEfficiencyPercentage * 0.01 - x.getSolarPanel().getCellEfficiency()) > 0.000001) {
                                                changed = true;
                                                break;
                                            }
                                        }
                                    }
                                    if (changed) {
                                        final SetSolarCellEfficiencyForRacksOnFoundationCommand c = new SetSolarCellEfficiencyForRacksOnFoundationCommand(foundation);
                                        foundation.setSolarCellEfficiencyForRacks(solarCellEfficiencyPercentage * 0.01);
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 1;
                                } else if (rb3.isSelected()) {
                                    if (!changed) {
                                        for (final Rack x : Scene.getInstance().getAllRacks()) {
                                            if (Math.abs(solarCellEfficiencyPercentage * 0.01 - x.getSolarPanel().getCellEfficiency()) > 0.000001) {
                                                changed = true;
                                                break;
                                            }
                                        }
                                    }
                                    if (changed) {
                                        final SetSolarCellEfficiencyForAllRacksCommand c = new SetSolarCellEfficiencyForAllRacksCommand();
                                        Scene.getInstance().setSolarCellEfficiencyForAllRacks(solarCellEfficiencyPercentage * 0.01);
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 2;
                                }
                                if (changed) {
                                    updateAfterEdit();
                                }
                                if (choice == options[0]) {
                                    break;
                                }
                            }
                        }
                    }
                }
            }
        });
        final JMenuItem miSolarPanelNoct = new JMenuItem("Nominal Operating Cell Temperature...");
        solarPanelMenu.add(miSolarPanelNoct);
        miSolarPanelNoct.addActionListener(new ActionListener() {

            // remember the scope selection as the next action will likely be applied to the same scope
            private int selectedScopeIndex = 0;

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (!(selectedPart instanceof Rack)) {
                    return;
                }
                final Rack r = (Rack) selectedPart;
                final Foundation foundation = r.getTopContainer();
                final SolarPanel s = r.getSolarPanel();
                final String title = "<html>Nominal Operating Cell Temperature (&deg;C) for " + r.toString().substring(0, r.toString().indexOf(')') + 1);
                final String footnote = "<html><hr><font size=2>Increased temperature reduces solar cell efficiency.<hr></html>";
                final JPanel gui = new JPanel(new BorderLayout(5, 5));
                final JTextField inputField = new JTextField(threeDecimalsFormat.format(s.getNominalOperatingCellTemperature()));
                gui.add(inputField, BorderLayout.NORTH);
                final JPanel scopePanel = new JPanel();
                scopePanel.setLayout(new BoxLayout(scopePanel, BoxLayout.Y_AXIS));
                scopePanel.setBorder(BorderFactory.createTitledBorder("Apply to:"));
                final JRadioButton rb1 = new JRadioButton("Only this Rack", true);
                final JRadioButton rb2 = new JRadioButton("All Racks on this Foundation");
                final JRadioButton rb3 = new JRadioButton("All Racks");
                scopePanel.add(rb1);
                scopePanel.add(rb2);
                scopePanel.add(rb3);
                final ButtonGroup bg = new ButtonGroup();
                bg.add(rb1);
                bg.add(rb2);
                bg.add(rb3);
                switch(selectedScopeIndex) {
                    case 0:
                        rb1.setSelected(true);
                        break;
                    case 1:
                        rb2.setSelected(true);
                        break;
                    case 2:
                        rb3.setSelected(true);
                        break;
                }
                gui.add(scopePanel, BorderLayout.CENTER);
                final Object[] options = new Object[] { "OK", "Cancel", "Apply" };
                final JOptionPane optionPane = new JOptionPane(new Object[] { title, footnote, gui }, JOptionPane.QUESTION_MESSAGE, JOptionPane.YES_NO_CANCEL_OPTION, null, options, options[2]);
                final JDialog dialog = optionPane.createDialog(MainFrame.getInstance(), "Nominal Operating Cell Temperature");
                while (true) {
                    inputField.selectAll();
                    inputField.requestFocusInWindow();
                    dialog.setVisible(true);
                    final Object choice = optionPane.getValue();
                    if (choice == options[1] || choice == null) {
                        break;
                    } else {
                        boolean ok = true;
                        try {
                            solarPanelNominalOperatingCellTemperature = Double.parseDouble(inputField.getText());
                        } catch (final NumberFormatException exception) {
                            JOptionPane.showMessageDialog(MainFrame.getInstance(), inputField.getText() + " is an invalid value!", "Error", JOptionPane.ERROR_MESSAGE);
                            ok = false;
                        }
                        if (ok) {
                            if (solarPanelNominalOperatingCellTemperature < 33 || solarPanelNominalOperatingCellTemperature > 58) {
                                JOptionPane.showMessageDialog(MainFrame.getInstance(), "Nominal Operating Cell Temperature must be between 33 and 58 degrees.", "Range Error", JOptionPane.ERROR_MESSAGE);
                            } else {
                                boolean changed = Math.abs(solarPanelNominalOperatingCellTemperature - s.getNominalOperatingCellTemperature()) > 0.000001;
                                if (rb1.isSelected()) {
                                    if (changed) {
                                        final SetNoctForRackCommand c = new SetNoctForRackCommand(r);
                                        s.setNominalOperatingCellTemperature(solarPanelNominalOperatingCellTemperature);
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 0;
                                } else if (rb2.isSelected()) {
                                    if (!changed) {
                                        for (final Rack x : foundation.getRacks()) {
                                            if (Math.abs(solarPanelNominalOperatingCellTemperature - x.getSolarPanel().getNominalOperatingCellTemperature()) > 0.000001) {
                                                changed = true;
                                                break;
                                            }
                                        }
                                    }
                                    if (changed) {
                                        final SetNoctForRacksOnFoundationCommand c = new SetNoctForRacksOnFoundationCommand(foundation);
                                        foundation.setNominalOperatingCellTemperatureForRacks(solarPanelNominalOperatingCellTemperature);
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 1;
                                } else if (rb3.isSelected()) {
                                    if (!changed) {
                                        for (final Rack x : Scene.getInstance().getAllRacks()) {
                                            if (Math.abs(solarPanelNominalOperatingCellTemperature - x.getSolarPanel().getNominalOperatingCellTemperature()) > 0.000001) {
                                                changed = true;
                                                break;
                                            }
                                        }
                                    }
                                    if (changed) {
                                        final SetNoctForAllRacksCommand c = new SetNoctForAllRacksCommand();
                                        Scene.getInstance().setNominalOperatingCellTemperatureForAllRacks(solarPanelNominalOperatingCellTemperature);
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 2;
                                }
                                if (changed) {
                                    updateAfterEdit();
                                }
                                if (choice == options[0]) {
                                    break;
                                }
                            }
                        }
                    }
                }
            }
        });
        final JMenuItem miSolarPanelPmaxTc = new JMenuItem("Temperature Coefficient of Pmax...");
        solarPanelMenu.add(miSolarPanelPmaxTc);
        miSolarPanelPmaxTc.addActionListener(new ActionListener() {

            // remember the scope selection as the next action will likely be applied to the same scope
            private int selectedScopeIndex = 0;

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (!(selectedPart instanceof Rack)) {
                    return;
                }
                final Rack r = (Rack) selectedPart;
                final Foundation foundation = r.getTopContainer();
                final SolarPanel s = r.getSolarPanel();
                final String title = "<html>Temperature Coefficienct of Pmax (%/&deg;C) for " + r.toString().substring(0, r.toString().indexOf(')') + 1);
                final String footnote = "<html><hr><font size=2>Increased temperature reduces solar cell efficiency.<hr></html>";
                final JPanel gui = new JPanel(new BorderLayout(5, 5));
                final JTextField inputField = new JTextField(threeDecimalsFormat.format(s.getTemperatureCoefficientPmax() * 100));
                gui.add(inputField, BorderLayout.NORTH);
                final JPanel scopePanel = new JPanel();
                scopePanel.setLayout(new BoxLayout(scopePanel, BoxLayout.Y_AXIS));
                scopePanel.setBorder(BorderFactory.createTitledBorder("Apply to:"));
                final JRadioButton rb1 = new JRadioButton("Only this Rack", true);
                final JRadioButton rb2 = new JRadioButton("All Racks on this Foundation");
                final JRadioButton rb3 = new JRadioButton("All Racks");
                scopePanel.add(rb1);
                scopePanel.add(rb2);
                scopePanel.add(rb3);
                final ButtonGroup bg = new ButtonGroup();
                bg.add(rb1);
                bg.add(rb2);
                bg.add(rb3);
                switch(selectedScopeIndex) {
                    case 0:
                        rb1.setSelected(true);
                        break;
                    case 1:
                        rb2.setSelected(true);
                        break;
                    case 2:
                        rb3.setSelected(true);
                        break;
                }
                gui.add(scopePanel, BorderLayout.CENTER);
                final Object[] options = new Object[] { "OK", "Cancel", "Apply" };
                final JOptionPane optionPane = new JOptionPane(new Object[] { title, footnote, gui }, JOptionPane.QUESTION_MESSAGE, JOptionPane.YES_NO_CANCEL_OPTION, null, options, options[2]);
                final JDialog dialog = optionPane.createDialog(MainFrame.getInstance(), "Temperature Coefficient of Pmax");
                while (true) {
                    inputField.selectAll();
                    inputField.requestFocusInWindow();
                    dialog.setVisible(true);
                    final Object choice = optionPane.getValue();
                    if (choice == options[1] || choice == null) {
                        break;
                    } else {
                        boolean ok = true;
                        try {
                            solarPanelTemperatureCoefficientPmaxPercentage = Double.parseDouble(inputField.getText());
                        } catch (final NumberFormatException exception) {
                            JOptionPane.showMessageDialog(MainFrame.getInstance(), inputField.getText() + " is an invalid value!", "Error", JOptionPane.ERROR_MESSAGE);
                            ok = false;
                        }
                        if (ok) {
                            if (solarPanelTemperatureCoefficientPmaxPercentage < -1 || solarPanelTemperatureCoefficientPmaxPercentage > 0) {
                                JOptionPane.showMessageDialog(MainFrame.getInstance(), "Temperature coefficient of Pmax must be between -1 and 0", "Range Error", JOptionPane.ERROR_MESSAGE);
                            } else {
                                boolean changed = Math.abs(solarPanelTemperatureCoefficientPmaxPercentage * 0.01 - s.getTemperatureCoefficientPmax()) > 0.000001;
                                if (rb1.isSelected()) {
                                    if (changed) {
                                        final SetTemperatureCoefficientPmaxForRackCommand c = new SetTemperatureCoefficientPmaxForRackCommand(r);
                                        s.setTemperatureCoefficientPmax(solarPanelTemperatureCoefficientPmaxPercentage * 0.01);
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 0;
                                } else if (rb2.isSelected()) {
                                    if (!changed) {
                                        for (final Rack x : foundation.getRacks()) {
                                            if (Math.abs(solarPanelTemperatureCoefficientPmaxPercentage * 0.01 - x.getSolarPanel().getTemperatureCoefficientPmax()) > 0.000001) {
                                                changed = true;
                                                break;
                                            }
                                        }
                                    }
                                    if (changed) {
                                        final SetTemperatureCoefficientPmaxForRacksOnFoundationCommand c = new SetTemperatureCoefficientPmaxForRacksOnFoundationCommand(foundation);
                                        foundation.setTemperatureCoefficientPmaxForRacks(solarPanelTemperatureCoefficientPmaxPercentage * 0.01);
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 1;
                                } else if (rb3.isSelected()) {
                                    if (!changed) {
                                        for (final Rack x : Scene.getInstance().getAllRacks()) {
                                            if (Math.abs(solarPanelTemperatureCoefficientPmaxPercentage * 0.01 - x.getSolarPanel().getTemperatureCoefficientPmax()) > 0.000001) {
                                                changed = true;
                                                break;
                                            }
                                        }
                                    }
                                    if (changed) {
                                        final SetTemperatureCoefficientPmaxForAllRacksCommand c = new SetTemperatureCoefficientPmaxForAllRacksCommand();
                                        Scene.getInstance().setTemperatureCoefficientPmaxForAllRacks(solarPanelTemperatureCoefficientPmaxPercentage * 0.01);
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 2;
                                }
                                if (changed) {
                                    updateAfterEdit();
                                }
                                if (choice == options[0]) {
                                    break;
                                }
                            }
                        }
                    }
                }
            }
        });
        final JMenuItem miSolarPanelShadeTolerance = new JMenuItem("Shade Tolerance...");
        solarPanelMenu.add(miSolarPanelShadeTolerance);
        miSolarPanelShadeTolerance.addActionListener(new ActionListener() {

            // remember the scope selection as the next action will likely be applied to the same scope
            private int selectedScopeIndex = 0;

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (!(selectedPart instanceof Rack)) {
                    return;
                }
                final Rack r = (Rack) selectedPart;
                final Foundation foundation = r.getTopContainer();
                final SolarPanel s = r.getSolarPanel();
                final String title = "Set Solar Panel Shade Tolerance for " + r.toString().substring(0, r.toString().indexOf(')') + 1);
                final String footnote = "<html><hr><font size=2>Use bypass diodes to direct current under shading conditions.<hr></html>";
                final JPanel gui = new JPanel(new BorderLayout(5, 5));
                final JComboBox<String> toleranceComboBox = new JComboBox<String>(new String[] { "Partial", "High", "None" });
                toleranceComboBox.setSelectedIndex(s.getShadeTolerance());
                gui.add(toleranceComboBox, BorderLayout.NORTH);
                final JPanel scopePanel = new JPanel();
                scopePanel.setLayout(new BoxLayout(scopePanel, BoxLayout.Y_AXIS));
                scopePanel.setBorder(BorderFactory.createTitledBorder("Apply to:"));
                final JRadioButton rb1 = new JRadioButton("Only this Rack", true);
                final JRadioButton rb2 = new JRadioButton("All Racks on this Foundation");
                final JRadioButton rb3 = new JRadioButton("All Racks");
                scopePanel.add(rb1);
                scopePanel.add(rb2);
                scopePanel.add(rb3);
                final ButtonGroup bg = new ButtonGroup();
                bg.add(rb1);
                bg.add(rb2);
                bg.add(rb3);
                switch(selectedScopeIndex) {
                    case 0:
                        rb1.setSelected(true);
                        break;
                    case 1:
                        rb2.setSelected(true);
                        break;
                    case 2:
                        rb3.setSelected(true);
                        break;
                }
                gui.add(scopePanel, BorderLayout.CENTER);
                final Object[] options = new Object[] { "OK", "Cancel", "Apply" };
                final JOptionPane optionPane = new JOptionPane(new Object[] { title, footnote, gui }, JOptionPane.QUESTION_MESSAGE, JOptionPane.YES_NO_CANCEL_OPTION, null, options, options[2]);
                final JDialog dialog = optionPane.createDialog(MainFrame.getInstance(), "Solar Panel Shade Tolerance");
                while (true) {
                    dialog.setVisible(true);
                    final Object choice = optionPane.getValue();
                    if (choice == options[1] || choice == null) {
                        break;
                    } else {
                        final int selectedIndex = toleranceComboBox.getSelectedIndex();
                        boolean changed = selectedIndex != s.getShadeTolerance();
                        if (rb1.isSelected()) {
                            if (changed) {
                                final SetSolarPanelShadeToleranceForRackCommand c = new SetSolarPanelShadeToleranceForRackCommand(r);
                                s.setShadeTolerance(selectedIndex);
                                SceneManager.getInstance().getUndoManager().addEdit(c);
                            }
                            selectedScopeIndex = 0;
                        } else if (rb2.isSelected()) {
                            if (!changed) {
                                for (final Rack x : foundation.getRacks()) {
                                    if (selectedIndex != x.getSolarPanel().getShadeTolerance()) {
                                        changed = true;
                                        break;
                                    }
                                }
                            }
                            if (changed) {
                                final SetSolarPanelShadeToleranceForRacksOnFoundationCommand c = new SetSolarPanelShadeToleranceForRacksOnFoundationCommand(foundation);
                                foundation.setSolarPanelShadeToleranceForRacks(selectedIndex);
                                SceneManager.getInstance().getUndoManager().addEdit(c);
                            }
                            selectedScopeIndex = 1;
                        } else if (rb3.isSelected()) {
                            if (!changed) {
                                for (final Rack x : Scene.getInstance().getAllRacks()) {
                                    if (selectedIndex != x.getSolarPanel().getShadeTolerance()) {
                                        changed = true;
                                        break;
                                    }
                                }
                            }
                            if (changed) {
                                final SetSolarPanelShadeToleranceForAllRacksCommand c = new SetSolarPanelShadeToleranceForAllRacksCommand();
                                Scene.getInstance().setSolarPanelShadeToleranceForAllRacks(selectedIndex);
                                SceneManager.getInstance().getUndoManager().addEdit(c);
                            }
                            selectedScopeIndex = 2;
                        }
                        if (changed) {
                            updateAfterEdit();
                        }
                        if (choice == options[0]) {
                            break;
                        }
                    }
                }
            }
        });
        solarPanelMenu.addSeparator();
        final JMenuItem miSolarPanelOrientation = new JMenuItem("Orientation...");
        solarPanelMenu.add(miSolarPanelOrientation);
        miSolarPanelOrientation.addActionListener(new ActionListener() {

            // remember the scope selection as the next action will likely be applied to the same scope
            private int selectedScopeIndex = 0;

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (!(selectedPart instanceof Rack)) {
                    return;
                }
                final Rack r = (Rack) selectedPart;
                final Foundation foundation = r.getTopContainer();
                final SolarPanel s = r.getSolarPanel();
                final String partInfo = r.toString().substring(0, r.toString().indexOf(')') + 1);
                final JPanel gui = new JPanel(new BorderLayout(5, 5));
                gui.setBorder(BorderFactory.createTitledBorder("Solar Panel Orientation for " + partInfo));
                final JComboBox<String> orientationComboBox = new JComboBox<String>(new String[] { "Portrait", "Landscape" });
                orientationComboBox.setSelectedIndex(s.isRotated() ? 1 : 0);
                gui.add(orientationComboBox, BorderLayout.NORTH);
                final JPanel scopePanel = new JPanel();
                scopePanel.setLayout(new BoxLayout(scopePanel, BoxLayout.Y_AXIS));
                scopePanel.setBorder(BorderFactory.createTitledBorder("Apply to:"));
                final JRadioButton rb1 = new JRadioButton("Only this Rack", true);
                final JRadioButton rb2 = new JRadioButton("All Racks on this Foundation");
                final JRadioButton rb3 = new JRadioButton("All Racks");
                scopePanel.add(rb1);
                scopePanel.add(rb2);
                scopePanel.add(rb3);
                final ButtonGroup bg = new ButtonGroup();
                bg.add(rb1);
                bg.add(rb2);
                bg.add(rb3);
                switch(selectedScopeIndex) {
                    case 0:
                        rb1.setSelected(true);
                        break;
                    case 1:
                        rb2.setSelected(true);
                        break;
                    case 2:
                        rb3.setSelected(true);
                        break;
                }
                gui.add(scopePanel, BorderLayout.CENTER);
                final Object[] options = new Object[] { "OK", "Cancel", "Apply" };
                final JOptionPane optionPane = new JOptionPane(gui, JOptionPane.QUESTION_MESSAGE, JOptionPane.YES_NO_CANCEL_OPTION, null, options, options[2]);
                final JDialog dialog = optionPane.createDialog(MainFrame.getInstance(), "Solar Panel Orientation");
                while (true) {
                    dialog.setVisible(true);
                    final Object choice = optionPane.getValue();
                    if (choice == options[1] || choice == null) {
                        break;
                    } else {
                        final boolean b = orientationComboBox.getSelectedIndex() == 1;
                        boolean changed = b ^ s.isRotated();
                        if (rb1.isSelected()) {
                            if (changed) {
                                final RotateSolarPanelsForRackCommand c = new RotateSolarPanelsForRackCommand(r);
                                s.setRotated(orientationComboBox.getSelectedIndex() == 1);
                                r.ensureFullSolarPanels(false);
                                r.draw();
                                SceneManager.getInstance().refresh();
                                SceneManager.getInstance().getUndoManager().addEdit(c);
                            }
                            selectedScopeIndex = 0;
                        } else if (rb2.isSelected()) {
                            if (!changed) {
                                for (final Rack x : foundation.getRacks()) {
                                    if (b ^ x.getSolarPanel().isRotated()) {
                                        changed = true;
                                        break;
                                    }
                                }
                            }
                            if (changed) {
                                final RotateSolarPanelsForRacksOnFoundationCommand c = new RotateSolarPanelsForRacksOnFoundationCommand(foundation);
                                foundation.rotateSolarPanelsOnRacks(orientationComboBox.getSelectedIndex() == 1);
                                SceneManager.getInstance().getUndoManager().addEdit(c);
                            }
                            selectedScopeIndex = 1;
                        } else if (rb3.isSelected()) {
                            if (!changed) {
                                for (final Rack x : Scene.getInstance().getAllRacks()) {
                                    if (b ^ x.getSolarPanel().isRotated()) {
                                        changed = true;
                                        break;
                                    }
                                }
                            }
                            if (changed) {
                                final RotateSolarPanelsForAllRacksCommand c = new RotateSolarPanelsForAllRacksCommand();
                                Scene.getInstance().rotateSolarPanelsOnAllRacks(orientationComboBox.getSelectedIndex() == 1);
                                SceneManager.getInstance().getUndoManager().addEdit(c);
                            }
                            selectedScopeIndex = 2;
                        }
                        if (changed) {
                            updateAfterEdit();
                        }
                        if (choice == options[0]) {
                            break;
                        }
                    }
                }
            }
        });
        final JMenuItem miInverterEfficiency = new JMenuItem("Inverter Efficiency...");
        solarPanelMenu.add(miInverterEfficiency);
        miInverterEfficiency.addActionListener(new ActionListener() {

            // remember the scope selection as the next action will likely be applied to the same scope
            private int selectedScopeIndex = 0;

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (!(selectedPart instanceof Rack)) {
                    return;
                }
                final Rack r = (Rack) selectedPart;
                final Foundation foundation = r.getTopContainer();
                final SolarPanel s = r.getSolarPanel();
                final String title = "Set Inverter Efficiency (%) for " + r.toString().substring(0, r.toString().indexOf(')') + 1);
                final String footnote = "<html><hr><font size=2>The efficiency of a micro inverter for converting electricity<br>from DC to AC is typically 95%.<hr></html>";
                final JPanel gui = new JPanel(new BorderLayout(5, 5));
                final JTextField inputField = new JTextField(threeDecimalsFormat.format(s.getInverterEfficiency() * 100));
                gui.add(inputField, BorderLayout.NORTH);
                final JPanel scopePanel = new JPanel();
                scopePanel.setLayout(new BoxLayout(scopePanel, BoxLayout.Y_AXIS));
                scopePanel.setBorder(BorderFactory.createTitledBorder("Apply to:"));
                final JRadioButton rb1 = new JRadioButton("Only this Rack", true);
                final JRadioButton rb2 = new JRadioButton("All Racks on this Foundation");
                final JRadioButton rb3 = new JRadioButton("All Racks");
                scopePanel.add(rb1);
                scopePanel.add(rb2);
                scopePanel.add(rb3);
                final ButtonGroup bg = new ButtonGroup();
                bg.add(rb1);
                bg.add(rb2);
                bg.add(rb3);
                switch(selectedScopeIndex) {
                    case 0:
                        rb1.setSelected(true);
                        break;
                    case 1:
                        rb2.setSelected(true);
                        break;
                    case 2:
                        rb3.setSelected(true);
                        break;
                }
                gui.add(scopePanel, BorderLayout.CENTER);
                final Object[] options = new Object[] { "OK", "Cancel", "Apply" };
                final JOptionPane optionPane = new JOptionPane(new Object[] { title, footnote, gui }, JOptionPane.QUESTION_MESSAGE, JOptionPane.YES_NO_CANCEL_OPTION, null, options, options[2]);
                final JDialog dialog = optionPane.createDialog(MainFrame.getInstance(), "Inverter Efficiency");
                while (true) {
                    inputField.selectAll();
                    inputField.requestFocusInWindow();
                    dialog.setVisible(true);
                    final Object choice = optionPane.getValue();
                    if (choice == options[1] || choice == null) {
                        break;
                    } else {
                        boolean ok = true;
                        try {
                            inverterEfficiencyPercentage = Double.parseDouble(inputField.getText());
                        } catch (final NumberFormatException exception) {
                            JOptionPane.showMessageDialog(MainFrame.getInstance(), inputField.getText() + " is an invalid value!", "Error", JOptionPane.ERROR_MESSAGE);
                            ok = false;
                        }
                        if (ok) {
                            if (inverterEfficiencyPercentage < SolarPanel.MIN_INVERTER_EFFICIENCY_PERCENTAGE || inverterEfficiencyPercentage > SolarPanel.MAX_INVERTER_EFFICIENCY_PERCENTAGE) {
                                JOptionPane.showMessageDialog(MainFrame.getInstance(), "Inverter efficiency must be between " + SolarPanel.MIN_INVERTER_EFFICIENCY_PERCENTAGE + "% and " + SolarPanel.MAX_INVERTER_EFFICIENCY_PERCENTAGE + "%.", "Range Error", JOptionPane.ERROR_MESSAGE);
                            } else {
                                boolean changed = Math.abs(inverterEfficiencyPercentage * 0.01 - s.getInverterEfficiency()) > 0.000001;
                                if (rb1.isSelected()) {
                                    if (changed) {
                                        final SetInverterEfficiencyForRackCommand c = new SetInverterEfficiencyForRackCommand(r);
                                        s.setInverterEfficiency(inverterEfficiencyPercentage * 0.01);
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 0;
                                } else if (rb2.isSelected()) {
                                    if (!changed) {
                                        for (final Rack x : foundation.getRacks()) {
                                            if (Math.abs(inverterEfficiencyPercentage * 0.01 - x.getSolarPanel().getInverterEfficiency()) > 0.000001) {
                                                changed = true;
                                                break;
                                            }
                                        }
                                    }
                                    if (changed) {
                                        final SetInverterEfficiencyForRacksOnFoundationCommand c = new SetInverterEfficiencyForRacksOnFoundationCommand(foundation);
                                        foundation.setInverterEfficiencyForRacks(inverterEfficiencyPercentage * 0.01);
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 1;
                                } else if (rb3.isSelected()) {
                                    if (!changed) {
                                        for (final Rack x : Scene.getInstance().getAllRacks()) {
                                            if (Math.abs(inverterEfficiencyPercentage * 0.01 - x.getSolarPanel().getInverterEfficiency()) > 0.000001) {
                                                changed = true;
                                                break;
                                            }
                                        }
                                    }
                                    if (changed) {
                                        final SetInverterEfficiencyForAllRacksCommand c = new SetInverterEfficiencyForAllRacksCommand();
                                        Scene.getInstance().setInverterEfficiencyForAllRacks(inverterEfficiencyPercentage * 0.01);
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 2;
                                }
                                if (changed) {
                                    updateAfterEdit();
                                }
                                if (choice == options[0]) {
                                    break;
                                }
                            }
                        }
                    }
                }
            }
        });
        final ButtonGroup trackerButtonGroup = new ButtonGroup();
        final JRadioButtonMenuItem miNoTracker = new JRadioButtonMenuItem("No Tracker...", true);
        trackerButtonGroup.add(miNoTracker);
        miNoTracker.addActionListener(new ActionListener() {

            // remember the scope selection as the next action will likely be applied to the same scope
            private int selectedScopeIndex = 0;

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (!(selectedPart instanceof Rack)) {
                    return;
                }
                final Rack rack = (Rack) selectedPart;
                final String partInfo = rack.toString().substring(0, rack.toString().indexOf(')') + 1);
                final JPanel panel = new JPanel();
                panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
                panel.setBorder(BorderFactory.createTitledBorder("Apply to:"));
                final JRadioButton rb1 = new JRadioButton("Only this Rack", true);
                final JRadioButton rb2 = new JRadioButton("All Racks on this Foundation");
                final JRadioButton rb3 = new JRadioButton("All Racks");
                panel.add(rb1);
                panel.add(rb2);
                panel.add(rb3);
                final ButtonGroup bg = new ButtonGroup();
                bg.add(rb1);
                bg.add(rb2);
                bg.add(rb3);
                switch(selectedScopeIndex) {
                    case 0:
                        rb1.setSelected(true);
                        break;
                    case 1:
                        rb2.setSelected(true);
                        break;
                    case 2:
                        rb3.setSelected(true);
                        break;
                }
                final String title = "<html>Remove tracker for " + partInfo + "</html>";
                final String footnote = "<html><hr><font size=2>No tracker will be used.<hr></html>";
                final Object[] params = { title, footnote, panel };
                if (JOptionPane.showConfirmDialog(MainFrame.getInstance(), params, "Remove solar tracker", JOptionPane.OK_CANCEL_OPTION) == JOptionPane.CANCEL_OPTION) {
                    return;
                }
                boolean changed = rack.getTracker() != Trackable.NO_TRACKER;
                if (rb1.isSelected()) {
                    if (changed) {
                        final SetSolarTrackerCommand c = new SetSolarTrackerCommand(rack, "No Tracker");
                        rack.setTracker(Trackable.NO_TRACKER);
                        rack.draw();
                        SceneManager.getInstance().refresh();
                        SceneManager.getInstance().getUndoManager().addEdit(c);
                    }
                    selectedScopeIndex = 0;
                } else if (rb2.isSelected()) {
                    final Foundation foundation = rack.getTopContainer();
                    if (!changed) {
                        for (final Rack x : foundation.getRacks()) {
                            if (x.getTracker() != Trackable.NO_TRACKER) {
                                changed = true;
                                break;
                            }
                        }
                    }
                    if (changed) {
                        final SetSolarTrackersOnFoundationCommand c = new SetSolarTrackersOnFoundationCommand(foundation, rack, "No Tracker for All Racks on Selected Foundation");
                        foundation.setTrackerForRacks(Trackable.NO_TRACKER);
                        SceneManager.getInstance().getUndoManager().addEdit(c);
                    }
                    selectedScopeIndex = 1;
                } else if (rb3.isSelected()) {
                    if (!changed) {
                        for (final Rack x : Scene.getInstance().getAllRacks()) {
                            if (x.getTracker() != Trackable.NO_TRACKER) {
                                changed = true;
                                break;
                            }
                        }
                    }
                    if (changed) {
                        final SetSolarTrackersForAllCommand c = new SetSolarTrackersForAllCommand(rack, "No Tracker for All Racks");
                        Scene.getInstance().setTrackerForAllRacks(Trackable.NO_TRACKER);
                        SceneManager.getInstance().getUndoManager().addEdit(c);
                    }
                    selectedScopeIndex = 2;
                }
                if (changed) {
                    updateAfterEdit();
                }
            }
        });
        final JRadioButtonMenuItem miHorizontalSingleAxisTracker = new JRadioButtonMenuItem("Horizontal Single-Axis Tracker...");
        trackerButtonGroup.add(miHorizontalSingleAxisTracker);
        miHorizontalSingleAxisTracker.addActionListener(new ActionListener() {

            // remember the scope selection as the next action will likely be applied to the same scope
            private int selectedScopeIndex = 0;

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (!(selectedPart instanceof Rack)) {
                    return;
                }
                final Rack rack = (Rack) selectedPart;
                final String partInfo = rack.toString().substring(0, rack.toString().indexOf(')') + 1);
                final JPanel panel = new JPanel();
                panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
                panel.setBorder(BorderFactory.createTitledBorder("Apply to:"));
                final JRadioButton rb1 = new JRadioButton("Only this Rack", true);
                final JRadioButton rb2 = new JRadioButton("All Racks on this Foundation");
                final JRadioButton rb3 = new JRadioButton("All Racks");
                panel.add(rb1);
                panel.add(rb2);
                panel.add(rb3);
                final ButtonGroup bg = new ButtonGroup();
                bg.add(rb1);
                bg.add(rb2);
                bg.add(rb3);
                switch(selectedScopeIndex) {
                    case 0:
                        rb1.setSelected(true);
                        break;
                    case 1:
                        rb2.setSelected(true);
                        break;
                    case 2:
                        rb3.setSelected(true);
                        break;
                }
                final String title = "<html>Set horizontal single-axis tracker for " + partInfo + "</html>";
                final String footnote = "<html><hr><font size=2>A horizontal single-axis tracker (HSAT) rotates about the north-south axis<br>to follow the sun from east to west during the day.<hr></html>";
                final Object[] params = { title, footnote, panel };
                if (JOptionPane.showConfirmDialog(MainFrame.getInstance(), params, "Set horizontal single-axis solar tracker", JOptionPane.OK_CANCEL_OPTION) == JOptionPane.CANCEL_OPTION) {
                    return;
                }
                boolean changed = rack.getTracker() != Trackable.HORIZONTAL_SINGLE_AXIS_TRACKER;
                if (rb1.isSelected()) {
                    if (changed) {
                        final SetSolarTrackerCommand c = new SetSolarTrackerCommand(rack, "Horizontal Single-Axis Tracker");
                        rack.setTracker(Trackable.HORIZONTAL_SINGLE_AXIS_TRACKER);
                        rack.draw();
                        SceneManager.getInstance().refresh();
                        SceneManager.getInstance().getUndoManager().addEdit(c);
                    }
                    selectedScopeIndex = 0;
                } else if (rb2.isSelected()) {
                    final Foundation foundation = rack.getTopContainer();
                    if (!changed) {
                        for (final Rack x : foundation.getRacks()) {
                            if (x.getTracker() != Trackable.HORIZONTAL_SINGLE_AXIS_TRACKER) {
                                changed = true;
                                break;
                            }
                        }
                    }
                    if (changed) {
                        final SetSolarTrackersOnFoundationCommand c = new SetSolarTrackersOnFoundationCommand(foundation, rack, "Horizontal Single-Axis Tracker for All Racks on Selected Foundation");
                        foundation.setTrackerForRacks(Trackable.HORIZONTAL_SINGLE_AXIS_TRACKER);
                        SceneManager.getInstance().getUndoManager().addEdit(c);
                    }
                    selectedScopeIndex = 1;
                } else if (rb3.isSelected()) {
                    if (!changed) {
                        for (final Rack x : Scene.getInstance().getAllRacks()) {
                            if (x.getTracker() != Trackable.HORIZONTAL_SINGLE_AXIS_TRACKER) {
                                changed = true;
                                break;
                            }
                        }
                    }
                    if (changed) {
                        final SetSolarTrackersForAllCommand c = new SetSolarTrackersForAllCommand(rack, "Horizontal Single-Axis Tracker for All Racks");
                        Scene.getInstance().setTrackerForAllRacks(Trackable.HORIZONTAL_SINGLE_AXIS_TRACKER);
                        SceneManager.getInstance().getUndoManager().addEdit(c);
                    }
                    selectedScopeIndex = 2;
                }
                if (changed) {
                    updateAfterEdit();
                }
            }
        });
        final JRadioButtonMenuItem miVerticalSingleAxisTracker = new JRadioButtonMenuItem("Vertical Single-Axis Tracker...");
        trackerButtonGroup.add(miVerticalSingleAxisTracker);
        miVerticalSingleAxisTracker.addActionListener(new ActionListener() {

            // remember the scope selection as the next action will likely be applied to the same scope
            private int selectedScopeIndex = 0;

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (!(selectedPart instanceof Rack)) {
                    return;
                }
                final Rack rack = (Rack) selectedPart;
                final String partInfo = rack.toString().substring(0, rack.toString().indexOf(')') + 1);
                final JPanel panel = new JPanel();
                panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
                panel.setBorder(BorderFactory.createTitledBorder("Apply to:"));
                final JRadioButton rb1 = new JRadioButton("Only this Rack", true);
                final JRadioButton rb2 = new JRadioButton("All Racks on this Foundation");
                final JRadioButton rb3 = new JRadioButton("All Racks");
                panel.add(rb1);
                panel.add(rb2);
                panel.add(rb3);
                final ButtonGroup bg = new ButtonGroup();
                bg.add(rb1);
                bg.add(rb2);
                bg.add(rb3);
                switch(selectedScopeIndex) {
                    case 0:
                        rb1.setSelected(true);
                        break;
                    case 1:
                        rb2.setSelected(true);
                        break;
                    case 2:
                        rb3.setSelected(true);
                        break;
                }
                final String title = "<html>Set vertical single-axis tracker for " + partInfo + "</html>";
                final String footnote = "<html><hr><font size=2>A vertical single-axis tracker (VSAT) rotates about an axis perpendicular to the ground<br>and follow the sun from east to west during the day.<hr></html>";
                final Object[] params = { title, footnote, panel };
                if (JOptionPane.showConfirmDialog(MainFrame.getInstance(), params, "Set vertical single-axis solar tracker", JOptionPane.OK_CANCEL_OPTION) == JOptionPane.CANCEL_OPTION) {
                    return;
                }
                boolean changed = rack.getTracker() != Trackable.VERTICAL_SINGLE_AXIS_TRACKER;
                if (rb1.isSelected()) {
                    if (changed) {
                        final SetSolarTrackerCommand c = new SetSolarTrackerCommand(rack, "Vertical Single-Axis Tracker");
                        rack.setTracker(Trackable.VERTICAL_SINGLE_AXIS_TRACKER);
                        rack.draw();
                        SceneManager.getInstance().refresh();
                        SceneManager.getInstance().getUndoManager().addEdit(c);
                    }
                    selectedScopeIndex = 0;
                } else if (rb2.isSelected()) {
                    final Foundation foundation = rack.getTopContainer();
                    if (!changed) {
                        for (final Rack x : foundation.getRacks()) {
                            if (x.getTracker() != Trackable.VERTICAL_SINGLE_AXIS_TRACKER) {
                                changed = true;
                                break;
                            }
                        }
                    }
                    if (changed) {
                        final SetSolarTrackersOnFoundationCommand c = new SetSolarTrackersOnFoundationCommand(foundation, rack, "Vertical Single-Axis Tracker for All Racks on Selected Foundation");
                        foundation.setTrackerForRacks(Trackable.VERTICAL_SINGLE_AXIS_TRACKER);
                        SceneManager.getInstance().getUndoManager().addEdit(c);
                    }
                    selectedScopeIndex = 1;
                } else if (rb3.isSelected()) {
                    if (!changed) {
                        for (final Rack x : Scene.getInstance().getAllRacks()) {
                            if (x.getTracker() != Trackable.VERTICAL_SINGLE_AXIS_TRACKER) {
                                changed = true;
                                break;
                            }
                        }
                    }
                    if (changed) {
                        final SetSolarTrackersForAllCommand c = new SetSolarTrackersForAllCommand(rack, "Vertical Single-Axis Tracker for All Racks");
                        Scene.getInstance().setTrackerForAllRacks(Trackable.VERTICAL_SINGLE_AXIS_TRACKER);
                        SceneManager.getInstance().getUndoManager().addEdit(c);
                    }
                    selectedScopeIndex = 2;
                }
                if (changed) {
                    updateAfterEdit();
                }
            }
        });
        final JRadioButtonMenuItem miTiltedSingleAxisTracker = new JRadioButtonMenuItem("Tilted Single-Axis Tracker...");
        miTiltedSingleAxisTracker.setEnabled(false);
        trackerButtonGroup.add(miTiltedSingleAxisTracker);
        miTiltedSingleAxisTracker.addActionListener(new ActionListener() {

            // remember the scope selection as the next action will likely be applied to the same scope
            private int selectedScopeIndex = 0;

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (!(selectedPart instanceof Rack)) {
                    return;
                }
                final Rack rack = (Rack) selectedPart;
                final String partInfo = rack.toString().substring(0, rack.toString().indexOf(')') + 1);
                final JPanel panel = new JPanel();
                panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
                panel.setBorder(BorderFactory.createTitledBorder("Apply to:"));
                final JRadioButton rb1 = new JRadioButton("Only this Rack", true);
                final JRadioButton rb2 = new JRadioButton("All Racks on this Foundation");
                final JRadioButton rb3 = new JRadioButton("All Racks");
                panel.add(rb1);
                panel.add(rb2);
                panel.add(rb3);
                final ButtonGroup bg = new ButtonGroup();
                bg.add(rb1);
                bg.add(rb2);
                bg.add(rb3);
                switch(selectedScopeIndex) {
                    case 0:
                        rb1.setSelected(true);
                        break;
                    case 1:
                        rb2.setSelected(true);
                        break;
                    case 2:
                        rb3.setSelected(true);
                        break;
                }
                final String title = "<html>Set tilted single-axis tracker for " + partInfo + "</html>";
                final String footnote = "<html><hr><font size=2>A tilted single-axis tracker (TSAT) rotates about an axis neither parallel nor perpendicular to the ground<br>and follow the sun from east to west during the day.<hr></html>";
                final Object[] params = { title, footnote, panel };
                if (JOptionPane.showConfirmDialog(MainFrame.getInstance(), params, "Set tilted single-axis solar tracker", JOptionPane.OK_CANCEL_OPTION) == JOptionPane.CANCEL_OPTION) {
                    return;
                }
                boolean changed = rack.getTracker() != Trackable.TILTED_SINGLE_AXIS_TRACKER;
                if (rb1.isSelected()) {
                    if (changed) {
                        final SetSolarTrackerCommand c = new SetSolarTrackerCommand(rack, "Tilted Single-Axis Tracker");
                        rack.setTracker(Trackable.TILTED_SINGLE_AXIS_TRACKER);
                        rack.draw();
                        SceneManager.getInstance().refresh();
                        SceneManager.getInstance().getUndoManager().addEdit(c);
                    }
                    selectedScopeIndex = 0;
                } else if (rb2.isSelected()) {
                    final Foundation foundation = rack.getTopContainer();
                    if (!changed) {
                        for (final Rack x : foundation.getRacks()) {
                            if (x.getTracker() != Trackable.TILTED_SINGLE_AXIS_TRACKER) {
                                changed = true;
                                break;
                            }
                        }
                    }
                    if (changed) {
                        final SetSolarTrackersOnFoundationCommand c = new SetSolarTrackersOnFoundationCommand(foundation, rack, "Tilted Single-Axis Tracker for All Racks on Selected Foundation");
                        foundation.setTrackerForRacks(Trackable.TILTED_SINGLE_AXIS_TRACKER);
                        SceneManager.getInstance().getUndoManager().addEdit(c);
                    }
                    selectedScopeIndex = 1;
                } else if (rb3.isSelected()) {
                    if (!changed) {
                        for (final Rack x : Scene.getInstance().getAllRacks()) {
                            if (x.getTracker() != Trackable.TILTED_SINGLE_AXIS_TRACKER) {
                                changed = true;
                                break;
                            }
                        }
                    }
                    if (changed) {
                        final SetSolarTrackersForAllCommand c = new SetSolarTrackersForAllCommand(rack, "Tilted Single-Axis Tracker for All Racks");
                        Scene.getInstance().setTrackerForAllRacks(Trackable.TILTED_SINGLE_AXIS_TRACKER);
                        SceneManager.getInstance().getUndoManager().addEdit(c);
                    }
                    selectedScopeIndex = 2;
                }
                if (changed) {
                    updateAfterEdit();
                }
            }
        });
        final JRadioButtonMenuItem miAltazimuthDualAxisTracker = new JRadioButtonMenuItem("Altazimuth Dual-Axis Tracker...");
        trackerButtonGroup.add(miAltazimuthDualAxisTracker);
        miAltazimuthDualAxisTracker.addActionListener(new ActionListener() {

            // remember the scope selection as the next action will likely be applied to the same scope
            private int selectedScopeIndex = 0;

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (!(selectedPart instanceof Rack)) {
                    return;
                }
                final Rack rack = (Rack) selectedPart;
                final String partInfo = rack.toString().substring(0, rack.toString().indexOf(')') + 1);
                final JPanel panel = new JPanel();
                panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
                panel.setBorder(BorderFactory.createTitledBorder("Apply to:"));
                final JRadioButton rb1 = new JRadioButton("Only this Rack", true);
                final JRadioButton rb2 = new JRadioButton("All Racks on this Foundation");
                final JRadioButton rb3 = new JRadioButton("All Racks");
                panel.add(rb1);
                panel.add(rb2);
                panel.add(rb3);
                final ButtonGroup bg = new ButtonGroup();
                bg.add(rb1);
                bg.add(rb2);
                bg.add(rb3);
                switch(selectedScopeIndex) {
                    case 0:
                        rb1.setSelected(true);
                        break;
                    case 1:
                        rb2.setSelected(true);
                        break;
                    case 2:
                        rb3.setSelected(true);
                        break;
                }
                final String title = "<html>Set altitude-azimuth dual-axis tracker for " + partInfo + "</html>";
                final String footnote = "<html><hr><font size=2>The Alt/Az dual-axis solar tracker will rotate the solar panel to face the sun<br>all the time during the day.<hr></html>";
                final Object[] params = { title, footnote, panel };
                if (JOptionPane.showConfirmDialog(MainFrame.getInstance(), params, "Set altitude-azimuth dual-axis solar tracker", JOptionPane.OK_CANCEL_OPTION) == JOptionPane.CANCEL_OPTION) {
                    return;
                }
                boolean changed = rack.getTracker() != Trackable.ALTAZIMUTH_DUAL_AXIS_TRACKER;
                if (rb1.isSelected()) {
                    if (changed) {
                        final SetSolarTrackerCommand c = new SetSolarTrackerCommand(rack, "Dual-Axis Tracker");
                        rack.setTracker(Trackable.ALTAZIMUTH_DUAL_AXIS_TRACKER);
                        rack.draw();
                        SceneManager.getInstance().refresh();
                        SceneManager.getInstance().getUndoManager().addEdit(c);
                    }
                    selectedScopeIndex = 0;
                } else if (rb2.isSelected()) {
                    final Foundation foundation = rack.getTopContainer();
                    if (!changed) {
                        for (final Rack x : foundation.getRacks()) {
                            if (x.getTracker() != Trackable.ALTAZIMUTH_DUAL_AXIS_TRACKER) {
                                changed = true;
                                break;
                            }
                        }
                    }
                    if (changed) {
                        final SetSolarTrackersOnFoundationCommand c = new SetSolarTrackersOnFoundationCommand(foundation, rack, "Dual-Axis Tracker for All Racks on Selected Foundation");
                        foundation.setTrackerForRacks(Trackable.ALTAZIMUTH_DUAL_AXIS_TRACKER);
                        SceneManager.getInstance().getUndoManager().addEdit(c);
                    }
                    selectedScopeIndex = 1;
                } else if (rb3.isSelected()) {
                    if (!changed) {
                        for (final Rack x : Scene.getInstance().getAllRacks()) {
                            if (x.getTracker() != Trackable.ALTAZIMUTH_DUAL_AXIS_TRACKER) {
                                changed = true;
                                break;
                            }
                        }
                    }
                    if (changed) {
                        final SetSolarTrackersForAllCommand c = new SetSolarTrackersForAllCommand(rack, "Dual-Axis Tracker for All Racks");
                        Scene.getInstance().setTrackerForAllRacks(Trackable.ALTAZIMUTH_DUAL_AXIS_TRACKER);
                        SceneManager.getInstance().getUndoManager().addEdit(c);
                    }
                    selectedScopeIndex = 2;
                }
                if (changed) {
                    updateAfterEdit();
                }
            }
        });
        trackerMenu.add(miNoTracker);
        trackerMenu.add(miHorizontalSingleAxisTracker);
        trackerMenu.add(miVerticalSingleAxisTracker);
        trackerMenu.add(miTiltedSingleAxisTracker);
        trackerMenu.add(miAltazimuthDualAxisTracker);
        final JCheckBoxMenuItem cbmiDisableEditPoints = new JCheckBoxMenuItem("Disable Edit Points");
        cbmiDisableEditPoints.addItemListener(new ItemListener() {

            // remember the scope selection as the next action will likely be applied to the same scope
            private int selectedScopeIndex = 0;

            @Override
            public void itemStateChanged(final ItemEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (!(selectedPart instanceof Rack)) {
                    return;
                }
                final boolean disabled = cbmiDisableEditPoints.isSelected();
                final Rack r = (Rack) selectedPart;
                final String partInfo = r.toString().substring(0, r.toString().indexOf(')') + 1);
                final JPanel gui = new JPanel(new BorderLayout(0, 20));
                final JPanel panel = new JPanel();
                gui.add(panel, BorderLayout.SOUTH);
                panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
                panel.setBorder(BorderFactory.createTitledBorder("Apply to:"));
                final JRadioButton rb1 = new JRadioButton("Only this Rack", true);
                final JRadioButton rb2 = new JRadioButton("All Racks on this Foundation");
                final JRadioButton rb3 = new JRadioButton("All Racks");
                panel.add(rb1);
                panel.add(rb2);
                panel.add(rb3);
                final ButtonGroup bg = new ButtonGroup();
                bg.add(rb1);
                bg.add(rb2);
                bg.add(rb3);
                switch(selectedScopeIndex) {
                    case 0:
                        rb1.setSelected(true);
                        break;
                    case 1:
                        rb2.setSelected(true);
                        break;
                    case 2:
                        rb3.setSelected(true);
                        break;
                }
                final String title = "<html>" + (disabled ? "Disable" : "Enable") + " edit points for " + partInfo + "</html>";
                final String footnote = "<html><hr><font size=2>Disable the edit points of a solar panel rack prevents it<br>from being unintentionally moved.<hr></html>";
                final Object[] options = new Object[] { "OK", "Cancel" };
                final JOptionPane optionPane = new JOptionPane(new Object[] { title, footnote, gui }, JOptionPane.QUESTION_MESSAGE, JOptionPane.YES_NO_CANCEL_OPTION, null, options, options[0]);
                final JDialog dialog = optionPane.createDialog(MainFrame.getInstance(), (disabled ? "Disable" : "Enable") + " Edit Points");
                dialog.setVisible(true);
                if (optionPane.getValue() == options[0]) {
                    if (rb1.isSelected()) {
                        final LockEditPointsCommand c = new LockEditPointsCommand(r);
                        r.setLockEdit(disabled);
                        SceneManager.getInstance().getUndoManager().addEdit(c);
                        selectedScopeIndex = 0;
                    } else if (rb2.isSelected()) {
                        final Foundation foundation = r.getTopContainer();
                        final LockEditPointsOnFoundationCommand c = new LockEditPointsOnFoundationCommand(foundation, r.getClass());
                        foundation.setLockEditForClass(disabled, r.getClass());
                        SceneManager.getInstance().getUndoManager().addEdit(c);
                        selectedScopeIndex = 1;
                    } else if (rb3.isSelected()) {
                        final LockEditPointsForClassCommand c = new LockEditPointsForClassCommand(r);
                        Scene.getInstance().setLockEditForClass(disabled, r.getClass());
                        SceneManager.getInstance().getUndoManager().addEdit(c);
                        selectedScopeIndex = 2;
                    }
                    SceneManager.getInstance().refresh();
                    Scene.getInstance().setEdited(true);
                }
            }
        });
        final JCheckBoxMenuItem cbmiDrawSunBeam = new JCheckBoxMenuItem("Draw Sun Beam");
        cbmiDrawSunBeam.addItemListener(new ItemListener() {

            @Override
            public void itemStateChanged(final ItemEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (!(selectedPart instanceof Rack)) {
                    return;
                }
                final Rack rack = (Rack) selectedPart;
                final ShowSunBeamCommand c = new ShowSunBeamCommand(rack);
                rack.setSunBeamVisible(cbmiDrawSunBeam.isSelected());
                rack.drawSunBeam();
                rack.draw();
                SceneManager.getInstance().refresh();
                SceneManager.getInstance().getUndoManager().addEdit(c);
                Scene.getInstance().setEdited(true);
            }
        });
        final JMenu labelMenu = new JMenu("Label");
        final JCheckBoxMenuItem miLabelNone = new JCheckBoxMenuItem("None", true);
        miLabelNone.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(final ActionEvent e) {
                if (miLabelNone.isSelected()) {
                    final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                    if (selectedPart instanceof Rack) {
                        final Rack r = (Rack) selectedPart;
                        final SetRackLabelCommand c = new SetRackLabelCommand(r);
                        r.clearLabels();
                        r.draw();
                        SceneManager.getInstance().getUndoManager().addEdit(c);
                        Scene.getInstance().setEdited(true);
                        SceneManager.getInstance().refresh();
                    }
                }
            }
        });
        labelMenu.add(miLabelNone);
        final JCheckBoxMenuItem miLabelCustom = new JCheckBoxMenuItem("Custom");
        miLabelCustom.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (selectedPart instanceof Rack) {
                    final Rack r = (Rack) selectedPart;
                    final SetRackLabelCommand c = new SetRackLabelCommand(r);
                    r.setLabelCustom(miLabelCustom.isSelected());
                    if (r.getLabelCustom()) {
                        r.setLabelCustomText(JOptionPane.showInputDialog(MainFrame.getInstance(), "Custom Text", r.getLabelCustomText()));
                    }
                    r.draw();
                    SceneManager.getInstance().getUndoManager().addEdit(c);
                    Scene.getInstance().setEdited(true);
                    SceneManager.getInstance().refresh();
                }
            }
        });
        labelMenu.add(miLabelCustom);
        final JCheckBoxMenuItem miLabelId = new JCheckBoxMenuItem("ID");
        miLabelId.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (selectedPart instanceof Rack) {
                    final Rack r = (Rack) selectedPart;
                    final SetRackLabelCommand c = new SetRackLabelCommand(r);
                    r.setLabelId(miLabelId.isSelected());
                    r.draw();
                    SceneManager.getInstance().getUndoManager().addEdit(c);
                    Scene.getInstance().setEdited(true);
                    SceneManager.getInstance().refresh();
                }
            }
        });
        labelMenu.add(miLabelId);
        final JCheckBoxMenuItem miLabelCellEfficiency = new JCheckBoxMenuItem("Cell Efficiency");
        miLabelCellEfficiency.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (selectedPart instanceof Rack) {
                    final Rack r = (Rack) selectedPart;
                    final SetRackLabelCommand c = new SetRackLabelCommand(r);
                    r.setLabelCellEfficiency(miLabelCellEfficiency.isSelected());
                    r.draw();
                    SceneManager.getInstance().getUndoManager().addEdit(c);
                    Scene.getInstance().setEdited(true);
                    SceneManager.getInstance().refresh();
                }
            }
        });
        labelMenu.add(miLabelCellEfficiency);
        final JCheckBoxMenuItem miLabelTiltAngle = new JCheckBoxMenuItem("Tilt Angle");
        miLabelTiltAngle.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (selectedPart instanceof Rack) {
                    final Rack r = (Rack) selectedPart;
                    final SetRackLabelCommand c = new SetRackLabelCommand(r);
                    r.setLabelTiltAngle(miLabelTiltAngle.isSelected());
                    r.draw();
                    SceneManager.getInstance().getUndoManager().addEdit(c);
                    Scene.getInstance().setEdited(true);
                    SceneManager.getInstance().refresh();
                }
            }
        });
        labelMenu.add(miLabelTiltAngle);
        final JCheckBoxMenuItem miLabelTracker = new JCheckBoxMenuItem("Tracker");
        miLabelTracker.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (selectedPart instanceof Rack) {
                    final Rack r = (Rack) selectedPart;
                    final SetRackLabelCommand c = new SetRackLabelCommand(r);
                    r.setLabelTracker(miLabelTracker.isSelected());
                    r.draw();
                    SceneManager.getInstance().getUndoManager().addEdit(c);
                    Scene.getInstance().setEdited(true);
                    SceneManager.getInstance().refresh();
                }
            }
        });
        labelMenu.add(miLabelTracker);
        final JCheckBoxMenuItem miLabelEnergyOutput = new JCheckBoxMenuItem("Energy Output");
        miLabelEnergyOutput.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (selectedPart instanceof Rack) {
                    final Rack r = (Rack) selectedPart;
                    final SetRackLabelCommand c = new SetRackLabelCommand(r);
                    r.setLabelEnergyOutput(miLabelEnergyOutput.isSelected());
                    r.draw();
                    SceneManager.getInstance().getUndoManager().addEdit(c);
                    Scene.getInstance().setEdited(true);
                    SceneManager.getInstance().refresh();
                }
            }
        });
        labelMenu.add(miLabelEnergyOutput);
        popupMenuForRack = createPopupMenu(true, true, new Runnable() {

            @Override
            public void run() {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (!(selectedPart instanceof Rack)) {
                    return;
                }
                final HousePart copyBuffer = Scene.getInstance().getCopyBuffer();
                miPaste.setEnabled(copyBuffer instanceof SolarPanel);
                final Rack rack = (Rack) selectedPart;
                switch(rack.getTracker()) {
                    case Trackable.ALTAZIMUTH_DUAL_AXIS_TRACKER:
                        Util.selectSilently(miAltazimuthDualAxisTracker, true);
                        break;
                    case Trackable.HORIZONTAL_SINGLE_AXIS_TRACKER:
                        Util.selectSilently(miHorizontalSingleAxisTracker, true);
                        break;
                    case Trackable.VERTICAL_SINGLE_AXIS_TRACKER:
                        Util.selectSilently(miVerticalSingleAxisTracker, true);
                        break;
                    case Trackable.TILTED_SINGLE_AXIS_TRACKER:
                        Util.selectSilently(miTiltedSingleAxisTracker, true);
                        break;
                    case Trackable.NO_TRACKER:
                        Util.selectSilently(miNoTracker, true);
                        break;
                }
                miAltazimuthDualAxisTracker.setEnabled(true);
                miHorizontalSingleAxisTracker.setEnabled(true);
                miVerticalSingleAxisTracker.setEnabled(true);
                if (rack.getContainer() instanceof Roof) {
                    final Roof roof = (Roof) rack.getContainer();
                    final boolean flat = Util.isZero(roof.getHeight());
                    miAltazimuthDualAxisTracker.setEnabled(flat);
                    miHorizontalSingleAxisTracker.setEnabled(flat);
                    miVerticalSingleAxisTracker.setEnabled(flat);
                }
                if (rack.getTracker() != Trackable.NO_TRACKER) {
                    // vertical and tilted single-axis trackers can adjust the tilt angle
                    miFixedTiltAngle.setEnabled(rack.getTracker() == Trackable.VERTICAL_SINGLE_AXIS_TRACKER || rack.getTracker() == Trackable.TILTED_SINGLE_AXIS_TRACKER);
                    miSeasonalTiltAngle.setEnabled(miFixedTiltAngle.isEnabled());
                    // any tracker that will alter the azimuth angle should disable the menu item
                    miAzimuth.setEnabled(rack.getTracker() != Trackable.ALTAZIMUTH_DUAL_AXIS_TRACKER && rack.getTracker() != Trackable.VERTICAL_SINGLE_AXIS_TRACKER);
                    miRotate.setEnabled(miAzimuth.isEnabled());
                } else {
                    miFixedTiltAngle.setEnabled(true);
                    miSeasonalTiltAngle.setEnabled(true);
                    miAzimuth.setEnabled(true);
                    miRotate.setEnabled(true);
                    miBaseHeight.setEnabled(true);
                    miPoleSpacing.setEnabled(true);
                    if (rack.getContainer() instanceof Roof) {
                        final Roof roof = (Roof) rack.getContainer();
                        if (roof.getHeight() > 0) {
                            miFixedTiltAngle.setEnabled(false);
                            miSeasonalTiltAngle.setEnabled(false);
                            miAzimuth.setEnabled(false);
                            miBaseHeight.setEnabled(false);
                            miPoleSpacing.setEnabled(false);
                            miRotate.setEnabled(false);
                        }
                    }
                }
                Util.selectSilently(cbmiDisableEditPoints, rack.getLockEdit());
                Util.selectSilently(cbmiDrawSunBeam, rack.isSunBeamVisible());
                Util.selectSilently(miLabelNone, !rack.isLabelVisible());
                Util.selectSilently(miLabelCustom, rack.getLabelCustom());
                Util.selectSilently(miLabelId, rack.getLabelId());
                Util.selectSilently(miLabelCellEfficiency, rack.getLabelCellEfficiency());
                Util.selectSilently(miLabelTiltAngle, rack.getLabelTiltAngle());
                Util.selectSilently(miLabelTracker, rack.getLabelTracker());
                Util.selectSilently(miLabelEnergyOutput, rack.getLabelEnergyOutput());
                final boolean isCustom = "Custom".equals(rack.getSolarPanel().getModelName());
                miSolarPanelCellEfficiency.setEnabled(isCustom);
                miSolarPanelCellType.setEnabled(isCustom);
                miSolarPanelColor.setEnabled(isCustom);
                miSolarPanelSize.setEnabled(isCustom);
                miSolarPanelShadeTolerance.setEnabled(isCustom);
                miSolarPanelNoct.setEnabled(isCustom);
                miSolarPanelPmaxTc.setEnabled(isCustom);
            }
        });
        popupMenuForRack.add(miPaste);
        popupMenuForRack.add(miClear);
        popupMenuForRack.addSeparator();
        popupMenuForRack.add(miSolarPanels);
        popupMenuForRack.add(solarPanelMenu);
        popupMenuForRack.addSeparator();
        popupMenuForRack.add(miFixedTiltAngle);
        popupMenuForRack.add(miSeasonalTiltAngle);
        popupMenuForRack.add(miAzimuth);
        popupMenuForRack.add(miRotate);
        popupMenuForRack.add(miRackWidth);
        popupMenuForRack.add(miRackLength);
        popupMenuForRack.add(miBaseHeight);
        popupMenuForRack.add(miPoleSpacing);
        popupMenuForRack.add(trackerMenu);
        popupMenuForRack.addSeparator();
        popupMenuForRack.add(cbmiDisableEditPoints);
        popupMenuForRack.add(cbmiDrawSunBeam);
        popupMenuForRack.add(labelMenu);
        popupMenuForRack.addSeparator();
        JMenuItem mi = new JMenuItem("Daily Yield Analysis...");
        mi.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(final ActionEvent e) {
                if (EnergyPanel.getInstance().adjustCellSize()) {
                    return;
                }
                if (SceneManager.getInstance().getSelectedPart() instanceof Rack) {
                    new PvDailyAnalysis().show();
                }
            }
        });
        popupMenuForRack.add(mi);
        mi = new JMenuItem("Annual Yield Analysis...");
        mi.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(final ActionEvent e) {
                if (EnergyPanel.getInstance().adjustCellSize()) {
                    return;
                }
                if (SceneManager.getInstance().getSelectedPart() instanceof Rack) {
                    new PvAnnualAnalysis().show();
                }
            }
        });
        popupMenuForRack.add(mi);
    }
    return popupMenuForRack;
}
Also used : JPanel(javax.swing.JPanel) ChooseSolarPanelSizeForRackCommand(org.concord.energy3d.undo.ChooseSolarPanelSizeForRackCommand) SetPartSizeCommand(org.concord.energy3d.undo.SetPartSizeCommand) SetSolarPanelColorForAllRacksCommand(org.concord.energy3d.undo.SetSolarPanelColorForAllRacksCommand) PvAnnualAnalysis(org.concord.energy3d.simulation.PvAnnualAnalysis) ChangeMonthlyTiltAnglesCommand(org.concord.energy3d.undo.ChangeMonthlyTiltAnglesCommand) ChangeFoundationRackAzimuthCommand(org.concord.energy3d.undo.ChangeFoundationRackAzimuthCommand) SetRackLabelCommand(org.concord.energy3d.undo.SetRackLabelCommand) Foundation(org.concord.energy3d.model.Foundation) LockEditPointsOnFoundationCommand(org.concord.energy3d.undo.LockEditPointsOnFoundationCommand) HousePart(org.concord.energy3d.model.HousePart) SetSolarCellEfficiencyForRacksOnFoundationCommand(org.concord.energy3d.undo.SetSolarCellEfficiencyForRacksOnFoundationCommand) RotateSolarPanelsForAllRacksCommand(org.concord.energy3d.undo.RotateSolarPanelsForAllRacksCommand) ChangeSolarPanelModelForRackCommand(org.concord.energy3d.undo.ChangeSolarPanelModelForRackCommand) SetTemperatureCoefficientPmaxForRacksOnFoundationCommand(org.concord.energy3d.undo.SetTemperatureCoefficientPmaxForRacksOnFoundationCommand) SetSolarPanelShadeToleranceForRackCommand(org.concord.energy3d.undo.SetSolarPanelShadeToleranceForRackCommand) ChangeAzimuthCommand(org.concord.energy3d.undo.ChangeAzimuthCommand) SetSolarPanelSizeForAllRacksCommand(org.concord.energy3d.undo.SetSolarPanelSizeForAllRacksCommand) SolarPanel(org.concord.energy3d.model.SolarPanel) SetSolarPanelCellTypeForRacksOnFoundationCommand(org.concord.energy3d.undo.SetSolarPanelCellTypeForRacksOnFoundationCommand) Map(java.util.Map) SetInverterEfficiencyForRacksOnFoundationCommand(org.concord.energy3d.undo.SetInverterEfficiencyForRacksOnFoundationCommand) ItemEvent(java.awt.event.ItemEvent) Callable(java.util.concurrent.Callable) SetSolarPanelArrayOnRackCustomCommand(org.concord.energy3d.undo.SetSolarPanelArrayOnRackCustomCommand) ChangeFoundationRackTiltAngleCommand(org.concord.energy3d.undo.ChangeFoundationRackTiltAngleCommand) GridLayout(java.awt.GridLayout) ChangePoleSettingsForAllRacksCommand(org.concord.energy3d.undo.ChangePoleSettingsForAllRacksCommand) PvDailyAnalysis(org.concord.energy3d.simulation.PvDailyAnalysis) PvModuleSpecs(org.concord.energy3d.simulation.PvModuleSpecs) SetSolarPanelArrayOnRackByModelCommand(org.concord.energy3d.undo.SetSolarPanelArrayOnRackByModelCommand) SetSolarPanelCellTypeForAllRacksCommand(org.concord.energy3d.undo.SetSolarPanelCellTypeForAllRacksCommand) JLabel(javax.swing.JLabel) ShowSunBeamCommand(org.concord.energy3d.undo.ShowSunBeamCommand) SetSolarPanelColorForRackCommand(org.concord.energy3d.undo.SetSolarPanelColorForRackCommand) ButtonGroup(javax.swing.ButtonGroup) SetInverterEfficiencyForAllRacksCommand(org.concord.energy3d.undo.SetInverterEfficiencyForAllRacksCommand) ItemListener(java.awt.event.ItemListener) ChangeFoundationRackMonthlyTiltAnglesCommand(org.concord.energy3d.undo.ChangeFoundationRackMonthlyTiltAnglesCommand) RotateSolarPanelsForRackCommand(org.concord.energy3d.undo.RotateSolarPanelsForRackCommand) SetSolarPanelSizeForRacksOnFoundationCommand(org.concord.energy3d.undo.SetSolarPanelSizeForRacksOnFoundationCommand) ActionEvent(java.awt.event.ActionEvent) ChangeBaseHeightForAllSolarCollectorsCommand(org.concord.energy3d.undo.ChangeBaseHeightForAllSolarCollectorsCommand) SetSolarTrackersForAllCommand(org.concord.energy3d.undo.SetSolarTrackersForAllCommand) SetSolarTrackerCommand(org.concord.energy3d.undo.SetSolarTrackerCommand) ChangePoleSettingsForRacksOnFoundationCommand(org.concord.energy3d.undo.ChangePoleSettingsForRacksOnFoundationCommand) BorderLayout(java.awt.BorderLayout) SetSolarPanelShadeToleranceForAllRacksCommand(org.concord.energy3d.undo.SetSolarPanelShadeToleranceForAllRacksCommand) List(java.util.List) SetNoctForRacksOnFoundationCommand(org.concord.energy3d.undo.SetNoctForRacksOnFoundationCommand) SetSolarTrackersOnFoundationCommand(org.concord.energy3d.undo.SetSolarTrackersOnFoundationCommand) SetSolarPanelShadeToleranceForRacksOnFoundationCommand(org.concord.energy3d.undo.SetSolarPanelShadeToleranceForRacksOnFoundationCommand) JRadioButtonMenuItem(javax.swing.JRadioButtonMenuItem) JOptionPane(javax.swing.JOptionPane) JCheckBoxMenuItem(javax.swing.JCheckBoxMenuItem) ChangeTiltAngleForAllRacksCommand(org.concord.energy3d.undo.ChangeTiltAngleForAllRacksCommand) ActionListener(java.awt.event.ActionListener) ChangeSolarPanelModelForAllRacksCommand(org.concord.energy3d.undo.ChangeSolarPanelModelForAllRacksCommand) ChangeMonthlyTiltAnglesForAllRacksCommand(org.concord.energy3d.undo.ChangeMonthlyTiltAnglesForAllRacksCommand) SpringLayout(javax.swing.SpringLayout) ChangeFoundationSolarCollectorBaseHeightCommand(org.concord.energy3d.undo.ChangeFoundationSolarCollectorBaseHeightCommand) SetTemperatureCoefficientPmaxForRackCommand(org.concord.energy3d.undo.SetTemperatureCoefficientPmaxForRackCommand) JDialog(javax.swing.JDialog) SetSolarPanelCellTypeForRackCommand(org.concord.energy3d.undo.SetSolarPanelCellTypeForRackCommand) JRadioButton(javax.swing.JRadioButton) SetSolarPanelColorForRacksOnFoundationCommand(org.concord.energy3d.undo.SetSolarPanelColorForRacksOnFoundationCommand) BoxLayout(javax.swing.BoxLayout) SetInverterEfficiencyForRackCommand(org.concord.energy3d.undo.SetInverterEfficiencyForRackCommand) JTextField(javax.swing.JTextField) Rack(org.concord.energy3d.model.Rack) Roof(org.concord.energy3d.model.Roof) SetSolarCellEfficiencyForAllRacksCommand(org.concord.energy3d.undo.SetSolarCellEfficiencyForAllRacksCommand) RotateSolarPanelsForRacksOnFoundationCommand(org.concord.energy3d.undo.RotateSolarPanelsForRacksOnFoundationCommand) JMenuItem(javax.swing.JMenuItem) SetSolarCellEfficiencyForRackCommand(org.concord.energy3d.undo.SetSolarCellEfficiencyForRackCommand) SetSizeForRacksOnFoundationCommand(org.concord.energy3d.undo.SetSizeForRacksOnFoundationCommand) ChangeSolarPanelModelForRacksOnFoundationCommand(org.concord.energy3d.undo.ChangeSolarPanelModelForRacksOnFoundationCommand) LockEditPointsCommand(org.concord.energy3d.undo.LockEditPointsCommand) JComboBox(javax.swing.JComboBox) ChangeAzimuthForAllRacksCommand(org.concord.energy3d.undo.ChangeAzimuthForAllRacksCommand) SetNoctForAllRacksCommand(org.concord.energy3d.undo.SetNoctForAllRacksCommand) SetTemperatureCoefficientPmaxForAllRacksCommand(org.concord.energy3d.undo.SetTemperatureCoefficientPmaxForAllRacksCommand) ChangeTiltAngleCommand(org.concord.energy3d.undo.ChangeTiltAngleCommand) ChangeBaseHeightCommand(org.concord.energy3d.undo.ChangeBaseHeightCommand) SetNoctForRackCommand(org.concord.energy3d.undo.SetNoctForRackCommand) LockEditPointsForClassCommand(org.concord.energy3d.undo.LockEditPointsForClassCommand) SetSizeForAllRacksCommand(org.concord.energy3d.undo.SetSizeForAllRacksCommand) ChangeRackPoleSettingsCommand(org.concord.energy3d.undo.ChangeRackPoleSettingsCommand) JMenu(javax.swing.JMenu)

Example 12 with SpringLayout

use of javax.swing.SpringLayout in project energy3d by concord-consortium.

the class PopupMenuForSky method getPopupMenu.

static JPopupMenu getPopupMenu() {
    if (popupMenuForSky == null) {
        final JMenuItem miInfo = new JMenuItem("Sky");
        miInfo.setEnabled(false);
        miInfo.setOpaque(true);
        miInfo.setBackground(Config.isMac() ? Color.DARK_GRAY : Color.GRAY);
        miInfo.setForeground(Color.WHITE);
        final JCheckBoxMenuItem miHeliodon = new JCheckBoxMenuItem("Heliodon");
        miHeliodon.addItemListener(new ItemListener() {

            @Override
            public void itemStateChanged(final ItemEvent e) {
                MainPanel.getInstance().getHeliodonButton().doClick();
            }
        });
        final JMenu weatherMenu = new JMenu("Weather");
        JMenuItem mi = new JMenuItem("Monthly Sunshine Hours...");
        mi.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(final ActionEvent e) {
                if (EnergyPanel.getInstance().checkCity()) {
                    new MonthlySunshineHours().showDialog();
                }
            }
        });
        weatherMenu.add(mi);
        mi = new JMenuItem("Annual Environmental Temperature...");
        mi.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(final ActionEvent e) {
                if (EnergyPanel.getInstance().checkCity()) {
                    new AnnualEnvironmentalTemperature().showDialog();
                }
            }
        });
        weatherMenu.add(mi);
        mi = new JMenuItem("Daily Environmental Temperature...");
        mi.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(final ActionEvent e) {
                if (EnergyPanel.getInstance().checkCity()) {
                    new DailyEnvironmentalTemperature().showDialog();
                }
            }
        });
        weatherMenu.add(mi);
        final JMenu themeMenu = new JMenu("Theme");
        final ButtonGroup themeButtonGroup = new ButtonGroup();
        final JRadioButtonMenuItem miBlueSky = new JRadioButtonMenuItem("Blue Sky");
        miBlueSky.addItemListener(new ItemListener() {

            @Override
            public void itemStateChanged(final ItemEvent e) {
                if (e.getStateChange() == ItemEvent.SELECTED) {
                    final ChangeThemeCommand c = new ChangeThemeCommand();
                    Scene.getInstance().setTheme(Scene.BLUE_SKY_THEME);
                    Scene.getInstance().setEdited(true);
                    SceneManager.getInstance().getUndoManager().addEdit(c);
                }
            }
        });
        themeButtonGroup.add(miBlueSky);
        themeMenu.add(miBlueSky);
        final JRadioButtonMenuItem miDesert = new JRadioButtonMenuItem("Desert");
        miDesert.addItemListener(new ItemListener() {

            @Override
            public void itemStateChanged(final ItemEvent e) {
                if (e.getStateChange() == ItemEvent.SELECTED) {
                    final ChangeThemeCommand c = new ChangeThemeCommand();
                    Scene.getInstance().setTheme(Scene.DESERT_THEME);
                    Scene.getInstance().setEdited(true);
                    SceneManager.getInstance().getUndoManager().addEdit(c);
                }
            }
        });
        themeButtonGroup.add(miDesert);
        themeMenu.add(miDesert);
        final JRadioButtonMenuItem miGrassland = new JRadioButtonMenuItem("Grassland");
        miGrassland.addItemListener(new ItemListener() {

            @Override
            public void itemStateChanged(final ItemEvent e) {
                if (e.getStateChange() == ItemEvent.SELECTED) {
                    final ChangeThemeCommand c = new ChangeThemeCommand();
                    Scene.getInstance().setTheme(Scene.GRASSLAND_THEME);
                    Scene.getInstance().setEdited(true);
                    SceneManager.getInstance().getUndoManager().addEdit(c);
                }
            }
        });
        themeButtonGroup.add(miGrassland);
        themeMenu.add(miGrassland);
        final JRadioButtonMenuItem miForest = new JRadioButtonMenuItem("Forest");
        miForest.addItemListener(new ItemListener() {

            @Override
            public void itemStateChanged(final ItemEvent e) {
                if (e.getStateChange() == ItemEvent.SELECTED) {
                    final ChangeThemeCommand c = new ChangeThemeCommand();
                    Scene.getInstance().setTheme(Scene.FOREST_THEME);
                    Scene.getInstance().setEdited(true);
                    SceneManager.getInstance().getUndoManager().addEdit(c);
                }
            }
        });
        themeButtonGroup.add(miForest);
        themeMenu.add(miForest);
        themeMenu.addMenuListener(new MenuListener() {

            @Override
            public void menuCanceled(final MenuEvent e) {
            }

            @Override
            public void menuDeselected(final MenuEvent e) {
                SceneManager.getInstance().refresh();
            }

            @Override
            public void menuSelected(final MenuEvent e) {
                Util.selectSilently(miBlueSky, Scene.getInstance().getTheme() == Scene.BLUE_SKY_THEME);
                Util.selectSilently(miDesert, Scene.getInstance().getTheme() == Scene.DESERT_THEME);
                Util.selectSilently(miGrassland, Scene.getInstance().getTheme() == Scene.GRASSLAND_THEME);
                Util.selectSilently(miForest, Scene.getInstance().getTheme() == Scene.FOREST_THEME);
            }
        });
        final JMenuItem miDustLoss = new JMenuItem("Dust & Pollen...");
        miDustLoss.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(final ActionEvent e) {
                final JPanel gui = new JPanel(new BorderLayout());
                final String title = "<html><b>Soiling loss factor:</b><br>Loss of productivity due to atmospheric dust and pollen<br>(a dimensionless parameter within [0, 1])</html>";
                gui.add(new JLabel(title), BorderLayout.NORTH);
                final JPanel inputPanel = new JPanel(new SpringLayout());
                inputPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
                gui.add(inputPanel, BorderLayout.CENTER);
                final JTextField[] fields = new JTextField[12];
                for (int i = 0; i < 12; i++) {
                    final JLabel l = new JLabel(AnnualGraph.THREE_LETTER_MONTH[i] + ": ", JLabel.LEFT);
                    inputPanel.add(l);
                    fields[i] = new JTextField(threeDecimalsFormat.format(Scene.getInstance().getAtmosphere().getDustLoss(i)), 5);
                    l.setLabelFor(fields[i]);
                    inputPanel.add(fields[i]);
                }
                SpringUtilities.makeCompactGrid(inputPanel, 12, 2, 6, 6, 6, 6);
                while (true) {
                    if (JOptionPane.showConfirmDialog(MainFrame.getInstance(), gui, "Dust and pollen loss", JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE) == JOptionPane.CANCEL_OPTION) {
                        break;
                    }
                    boolean pass = true;
                    final double[] val = new double[12];
                    for (int i = 0; i < 12; i++) {
                        try {
                            val[i] = Double.parseDouble(fields[i].getText());
                            if (val[i] < 0 || val[i] > 1) {
                                JOptionPane.showMessageDialog(MainFrame.getInstance(), "Dust and pollen loss value must be in 0-1.", "Range Error", JOptionPane.ERROR_MESSAGE);
                                pass = false;
                            }
                        } catch (final NumberFormatException exception) {
                            JOptionPane.showMessageDialog(MainFrame.getInstance(), fields[i].getText() + " is an invalid value!", "Error", JOptionPane.ERROR_MESSAGE);
                            pass = false;
                        }
                    }
                    if (pass) {
                        boolean changed = false;
                        for (int i = 0; i < 12; i++) {
                            if (Math.abs(Scene.getInstance().getAtmosphere().getDustLoss(i) - val[i]) > 0.000001) {
                                changed = true;
                                break;
                            }
                        }
                        if (changed) {
                            final ChangeAtmosphericDustLossCommand c = new ChangeAtmosphericDustLossCommand();
                            for (int i = 0; i < 12; i++) {
                                Scene.getInstance().getAtmosphere().setDustLoss(val[i], i);
                            }
                            updateAfterEdit();
                            SceneManager.getInstance().getUndoManager().addEdit(c);
                        }
                        break;
                    }
                }
            }
        });
        popupMenuForSky = new JPopupMenu();
        popupMenuForSky.setInvoker(MainPanel.getInstance().getCanvasPanel());
        popupMenuForSky.addPopupMenuListener(new PopupMenuListener() {

            @Override
            public void popupMenuWillBecomeVisible(final PopupMenuEvent e) {
                Util.selectSilently(miHeliodon, MainPanel.getInstance().getHeliodonButton().isSelected());
            }

            @Override
            public void popupMenuWillBecomeInvisible(final PopupMenuEvent e) {
            }

            @Override
            public void popupMenuCanceled(final PopupMenuEvent e) {
            }
        });
        popupMenuForSky.add(miInfo);
        popupMenuForSky.add(miDustLoss);
        popupMenuForSky.add(miHeliodon);
        popupMenuForSky.addSeparator();
        popupMenuForSky.add(weatherMenu);
        popupMenuForSky.add(themeMenu);
    }
    return popupMenuForSky;
}
Also used : JPanel(javax.swing.JPanel) ItemEvent(java.awt.event.ItemEvent) ActionEvent(java.awt.event.ActionEvent) PopupMenuListener(javax.swing.event.PopupMenuListener) MenuListener(javax.swing.event.MenuListener) PopupMenuListener(javax.swing.event.PopupMenuListener) JTextField(javax.swing.JTextField) PopupMenuEvent(javax.swing.event.PopupMenuEvent) ChangeAtmosphericDustLossCommand(org.concord.energy3d.undo.ChangeAtmosphericDustLossCommand) BorderLayout(java.awt.BorderLayout) JMenuItem(javax.swing.JMenuItem) ChangeThemeCommand(org.concord.energy3d.undo.ChangeThemeCommand) PopupMenuEvent(javax.swing.event.PopupMenuEvent) MenuEvent(javax.swing.event.MenuEvent) AnnualEnvironmentalTemperature(org.concord.energy3d.simulation.AnnualEnvironmentalTemperature) MonthlySunshineHours(org.concord.energy3d.simulation.MonthlySunshineHours) JRadioButtonMenuItem(javax.swing.JRadioButtonMenuItem) JLabel(javax.swing.JLabel) JCheckBoxMenuItem(javax.swing.JCheckBoxMenuItem) DailyEnvironmentalTemperature(org.concord.energy3d.simulation.DailyEnvironmentalTemperature) JPopupMenu(javax.swing.JPopupMenu) ActionListener(java.awt.event.ActionListener) ButtonGroup(javax.swing.ButtonGroup) SpringLayout(javax.swing.SpringLayout) ItemListener(java.awt.event.ItemListener) JMenu(javax.swing.JMenu)

Example 13 with SpringLayout

use of javax.swing.SpringLayout in project energy3d by concord-consortium.

the class PopupMenuForSolarPanel method getPopupMenu.

static JPopupMenu getPopupMenu() {
    if (popupMenuForSolarPanel == null) {
        final JMenu trackerMenu = new JMenu("Tracker");
        final JMenu shadeToleranceMenu = new JMenu("Shade Tolerance");
        final ButtonGroup shadeToleranceButtonGroup = new ButtonGroup();
        final JRadioButtonMenuItem miHighTolerance = new JRadioButtonMenuItem("High Tolerance...");
        shadeToleranceButtonGroup.add(miHighTolerance);
        miHighTolerance.addActionListener(new ActionListener() {

            // remember the scope selection as the next action will likely be applied to the same scope
            private int selectedScopeIndex = 0;

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (!(selectedPart instanceof SolarPanel)) {
                    return;
                }
                final SolarPanel sp = (SolarPanel) selectedPart;
                final String partInfo = sp.toString().substring(0, sp.toString().indexOf(')') + 1);
                final JPanel panel = new JPanel();
                panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
                panel.setBorder(BorderFactory.createTitledBorder("Apply to:"));
                final JRadioButton rb1 = new JRadioButton("Only this Solar Panel", true);
                final JRadioButton rb2 = new JRadioButton("All Solar Panels on this Foundation");
                final JRadioButton rb3 = new JRadioButton("All Solar Panels");
                panel.add(rb1);
                panel.add(rb2);
                panel.add(rb3);
                final ButtonGroup bg = new ButtonGroup();
                bg.add(rb1);
                bg.add(rb2);
                bg.add(rb3);
                switch(selectedScopeIndex) {
                    case 0:
                        rb1.setSelected(true);
                        break;
                    case 1:
                        rb2.setSelected(true);
                        break;
                    case 2:
                        rb3.setSelected(true);
                        break;
                }
                final String title = "<html>Choose shade tolerance level for " + partInfo + "</html>";
                final String footnote = "<html><hr><font size=2>The energy generated by this panel comes from each cell proportionally (ideal case).<hr></html>";
                final Object[] options = new Object[] { "OK", "Cancel", "Apply" };
                final JOptionPane optionPane = new JOptionPane(new Object[] { title, footnote, panel }, JOptionPane.QUESTION_MESSAGE, JOptionPane.YES_NO_CANCEL_OPTION, null, options, options[2]);
                final JDialog dialog = optionPane.createDialog(MainFrame.getInstance(), "High Shade Tolerance");
                while (true) {
                    dialog.setVisible(true);
                    final Object choice = optionPane.getValue();
                    if (choice == options[1] || choice == null) {
                        break;
                    } else {
                        boolean changed = sp.getShadeTolerance() != SolarPanel.HIGH_SHADE_TOLERANCE;
                        if (rb1.isSelected()) {
                            if (changed) {
                                final SetShadeToleranceCommand c = new SetShadeToleranceCommand(sp);
                                sp.setShadeTolerance(SolarPanel.HIGH_SHADE_TOLERANCE);
                                sp.draw();
                                SceneManager.getInstance().getUndoManager().addEdit(c);
                            }
                            selectedScopeIndex = 0;
                        } else if (rb2.isSelected()) {
                            final Foundation foundation = sp.getTopContainer();
                            if (!changed) {
                                for (final SolarPanel x : foundation.getSolarPanels()) {
                                    if (x.getShadeTolerance() != SolarPanel.HIGH_SHADE_TOLERANCE) {
                                        changed = true;
                                        break;
                                    }
                                }
                            }
                            if (changed) {
                                final SetShadeToleranceForSolarPanelsOnFoundationCommand c = new SetShadeToleranceForSolarPanelsOnFoundationCommand(foundation);
                                foundation.setShadeToleranceForSolarPanels(SolarPanel.HIGH_SHADE_TOLERANCE);
                                SceneManager.getInstance().getUndoManager().addEdit(c);
                            }
                            selectedScopeIndex = 1;
                        } else if (rb3.isSelected()) {
                            if (!changed) {
                                for (final SolarPanel x : Scene.getInstance().getAllSolarPanels()) {
                                    if (x.getShadeTolerance() != SolarPanel.HIGH_SHADE_TOLERANCE) {
                                        changed = true;
                                        break;
                                    }
                                }
                            }
                            if (changed) {
                                final SetShadeToleranceForAllSolarPanelsCommand c = new SetShadeToleranceForAllSolarPanelsCommand();
                                Scene.getInstance().setShadeToleranceForAllSolarPanels(SolarPanel.HIGH_SHADE_TOLERANCE);
                                SceneManager.getInstance().getUndoManager().addEdit(c);
                            }
                            selectedScopeIndex = 2;
                        }
                        if (changed) {
                            updateAfterEdit();
                        }
                        if (choice == options[0]) {
                            break;
                        }
                    }
                }
            }
        });
        final JRadioButtonMenuItem miPartialTolerance = new JRadioButtonMenuItem("Partial Tolerance...", true);
        shadeToleranceButtonGroup.add(miPartialTolerance);
        miPartialTolerance.addActionListener(new ActionListener() {

            // remember the scope selection as the next action will likely be applied to the same scope
            private int selectedScopeIndex = 0;

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (!(selectedPart instanceof SolarPanel)) {
                    return;
                }
                final SolarPanel sp = (SolarPanel) selectedPart;
                final String partInfo = sp.toString().substring(0, sp.toString().indexOf(')') + 1);
                final JPanel panel = new JPanel();
                panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
                panel.setBorder(BorderFactory.createTitledBorder("Apply to:"));
                final JRadioButton rb1 = new JRadioButton("Only this Solar Panel", true);
                final JRadioButton rb2 = new JRadioButton("All Solar Panels on this Foundation");
                final JRadioButton rb3 = new JRadioButton("All Solar Panels");
                panel.add(rb1);
                panel.add(rb2);
                panel.add(rb3);
                final ButtonGroup bg = new ButtonGroup();
                bg.add(rb1);
                bg.add(rb2);
                bg.add(rb3);
                switch(selectedScopeIndex) {
                    case 0:
                        rb1.setSelected(true);
                        break;
                    case 1:
                        rb2.setSelected(true);
                        break;
                    case 2:
                        rb3.setSelected(true);
                        break;
                }
                final String title = "<html>Choose shade tolerance level for " + partInfo + "</html>";
                final String footnote = "<html><hr><font size=2>Use bypass diodes to direct current under shading conditions.<hr></html>";
                final Object[] options = new Object[] { "OK", "Cancel", "Apply" };
                final JOptionPane optionPane = new JOptionPane(new Object[] { title, footnote, panel }, JOptionPane.QUESTION_MESSAGE, JOptionPane.YES_NO_CANCEL_OPTION, null, options, options[2]);
                final JDialog dialog = optionPane.createDialog(MainFrame.getInstance(), "Partial Shade Tolerance");
                while (true) {
                    dialog.setVisible(true);
                    final Object choice = optionPane.getValue();
                    if (choice == options[1] || choice == null) {
                        break;
                    } else {
                        boolean changed = sp.getShadeTolerance() != SolarPanel.PARTIAL_SHADE_TOLERANCE;
                        if (rb1.isSelected()) {
                            if (changed) {
                                final SetShadeToleranceCommand c = new SetShadeToleranceCommand(sp);
                                sp.setShadeTolerance(SolarPanel.PARTIAL_SHADE_TOLERANCE);
                                sp.draw();
                                SceneManager.getInstance().getUndoManager().addEdit(c);
                            }
                            selectedScopeIndex = 0;
                        } else if (rb2.isSelected()) {
                            final Foundation foundation = sp.getTopContainer();
                            if (!changed) {
                                for (final SolarPanel x : foundation.getSolarPanels()) {
                                    if (x.getShadeTolerance() != SolarPanel.PARTIAL_SHADE_TOLERANCE) {
                                        changed = true;
                                        break;
                                    }
                                }
                            }
                            if (changed) {
                                final SetShadeToleranceForSolarPanelsOnFoundationCommand c = new SetShadeToleranceForSolarPanelsOnFoundationCommand(foundation);
                                foundation.setShadeToleranceForSolarPanels(SolarPanel.PARTIAL_SHADE_TOLERANCE);
                                SceneManager.getInstance().getUndoManager().addEdit(c);
                            }
                            selectedScopeIndex = 1;
                        } else if (rb3.isSelected()) {
                            if (!changed) {
                                for (final SolarPanel x : Scene.getInstance().getAllSolarPanels()) {
                                    if (x.getShadeTolerance() != SolarPanel.PARTIAL_SHADE_TOLERANCE) {
                                        changed = true;
                                        break;
                                    }
                                }
                            }
                            if (changed) {
                                final SetShadeToleranceForAllSolarPanelsCommand c = new SetShadeToleranceForAllSolarPanelsCommand();
                                Scene.getInstance().setShadeToleranceForAllSolarPanels(SolarPanel.PARTIAL_SHADE_TOLERANCE);
                                SceneManager.getInstance().getUndoManager().addEdit(c);
                            }
                            selectedScopeIndex = 2;
                        }
                        if (changed) {
                            updateAfterEdit();
                        }
                        if (choice == options[0]) {
                            break;
                        }
                    }
                }
            }
        });
        final JRadioButtonMenuItem miNoTolerance = new JRadioButtonMenuItem("No Tolerance...");
        shadeToleranceButtonGroup.add(miNoTolerance);
        miNoTolerance.addActionListener(new ActionListener() {

            // remember the scope selection as the next action will likely be applied to the same scope
            private int selectedScopeIndex = 0;

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (!(selectedPart instanceof SolarPanel)) {
                    return;
                }
                final SolarPanel sp = (SolarPanel) selectedPart;
                final String partInfo = sp.toString().substring(0, sp.toString().indexOf(')') + 1);
                final JPanel panel = new JPanel();
                panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
                panel.setBorder(BorderFactory.createTitledBorder("Apply to:"));
                final JRadioButton rb1 = new JRadioButton("Only this Solar Panel", true);
                final JRadioButton rb2 = new JRadioButton("All Solar Panels on this Foundation");
                final JRadioButton rb3 = new JRadioButton("All Solar Panels");
                panel.add(rb1);
                panel.add(rb2);
                panel.add(rb3);
                final ButtonGroup bg = new ButtonGroup();
                bg.add(rb1);
                bg.add(rb2);
                bg.add(rb3);
                switch(selectedScopeIndex) {
                    case 0:
                        rb1.setSelected(true);
                        break;
                    case 1:
                        rb2.setSelected(true);
                        break;
                    case 2:
                        rb3.setSelected(true);
                        break;
                }
                final String title = "<html>Choose shade tolerance level for " + partInfo + "</html>";
                final String footnote = "<html><hr><font size=2>Shading greatly reduces the output of the entire panel.<hr></html>";
                final Object[] options = new Object[] { "OK", "Cancel", "Apply" };
                final JOptionPane optionPane = new JOptionPane(new Object[] { title, footnote, panel }, JOptionPane.QUESTION_MESSAGE, JOptionPane.YES_NO_CANCEL_OPTION, null, options, options[2]);
                final JDialog dialog = optionPane.createDialog(MainFrame.getInstance(), "No Shade Tolerance");
                while (true) {
                    dialog.setVisible(true);
                    final Object choice = optionPane.getValue();
                    if (choice == options[1] || choice == null) {
                        break;
                    } else {
                        boolean changed = sp.getShadeTolerance() != SolarPanel.NO_SHADE_TOLERANCE;
                        if (rb1.isSelected()) {
                            if (changed) {
                                final SetShadeToleranceCommand c = new SetShadeToleranceCommand(sp);
                                sp.setShadeTolerance(SolarPanel.NO_SHADE_TOLERANCE);
                                sp.draw();
                                SceneManager.getInstance().getUndoManager().addEdit(c);
                            }
                            selectedScopeIndex = 0;
                        } else if (rb2.isSelected()) {
                            final Foundation foundation = sp.getTopContainer();
                            if (!changed) {
                                for (final SolarPanel x : foundation.getSolarPanels()) {
                                    if (x.getShadeTolerance() != SolarPanel.NO_SHADE_TOLERANCE) {
                                        changed = true;
                                        break;
                                    }
                                }
                            }
                            if (changed) {
                                final SetShadeToleranceForSolarPanelsOnFoundationCommand c = new SetShadeToleranceForSolarPanelsOnFoundationCommand(foundation);
                                foundation.setShadeToleranceForSolarPanels(SolarPanel.NO_SHADE_TOLERANCE);
                                SceneManager.getInstance().getUndoManager().addEdit(c);
                            }
                            selectedScopeIndex = 1;
                        } else if (rb3.isSelected()) {
                            if (!changed) {
                                for (final SolarPanel x : Scene.getInstance().getAllSolarPanels()) {
                                    if (x.getShadeTolerance() != SolarPanel.NO_SHADE_TOLERANCE) {
                                        changed = true;
                                        break;
                                    }
                                }
                            }
                            if (changed) {
                                final SetShadeToleranceForAllSolarPanelsCommand c = new SetShadeToleranceForAllSolarPanelsCommand();
                                Scene.getInstance().setShadeToleranceForAllSolarPanels(SolarPanel.NO_SHADE_TOLERANCE);
                                SceneManager.getInstance().getUndoManager().addEdit(c);
                            }
                            selectedScopeIndex = 2;
                        }
                        if (changed) {
                            updateAfterEdit();
                        }
                        if (choice == options[0]) {
                            break;
                        }
                    }
                }
            }
        });
        final ButtonGroup trackerButtonGroup = new ButtonGroup();
        final JRadioButtonMenuItem miNoTracker = new JRadioButtonMenuItem("No Tracker...", true);
        trackerButtonGroup.add(miNoTracker);
        miNoTracker.addActionListener(new ActionListener() {

            // remember the scope selection as the next action will likely be applied to the same scope
            private int selectedScopeIndex = 0;

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (!(selectedPart instanceof SolarPanel)) {
                    return;
                }
                final SolarPanel sp = (SolarPanel) selectedPart;
                final String partInfo = sp.toString().substring(0, sp.toString().indexOf(')') + 1);
                final JPanel panel = new JPanel();
                panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
                panel.setBorder(BorderFactory.createTitledBorder("Apply to:"));
                final JRadioButton rb1 = new JRadioButton("Only this Solar Panel", true);
                final JRadioButton rb2 = new JRadioButton("All Solar Panels on this Foundation");
                final JRadioButton rb3 = new JRadioButton("All Solar Panels");
                panel.add(rb1);
                panel.add(rb2);
                panel.add(rb3);
                final ButtonGroup bg = new ButtonGroup();
                bg.add(rb1);
                bg.add(rb2);
                bg.add(rb3);
                switch(selectedScopeIndex) {
                    case 0:
                        rb1.setSelected(true);
                        break;
                    case 1:
                        rb2.setSelected(true);
                        break;
                    case 2:
                        rb3.setSelected(true);
                        break;
                }
                final String title = "<html>Remove tracker for " + partInfo + "</html>";
                final String footnote = "<html><hr><font size=2>No tracker will be used.<hr></html>";
                final Object[] options = new Object[] { "OK", "Cancel", "Apply" };
                final JOptionPane optionPane = new JOptionPane(new Object[] { title, footnote, panel }, JOptionPane.QUESTION_MESSAGE, JOptionPane.YES_NO_CANCEL_OPTION, null, options, options[2]);
                final JDialog dialog = optionPane.createDialog(MainFrame.getInstance(), "No Tracker");
                while (true) {
                    dialog.setVisible(true);
                    final Object choice = optionPane.getValue();
                    if (choice == options[1] || choice == null) {
                        break;
                    } else {
                        boolean changed = sp.getTracker() != Trackable.NO_TRACKER;
                        if (rb1.isSelected()) {
                            if (changed) {
                                final SetSolarTrackerCommand c = new SetSolarTrackerCommand(sp, "No Tracker");
                                sp.setTracker(Trackable.NO_TRACKER);
                                sp.draw();
                                SceneManager.getInstance().refresh();
                                SceneManager.getInstance().getUndoManager().addEdit(c);
                            }
                            selectedScopeIndex = 0;
                        } else if (rb2.isSelected()) {
                            final Foundation foundation = sp.getTopContainer();
                            if (!changed) {
                                for (final SolarPanel x : foundation.getSolarPanels()) {
                                    if (x.getTracker() != Trackable.NO_TRACKER) {
                                        changed = true;
                                        break;
                                    }
                                }
                            }
                            if (changed) {
                                final SetSolarTrackersOnFoundationCommand c = new SetSolarTrackersOnFoundationCommand(foundation, sp, "No Tracker for All Solar Panels on Selected Foundation");
                                foundation.setTrackerForSolarPanels(Trackable.NO_TRACKER);
                                SceneManager.getInstance().getUndoManager().addEdit(c);
                            }
                            selectedScopeIndex = 1;
                        } else if (rb3.isSelected()) {
                            if (!changed) {
                                for (final SolarPanel x : Scene.getInstance().getAllSolarPanels()) {
                                    if (x.getTracker() != Trackable.NO_TRACKER) {
                                        changed = true;
                                        break;
                                    }
                                }
                            }
                            if (changed) {
                                final SetSolarTrackersForAllCommand c = new SetSolarTrackersForAllCommand(sp, "No Tracker for All Solar Panels");
                                Scene.getInstance().setTrackerForAllSolarPanels(Trackable.NO_TRACKER);
                                SceneManager.getInstance().getUndoManager().addEdit(c);
                            }
                            selectedScopeIndex = 2;
                        }
                        if (changed) {
                            updateAfterEdit();
                        }
                        if (choice == options[0]) {
                            break;
                        }
                    }
                }
            }
        });
        final JRadioButtonMenuItem miHorizontalSingleAxisTracker = new JRadioButtonMenuItem("Horizontal Single-Axis Tracker...");
        trackerButtonGroup.add(miHorizontalSingleAxisTracker);
        miHorizontalSingleAxisTracker.addActionListener(new ActionListener() {

            // remember the scope selection as the next action will likely be applied to the same scope
            private int selectedScopeIndex = 0;

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (!(selectedPart instanceof SolarPanel)) {
                    return;
                }
                final SolarPanel sp = (SolarPanel) selectedPart;
                final String partInfo = sp.toString().substring(0, sp.toString().indexOf(')') + 1);
                final JPanel panel = new JPanel();
                panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
                panel.setBorder(BorderFactory.createTitledBorder("Apply to:"));
                final JRadioButton rb1 = new JRadioButton("Only this Solar Panel", true);
                final JRadioButton rb2 = new JRadioButton("All Solar Panels on this Foundation");
                final JRadioButton rb3 = new JRadioButton("All Solar Panels");
                panel.add(rb1);
                panel.add(rb2);
                panel.add(rb3);
                final ButtonGroup bg = new ButtonGroup();
                bg.add(rb1);
                bg.add(rb2);
                bg.add(rb3);
                switch(selectedScopeIndex) {
                    case 0:
                        rb1.setSelected(true);
                        break;
                    case 1:
                        rb2.setSelected(true);
                        break;
                    case 2:
                        rb3.setSelected(true);
                        break;
                }
                final String title = "<html>Set horizontal single-axis tracker for " + partInfo + "</html>";
                final String footnote = "<html><hr><font size=2>A horizontal single-axis tracker (HSAT) rotates about the north-south axis<br>to follow the sun from east to west during the day.<hr></html>";
                final Object[] options = new Object[] { "OK", "Cancel", "Apply" };
                final JOptionPane optionPane = new JOptionPane(new Object[] { title, footnote, panel }, JOptionPane.QUESTION_MESSAGE, JOptionPane.YES_NO_CANCEL_OPTION, null, options, options[2]);
                final JDialog dialog = optionPane.createDialog(MainFrame.getInstance(), "Horizontal Single-Axis Tracker");
                while (true) {
                    dialog.setVisible(true);
                    final Object choice = optionPane.getValue();
                    if (choice == options[1] || choice == null) {
                        break;
                    } else {
                        boolean changed = sp.getTracker() != Trackable.HORIZONTAL_SINGLE_AXIS_TRACKER;
                        if (rb1.isSelected()) {
                            if (changed) {
                                final SetSolarTrackerCommand c = new SetSolarTrackerCommand(sp, "Horizontal Single-Axis Tracker");
                                sp.setTracker(SolarPanel.HORIZONTAL_SINGLE_AXIS_TRACKER);
                                sp.draw();
                                SceneManager.getInstance().refresh();
                                SceneManager.getInstance().getUndoManager().addEdit(c);
                            }
                            selectedScopeIndex = 0;
                        } else if (rb2.isSelected()) {
                            final Foundation foundation = sp.getTopContainer();
                            if (!changed) {
                                for (final SolarPanel x : foundation.getSolarPanels()) {
                                    if (x.getTracker() != Trackable.HORIZONTAL_SINGLE_AXIS_TRACKER) {
                                        changed = true;
                                        break;
                                    }
                                }
                            }
                            if (changed) {
                                final SetSolarTrackersOnFoundationCommand c = new SetSolarTrackersOnFoundationCommand(foundation, sp, "Horizontal Single-Axis Tracker for All Solar Panels on Selected Foundation");
                                foundation.setTrackerForSolarPanels(SolarPanel.HORIZONTAL_SINGLE_AXIS_TRACKER);
                                SceneManager.getInstance().getUndoManager().addEdit(c);
                            }
                            selectedScopeIndex = 1;
                        } else if (rb3.isSelected()) {
                            if (!changed) {
                                for (final SolarPanel x : Scene.getInstance().getAllSolarPanels()) {
                                    if (x.getTracker() != Trackable.HORIZONTAL_SINGLE_AXIS_TRACKER) {
                                        changed = true;
                                        break;
                                    }
                                }
                            }
                            if (changed) {
                                final SetSolarTrackersForAllCommand c = new SetSolarTrackersForAllCommand(sp, "Horizontal Single-Axis Tracker for All Solar Panels");
                                Scene.getInstance().setTrackerForAllSolarPanels(SolarPanel.HORIZONTAL_SINGLE_AXIS_TRACKER);
                                SceneManager.getInstance().getUndoManager().addEdit(c);
                            }
                            selectedScopeIndex = 2;
                        }
                        if (changed) {
                            updateAfterEdit();
                        }
                        if (choice == options[0]) {
                            break;
                        }
                    }
                }
            }
        });
        final JRadioButtonMenuItem miVerticalSingleAxisTracker = new JRadioButtonMenuItem("Vertical Single-Axis Tracker...");
        trackerButtonGroup.add(miVerticalSingleAxisTracker);
        miVerticalSingleAxisTracker.addActionListener(new ActionListener() {

            // remember the scope selection as the next action will likely be applied to the same scope
            private int selectedScopeIndex = 0;

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (!(selectedPart instanceof SolarPanel)) {
                    return;
                }
                final SolarPanel sp = (SolarPanel) selectedPart;
                final String partInfo = sp.toString().substring(0, sp.toString().indexOf(')') + 1);
                final JPanel panel = new JPanel();
                panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
                panel.setBorder(BorderFactory.createTitledBorder("Apply to:"));
                final JRadioButton rb1 = new JRadioButton("Only this Solar Panel", true);
                final JRadioButton rb2 = new JRadioButton("All Solar Panels on this Foundation");
                final JRadioButton rb3 = new JRadioButton("All Solar Panels");
                panel.add(rb1);
                panel.add(rb2);
                panel.add(rb3);
                final ButtonGroup bg = new ButtonGroup();
                bg.add(rb1);
                bg.add(rb2);
                bg.add(rb3);
                switch(selectedScopeIndex) {
                    case 0:
                        rb1.setSelected(true);
                        break;
                    case 1:
                        rb2.setSelected(true);
                        break;
                    case 2:
                        rb3.setSelected(true);
                        break;
                }
                final String title = "<html>Set vertical single-axis tracker for " + partInfo + "</html>";
                final String footnote = "<html><hr><font size=2>A vertical single-axis tracker (VSAT) rotates about an axis perpendicular to the ground<br>and follow the sun from east to west during the day.<hr></html>";
                final Object[] options = new Object[] { "OK", "Cancel", "Apply" };
                final JOptionPane optionPane = new JOptionPane(new Object[] { title, footnote, panel }, JOptionPane.QUESTION_MESSAGE, JOptionPane.YES_NO_CANCEL_OPTION, null, options, options[2]);
                final JDialog dialog = optionPane.createDialog(MainFrame.getInstance(), "Vertical Single-Axis Tracker");
                while (true) {
                    dialog.setVisible(true);
                    final Object choice = optionPane.getValue();
                    if (choice == options[1] || choice == null) {
                        break;
                    } else {
                        boolean changed = sp.getTracker() != Trackable.VERTICAL_SINGLE_AXIS_TRACKER;
                        if (rb1.isSelected()) {
                            if (changed) {
                                final SetSolarTrackerCommand c = new SetSolarTrackerCommand(sp, "Vertical Single-Axis Tracker");
                                sp.setTracker(SolarPanel.VERTICAL_SINGLE_AXIS_TRACKER);
                                sp.draw();
                                SceneManager.getInstance().refresh();
                                SceneManager.getInstance().getUndoManager().addEdit(c);
                            }
                            selectedScopeIndex = 0;
                        } else if (rb2.isSelected()) {
                            final Foundation foundation = sp.getTopContainer();
                            if (!changed) {
                                for (final SolarPanel x : foundation.getSolarPanels()) {
                                    if (x.getTracker() != Trackable.VERTICAL_SINGLE_AXIS_TRACKER) {
                                        changed = true;
                                        break;
                                    }
                                }
                            }
                            if (changed) {
                                final SetSolarTrackersOnFoundationCommand c = new SetSolarTrackersOnFoundationCommand(foundation, sp, "Vertical Single-Axis Tracker for All Solar Panels on Selected Foundation");
                                foundation.setTrackerForSolarPanels(SolarPanel.VERTICAL_SINGLE_AXIS_TRACKER);
                                SceneManager.getInstance().getUndoManager().addEdit(c);
                            }
                            selectedScopeIndex = 1;
                        } else if (rb3.isSelected()) {
                            if (!changed) {
                                for (final SolarPanel x : Scene.getInstance().getAllSolarPanels()) {
                                    if (x.getTracker() != Trackable.VERTICAL_SINGLE_AXIS_TRACKER) {
                                        changed = true;
                                        break;
                                    }
                                }
                            }
                            if (changed) {
                                final SetSolarTrackersForAllCommand c = new SetSolarTrackersForAllCommand(sp, "Vertical Single-Axis Tracker for All Solar Panels");
                                Scene.getInstance().setTrackerForAllSolarPanels(SolarPanel.VERTICAL_SINGLE_AXIS_TRACKER);
                                SceneManager.getInstance().getUndoManager().addEdit(c);
                            }
                            selectedScopeIndex = 2;
                        }
                        if (changed) {
                            updateAfterEdit();
                        }
                        if (choice == options[0]) {
                            break;
                        }
                    }
                }
            }
        });
        final JRadioButtonMenuItem miAltazimuthDualAxisTracker = new JRadioButtonMenuItem("Altazimuth Dual-Axis Tracker...");
        trackerButtonGroup.add(miAltazimuthDualAxisTracker);
        miAltazimuthDualAxisTracker.addActionListener(new ActionListener() {

            // remember the scope selection as the next action will likely be applied to the same scope
            private int selectedScopeIndex = 0;

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (!(selectedPart instanceof SolarPanel)) {
                    return;
                }
                final SolarPanel sp = (SolarPanel) selectedPart;
                final String partInfo = sp.toString().substring(0, sp.toString().indexOf(')') + 1);
                final JPanel panel = new JPanel();
                panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
                panel.setBorder(BorderFactory.createTitledBorder("Apply to:"));
                final JRadioButton rb1 = new JRadioButton("Only this Solar Panel", true);
                final JRadioButton rb2 = new JRadioButton("All Solar Panels on this Foundation");
                final JRadioButton rb3 = new JRadioButton("All Solar Panels");
                panel.add(rb1);
                panel.add(rb2);
                panel.add(rb3);
                final ButtonGroup bg = new ButtonGroup();
                bg.add(rb1);
                bg.add(rb2);
                bg.add(rb3);
                switch(selectedScopeIndex) {
                    case 0:
                        rb1.setSelected(true);
                        break;
                    case 1:
                        rb2.setSelected(true);
                        break;
                    case 2:
                        rb3.setSelected(true);
                        break;
                }
                final String title = "<html>Set altitude-azimuth dual-axis tracker for " + partInfo + "</html>";
                final String footnote = "<html><hr><font size=2>The Alt/Az dual-axis solar tracker will rotate the solar panel to face the sun<br>all the time during the day.<hr></html>";
                final Object[] options = new Object[] { "OK", "Cancel", "Apply" };
                final JOptionPane optionPane = new JOptionPane(new Object[] { title, footnote, panel }, JOptionPane.QUESTION_MESSAGE, JOptionPane.YES_NO_CANCEL_OPTION, null, options, options[2]);
                final JDialog dialog = optionPane.createDialog(MainFrame.getInstance(), "Altitude-Azimuth Dual-Axis Tracker");
                while (true) {
                    dialog.setVisible(true);
                    final Object choice = optionPane.getValue();
                    if (choice == options[1] || choice == null) {
                        break;
                    } else {
                        boolean changed = sp.getTracker() != Trackable.ALTAZIMUTH_DUAL_AXIS_TRACKER;
                        if (rb1.isSelected()) {
                            if (changed) {
                                final SetSolarTrackerCommand c = new SetSolarTrackerCommand(sp, "Dual-Axis Tracker");
                                sp.setTracker(SolarPanel.ALTAZIMUTH_DUAL_AXIS_TRACKER);
                                sp.draw();
                                SceneManager.getInstance().refresh();
                                SceneManager.getInstance().getUndoManager().addEdit(c);
                            }
                            selectedScopeIndex = 0;
                        } else if (rb2.isSelected()) {
                            final Foundation foundation = sp.getTopContainer();
                            if (!changed) {
                                for (final SolarPanel x : foundation.getSolarPanels()) {
                                    if (x.getTracker() != Trackable.ALTAZIMUTH_DUAL_AXIS_TRACKER) {
                                        changed = true;
                                        break;
                                    }
                                }
                            }
                            if (changed) {
                                final SetSolarTrackersOnFoundationCommand c = new SetSolarTrackersOnFoundationCommand(foundation, sp, "Dual-Axis Tracker for All Solar Panels on Selected Foundation");
                                foundation.setTrackerForSolarPanels(SolarPanel.ALTAZIMUTH_DUAL_AXIS_TRACKER);
                                SceneManager.getInstance().getUndoManager().addEdit(c);
                            }
                            selectedScopeIndex = 1;
                        } else if (rb3.isSelected()) {
                            if (!changed) {
                                for (final SolarPanel x : Scene.getInstance().getAllSolarPanels()) {
                                    if (x.getTracker() != Trackable.ALTAZIMUTH_DUAL_AXIS_TRACKER) {
                                        changed = true;
                                        break;
                                    }
                                }
                            }
                            if (changed) {
                                final SetSolarTrackersForAllCommand c = new SetSolarTrackersForAllCommand(sp, "Dual-Axis Tracker for All Solar Panels");
                                Scene.getInstance().setTrackerForAllSolarPanels(SolarPanel.ALTAZIMUTH_DUAL_AXIS_TRACKER);
                                SceneManager.getInstance().getUndoManager().addEdit(c);
                            }
                            selectedScopeIndex = 2;
                        }
                        if (changed) {
                            updateAfterEdit();
                        }
                        if (choice == options[0]) {
                            break;
                        }
                    }
                }
            }
        });
        final JMenu orientationMenu = new JMenu("Orientation");
        final ButtonGroup orientationGroup = new ButtonGroup();
        final JRadioButtonMenuItem rbmiLandscape = new JRadioButtonMenuItem("Landscape");
        rbmiLandscape.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(final ActionEvent e) {
                if (rbmiLandscape.isSelected()) {
                    final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                    if (!(selectedPart instanceof SolarPanel)) {
                        return;
                    }
                    final SolarPanel s = (SolarPanel) selectedPart;
                    if (!s.isRotated()) {
                        final RotateSolarPanelCommand c = new RotateSolarPanelCommand(s);
                        s.setRotated(true);
                        SceneManager.getInstance().getUndoManager().addEdit(c);
                        s.draw();
                        SceneManager.getInstance().refresh();
                        updateAfterEdit();
                    }
                }
            }
        });
        orientationMenu.add(rbmiLandscape);
        orientationGroup.add(rbmiLandscape);
        final JRadioButtonMenuItem rbmiPortrait = new JRadioButtonMenuItem("Portrait", true);
        rbmiPortrait.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(final ActionEvent e) {
                if (rbmiPortrait.isSelected()) {
                    final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                    if (!(selectedPart instanceof SolarPanel)) {
                        return;
                    }
                    final SolarPanel s = (SolarPanel) selectedPart;
                    if (s.isRotated()) {
                        final RotateSolarPanelCommand c = new RotateSolarPanelCommand(s);
                        s.setRotated(false);
                        SceneManager.getInstance().getUndoManager().addEdit(c);
                        s.draw();
                        SceneManager.getInstance().refresh();
                        updateAfterEdit();
                    }
                }
            }
        });
        orientationMenu.add(rbmiPortrait);
        orientationGroup.add(rbmiPortrait);
        final JMenu labelMenu = new JMenu("Label");
        final JCheckBoxMenuItem miLabelNone = new JCheckBoxMenuItem("None", true);
        miLabelNone.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(final ActionEvent e) {
                if (miLabelNone.isSelected()) {
                    final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                    if (selectedPart instanceof SolarPanel) {
                        final SolarPanel s = (SolarPanel) selectedPart;
                        final SetSolarPanelLabelCommand c = new SetSolarPanelLabelCommand(s);
                        s.clearLabels();
                        s.draw();
                        SceneManager.getInstance().getUndoManager().addEdit(c);
                        Scene.getInstance().setEdited(true);
                        SceneManager.getInstance().refresh();
                    }
                }
            }
        });
        labelMenu.add(miLabelNone);
        final JCheckBoxMenuItem miLabelCustom = new JCheckBoxMenuItem("Custom");
        miLabelCustom.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (selectedPart instanceof SolarPanel) {
                    final SolarPanel s = (SolarPanel) selectedPart;
                    final SetSolarPanelLabelCommand c = new SetSolarPanelLabelCommand(s);
                    s.setLabelCustom(miLabelCustom.isSelected());
                    if (s.getLabelCustom()) {
                        s.setLabelCustomText(JOptionPane.showInputDialog(MainFrame.getInstance(), "Custom Text", s.getLabelCustomText()));
                    }
                    s.draw();
                    SceneManager.getInstance().getUndoManager().addEdit(c);
                    Scene.getInstance().setEdited(true);
                    SceneManager.getInstance().refresh();
                }
            }
        });
        labelMenu.add(miLabelCustom);
        final JCheckBoxMenuItem miLabelId = new JCheckBoxMenuItem("ID");
        miLabelId.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (selectedPart instanceof SolarPanel) {
                    final SolarPanel s = (SolarPanel) selectedPart;
                    final SetSolarPanelLabelCommand c = new SetSolarPanelLabelCommand(s);
                    s.setLabelId(miLabelId.isSelected());
                    s.draw();
                    SceneManager.getInstance().getUndoManager().addEdit(c);
                    Scene.getInstance().setEdited(true);
                    SceneManager.getInstance().refresh();
                }
            }
        });
        labelMenu.add(miLabelId);
        final JCheckBoxMenuItem miLabelModelName = new JCheckBoxMenuItem("Model");
        miLabelModelName.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (selectedPart instanceof SolarPanel) {
                    final SolarPanel s = (SolarPanel) selectedPart;
                    final SetSolarPanelLabelCommand c = new SetSolarPanelLabelCommand(s);
                    s.setLabelModelName(miLabelModelName.isSelected());
                    s.draw();
                    SceneManager.getInstance().getUndoManager().addEdit(c);
                    Scene.getInstance().setEdited(true);
                    SceneManager.getInstance().refresh();
                }
            }
        });
        labelMenu.add(miLabelModelName);
        final JCheckBoxMenuItem miLabelCellEfficiency = new JCheckBoxMenuItem("Cell Efficiency");
        miLabelCellEfficiency.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (selectedPart instanceof SolarPanel) {
                    final SolarPanel s = (SolarPanel) selectedPart;
                    final SetSolarPanelLabelCommand c = new SetSolarPanelLabelCommand(s);
                    s.setLabelCellEfficiency(miLabelCellEfficiency.isSelected());
                    s.draw();
                    SceneManager.getInstance().getUndoManager().addEdit(c);
                    Scene.getInstance().setEdited(true);
                    SceneManager.getInstance().refresh();
                }
            }
        });
        labelMenu.add(miLabelCellEfficiency);
        final JCheckBoxMenuItem miLabelTiltAngle = new JCheckBoxMenuItem("Tilt Angle");
        miLabelTiltAngle.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (selectedPart instanceof SolarPanel) {
                    final SolarPanel s = (SolarPanel) selectedPart;
                    final SetSolarPanelLabelCommand c = new SetSolarPanelLabelCommand(s);
                    s.setLabelTiltAngle(miLabelTiltAngle.isSelected());
                    s.draw();
                    SceneManager.getInstance().getUndoManager().addEdit(c);
                    Scene.getInstance().setEdited(true);
                    SceneManager.getInstance().refresh();
                }
            }
        });
        labelMenu.add(miLabelTiltAngle);
        final JCheckBoxMenuItem miLabelTracker = new JCheckBoxMenuItem("Tracker");
        miLabelTracker.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (selectedPart instanceof SolarPanel) {
                    final SolarPanel s = (SolarPanel) selectedPart;
                    final SetSolarPanelLabelCommand c = new SetSolarPanelLabelCommand(s);
                    s.setLabelTracker(miLabelTracker.isSelected());
                    s.draw();
                    SceneManager.getInstance().getUndoManager().addEdit(c);
                    Scene.getInstance().setEdited(true);
                    SceneManager.getInstance().refresh();
                }
            }
        });
        labelMenu.add(miLabelTracker);
        final JCheckBoxMenuItem miLabelEnergyOutput = new JCheckBoxMenuItem("Energy Output");
        miLabelEnergyOutput.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (selectedPart instanceof SolarPanel) {
                    final SolarPanel s = (SolarPanel) selectedPart;
                    final SetSolarPanelLabelCommand c = new SetSolarPanelLabelCommand(s);
                    s.setLabelEnergyOutput(miLabelEnergyOutput.isSelected());
                    s.draw();
                    SceneManager.getInstance().getUndoManager().addEdit(c);
                    Scene.getInstance().setEdited(true);
                    SceneManager.getInstance().refresh();
                }
            }
        });
        labelMenu.add(miLabelEnergyOutput);
        final JMenuItem miTiltAngle = new JMenuItem("Tilt Angle...");
        miTiltAngle.addActionListener(new ActionListener() {

            // remember the scope selection as the next action will likely be applied to the same scope
            private int selectedScopeIndex = 0;

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (!(selectedPart instanceof SolarPanel)) {
                    return;
                }
                final String partInfo = selectedPart.toString().substring(0, selectedPart.toString().indexOf(')') + 1);
                final SolarPanel sp = (SolarPanel) selectedPart;
                final String title = "<html>Tilt Angle of " + partInfo + " (&deg;)</html>";
                final String footnote = "<html><hr><font size=2>The tilt angle of a solar panel is the angle between its surface and the base surface.<br>The tilt angle must be between -90&deg; and 90&deg;.<hr></html>";
                final JPanel gui = new JPanel(new BorderLayout());
                final JPanel panel = new JPanel();
                gui.add(panel, BorderLayout.CENTER);
                panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
                panel.setBorder(BorderFactory.createTitledBorder("Apply to:"));
                final JRadioButton rb1 = new JRadioButton("Only this Solar Panel", true);
                final JRadioButton rb2 = new JRadioButton("Only this Row", true);
                final JRadioButton rb3 = new JRadioButton("All Solar Panels on This Foundation");
                final JRadioButton rb4 = new JRadioButton("All Solar Panels");
                panel.add(rb1);
                panel.add(rb2);
                panel.add(rb3);
                panel.add(rb4);
                final ButtonGroup bg = new ButtonGroup();
                bg.add(rb1);
                bg.add(rb2);
                bg.add(rb3);
                bg.add(rb4);
                switch(selectedScopeIndex) {
                    case 0:
                        rb1.setSelected(true);
                        break;
                    case 1:
                        rb2.setSelected(true);
                        break;
                    case 2:
                        rb3.setSelected(true);
                        break;
                    case 3:
                        rb4.setSelected(true);
                        break;
                }
                gui.add(panel, BorderLayout.CENTER);
                final JTextField inputField = new JTextField(EnergyPanel.TWO_DECIMALS.format(sp.getTiltAngle()));
                gui.add(inputField, BorderLayout.SOUTH);
                final Object[] options = new Object[] { "OK", "Cancel", "Apply" };
                final JOptionPane optionPane = new JOptionPane(new Object[] { title, footnote, gui }, JOptionPane.QUESTION_MESSAGE, JOptionPane.YES_NO_CANCEL_OPTION, null, options, options[2]);
                final JDialog dialog = optionPane.createDialog(MainFrame.getInstance(), "Solar Panel Tilt Angle");
                while (true) {
                    inputField.selectAll();
                    inputField.requestFocusInWindow();
                    dialog.setVisible(true);
                    final Object choice = optionPane.getValue();
                    if (choice == options[1] || choice == null) {
                        break;
                    } else {
                        double val = 0;
                        boolean ok = true;
                        try {
                            val = Double.parseDouble(inputField.getText());
                        } catch (final NumberFormatException exception) {
                            JOptionPane.showMessageDialog(MainFrame.getInstance(), inputField.getText() + " is an invalid value!", "Error", JOptionPane.ERROR_MESSAGE);
                            ok = false;
                        }
                        if (ok) {
                            if (val < -90 || val > 90) {
                                JOptionPane.showMessageDialog(MainFrame.getInstance(), "The tilt angle must be between -90 and 90 degrees.", "Range Error", JOptionPane.ERROR_MESSAGE);
                            } else {
                                if (Util.isZero(val - 90)) {
                                    val = 89.999;
                                } else if (Util.isZero(val + 90)) {
                                    val = -89.999;
                                }
                                boolean changed = Math.abs(val - sp.getTiltAngle()) > 0.000001;
                                if (rb1.isSelected()) {
                                    if (changed) {
                                        final ChangeTiltAngleCommand c = new ChangeTiltAngleCommand(sp);
                                        sp.setTiltAngle(val);
                                        sp.draw();
                                        if (sp.checkContainerIntersection()) {
                                            JOptionPane.showMessageDialog(MainFrame.getInstance(), "This tilt angle cannot be set as the solar panel would cut into the underlying surface.", "Illegal Tilt Angle", JOptionPane.ERROR_MESSAGE);
                                            c.undo();
                                        } else {
                                            SceneManager.getInstance().refresh();
                                            SceneManager.getInstance().getUndoManager().addEdit(c);
                                        }
                                    }
                                    selectedScopeIndex = 0;
                                } else if (rb2.isSelected()) {
                                    final List<SolarPanel> row = sp.getRow();
                                    if (!changed) {
                                        for (final SolarPanel x : row) {
                                            if (Math.abs(val - x.getTiltAngle()) > 0.000001) {
                                                changed = true;
                                                break;
                                            }
                                        }
                                    }
                                    if (changed) {
                                        final ChangeTiltAngleForSolarPanelRowCommand c = new ChangeTiltAngleForSolarPanelRowCommand(row);
                                        boolean intersected = false;
                                        for (final SolarPanel x : row) {
                                            x.setTiltAngle(val);
                                            x.draw();
                                            if (x.checkContainerIntersection()) {
                                                intersected = true;
                                                break;
                                            }
                                        }
                                        SceneManager.getInstance().refresh();
                                        if (intersected) {
                                            JOptionPane.showMessageDialog(MainFrame.getInstance(), "This tilt angle cannot be set as one or more solar panels would cut into the underlying surface.", "Illegal Tilt Angle", JOptionPane.ERROR_MESSAGE);
                                            c.undo();
                                        } else {
                                            SceneManager.getInstance().refresh();
                                            SceneManager.getInstance().getUndoManager().addEdit(c);
                                        }
                                    }
                                    selectedScopeIndex = 1;
                                } else if (rb3.isSelected()) {
                                    final Foundation foundation = sp.getTopContainer();
                                    if (!changed) {
                                        for (final SolarPanel x : foundation.getSolarPanels()) {
                                            if (Math.abs(val - x.getTiltAngle()) > 0.000001) {
                                                changed = true;
                                                break;
                                            }
                                        }
                                    }
                                    if (changed) {
                                        final ChangeFoundationSolarPanelTiltAngleCommand c = new ChangeFoundationSolarPanelTiltAngleCommand(foundation);
                                        foundation.setTiltAngleForSolarPanels(val);
                                        if (foundation.checkContainerIntersectionForSolarPanels()) {
                                            JOptionPane.showMessageDialog(MainFrame.getInstance(), "This tilt angle cannot be set as one or more solar panels would cut into the underlying surface.", "Illegal Tilt Angle", JOptionPane.ERROR_MESSAGE);
                                            c.undo();
                                        } else {
                                            SceneManager.getInstance().getUndoManager().addEdit(c);
                                        }
                                    }
                                    selectedScopeIndex = 2;
                                } else if (rb4.isSelected()) {
                                    if (!changed) {
                                        for (final SolarPanel x : Scene.getInstance().getAllSolarPanels()) {
                                            if (Math.abs(val - x.getTiltAngle()) > 0.000001) {
                                                changed = true;
                                                break;
                                            }
                                        }
                                    }
                                    if (changed) {
                                        final ChangeTiltAngleForAllSolarPanelsCommand c = new ChangeTiltAngleForAllSolarPanelsCommand();
                                        Scene.getInstance().setTiltAngleForAllSolarPanels(val);
                                        if (Scene.getInstance().checkContainerIntersectionForAllSolarPanels()) {
                                            JOptionPane.showMessageDialog(MainFrame.getInstance(), "This tilt angle cannot be set as one or more solar panels would cut into the underlying surface.", "Illegal Tilt Angle", JOptionPane.ERROR_MESSAGE);
                                            c.undo();
                                        } else {
                                            SceneManager.getInstance().getUndoManager().addEdit(c);
                                        }
                                    }
                                    selectedScopeIndex = 3;
                                }
                                if (changed) {
                                    updateAfterEdit();
                                }
                                if (choice == options[0]) {
                                    break;
                                }
                            }
                        }
                    }
                }
            }
        });
        final JMenuItem miAzimuth = new JMenuItem("Azimuth...");
        miAzimuth.addActionListener(new ActionListener() {

            // remember the scope selection as the next action will likely be applied to the same scope
            private int selectedScopeIndex = 0;

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (!(selectedPart instanceof SolarPanel)) {
                    return;
                }
                final String partInfo = selectedPart.toString().substring(0, selectedPart.toString().indexOf(')') + 1);
                final SolarPanel sp = (SolarPanel) selectedPart;
                final Foundation foundation = sp.getTopContainer();
                final String title = "<html>Azimuth Angle of " + partInfo + " (&deg;)</html>";
                final String footnote = "<html><hr><font size=2>The azimuth angle is measured clockwise from the true north.<hr></html>";
                final JPanel gui = new JPanel(new BorderLayout());
                final JPanel panel = new JPanel();
                gui.add(panel, BorderLayout.CENTER);
                panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
                panel.setBorder(BorderFactory.createTitledBorder("Apply to:"));
                final JRadioButton rb1 = new JRadioButton("Only this Solar Panel", true);
                final JRadioButton rb2 = new JRadioButton("All Solar Panels on this Foundation");
                final JRadioButton rb3 = new JRadioButton("All Solar Panels");
                panel.add(rb1);
                panel.add(rb2);
                panel.add(rb3);
                final ButtonGroup bg = new ButtonGroup();
                bg.add(rb1);
                bg.add(rb2);
                bg.add(rb3);
                switch(selectedScopeIndex) {
                    case 0:
                        rb1.setSelected(true);
                        break;
                    case 1:
                        rb2.setSelected(true);
                        break;
                    case 2:
                        rb3.setSelected(true);
                        break;
                }
                gui.add(panel, BorderLayout.CENTER);
                double a = sp.getRelativeAzimuth() + foundation.getAzimuth();
                if (a > 360) {
                    a -= 360;
                }
                final JTextField inputField = new JTextField(EnergyPanel.TWO_DECIMALS.format(a));
                gui.add(inputField, BorderLayout.SOUTH);
                final Object[] options = new Object[] { "OK", "Cancel", "Apply" };
                final JOptionPane optionPane = new JOptionPane(new Object[] { title, footnote, gui }, JOptionPane.QUESTION_MESSAGE, JOptionPane.YES_NO_CANCEL_OPTION, null, options, options[2]);
                final JDialog dialog = optionPane.createDialog(MainFrame.getInstance(), "Solar Panel Azimuth");
                while (true) {
                    inputField.selectAll();
                    inputField.requestFocusInWindow();
                    dialog.setVisible(true);
                    final Object choice = optionPane.getValue();
                    if (choice == options[1] || choice == null) {
                        break;
                    } else {
                        double val = 0;
                        boolean ok = true;
                        try {
                            val = Double.parseDouble(inputField.getText());
                        } catch (final NumberFormatException exception) {
                            JOptionPane.showMessageDialog(MainFrame.getInstance(), inputField.getText() + " is an invalid value!", "Error", JOptionPane.ERROR_MESSAGE);
                            ok = false;
                        }
                        if (ok) {
                            a = val - foundation.getAzimuth();
                            if (a < 0) {
                                a += 360;
                            }
                            boolean changed = Math.abs(a - sp.getRelativeAzimuth()) > 0.000001;
                            if (rb1.isSelected()) {
                                if (changed) {
                                    final ChangeAzimuthCommand c = new ChangeAzimuthCommand(sp);
                                    sp.setRelativeAzimuth(a);
                                    sp.draw();
                                    SceneManager.getInstance().refresh();
                                    SceneManager.getInstance().getUndoManager().addEdit(c);
                                }
                                selectedScopeIndex = 0;
                            } else if (rb2.isSelected()) {
                                if (!changed) {
                                    for (final SolarPanel x : foundation.getSolarPanels()) {
                                        if (Math.abs(a - x.getRelativeAzimuth()) > 0.000001) {
                                            changed = true;
                                            break;
                                        }
                                    }
                                }
                                if (changed) {
                                    final ChangeFoundationSolarPanelAzimuthCommand c = new ChangeFoundationSolarPanelAzimuthCommand(foundation);
                                    foundation.setAzimuthForSolarPanels(a);
                                    SceneManager.getInstance().getUndoManager().addEdit(c);
                                }
                                selectedScopeIndex = 1;
                            } else if (rb3.isSelected()) {
                                if (!changed) {
                                    for (final SolarPanel x : Scene.getInstance().getAllSolarPanels()) {
                                        if (Math.abs(a - x.getRelativeAzimuth()) > 0.000001) {
                                            changed = true;
                                            break;
                                        }
                                    }
                                }
                                if (changed) {
                                    final ChangeAzimuthForAllSolarPanelsCommand c = new ChangeAzimuthForAllSolarPanelsCommand();
                                    Scene.getInstance().setAzimuthForAllSolarPanels(a);
                                    SceneManager.getInstance().getUndoManager().addEdit(c);
                                }
                                selectedScopeIndex = 2;
                            }
                            if (changed) {
                                updateAfterEdit();
                            }
                            if (choice == options[0]) {
                                break;
                            }
                        }
                    }
                }
            }
        });
        final JMenuItem miSize = new JMenuItem("Size...");
        miSize.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (!(selectedPart instanceof SolarPanel)) {
                    return;
                }
                final SolarPanel s = (SolarPanel) selectedPart;
                final String partInfo = s.toString().substring(0, selectedPart.toString().indexOf(')') + 1);
                final JPanel gui = new JPanel(new BorderLayout(5, 5));
                gui.setBorder(BorderFactory.createTitledBorder("Choose Size for " + partInfo));
                final JComboBox<String> sizeComboBox = new JComboBox<String>(solarPanelNominalSize.getStrings());
                final int nItems = sizeComboBox.getItemCount();
                for (int i = 0; i < nItems; i++) {
                    if (Util.isZero(s.getPanelHeight() - solarPanelNominalSize.getNominalHeights()[i]) && Util.isZero(s.getPanelWidth() - solarPanelNominalSize.getNominalWidths()[i])) {
                        sizeComboBox.setSelectedIndex(i);
                    }
                }
                gui.add(sizeComboBox, BorderLayout.NORTH);
                if (JOptionPane.showConfirmDialog(MainFrame.getInstance(), gui, "Set Size", JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE) == JOptionPane.CANCEL_OPTION) {
                    return;
                }
                final int i = sizeComboBox.getSelectedIndex();
                boolean changed = s.getNumberOfCellsInX() != solarPanelNominalSize.getCellNx()[i] || s.getNumberOfCellsInY() != solarPanelNominalSize.getCellNy()[i];
                if (!changed) {
                    if (Math.abs(s.getPanelWidth() - solarPanelNominalSize.getNominalWidths()[i]) > 0.000001 || Math.abs(s.getPanelHeight() - solarPanelNominalSize.getNominalHeights()[i]) > 0.000001) {
                        changed = true;
                    }
                }
                if (changed) {
                    final ChooseSolarPanelSizeCommand c = new ChooseSolarPanelSizeCommand(s);
                    s.setPanelWidth(solarPanelNominalSize.getNominalWidths()[i]);
                    s.setPanelHeight(solarPanelNominalSize.getNominalHeights()[i]);
                    s.setNumberOfCellsInX(solarPanelNominalSize.getCellNx()[i]);
                    s.setNumberOfCellsInY(solarPanelNominalSize.getCellNy()[i]);
                    s.draw();
                    SceneManager.getInstance().refresh();
                    SceneManager.getInstance().getUndoManager().addEdit(c);
                    updateAfterEdit();
                }
            }
        });
        final JMenuItem miBaseHeight = new JMenuItem("Base Height...");
        miBaseHeight.addActionListener(new ActionListener() {

            // remember the scope selection as the next action will likely be applied to the same scope
            private int selectedScopeIndex = 0;

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (!(selectedPart instanceof SolarPanel)) {
                    return;
                }
                final String partInfo = selectedPart.toString().substring(0, selectedPart.toString().indexOf(')') + 1);
                final SolarPanel sp = (SolarPanel) selectedPart;
                final Foundation foundation = sp.getTopContainer();
                final String title = "<html>Base Height (m) of " + partInfo + "</html>";
                final String footnote = "<html><hr><font size=2></html>";
                final JPanel gui = new JPanel(new BorderLayout());
                final JPanel panel = new JPanel();
                gui.add(panel, BorderLayout.CENTER);
                panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
                panel.setBorder(BorderFactory.createTitledBorder("Apply to:"));
                final JRadioButton rb1 = new JRadioButton("Only this Solar Panel", true);
                final JRadioButton rb2 = new JRadioButton("Only this Row");
                final JRadioButton rb3 = new JRadioButton("All Solar Panels on this Foundation");
                final JRadioButton rb4 = new JRadioButton("All Solar Panels");
                panel.add(rb1);
                panel.add(rb2);
                panel.add(rb3);
                panel.add(rb4);
                final ButtonGroup bg = new ButtonGroup();
                bg.add(rb1);
                bg.add(rb2);
                bg.add(rb3);
                bg.add(rb4);
                switch(selectedScopeIndex) {
                    case 0:
                        rb1.setSelected(true);
                        break;
                    case 1:
                        rb2.setSelected(true);
                        break;
                    case 2:
                        rb3.setSelected(true);
                        break;
                    case 3:
                        rb4.setSelected(true);
                        break;
                }
                gui.add(panel, BorderLayout.CENTER);
                final JTextField inputField = new JTextField(EnergyPanel.TWO_DECIMALS.format(sp.getBaseHeight() * Scene.getInstance().getAnnotationScale()));
                gui.add(inputField, BorderLayout.SOUTH);
                final Object[] options = new Object[] { "OK", "Cancel", "Apply" };
                final JOptionPane optionPane = new JOptionPane(new Object[] { title, footnote, gui }, JOptionPane.QUESTION_MESSAGE, JOptionPane.YES_NO_CANCEL_OPTION, null, options, options[2]);
                final JDialog dialog = optionPane.createDialog(MainFrame.getInstance(), "Solar Panel Base Height");
                while (true) {
                    inputField.selectAll();
                    inputField.requestFocusInWindow();
                    dialog.setVisible(true);
                    final Object choice = optionPane.getValue();
                    if (choice == options[1] || choice == null) {
                        break;
                    } else {
                        double val = 0;
                        boolean ok = true;
                        try {
                            val = Double.parseDouble(inputField.getText()) / Scene.getInstance().getAnnotationScale();
                        } catch (final NumberFormatException exception) {
                            JOptionPane.showMessageDialog(MainFrame.getInstance(), inputField.getText() + " is an invalid value!", "Error", JOptionPane.ERROR_MESSAGE);
                            ok = false;
                        }
                        if (ok) {
                            boolean changed = Math.abs(val - sp.getBaseHeight()) > 0.000001;
                            if (rb1.isSelected()) {
                                if (changed) {
                                    final ChangeBaseHeightCommand c = new ChangeBaseHeightCommand(sp);
                                    sp.setBaseHeight(val);
                                    sp.draw();
                                    if (sp.checkContainerIntersection()) {
                                        JOptionPane.showMessageDialog(MainFrame.getInstance(), "This base height cannot be set as the solar panel would cut into the underlying surface.", "Illegal Base Height", JOptionPane.ERROR_MESSAGE);
                                        c.undo();
                                    } else {
                                        SceneManager.getInstance().refresh();
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                }
                                selectedScopeIndex = 0;
                            } else if (rb2.isSelected()) {
                                final List<SolarPanel> row = sp.getRow();
                                for (final SolarPanel x : row) {
                                    if (Math.abs(val - x.getBaseHeight()) > 0.000001) {
                                        changed = true;
                                        break;
                                    }
                                }
                                if (changed) {
                                    final ChangeBaseHeightForSolarPanelRowCommand c = new ChangeBaseHeightForSolarPanelRowCommand(row);
                                    boolean intersected = false;
                                    for (final SolarPanel x : row) {
                                        x.setBaseHeight(val);
                                        x.draw();
                                        if (x.checkContainerIntersection()) {
                                            intersected = true;
                                            break;
                                        }
                                    }
                                    if (intersected) {
                                        JOptionPane.showMessageDialog(MainFrame.getInstance(), "This base height cannot be set as one or more solar panels in the row would cut into the underlying surface.", "Illegal Base Height", JOptionPane.ERROR_MESSAGE);
                                        c.undo();
                                    } else {
                                        SceneManager.getInstance().refresh();
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                }
                                selectedScopeIndex = 1;
                            } else if (rb3.isSelected()) {
                                for (final SolarPanel x : foundation.getSolarPanels()) {
                                    if (Math.abs(val - x.getBaseHeight()) > 0.000001) {
                                        changed = true;
                                        break;
                                    }
                                }
                                if (changed) {
                                    final ChangeFoundationSolarCollectorBaseHeightCommand c = new ChangeFoundationSolarCollectorBaseHeightCommand(foundation, sp.getClass());
                                    foundation.setBaseHeightForSolarPanels(val);
                                    if (foundation.checkContainerIntersectionForSolarPanels()) {
                                        JOptionPane.showMessageDialog(MainFrame.getInstance(), "This base height cannot be set as one or more solar panels would cut into the underlying surface.", "Illegal Base Height", JOptionPane.ERROR_MESSAGE);
                                        c.undo();
                                    } else {
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                }
                                selectedScopeIndex = 2;
                            } else if (rb4.isSelected()) {
                                for (final SolarPanel x : Scene.getInstance().getAllSolarPanels()) {
                                    if (Math.abs(val - x.getBaseHeight()) > 0.000001) {
                                        changed = true;
                                        break;
                                    }
                                }
                                if (changed) {
                                    final ChangeBaseHeightForAllSolarCollectorsCommand c = new ChangeBaseHeightForAllSolarCollectorsCommand(sp.getClass());
                                    Scene.getInstance().setBaseHeightForAllSolarPanels(val);
                                    if (Scene.getInstance().checkContainerIntersectionForAllSolarPanels()) {
                                        JOptionPane.showMessageDialog(MainFrame.getInstance(), "This base height cannot be set as one or more solar panels would cut into the underlying surface.", "Illegal Base Height", JOptionPane.ERROR_MESSAGE);
                                        c.undo();
                                    } else {
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                }
                                selectedScopeIndex = 3;
                            }
                            if (changed) {
                                updateAfterEdit();
                            }
                            if (choice == options[0]) {
                                break;
                            }
                        }
                    }
                }
            }
        });
        final JCheckBoxMenuItem cbmiDisableEditPoint = new JCheckBoxMenuItem("Disable Edit Point");
        cbmiDisableEditPoint.addItemListener(new ItemListener() {

            // remember the scope selection as the next action will likely be applied to the same scope
            private int selectedScopeIndex = 0;

            @Override
            public void itemStateChanged(final ItemEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (!(selectedPart instanceof SolarPanel)) {
                    return;
                }
                final boolean disabled = cbmiDisableEditPoint.isSelected();
                final SolarPanel sp = (SolarPanel) selectedPart;
                final String partInfo = sp.toString().substring(0, sp.toString().indexOf(')') + 1);
                final JPanel gui = new JPanel(new BorderLayout(0, 20));
                final JPanel panel = new JPanel();
                gui.add(panel, BorderLayout.SOUTH);
                panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
                panel.setBorder(BorderFactory.createTitledBorder("Apply to:"));
                final JRadioButton rb1 = new JRadioButton("Only this Solar Panel", true);
                final JRadioButton rb2 = new JRadioButton("All Solar Panels on this Foundation");
                final JRadioButton rb3 = new JRadioButton("All Solar Panels");
                panel.add(rb1);
                panel.add(rb2);
                panel.add(rb3);
                final ButtonGroup bg = new ButtonGroup();
                bg.add(rb1);
                bg.add(rb2);
                bg.add(rb3);
                switch(selectedScopeIndex) {
                    case 0:
                        rb1.setSelected(true);
                        break;
                    case 1:
                        rb2.setSelected(true);
                        break;
                    case 2:
                        rb3.setSelected(true);
                        break;
                }
                final String title = "<html>" + (disabled ? "Disable" : "Enable") + " edit point for " + partInfo + "</html>";
                final String footnote = "<html><hr><font size=2>Disable the edit point of a solar panel prevents it<br>from being unintentionally moved.<hr></html>";
                final Object[] options = new Object[] { "OK", "Cancel" };
                final JOptionPane optionPane = new JOptionPane(new Object[] { title, footnote, gui }, JOptionPane.QUESTION_MESSAGE, JOptionPane.YES_NO_CANCEL_OPTION, null, options, options[0]);
                final JDialog dialog = optionPane.createDialog(MainFrame.getInstance(), (disabled ? "Disable" : "Enable") + " Edit Point");
                dialog.setVisible(true);
                if (optionPane.getValue() == options[0]) {
                    if (rb1.isSelected()) {
                        final LockEditPointsCommand c = new LockEditPointsCommand(sp);
                        sp.setLockEdit(disabled);
                        SceneManager.getInstance().getUndoManager().addEdit(c);
                        selectedScopeIndex = 0;
                    } else if (rb2.isSelected()) {
                        final Foundation foundation = sp.getTopContainer();
                        final LockEditPointsOnFoundationCommand c = new LockEditPointsOnFoundationCommand(foundation, sp.getClass());
                        foundation.setLockEditForClass(disabled, sp.getClass());
                        SceneManager.getInstance().getUndoManager().addEdit(c);
                        selectedScopeIndex = 1;
                    } else if (rb3.isSelected()) {
                        final LockEditPointsForClassCommand c = new LockEditPointsForClassCommand(sp);
                        Scene.getInstance().setLockEditForClass(disabled, sp.getClass());
                        SceneManager.getInstance().getUndoManager().addEdit(c);
                        selectedScopeIndex = 2;
                    }
                    SceneManager.getInstance().refresh();
                    Scene.getInstance().setEdited(true);
                }
            }
        });
        final JCheckBoxMenuItem cbmiDrawSunBeam = new JCheckBoxMenuItem("Draw Sun Beam");
        cbmiDrawSunBeam.addItemListener(new ItemListener() {

            @Override
            public void itemStateChanged(final ItemEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (!(selectedPart instanceof SolarPanel)) {
                    return;
                }
                final SolarPanel sp = (SolarPanel) selectedPart;
                final ShowSunBeamCommand c = new ShowSunBeamCommand(sp);
                sp.setSunBeamVisible(cbmiDrawSunBeam.isSelected());
                sp.drawSunBeam();
                sp.draw();
                SceneManager.getInstance().refresh();
                SceneManager.getInstance().getUndoManager().addEdit(c);
                Scene.getInstance().setEdited(true);
            }
        });
        final JMenuItem miCells = new JMenuItem("Solar Cells...");
        miCells.addActionListener(new ActionListener() {

            // remember the scope selection as the next action will likely be applied to the same scope
            private int selectedScopeIndex = 0;

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (!(selectedPart instanceof SolarPanel)) {
                    return;
                }
                final SolarPanel solarPanel = (SolarPanel) selectedPart;
                final String title = "<html>Solar Cell Properties of " + selectedPart.toString().substring(0, selectedPart.toString().indexOf(')') + 1) + "</html>";
                String footnote = "<html><hr><font size=2><b>How efficiently can a solar cell convert light into electricity?</b><br>The Shockley-Queisser limit is 34%. The theoretical limit for multilayer cells<br>is 86%. As of 2017, the best solar panel in the market has an efficiency of 24%.<br>The highest efficiency you can choose is limited to " + SolarPanel.MAX_SOLAR_CELL_EFFICIENCY_PERCENTAGE + "%.<hr>";
                footnote += "<font size=2>Solar cells made of monocrystalline silicon are usually round or semi-round.<br>Hence, there is a small fraction of area on a solar panel not covered by cells.<br>In other words, a monocrystalline solar panel has a smaller packing density.";
                footnote += "<br><font size=2>Solar cells made of polycrystalline silicon are usually square. Compared with a<br>monocrystalline solar panel, a polycrystalline one has a higher packing density.<br>Color has no relationship with efficiency.<hr></html>";
                final JPanel gui = new JPanel(new BorderLayout());
                final JPanel panel = new JPanel();
                gui.add(panel, BorderLayout.SOUTH);
                panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
                panel.setBorder(BorderFactory.createTitledBorder("Apply to:"));
                final JRadioButton rb1 = new JRadioButton("Only this Solar Panel", true);
                final JRadioButton rb2 = new JRadioButton("All Solar Panels on this Foundation");
                final JRadioButton rb3 = new JRadioButton("All Solar Panels");
                panel.add(rb1);
                panel.add(rb2);
                panel.add(rb3);
                final ButtonGroup bg = new ButtonGroup();
                bg.add(rb1);
                bg.add(rb2);
                bg.add(rb3);
                switch(selectedScopeIndex) {
                    case 0:
                        rb1.setSelected(true);
                        break;
                    case 1:
                        rb2.setSelected(true);
                        break;
                    case 2:
                        rb3.setSelected(true);
                        break;
                }
                final JPanel inputPanel = new JPanel(new SpringLayout());
                inputPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
                gui.add(inputPanel, BorderLayout.CENTER);
                JLabel label = new JLabel("Type: ", JLabel.LEFT);
                inputPanel.add(label);
                final JComboBox<String> typeComboBox = new JComboBox<String>(new String[] { "Polycrystalline", "Monocrystalline", "Thin Film" });
                typeComboBox.setSelectedIndex(solarPanel.getCellType());
                label.setLabelFor(typeComboBox);
                inputPanel.add(typeComboBox);
                label = new JLabel("Color: ", JLabel.LEFT);
                inputPanel.add(label);
                final JComboBox<String> colorComboBox = new JComboBox<String>(new String[] { "Blue", "Black", "Gray" });
                colorComboBox.setSelectedIndex(solarPanel.getColorOption());
                label.setLabelFor(colorComboBox);
                inputPanel.add(colorComboBox);
                label = new JLabel("Efficiency (%): ", JLabel.LEFT);
                inputPanel.add(label);
                final JTextField efficiencyField = new JTextField(EnergyPanel.TWO_DECIMALS.format(solarPanel.getCellEfficiency() * 100));
                label.setLabelFor(efficiencyField);
                inputPanel.add(efficiencyField);
                SpringUtilities.makeCompactGrid(inputPanel, 3, 2, 6, 6, 6, 6);
                final Object[] options = new Object[] { "OK", "Cancel", "Apply" };
                final JOptionPane optionPane = new JOptionPane(new Object[] { title, footnote, gui }, JOptionPane.QUESTION_MESSAGE, JOptionPane.YES_NO_CANCEL_OPTION, null, options, options[2]);
                final JDialog dialog = optionPane.createDialog(MainFrame.getInstance(), "Solar Cell Properties");
                while (true) {
                    efficiencyField.selectAll();
                    efficiencyField.requestFocusInWindow();
                    dialog.setVisible(true);
                    final Object choice = optionPane.getValue();
                    if (choice == options[1] || choice == null) {
                        break;
                    } else {
                        double val = 0;
                        boolean ok = true;
                        try {
                            val = Double.parseDouble(efficiencyField.getText());
                        } catch (final NumberFormatException exception) {
                            JOptionPane.showMessageDialog(MainFrame.getInstance(), efficiencyField.getText() + " is an invalid value!", "Error", JOptionPane.ERROR_MESSAGE);
                            ok = false;
                        }
                        if (ok) {
                            if (val < SolarPanel.MIN_SOLAR_CELL_EFFICIENCY_PERCENTAGE || val > SolarPanel.MAX_SOLAR_CELL_EFFICIENCY_PERCENTAGE) {
                                JOptionPane.showMessageDialog(MainFrame.getInstance(), "Solar cell efficiency must be between " + SolarPanel.MIN_SOLAR_CELL_EFFICIENCY_PERCENTAGE + "% and " + SolarPanel.MAX_SOLAR_CELL_EFFICIENCY_PERCENTAGE + "%.", "Range Error", JOptionPane.ERROR_MESSAGE);
                            } else {
                                final int cellType = typeComboBox.getSelectedIndex();
                                final int colorOption = colorComboBox.getSelectedIndex();
                                boolean changed = cellType != solarPanel.getCellType() || colorOption != solarPanel.getColorOption() || Math.abs(val * 0.01 - solarPanel.getCellEfficiency()) > 0.000001;
                                if (rb1.isSelected()) {
                                    if (changed) {
                                        final ChangeSolarCellPropertiesCommand c = new ChangeSolarCellPropertiesCommand(solarPanel);
                                        solarPanel.setCellEfficiency(val * 0.01);
                                        solarPanel.setCellType(cellType);
                                        solarPanel.setColorOption(colorOption);
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 0;
                                } else if (rb2.isSelected()) {
                                    final Foundation foundation = solarPanel.getTopContainer();
                                    if (!changed) {
                                        for (final SolarPanel x : foundation.getSolarPanels()) {
                                            if (cellType != x.getCellType() || colorOption != x.getColorOption() || Math.abs(val * 0.01 - x.getCellEfficiency()) > 0.000001) {
                                                changed = true;
                                                break;
                                            }
                                        }
                                    }
                                    if (changed) {
                                        final ChangeFoundationSolarCellPropertiesCommand c = new ChangeFoundationSolarCellPropertiesCommand(foundation);
                                        foundation.setSolarCellEfficiency(val * 0.01);
                                        foundation.setCellTypeForSolarPanels(cellType);
                                        foundation.setColorForSolarPanels(colorOption);
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 1;
                                } else if (rb3.isSelected()) {
                                    if (!changed) {
                                        for (final SolarPanel x : Scene.getInstance().getAllSolarPanels()) {
                                            if (cellType != x.getCellType() || colorOption != x.getColorOption() || Math.abs(val * 0.01 - x.getCellEfficiency()) > 0.000001) {
                                                changed = true;
                                                break;
                                            }
                                        }
                                    }
                                    if (changed) {
                                        final ChangeSolarCellPropertiesForAllCommand c = new ChangeSolarCellPropertiesForAllCommand();
                                        Scene.getInstance().setSolarCellEfficiencyForAll(val * 0.01);
                                        Scene.getInstance().setCellTypeForAllSolarPanels(cellType);
                                        Scene.getInstance().setColorForAllSolarPanels(colorOption);
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 2;
                                }
                                if (changed) {
                                    updateAfterEdit();
                                }
                                if (choice == options[0]) {
                                    break;
                                }
                            }
                        }
                    }
                }
            }
        });
        final JMenuItem miTemperatureEffects = new JMenuItem("Temperature Effects...");
        miTemperatureEffects.addActionListener(new ActionListener() {

            // remember the scope selection as the next action will likely be applied to the same scope
            private int selectedScopeIndex = 0;

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (!(selectedPart instanceof SolarPanel)) {
                    return;
                }
                final String partInfo = selectedPart.toString().substring(0, selectedPart.toString().indexOf(')') + 1);
                final SolarPanel solarPanel = (SolarPanel) selectedPart;
                final String title = "<html>Temperature Effects of " + partInfo + "</html>";
                final String footnote = "<html><hr><font size=2>Increased temperature reduces solar cell efficiency. To determine this temperature effect,<br>it is important to know the expected operating temperature: the Nominal Operating Cell<br>Temperature (NOCT). NOCT ranges from 33&deg;C to 58&deg;C.<hr></html>";
                final JPanel gui = new JPanel(new BorderLayout());
                final JPanel panel = new JPanel();
                gui.add(panel, BorderLayout.CENTER);
                panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
                panel.setBorder(BorderFactory.createTitledBorder("Apply to:"));
                final JRadioButton rb1 = new JRadioButton("Only this Solar Panel", true);
                final JRadioButton rb2 = new JRadioButton("All Solar Panels on this Foundation");
                final JRadioButton rb3 = new JRadioButton("All Solar Panels");
                panel.add(rb1);
                panel.add(rb2);
                panel.add(rb3);
                final ButtonGroup bg = new ButtonGroup();
                bg.add(rb1);
                bg.add(rb2);
                bg.add(rb3);
                switch(selectedScopeIndex) {
                    case 0:
                        rb1.setSelected(true);
                        break;
                    case 1:
                        rb2.setSelected(true);
                        break;
                    case 2:
                        rb3.setSelected(true);
                        break;
                }
                gui.add(panel, BorderLayout.SOUTH);
                final JPanel inputPanel = new JPanel(new SpringLayout());
                gui.add(inputPanel, BorderLayout.CENTER);
                inputPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
                JLabel label = new JLabel("<html>Nominal Operating Cell Temperature (&deg;C): ", JLabel.LEFT);
                inputPanel.add(label);
                final JTextField noctField = new JTextField(EnergyPanel.TWO_DECIMALS.format(solarPanel.getNominalOperatingCellTemperature()));
                label.setLabelFor(noctField);
                inputPanel.add(noctField);
                label = new JLabel("<html>Temperature Coefficient of Pmax (%/&deg;C): ", JLabel.LEFT);
                inputPanel.add(label);
                final JTextField pmaxField = new JTextField(EnergyPanel.TWO_DECIMALS.format(solarPanel.getTemperatureCoefficientPmax() * 100));
                label.setLabelFor(pmaxField);
                inputPanel.add(pmaxField);
                SpringUtilities.makeCompactGrid(inputPanel, 2, 2, 6, 6, 6, 6);
                final Object[] options = new Object[] { "OK", "Cancel", "Apply" };
                final JOptionPane optionPane = new JOptionPane(new Object[] { title, footnote, gui }, JOptionPane.QUESTION_MESSAGE, JOptionPane.YES_NO_CANCEL_OPTION, null, options, options[2]);
                final JDialog dialog = optionPane.createDialog(MainFrame.getInstance(), "Temperature Effects");
                while (true) {
                    dialog.setVisible(true);
                    final Object choice = optionPane.getValue();
                    if (choice == options[1] || choice == null) {
                        break;
                    } else {
                        double noct = 0;
                        double pmax = 0;
                        boolean ok = true;
                        try {
                            noct = Double.parseDouble(noctField.getText());
                            pmax = Double.parseDouble(pmaxField.getText());
                        } catch (final NumberFormatException exception) {
                            JOptionPane.showMessageDialog(MainFrame.getInstance(), "Invalid input!", "Error", JOptionPane.ERROR_MESSAGE);
                            ok = false;
                        }
                        if (ok) {
                            if (noct < 33 || noct > 58) {
                                JOptionPane.showMessageDialog(MainFrame.getInstance(), "Nominal Operating Cell Temperature must be between 33 and 58 Celsius degrees.", "Range Error", JOptionPane.ERROR_MESSAGE);
                            } else if (pmax < -1 || pmax > 0) {
                                JOptionPane.showMessageDialog(MainFrame.getInstance(), "Temperature coefficient of Pmax must be between -1% and 0% per Celsius degree.", "Range Error", JOptionPane.ERROR_MESSAGE);
                            } else {
                                boolean changed = Math.abs(noct - solarPanel.getNominalOperatingCellTemperature()) > 0.000001 || Math.abs(pmax * 0.01 - solarPanel.getTemperatureCoefficientPmax()) > 0.000001;
                                if (rb1.isSelected()) {
                                    if (changed) {
                                        final SetTemperatureEffectsCommand c = new SetTemperatureEffectsCommand(solarPanel);
                                        solarPanel.setTemperatureCoefficientPmax(pmax * 0.01);
                                        solarPanel.setNominalOperatingCellTemperature(noct);
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 0;
                                } else if (rb2.isSelected()) {
                                    final Foundation foundation = solarPanel.getTopContainer();
                                    if (!changed) {
                                        for (final SolarPanel x : foundation.getSolarPanels()) {
                                            if (Math.abs(noct - x.getNominalOperatingCellTemperature()) > 0.000001 || Math.abs(pmax * 0.01 - x.getTemperatureCoefficientPmax()) > 0.000001) {
                                                changed = true;
                                                break;
                                            }
                                        }
                                    }
                                    if (changed) {
                                        final SetFoundationTemperatureEffectsCommand c = new SetFoundationTemperatureEffectsCommand(foundation);
                                        foundation.setTemperatureCoefficientPmax(pmax * 0.01);
                                        foundation.setNominalOperatingCellTemperature(noct);
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 1;
                                } else if (rb3.isSelected()) {
                                    if (!changed) {
                                        for (final SolarPanel x : Scene.getInstance().getAllSolarPanels()) {
                                            if (Math.abs(noct - x.getNominalOperatingCellTemperature()) > 0.000001 || Math.abs(pmax * 0.01 - x.getTemperatureCoefficientPmax()) > 0.000001) {
                                                changed = true;
                                                break;
                                            }
                                        }
                                    }
                                    if (changed) {
                                        final SetTemperatrureEffectsForAllCommand c = new SetTemperatrureEffectsForAllCommand();
                                        Scene.getInstance().setTemperatureCoefficientPmaxForAll(pmax * 0.01);
                                        Scene.getInstance().setNominalOperatingCellTemperatureForAll(noct);
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 2;
                                }
                                if (changed) {
                                    updateAfterEdit();
                                }
                                if (choice == options[0]) {
                                    break;
                                }
                            }
                        }
                    }
                }
            }
        });
        final JMenuItem miInverterEff = new JMenuItem("Inverter Efficiency...");
        miInverterEff.addActionListener(new ActionListener() {

            // remember the scope selection as the next action will likely be applied to the same scope
            private int selectedScopeIndex = 0;

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (!(selectedPart instanceof SolarPanel)) {
                    return;
                }
                final String partInfo = selectedPart.toString().substring(0, selectedPart.toString().indexOf(')') + 1);
                final SolarPanel solarPanel = (SolarPanel) selectedPart;
                final String title = "<html>Inverter Efficiency (%) of " + partInfo + "</html>";
                final String footnote = "<html><hr><font size=2>The efficiency of a micro inverter for converting electricity from DC to AC is typically 95%.<hr></html>";
                final JPanel gui = new JPanel(new BorderLayout());
                final JPanel panel = new JPanel();
                gui.add(panel, BorderLayout.CENTER);
                panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
                panel.setBorder(BorderFactory.createTitledBorder("Apply to:"));
                final JRadioButton rb1 = new JRadioButton("Only this Solar Panel", true);
                final JRadioButton rb2 = new JRadioButton("All Solar Panels on this Foundation");
                final JRadioButton rb3 = new JRadioButton("All Solar Panels");
                panel.add(rb1);
                panel.add(rb2);
                panel.add(rb3);
                final ButtonGroup bg = new ButtonGroup();
                bg.add(rb1);
                bg.add(rb2);
                bg.add(rb3);
                switch(selectedScopeIndex) {
                    case 0:
                        rb1.setSelected(true);
                        break;
                    case 1:
                        rb2.setSelected(true);
                        break;
                    case 2:
                        rb3.setSelected(true);
                        break;
                }
                gui.add(panel, BorderLayout.CENTER);
                final JTextField inputField = new JTextField(EnergyPanel.TWO_DECIMALS.format(solarPanel.getInverterEfficiency() * 100));
                gui.add(inputField, BorderLayout.SOUTH);
                final Object[] options = new Object[] { "OK", "Cancel", "Apply" };
                final JOptionPane optionPane = new JOptionPane(new Object[] { title, footnote, gui }, JOptionPane.QUESTION_MESSAGE, JOptionPane.YES_NO_CANCEL_OPTION, null, options, options[2]);
                final JDialog dialog = optionPane.createDialog(MainFrame.getInstance(), "Inverter Efficiency");
                while (true) {
                    inputField.selectAll();
                    inputField.requestFocusInWindow();
                    dialog.setVisible(true);
                    final Object choice = optionPane.getValue();
                    if (choice == options[1] || choice == null) {
                        break;
                    } else {
                        double val = 0;
                        boolean ok = true;
                        try {
                            val = Double.parseDouble(inputField.getText());
                        } catch (final NumberFormatException exception) {
                            JOptionPane.showMessageDialog(MainFrame.getInstance(), inputField.getText() + " is an invalid value!", "Error", JOptionPane.ERROR_MESSAGE);
                            ok = false;
                        }
                        if (ok) {
                            if (val < SolarPanel.MIN_INVERTER_EFFICIENCY_PERCENTAGE || val >= SolarPanel.MAX_INVERTER_EFFICIENCY_PERCENTAGE) {
                                JOptionPane.showMessageDialog(MainFrame.getInstance(), "Inverter efficiency must be greater than " + SolarPanel.MIN_INVERTER_EFFICIENCY_PERCENTAGE + "% and less than " + SolarPanel.MAX_INVERTER_EFFICIENCY_PERCENTAGE + "%.", "Range Error", JOptionPane.ERROR_MESSAGE);
                            } else {
                                boolean changed = Math.abs(val * 0.01 - solarPanel.getInverterEfficiency()) > 0.000001;
                                if (rb1.isSelected()) {
                                    if (changed) {
                                        final ChangeInverterEfficiencyCommand c = new ChangeInverterEfficiencyCommand(solarPanel);
                                        solarPanel.setInverterEfficiency(val * 0.01);
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 0;
                                } else if (rb2.isSelected()) {
                                    final Foundation foundation = solarPanel.getTopContainer();
                                    if (!changed) {
                                        for (final SolarPanel x : foundation.getSolarPanels()) {
                                            if (Math.abs(val * 0.01 - x.getInverterEfficiency()) > 0.000001) {
                                                changed = true;
                                                break;
                                            }
                                        }
                                    }
                                    if (changed) {
                                        final ChangeFoundationInverterEfficiencyCommand c = new ChangeFoundationInverterEfficiencyCommand(foundation);
                                        foundation.setSolarPanelInverterEfficiency(val * 0.01);
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 1;
                                } else if (rb3.isSelected()) {
                                    if (!changed) {
                                        for (final SolarPanel x : Scene.getInstance().getAllSolarPanels()) {
                                            if (Math.abs(val * 0.01 - x.getInverterEfficiency()) > 0.000001) {
                                                changed = true;
                                                break;
                                            }
                                        }
                                    }
                                    if (changed) {
                                        final ChangeInverterEfficiencyForAllCommand c = new ChangeInverterEfficiencyForAllCommand();
                                        Scene.getInstance().setSolarPanelInverterEfficiencyForAll(val * 0.01);
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 2;
                                }
                                if (changed) {
                                    updateAfterEdit();
                                }
                                if (choice == options[0]) {
                                    break;
                                }
                            }
                        }
                    }
                }
            }
        });
        trackerMenu.add(miNoTracker);
        trackerMenu.add(miHorizontalSingleAxisTracker);
        trackerMenu.add(miVerticalSingleAxisTracker);
        trackerMenu.add(miAltazimuthDualAxisTracker);
        shadeToleranceMenu.add(miNoTolerance);
        shadeToleranceMenu.add(miPartialTolerance);
        shadeToleranceMenu.add(miHighTolerance);
        final JMenuItem miModel = new JMenuItem("Model...");
        miModel.addActionListener(new ActionListener() {

            private String modelName;

            // remember the scope selection as the next action will likely be applied to the same scope
            private int selectedScopeIndex = 0;

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (!(selectedPart instanceof SolarPanel)) {
                    return;
                }
                final SolarPanel s = (SolarPanel) selectedPart;
                final Foundation foundation = s.getTopContainer();
                final String partInfo = s.toString().substring(0, s.toString().indexOf(')') + 1);
                final Map<String, PvModuleSpecs> modules = PvModulesData.getInstance().getModules();
                final String[] models = new String[modules.size() + 1];
                int i = 0;
                models[i] = "Custom";
                for (final String key : modules.keySet()) {
                    models[++i] = key;
                }
                final PvModuleSpecs specs = s.getPvModuleSpecs();
                modelName = specs.getModel();
                final JPanel gui = new JPanel(new BorderLayout(5, 5));
                gui.setBorder(BorderFactory.createTitledBorder("Model for " + partInfo));
                final JComboBox<String> typeComboBox = new JComboBox<String>(models);
                typeComboBox.setSelectedItem(specs.getModel());
                typeComboBox.addItemListener(new ItemListener() {

                    @Override
                    public void itemStateChanged(final ItemEvent e) {
                        if (e.getStateChange() == ItemEvent.SELECTED) {
                            modelName = (String) typeComboBox.getSelectedItem();
                        }
                    }
                });
                gui.add(typeComboBox, BorderLayout.NORTH);
                final JPanel scopePanel = new JPanel();
                scopePanel.setLayout(new BoxLayout(scopePanel, BoxLayout.Y_AXIS));
                scopePanel.setBorder(BorderFactory.createTitledBorder("Apply to:"));
                final JRadioButton rb1 = new JRadioButton("Only this Solar Panel", true);
                final JRadioButton rb2 = new JRadioButton("All Solar Panels on this Foundation");
                final JRadioButton rb3 = new JRadioButton("All Solar Panels");
                scopePanel.add(rb1);
                scopePanel.add(rb2);
                scopePanel.add(rb3);
                final ButtonGroup bg = new ButtonGroup();
                bg.add(rb1);
                bg.add(rb2);
                bg.add(rb3);
                switch(selectedScopeIndex) {
                    case 0:
                        rb1.setSelected(true);
                        break;
                    case 1:
                        rb2.setSelected(true);
                        break;
                    case 2:
                        rb3.setSelected(true);
                        break;
                }
                gui.add(scopePanel, BorderLayout.CENTER);
                final Object[] options = new Object[] { "OK", "Cancel", "Apply" };
                final JOptionPane optionPane = new JOptionPane(gui, JOptionPane.QUESTION_MESSAGE, JOptionPane.YES_NO_CANCEL_OPTION, null, options, options[2]);
                final JDialog dialog = optionPane.createDialog(MainFrame.getInstance(), "Solar Panel Model");
                while (true) {
                    dialog.setVisible(true);
                    final Object choice = optionPane.getValue();
                    if (choice == options[1] || choice == null) {
                        break;
                    } else {
                        boolean changed = !modelName.equals(s.getModelName());
                        if (rb1.isSelected()) {
                            if (changed) {
                                final ChangeSolarPanelModelCommand c = new ChangeSolarPanelModelCommand(s);
                                s.setPvModuleSpecs(PvModulesData.getInstance().getModuleSpecs(modelName));
                                s.draw();
                                SceneManager.getInstance().refresh();
                                SceneManager.getInstance().getUndoManager().addEdit(c);
                            }
                            selectedScopeIndex = 0;
                        } else if (rb2.isSelected()) {
                            if (!changed) {
                                for (final SolarPanel x : foundation.getSolarPanels()) {
                                    if (!modelName.equals(x.getModelName())) {
                                        changed = true;
                                        break;
                                    }
                                }
                            }
                            if (changed) {
                                final ChangeFoundationSolarPanelModelCommand c = new ChangeFoundationSolarPanelModelCommand(foundation);
                                foundation.setModelForSolarPanels(PvModulesData.getInstance().getModuleSpecs(modelName));
                                SceneManager.getInstance().getUndoManager().addEdit(c);
                            }
                            selectedScopeIndex = 1;
                        } else if (rb3.isSelected()) {
                            if (!changed) {
                                for (final SolarPanel x : Scene.getInstance().getAllSolarPanels()) {
                                    if (!modelName.equals(x.getModelName())) {
                                        changed = true;
                                        break;
                                    }
                                }
                            }
                            if (changed) {
                                final ChangeModelForAllSolarPanelsCommand c = new ChangeModelForAllSolarPanelsCommand();
                                Scene.getInstance().setModelForAllSolarPanels(PvModulesData.getInstance().getModuleSpecs(modelName));
                                SceneManager.getInstance().getUndoManager().addEdit(c);
                            }
                            selectedScopeIndex = 2;
                        }
                        if (changed) {
                            updateAfterEdit();
                        }
                        if (choice == options[0]) {
                            break;
                        }
                    }
                }
            }
        });
        popupMenuForSolarPanel = createPopupMenu(true, true, new Runnable() {

            @Override
            public void run() {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (!(selectedPart instanceof SolarPanel)) {
                    return;
                }
                final SolarPanel sp = (SolarPanel) selectedPart;
                switch(sp.getShadeTolerance()) {
                    case SolarPanel.HIGH_SHADE_TOLERANCE:
                        Util.selectSilently(miHighTolerance, true);
                        break;
                    case SolarPanel.PARTIAL_SHADE_TOLERANCE:
                        Util.selectSilently(miPartialTolerance, true);
                        break;
                    case SolarPanel.NO_SHADE_TOLERANCE:
                        Util.selectSilently(miNoTolerance, true);
                        break;
                }
                Util.selectSilently(cbmiDrawSunBeam, sp.isSunBeamVisible());
                Util.selectSilently(cbmiDisableEditPoint, sp.getLockEdit());
                Util.selectSilently(rbmiLandscape, sp.isRotated());
                Util.selectSilently(rbmiPortrait, !sp.isRotated());
                Util.selectSilently(miLabelNone, !sp.isLabelVisible());
                Util.selectSilently(miLabelCustom, sp.getLabelCustom());
                Util.selectSilently(miLabelId, sp.getLabelId());
                Util.selectSilently(miLabelModelName, sp.getLabelModelName());
                Util.selectSilently(miLabelCellEfficiency, sp.getLabelCellEfficiency());
                Util.selectSilently(miLabelTiltAngle, sp.getLabelTiltAngle());
                Util.selectSilently(miLabelTracker, sp.getLabelTracker());
                Util.selectSilently(miLabelEnergyOutput, sp.getLabelEnergyOutput());
                final PvModuleSpecs pms = sp.getPvModuleSpecs();
                final boolean isCustom = "Custom".equals(pms.getModel());
                miCells.setEnabled(isCustom);
                miSize.setEnabled(isCustom);
                miTemperatureEffects.setEnabled(isCustom);
                shadeToleranceMenu.setEnabled(isCustom);
                switch(sp.getTracker()) {
                    case Trackable.ALTAZIMUTH_DUAL_AXIS_TRACKER:
                        Util.selectSilently(miAltazimuthDualAxisTracker, true);
                        break;
                    case Trackable.HORIZONTAL_SINGLE_AXIS_TRACKER:
                        Util.selectSilently(miHorizontalSingleAxisTracker, true);
                        break;
                    case Trackable.VERTICAL_SINGLE_AXIS_TRACKER:
                        Util.selectSilently(miVerticalSingleAxisTracker, true);
                        break;
                    case Trackable.NO_TRACKER:
                        Util.selectSilently(miNoTracker, true);
                        break;
                }
                miAltazimuthDualAxisTracker.setEnabled(true);
                miHorizontalSingleAxisTracker.setEnabled(true);
                miVerticalSingleAxisTracker.setEnabled(true);
                if (sp.getContainer() instanceof Roof) {
                    final Roof roof = (Roof) sp.getContainer();
                    final boolean flat = Util.isZero(roof.getHeight());
                    miAltazimuthDualAxisTracker.setEnabled(flat);
                    miHorizontalSingleAxisTracker.setEnabled(flat);
                    miVerticalSingleAxisTracker.setEnabled(flat);
                } else if (sp.getContainer() instanceof Wall || sp.getContainer() instanceof Rack) {
                    miAltazimuthDualAxisTracker.setEnabled(false);
                    miHorizontalSingleAxisTracker.setEnabled(false);
                    miVerticalSingleAxisTracker.setEnabled(false);
                }
                if (sp.getTracker() != Trackable.NO_TRACKER) {
                    // vertical and tilted single-axis trackers can adjust the tilt angle
                    miTiltAngle.setEnabled(sp.getTracker() == Trackable.VERTICAL_SINGLE_AXIS_TRACKER || sp.getTracker() == Trackable.TILTED_SINGLE_AXIS_TRACKER);
                    // any tracker that will alter the azimuth angle should disable the menu item
                    miAzimuth.setEnabled(sp.getTracker() != Trackable.ALTAZIMUTH_DUAL_AXIS_TRACKER && sp.getTracker() != Trackable.VERTICAL_SINGLE_AXIS_TRACKER);
                } else {
                    miTiltAngle.setEnabled(true);
                    miAzimuth.setEnabled(true);
                    miBaseHeight.setEnabled(true);
                    if (sp.getContainer() instanceof Roof) {
                        final Roof roof = (Roof) sp.getContainer();
                        if (roof.getHeight() > 0) {
                            miTiltAngle.setEnabled(false);
                            miAzimuth.setEnabled(false);
                            miBaseHeight.setEnabled(false);
                        }
                    } else if (sp.getContainer() instanceof Wall || sp.getContainer() instanceof Rack) {
                        miTiltAngle.setEnabled(false);
                        miAzimuth.setEnabled(false);
                        miBaseHeight.setEnabled(false);
                    }
                }
            }
        });
        final JMenuItem miDeleteRow = new JMenuItem("Delete Row");
        miDeleteRow.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (!(selectedPart instanceof SolarPanel)) {
                    return;
                }
                SceneManager.getTaskManager().update(new Callable<Object>() {

                    @Override
                    public Object call() {
                        Scene.getInstance().removeAllSolarPanels(((SolarPanel) selectedPart).getRow());
                        return null;
                    }
                });
            }
        });
        popupMenuForSolarPanel.add(miDeleteRow);
        popupMenuForSolarPanel.addSeparator();
        popupMenuForSolarPanel.add(miModel);
        popupMenuForSolarPanel.add(miCells);
        popupMenuForSolarPanel.add(miSize);
        popupMenuForSolarPanel.add(miTemperatureEffects);
        popupMenuForSolarPanel.add(shadeToleranceMenu);
        popupMenuForSolarPanel.addSeparator();
        popupMenuForSolarPanel.add(miTiltAngle);
        popupMenuForSolarPanel.add(miAzimuth);
        popupMenuForSolarPanel.add(miBaseHeight);
        popupMenuForSolarPanel.add(miInverterEff);
        popupMenuForSolarPanel.add(orientationMenu);
        popupMenuForSolarPanel.add(trackerMenu);
        popupMenuForSolarPanel.addSeparator();
        popupMenuForSolarPanel.add(cbmiDisableEditPoint);
        popupMenuForSolarPanel.add(cbmiDrawSunBeam);
        popupMenuForSolarPanel.add(labelMenu);
        popupMenuForSolarPanel.addSeparator();
        JMenuItem mi = new JMenuItem("Daily Yield Analysis...");
        mi.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(final ActionEvent e) {
                if (EnergyPanel.getInstance().adjustCellSize()) {
                    return;
                }
                if (SceneManager.getInstance().getSelectedPart() instanceof SolarPanel) {
                    new PvDailyAnalysis().show();
                }
            }
        });
        popupMenuForSolarPanel.add(mi);
        mi = new JMenuItem("Annual Yield Analysis...");
        mi.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(final ActionEvent e) {
                if (EnergyPanel.getInstance().adjustCellSize()) {
                    return;
                }
                if (SceneManager.getInstance().getSelectedPart() instanceof SolarPanel) {
                    new PvAnnualAnalysis().show();
                }
            }
        });
        popupMenuForSolarPanel.add(mi);
    }
    return popupMenuForSolarPanel;
}
Also used : JPanel(javax.swing.JPanel) ChangeSolarCellPropertiesCommand(org.concord.energy3d.undo.ChangeSolarCellPropertiesCommand) ActionEvent(java.awt.event.ActionEvent) ChangeBaseHeightForAllSolarCollectorsCommand(org.concord.energy3d.undo.ChangeBaseHeightForAllSolarCollectorsCommand) SetShadeToleranceForSolarPanelsOnFoundationCommand(org.concord.energy3d.undo.SetShadeToleranceForSolarPanelsOnFoundationCommand) SetSolarTrackersForAllCommand(org.concord.energy3d.undo.SetSolarTrackersForAllCommand) PvAnnualAnalysis(org.concord.energy3d.simulation.PvAnnualAnalysis) SetSolarTrackerCommand(org.concord.energy3d.undo.SetSolarTrackerCommand) BorderLayout(java.awt.BorderLayout) ChangeTiltAngleForAllSolarPanelsCommand(org.concord.energy3d.undo.ChangeTiltAngleForAllSolarPanelsCommand) ChangeTiltAngleForSolarPanelRowCommand(org.concord.energy3d.undo.ChangeTiltAngleForSolarPanelRowCommand) Foundation(org.concord.energy3d.model.Foundation) LockEditPointsOnFoundationCommand(org.concord.energy3d.undo.LockEditPointsOnFoundationCommand) ChangeFoundationSolarPanelModelCommand(org.concord.energy3d.undo.ChangeFoundationSolarPanelModelCommand) HousePart(org.concord.energy3d.model.HousePart) SetSolarTrackersOnFoundationCommand(org.concord.energy3d.undo.SetSolarTrackersOnFoundationCommand) SetTemperatureEffectsCommand(org.concord.energy3d.undo.SetTemperatureEffectsCommand) ChangeAzimuthForAllSolarPanelsCommand(org.concord.energy3d.undo.ChangeAzimuthForAllSolarPanelsCommand) ChangeInverterEfficiencyForAllCommand(org.concord.energy3d.undo.ChangeInverterEfficiencyForAllCommand) SetTemperatrureEffectsForAllCommand(org.concord.energy3d.undo.SetTemperatrureEffectsForAllCommand) JRadioButtonMenuItem(javax.swing.JRadioButtonMenuItem) ChangeFoundationSolarPanelAzimuthCommand(org.concord.energy3d.undo.ChangeFoundationSolarPanelAzimuthCommand) ChooseSolarPanelSizeCommand(org.concord.energy3d.undo.ChooseSolarPanelSizeCommand) JOptionPane(javax.swing.JOptionPane) ChangeAzimuthCommand(org.concord.energy3d.undo.ChangeAzimuthCommand) JCheckBoxMenuItem(javax.swing.JCheckBoxMenuItem) ChangeSolarCellPropertiesForAllCommand(org.concord.energy3d.undo.ChangeSolarCellPropertiesForAllCommand) ActionListener(java.awt.event.ActionListener) ChangeModelForAllSolarPanelsCommand(org.concord.energy3d.undo.ChangeModelForAllSolarPanelsCommand) SolarPanel(org.concord.energy3d.model.SolarPanel) SpringLayout(javax.swing.SpringLayout) ChangeFoundationSolarCollectorBaseHeightCommand(org.concord.energy3d.undo.ChangeFoundationSolarCollectorBaseHeightCommand) ChangeFoundationSolarPanelTiltAngleCommand(org.concord.energy3d.undo.ChangeFoundationSolarPanelTiltAngleCommand) Map(java.util.Map) JDialog(javax.swing.JDialog) ItemEvent(java.awt.event.ItemEvent) JRadioButton(javax.swing.JRadioButton) Wall(org.concord.energy3d.model.Wall) ChangeBaseHeightForSolarPanelRowCommand(org.concord.energy3d.undo.ChangeBaseHeightForSolarPanelRowCommand) BoxLayout(javax.swing.BoxLayout) JTextField(javax.swing.JTextField) ChangeSolarPanelModelCommand(org.concord.energy3d.undo.ChangeSolarPanelModelCommand) Callable(java.util.concurrent.Callable) ChangeFoundationInverterEfficiencyCommand(org.concord.energy3d.undo.ChangeFoundationInverterEfficiencyCommand) Rack(org.concord.energy3d.model.Rack) RotateSolarPanelCommand(org.concord.energy3d.undo.RotateSolarPanelCommand) Roof(org.concord.energy3d.model.Roof) PvDailyAnalysis(org.concord.energy3d.simulation.PvDailyAnalysis) JMenuItem(javax.swing.JMenuItem) ChangeFoundationSolarCellPropertiesCommand(org.concord.energy3d.undo.ChangeFoundationSolarCellPropertiesCommand) PvModuleSpecs(org.concord.energy3d.simulation.PvModuleSpecs) LockEditPointsCommand(org.concord.energy3d.undo.LockEditPointsCommand) JComboBox(javax.swing.JComboBox) JLabel(javax.swing.JLabel) ShowSunBeamCommand(org.concord.energy3d.undo.ShowSunBeamCommand) ChangeTiltAngleCommand(org.concord.energy3d.undo.ChangeTiltAngleCommand) ChangeBaseHeightCommand(org.concord.energy3d.undo.ChangeBaseHeightCommand) LockEditPointsForClassCommand(org.concord.energy3d.undo.LockEditPointsForClassCommand) ButtonGroup(javax.swing.ButtonGroup) ChangeInverterEfficiencyCommand(org.concord.energy3d.undo.ChangeInverterEfficiencyCommand) ItemListener(java.awt.event.ItemListener) SetShadeToleranceCommand(org.concord.energy3d.undo.SetShadeToleranceCommand) SetShadeToleranceForAllSolarPanelsCommand(org.concord.energy3d.undo.SetShadeToleranceForAllSolarPanelsCommand) SetFoundationTemperatureEffectsCommand(org.concord.energy3d.undo.SetFoundationTemperatureEffectsCommand) JMenu(javax.swing.JMenu) SetSolarPanelLabelCommand(org.concord.energy3d.undo.SetSolarPanelLabelCommand)

Example 14 with SpringLayout

use of javax.swing.SpringLayout in project energy3d by concord-consortium.

the class PropertiesDialogForFoundation method getDialog.

static JDialog getDialog(final Foundation foundation) {
    final JDialog dialog = new JDialog(MainFrame.getInstance(), "Foundation", true);
    dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
    final String info = foundation.toString().substring(0, foundation.toString().indexOf(')') + 1);
    dialog.setTitle("Properties - " + info);
    dialog.getContentPane().setLayout(new BorderLayout());
    final JPanel panel = new JPanel(new SpringLayout());
    panel.setBorder(new EmptyBorder(8, 8, 8, 8));
    final JScrollPane scroller = new JScrollPane(panel);
    scroller.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
    scroller.setPreferredSize(new Dimension(300, 120));
    dialog.getContentPane().add(scroller, BorderLayout.CENTER);
    final double s = Scene.getInstance().getAnnotationScale();
    final Vector3 v0 = foundation.getAbsPoint(0);
    final Vector3 v1 = foundation.getAbsPoint(1);
    final Vector3 v2 = foundation.getAbsPoint(2);
    final Vector3 v3 = foundation.getAbsPoint(3);
    final double cx = 0.25 * (v0.getX() + v1.getX() + v2.getX() + v3.getX()) * s;
    final double cy = 0.25 * (v0.getY() + v1.getY() + v2.getY() + v3.getY()) * s;
    final double lx = v0.distance(v2) * s;
    final double ly = v0.distance(v1) * s;
    final double lz = foundation.getHeight() * s;
    int i = 0;
    panel.add(new JLabel("Size: "));
    final JTextField sizeField = new JTextField(PopupMenuFactory.threeDecimalsFormat.format(lx) + "\u00D7" + PopupMenuFactory.threeDecimalsFormat.format(ly) + "\u00D7" + PopupMenuFactory.threeDecimalsFormat.format(lz) + " m");
    sizeField.setEditable(false);
    panel.add(sizeField);
    i++;
    panel.add(new JLabel("Center: "));
    final JTextField reflectanceField = new JTextField("(" + PopupMenuFactory.threeDecimalsFormat.format(cx) + ", " + PopupMenuFactory.threeDecimalsFormat.format(cy) + ") m");
    reflectanceField.setEditable(false);
    panel.add(reflectanceField);
    i++;
    panel.add(new JLabel("Project Type: "));
    String project = "Auto";
    switch(foundation.getProjectType()) {
        case Foundation.TYPE_BUILDING:
            project = "Building";
            break;
        case Foundation.TYPE_PV_PROJECT:
            project = "PV";
            break;
        case Foundation.TYPE_CSP_PROJECT:
            project = "CSP";
            break;
    }
    final JTextField projectField = new JTextField(project);
    projectField.setEditable(false);
    panel.add(projectField);
    i++;
    panel.add(new JLabel("Group Master: "));
    final JTextField groupMasterField = new JTextField(foundation.isGroupMaster() ? "Yes" : "No");
    groupMasterField.setEditable(false);
    panel.add(groupMasterField);
    i++;
    SpringUtilities.makeCompactGrid(panel, i, 2, 4, 4, 4, 4);
    final JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
    dialog.getContentPane().add(buttonPanel, BorderLayout.SOUTH);
    final JButton button = new JButton("Close");
    button.addActionListener(new ActionListener() {

        @Override
        public void actionPerformed(final ActionEvent e) {
            dialog.dispose();
        }
    });
    buttonPanel.add(button);
    dialog.pack();
    return dialog;
}
Also used : JScrollPane(javax.swing.JScrollPane) JPanel(javax.swing.JPanel) FlowLayout(java.awt.FlowLayout) ActionEvent(java.awt.event.ActionEvent) JButton(javax.swing.JButton) JLabel(javax.swing.JLabel) Vector3(com.ardor3d.math.Vector3) Dimension(java.awt.Dimension) JTextField(javax.swing.JTextField) BorderLayout(java.awt.BorderLayout) ActionListener(java.awt.event.ActionListener) SpringLayout(javax.swing.SpringLayout) EmptyBorder(javax.swing.border.EmptyBorder) JDialog(javax.swing.JDialog)

Example 15 with SpringLayout

use of javax.swing.SpringLayout in project energy3d by concord-consortium.

the class PropertiesDialogForHeliostat method getDialog.

static JDialog getDialog(final Mirror mirror) {
    final JDialog dialog = new JDialog(MainFrame.getInstance(), "Heliostat", true);
    dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
    final String info = mirror.toString().substring(0, mirror.toString().indexOf(')') + 1);
    dialog.setTitle("Properties - " + info);
    dialog.getContentPane().setLayout(new BorderLayout());
    final JPanel panel = new JPanel(new SpringLayout());
    panel.setBorder(new EmptyBorder(8, 8, 8, 8));
    final JScrollPane scroller = new JScrollPane(panel);
    scroller.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
    scroller.setPreferredSize(new Dimension(300, 80));
    dialog.getContentPane().add(scroller, BorderLayout.CENTER);
    int i = 0;
    panel.add(new JLabel("Size: "));
    final JTextField sizeField = new JTextField(PopupMenuFactory.threeDecimalsFormat.format(mirror.getMirrorWidth()) + "\u00D7" + PopupMenuFactory.threeDecimalsFormat.format(mirror.getMirrorHeight()) + " m");
    sizeField.setEditable(false);
    panel.add(sizeField);
    i++;
    panel.add(new JLabel("Reflectance: "));
    final JTextField reflectanceField = new JTextField(PopupMenuFactory.threeDecimalsFormat.format(mirror.getReflectance() * 100) + "%");
    reflectanceField.setEditable(false);
    panel.add(reflectanceField);
    i++;
    SpringUtilities.makeCompactGrid(panel, i, 2, 4, 4, 4, 4);
    final JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
    dialog.getContentPane().add(buttonPanel, BorderLayout.SOUTH);
    final JButton button = new JButton("Close");
    button.addActionListener(new ActionListener() {

        @Override
        public void actionPerformed(final ActionEvent e) {
            dialog.dispose();
        }
    });
    buttonPanel.add(button);
    dialog.pack();
    return dialog;
}
Also used : JScrollPane(javax.swing.JScrollPane) JPanel(javax.swing.JPanel) FlowLayout(java.awt.FlowLayout) ActionEvent(java.awt.event.ActionEvent) JButton(javax.swing.JButton) JLabel(javax.swing.JLabel) Dimension(java.awt.Dimension) JTextField(javax.swing.JTextField) BorderLayout(java.awt.BorderLayout) ActionListener(java.awt.event.ActionListener) SpringLayout(javax.swing.SpringLayout) EmptyBorder(javax.swing.border.EmptyBorder) JDialog(javax.swing.JDialog)

Aggregations

SpringLayout (javax.swing.SpringLayout)57 JLabel (javax.swing.JLabel)21 JPanel (javax.swing.JPanel)21 Spring (javax.swing.Spring)21 BorderLayout (java.awt.BorderLayout)18 JTextField (javax.swing.JTextField)17 ActionEvent (java.awt.event.ActionEvent)15 ActionListener (java.awt.event.ActionListener)15 Component (java.awt.Component)12 JDialog (javax.swing.JDialog)12 Dimension (java.awt.Dimension)11 JButton (javax.swing.JButton)10 JScrollPane (javax.swing.JScrollPane)10 EmptyBorder (javax.swing.border.EmptyBorder)9 FlowLayout (java.awt.FlowLayout)8 JMenuItem (javax.swing.JMenuItem)7 Constraints (javax.swing.SpringLayout.Constraints)6 ItemEvent (java.awt.event.ItemEvent)5 Callable (java.util.concurrent.Callable)5 JMenu (javax.swing.JMenu)5