Search in sources :

Example 1 with ChangeTiltAngleCommand

use of org.concord.energy3d.undo.ChangeTiltAngleCommand 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 2 with ChangeTiltAngleCommand

use of org.concord.energy3d.undo.ChangeTiltAngleCommand 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 3 with ChangeTiltAngleCommand

use of org.concord.energy3d.undo.ChangeTiltAngleCommand in project energy3d by concord-consortium.

the class PopupMenuForHeliostat method getPopupMenu.

static JPopupMenu getPopupMenu() {
    if (popupMenuForHeliostat == null) {
        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 Mirror)) {
                    return;
                }
                final boolean disabled = cbmiDisableEditPoint.isSelected();
                final Mirror m = (Mirror) selectedPart;
                final String partInfo = m.toString().substring(0, m.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 Heliostat", true);
                final JRadioButton rb2 = new JRadioButton("All Heliostats on this Foundation");
                final JRadioButton rb3 = new JRadioButton("All Heliostats");
                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 heliostat prevents it from<br>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(m);
                        m.setLockEdit(disabled);
                        SceneManager.getInstance().getUndoManager().addEdit(c);
                        selectedScopeIndex = 0;
                    } else if (rb2.isSelected()) {
                        final Foundation foundation = m.getTopContainer();
                        final LockEditPointsOnFoundationCommand c = new LockEditPointsOnFoundationCommand(foundation, m.getClass());
                        foundation.setLockEditForClass(disabled, m.getClass());
                        SceneManager.getInstance().getUndoManager().addEdit(c);
                        selectedScopeIndex = 1;
                    } else if (rb3.isSelected()) {
                        final LockEditPointsForClassCommand c = new LockEditPointsForClassCommand(m);
                        Scene.getInstance().setLockEditForClass(disabled, m.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 Mirror)) {
                    return;
                }
                final Mirror m = (Mirror) selectedPart;
                final ShowSunBeamCommand c = new ShowSunBeamCommand(m);
                m.setSunBeamVisible(cbmiDrawSunBeam.isSelected());
                m.draw();
                SceneManager.getInstance().refresh();
                SceneManager.getInstance().getUndoManager().addEdit(c);
                Scene.getInstance().setEdited(true);
            }
        });
        final JMenuItem miSetHeliostat = new JMenuItem("Set Target Tower...");
        miSetHeliostat.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 Mirror)) {
                    return;
                }
                final Mirror m = (Mirror) selectedPart;
                final String partInfo = m.toString().substring(0, m.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 Heliostat", true);
                final JRadioButton rb2 = new JRadioButton("All Heliostats on this Foundation");
                final JRadioButton rb3 = new JRadioButton("All Heliostats");
                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 List<Foundation> foundations = Scene.getInstance().getAllFoundations();
                final JComboBox<String> comboBox = new JComboBox<String>();
                comboBox.addItemListener(new ItemListener() {

                    @Override
                    public void itemStateChanged(final ItemEvent e) {
                    }
                });
                comboBox.addItem("None");
                for (final Foundation x : foundations) {
                    if (!x.getChildren().isEmpty()) {
                        comboBox.addItem(x.getId() + "");
                    }
                }
                if (m.getReceiver() != null) {
                    comboBox.setSelectedItem(m.getReceiver().getId() + "");
                }
                gui.add(comboBox, BorderLayout.CENTER);
                final String title = "<html>Select the ID of the foundation<br>of the target tower for " + partInfo + "</html>";
                final String footnote = "<html><hr><font size=2>The sunlight reflected by the mirror(s) of this heliostat will<br>focus on the top of the target tower.<hr></html>";
                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(), "Heliostat Target");
                while (true) {
                    dialog.setVisible(true);
                    final Object choice = optionPane.getValue();
                    if (choice == options[1] || choice == null) {
                        break;
                    } else {
                        Foundation target = null;
                        if (comboBox.getSelectedIndex() > 0) {
                            boolean ok = true;
                            int id = -1;
                            try {
                                id = Integer.parseInt((String) comboBox.getSelectedItem());
                            } catch (final NumberFormatException exception) {
                                JOptionPane.showMessageDialog(MainFrame.getInstance(), comboBox.getSelectedItem() + " is an invalid value!", "Error", JOptionPane.ERROR_MESSAGE);
                                ok = false;
                            }
                            if (ok) {
                                final HousePart p = Scene.getInstance().getPart(id);
                                if (p instanceof Foundation) {
                                    target = (Foundation) p;
                                } else {
                                    JOptionPane.showMessageDialog(MainFrame.getInstance(), "ID must be that of a foundation.", "ID Error", JOptionPane.ERROR_MESSAGE);
                                }
                            }
                        }
                        boolean changed = target != m.getReceiver();
                        if (rb1.isSelected()) {
                            if (changed) {
                                final Foundation oldTarget = m.getReceiver();
                                final ChangeHeliostatTargetCommand c = new ChangeHeliostatTargetCommand(m);
                                m.setReceiver(target);
                                m.draw();
                                if (oldTarget != null) {
                                    oldTarget.drawSolarReceiver();
                                }
                                SceneManager.getInstance().refresh();
                                SceneManager.getInstance().getUndoManager().addEdit(c);
                            }
                            selectedScopeIndex = 0;
                        } else if (rb2.isSelected()) {
                            final Foundation foundation = m.getTopContainer();
                            if (!changed) {
                                for (final Mirror x : foundation.getHeliostats()) {
                                    if (target != x.getReceiver()) {
                                        changed = true;
                                        break;
                                    }
                                }
                            }
                            if (changed) {
                                final ChangeFoundationHeliostatTargetCommand c = new ChangeFoundationHeliostatTargetCommand(foundation);
                                foundation.setTargetForHeliostats(target);
                                SceneManager.getInstance().getUndoManager().addEdit(c);
                            }
                            selectedScopeIndex = 1;
                        } else if (rb3.isSelected()) {
                            if (!changed) {
                                for (final Mirror x : Scene.getInstance().getAllHeliostats()) {
                                    if (target != x.getReceiver()) {
                                        changed = true;
                                        break;
                                    }
                                }
                            }
                            if (changed) {
                                final ChangeTargetForAllHeliostatsCommand c = new ChangeTargetForAllHeliostatsCommand();
                                Scene.getInstance().setTargetForAllHeliostats(target);
                                SceneManager.getInstance().getUndoManager().addEdit(c);
                            }
                            selectedScopeIndex = 2;
                        }
                        if (changed) {
                            if (target != null) {
                                target.drawSolarReceiver();
                            }
                            updateAfterEdit();
                        }
                        if (choice == options[0]) {
                            break;
                        }
                    }
                }
            }
        });
        final JMenuItem miZenith = new JMenuItem("Tilt Angle...");
        miZenith.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 Mirror)) {
                    return;
                }
                final Mirror m = (Mirror) selectedPart;
                final String partInfo = m.toString().substring(0, m.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 the mirror of a heliostat 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 Heliostat", true);
                final JRadioButton rb2 = new JRadioButton("All Heliostats on this Foundation");
                final JRadioButton rb3 = new JRadioButton("All Heliostats");
                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 JTextField inputField = new JTextField(EnergyPanel.TWO_DECIMALS.format(m.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(), "Heliostat Mirror 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 - m.getTiltAngle()) > 0.000001;
                                if (rb1.isSelected()) {
                                    if (changed) {
                                        final ChangeTiltAngleCommand c = new ChangeTiltAngleCommand(m);
                                        m.setTiltAngle(val);
                                        m.draw();
                                        SceneManager.getInstance().refresh();
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 0;
                                } else if (rb2.isSelected()) {
                                    final Foundation foundation = m.getTopContainer();
                                    if (!changed) {
                                        for (final Mirror x : foundation.getHeliostats()) {
                                            if (Math.abs(val - x.getTiltAngle()) > 0.000001) {
                                                changed = true;
                                                break;
                                            }
                                        }
                                    }
                                    if (changed) {
                                        final ChangeFoundationHeliostatTiltAngleCommand c = new ChangeFoundationHeliostatTiltAngleCommand(foundation);
                                        foundation.setTiltAngleForHeliostats(val);
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 1;
                                } else if (rb3.isSelected()) {
                                    if (!changed) {
                                        for (final Mirror x : Scene.getInstance().getAllHeliostats()) {
                                            if (Math.abs(val - x.getTiltAngle()) > 0.000001) {
                                                changed = true;
                                                break;
                                            }
                                        }
                                    }
                                    if (changed) {
                                        final ChangeTiltAngleForAllHeliostatsCommand c = new ChangeTiltAngleForAllHeliostatsCommand();
                                        Scene.getInstance().setTiltAngleForAllHeliostats(val);
                                        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 Mirror)) {
                    return;
                }
                final String partInfo = selectedPart.toString().substring(0, selectedPart.toString().indexOf(')') + 1);
                final Mirror mirror = (Mirror) selectedPart;
                final Foundation foundation = mirror.getTopContainer();
                final String title = "<html>Azimuth Angle (&deg;) of " + partInfo + "</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 Heliostat", true);
                final JRadioButton rb2 = new JRadioButton("All Heliostats on this Foundation");
                final JRadioButton rb3 = new JRadioButton("All Heliostats");
                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;
                }
                double a = mirror.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(), "Heliostat 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 - mirror.getRelativeAzimuth()) > 0.000001;
                            if (rb1.isSelected()) {
                                if (changed) {
                                    final ChangeAzimuthCommand c = new ChangeAzimuthCommand(mirror);
                                    mirror.setRelativeAzimuth(a);
                                    mirror.draw();
                                    SceneManager.getInstance().refresh();
                                    SceneManager.getInstance().getUndoManager().addEdit(c);
                                }
                                selectedScopeIndex = 0;
                            } else if (rb2.isSelected()) {
                                if (!changed) {
                                    for (final Mirror x : foundation.getHeliostats()) {
                                        if (Math.abs(a - x.getRelativeAzimuth()) > 0.000001) {
                                            changed = true;
                                            break;
                                        }
                                    }
                                }
                                if (changed) {
                                    final ChangeFoundationHeliostatAzimuthCommand c = new ChangeFoundationHeliostatAzimuthCommand(foundation);
                                    foundation.setAzimuthForHeliostats(a);
                                    SceneManager.getInstance().getUndoManager().addEdit(c);
                                }
                                selectedScopeIndex = 1;
                            } else if (rb3.isSelected()) {
                                if (!changed) {
                                    for (final Mirror x : Scene.getInstance().getAllHeliostats()) {
                                        if (Math.abs(a - x.getRelativeAzimuth()) > 0.000001) {
                                            changed = true;
                                            break;
                                        }
                                    }
                                }
                                if (changed) {
                                    final ChangeAzimuthForAllHeliostatsCommand c = new ChangeAzimuthForAllHeliostatsCommand();
                                    Scene.getInstance().setAzimuthForAllHeliostats(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() {

            // 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 Mirror)) {
                    return;
                }
                final Mirror m = (Mirror) selectedPart;
                final Foundation foundation = m.getTopContainer();
                final String partInfo = m.toString().substring(0, selectedPart.toString().indexOf(')') + 1);
                final JPanel gui = new JPanel(new BorderLayout());
                final JPanel inputPanel = new JPanel(new GridLayout(2, 2, 5, 5));
                gui.add(inputPanel, BorderLayout.CENTER);
                inputPanel.add(new JLabel("Width: "));
                final JTextField widthField = new JTextField(threeDecimalsFormat.format(m.getMirrorWidth()));
                inputPanel.add(widthField);
                inputPanel.add(new JLabel("Length: "));
                final JTextField heightField = new JTextField(threeDecimalsFormat.format(m.getMirrorHeight()));
                inputPanel.add(heightField);
                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 Heliostat", true);
                final JRadioButton rb2 = new JRadioButton("All Heliostats on this Foundation");
                final JRadioButton rb3 = new JRadioButton("All Heliostats");
                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 size for " + partInfo, gui }, JOptionPane.QUESTION_MESSAGE, JOptionPane.YES_NO_CANCEL_OPTION, null, options, options[2]);
                final JDialog dialog = optionPane.createDialog(MainFrame.getInstance(), "Heliostat Size");
                while (true) {
                    dialog.setVisible(true);
                    final Object choice = optionPane.getValue();
                    if (choice == options[1] || choice == null) {
                        break;
                    } else {
                        double w = 0, h = 0;
                        boolean ok = true;
                        try {
                            w = Double.parseDouble(widthField.getText());
                            h = Double.parseDouble(heightField.getText());
                        } catch (final NumberFormatException x) {
                            JOptionPane.showMessageDialog(MainFrame.getInstance(), "Invalid input!", "Error", JOptionPane.ERROR_MESSAGE);
                            ok = false;
                        }
                        if (ok) {
                            if (w < 1 || w > 50) {
                                JOptionPane.showMessageDialog(MainFrame.getInstance(), "Width must be between 1 and 50 m.", "Range Error", JOptionPane.ERROR_MESSAGE);
                            } else if (h < 1 || h > 50) {
                                JOptionPane.showMessageDialog(MainFrame.getInstance(), "Height must be between 1 and 50 m.", "Range Error", JOptionPane.ERROR_MESSAGE);
                            } else {
                                boolean changed = Math.abs(w - m.getMirrorWidth()) > 0.000001 || Math.abs(h - m.getMirrorHeight()) > 0.000001;
                                if (rb1.isSelected()) {
                                    if (changed) {
                                        final SetPartSizeCommand c = new SetPartSizeCommand(m);
                                        m.setMirrorWidth(w);
                                        m.setMirrorHeight(h);
                                        m.draw();
                                        SceneManager.getInstance().refresh();
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 0;
                                } else if (rb2.isSelected()) {
                                    if (!changed) {
                                        for (final Mirror x : foundation.getHeliostats()) {
                                            if (Math.abs(w - x.getMirrorWidth()) > 0.000001 || Math.abs(h - x.getMirrorHeight()) > 0.000001) {
                                                changed = true;
                                                break;
                                            }
                                        }
                                    }
                                    if (changed) {
                                        final SetSizeForHeliostatsOnFoundationCommand c = new SetSizeForHeliostatsOnFoundationCommand(foundation);
                                        foundation.setSizeForHeliostats(w, h);
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 1;
                                } else if (rb3.isSelected()) {
                                    if (!changed) {
                                        for (final Mirror x : Scene.getInstance().getAllHeliostats()) {
                                            if (Math.abs(w - x.getMirrorWidth()) > 0.000001 || Math.abs(h - x.getMirrorHeight()) > 0.000001) {
                                                changed = true;
                                                break;
                                            }
                                        }
                                    }
                                    if (changed) {
                                        final SetSizeForAllHeliostatsCommand c = new SetSizeForAllHeliostatsCommand();
                                        Scene.getInstance().setSizeForAllHeliostats(w, h);
                                        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 Mirror)) {
                    return;
                }
                final String partInfo = selectedPart.toString().substring(0, selectedPart.toString().indexOf(')') + 1);
                final Mirror m = (Mirror) selectedPart;
                final Foundation foundation = m.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 Heliostat", true);
                final JRadioButton rb2 = new JRadioButton("All Heliostats on this Foundation");
                final JRadioButton rb3 = new JRadioButton("All Heliostats");
                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 JTextField inputField = new JTextField(EnergyPanel.TWO_DECIMALS.format(m.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(), "Heliostat 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 - m.getBaseHeight()) > 0.000001;
                            if (rb1.isSelected()) {
                                if (changed) {
                                    final ChangeBaseHeightCommand c = new ChangeBaseHeightCommand(m);
                                    m.setBaseHeight(val);
                                    m.draw();
                                    SceneManager.getInstance().refresh();
                                    SceneManager.getInstance().getUndoManager().addEdit(c);
                                }
                                selectedScopeIndex = 0;
                            } else if (rb2.isSelected()) {
                                if (!changed) {
                                    for (final Mirror x : foundation.getHeliostats()) {
                                        if (Math.abs(val - x.getBaseHeight()) > 0.000001) {
                                            changed = true;
                                            break;
                                        }
                                    }
                                }
                                if (changed) {
                                    final ChangeFoundationSolarCollectorBaseHeightCommand c = new ChangeFoundationSolarCollectorBaseHeightCommand(foundation, m.getClass());
                                    foundation.setBaseHeightForHeliostats(val);
                                    SceneManager.getInstance().getUndoManager().addEdit(c);
                                }
                                selectedScopeIndex = 1;
                            } else if (rb3.isSelected()) {
                                if (!changed) {
                                    for (final Mirror x : Scene.getInstance().getAllHeliostats()) {
                                        if (Math.abs(val - x.getBaseHeight()) > 0.000001) {
                                            changed = true;
                                            break;
                                        }
                                    }
                                }
                                if (changed) {
                                    final ChangeBaseHeightForAllSolarCollectorsCommand c = new ChangeBaseHeightForAllSolarCollectorsCommand(m.getClass());
                                    Scene.getInstance().setBaseHeightForAllHeliostats(val);
                                    SceneManager.getInstance().getUndoManager().addEdit(c);
                                }
                                selectedScopeIndex = 2;
                            }
                            if (changed) {
                                updateAfterEdit();
                            }
                            if (choice == options[0]) {
                                break;
                            }
                        }
                    }
                }
            }
        });
        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 Mirror) {
                        final Mirror m = (Mirror) selectedPart;
                        final SetHeliostatLabelCommand c = new SetHeliostatLabelCommand(m);
                        m.clearLabels();
                        m.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 Mirror) {
                    final Mirror m = (Mirror) selectedPart;
                    final SetHeliostatLabelCommand c = new SetHeliostatLabelCommand(m);
                    m.setLabelCustom(miLabelCustom.isSelected());
                    if (m.getLabelCustom()) {
                        m.setLabelCustomText(JOptionPane.showInputDialog(MainFrame.getInstance(), "Custom Text", m.getLabelCustomText()));
                    }
                    m.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 Mirror) {
                    final Mirror m = (Mirror) selectedPart;
                    final SetHeliostatLabelCommand c = new SetHeliostatLabelCommand(m);
                    m.setLabelId(miLabelId.isSelected());
                    m.draw();
                    SceneManager.getInstance().getUndoManager().addEdit(c);
                    Scene.getInstance().setEdited(true);
                    SceneManager.getInstance().refresh();
                }
            }
        });
        labelMenu.add(miLabelId);
        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 Mirror) {
                    final Mirror m = (Mirror) selectedPart;
                    final SetHeliostatLabelCommand c = new SetHeliostatLabelCommand(m);
                    m.setLabelEnergyOutput(miLabelEnergyOutput.isSelected());
                    m.draw();
                    SceneManager.getInstance().getUndoManager().addEdit(c);
                    Scene.getInstance().setEdited(true);
                    SceneManager.getInstance().refresh();
                }
            }
        });
        labelMenu.add(miLabelEnergyOutput);
        final JMenu textureMenu = new JMenu("Texture");
        final ButtonGroup textureButtonGroup = new ButtonGroup();
        final JRadioButtonMenuItem texture1MenuItem = new JRadioButtonMenuItem("Whole Mirror");
        texture1MenuItem.addItemListener(new ItemListener() {

            @Override
            public void itemStateChanged(final ItemEvent e) {
                if (e.getStateChange() == ItemEvent.SELECTED) {
                    final ChangeHeliostatTextureCommand c = new ChangeHeliostatTextureCommand();
                    Scene.getInstance().setHeliostatTextureType(Mirror.TEXTURE_ONE_MIRROR);
                    Scene.getInstance().setEdited(true);
                    if (MainPanel.getInstance().getEnergyButton().isSelected()) {
                        MainPanel.getInstance().getEnergyButton().setSelected(false);
                    }
                    Scene.getInstance().redrawAll();
                    SceneManager.getInstance().getUndoManager().addEdit(c);
                }
            }
        });
        textureButtonGroup.add(texture1MenuItem);
        textureMenu.add(texture1MenuItem);
        final JRadioButtonMenuItem texture2MenuItem = new JRadioButtonMenuItem("2 \u00D7 1 Mirrors");
        texture2MenuItem.addItemListener(new ItemListener() {

            @Override
            public void itemStateChanged(final ItemEvent e) {
                if (e.getStateChange() == ItemEvent.SELECTED) {
                    final ChangeHeliostatTextureCommand c = new ChangeHeliostatTextureCommand();
                    Scene.getInstance().setHeliostatTextureType(Mirror.TEXTURE_2X1_MIRRORS);
                    Scene.getInstance().setEdited(true);
                    if (MainPanel.getInstance().getEnergyButton().isSelected()) {
                        MainPanel.getInstance().getEnergyButton().setSelected(false);
                    }
                    Scene.getInstance().redrawAll();
                    SceneManager.getInstance().getUndoManager().addEdit(c);
                }
            }
        });
        textureButtonGroup.add(texture2MenuItem);
        textureMenu.add(texture2MenuItem);
        final JRadioButtonMenuItem texture3MenuItem = new JRadioButtonMenuItem("1 \u00D7 2 Mirrors");
        texture3MenuItem.addItemListener(new ItemListener() {

            @Override
            public void itemStateChanged(final ItemEvent e) {
                if (e.getStateChange() == ItemEvent.SELECTED) {
                    final ChangeHeliostatTextureCommand c = new ChangeHeliostatTextureCommand();
                    Scene.getInstance().setHeliostatTextureType(Mirror.TEXTURE_1X2_MIRRORS);
                    Scene.getInstance().setEdited(true);
                    if (MainPanel.getInstance().getEnergyButton().isSelected()) {
                        MainPanel.getInstance().getEnergyButton().setSelected(false);
                    }
                    Scene.getInstance().redrawAll();
                    SceneManager.getInstance().getUndoManager().addEdit(c);
                }
            }
        });
        textureButtonGroup.add(texture3MenuItem);
        textureMenu.add(texture3MenuItem);
        final JRadioButtonMenuItem texture4MenuItem = new JRadioButtonMenuItem("7 \u00D7 5 Mirrors");
        texture4MenuItem.addItemListener(new ItemListener() {

            @Override
            public void itemStateChanged(final ItemEvent e) {
                if (e.getStateChange() == ItemEvent.SELECTED) {
                    final ChangeHeliostatTextureCommand c = new ChangeHeliostatTextureCommand();
                    Scene.getInstance().setHeliostatTextureType(Mirror.TEXTURE_7X5_MIRRORS);
                    Scene.getInstance().setEdited(true);
                    if (MainPanel.getInstance().getEnergyButton().isSelected()) {
                        MainPanel.getInstance().getEnergyButton().setSelected(false);
                    }
                    Scene.getInstance().redrawAll();
                    SceneManager.getInstance().getUndoManager().addEdit(c);
                }
            }
        });
        textureButtonGroup.add(texture4MenuItem);
        textureMenu.add(texture4MenuItem);
        textureMenu.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) {
                switch(Scene.getInstance().getHeliostatTextureType()) {
                    default:
                        Util.selectSilently(texture1MenuItem, true);
                        break;
                    case Mirror.TEXTURE_2X1_MIRRORS:
                        Util.selectSilently(texture2MenuItem, true);
                        break;
                    case Mirror.TEXTURE_1X2_MIRRORS:
                        Util.selectSilently(texture3MenuItem, true);
                        break;
                    case Mirror.TEXTURE_7X5_MIRRORS:
                        Util.selectSilently(texture4MenuItem, true);
                        break;
                }
            }
        });
        popupMenuForHeliostat = createPopupMenu(true, true, new Runnable() {

            @Override
            public void run() {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (!(selectedPart instanceof Mirror)) {
                    return;
                }
                final Mirror m = (Mirror) selectedPart;
                if (m.getReceiver() == null) {
                    miZenith.setEnabled(true);
                    miAzimuth.setEnabled(true);
                } else {
                    miZenith.setEnabled(false);
                    miAzimuth.setEnabled(false);
                }
                Util.selectSilently(cbmiDisableEditPoint, m.getLockEdit());
                Util.selectSilently(cbmiDrawSunBeam, m.isSunBeamVisible());
                Util.selectSilently(miLabelNone, !m.isLabelVisible());
                Util.selectSilently(miLabelCustom, m.getLabelCustom());
                Util.selectSilently(miLabelId, m.getLabelId());
                Util.selectSilently(miLabelEnergyOutput, m.getLabelEnergyOutput());
            }
        });
        final JMenuItem miReflectance = new JMenuItem("Reflectance...");
        miReflectance.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 Mirror)) {
                    return;
                }
                final String partInfo = selectedPart.toString().substring(0, selectedPart.toString().indexOf(')') + 1);
                final Mirror m = (Mirror) selectedPart;
                final String title = "<html>Reflectance (%) of " + partInfo + "</html>";
                final String footnote = "<html><hr><font size=2>Reflectance can be affected by pollen and dust.<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 Heliostat", true);
                final JRadioButton rb2 = new JRadioButton("All Heliostats on this Foundation");
                final JRadioButton rb3 = new JRadioButton("All Heliostats");
                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 JTextField inputField = new JTextField(EnergyPanel.TWO_DECIMALS.format(m.getReflectance() * 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(), "Heliostat Mirror Reflectance");
                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 < 50 || val > 99) {
                                JOptionPane.showMessageDialog(MainFrame.getInstance(), "Heliostat mirror reflectance must be between 50% and 99%.", "Range Error", JOptionPane.ERROR_MESSAGE);
                            } else {
                                boolean changed = Math.abs(val * 0.01 - m.getReflectance()) > 0.000001;
                                if (rb1.isSelected()) {
                                    if (changed) {
                                        final ChangeSolarReflectorReflectanceCommand c = new ChangeSolarReflectorReflectanceCommand(m);
                                        m.setReflectance(val * 0.01);
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 0;
                                } else if (rb2.isSelected()) {
                                    final Foundation foundation = m.getTopContainer();
                                    if (!changed) {
                                        for (final Mirror x : foundation.getHeliostats()) {
                                            if (Math.abs(val * 0.01 - x.getReflectance()) > 0.000001) {
                                                changed = true;
                                                break;
                                            }
                                        }
                                    }
                                    if (changed) {
                                        final ChangeFoundationSolarReflectorReflectanceCommand c = new ChangeFoundationSolarReflectorReflectanceCommand(foundation, m.getClass());
                                        foundation.setReflectanceForHeliostatMirrors(val * 0.01);
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 1;
                                } else if (rb3.isSelected()) {
                                    if (!changed) {
                                        for (final Mirror x : Scene.getInstance().getAllHeliostats()) {
                                            if (Math.abs(val * 0.01 - x.getReflectance()) > 0.000001) {
                                                changed = true;
                                                break;
                                            }
                                        }
                                    }
                                    if (changed) {
                                        final ChangeReflectanceForAllSolarReflectorsCommand c = new ChangeReflectanceForAllSolarReflectorsCommand(m.getClass());
                                        Scene.getInstance().setReflectanceForAllSolarReflectors(val * 0.01, m.getClass());
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 2;
                                }
                                if (changed) {
                                    updateAfterEdit();
                                }
                                if (choice == options[0]) {
                                    break;
                                }
                            }
                        }
                    }
                }
            }
        });
        final JMenuItem miApertureRatio = new JMenuItem("Aperture Ratio...");
        miApertureRatio.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 Mirror)) {
                    return;
                }
                final String partInfo = selectedPart.toString().substring(0, selectedPart.toString().indexOf(')') + 1);
                final Mirror m = (Mirror) selectedPart;
                final String title = "<html>Aperture percentage of " + partInfo + "</html>";
                final String footnote = "<html><hr><font size=2>The percentage of the effective area for reflection<br>after deducting the area of gaps, frames, etc.<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 Heliostat", true);
                final JRadioButton rb2 = new JRadioButton("All Heliostats on this Foundation");
                final JRadioButton rb3 = new JRadioButton("All Heliostats");
                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 JTextField inputField = new JTextField(EnergyPanel.TWO_DECIMALS.format(m.getOpticalEfficiency() * 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(), "Aperture Percentage of Heliostat Surface");
                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 < 70 || val > 100) {
                                JOptionPane.showMessageDialog(MainFrame.getInstance(), "Heliostat aperature percentage must be between 70% and 100%.", "Range Error", JOptionPane.ERROR_MESSAGE);
                            } else {
                                boolean changed = Math.abs(val * 0.01 - m.getOpticalEfficiency()) > 0.000001;
                                if (rb1.isSelected()) {
                                    if (changed) {
                                        final ChangeSolarReflectorOpticalEfficiencyCommand c = new ChangeSolarReflectorOpticalEfficiencyCommand(m);
                                        m.setOpticalEfficiency(val * 0.01);
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 0;
                                } else if (rb2.isSelected()) {
                                    final Foundation foundation = m.getTopContainer();
                                    if (!changed) {
                                        for (final Mirror x : foundation.getHeliostats()) {
                                            if (Math.abs(val * 0.01 - x.getOpticalEfficiency()) > 0.000001) {
                                                changed = true;
                                                break;
                                            }
                                        }
                                    }
                                    if (changed) {
                                        final ChangeFoundationSolarReflectorOpticalEfficiencyCommand c = new ChangeFoundationSolarReflectorOpticalEfficiencyCommand(foundation, m.getClass());
                                        foundation.setOpticalEfficiencyForSolarReflectors(val * 0.01, m.getClass());
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 1;
                                } else if (rb3.isSelected()) {
                                    if (!changed) {
                                        for (final Mirror x : Scene.getInstance().getAllHeliostats()) {
                                            if (Math.abs(val * 0.01 - x.getOpticalEfficiency()) > 0.000001) {
                                                changed = true;
                                                break;
                                            }
                                        }
                                    }
                                    if (changed) {
                                        final ChangeOpticalEfficiencyForAllSolarReflectorsCommand c = new ChangeOpticalEfficiencyForAllSolarReflectorsCommand(m.getClass());
                                        Scene.getInstance().setOpticalEfficiencyForAllSolarReflectors(val * 0.01, m.getClass());
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 2;
                                }
                                if (changed) {
                                    updateAfterEdit();
                                }
                                if (choice == options[0]) {
                                    break;
                                }
                            }
                        }
                    }
                }
            }
        });
        final JMenuItem miConversionEfficiency = new JMenuItem("Central Receiver Conversion Efficiency...");
        miConversionEfficiency.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (!(selectedPart instanceof Mirror)) {
                    return;
                }
                final Mirror m = (Mirror) selectedPart;
                final Foundation receiver = m.getReceiver();
                if (receiver == null) {
                    JOptionPane.showMessageDialog(MainFrame.getInstance(), "This heliostat does not link to a receiver.", "No Receiver", JOptionPane.ERROR_MESSAGE);
                    return;
                }
                final String partInfo = selectedPart.toString().substring(0, selectedPart.toString().indexOf(')') + 1);
                final String title = "<html>Light-electricity conversion efficiency (%) of " + partInfo + "'s central receiver</html>";
                final String footnote = "<html><hr><font size=2><hr></html>";
                final JPanel gui = new JPanel(new BorderLayout());
                final JTextField inputField = new JTextField(EnergyPanel.TWO_DECIMALS.format(receiver.getSolarReceiverEfficiency() * 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(), "Receiver Conversion 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 < 5 || val > 50) {
                                JOptionPane.showMessageDialog(MainFrame.getInstance(), "Light-electricity conversion efficiency must be between 5% and 50%.", "Range Error", JOptionPane.ERROR_MESSAGE);
                            } else {
                                final boolean changed = Math.abs(val * 0.01 - receiver.getSolarReceiverEfficiency()) > 0.000001;
                                if (changed) {
                                    final ChangeSolarReceiverEfficiencyCommand c = new ChangeSolarReceiverEfficiencyCommand(receiver);
                                    receiver.setSolarReceiverEfficiency(val * 0.01);
                                    SceneManager.getInstance().getUndoManager().addEdit(c);
                                    updateAfterEdit();
                                }
                                if (choice == options[0]) {
                                    break;
                                }
                            }
                        }
                    }
                }
            }
        });
        popupMenuForHeliostat.addSeparator();
        popupMenuForHeliostat.add(miSetHeliostat);
        popupMenuForHeliostat.add(miZenith);
        popupMenuForHeliostat.add(miAzimuth);
        popupMenuForHeliostat.add(miSize);
        popupMenuForHeliostat.add(miBaseHeight);
        popupMenuForHeliostat.addSeparator();
        popupMenuForHeliostat.add(miReflectance);
        popupMenuForHeliostat.add(miApertureRatio);
        popupMenuForHeliostat.add(miConversionEfficiency);
        popupMenuForHeliostat.addSeparator();
        popupMenuForHeliostat.add(cbmiDisableEditPoint);
        popupMenuForHeliostat.add(cbmiDrawSunBeam);
        popupMenuForHeliostat.add(labelMenu);
        popupMenuForHeliostat.add(textureMenu);
        popupMenuForHeliostat.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 Mirror) {
                    new HeliostatDailyAnalysis().show();
                }
            }
        });
        popupMenuForHeliostat.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 Mirror) {
                    new HeliostatAnnualAnalysis().show();
                }
            }
        });
        popupMenuForHeliostat.add(mi);
    }
    return popupMenuForHeliostat;
}
Also used : JPanel(javax.swing.JPanel) ChangeFoundationHeliostatTiltAngleCommand(org.concord.energy3d.undo.ChangeFoundationHeliostatTiltAngleCommand) ActionEvent(java.awt.event.ActionEvent) SetPartSizeCommand(org.concord.energy3d.undo.SetPartSizeCommand) ChangeFoundationHeliostatAzimuthCommand(org.concord.energy3d.undo.ChangeFoundationHeliostatAzimuthCommand) ChangeSolarReflectorOpticalEfficiencyCommand(org.concord.energy3d.undo.ChangeSolarReflectorOpticalEfficiencyCommand) ChangeBaseHeightForAllSolarCollectorsCommand(org.concord.energy3d.undo.ChangeBaseHeightForAllSolarCollectorsCommand) BorderLayout(java.awt.BorderLayout) ChangeTargetForAllHeliostatsCommand(org.concord.energy3d.undo.ChangeTargetForAllHeliostatsCommand) Foundation(org.concord.energy3d.model.Foundation) LockEditPointsOnFoundationCommand(org.concord.energy3d.undo.LockEditPointsOnFoundationCommand) List(java.util.List) ChangeReflectanceForAllSolarReflectorsCommand(org.concord.energy3d.undo.ChangeReflectanceForAllSolarReflectorsCommand) HousePart(org.concord.energy3d.model.HousePart) HeliostatDailyAnalysis(org.concord.energy3d.simulation.HeliostatDailyAnalysis) JRadioButtonMenuItem(javax.swing.JRadioButtonMenuItem) JOptionPane(javax.swing.JOptionPane) ChangeAzimuthCommand(org.concord.energy3d.undo.ChangeAzimuthCommand) JCheckBoxMenuItem(javax.swing.JCheckBoxMenuItem) ChangeHeliostatTextureCommand(org.concord.energy3d.undo.ChangeHeliostatTextureCommand) ActionListener(java.awt.event.ActionListener) ChangeFoundationSolarCollectorBaseHeightCommand(org.concord.energy3d.undo.ChangeFoundationSolarCollectorBaseHeightCommand) Mirror(org.concord.energy3d.model.Mirror) JDialog(javax.swing.JDialog) ItemEvent(java.awt.event.ItemEvent) HeliostatAnnualAnalysis(org.concord.energy3d.simulation.HeliostatAnnualAnalysis) JRadioButton(javax.swing.JRadioButton) ChangeSolarReflectorReflectanceCommand(org.concord.energy3d.undo.ChangeSolarReflectorReflectanceCommand) MenuListener(javax.swing.event.MenuListener) BoxLayout(javax.swing.BoxLayout) JTextField(javax.swing.JTextField) SetSizeForHeliostatsOnFoundationCommand(org.concord.energy3d.undo.SetSizeForHeliostatsOnFoundationCommand) ChangeHeliostatTargetCommand(org.concord.energy3d.undo.ChangeHeliostatTargetCommand) GridLayout(java.awt.GridLayout) JMenuItem(javax.swing.JMenuItem) SetHeliostatLabelCommand(org.concord.energy3d.undo.SetHeliostatLabelCommand) MenuEvent(javax.swing.event.MenuEvent) 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) ChangeOpticalEfficiencyForAllSolarReflectorsCommand(org.concord.energy3d.undo.ChangeOpticalEfficiencyForAllSolarReflectorsCommand) ChangeSolarReceiverEfficiencyCommand(org.concord.energy3d.undo.ChangeSolarReceiverEfficiencyCommand) SetSizeForAllHeliostatsCommand(org.concord.energy3d.undo.SetSizeForAllHeliostatsCommand) LockEditPointsForClassCommand(org.concord.energy3d.undo.LockEditPointsForClassCommand) ChangeFoundationSolarReflectorReflectanceCommand(org.concord.energy3d.undo.ChangeFoundationSolarReflectorReflectanceCommand) ChangeFoundationSolarReflectorOpticalEfficiencyCommand(org.concord.energy3d.undo.ChangeFoundationSolarReflectorOpticalEfficiencyCommand) ButtonGroup(javax.swing.ButtonGroup) ChangeAzimuthForAllHeliostatsCommand(org.concord.energy3d.undo.ChangeAzimuthForAllHeliostatsCommand) ChangeFoundationHeliostatTargetCommand(org.concord.energy3d.undo.ChangeFoundationHeliostatTargetCommand) ItemListener(java.awt.event.ItemListener) JMenu(javax.swing.JMenu) ChangeTiltAngleForAllHeliostatsCommand(org.concord.energy3d.undo.ChangeTiltAngleForAllHeliostatsCommand)

Aggregations

BorderLayout (java.awt.BorderLayout)3 ActionEvent (java.awt.event.ActionEvent)3 ActionListener (java.awt.event.ActionListener)3 ItemEvent (java.awt.event.ItemEvent)3 ItemListener (java.awt.event.ItemListener)3 BoxLayout (javax.swing.BoxLayout)3 ButtonGroup (javax.swing.ButtonGroup)3 JCheckBoxMenuItem (javax.swing.JCheckBoxMenuItem)3 JComboBox (javax.swing.JComboBox)3 JDialog (javax.swing.JDialog)3 JLabel (javax.swing.JLabel)3 JMenu (javax.swing.JMenu)3 JMenuItem (javax.swing.JMenuItem)3 JOptionPane (javax.swing.JOptionPane)3 JPanel (javax.swing.JPanel)3 JRadioButton (javax.swing.JRadioButton)3 JRadioButtonMenuItem (javax.swing.JRadioButtonMenuItem)3 JTextField (javax.swing.JTextField)3 Foundation (org.concord.energy3d.model.Foundation)3 HousePart (org.concord.energy3d.model.HousePart)3