Search in sources :

Example 1 with PvDailyAnalysis

use of org.concord.energy3d.simulation.PvDailyAnalysis 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 PvDailyAnalysis

use of org.concord.energy3d.simulation.PvDailyAnalysis 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 PvDailyAnalysis

use of org.concord.energy3d.simulation.PvDailyAnalysis in project energy3d by concord-consortium.

the class TimeSeriesLogger method record.

private void record() {
    final URL url = Scene.getURL();
    if (url == null) {
        return;
    }
    /* write the header */
    String filename = url == null ? null : new File(url.getFile()).getName();
    if (Scene.isInternalFile()) {
        filename = "@" + filename;
    }
    final Date time = Calendar.getInstance().getTime();
    final String timestamp = TIME_FORMAT.format(time);
    String line = "";
    if (Scene.getInstance().getProjectName() != null && !Scene.getInstance().getProjectName().trim().equals("")) {
        line += "\"Project\": \"" + Scene.getInstance().getProjectName() + "\", ";
    }
    line += "\"File\": \"" + filename + "\"";
    if (action != null) {
        if ((lastTime != null && Math.abs(time.getTime() - lastTime.getTime()) < MINIMUM_INTERVAL) && action.equals(lastAction)) {
            // don't log too frequently for the same type of action
            return;
        }
        HousePart actedPart = null;
        String stateValue = null;
        // special treatments
        if (action.equals("Undo")) {
            String s = SceneManager.getInstance().getUndoManager().getRedoPresentationName();
            if (s.length() >= 4) {
                s = s.substring(4, s.length()).trim();
            }
            stateValue = "\"" + s + "\"";
        } else if (action.equals("Redo")) {
            String s = SceneManager.getInstance().getUndoManager().getUndoPresentationName();
            if (s.length() >= 4) {
                s = s.substring(4, s.length()).trim();
            }
            stateValue = "\"" + s + "\"";
        } else if (action.equals("Save")) {
            // append * at the end so that the ng3 suffix is not interpreted as a delimiter
            stateValue = "\"" + Scene.getURL().toString() + "*\"";
        } else if (action.equals("Note")) {
            final String s = MainPanel.getInstance().getNoteString();
            if (s.length() > 0) {
                stateValue = "\"" + s + "\"";
                MainPanel.getInstance().setNoteString("");
            }
        } else if (action.equals("Camera")) {
            stateValue = "{" + cameraPosition + ", \"Mode\": \"" + cameraMode + "\"}";
        } else if (action.equals("Graph Tab")) {
            stateValue = "\"" + graphTabName + "\"";
        } else if (action.equals("Adjust Thermostat")) {
            final HousePart p = SceneManager.getInstance().getSelectedPart();
            if (p instanceof Foundation) {
                stateValue = "{\"Building\": " + p.getId() + "}";
            }
        } else if (action.equals("Show Curve")) {
            stateValue = "{\"Graph\": \"" + graphName + "\", \"Name\": \"" + curveName + "\", \"Shown\": " + curveShown + "}";
        } else if (action.equals("Show Run")) {
            stateValue = "{\"Graph\": \"" + graphName + "\", \"ID\": \"" + runID + "\", \"Shown\": " + runShown + "}";
        } else if (action.equals("Clear Graph Data")) {
            stateValue = "\"" + graphName + "\"";
        } else {
            /* everything else */
            final UndoableEdit lastEdit = SceneManager.getInstance().getUndoManager().lastEdit();
            if (lastEdit instanceof AddPartCommand) {
                actedPart = ((AddPartCommand) lastEdit).getPart();
            } else if (lastEdit instanceof AddMultiplePartsCommand) {
                final AddMultiplePartsCommand c = (AddMultiplePartsCommand) lastEdit;
                if (c.getURL() != null) {
                    stateValue = "{\"Import\": \"" + c.getURL() + "\"}";
                }
            } else if (lastEdit instanceof AddArrayCommand) {
                final AddArrayCommand c = (AddArrayCommand) lastEdit;
                final HousePart p = c.getParent();
                if (p instanceof Foundation) {
                    final Foundation f = (Foundation) p;
                    stateValue = "{\"Foundation\": " + f.getId() + ", \"Old Array Size\": " + c.getOldArray().size() + ", \"New Array Size\": " + f.countParts(c.getTypes()) + "}";
                } else if (p instanceof Rack) {
                    final Foundation f = p.getTopContainer();
                    stateValue = "{\"Foundation\": " + f.getId() + ", \"Rack\": " + p.getId() + ", \"Old Array Size\": " + c.getOldArray().size() + ", \"New Array Size\": " + p.getChildren().size() + "}";
                }
            } else if (lastEdit instanceof PastePartCommand) {
                actedPart = ((PastePartCommand) lastEdit).getPart();
                if (actedPart instanceof Foundation) {
                    stateValue = "{\"Building\": " + actedPart.getId() + "}";
                    // FIXME: work around a bug related to pasting a building, fix later
                    actedPart = null;
                }
            } else if (lastEdit instanceof EditPartCommand) {
                actedPart = ((EditPartCommand) lastEdit).getPart();
            } else if (lastEdit instanceof RemovePartCommand) {
                actedPart = ((RemovePartCommand) lastEdit).getPart();
            } else if (lastEdit instanceof RemoveMultiplePartsCommand) {
                final Foundation f = ((RemoveMultiplePartsCommand) lastEdit).getFoundation();
                if (f != null) {
                    stateValue = "{\"Building\": " + f.getId() + "}";
                }
            } else if (lastEdit instanceof RotateBuildingCommand) {
                final RotateBuildingCommand c = (RotateBuildingCommand) lastEdit;
                final Foundation f = c.getFoundation();
                if (f != null) {
                    stateValue = "{\"Building\": " + f.getId() + ", \"Angle\": " + Math.toDegrees(c.getRotationAngle()) + "}";
                } else {
                    stateValue = "{\"Angle\": " + Math.toDegrees(c.getRotationAngle()) + "}";
                }
            } else if (lastEdit instanceof RescaleBuildingCommand) {
                final RescaleBuildingCommand c = (RescaleBuildingCommand) lastEdit;
                final Foundation f = c.getFoundation();
                if (f != null) {
                    stateValue = "{\"Building\": " + f.getId();
                    stateValue += ", \"Old X Length\": " + c.getOldXLength() + ", \"New X Length\": " + c.getNewXLength();
                    stateValue += ", \"Old Y Length\": " + c.getOldYLength() + ", \"New Y Length\": " + c.getNewYLength();
                    stateValue += ", \"Old Z Length\": " + c.getOldZLength() + ", \"New Z Length\": " + c.getNewZLength();
                    stateValue += "}";
                }
            } else if (lastEdit instanceof MovePartCommand) {
                final MovePartCommand c = (MovePartCommand) lastEdit;
                final HousePart p = c.getPart();
                final Vector3 d = c.getDisplacement();
                final String s = "\"(" + d.getX() + ", " + d.getY() + ")\"";
                if (p != null) {
                    stateValue = "{\"Part\": " + p.getId() + ", \"Displacement\": " + s + "}";
                } else {
                    stateValue = "{\"Displacement\": " + s + "}";
                }
            } else if (lastEdit instanceof DeleteUtilityBillCommand) {
                final DeleteUtilityBillCommand c = (DeleteUtilityBillCommand) lastEdit;
                final Foundation f = c.getFoundation();
                stateValue = "{\"Building\": " + f.getId() + "}";
            } else if (lastEdit instanceof AnimateSunCommand) {
                stateValue = "" + ((AnimateSunCommand) lastEdit).getNewValue();
            } else if (lastEdit instanceof ShowShadowCommand) {
                stateValue = "" + ((ShowShadowCommand) lastEdit).getNewValue();
            } else if (lastEdit instanceof ShowHeatFluxCommand) {
                stateValue = "" + ((ShowHeatFluxCommand) lastEdit).getNewValue();
            } else if (lastEdit instanceof SpinViewCommand) {
                stateValue = "" + ((SpinViewCommand) lastEdit).getNewValue();
            } else if (lastEdit instanceof ShowAxesCommand) {
                stateValue = "" + ((ShowAxesCommand) lastEdit).getNewValue();
            } else if (lastEdit instanceof ShowAnnotationCommand) {
                stateValue = "" + ((ShowAnnotationCommand) lastEdit).getNewValue();
            } else if (lastEdit instanceof ShowHeliodonCommand) {
                stateValue = "" + ((ShowHeliodonCommand) lastEdit).getNewValue();
            } else if (lastEdit instanceof TopViewCommand) {
                stateValue = "" + (SceneManager.getInstance().getViewMode() == ViewMode.TOP_VIEW);
            } else if (lastEdit instanceof ZoomCommand) {
                stateValue = "" + ((ZoomCommand) lastEdit).getValue();
            } else if (lastEdit instanceof RescaleCommand) {
                final RescaleCommand c = (RescaleCommand) lastEdit;
                stateValue = "{\"Old Value\": " + c.getOldValue() + ", \"New Value\": " + Scene.getInstance().getAnnotationScale() + "}";
            } else if (lastEdit instanceof ChangeBackgroundAlbedoCommand) {
                final ChangeBackgroundAlbedoCommand c = (ChangeBackgroundAlbedoCommand) lastEdit;
                stateValue = "{\"Old Value\": " + c.getOldValue() + ", \"New Value\": " + Scene.getInstance().getGround().getAlbedo() + "}";
            } else if (lastEdit instanceof ChangeGroundThermalDiffusivityCommand) {
                final ChangeGroundThermalDiffusivityCommand c = (ChangeGroundThermalDiffusivityCommand) lastEdit;
                stateValue = "{\"Old Value\": " + c.getOldValue() + ", \"New Value\": " + Scene.getInstance().getGround().getThermalDiffusivity() + "}";
            } else if (lastEdit instanceof ChangeAtmosphericDustLossCommand) {
                final ChangeAtmosphericDustLossCommand c = (ChangeAtmosphericDustLossCommand) lastEdit;
                final double[] oldValues = c.getOldValue();
                String oldValueString = "\"";
                for (final double x : oldValues) {
                    oldValueString += x + " ";
                }
                oldValueString.trim();
                oldValueString += "\"";
                String newValueString = "\"";
                for (int i = 0; i < 12; i++) {
                    newValueString += Scene.getInstance().getAtmosphere().getDustLoss(i) + " ";
                }
                newValueString.trim();
                newValueString += "\"";
                stateValue = "{\"Old Values\": " + oldValueString + ", \"New Values\": " + newValueString + "}";
            } else if (lastEdit instanceof ChangeSnowReflectionFactorCommand) {
                final ChangeSnowReflectionFactorCommand c = (ChangeSnowReflectionFactorCommand) lastEdit;
                final double[] oldValues = c.getOldValue();
                String oldValueString = "\"";
                for (final double x : oldValues) {
                    oldValueString += x + " ";
                }
                oldValueString.trim();
                oldValueString += "\"";
                String newValueString = "\"";
                for (int i = 0; i < 12; i++) {
                    newValueString += Scene.getInstance().getGround().getSnowReflectionFactor(i) + " ";
                }
                newValueString.trim();
                newValueString += "\"";
                stateValue = "{\"Old Values\": " + oldValueString + ", \"New Values\": " + newValueString + "}";
            } else if (lastEdit instanceof ChangeSolarHeatMapColorContrastCommand) {
                final ChangeSolarHeatMapColorContrastCommand c = (ChangeSolarHeatMapColorContrastCommand) lastEdit;
                stateValue = "{\"Old Value\": " + c.getOldValue() + ", \"New Value\": " + Scene.getInstance().getSolarHeatMapColorContrast() + "}";
            } else if (lastEdit instanceof ChangeLatitudeCommand) {
                final ChangeLatitudeCommand c = (ChangeLatitudeCommand) lastEdit;
                stateValue = "{\"Old Value\": " + Math.round(Math.toDegrees(c.getOldValue())) + ", \"New Value\": " + Math.round(Math.toDegrees(Heliodon.getInstance().getLatitude())) + "}";
            } else if (lastEdit instanceof ChangeCityCommand) {
                final ChangeCityCommand c = (ChangeCityCommand) lastEdit;
                stateValue = "{\"Old City\": \"" + c.getOldValue() + "\", \"New City\": \"" + Scene.getInstance().getCity() + "\"}";
            } else if (lastEdit instanceof ChangeDateCommand) {
                final ChangeDateCommand c = (ChangeDateCommand) lastEdit;
                final Calendar calendar = new GregorianCalendar();
                calendar.setTime(c.getOldDate());
                stateValue = "{\"Old Date\": \"" + (calendar.get(Calendar.MONTH) + 1) + "/" + calendar.get(Calendar.DAY_OF_MONTH) + "\"";
                calendar.setTime(Scene.getInstance().getDate());
                stateValue += ", \"New Date\": \"" + (calendar.get(Calendar.MONTH) + 1) + "/" + calendar.get(Calendar.DAY_OF_MONTH) + "\"}";
            } else if (lastEdit instanceof ChangeTimeCommand) {
                final ChangeTimeCommand c = (ChangeTimeCommand) lastEdit;
                final Calendar cal0 = new GregorianCalendar();
                cal0.setTime(c.getOldTime());
                stateValue = "{\"Old Time\": \"" + (cal0.get(Calendar.HOUR_OF_DAY)) + ":" + cal0.get(Calendar.MINUTE) + "\"";
                final Calendar cal1 = Heliodon.getInstance().getCalendar();
                stateValue += ", \"New Time\": \"" + (cal1.get(Calendar.HOUR_OF_DAY)) + ":" + cal1.get(Calendar.MINUTE) + "\"}";
            } else if (lastEdit instanceof ChangeTimeAndDateWithHeliodonCommand) {
                final ChangeTimeAndDateWithHeliodonCommand c = (ChangeTimeAndDateWithHeliodonCommand) lastEdit;
                final Calendar oldCal = new GregorianCalendar();
                oldCal.setTime(c.getOldTime());
                final Calendar newCal = Heliodon.getInstance().getCalendar();
                stateValue = "{\"Old Time\": \"" + (oldCal.get(Calendar.HOUR_OF_DAY)) + ":" + oldCal.get(Calendar.MINUTE) + "\"";
                stateValue += ", \"New Time\": \"" + (newCal.get(Calendar.HOUR_OF_DAY)) + ":" + newCal.get(Calendar.MINUTE) + "\"";
                stateValue += ", \"Old Date\": \"" + (oldCal.get(Calendar.MONTH) + 1) + "/" + oldCal.get(Calendar.DAY_OF_MONTH) + "\"";
                stateValue += ", \"New Date\": \"" + (newCal.get(Calendar.MONTH) + 1) + "/" + newCal.get(Calendar.DAY_OF_MONTH) + "\"}";
            } else if (lastEdit instanceof ChangeBuildingTextureCommand) {
                stateValue = "{\"Old Value\": ";
                TextureMode textureMode = ((ChangeBuildingTextureCommand) lastEdit).getOldValue();
                if (textureMode == TextureMode.Full) {
                    stateValue += "\"Full\"";
                } else if (textureMode == TextureMode.Simple) {
                    stateValue += "\"Simple\"";
                } else if (textureMode == TextureMode.None) {
                    stateValue += "\"None\"";
                }
                stateValue += ", \"New Value\": ";
                textureMode = Scene.getInstance().getTextureMode();
                if (textureMode == TextureMode.Full) {
                    stateValue += "\"Full\"";
                } else if (textureMode == TextureMode.Simple) {
                    stateValue += "\"Simple\"";
                } else if (textureMode == TextureMode.None) {
                    stateValue += "\"None\"";
                }
                stateValue += "}";
            } else if (lastEdit instanceof ChangeThemeCommand) {
                stateValue = "{\"Old Value\": " + ((ChangeThemeCommand) lastEdit).getOldValue() + ", \"New Value\": " + Scene.getInstance().getTheme() + "}";
            } else if (lastEdit instanceof ChangeRoofOverhangCommand) {
                final ChangeRoofOverhangCommand c = (ChangeRoofOverhangCommand) lastEdit;
                final Roof r = c.getRoof();
                stateValue = "{\"Building\": " + r.getTopContainer().getId() + ", \"ID\": " + r.getId();
                stateValue += ", \"Old Value\": " + c.getOldValue() * Scene.getInstance().getAnnotationScale();
                stateValue += ", \"New Value\": " + r.getOverhangLength() * Scene.getInstance().getAnnotationScale() + "}";
            } else if (lastEdit instanceof ChangeFoundationSizeCommand) {
                final ChangeFoundationSizeCommand c = (ChangeFoundationSizeCommand) lastEdit;
                final Foundation f = c.getFoundation();
                stateValue = "{\"Foundation\": " + f.getId();
                stateValue += ", \"Old Length\": " + c.getOldLength();
                stateValue += ", \"New Length\": " + c.getNewLength();
                stateValue += ", \"Old Width\": " + c.getOldWidth();
                stateValue += ", \"New Width\": " + c.getNewWidth();
                stateValue += ", \"Old Height\": " + c.getOldHeight();
                stateValue += ", \"New Height\": " + c.getNewHeight() + "}";
            } else if (lastEdit instanceof AdjustThermostatCommand) {
                final Foundation f = ((AdjustThermostatCommand) lastEdit).getFoundation();
                stateValue = "{\"Building\":" + f.getId() + "}";
            } else if (lastEdit instanceof ChangePartColorCommand) {
                final ChangePartColorCommand c = (ChangePartColorCommand) lastEdit;
                final HousePart p = c.getPart();
                final Foundation f = p instanceof Foundation ? (Foundation) p : p.getTopContainer();
                stateValue = "{\"Building\": " + f.getId() + ", \"ID\": " + p.getId();
                stateValue += ", \"Type\": \"" + p.getClass().getSimpleName() + "\"";
                stateValue += ", \"Old Color\": \"" + Util.toString(c.getOldColor()) + "\", \"New Color\": \"" + Util.toString(p.getColor()) + "\"}";
            } else if (lastEdit instanceof ChangeContainerWindowColorCommand) {
                final ChangeContainerWindowColorCommand cmd = (ChangeContainerWindowColorCommand) lastEdit;
                final HousePart container = cmd.getContainer();
                final List<Window> windows = Scene.getInstance().getWindowsOnContainer(container);
                final String containerType = container instanceof Wall ? "Wall" : "Roof";
                stateValue = "{\"" + containerType + "\":" + container.getId() + ", \"New Color\": \"" + Util.toString(windows.get(0).getColor()) + "\"}";
            } else if (lastEdit instanceof ChangeBuildingColorCommand) {
                final ChangeBuildingColorCommand c = (ChangeBuildingColorCommand) lastEdit;
                final HousePart p = c.getPart();
                String s = "{\"Building\":" + c.getFoundation().getId();
                s += ", \"Type\": \"" + p.getClass().getSimpleName() + "\"";
                s += ", \"New Color\": \"" + Util.toString(p.getColor()) + "\"}";
                stateValue = s;
            } else if (lastEdit instanceof ChangeLandColorCommand) {
                final ChangeLandColorCommand c = (ChangeLandColorCommand) lastEdit;
                stateValue = "{\"Old Color\": \"" + Util.toString(c.getOldColor()) + "\", \"New Color\": \"" + Util.toString(Scene.getInstance().getLandColor()) + "\"}";
            } else if (lastEdit instanceof ChangePartUValueCommand) {
                final ChangePartUValueCommand c = (ChangePartUValueCommand) lastEdit;
                final HousePart p = c.getPart();
                if (p instanceof Thermal) {
                    final Foundation f = p instanceof Foundation ? (Foundation) p : p.getTopContainer();
                    stateValue = "{\"Building\":" + f.getId() + ", \"ID\":" + p.getId();
                    stateValue += ", \"Type\": \"" + p.getClass().getSimpleName() + "\"";
                    stateValue += ", \"Old Value\": " + c.getOldValue();
                    stateValue += ", \"New Value\": " + ((Thermal) p).getUValue() + "}";
                }
            } else if (lastEdit instanceof ChangeBuildingUValueCommand) {
                final ChangeBuildingUValueCommand c = (ChangeBuildingUValueCommand) lastEdit;
                final HousePart p = c.getPart();
                if (p instanceof Thermal) {
                    final Foundation f = p instanceof Foundation ? (Foundation) p : p.getTopContainer();
                    stateValue = "{\"Building\":" + f.getId();
                    stateValue += ", \"Type\": \"" + p.getClass().getSimpleName() + "\"";
                    stateValue += ", \"New Value\": " + ((Thermal) p).getUValue() + "}";
                }
            } else if (lastEdit instanceof ChangeVolumetricHeatCapacityCommand) {
                final ChangeVolumetricHeatCapacityCommand c = (ChangeVolumetricHeatCapacityCommand) lastEdit;
                final HousePart p = c.getPart();
                if (p instanceof Thermal) {
                    final Foundation f = p instanceof Foundation ? (Foundation) p : p.getTopContainer();
                    stateValue = "{\"Building\":" + f.getId() + ", \"ID\":" + p.getId();
                    stateValue += ", \"Type\": \"" + p.getClass().getSimpleName() + "\"";
                    stateValue += ", \"Old Value\": " + c.getOldValue();
                    stateValue += ", \"New Value\": " + ((Thermal) p).getVolumetricHeatCapacity() + "}";
                }
            } else if (lastEdit instanceof ChangeFoundationSolarCollectorBaseHeightCommand) {
                final ChangeFoundationSolarCollectorBaseHeightCommand c = (ChangeFoundationSolarCollectorBaseHeightCommand) lastEdit;
                stateValue = "{\"Foundation\": " + c.getFoundation().getId() + ", \"New Value\": " + c.getFirstSolarCollector().getBaseHeight() + "}";
            } else if (lastEdit instanceof ChangeBaseHeightForAllSolarCollectorsCommand) {
                final ChangeBaseHeightForAllSolarCollectorsCommand c = (ChangeBaseHeightForAllSolarCollectorsCommand) lastEdit;
                stateValue = "{\"New Value\": " + c.getFirstSolarCollector().getBaseHeight() + "}";
            } else if (lastEdit instanceof ChangeTiltAngleCommand) {
                final ChangeTiltAngleCommand c = (ChangeTiltAngleCommand) lastEdit;
                final HousePart p = c.getPart();
                stateValue = "{\"Foundation\": " + p.getTopContainer().getId() + ", \"ID\": " + p.getId() + ", \"Old Value\": " + c.getOldValue() + ", \"New Value\": " + c.getNewValue() + "}";
            } else if (lastEdit instanceof ChangeAzimuthCommand) {
                final ChangeAzimuthCommand c = (ChangeAzimuthCommand) lastEdit;
                final HousePart p = c.getPart();
                stateValue = "{\"Foundation\": " + (p instanceof Foundation ? (Foundation) p : p.getTopContainer()).getId() + ", \"ID\": " + p.getId() + ", \"Old Value\": " + c.getOldValue() + ", \"New Value\": " + c.getNewValue() + "}";
            } else if (lastEdit instanceof ChangeBaseHeightCommand) {
                final ChangeBaseHeightCommand c = (ChangeBaseHeightCommand) lastEdit;
                final SolarCollector s = c.getPart();
                if (s instanceof HousePart) {
                    final HousePart p = (HousePart) s;
                    stateValue = "{\"Foundation\": " + p.getTopContainer().getId() + ", \"ID\": " + p.getId() + ", \"Old Value\": " + c.getOldValue() + ", \"New Value\": " + c.getNewValue() + "}";
                }
            } else if (lastEdit instanceof SetPartSizeCommand) {
                final SetPartSizeCommand c = (SetPartSizeCommand) lastEdit;
                if (c.getPart() instanceof Mirror) {
                    final Mirror m = (Mirror) c.getPart();
                    stateValue = "{\"Foundation\": " + m.getTopContainer().getId() + ", \"ID\": " + m.getId();
                    stateValue += ", \"Old Width\": " + c.getOldWidth() + ", \"New Width\": " + m.getMirrorWidth();
                    stateValue += ", \"Old Height\": " + c.getOldHeight() + ", \"New Height\": " + m.getMirrorHeight() + "}";
                } else if (c.getPart() instanceof ParabolicDish) {
                    final ParabolicDish d = (ParabolicDish) c.getPart();
                    stateValue = "{\"Foundation\": " + d.getTopContainer().getId() + ", \"ID\": " + d.getId();
                    stateValue += ", \"Old Rim Radius\": " + c.getOldWidth() + ", \"New Rim Radius\": " + d.getRimRadius() + "}";
                } else if (c.getPart() instanceof ParabolicTrough) {
                    final ParabolicTrough t = (ParabolicTrough) c.getPart();
                    stateValue = "{\"Foundation\": " + t.getTopContainer().getId() + ", \"ID\": " + t.getId();
                    stateValue += ", \"Old Aperture\": " + c.getOldWidth() + ", \"New Aperture\": " + t.getApertureWidth();
                    stateValue += ", \"Old Length\": " + c.getOldHeight() + ", \"New Length\": " + t.getTroughLength();
                    stateValue += ", \"Old Module Length\": " + c.getOldModuleLength() + ", \"New Module Length\": " + t.getModuleLength() + "}";
                } else if (c.getPart() instanceof FresnelReflector) {
                    final FresnelReflector t = (FresnelReflector) c.getPart();
                    stateValue = "{\"Foundation\": " + t.getTopContainer().getId() + ", \"ID\": " + t.getId();
                    stateValue += ", \"Old Module Width\": " + c.getOldWidth() + ", \"New Module Width\": " + t.getModuleWidth();
                    stateValue += ", \"Old Length\": " + c.getOldHeight() + ", \"New Length\": " + t.getLength();
                    stateValue += ", \"Old Module Length\": " + c.getOldModuleLength() + ", \"New Module Length\": " + t.getModuleLength() + "}";
                } else if (c.getPart() instanceof Rack) {
                    final Rack r = (Rack) c.getPart();
                    stateValue = "{\"Foundation\": " + r.getTopContainer().getId() + ", \"ID\": " + r.getId();
                    stateValue += ", \"Old Width\": " + c.getOldWidth() + ", \"New Width\": " + r.getRackWidth();
                    stateValue += ", \"Old Height\": " + c.getOldHeight() + ", \"New Height\": " + r.getRackHeight() + "}";
                } else if (c.getPart() instanceof Window) {
                    final Window r = (Window) c.getPart();
                    stateValue = "{\"Foundation\": " + r.getTopContainer().getId() + ", \"ID\": " + r.getId();
                    stateValue += ", \"Old Width\": " + c.getOldWidth() + ", \"New Width\": " + r.getWindowWidth();
                    stateValue += ", \"Old Height\": " + c.getOldHeight() + ", \"New Height\": " + r.getWindowHeight() + "}";
                }
            } else if (lastEdit instanceof ChangeSolarPanelModelCommand) {
                final ChangeSolarPanelModelCommand c = (ChangeSolarPanelModelCommand) lastEdit;
                final SolarPanel sp = c.getSolarPanel();
                stateValue = "{\"Foundation\": " + sp.getTopContainer().getId() + ", \"ID\": " + sp.getId();
                stateValue += ", \"Old Model\": \"" + c.getOldModel().getModel() + "\", \"New Model\": \"" + sp.getPvModuleSpecs().getModel() + "\"}";
            } else if (lastEdit instanceof ChooseSolarPanelSizeCommand) {
                final ChooseSolarPanelSizeCommand c = (ChooseSolarPanelSizeCommand) lastEdit;
                final SolarPanel sp = c.getSolarPanel();
                stateValue = "{\"Foundation\": " + sp.getTopContainer().getId() + ", \"ID\": " + sp.getId();
                stateValue += ", \"Old Width\": " + c.getOldWidth() + ", \"New Width\": " + sp.getPanelWidth();
                stateValue += ", \"Old Height\": " + c.getOldHeight() + ", \"New Height\": " + sp.getPanelHeight() + "}";
            } else if (lastEdit instanceof RotateSolarPanelCommand) {
                final RotateSolarPanelCommand c = (RotateSolarPanelCommand) lastEdit;
                final SolarPanel sp = c.getSolarPanel();
                stateValue = "{\"Foundation\": " + sp.getTopContainer().getId() + ", \"ID\": " + sp.getId() + ", \"New Value\": " + sp.isRotated() + "}";
            } else if (lastEdit instanceof ChangeSolarCellPropertiesCommand) {
                final ChangeSolarCellPropertiesCommand c = (ChangeSolarCellPropertiesCommand) lastEdit;
                final SolarPanel sp = c.getSolarPanel();
                stateValue = "{\"Foundation\": " + sp.getTopContainer().getId() + ", \"ID\": " + sp.getId();
                stateValue += ", \"Old Efficiency\": " + c.getOldEfficiency() + ", \"New Efficiency\": " + sp.getCellEfficiency();
                stateValue += ", \"Old Type\": " + c.getOldType() + ", \"New Type\": " + sp.getCellType();
                stateValue += ", \"Old Color\": " + c.getOldColor() + ", \"New Color\": " + sp.getColorOption();
                stateValue += "}";
            } else if (lastEdit instanceof ChangeFoundationSolarCellPropertiesCommand) {
                final Foundation f = ((ChangeFoundationSolarCellPropertiesCommand) lastEdit).getFoundation();
                final List<SolarPanel> solarPanels = f.getSolarPanels();
                if (solarPanels.isEmpty()) {
                    stateValue = "{\"Foundation\": " + f.getId() + "}";
                } else {
                    final SolarPanel p = solarPanels.get(0);
                    stateValue = "{\"Foundation\": " + f.getId() + ", \"New Efficiency\": " + p.getCellEfficiency();
                    stateValue += ", \"New Type\": " + p.getCellType();
                    stateValue += ", \"New Color\": " + p.getColorOption() + "}";
                }
            } else if (lastEdit instanceof ChangeSolarCellPropertiesForAllCommand) {
                final List<SolarPanel> solarPanels = Scene.getInstance().getAllSolarPanels();
                if (solarPanels.isEmpty()) {
                    stateValue = "{}";
                } else {
                    final SolarPanel p = solarPanels.get(0);
                    stateValue = "{\"New Efficiency\": " + p.getCellEfficiency();
                    stateValue += ", \"New Type\": " + p.getCellType();
                    stateValue += ", \"New Color\": " + p.getColorOption() + "}";
                }
            } else if (lastEdit instanceof SetTemperatureEffectsCommand) {
                final SetTemperatureEffectsCommand c = (SetTemperatureEffectsCommand) lastEdit;
                final SolarPanel sp = c.getSolarPanel();
                stateValue = "{\"Foundation\": " + sp.getTopContainer().getId() + ", \"ID\": " + sp.getId() + ", \"Old Noct\": " + c.getOldNoct() + ", \"New Noct\": " + sp.getNominalOperatingCellTemperature() + ", \"Old Pmax\": " + c.getOldPmax() + ", \"New Pmax\": " + sp.getTemperatureCoefficientPmax() + "}";
            } else if (lastEdit instanceof SetFoundationTemperatureEffectsCommand) {
                final Foundation f = ((SetFoundationTemperatureEffectsCommand) lastEdit).getFoundation();
                final List<SolarPanel> solarPanels = f.getSolarPanels();
                stateValue = "{\"Foundation\": " + f.getId() + ", \"New Pmax\": " + (solarPanels.isEmpty() ? -1 : solarPanels.get(0).getTemperatureCoefficientPmax()) + "}";
            } else if (lastEdit instanceof SetTemperatrureEffectsForAllCommand) {
                final List<SolarPanel> solarPanels = Scene.getInstance().getAllSolarPanels();
                stateValue = "{\"New Pmax\": " + (solarPanels.isEmpty() ? -1 : solarPanels.get(0).getTemperatureCoefficientPmax()) + "}";
            } else if (lastEdit instanceof ChangeInverterEfficiencyCommand) {
                final ChangeInverterEfficiencyCommand c = (ChangeInverterEfficiencyCommand) lastEdit;
                final SolarPanel sp = c.getSolarPanel();
                stateValue = "{\"Foundation\": " + sp.getTopContainer().getId() + ", \"ID\": " + sp.getId() + ", \"Old Value\": " + c.getOldValue() + ", \"New Value\": " + sp.getInverterEfficiency() + "}";
            } else if (lastEdit instanceof ChangeFoundationInverterEfficiencyCommand) {
                final Foundation f = ((ChangeFoundationInverterEfficiencyCommand) lastEdit).getFoundation();
                final List<SolarPanel> solarPanels = f.getSolarPanels();
                stateValue = "{\"Foundation\": " + f.getId() + ", \"New Value\": " + (solarPanels.isEmpty() ? -1 : solarPanels.get(0).getInverterEfficiency()) + "}";
            } else if (lastEdit instanceof ChangeInverterEfficiencyForAllCommand) {
                final List<SolarPanel> solarPanels = Scene.getInstance().getAllSolarPanels();
                stateValue = "{\"New Value\": " + (solarPanels.isEmpty() ? -1 : solarPanels.get(0).getInverterEfficiency()) + "}";
            } else if (lastEdit instanceof SetShadeToleranceCommand) {
                final SetShadeToleranceCommand c = (SetShadeToleranceCommand) lastEdit;
                final SolarPanel sp = c.getSolarPanel();
                stateValue = "{\"Foundation\": " + sp.getTopContainer().getId() + ", \"ID\": " + sp.getId() + ", \"Old Value\": " + c.getOldValue() + ", \"New Value\": " + sp.getShadeTolerance() + "}";
            } else if (lastEdit instanceof SetShadeToleranceForSolarPanelsOnFoundationCommand) {
                final SetShadeToleranceForSolarPanelsOnFoundationCommand c = (SetShadeToleranceForSolarPanelsOnFoundationCommand) lastEdit;
                final Foundation f = c.getFoundation();
                final List<SolarPanel> solarPanels = f.getSolarPanels();
                stateValue = "{\"Foundation\": " + f.getId() + ", \"New Value\": " + (solarPanels.isEmpty() ? -1 : solarPanels.get(0).getShadeTolerance()) + "}";
            } else if (lastEdit instanceof SetShadeToleranceForAllSolarPanelsCommand) {
                final List<SolarPanel> solarPanels = Scene.getInstance().getAllSolarPanels();
                stateValue = "{\"New Value\": " + (solarPanels.isEmpty() ? -1 : solarPanels.get(0).getShadeTolerance()) + "}";
            } else if (lastEdit instanceof ChangeFoundationSolarPanelTiltAngleCommand) {
                final Foundation f = ((ChangeFoundationSolarPanelTiltAngleCommand) lastEdit).getFoundation();
                final List<SolarPanel> solarPanels = f.getSolarPanels();
                stateValue = "{\"Foundation\": " + f.getId() + ", \"New Value\": " + (solarPanels.isEmpty() ? -1 : solarPanels.get(0).getTiltAngle()) + "}";
            } else if (lastEdit instanceof ChangeTiltAngleForAllSolarPanelsCommand) {
                final List<SolarPanel> solarPanels = Scene.getInstance().getAllSolarPanels();
                stateValue = "{\"New Value\": " + (solarPanels.isEmpty() ? -1 : solarPanels.get(0).getTiltAngle()) + "}";
            } else if (lastEdit instanceof ChangeFoundationSolarPanelAzimuthCommand) {
                final Foundation f = ((ChangeFoundationSolarPanelAzimuthCommand) lastEdit).getFoundation();
                final List<SolarPanel> solarPanels = f.getSolarPanels();
                stateValue = "{\"Foundation\": " + f.getId() + ", \"New Value\": " + (solarPanels.isEmpty() ? -1 : solarPanels.get(0).getRelativeAzimuth()) + "}";
            } else if (lastEdit instanceof ChangeAzimuthForAllSolarPanelsCommand) {
                final List<SolarPanel> solarPanels = Scene.getInstance().getAllSolarPanels();
                stateValue = "{\"New Value\": " + (solarPanels.isEmpty() ? -1 : solarPanels.get(0).getRelativeAzimuth()) + "}";
            } else if (lastEdit instanceof ChangeFoundationSolarPanelModelCommand) {
                final Foundation f = ((ChangeFoundationSolarPanelModelCommand) lastEdit).getFoundation();
                final List<SolarPanel> solarPanels = f.getSolarPanels();
                stateValue = "{\"Foundation\": " + f.getId() + ", \"New Model\": " + (solarPanels.isEmpty() ? null : "\"" + solarPanels.get(0).getPvModuleSpecs().getModel() + "\"") + "}";
            } else if (lastEdit instanceof ChangeModelForAllSolarPanelsCommand) {
                final List<SolarPanel> solarPanels = Scene.getInstance().getAllSolarPanels();
                stateValue = "{\"New Model\": " + (solarPanels.isEmpty() ? null : "\"" + solarPanels.get(0).getPvModuleSpecs().getModel() + "\"") + "}";
            } else if (lastEdit instanceof ChangeSolarPanelModelForRackCommand) {
                final ChangeSolarPanelModelForRackCommand c = (ChangeSolarPanelModelForRackCommand) lastEdit;
                final Rack rack = c.getRack();
                stateValue = "{\"Foundation\": " + rack.getTopContainer().getId() + ", \"ID\": " + rack.getId();
                stateValue += ", \"Old Model\": \"" + c.getOldModel().getModel() + "\", \"New Model\": \"" + rack.getSolarPanel().getPvModuleSpecs().getModel() + "\"}";
            } else if (lastEdit instanceof ChangeSolarPanelModelForRacksOnFoundationCommand) {
                final Foundation f = ((ChangeSolarPanelModelForRacksOnFoundationCommand) lastEdit).getFoundation();
                final List<Rack> racks = f.getRacks();
                stateValue = "{\"Foundation\": " + f.getId() + ", \"New Model\": " + (racks.isEmpty() ? null : "\"" + racks.get(0).getSolarPanel().getPvModuleSpecs().getModel() + "\"") + "}";
            } else if (lastEdit instanceof ChangeSolarPanelModelForAllRacksCommand) {
                final List<Rack> racks = Scene.getInstance().getAllRacks();
                stateValue = "{\"New Model\": " + (racks.isEmpty() ? null : "\"" + racks.get(0).getSolarPanel().getPvModuleSpecs().getModel() + "\"") + "}";
            } else if (lastEdit instanceof SetSolarCellEfficiencyForRackCommand) {
                final SetSolarCellEfficiencyForRackCommand c = (SetSolarCellEfficiencyForRackCommand) lastEdit;
                final Rack rack = c.getRack();
                stateValue = "{\"Foundation\": " + rack.getTopContainer().getId() + ", \"ID\": " + rack.getId();
                stateValue += ", \"Old Value\": \"" + c.getOldValue() + "\", \"New Value\": \"" + rack.getSolarPanel().getCellEfficiency() + "\"}";
            } else if (lastEdit instanceof SetSolarCellEfficiencyForRacksOnFoundationCommand) {
                final SetSolarCellEfficiencyForRacksOnFoundationCommand c = (SetSolarCellEfficiencyForRacksOnFoundationCommand) lastEdit;
                final Foundation f = c.getFoundation();
                final List<Rack> racks = f.getRacks();
                stateValue = "{\"Foundation\": " + f.getId() + ", \"New Value\": " + (racks.isEmpty() ? null : "\"" + racks.get(0).getSolarPanel().getCellEfficiency() + "\"") + "}";
            } else if (lastEdit instanceof SetSolarCellEfficiencyForAllRacksCommand) {
                final List<Rack> racks = Scene.getInstance().getAllRacks();
                stateValue = "{\"New Value\": " + (racks.isEmpty() ? null : "\"" + racks.get(0).getSolarPanel().getCellEfficiency() + "\"") + "}";
            } else if (lastEdit instanceof SetNoctForRackCommand) {
                final SetNoctForRackCommand c = (SetNoctForRackCommand) lastEdit;
                final Rack rack = c.getRack();
                stateValue = "{\"Foundation\": " + rack.getTopContainer().getId() + ", \"ID\": " + rack.getId();
                stateValue += ", \"Old Value\": \"" + c.getOldValue() + "\", \"New Value\": \"" + rack.getSolarPanel().getNominalOperatingCellTemperature() + "\"}";
            } else if (lastEdit instanceof SetNoctForRacksOnFoundationCommand) {
                final SetNoctForRacksOnFoundationCommand c = (SetNoctForRacksOnFoundationCommand) lastEdit;
                final Foundation f = c.getFoundation();
                final List<Rack> racks = f.getRacks();
                stateValue = "{\"Foundation\": " + f.getId() + ", \"New Value\": " + (racks.isEmpty() ? null : "\"" + racks.get(0).getSolarPanel().getNominalOperatingCellTemperature() + "\"") + "}";
            } else if (lastEdit instanceof SetNoctForAllRacksCommand) {
                final List<Rack> racks = Scene.getInstance().getAllRacks();
                stateValue = "{\"New Value\": " + (racks.isEmpty() ? null : "\"" + racks.get(0).getSolarPanel().getNominalOperatingCellTemperature() + "\"") + "}";
            } else if (lastEdit instanceof SetTemperatureCoefficientPmaxForRackCommand) {
                final SetTemperatureCoefficientPmaxForRackCommand c = (SetTemperatureCoefficientPmaxForRackCommand) lastEdit;
                final Rack rack = c.getRack();
                stateValue = "{\"Foundation\": " + rack.getTopContainer().getId() + ", \"ID\": " + rack.getId();
                stateValue += ", \"Old Value\": \"" + c.getOldValue() + "\", \"New Value\": \"" + rack.getSolarPanel().getTemperatureCoefficientPmax() + "\"}";
            } else if (lastEdit instanceof SetTemperatureCoefficientPmaxForRacksOnFoundationCommand) {
                final SetTemperatureCoefficientPmaxForRacksOnFoundationCommand c = (SetTemperatureCoefficientPmaxForRacksOnFoundationCommand) lastEdit;
                final Foundation f = c.getFoundation();
                final List<Rack> racks = f.getRacks();
                stateValue = "{\"Foundation\": " + f.getId() + ", \"New Value\": " + (racks.isEmpty() ? null : "\"" + racks.get(0).getSolarPanel().getTemperatureCoefficientPmax() + "\"") + "}";
            } else if (lastEdit instanceof SetTemperatureCoefficientPmaxForAllRacksCommand) {
                final List<Rack> racks = Scene.getInstance().getAllRacks();
                stateValue = "{\"New Value\": " + (racks.isEmpty() ? null : "\"" + racks.get(0).getSolarPanel().getTemperatureCoefficientPmax() + "\"") + "}";
            } else if (lastEdit instanceof SetSolarPanelCellTypeForRackCommand) {
                final SetSolarPanelCellTypeForRackCommand c = (SetSolarPanelCellTypeForRackCommand) lastEdit;
                final Rack rack = c.getRack();
                stateValue = "{\"Foundation\": " + rack.getTopContainer().getId() + ", \"ID\": " + rack.getId();
                stateValue += ", \"Old Type\": \"" + c.getOldValue() + "\", \"New Type\": \"" + rack.getSolarPanel().getCellType() + "\"}";
            } else if (lastEdit instanceof SetSolarPanelCellTypeForRacksOnFoundationCommand) {
                final SetSolarPanelCellTypeForRacksOnFoundationCommand c = (SetSolarPanelCellTypeForRacksOnFoundationCommand) lastEdit;
                final Foundation f = c.getFoundation();
                final List<Rack> racks = f.getRacks();
                stateValue = "{\"Foundation\": " + f.getId() + ", \"New Type\": " + (racks.isEmpty() ? null : "\"" + racks.get(0).getSolarPanel().getCellType() + "\"") + "}";
            } else if (lastEdit instanceof SetSolarPanelCellTypeForAllRacksCommand) {
                final List<Rack> racks = Scene.getInstance().getAllRacks();
                stateValue = "{\"New Type\": " + (racks.isEmpty() ? null : "\"" + racks.get(0).getSolarPanel().getCellType() + "\"") + "}";
            } else if (lastEdit instanceof SetSolarPanelShadeToleranceForRackCommand) {
                final SetSolarPanelShadeToleranceForRackCommand c = (SetSolarPanelShadeToleranceForRackCommand) lastEdit;
                final Rack rack = c.getRack();
                stateValue = "{\"Foundation\": " + rack.getTopContainer().getId() + ", \"ID\": " + rack.getId();
                stateValue += ", \"Old Value\": \"" + c.getOldValue() + "\", \"New Value\": \"" + rack.getSolarPanel().getShadeTolerance() + "\"}";
            } else if (lastEdit instanceof SetSolarPanelShadeToleranceForRacksOnFoundationCommand) {
                final SetSolarPanelShadeToleranceForRacksOnFoundationCommand c = (SetSolarPanelShadeToleranceForRacksOnFoundationCommand) lastEdit;
                final Foundation f = c.getFoundation();
                final List<Rack> racks = f.getRacks();
                stateValue = "{\"Foundation\": " + f.getId() + ", \"New Value\": " + (racks.isEmpty() ? null : "\"" + racks.get(0).getSolarPanel().getShadeTolerance() + "\"") + "}";
            } else if (lastEdit instanceof SetSolarPanelShadeToleranceForAllRacksCommand) {
                final List<Rack> racks = Scene.getInstance().getAllRacks();
                stateValue = "{\"New Value\": " + (racks.isEmpty() ? null : "\"" + racks.get(0).getSolarPanel().getShadeTolerance() + "\"") + "}";
            } else if (lastEdit instanceof SetSolarPanelColorForRackCommand) {
                final SetSolarPanelColorForRackCommand c = (SetSolarPanelColorForRackCommand) lastEdit;
                final Rack rack = c.getRack();
                stateValue = "{\"Foundation\": " + rack.getTopContainer().getId() + ", \"ID\": " + rack.getId();
                stateValue += ", \"Old Color\": \"" + c.getOldValue() + "\", \"New Color\": \"" + rack.getSolarPanel().getColorOption() + "\"}";
            } else if (lastEdit instanceof SetSolarPanelColorForRacksOnFoundationCommand) {
                final SetSolarPanelColorForRacksOnFoundationCommand c = (SetSolarPanelColorForRacksOnFoundationCommand) lastEdit;
                final Foundation f = c.getFoundation();
                final List<Rack> racks = f.getRacks();
                stateValue = "{\"Foundation\": " + f.getId() + ", \"New Color\": " + (racks.isEmpty() ? null : "\"" + racks.get(0).getSolarPanel().getColorOption() + "\"") + "}";
            } else if (lastEdit instanceof SetSolarPanelColorForAllRacksCommand) {
                final List<Rack> racks = Scene.getInstance().getAllRacks();
                stateValue = "{\"New Color\": " + (racks.isEmpty() ? null : "\"" + racks.get(0).getSolarPanel().getColorOption() + "\"") + "}";
            } else if (lastEdit instanceof ChooseSolarPanelSizeForRackCommand) {
                final ChooseSolarPanelSizeForRackCommand c = (ChooseSolarPanelSizeForRackCommand) lastEdit;
                final Rack rack = c.getRack();
                stateValue = "{\"Foundation\": " + rack.getTopContainer().getId() + ", \"ID\": " + rack.getId();
                stateValue += ", \"Old Width\": " + c.getOldWidth() + ", \"New Width\": " + rack.getSolarPanel().getPanelWidth();
                stateValue += ", \"Old Height\": " + c.getOldHeight() + ", \"New Height\": " + rack.getSolarPanel().getPanelHeight() + "}";
            } else if (lastEdit instanceof SetSizeForRacksOnFoundationCommand) {
                final Foundation f = ((SetSizeForRacksOnFoundationCommand) lastEdit).getFoundation();
                final List<Rack> racks = f.getRacks();
                stateValue = "{\"Foundation\": " + f.getId() + ", \"New Width\": " + (racks.isEmpty() ? -1 : racks.get(0).getRackWidth()) + ", \"New Height\": " + (racks.isEmpty() ? -1 : racks.get(0).getRackHeight()) + "}";
            } else if (lastEdit instanceof SetSizeForAllRacksCommand) {
                final List<Rack> racks = Scene.getInstance().getAllRacks();
                stateValue = "{\"New Width\": " + (racks.isEmpty() ? -1 : racks.get(0).getRackWidth()) + ", \"New Height\": " + (racks.isEmpty() ? -1 : racks.get(0).getRackHeight()) + "}";
            } else if (lastEdit instanceof RotateSolarPanelsForRackCommand) {
                final RotateSolarPanelsForRackCommand c = (RotateSolarPanelsForRackCommand) lastEdit;
                final Rack rack = c.getRack();
                stateValue = "{\"Foundation\": " + rack.getTopContainer().getId() + ", \"ID\": " + rack.getId();
                stateValue += ", \"Old Value\": \"" + c.getOldValue() + "\", \"New Value\": \"" + rack.getSolarPanel().isRotated() + "\"}";
            } else if (lastEdit instanceof RotateSolarPanelsForRacksOnFoundationCommand) {
                final RotateSolarPanelsForRacksOnFoundationCommand c = (RotateSolarPanelsForRacksOnFoundationCommand) lastEdit;
                final Foundation f = c.getFoundation();
                final List<Rack> racks = f.getRacks();
                stateValue = "{\"Foundation\": " + f.getId() + ", \"New Value\": " + (racks.isEmpty() ? null : "\"" + racks.get(0).getSolarPanel().isRotated() + "\"") + "}";
            } else if (lastEdit instanceof RotateSolarPanelsForAllRacksCommand) {
                final List<Rack> racks = Scene.getInstance().getAllRacks();
                stateValue = "{\"New Value\": " + (racks.isEmpty() ? null : "\"" + racks.get(0).getSolarPanel().isRotated() + "\"") + "}";
            } else if (lastEdit instanceof SetInverterEfficiencyForRackCommand) {
                final SetInverterEfficiencyForRackCommand c = (SetInverterEfficiencyForRackCommand) lastEdit;
                final Rack rack = c.getRack();
                stateValue = "{\"Foundation\": " + rack.getTopContainer().getId() + ", \"ID\": " + rack.getId();
                stateValue += ", \"Old Value\": \"" + c.getOldValue() + "\", \"New Value\": \"" + rack.getSolarPanel().getInverterEfficiency() + "\"}";
            } else if (lastEdit instanceof SetInverterEfficiencyForRacksOnFoundationCommand) {
                final SetInverterEfficiencyForRacksOnFoundationCommand c = (SetInverterEfficiencyForRacksOnFoundationCommand) lastEdit;
                final Foundation f = c.getFoundation();
                final List<Rack> racks = f.getRacks();
                stateValue = "{\"Foundation\": " + f.getId() + ", \"New Value\": " + (racks.isEmpty() ? null : "\"" + racks.get(0).getSolarPanel().getInverterEfficiency() + "\"") + "}";
            } else if (lastEdit instanceof SetInverterEfficiencyForAllRacksCommand) {
                final List<Rack> racks = Scene.getInstance().getAllRacks();
                stateValue = "{\"New Value\": " + (racks.isEmpty() ? null : "\"" + racks.get(0).getSolarPanel().getInverterEfficiency() + "\"") + "}";
            } else if (lastEdit instanceof ChangeFoundationRackTiltAngleCommand) {
                final Foundation f = ((ChangeFoundationRackTiltAngleCommand) lastEdit).getFoundation();
                final List<Rack> racks = f.getRacks();
                stateValue = "{\"Foundation\": " + f.getId() + ", \"New Value\": " + (racks.isEmpty() ? -1 : racks.get(0).getTiltAngle()) + "}";
            } else if (lastEdit instanceof ChangeTiltAngleForAllRacksCommand) {
                final List<Rack> racks = Scene.getInstance().getAllRacks();
                stateValue = "{\"New Value\": " + (racks.isEmpty() ? -1 : racks.get(0).getTiltAngle()) + "}";
            } else if (lastEdit instanceof ChangeFoundationRackAzimuthCommand) {
                final Foundation f = ((ChangeFoundationRackAzimuthCommand) lastEdit).getFoundation();
                final List<Rack> racks = f.getRacks();
                stateValue = "{\"Foundation\": " + f.getId() + ", \"New Value\": " + (racks.isEmpty() ? -1 : racks.get(0).getRelativeAzimuth()) + "}";
            } else if (lastEdit instanceof ChangeAzimuthForAllRacksCommand) {
                final List<Rack> racks = Scene.getInstance().getAllRacks();
                stateValue = "{\"New Value\": " + (racks.isEmpty() ? -1 : racks.get(0).getRelativeAzimuth()) + "}";
            } else if (lastEdit instanceof SetSolarPanelSizeForRacksOnFoundationCommand) {
                final Foundation f = ((SetSolarPanelSizeForRacksOnFoundationCommand) lastEdit).getFoundation();
                final List<Rack> racks = f.getRacks();
                stateValue = "{\"Foundation\": " + f.getId() + ", \"New Width\": " + (racks.isEmpty() ? -1 : racks.get(0).getSolarPanel().getPanelWidth()) + ", \"New Height\": " + (racks.isEmpty() ? -1 : racks.get(0).getSolarPanel().getPanelHeight()) + "}";
            } else if (lastEdit instanceof SetSolarPanelSizeForAllRacksCommand) {
                final List<Rack> racks = Scene.getInstance().getAllRacks();
                stateValue = "{\"New Width\": " + (racks.isEmpty() ? -1 : racks.get(0).getSolarPanel().getPanelWidth()) + ", \"New Height\": " + (racks.isEmpty() ? -1 : racks.get(0).getSolarPanel().getPanelHeight()) + "}";
            } else if (lastEdit instanceof SetSolarTrackerCommand) {
                final SetSolarTrackerCommand c = (SetSolarTrackerCommand) lastEdit;
                final Trackable tracker = c.getTracker();
                long bid = -1;
                long cid = -1;
                if (tracker instanceof HousePart) {
                    bid = ((HousePart) tracker).getTopContainer().getId();
                    cid = ((HousePart) tracker).getId();
                }
                stateValue = "{\"Foundation\": " + bid + ", \"ID\": " + cid + ", \"Old Value\": " + c.getOldValue() + ", \"New Value\": " + tracker.getTracker() + "}";
            } else if (lastEdit instanceof SetSolarTrackersOnFoundationCommand) {
                final SetSolarTrackersOnFoundationCommand c = (SetSolarTrackersOnFoundationCommand) lastEdit;
                final Foundation f = c.getFoundation();
                final Trackable tracker = c.getTracker();
                if (tracker instanceof SolarPanel) {
                    final List<SolarPanel> solarPanels = f.getSolarPanels();
                    stateValue = "{\"Foundation\": " + f.getId() + ", \"New Value\": " + (solarPanels.isEmpty() ? -1 : solarPanels.get(0).getTracker()) + "}";
                } else if (tracker instanceof Rack) {
                    final List<Rack> racks = f.getRacks();
                    stateValue = "{\"Foundation\": " + f.getId() + ", \"New Value\": " + (racks.isEmpty() ? -1 : racks.get(0).getTracker()) + "}";
                }
            } else if (lastEdit instanceof SetSolarTrackersForAllCommand) {
                final SetSolarTrackersForAllCommand c = (SetSolarTrackersForAllCommand) lastEdit;
                final Trackable tracker = c.getTracker();
                if (tracker instanceof SolarPanel) {
                    final List<SolarPanel> solarPanels = Scene.getInstance().getAllSolarPanels();
                    stateValue = "{\"New Value\": " + (solarPanels.isEmpty() ? -1 : solarPanels.get(0).getTracker()) + "}";
                } else if (tracker instanceof Rack) {
                    final List<Rack> racks = Scene.getInstance().getAllRacks();
                    stateValue = "{\"New Value\": " + (racks.isEmpty() ? -1 : racks.get(0).getTracker()) + "}";
                }
            } else if (lastEdit instanceof SetSizeForHeliostatsOnFoundationCommand) {
                final Foundation f = ((SetSizeForHeliostatsOnFoundationCommand) lastEdit).getFoundation();
                final List<Mirror> mirrors = f.getHeliostats();
                stateValue = "{\"Foundation\": " + f.getId() + ", \"New Width\": " + (mirrors.isEmpty() ? -1 : mirrors.get(0).getMirrorWidth()) + ", \"New Height\": " + (mirrors.isEmpty() ? -1 : mirrors.get(0).getMirrorHeight()) + "}";
            } else if (lastEdit instanceof SetSizeForAllHeliostatsCommand) {
                final List<Mirror> mirrors = Scene.getInstance().getAllHeliostats();
                stateValue = "{\"New Width\": " + (mirrors.isEmpty() ? -1 : mirrors.get(0).getMirrorWidth()) + ", \"New Height\": " + (mirrors.isEmpty() ? -1 : mirrors.get(0).getMirrorHeight()) + "}";
            } else if (lastEdit instanceof ChangeHeliostatTargetCommand) {
                final ChangeHeliostatTargetCommand c = (ChangeHeliostatTargetCommand) lastEdit;
                final Mirror m = c.getMirror();
                stateValue = "{\"Foundation\": " + m.getTopContainer().getId() + ", \"ID\": " + m.getId();
                stateValue += ", \"Old Value\": " + (c.getOldValue() == null ? -1 : c.getOldValue().getId()) + ", \"New Value\": " + (c.getNewValue() == null ? -1 : c.getNewValue().getId()) + "}";
            } else if (lastEdit instanceof ChangeFoundationHeliostatTargetCommand) {
                final Foundation f = ((ChangeFoundationHeliostatTargetCommand) lastEdit).getFoundation();
                final List<Mirror> mirrors = f.getHeliostats();
                long newValue = -1;
                if (!mirrors.isEmpty()) {
                    final Foundation t = mirrors.get(0).getReceiver();
                    if (t != null) {
                        newValue = t.getId();
                    }
                }
                stateValue = "{\"Foundation\": " + f.getId() + ", \"New Value\": " + newValue + "}";
            } else if (lastEdit instanceof ChangeTargetForAllHeliostatsCommand) {
                final List<Mirror> mirrors = Scene.getInstance().getAllHeliostats();
                long newValue = -1;
                if (!mirrors.isEmpty()) {
                    final Foundation t = mirrors.get(0).getReceiver();
                    if (t != null) {
                        newValue = t.getId();
                    }
                }
                stateValue = "{\"New Value\": " + newValue + "}";
            } else if (lastEdit instanceof ChangeFoundationHeliostatTiltAngleCommand) {
                final Foundation f = ((ChangeFoundationHeliostatTiltAngleCommand) lastEdit).getFoundation();
                final List<Mirror> mirrors = f.getHeliostats();
                stateValue = "{\"Foundation\": " + f.getId() + ", \"New Value\": " + (mirrors.isEmpty() ? -1 : mirrors.get(0).getTiltAngle()) + "}";
            } else if (lastEdit instanceof ChangeTiltAngleForAllHeliostatsCommand) {
                final List<Mirror> mirrors = Scene.getInstance().getAllHeliostats();
                stateValue = "{\"New Value\": " + (mirrors.isEmpty() ? -1 : mirrors.get(0).getTiltAngle()) + "}";
            } else if (lastEdit instanceof ChangeFoundationHeliostatAzimuthCommand) {
                final Foundation f = ((ChangeFoundationHeliostatAzimuthCommand) lastEdit).getFoundation();
                final List<Mirror> mirrors = f.getHeliostats();
                stateValue = "{\"Foundation\": " + f.getId() + ", \"New Value\": " + (mirrors.isEmpty() ? -1 : mirrors.get(0).getRelativeAzimuth()) + "}";
            } else if (lastEdit instanceof ChangeAzimuthForAllHeliostatsCommand) {
                final List<Mirror> mirrors = Scene.getInstance().getAllHeliostats();
                stateValue = "{\"New Value\": " + (mirrors.isEmpty() ? -1 : mirrors.get(0).getRelativeAzimuth()) + "}";
            } else if (lastEdit instanceof SetShapeForParabolicTroughsOnFoundationCommand) {
                final Foundation f = ((SetShapeForParabolicTroughsOnFoundationCommand) lastEdit).getFoundation();
                final List<ParabolicTrough> troughs = f.getParabolicTroughs();
                final ParabolicTrough t = troughs.isEmpty() ? null : troughs.get(0);
                stateValue = "{\"Foundation\": " + f.getId() + ", \"New Aperture Width\": " + (t == null ? -1 : t.getApertureWidth()) + ", \"New Length\": " + (t == null ? -1 : t.getTroughLength()) + ", \"New Module Length\": " + (t == null ? -1 : t.getModuleLength()) + ", \"New Semilatus Rectum\": " + (t == null ? -1 : t.getSemilatusRectum()) + "}";
            } else if (lastEdit instanceof SetShapeForAllParabolicTroughsCommand) {
                final List<ParabolicTrough> troughs = Scene.getInstance().getAllParabolicTroughs();
                final ParabolicTrough t = troughs.isEmpty() ? null : troughs.get(0);
                stateValue = "{\"New Aperture Width\": " + (t == null ? -1 : t.getApertureWidth()) + ", \"New Length\": " + (t == null ? -1 : t.getTroughLength()) + ", \"New Module Length\": " + (t == null ? -1 : t.getModuleLength()) + ", \"New Semilatus Rectum\": " + (t == null ? -1 : t.getSemilatusRectum()) + "}";
            } else if (lastEdit instanceof SetRimRadiusForParabolicDishesOnFoundationCommand) {
                final Foundation f = ((SetRimRadiusForParabolicDishesOnFoundationCommand) lastEdit).getFoundation();
                final List<ParabolicDish> dishes = f.getParabolicDishes();
                final ParabolicDish d = dishes.isEmpty() ? null : dishes.get(0);
                stateValue = "{\"Foundation\": " + f.getId() + ", \"New Rim Radius\": " + (d == null ? -1 : d.getRimRadius()) + "}";
            } else if (lastEdit instanceof SetRimRadiusForAllParabolicDishesCommand) {
                final List<ParabolicDish> dishes = Scene.getInstance().getAllParabolicDishes();
                final ParabolicDish d = dishes.isEmpty() ? null : dishes.get(0);
                stateValue = "{\"New Rim Radius\": " + (d == null ? -1 : d.getRimRadius()) + "}";
            } else if (lastEdit instanceof SetSizeForFresnelReflectorsOnFoundationCommand) {
                final Foundation f = ((SetSizeForFresnelReflectorsOnFoundationCommand) lastEdit).getFoundation();
                final List<FresnelReflector> reflectors = f.getFresnelReflectors();
                final FresnelReflector r = reflectors.isEmpty() ? null : reflectors.get(0);
                stateValue = "{\"Foundation\": " + f.getId() + ", \"New Length\": " + (r == null ? -1 : r.getLength()) + ", \"New Module Length\": " + (r == null ? -1 : r.getModuleLength()) + ", \"New Module Width\": " + (r == null ? -1 : r.getModuleWidth()) + "}";
            } else if (lastEdit instanceof SetSizeForAllFresnelReflectorsCommand) {
                final List<FresnelReflector> reflectors = Scene.getInstance().getAllFresnelReflectors();
                final FresnelReflector r = reflectors.isEmpty() ? null : reflectors.get(0);
                stateValue = "{\"New Length\": " + (r == null ? -1 : r.getLength()) + ", \"New Module Length\": " + (r == null ? -1 : r.getModuleLength()) + ", \"New Module Width\": " + (r == null ? -1 : r.getModuleWidth()) + "}";
            } else if (lastEdit instanceof ChangeWallTypeCommand) {
                final ChangeWallTypeCommand c = (ChangeWallTypeCommand) lastEdit;
                final Wall w = c.getWall();
                stateValue = "{\"Building\": " + w.getContainer().getId() + ", \"ID\": " + w.getId();
                stateValue += ", \"Old Value\": " + c.getOldValue() + ", \"New Value\": " + w.getType() + "}";
            } else if (lastEdit instanceof ChangeWallThicknessCommand) {
                final ChangeWallThicknessCommand c = (ChangeWallThicknessCommand) lastEdit;
                final Wall w = c.getWall();
                stateValue = "{\"Building\": " + w.getContainer().getId() + ", \"ID\": " + w.getId();
                stateValue += ", \"Old Value\": " + c.getOldValue() + ", \"New Value\": " + w.getThickness() + "}";
            } else if (lastEdit instanceof ChangeFoundationWallThicknessCommand) {
                final ChangeFoundationWallThicknessCommand c = (ChangeFoundationWallThicknessCommand) lastEdit;
                final Foundation f = c.getFoundation();
                stateValue = "{\"Foundation\": " + f.getId() + ", \"New Value\": " + c.getWalls().get(0).getThickness() + "}";
            } else if (lastEdit instanceof ChangeThicknessForAllWallsCommand) {
                final ChangeThicknessForAllWallsCommand c = (ChangeThicknessForAllWallsCommand) lastEdit;
                stateValue = "{\"New Value\": " + (c.getWalls().isEmpty() ? -1 : ((Wall) c.getWalls().get(0)).getThickness()) + "}";
            } else if (lastEdit instanceof ChangeWallHeightCommand) {
                final ChangeWallHeightCommand c = (ChangeWallHeightCommand) lastEdit;
                final Wall w = c.getWall();
                stateValue = "{\"Building\": " + w.getContainer().getId() + ", \"ID\": " + w.getId();
                stateValue += ", \"Old Value\": " + c.getOldValue() + ", \"New Value\": " + w.getHeight() + "}";
            } else if (lastEdit instanceof ChangeFoundationWallHeightCommand) {
                final ChangeFoundationWallHeightCommand c = (ChangeFoundationWallHeightCommand) lastEdit;
                final Foundation f = c.getFoundation();
                stateValue = "{\"Foundation\": " + f.getId() + ", \"New Value\": " + c.getWalls().get(0).getHeight() + "}";
            } else if (lastEdit instanceof ChangeHeightForAllWallsCommand) {
                final ChangeHeightForAllWallsCommand c = (ChangeHeightForAllWallsCommand) lastEdit;
                stateValue = "{\"New Value\": " + (c.getWalls().isEmpty() ? -1 : ((Wall) c.getWalls().get(0)).getHeight()) + "}";
            } else if (lastEdit instanceof ChangeHeightForConnectedWallsCommand) {
                final ChangeHeightForConnectedWallsCommand c = (ChangeHeightForConnectedWallsCommand) lastEdit;
                stateValue = "{\"New Value\": " + (c.getWalls().isEmpty() ? -1 : c.getWalls().get(0).getHeight()) + "}";
            } else if (lastEdit instanceof ChangeWindowShgcCommand) {
                final ChangeWindowShgcCommand c = (ChangeWindowShgcCommand) lastEdit;
                final Window w = c.getWindow();
                stateValue = "{\"Building\": " + w.getTopContainer().getId() + ", \"ID\": " + w.getId() + ", \"Old Value\": " + c.getOldValue() + ", \"New Value\": " + w.getSolarHeatGainCoefficient() + "}";
            } else if (lastEdit instanceof ChangeContainerWindowShgcCommand) {
                final ChangeContainerWindowShgcCommand c = (ChangeContainerWindowShgcCommand) lastEdit;
                final HousePart container = c.getContainer();
                final List<Window> windows = Scene.getInstance().getWindowsOnContainer(container);
                final String containerType = container instanceof Wall ? "Wall" : "Roof";
                stateValue = "{\"" + containerType + "\": " + container.getId() + ", \"New Value\": " + (windows.isEmpty() ? -1 : windows.get(0).getSolarHeatGainCoefficient()) + "}";
            } else if (lastEdit instanceof ChangeBuildingWindowShgcCommand) {
                final ChangeBuildingWindowShgcCommand c = (ChangeBuildingWindowShgcCommand) lastEdit;
                final Foundation foundation = c.getFoundation();
                final List<Window> windows = Scene.getInstance().getWindowsOfBuilding(foundation);
                stateValue = "{\"Building\": " + foundation.getId() + ", \"New Value\": " + (windows.isEmpty() ? -1 : windows.get(0).getSolarHeatGainCoefficient()) + "}";
            }
        }
        line += ", \"" + action + "\": ";
        if (actedPart != null) {
            line += LoggerUtil.getInfo(actedPart);
        } else if (stateValue != null) {
            line += stateValue;
        } else {
            line += "null";
        }
        lastAction = action;
    }
    if (analysisRequester != null) {
        final HousePart analyzedPart = SceneManager.getInstance().getSelectedPart();
        line += ", \"" + analysisRequester.getClass().getSimpleName() + "\": ";
        if (analysisRequester instanceof AnnualSensorData) {
            line += ((AnnualSensorData) analysisRequester).toJson();
        } else if (analysisRequester instanceof DailySensorData) {
            line += ((DailySensorData) analysisRequester).toJson();
        } else if (analysisRequester instanceof DailyEnvironmentalTemperature) {
            line += ((DailyEnvironmentalTemperature) analysisRequester).toJson();
        } else if (analysisRequester instanceof AnnualEnvironmentalTemperature) {
            line += ((AnnualEnvironmentalTemperature) analysisRequester).toJson();
        } else {
            if (analyzedPart != null && !(analyzedPart instanceof Tree) && !(analyzedPart instanceof Human)) {
                // if something analyzable is selected
                if (analysisRequester instanceof EnergyDailyAnalysis) {
                    line += ((EnergyDailyAnalysis) analysisRequester).toJson();
                } else if (analysisRequester instanceof BuildingDailyEnergyGraph) {
                    line += ((BuildingDailyEnergyGraph) analysisRequester).toJson();
                    final String result = Building.getBuildingSolarPotentials();
                    if (result != null) {
                        line += ", \"Solar Potential\": " + result;
                    }
                } else if (analysisRequester instanceof EnergyAnnualAnalysis) {
                    line += ((EnergyAnnualAnalysis) analysisRequester).toJson();
                } else if (analysisRequester instanceof EnergyAngularAnalysis) {
                    line += ((EnergyAngularAnalysis) analysisRequester).toJson();
                } else if (analysisRequester instanceof ProjectCost) {
                    line += ((ProjectCost) analysisRequester).toJson();
                }
            } else {
                if (analysisRequester instanceof ProjectCost) {
                    line += ((ProjectCost) analysisRequester).toJson();
                } else if (analysisRequester instanceof BuildingDailyEnergyGraph) {
                    line += ((BuildingDailyEnergyGraph) analysisRequester).toJson();
                    final String result = Building.getBuildingSolarPotentials();
                    if (result != null) {
                        line += ", \"Solar Potential\": " + result;
                    }
                }
            }
            if (analysisRequester instanceof PvDailyAnalysis) {
                line += ((PvDailyAnalysis) analysisRequester).toJson();
            } else if (analysisRequester instanceof PvAnnualAnalysis) {
                line += ((PvAnnualAnalysis) analysisRequester).toJson();
            }
            if (analysisRequester instanceof GroupDailyAnalysis) {
                line += ((GroupDailyAnalysis) analysisRequester).toJson();
            } else if (analysisRequester instanceof GroupAnnualAnalysis) {
                line += ((GroupAnnualAnalysis) analysisRequester).toJson();
            }
        }
    }
    if (firstRecord) {
        firstRecord = false;
    } else {
        writer.write(",\n");
    }
    writer.write("{\"Timestamp\": \"" + timestamp + "\", " + line + "}");
    writer.flush();
    lastTime = time;
}
Also used : PvAnnualAnalysis(org.concord.energy3d.simulation.PvAnnualAnalysis) Foundation(org.concord.energy3d.model.Foundation) HousePart(org.concord.energy3d.model.HousePart) Human(org.concord.energy3d.model.Human) GregorianCalendar(java.util.GregorianCalendar) SolarPanel(org.concord.energy3d.model.SolarPanel) ProjectCost(org.concord.energy3d.simulation.ProjectCost) GroupDailyAnalysis(org.concord.energy3d.simulation.GroupDailyAnalysis) URL(java.net.URL) SolarCollector(org.concord.energy3d.model.SolarCollector) PvDailyAnalysis(org.concord.energy3d.simulation.PvDailyAnalysis) Window(org.concord.energy3d.model.Window) AnnualEnvironmentalTemperature(org.concord.energy3d.simulation.AnnualEnvironmentalTemperature) GroupAnnualAnalysis(org.concord.energy3d.simulation.GroupAnnualAnalysis) GregorianCalendar(java.util.GregorianCalendar) Calendar(java.util.Calendar) ReadOnlyVector3(com.ardor3d.math.type.ReadOnlyVector3) Vector3(com.ardor3d.math.Vector3) ParabolicTrough(org.concord.energy3d.model.ParabolicTrough) UndoableEdit(javax.swing.undo.UndoableEdit) Tree(org.concord.energy3d.model.Tree) List(java.util.List) BuildingDailyEnergyGraph(org.concord.energy3d.gui.BuildingDailyEnergyGraph) TextureMode(org.concord.energy3d.scene.Scene.TextureMode) EnergyAngularAnalysis(org.concord.energy3d.simulation.EnergyAngularAnalysis) DailySensorData(org.concord.energy3d.simulation.DailySensorData) File(java.io.File) Mirror(org.concord.energy3d.model.Mirror) Thermal(org.concord.energy3d.model.Thermal) EnergyAnnualAnalysis(org.concord.energy3d.simulation.EnergyAnnualAnalysis) Wall(org.concord.energy3d.model.Wall) Rack(org.concord.energy3d.model.Rack) Roof(org.concord.energy3d.model.Roof) FresnelReflector(org.concord.energy3d.model.FresnelReflector) Date(java.util.Date) DailyEnvironmentalTemperature(org.concord.energy3d.simulation.DailyEnvironmentalTemperature) ParabolicDish(org.concord.energy3d.model.ParabolicDish) EnergyDailyAnalysis(org.concord.energy3d.simulation.EnergyDailyAnalysis) AnnualSensorData(org.concord.energy3d.simulation.AnnualSensorData) Trackable(org.concord.energy3d.model.Trackable)

Example 4 with PvDailyAnalysis

use of org.concord.energy3d.simulation.PvDailyAnalysis in project energy3d by concord-consortium.

the class MainFrame method getDailyPvAnalysisMenuItem.

private JMenuItem getDailyPvAnalysisMenuItem() {
    if (dailyPvAnalysisMenuItem == null) {
        dailyPvAnalysisMenuItem = new JMenuItem("Daily Yield Analysis of Solar Panels...");
        dailyPvAnalysisMenuItem.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(final ActionEvent e) {
                if (EnergyPanel.getInstance().checkCity()) {
                    int n = Scene.getInstance().countParts(new Class[] { SolarPanel.class, Rack.class });
                    if (n <= 0) {
                        JOptionPane.showMessageDialog(MainFrame.this, "There is no solar panel to analyze.", "No Solar Panel", JOptionPane.WARNING_MESSAGE);
                        return;
                    }
                    if (EnergyPanel.getInstance().adjustCellSize()) {
                        return;
                    }
                    final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                    if (selectedPart != null) {
                        Foundation foundation;
                        if (selectedPart instanceof Foundation) {
                            foundation = (Foundation) selectedPart;
                        } else {
                            foundation = selectedPart.getTopContainer();
                        }
                        if (foundation != null) {
                            n = foundation.countParts(new Class[] { SolarPanel.class, Rack.class });
                            if (n <= 0) {
                                JOptionPane.showMessageDialog(MainFrame.this, "There is no solar panel on this foundation to analyze.", "No Solar Panel", JOptionPane.WARNING_MESSAGE);
                                return;
                            }
                        }
                    }
                    final PvDailyAnalysis a = new PvDailyAnalysis();
                    if (SceneManager.getInstance().getSolarHeatMap()) {
                        a.updateGraph();
                    }
                    a.show();
                }
            }
        });
    }
    return dailyPvAnalysisMenuItem;
}
Also used : Rack(org.concord.energy3d.model.Rack) ActionListener(java.awt.event.ActionListener) ActionEvent(java.awt.event.ActionEvent) SolarPanel(org.concord.energy3d.model.SolarPanel) Foundation(org.concord.energy3d.model.Foundation) PvDailyAnalysis(org.concord.energy3d.simulation.PvDailyAnalysis) JMenuItem(javax.swing.JMenuItem) HousePart(org.concord.energy3d.model.HousePart)

Example 5 with PvDailyAnalysis

use of org.concord.energy3d.simulation.PvDailyAnalysis in project energy3d by concord-consortium.

the class PopupMenuForFoundation method getPopupMenu.

static JPopupMenu getPopupMenu(final MouseEvent e) {
    if (e.isShiftDown()) {
        SceneManager.getTaskManager().update(new Callable<Object>() {

            @Override
            public Object call() throws Exception {
                Scene.getInstance().pasteToPickedLocationOnFoundation();
                Scene.getInstance().setEdited(true);
                return null;
            }
        });
        return null;
    }
    if (popupMenuForFoundation == null) {
        final JMenuItem miImportCollada = new JMenuItem("Import Collada...");
        miImportCollada.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (selectedPart instanceof Foundation) {
                    final File file = FileChooser.getInstance().showDialog(".dae", FileChooser.daeFilter, false);
                    if (file != null) {
                        EnergyPanel.getInstance().updateRadiationHeatMap();
                        SceneManager.getTaskManager().update(new Callable<Object>() {

                            @Override
                            public Object call() throws Exception {
                                boolean success = true;
                                final Vector3 position = SceneManager.getInstance().getPickedLocationOnFoundation();
                                try {
                                    ((Foundation) selectedPart).importCollada(file.toURI().toURL(), position);
                                } catch (final Throwable t) {
                                    BugReporter.report(t);
                                    success = false;
                                }
                                if (success) {
                                    SceneManager.getInstance().getUndoManager().addEdit(new AddNodeCommand((Foundation) selectedPart));
                                }
                                return null;
                            }
                        });
                    }
                }
            }
        });
        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().pasteToPickedLocationOnFoundation();
                        Scene.getInstance().setEdited(true);
                        return null;
                    }
                });
            }
        });
        final JMenuItem miCopy = new JMenuItem("Copy");
        miCopy.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (selectedPart instanceof Foundation) {
                    Scene.getInstance().setCopyBuffer(selectedPart);
                }
            }
        });
        final JMenuItem miRescale = new JMenuItem("Rescale...");
        miRescale.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (!(selectedPart instanceof Foundation)) {
                    return;
                }
                new RescaleBuildingDialog((Foundation) selectedPart).setVisible(true);
                Scene.getInstance().setEdited(true);
            }
        });
        final JMenu rotateMenu = new JMenu("Rotate");
        final JMenuItem mi180 = new JMenuItem("180\u00B0");
        mi180.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(final ActionEvent e) {
                SceneManager.getInstance().rotate(Math.PI);
                Scene.getInstance().setEdited(true);
            }
        });
        rotateMenu.add(mi180);
        final JMenuItem mi90CW = new JMenuItem("90\u00B0 Clockwise");
        mi90CW.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(final ActionEvent e) {
                SceneManager.getInstance().rotate(-Math.PI / 2);
                Scene.getInstance().setEdited(true);
            }
        });
        rotateMenu.add(mi90CW);
        final JMenuItem mi90CCW = new JMenuItem("90\u00B0 Counter Clockwise");
        mi90CCW.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(final ActionEvent e) {
                SceneManager.getInstance().rotate(Math.PI / 2);
                Scene.getInstance().setEdited(true);
            }
        });
        rotateMenu.add(mi90CCW);
        rotateMenu.addSeparator();
        final JMenuItem miArbitraryRotation = new JMenuItem("Arbitrary...");
        rotateMenu.add(miArbitraryRotation);
        miArbitraryRotation.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (!(selectedPart instanceof Foundation)) {
                    return;
                }
                final String partInfo = selectedPart.toString().substring(0, selectedPart.toString().indexOf(')') + 1);
                final String title = "<html>Rotate " + partInfo + " (&deg;)</html>";
                final String footnote = "<html><hr><font size=2>Rotate a foundation to any angle by degrees.<br>Note: By convention, the angle for counter-wise<br>rotation (e.g., from north to west) is positive.<hr></html>";
                final JPanel gui = new JPanel(new BorderLayout());
                final JTextField inputField = new JTextField("0");
                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(), "Rotation Angle (\u00B0)");
                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 a = 0;
                        try {
                            a = 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 (!Util.isZero(a)) {
                                SceneManager.getInstance().rotate(Math.toRadians(a));
                                updateAfterEdit();
                            }
                            if (choice == options[0]) {
                                break;
                            }
                        }
                    }
                }
            }
        });
        final JMenu clearMenu = new JMenu("Clear");
        final JMenuItem miRemoveAllWalls = new JMenuItem("Remove All Walls");
        miRemoveAllWalls.addActionListener(new ActionListener() {

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

                    @Override
                    public Object call() {
                        Scene.getInstance().removeAllWalls();
                        EventQueue.invokeLater(new Runnable() {

                            @Override
                            public void run() {
                                MainPanel.getInstance().getEnergyButton().setSelected(false);
                                Scene.getInstance().setEdited(true);
                            }
                        });
                        return null;
                    }
                });
            }
        });
        clearMenu.add(miRemoveAllWalls);
        final JMenuItem miRemoveAllWindows = new JMenuItem("Remove All Windows");
        miRemoveAllWindows.addActionListener(new ActionListener() {

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

                    @Override
                    public Object call() {
                        Scene.getInstance().removeAllWindows();
                        EventQueue.invokeLater(new Runnable() {

                            @Override
                            public void run() {
                                MainPanel.getInstance().getEnergyButton().setSelected(false);
                                Scene.getInstance().setEdited(true);
                            }
                        });
                        return null;
                    }
                });
            }
        });
        clearMenu.add(miRemoveAllWindows);
        final JMenuItem miRemoveAllWindowShutters = new JMenuItem("Remove All Window Shutters");
        miRemoveAllWindowShutters.addActionListener(new ActionListener() {

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

                    @Override
                    public Object call() {
                        Scene.getInstance().removeAllWindowShutters();
                        EventQueue.invokeLater(new Runnable() {

                            @Override
                            public void run() {
                                MainPanel.getInstance().getEnergyButton().setSelected(false);
                                Scene.getInstance().setEdited(true);
                            }
                        });
                        return null;
                    }
                });
            }
        });
        clearMenu.add(miRemoveAllWindowShutters);
        final JMenuItem miRemoveAllSolarPanels = new JMenuItem("Remove All Solar Panels");
        miRemoveAllSolarPanels.addActionListener(new ActionListener() {

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

                    @Override
                    public Object call() {
                        Scene.getInstance().removeAllSolarPanels(null);
                        EventQueue.invokeLater(new Runnable() {

                            @Override
                            public void run() {
                                MainPanel.getInstance().getEnergyButton().setSelected(false);
                                Scene.getInstance().setEdited(true);
                            }
                        });
                        return null;
                    }
                });
            }
        });
        clearMenu.add(miRemoveAllSolarPanels);
        final JMenuItem miRemoveAllRacks = new JMenuItem("Remove All Solar Panel Racks");
        miRemoveAllRacks.addActionListener(new ActionListener() {

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

                    @Override
                    public Object call() {
                        Scene.getInstance().removeAllRacks();
                        EventQueue.invokeLater(new Runnable() {

                            @Override
                            public void run() {
                                MainPanel.getInstance().getEnergyButton().setSelected(false);
                                Scene.getInstance().setEdited(true);
                            }
                        });
                        return null;
                    }
                });
            }
        });
        clearMenu.add(miRemoveAllRacks);
        final JMenuItem miRemoveAllHeliostats = new JMenuItem("Remove All Heliostats");
        miRemoveAllHeliostats.addActionListener(new ActionListener() {

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

                    @Override
                    public Object call() {
                        Scene.getInstance().removeAllHeliostats();
                        EventQueue.invokeLater(new Runnable() {

                            @Override
                            public void run() {
                                MainPanel.getInstance().getEnergyButton().setSelected(false);
                                Scene.getInstance().setEdited(true);
                            }
                        });
                        return null;
                    }
                });
            }
        });
        clearMenu.add(miRemoveAllHeliostats);
        final JMenuItem miRemoveAllParabolicTroughs = new JMenuItem("Remove All Parabolic Troughs");
        miRemoveAllParabolicTroughs.addActionListener(new ActionListener() {

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

                    @Override
                    public Object call() {
                        Scene.getInstance().removeAllParabolicTroughs();
                        EventQueue.invokeLater(new Runnable() {

                            @Override
                            public void run() {
                                MainPanel.getInstance().getEnergyButton().setSelected(false);
                                Scene.getInstance().setEdited(true);
                            }
                        });
                        return null;
                    }
                });
            }
        });
        clearMenu.add(miRemoveAllParabolicTroughs);
        final JMenuItem miRemoveAllParabolicDishes = new JMenuItem("Remove All Parabolic Dishes");
        miRemoveAllParabolicDishes.addActionListener(new ActionListener() {

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

                    @Override
                    public Object call() {
                        Scene.getInstance().removeAllParabolicDishes();
                        EventQueue.invokeLater(new Runnable() {

                            @Override
                            public void run() {
                                MainPanel.getInstance().getEnergyButton().setSelected(false);
                                Scene.getInstance().setEdited(true);
                            }
                        });
                        return null;
                    }
                });
            }
        });
        clearMenu.add(miRemoveAllParabolicDishes);
        final JMenuItem miRemoveAllFresnelReflectors = new JMenuItem("Remove All Fresnel Reflectors");
        miRemoveAllFresnelReflectors.addActionListener(new ActionListener() {

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

                    @Override
                    public Object call() {
                        Scene.getInstance().removeAllFresnelReflectors();
                        EventQueue.invokeLater(new Runnable() {

                            @Override
                            public void run() {
                                MainPanel.getInstance().getEnergyButton().setSelected(false);
                                Scene.getInstance().setEdited(true);
                            }
                        });
                        return null;
                    }
                });
            }
        });
        clearMenu.add(miRemoveAllFresnelReflectors);
        final JMenuItem miRemoveAllSensors = new JMenuItem("Remove All Sensors");
        miRemoveAllSensors.addActionListener(new ActionListener() {

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

                    @Override
                    public Object call() {
                        Scene.getInstance().removeAllSensors();
                        EventQueue.invokeLater(new Runnable() {

                            @Override
                            public void run() {
                                MainPanel.getInstance().getEnergyButton().setSelected(false);
                                Scene.getInstance().setEdited(true);
                            }
                        });
                        return null;
                    }
                });
            }
        });
        clearMenu.add(miRemoveAllSensors);
        final JMenuItem removeAllFloorsMenuItem = new JMenuItem("Remove All Floors");
        removeAllFloorsMenuItem.addActionListener(new ActionListener() {

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

                    @Override
                    public Object call() {
                        Scene.getInstance().removeAllFloors();
                        EventQueue.invokeLater(new Runnable() {

                            @Override
                            public void run() {
                                MainPanel.getInstance().getEnergyButton().setSelected(false);
                                Scene.getInstance().setEdited(true);
                            }
                        });
                        return null;
                    }
                });
            }
        });
        clearMenu.add(removeAllFloorsMenuItem);
        final JMenuItem miRemoveAllImportedNodes = new JMenuItem("Remove All Nodes");
        miRemoveAllImportedNodes.addActionListener(new ActionListener() {

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

                    @Override
                    public Object call() {
                        final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                        if (selectedPart instanceof Foundation) {
                            final Foundation f = (Foundation) selectedPart;
                            f.removeAllImports();
                            f.setMeshSelectionVisible(false);
                            EventQueue.invokeLater(new Runnable() {

                                @Override
                                public void run() {
                                    MainPanel.getInstance().getEnergyButton().setSelected(false);
                                    Scene.getInstance().setEdited(true);
                                }
                            });
                        }
                        return null;
                    }
                });
            }
        });
        clearMenu.add(miRemoveAllImportedNodes);
        final JMenuItem miRemoveAllWithinInset = new JMenuItem("Remove All Objects within Inset");
        miRemoveAllWithinInset.addActionListener(new ActionListener() {

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

                    @Override
                    public Object call() {
                        ((Foundation) selectedPart).removeAllWithinPolygon();
                        EventQueue.invokeLater(new Runnable() {

                            @Override
                            public void run() {
                                MainPanel.getInstance().getEnergyButton().setSelected(false);
                                Scene.getInstance().setEdited(true);
                            }
                        });
                        return null;
                    }
                });
            }
        });
        clearMenu.add(miRemoveAllWithinInset);
        final JMenuItem miResetPolygonInset = new JMenuItem("Reset Inset");
        miResetPolygonInset.addActionListener(new ActionListener() {

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

                    @Override
                    public Object call() {
                        ((Foundation) selectedPart).resetPolygon();
                        EventQueue.invokeLater(new Runnable() {

                            @Override
                            public void run() {
                                Scene.getInstance().setEdited(true);
                            }
                        });
                        return null;
                    }
                });
            }
        });
        clearMenu.add(miResetPolygonInset);
        final JMenu layoutMenu = new JMenu("Layout");
        final JMenuItem miSolarPanelArrays = new JMenuItem("Solar Panel Arrays...");
        layoutMenu.add(miSolarPanelArrays);
        miSolarPanelArrays.addActionListener(new ActionListener() {

            private Foundation f;

            private JComboBox<String> modelComboBox;

            private JComboBox<String> cellTypeComboBox;

            private JComboBox<String> colorOptionComboBox;

            private JComboBox<String> sizeComboBox;

            private JComboBox<String> shadeToleranceComboBox;

            private JComboBox<String> orientationComboBox;

            private JComboBox<String> rowAxisComboBox;

            private JTextField cellEfficiencyField;

            private JTextField noctField;

            private JTextField pmaxTcField;

            private int numberOfCellsInX = 6;

            private int numberOfCellsInY = 10;

            private void enableSettings(final boolean b) {
                sizeComboBox.setEnabled(b);
                cellTypeComboBox.setEnabled(b);
                colorOptionComboBox.setEnabled(b);
                shadeToleranceComboBox.setEnabled(b);
                cellEfficiencyField.setEnabled(b);
                noctField.setEnabled(b);
                pmaxTcField.setEnabled(b);
            }

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (selectedPart instanceof Foundation) {
                    f = (Foundation) selectedPart;
                    int n = f.countParts(SolarPanel.class);
                    if (n > 0 && JOptionPane.showConfirmDialog(MainFrame.getInstance(), "All existing " + n + " solar panels on this foundation must be removed before\na new layout can be applied. Do you want to continue?", "Confirmation", JOptionPane.OK_CANCEL_OPTION) == JOptionPane.CANCEL_OPTION) {
                        return;
                    }
                    n = f.countParts(Rack.class);
                    if (n > 0 && JOptionPane.showConfirmDialog(MainFrame.getInstance(), "All existing " + n + " solar panel racks on this foundation 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 JPanel panel = new JPanel(new SpringLayout());
                    final Map<String, PvModuleSpecs> modules = PvModulesData.getInstance().getModules();
                    final String[] models = new String[modules.size() + 1];
                    int j = 0;
                    models[j] = "Custom";
                    for (final String key : modules.keySet()) {
                        models[++j] = key;
                    }
                    panel.add(new JLabel("Model:"));
                    modelComboBox = new JComboBox<String>(models);
                    modelComboBox.setSelectedItem(solarPanelModel);
                    modelComboBox.addItemListener(new ItemListener() {

                        @Override
                        public void itemStateChanged(final ItemEvent e) {
                            if (e.getStateChange() == ItemEvent.SELECTED) {
                                final boolean isCustom = modelComboBox.getSelectedIndex() == 0;
                                enableSettings(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);
                    panel.add(new JLabel("Cell Type:"));
                    cellTypeComboBox = new JComboBox<String>(new String[] { "Polycrystalline", "Monocrystalline", "Thin Film" });
                    cellTypeComboBox.setSelectedIndex(solarPanelCellType);
                    panel.add(cellTypeComboBox);
                    panel.add(new JLabel("Color:"));
                    colorOptionComboBox = new JComboBox<String>(new String[] { "Blue", "Black", "Gray" });
                    colorOptionComboBox.setSelectedIndex(solarPanelColorOption);
                    panel.add(colorOptionComboBox);
                    panel.add(new JLabel("Size:"));
                    sizeComboBox = new JComboBox<String>(solarPanelNominalSize.getStrings());
                    final int nItems = sizeComboBox.getItemCount();
                    for (int i = 0; i < nItems; i++) {
                        if (Util.isZero(solarPanelHeight - solarPanelNominalSize.getNominalHeights()[i]) && Util.isZero(solarPanelWidth - solarPanelNominalSize.getNominalWidths()[i])) {
                            sizeComboBox.setSelectedIndex(i);
                        }
                    }
                    panel.add(sizeComboBox);
                    panel.add(new JLabel("Solar Cell Efficiency (%):"));
                    cellEfficiencyField = new JTextField(threeDecimalsFormat.format(solarCellEfficiencyPercentage));
                    panel.add(cellEfficiencyField);
                    panel.add(new JLabel("<html>Nominal Operating Cell Temperature (&deg;C):"));
                    noctField = new JTextField(threeDecimalsFormat.format(solarPanelNominalOperatingCellTemperature));
                    panel.add(noctField);
                    panel.add(new JLabel("<html>Temperature Coefficient of Pmax (%/&deg;C):"));
                    pmaxTcField = new JTextField(sixDecimalsFormat.format(solarPanelTemperatureCoefficientPmaxPercentage));
                    panel.add(pmaxTcField);
                    panel.add(new JLabel("Shade Tolerance:"));
                    shadeToleranceComboBox = new JComboBox<String>(new String[] { "Partial", "High", "None" });
                    shadeToleranceComboBox.setSelectedIndex(solarPanelShadeTolerance);
                    panel.add(shadeToleranceComboBox);
                    panel.add(new JLabel("Inverter Efficiency (%):"));
                    final JTextField inverterEfficiencyField = new JTextField(threeDecimalsFormat.format(inverterEfficiencyPercentage));
                    panel.add(inverterEfficiencyField);
                    panel.add(new JLabel("Tile Angle (\u00B0):"));
                    final JTextField tiltAngleField = new JTextField(threeDecimalsFormat.format(solarPanelTiltAngle));
                    panel.add(tiltAngleField);
                    panel.add(new JLabel("Orientation:"));
                    orientationComboBox = new JComboBox<String>(new String[] { "Portrait", "Landscape" });
                    orientationComboBox.setSelectedIndex(solarPanelOrientation);
                    panel.add(orientationComboBox);
                    panel.add(new JLabel("Row Axis:"));
                    rowAxisComboBox = new JComboBox<String>(new String[] { "North-South", "East-West" });
                    rowAxisComboBox.setSelectedIndex(solarPanelArrayRowAxis);
                    panel.add(rowAxisComboBox);
                    panel.add(new JLabel("Row Spacing (m):"));
                    final JTextField rowSpacingField = new JTextField(threeDecimalsFormat.format(solarPanelArrayRowSpacing));
                    panel.add(rowSpacingField);
                    panel.add(new JLabel("Column Spacing (m):"));
                    final JTextField colSpacingField = new JTextField(threeDecimalsFormat.format(solarPanelArrayColSpacing));
                    panel.add(colSpacingField);
                    panel.add(new JLabel("Base Height (m):"));
                    final JTextField baseHeightField = new JTextField(threeDecimalsFormat.format(solarPanelArrayBaseHeight));
                    panel.add(baseHeightField);
                    SpringUtilities.makeCompactGrid(panel, 15, 2, 6, 6, 6, 6);
                    enableSettings(modelComboBox.getSelectedIndex() == 0);
                    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 Panel Array Options");
                    while (true) {
                        dialog.setVisible(true);
                        final Object choice = optionPane.getValue();
                        if (choice == options[1] || choice == null) {
                            break;
                        } else {
                            boolean ok = true;
                            try {
                                solarPanelArrayRowSpacing = Double.parseDouble(rowSpacingField.getText());
                                solarPanelArrayColSpacing = Double.parseDouble(colSpacingField.getText());
                                solarPanelArrayBaseHeight = Double.parseDouble(baseHeightField.getText());
                                solarPanelTiltAngle = Double.parseDouble(tiltAngleField.getText());
                                solarCellEfficiencyPercentage = Double.parseDouble(cellEfficiencyField.getText());
                                inverterEfficiencyPercentage = Double.parseDouble(inverterEfficiencyField.getText());
                                solarPanelTemperatureCoefficientPmaxPercentage = Double.parseDouble(pmaxTcField.getText());
                                solarPanelNominalOperatingCellTemperature = Double.parseDouble(noctField.getText());
                            } catch (final NumberFormatException exception) {
                                JOptionPane.showMessageDialog(MainFrame.getInstance(), "Invalid input!", "Error", JOptionPane.ERROR_MESSAGE);
                                ok = false;
                            }
                            if (ok) {
                                final int i = sizeComboBox.getSelectedIndex();
                                solarPanelWidth = solarPanelNominalSize.getNominalWidths()[i];
                                solarPanelHeight = solarPanelNominalSize.getNominalHeights()[i];
                                numberOfCellsInX = solarPanelNominalSize.getCellNx()[i];
                                numberOfCellsInY = solarPanelNominalSize.getCellNy()[i];
                                solarPanelOrientation = orientationComboBox.getSelectedIndex();
                                if (solarPanelArrayRowSpacing < (solarPanelOrientation == 0 ? solarPanelHeight : solarPanelWidth) || solarPanelArrayColSpacing < (solarPanelOrientation == 0 ? solarPanelWidth : solarPanelHeight)) {
                                    JOptionPane.showMessageDialog(MainFrame.getInstance(), "Solar panel row or column spacing is too small.", "Range Error", JOptionPane.ERROR_MESSAGE);
                                } else if (solarPanelArrayBaseHeight < 0) {
                                    JOptionPane.showMessageDialog(MainFrame.getInstance(), "Solar panel base height can't be negative.", "Range Error", JOptionPane.ERROR_MESSAGE);
                                } else if (solarPanelTiltAngle < -90 || solarPanelTiltAngle > 90) {
                                    JOptionPane.showMessageDialog(MainFrame.getInstance(), "Solar panel tilt angle must be between -90\u00B0 and 90\u00B0.", "Range Error", JOptionPane.ERROR_MESSAGE);
                                } else if (Math.abs(0.5 * (solarPanelOrientation == 0 ? solarPanelHeight : solarPanelWidth) * Math.sin(Math.toRadians(solarPanelTiltAngle))) > solarPanelArrayBaseHeight) {
                                    JOptionPane.showMessageDialog(MainFrame.getInstance(), "Solar panels intersect with ground.", "Geometry Error", JOptionPane.ERROR_MESSAGE);
                                } else 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 if (inverterEfficiencyPercentage < SolarPanel.MIN_INVERTER_EFFICIENCY_PERCENTAGE || inverterEfficiencyPercentage >= 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 (solarPanelTemperatureCoefficientPmaxPercentage < -1 || solarPanelTemperatureCoefficientPmaxPercentage > 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 (solarPanelNominalOperatingCellTemperature < 33 || solarPanelNominalOperatingCellTemperature > 58) {
                                    JOptionPane.showMessageDialog(MainFrame.getInstance(), "Nominal operating cell temperature must be between 33 and 58 Celsius degree.", "Range Error", JOptionPane.ERROR_MESSAGE);
                                } else {
                                    addSolarPanelArrays();
                                    if (choice == options[0]) {
                                        break;
                                    }
                                }
                            }
                        }
                    }
                }
            }

            private void addSolarPanelArrays() {
                solarPanelArrayRowAxis = rowAxisComboBox.getSelectedIndex();
                solarPanelShadeTolerance = shadeToleranceComboBox.getSelectedIndex();
                solarPanelColorOption = colorOptionComboBox.getSelectedIndex();
                solarPanelCellType = cellTypeComboBox.getSelectedIndex();
                solarPanelModel = (String) modelComboBox.getSelectedItem();
                final SolarPanel sp = new SolarPanel();
                sp.setModelName((String) modelComboBox.getSelectedItem());
                sp.setRotated(solarPanelOrientation == 1);
                sp.setCellType(solarPanelCellType);
                sp.setColorOption(solarPanelColorOption);
                sp.setTiltAngle(solarPanelTiltAngle);
                sp.setPanelWidth(solarPanelWidth);
                sp.setPanelHeight(solarPanelHeight);
                sp.setNumberOfCellsInX(numberOfCellsInX);
                sp.setNumberOfCellsInY(numberOfCellsInY);
                sp.setBaseHeight(solarPanelArrayBaseHeight / Scene.getInstance().getAnnotationScale());
                sp.setShadeTolerance(solarPanelShadeTolerance);
                sp.setCellEfficiency(solarCellEfficiencyPercentage * 0.01);
                sp.setInverterEfficiency(inverterEfficiencyPercentage * 0.01);
                sp.setTemperatureCoefficientPmax(solarPanelTemperatureCoefficientPmaxPercentage * 0.01);
                sp.setNominalOperatingCellTemperature(solarPanelNominalOperatingCellTemperature);
                SceneManager.getTaskManager().update(new Callable<Object>() {

                    @Override
                    public Object call() {
                        f.addSolarPanelArrays(sp, solarPanelArrayRowSpacing, solarPanelArrayColSpacing, solarPanelArrayRowAxis);
                        return null;
                    }
                });
                updateAfterEdit();
            }
        });
        final JMenuItem miSolarRackArrays = new JMenuItem("Solar Panel Rack Arrays...");
        layoutMenu.add(miSolarRackArrays);
        miSolarRackArrays.addActionListener(new ActionListener() {

            private Foundation foundation;

            private JComboBox<String> modelComboBox;

            private JComboBox<String> orientationComboBox;

            private JComboBox<String> sizeComboBox;

            private JComboBox<String> cellTypeComboBox;

            private JComboBox<String> colorOptionComboBox;

            private JComboBox<String> shadeToleranceComboBox;

            private JComboBox<String> rowAxisComboBox;

            private JTextField cellEfficiencyField;

            private JTextField noctField;

            private JTextField pmaxTcField;

            private int numberOfCellsInX = 6;

            private int numberOfCellsInY = 10;

            private void enableSettings(final boolean b) {
                sizeComboBox.setEnabled(b);
                cellTypeComboBox.setEnabled(b);
                colorOptionComboBox.setEnabled(b);
                shadeToleranceComboBox.setEnabled(b);
                cellEfficiencyField.setEnabled(b);
                noctField.setEnabled(b);
                pmaxTcField.setEnabled(b);
            }

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (selectedPart instanceof Foundation) {
                    foundation = (Foundation) selectedPart;
                    int n = foundation.countParts(Rack.class);
                    if (n > 0 && JOptionPane.showConfirmDialog(MainFrame.getInstance(), "All existing " + n + " solar panel racks on this foundation must be removed before\na new layout can be applied. Do you want to continue?", "Confirmation", JOptionPane.OK_CANCEL_OPTION) == JOptionPane.CANCEL_OPTION) {
                        return;
                    }
                    n = foundation.countParts(SolarPanel.class);
                    if (n > 0 && JOptionPane.showConfirmDialog(MainFrame.getInstance(), "All existing " + n + " solar panels on this foundation 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 JPanel panel = new JPanel(new SpringLayout());
                    final Map<String, PvModuleSpecs> modules = PvModulesData.getInstance().getModules();
                    final String[] models = new String[modules.size() + 1];
                    int j = 0;
                    models[j] = "Custom";
                    for (final String key : modules.keySet()) {
                        models[++j] = key;
                    }
                    panel.add(new JLabel("Solar Panel Model:"));
                    modelComboBox = new JComboBox<String>(models);
                    modelComboBox.setSelectedItem(solarPanelModel);
                    modelComboBox.addItemListener(new ItemListener() {

                        @Override
                        public void itemStateChanged(final ItemEvent e) {
                            if (e.getStateChange() == ItemEvent.SELECTED) {
                                final boolean isCustom = modelComboBox.getSelectedIndex() == 0;
                                enableSettings(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);
                    panel.add(new JLabel("Solar Panel Cell Type:"));
                    cellTypeComboBox = new JComboBox<String>(new String[] { "Polycrystalline", "Monocrystalline", "Thin Film" });
                    cellTypeComboBox.setSelectedIndex(solarPanelCellType);
                    panel.add(cellTypeComboBox);
                    panel.add(new JLabel("Solar Panel Color:"));
                    colorOptionComboBox = new JComboBox<String>(new String[] { "Blue", "Black", "Gray" });
                    colorOptionComboBox.setSelectedIndex(solarPanelColorOption);
                    panel.add(colorOptionComboBox);
                    panel.add(new JLabel("Solar Panel Size:"));
                    sizeComboBox = new JComboBox<String>(solarPanelNominalSize.getStrings());
                    final int nItems = sizeComboBox.getItemCount();
                    for (int i = 0; i < nItems; i++) {
                        if (Util.isZero(solarPanelHeight - solarPanelNominalSize.getNominalHeights()[i]) && Util.isZero(solarPanelWidth - solarPanelNominalSize.getNominalWidths()[i])) {
                            sizeComboBox.setSelectedIndex(i);
                        }
                    }
                    panel.add(sizeComboBox);
                    panel.add(new JLabel("Solar Cell Efficiency (%):"));
                    cellEfficiencyField = new JTextField(threeDecimalsFormat.format(solarCellEfficiencyPercentage));
                    panel.add(cellEfficiencyField);
                    panel.add(new JLabel("<html>Nominal Operating Cell Temperature (&deg;C):"));
                    noctField = new JTextField(threeDecimalsFormat.format(solarPanelNominalOperatingCellTemperature));
                    panel.add(noctField);
                    panel.add(new JLabel("<html>Temperature Coefficient of Pmax (%/&deg;C):"));
                    pmaxTcField = new JTextField(sixDecimalsFormat.format(solarPanelTemperatureCoefficientPmaxPercentage));
                    panel.add(pmaxTcField);
                    panel.add(new JLabel("Shade Tolerance:"));
                    shadeToleranceComboBox = new JComboBox<String>(new String[] { "Partial", "High", "None" });
                    shadeToleranceComboBox.setSelectedIndex(solarPanelShadeTolerance);
                    panel.add(shadeToleranceComboBox);
                    panel.add(new JLabel("Inverter Efficiency (%):"));
                    final JTextField inverterEfficiencyField = new JTextField(threeDecimalsFormat.format(inverterEfficiencyPercentage));
                    panel.add(inverterEfficiencyField);
                    panel.add(new JLabel("Tile Angle (\u00B0):"));
                    final JTextField tiltAngleField = new JTextField(threeDecimalsFormat.format(solarPanelTiltAngle));
                    panel.add(tiltAngleField);
                    panel.add(new JLabel("Solar Panel Sub-Rows Per Rack:"));
                    final JTextField rowsPerRackField = new JTextField(threeDecimalsFormat.format(solarPanelRowsPerRack));
                    panel.add(rowsPerRackField);
                    panel.add(new JLabel("Solar Panel Orientation:"));
                    orientationComboBox = new JComboBox<String>(new String[] { "Portrait", "Landscape" });
                    orientationComboBox.setSelectedIndex(solarPanelOrientation);
                    panel.add(orientationComboBox);
                    panel.add(new JLabel("Row Axis:"));
                    rowAxisComboBox = new JComboBox<String>(new String[] { "North-South", "East-West" });
                    rowAxisComboBox.setSelectedIndex(solarPanelArrayRowAxis);
                    panel.add(rowAxisComboBox);
                    panel.add(new JLabel("Inter-Row Center-to-Center Distance (m):"));
                    final JTextField interrowSpacingField = new JTextField(threeDecimalsFormat.format(solarPanelRackArrayInterRowSpacing));
                    panel.add(interrowSpacingField);
                    panel.add(new JLabel("Pole Spacing X (m):"));
                    final JTextField poleSpacingXField = new JTextField(threeDecimalsFormat.format(solarPanelRackPoleSpacingX));
                    panel.add(poleSpacingXField);
                    panel.add(new JLabel("Pole Spacing Y (m):"));
                    final JTextField poleSpacingYField = new JTextField(threeDecimalsFormat.format(solarPanelRackPoleSpacingY));
                    panel.add(poleSpacingYField);
                    panel.add(new JLabel("Base Height (m):"));
                    final JTextField baseHeightField = new JTextField(threeDecimalsFormat.format(solarPanelRackBaseHeight));
                    panel.add(baseHeightField);
                    SpringUtilities.makeCompactGrid(panel, 17, 2, 6, 6, 6, 6);
                    enableSettings(modelComboBox.getSelectedIndex() == 0);
                    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 Panel Rack Options");
                    while (true) {
                        dialog.setVisible(true);
                        final Object choice = optionPane.getValue();
                        if (choice == options[1] || choice == null) {
                            break;
                        } else {
                            boolean ok = true;
                            try {
                                solarPanelRackArrayInterRowSpacing = Double.parseDouble(interrowSpacingField.getText());
                                solarPanelTiltAngle = Double.parseDouble(tiltAngleField.getText());
                                solarPanelRowsPerRack = Integer.parseInt(rowsPerRackField.getText());
                                solarCellEfficiencyPercentage = Double.parseDouble(cellEfficiencyField.getText());
                                inverterEfficiencyPercentage = Double.parseDouble(inverterEfficiencyField.getText());
                                solarPanelTemperatureCoefficientPmaxPercentage = Double.parseDouble(pmaxTcField.getText());
                                solarPanelNominalOperatingCellTemperature = Double.parseDouble(noctField.getText());
                                solarPanelRackPoleSpacingX = Double.parseDouble(poleSpacingXField.getText());
                                solarPanelRackPoleSpacingY = Double.parseDouble(poleSpacingYField.getText());
                                solarPanelRackBaseHeight = Double.parseDouble(baseHeightField.getText());
                            } catch (final NumberFormatException exception) {
                                JOptionPane.showMessageDialog(MainFrame.getInstance(), "Invalid value!", "Error", JOptionPane.ERROR_MESSAGE);
                                ok = false;
                            }
                            if (ok) {
                                final int i = sizeComboBox.getSelectedIndex();
                                solarPanelWidth = solarPanelNominalSize.getNominalWidths()[i];
                                solarPanelHeight = solarPanelNominalSize.getNominalHeights()[i];
                                numberOfCellsInX = solarPanelNominalSize.getCellNx()[i];
                                numberOfCellsInY = solarPanelNominalSize.getCellNy()[i];
                                solarPanelOrientation = orientationComboBox.getSelectedIndex();
                                final double rackHeight = (solarPanelOrientation == 0 ? solarPanelHeight : solarPanelWidth) * solarPanelRowsPerRack;
                                if (solarPanelTiltAngle < -90 || solarPanelTiltAngle > 90) {
                                    JOptionPane.showMessageDialog(MainFrame.getInstance(), "Rack tilt angle must be between -90\u00B0 and 90\u00B0.", "Range Error", JOptionPane.ERROR_MESSAGE);
                                } else if (solarPanelRackPoleSpacingX < 1 || solarPanelRackPoleSpacingX > 50) {
                                    JOptionPane.showMessageDialog(MainFrame.getInstance(), "Pole spacing X must be between 1 and 50 m.", "Range Error", JOptionPane.ERROR_MESSAGE);
                                } else if (solarPanelRackBaseHeight < 0) {
                                    JOptionPane.showMessageDialog(MainFrame.getInstance(), "Base height can't be negative.", "Range Error", JOptionPane.ERROR_MESSAGE);
                                } else if (Math.abs(0.5 * rackHeight * Math.sin(Math.toRadians(solarPanelTiltAngle))) > solarPanelRackBaseHeight) {
                                    JOptionPane.showMessageDialog(MainFrame.getInstance(), "Solar panels intersect with ground.", "Geometry Error", JOptionPane.ERROR_MESSAGE);
                                } else if (solarPanelRackPoleSpacingY < 1 || solarPanelRackPoleSpacingY > 50) {
                                    JOptionPane.showMessageDialog(MainFrame.getInstance(), "Pole spacing Y must be between 1 and 50 m.", "Range Error", JOptionPane.ERROR_MESSAGE);
                                } else if (solarPanelRowsPerRack <= 0 || solarPanelRowsPerRack > 10) {
                                    JOptionPane.showMessageDialog(MainFrame.getInstance(), "Illegal value for solar panel rows per rack.", "Range Error", JOptionPane.ERROR_MESSAGE);
                                } else 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 if (inverterEfficiencyPercentage < SolarPanel.MIN_INVERTER_EFFICIENCY_PERCENTAGE || inverterEfficiencyPercentage >= 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 (solarPanelTemperatureCoefficientPmaxPercentage < -1 || solarPanelTemperatureCoefficientPmaxPercentage > 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 (solarPanelNominalOperatingCellTemperature < 33 || solarPanelNominalOperatingCellTemperature > 58) {
                                    JOptionPane.showMessageDialog(MainFrame.getInstance(), "Nominal operating cell temperature must be between 33 and 58 Celsius degree.", "Range Error", JOptionPane.ERROR_MESSAGE);
                                } else if (solarPanelRackArrayInterRowSpacing < rackHeight) {
                                    JOptionPane.showMessageDialog(MainFrame.getInstance(), "Inter-row center-to-center distance cannot be smaller than " + EnergyPanel.TWO_DECIMALS.format(rackHeight) + "m (" + solarPanelRowsPerRack + "\u00d7" + EnergyPanel.TWO_DECIMALS.format((solarPanelOrientation == 0 ? solarPanelHeight : solarPanelWidth)) + "m)", "Range Error", JOptionPane.ERROR_MESSAGE);
                                } else {
                                    addSolarRackArrays();
                                    if (choice == options[0]) {
                                        break;
                                    }
                                }
                            }
                        }
                    }
                }
            }

            private void addSolarRackArrays() {
                solarPanelColorOption = colorOptionComboBox.getSelectedIndex();
                solarPanelCellType = cellTypeComboBox.getSelectedIndex();
                solarPanelShadeTolerance = shadeToleranceComboBox.getSelectedIndex();
                solarPanelArrayRowAxis = rowAxisComboBox.getSelectedIndex();
                solarPanelModel = (String) modelComboBox.getSelectedItem();
                final SolarPanel sp = new SolarPanel();
                sp.setModelName((String) modelComboBox.getSelectedItem());
                sp.setRotated(solarPanelOrientation == 1);
                sp.setCellType(solarPanelCellType);
                sp.setColorOption(solarPanelColorOption);
                sp.setPanelWidth(solarPanelWidth);
                sp.setPanelHeight(solarPanelHeight);
                sp.setNumberOfCellsInX(numberOfCellsInX);
                sp.setNumberOfCellsInY(numberOfCellsInY);
                sp.setShadeTolerance(solarPanelShadeTolerance);
                sp.setCellEfficiency(solarCellEfficiencyPercentage * 0.01);
                sp.setInverterEfficiency(inverterEfficiencyPercentage * 0.01);
                sp.setTemperatureCoefficientPmax(solarPanelTemperatureCoefficientPmaxPercentage * 0.01);
                sp.setNominalOperatingCellTemperature(solarPanelNominalOperatingCellTemperature);
                SceneManager.getTaskManager().update(new Callable<Object>() {

                    @Override
                    public Object call() {
                        foundation.addSolarRackArrays(sp, solarPanelTiltAngle, solarPanelRackBaseHeight, solarPanelRowsPerRack, solarPanelRackArrayInterRowSpacing, solarPanelArrayRowAxis, solarPanelRackPoleSpacingX, solarPanelRackPoleSpacingY);
                        return null;
                    }
                });
                updateAfterEdit();
            }
        });
        layoutMenu.addSeparator();
        final JMenuItem miHeliostatCircularArrays = new JMenuItem("Heliostat Circular Layout...");
        layoutMenu.add(miHeliostatCircularArrays);
        miHeliostatCircularArrays.addActionListener(new ActionListener() {

            private Foundation f;

            private JComboBox<String> typeComboBox;

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (selectedPart instanceof Foundation) {
                    f = (Foundation) selectedPart;
                    final int n = f.countParts(Mirror.class);
                    if (n > 0 && JOptionPane.showConfirmDialog(MainFrame.getInstance(), "All existing " + n + " heliostats on this foundation 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 JPanel panel = new JPanel(new SpringLayout());
                    panel.add(new JLabel("Type:"));
                    typeComboBox = new JComboBox<String>(new String[] { "Equal Azimuthal Spacing", "Radial Stagger" });
                    typeComboBox.setSelectedIndex(heliostatCircularFieldLayout.getType());
                    panel.add(typeComboBox);
                    panel.add(new JLabel("Aperture Width:"));
                    final JTextField widthField = new JTextField(threeDecimalsFormat.format(heliostatCircularFieldLayout.getApertureWidth()));
                    panel.add(widthField);
                    panel.add(new JLabel("Aperture Height:"));
                    final JTextField heightField = new JTextField(threeDecimalsFormat.format(heliostatCircularFieldLayout.getApertureHeight()));
                    panel.add(heightField);
                    panel.add(new JLabel("Start Angle (CCW from East):"));
                    final JTextField startAngleField = new JTextField(threeDecimalsFormat.format(heliostatCircularFieldLayout.getStartAngle()));
                    panel.add(startAngleField);
                    panel.add(new JLabel("End Angle (CCW from East):"));
                    final JTextField endAngleField = new JTextField(threeDecimalsFormat.format(heliostatCircularFieldLayout.getEndAngle()));
                    panel.add(endAngleField);
                    panel.add(new JLabel("Radial Spacing:"));
                    final JTextField rowSpacingField = new JTextField(threeDecimalsFormat.format(heliostatCircularFieldLayout.getRadialSpacing()));
                    panel.add(rowSpacingField);
                    panel.add(new JLabel("Radial Spacing Increase Ratio:"));
                    final JTextField radialSpacingIncrementField = new JTextField(sixDecimalsFormat.format(heliostatCircularFieldLayout.getRadialSpacingIncrement()));
                    panel.add(radialSpacingIncrementField);
                    panel.add(new JLabel("Azimuthal Spacing:"));
                    final JTextField azimuthalSpacingField = new JTextField(threeDecimalsFormat.format(heliostatCircularFieldLayout.getAzimuthalSpacing()));
                    panel.add(azimuthalSpacingField);
                    panel.add(new JLabel("Axis Road Width:"));
                    final JTextField axisRoadWidthField = new JTextField(threeDecimalsFormat.format(heliostatCircularFieldLayout.getAxisRoadWidth()));
                    panel.add(axisRoadWidthField);
                    panel.add(new JLabel("Base Height:"));
                    final JTextField baseHeightField = new JTextField(threeDecimalsFormat.format(heliostatCircularFieldLayout.getBaseHeight()));
                    panel.add(baseHeightField);
                    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(), "Circular Heliostat Array Options");
                    while (true) {
                        dialog.setVisible(true);
                        final Object choice = optionPane.getValue();
                        if (choice == options[1] || choice == null) {
                            break;
                        } else {
                            boolean ok = true;
                            try {
                                heliostatCircularFieldLayout.setRadialSpacing(Double.parseDouble(rowSpacingField.getText()));
                                heliostatCircularFieldLayout.setRadialSpacingIncrement(Double.parseDouble(radialSpacingIncrementField.getText()));
                                heliostatCircularFieldLayout.setAzimuthalSpacing(Double.parseDouble(azimuthalSpacingField.getText()));
                                heliostatCircularFieldLayout.setApertureWidth(Double.parseDouble(widthField.getText()));
                                heliostatCircularFieldLayout.setApertureHeight(Double.parseDouble(heightField.getText()));
                                heliostatCircularFieldLayout.setStartAngle(Double.parseDouble(startAngleField.getText()));
                                heliostatCircularFieldLayout.setEndAngle(Double.parseDouble(endAngleField.getText()));
                                heliostatCircularFieldLayout.setAxisRoadWidth(Double.parseDouble(axisRoadWidthField.getText()));
                                heliostatCircularFieldLayout.setBaseHeight(Double.parseDouble(baseHeightField.getText()));
                            } catch (final NumberFormatException exception) {
                                JOptionPane.showMessageDialog(MainFrame.getInstance(), "Invalid value!", "Error", JOptionPane.ERROR_MESSAGE);
                                ok = false;
                            }
                            if (ok) {
                                if (heliostatCircularFieldLayout.getRadialSpacing() < 0 || heliostatCircularFieldLayout.getAzimuthalSpacing() < 0) {
                                    JOptionPane.showMessageDialog(MainFrame.getInstance(), "Heliostat spacing cannot be negative.", "Range Error", JOptionPane.ERROR_MESSAGE);
                                } else if (heliostatCircularFieldLayout.getRadialSpacingIncrement() < 0) {
                                    JOptionPane.showMessageDialog(MainFrame.getInstance(), "Radial spacing increment ratio cannot be negative.", "Range Error", JOptionPane.ERROR_MESSAGE);
                                } else if (heliostatCircularFieldLayout.getAxisRoadWidth() < 0) {
                                    JOptionPane.showMessageDialog(MainFrame.getInstance(), "Axis road width cannot be negative.", "Range Error", JOptionPane.ERROR_MESSAGE);
                                } else if (heliostatCircularFieldLayout.getStartAngle() < 0 || heliostatCircularFieldLayout.getStartAngle() > 360) {
                                    JOptionPane.showMessageDialog(MainFrame.getInstance(), "Start angle must be between 0 and 360 degrees.", "Range Error", JOptionPane.ERROR_MESSAGE);
                                } else if (heliostatCircularFieldLayout.getEndAngle() < 0 || heliostatCircularFieldLayout.getEndAngle() > 360) {
                                    JOptionPane.showMessageDialog(MainFrame.getInstance(), "End angle must be between 0 and 360 degrees.", "Range Error", JOptionPane.ERROR_MESSAGE);
                                } else if (heliostatCircularFieldLayout.getEndAngle() <= heliostatCircularFieldLayout.getStartAngle()) {
                                    JOptionPane.showMessageDialog(MainFrame.getInstance(), "End angle must be greater than start angle.", "Range Error", JOptionPane.ERROR_MESSAGE);
                                } else if (heliostatCircularFieldLayout.getApertureWidth() < 1 || heliostatCircularFieldLayout.getApertureWidth() > 50) {
                                    JOptionPane.showMessageDialog(MainFrame.getInstance(), "Heliostat aperture width must be between 1 and 50 m.", "Range Error", JOptionPane.ERROR_MESSAGE);
                                } else if (heliostatCircularFieldLayout.getApertureHeight() < 1 || heliostatCircularFieldLayout.getApertureHeight() > 50) {
                                    JOptionPane.showMessageDialog(MainFrame.getInstance(), "Heliostat aperture height must be between 1 and 50 m.", "Range Error", JOptionPane.ERROR_MESSAGE);
                                } else if (heliostatCircularFieldLayout.getBaseHeight() < 0) {
                                    JOptionPane.showMessageDialog(MainFrame.getInstance(), "Base height can't be negative.", "Range Error", JOptionPane.ERROR_MESSAGE);
                                } else {
                                    addCircularHeliostatArrays();
                                    if (choice == options[0]) {
                                        break;
                                    }
                                }
                            }
                        }
                    }
                }
            }

            private void addCircularHeliostatArrays() {
                heliostatCircularFieldLayout.setType(typeComboBox.getSelectedIndex());
                SceneManager.getTaskManager().update(new Callable<Object>() {

                    @Override
                    public Object call() {
                        final int count = f.addCircularHeliostatArrays(heliostatCircularFieldLayout);
                        if (count == 0) {
                            JOptionPane.showMessageDialog(MainFrame.getInstance(), "Heliostat array can't be created. Check your parameters.", "Error", JOptionPane.ERROR_MESSAGE);
                        }
                        return null;
                    }
                });
                updateAfterEdit();
            }
        });
        final JMenuItem miHeliostatRectangularArrays = new JMenuItem("Heliostat Rectangular Layout...");
        layoutMenu.add(miHeliostatRectangularArrays);
        miHeliostatRectangularArrays.addActionListener(new ActionListener() {

            private Foundation f;

            private JComboBox<String> rowAxisComboBox;

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (selectedPart instanceof Foundation) {
                    f = (Foundation) selectedPart;
                    final int n = f.countParts(Mirror.class);
                    if (n > 0 && JOptionPane.showConfirmDialog(MainFrame.getInstance(), "All existing " + n + " heliostats on this foundation 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 JPanel panel = new JPanel(new SpringLayout());
                    panel.add(new JLabel("Row Axis:"));
                    rowAxisComboBox = new JComboBox<String>(new String[] { "North-South", "East-West" });
                    rowAxisComboBox.setSelectedIndex(heliostatRectangularFieldLayout.getRowAxis());
                    panel.add(rowAxisComboBox);
                    panel.add(new JLabel("Aperture Width:"));
                    final JTextField widthField = new JTextField(threeDecimalsFormat.format(heliostatRectangularFieldLayout.getApertureWidth()));
                    panel.add(widthField);
                    panel.add(new JLabel("Aperture Height:"));
                    final JTextField heightField = new JTextField(threeDecimalsFormat.format(heliostatRectangularFieldLayout.getApertureHeight()));
                    panel.add(heightField);
                    panel.add(new JLabel("Row Spacing:"));
                    final JTextField rowSpacingField = new JTextField(threeDecimalsFormat.format(heliostatRectangularFieldLayout.getRowSpacing()));
                    panel.add(rowSpacingField);
                    panel.add(new JLabel("Column Spacing:"));
                    final JTextField columnSpacingField = new JTextField(threeDecimalsFormat.format(heliostatRectangularFieldLayout.getColumnSpacing()));
                    panel.add(columnSpacingField);
                    panel.add(new JLabel("Base Height:"));
                    final JTextField baseHeightField = new JTextField(threeDecimalsFormat.format(heliostatRectangularFieldLayout.getBaseHeight()));
                    panel.add(baseHeightField);
                    SpringUtilities.makeCompactGrid(panel, 6, 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(), "Rectangular Heliostat Array Options");
                    while (true) {
                        dialog.setVisible(true);
                        final Object choice = optionPane.getValue();
                        if (choice == options[1] || choice == null) {
                            break;
                        } else {
                            boolean ok = true;
                            try {
                                heliostatRectangularFieldLayout.setRowSpacing(Double.parseDouble(rowSpacingField.getText()));
                                heliostatRectangularFieldLayout.setColumnSpacing(Double.parseDouble(columnSpacingField.getText()));
                                heliostatRectangularFieldLayout.setApertureWidth(Double.parseDouble(widthField.getText()));
                                heliostatRectangularFieldLayout.setApertureHeight(Double.parseDouble(heightField.getText()));
                                heliostatRectangularFieldLayout.setBaseHeight(Double.parseDouble(baseHeightField.getText()));
                            } catch (final NumberFormatException exception) {
                                JOptionPane.showMessageDialog(MainFrame.getInstance(), "Invalid value!", "Error", JOptionPane.ERROR_MESSAGE);
                                ok = false;
                            }
                            if (ok) {
                                if (heliostatRectangularFieldLayout.getRowSpacing() < 0 || heliostatRectangularFieldLayout.getColumnSpacing() < 0) {
                                    JOptionPane.showMessageDialog(MainFrame.getInstance(), "Heliostat spacing cannot be negative.", "Range Error", JOptionPane.ERROR_MESSAGE);
                                } else if (heliostatRectangularFieldLayout.getApertureWidth() < 1 || heliostatRectangularFieldLayout.getApertureWidth() > 50) {
                                    JOptionPane.showMessageDialog(MainFrame.getInstance(), "Aperture width must be between 1 and 50 m.", "Range Error", JOptionPane.ERROR_MESSAGE);
                                } else if (heliostatRectangularFieldLayout.getApertureHeight() < 1 || heliostatRectangularFieldLayout.getApertureHeight() > 50) {
                                    JOptionPane.showMessageDialog(MainFrame.getInstance(), "Aperture height must be between 1 and 50 m.", "Range Error", JOptionPane.ERROR_MESSAGE);
                                } else if (heliostatRectangularFieldLayout.getBaseHeight() < 0) {
                                    JOptionPane.showMessageDialog(MainFrame.getInstance(), "Base height can't be negative.", "Range Error", JOptionPane.ERROR_MESSAGE);
                                } else {
                                    addRectangularHeliostatArrays();
                                    if (choice == options[0]) {
                                        break;
                                    }
                                }
                            }
                        }
                    }
                }
            }

            private void addRectangularHeliostatArrays() {
                heliostatRectangularFieldLayout.setRowAxis(rowAxisComboBox.getSelectedIndex());
                SceneManager.getTaskManager().update(new Callable<Object>() {

                    @Override
                    public Object call() {
                        final int count = f.addRectangularHeliostatArrays(heliostatRectangularFieldLayout);
                        if (count == 0) {
                            JOptionPane.showMessageDialog(MainFrame.getInstance(), "Heliostat array can't be created. Check your parameters.", "Error", JOptionPane.ERROR_MESSAGE);
                        }
                        return null;
                    }
                });
                updateAfterEdit();
            }
        });
        final JMenuItem miHeliostatFermatSpiralArrays = new JMenuItem("Heliostat Spiral Layout...");
        layoutMenu.add(miHeliostatFermatSpiralArrays);
        miHeliostatFermatSpiralArrays.addActionListener(new ActionListener() {

            private Foundation f;

            private JComboBox<String> typeComboBox;

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (selectedPart instanceof Foundation) {
                    f = (Foundation) selectedPart;
                    final int n = f.countParts(Mirror.class);
                    if (n > 0 && JOptionPane.showConfirmDialog(MainFrame.getInstance(), "All existing " + n + " heliostats on this foundation 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 JPanel panel = new JPanel(new SpringLayout());
                    panel.add(new JLabel("Type:"));
                    typeComboBox = new JComboBox<String>(new String[] { "Fermat Spiral" });
                    typeComboBox.setSelectedIndex(heliostatSpiralFieldLayout.getType());
                    panel.add(typeComboBox);
                    panel.add(new JLabel("Aperture Width:"));
                    final JTextField widthField = new JTextField(threeDecimalsFormat.format(heliostatSpiralFieldLayout.getApertureWidth()));
                    panel.add(widthField);
                    panel.add(new JLabel("Aperture Height:"));
                    final JTextField heightField = new JTextField(threeDecimalsFormat.format(heliostatSpiralFieldLayout.getApertureHeight()));
                    panel.add(heightField);
                    panel.add(new JLabel("Start Turn:"));
                    final JTextField startTurnField = new JTextField(heliostatSpiralFieldLayout.getStartTurn() + "");
                    panel.add(startTurnField);
                    panel.add(new JLabel("Scaling Factor:"));
                    final JTextField scalingFactorField = new JTextField(threeDecimalsFormat.format(heliostatSpiralFieldLayout.getScalingFactor()));
                    panel.add(scalingFactorField);
                    panel.add(new JLabel("Radial Spacing Increase Ratio:"));
                    final JTextField radialSpacingIncrementField = new JTextField(sixDecimalsFormat.format(heliostatSpiralFieldLayout.getRadialSpacingIncrement()));
                    panel.add(radialSpacingIncrementField);
                    panel.add(new JLabel("Start Angle (CCW from East):"));
                    final JTextField startAngleField = new JTextField(threeDecimalsFormat.format(heliostatSpiralFieldLayout.getStartAngle()));
                    panel.add(startAngleField);
                    panel.add(new JLabel("End Angle (CCW from East):"));
                    final JTextField endAngleField = new JTextField(threeDecimalsFormat.format(heliostatSpiralFieldLayout.getEndAngle()));
                    panel.add(endAngleField);
                    panel.add(new JLabel("Axis Road Width:"));
                    final JTextField axisRoadWidthField = new JTextField(threeDecimalsFormat.format(heliostatSpiralFieldLayout.getAxisRoadWidth()));
                    panel.add(axisRoadWidthField);
                    panel.add(new JLabel("Base Height:"));
                    final JTextField baseHeightField = new JTextField(threeDecimalsFormat.format(heliostatSpiralFieldLayout.getBaseHeight()));
                    panel.add(baseHeightField);
                    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(), "Spiral Heliostat Array Options");
                    while (true) {
                        dialog.setVisible(true);
                        final Object choice = optionPane.getValue();
                        if (choice == options[1] || choice == null) {
                            break;
                        } else {
                            boolean ok = true;
                            try {
                                heliostatSpiralFieldLayout.setApertureWidth(Double.parseDouble(widthField.getText()));
                                heliostatSpiralFieldLayout.setApertureHeight(Double.parseDouble(heightField.getText()));
                                heliostatSpiralFieldLayout.setStartTurn(Integer.parseInt(startTurnField.getText()));
                                heliostatSpiralFieldLayout.setScalingFactor(Double.parseDouble(scalingFactorField.getText()));
                                heliostatSpiralFieldLayout.setRadialSpacingIncrement(Double.parseDouble(radialSpacingIncrementField.getText()));
                                heliostatSpiralFieldLayout.setStartAngle(Double.parseDouble(startAngleField.getText()));
                                heliostatSpiralFieldLayout.setEndAngle(Double.parseDouble(endAngleField.getText()));
                                heliostatSpiralFieldLayout.setAxisRoadWidth(Double.parseDouble(axisRoadWidthField.getText()));
                                heliostatSpiralFieldLayout.setBaseHeight(Double.parseDouble(baseHeightField.getText()));
                            } catch (final NumberFormatException exception) {
                                JOptionPane.showMessageDialog(MainFrame.getInstance(), "Invalid value!", "Error", JOptionPane.ERROR_MESSAGE);
                                ok = false;
                            }
                            if (ok) {
                                if (heliostatSpiralFieldLayout.getStartTurn() < 0) {
                                    JOptionPane.showMessageDialog(MainFrame.getInstance(), "Start turn cannot be negative.", "Range Error", JOptionPane.ERROR_MESSAGE);
                                } else if (heliostatSpiralFieldLayout.getScalingFactor() <= 0) {
                                    JOptionPane.showMessageDialog(MainFrame.getInstance(), "Scaling factor must be greater than zero.", "Range Error", JOptionPane.ERROR_MESSAGE);
                                } else if (heliostatSpiralFieldLayout.getRadialSpacingIncrement() < 0) {
                                    JOptionPane.showMessageDialog(MainFrame.getInstance(), "Radial spacing increment ratio cannot be negative.", "Range Error", JOptionPane.ERROR_MESSAGE);
                                } else if (heliostatSpiralFieldLayout.getAxisRoadWidth() < 0) {
                                    JOptionPane.showMessageDialog(MainFrame.getInstance(), "Axis road width cannot be negative.", "Range Error", JOptionPane.ERROR_MESSAGE);
                                } else if (heliostatSpiralFieldLayout.getStartAngle() < 0 || heliostatSpiralFieldLayout.getStartAngle() > 360) {
                                    JOptionPane.showMessageDialog(MainFrame.getInstance(), "Start angle must be between 0 and 360 degrees.", "Range Error", JOptionPane.ERROR_MESSAGE);
                                } else if (heliostatSpiralFieldLayout.getEndAngle() < 0 || heliostatSpiralFieldLayout.getEndAngle() > 360) {
                                    JOptionPane.showMessageDialog(MainFrame.getInstance(), "End angle must be between 0 and 360 degrees.", "Range Error", JOptionPane.ERROR_MESSAGE);
                                } else if (heliostatSpiralFieldLayout.getEndAngle() <= heliostatSpiralFieldLayout.getStartAngle()) {
                                    JOptionPane.showMessageDialog(MainFrame.getInstance(), "End angle must be greater than start angle.", "Range Error", JOptionPane.ERROR_MESSAGE);
                                } else if (heliostatSpiralFieldLayout.getApertureWidth() < 1 || heliostatSpiralFieldLayout.getApertureWidth() > 50) {
                                    JOptionPane.showMessageDialog(MainFrame.getInstance(), "Aperture width must be between 1 and 50 m.", "Range Error", JOptionPane.ERROR_MESSAGE);
                                } else if (heliostatSpiralFieldLayout.getApertureHeight() < 1 || heliostatSpiralFieldLayout.getApertureHeight() > 50) {
                                    JOptionPane.showMessageDialog(MainFrame.getInstance(), "Aperture height must be between 1 and 50 m.", "Range Error", JOptionPane.ERROR_MESSAGE);
                                } else if (heliostatSpiralFieldLayout.getBaseHeight() < 0) {
                                    JOptionPane.showMessageDialog(MainFrame.getInstance(), "Base height can't be negative.", "Range Error", JOptionPane.ERROR_MESSAGE);
                                } else {
                                    addSpiralHeliostatArrays();
                                    if (choice == options[0]) {
                                        break;
                                    }
                                }
                            }
                        }
                    }
                }
            }

            private void addSpiralHeliostatArrays() {
                heliostatSpiralFieldLayout.setType(typeComboBox.getSelectedIndex());
                SceneManager.getTaskManager().update(new Callable<Object>() {

                    @Override
                    public Object call() {
                        final int count = f.addSpiralHeliostatArrays(heliostatSpiralFieldLayout);
                        if (count == 0) {
                            JOptionPane.showMessageDialog(MainFrame.getInstance(), "Heliostat array can't be created. Check your parameters.", "Error", JOptionPane.ERROR_MESSAGE);
                        }
                        return null;
                    }
                });
                updateAfterEdit();
            }
        });
        final JMenuItem miAddUtilityBill = new JMenuItem("Add Utility Bill");
        miAddUtilityBill.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (selectedPart instanceof Foundation) {
                    final Foundation f = (Foundation) selectedPart;
                    UtilityBill b = f.getUtilityBill();
                    if (b == null) {
                        if (JOptionPane.showConfirmDialog(MainFrame.getInstance(), "No utility bill is found for this building. Create one?", "Utility Bill for Building #" + f.getId(), JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE) == JOptionPane.NO_OPTION) {
                            return;
                        }
                        b = new UtilityBill();
                        f.setUtilityBill(b);
                    }
                    new UtilityBillDialog(b).setVisible(true);
                    Scene.getInstance().setEdited(true);
                }
            }
        });
        final JMenuItem miDeleteUtilityBill = new JMenuItem("Delete Utility Bill");
        miDeleteUtilityBill.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (selectedPart instanceof Foundation) {
                    final Foundation f = (Foundation) selectedPart;
                    if (f.getUtilityBill() == null) {
                        JOptionPane.showMessageDialog(MainFrame.getInstance(), "There is no utilitiy bill associated with this building.", "No Utility Bill", JOptionPane.INFORMATION_MESSAGE);
                    } else {
                        if (JOptionPane.showConfirmDialog(MainFrame.getInstance(), "Do you really want to remove the utility bill associated with this building?", "Confirm", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE) == JOptionPane.YES_OPTION) {
                            final DeleteUtilityBillCommand c = new DeleteUtilityBillCommand(f);
                            f.setUtilityBill(null);
                            Scene.getInstance().setEdited(true);
                            SceneManager.getInstance().getUndoManager().addEdit(c);
                        }
                    }
                }
            }
        });
        final JMenuItem miGroupMaster = new JCheckBoxMenuItem("Group Master");
        miGroupMaster.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (selectedPart instanceof Foundation) {
                    SceneManager.getInstance().getUndoManager().addEdit(new SetGroupMasterCommand((Foundation) selectedPart));
                    ((Foundation) selectedPart).setGroupMaster(miGroupMaster.isSelected());
                    Scene.getInstance().setEdited(true);
                }
            }
        });
        final JCheckBoxMenuItem miEnableInset = new JCheckBoxMenuItem("Enable Polygon Inset");
        miEnableInset.addItemListener(new ItemListener() {

            @Override
            public void itemStateChanged(final ItemEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (selectedPart instanceof Foundation) {
                    final Foundation foundation = (Foundation) selectedPart;
                    SceneManager.getInstance().getUndoManager().addEdit(new ShowFoundationInsetCommand(foundation));
                    foundation.getPolygon().setVisible(miEnableInset.isSelected());
                    foundation.draw();
                    Scene.getInstance().setEdited(true);
                }
            }
        });
        final JCheckBoxMenuItem miDisableEditPoints = new JCheckBoxMenuItem("Disable Edit Points");
        miDisableEditPoints.addItemListener(new ItemListener() {

            @Override
            public void itemStateChanged(final ItemEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (selectedPart instanceof Foundation) {
                    ((Foundation) selectedPart).setLockEdit(miDisableEditPoints.isSelected());
                    Scene.getInstance().setEdited(true);
                }
            }
        });
        final JMenu optionsMenu = new JMenu("Options");
        final JMenuItem miChildGridSize = new JMenuItem("Grid Size...");
        miChildGridSize.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (!(selectedPart instanceof Foundation)) {
                    return;
                }
                final Foundation f = (Foundation) selectedPart;
                while (true) {
                    final String newValue = JOptionPane.showInputDialog(MainFrame.getInstance(), "Grid Size (m)", f.getChildGridSize() * Scene.getInstance().getAnnotationScale());
                    if (newValue == null) {
                        break;
                    } else {
                        try {
                            final double val = Double.parseDouble(newValue);
                            if (val < 0.1 || val > 5) {
                                JOptionPane.showMessageDialog(MainFrame.getInstance(), "Grid size must be between 0.1 and 5 m.", "Error", JOptionPane.ERROR_MESSAGE);
                            } else {
                                f.setChildGridSize(val / Scene.getInstance().getAnnotationScale());
                                updateAfterEdit();
                                break;
                            }
                        } catch (final NumberFormatException exception) {
                            exception.printStackTrace();
                            JOptionPane.showMessageDialog(MainFrame.getInstance(), newValue + " is an invalid value!", "Error", JOptionPane.ERROR_MESSAGE);
                        }
                    }
                }
            }
        });
        optionsMenu.add(miChildGridSize);
        final JMenu projectTypeSubMenu = new JMenu("Project Type");
        optionsMenu.add(projectTypeSubMenu);
        final ButtonGroup bgStructureTypes = new ButtonGroup();
        final JRadioButtonMenuItem rbmiTypeAutoDetected = new JRadioButtonMenuItem("Auto Detected");
        rbmiTypeAutoDetected.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (selectedPart instanceof Foundation) {
                    final Foundation foundation = (Foundation) selectedPart;
                    foundation.setProjectType(Foundation.TYPE_AUTO_DETECTED);
                }
            }
        });
        projectTypeSubMenu.add(rbmiTypeAutoDetected);
        bgStructureTypes.add(rbmiTypeAutoDetected);
        final JRadioButtonMenuItem rbmiTypeBuilding = new JRadioButtonMenuItem("Building");
        rbmiTypeBuilding.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (selectedPart instanceof Foundation) {
                    final Foundation foundation = (Foundation) selectedPart;
                    foundation.setProjectType(Foundation.TYPE_BUILDING);
                }
            }
        });
        projectTypeSubMenu.add(rbmiTypeBuilding);
        bgStructureTypes.add(rbmiTypeBuilding);
        final JRadioButtonMenuItem rbmiTypePvStation = new JRadioButtonMenuItem("Photovoltaic Solar Power System");
        rbmiTypePvStation.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (selectedPart instanceof Foundation) {
                    final Foundation foundation = (Foundation) selectedPart;
                    foundation.setProjectType(Foundation.TYPE_PV_PROJECT);
                }
            }
        });
        projectTypeSubMenu.add(rbmiTypePvStation);
        bgStructureTypes.add(rbmiTypePvStation);
        final JRadioButtonMenuItem rbmiTypeCspStation = new JRadioButtonMenuItem("Concentrated Solar Power System");
        rbmiTypeCspStation.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (selectedPart instanceof Foundation) {
                    final Foundation foundation = (Foundation) selectedPart;
                    foundation.setProjectType(Foundation.TYPE_CSP_PROJECT);
                }
            }
        });
        projectTypeSubMenu.add(rbmiTypeCspStation);
        bgStructureTypes.add(rbmiTypeCspStation);
        final JMenuItem miThermostat = new JMenuItem("Thermostat...");
        miThermostat.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (selectedPart instanceof Foundation) {
                    final Foundation foundation = (Foundation) selectedPart;
                    MainPanel.getInstance().getEnergyButton().setSelected(false);
                    new ThermostatDialog(foundation).setVisible(true);
                    TimeSeriesLogger.getInstance().logAdjustThermostatButton();
                    Scene.getInstance().setEdited(true);
                }
            }
        });
        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 Foundation)) {
                    return;
                }
                final Foundation f = (Foundation) selectedPart;
                final boolean hasChildren = !f.getChildren().isEmpty();
                final Vector3 v0 = f.getAbsPoint(0);
                final Vector3 v1 = f.getAbsPoint(1);
                final Vector3 v2 = f.getAbsPoint(2);
                double lx0 = v0.distance(v2) * Scene.getInstance().getAnnotationScale();
                double ly0 = v0.distance(v1) * Scene.getInstance().getAnnotationScale();
                double lz0 = f.getHeight() * Scene.getInstance().getAnnotationScale();
                final JPanel gui = new JPanel(new BorderLayout());
                final String title = "<html>Size of Foundation #" + f.getId() + " (in meters)</html>";
                gui.add(new JLabel(title), BorderLayout.NORTH);
                final JPanel inputPanel = new JPanel(new SpringLayout());
                inputPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
                gui.add(inputPanel, BorderLayout.CENTER);
                JLabel l = new JLabel("Length: ", JLabel.TRAILING);
                inputPanel.add(l);
                final JTextField lxField = new JTextField(threeDecimalsFormat.format(lx0), 5);
                lxField.setEditable(!hasChildren);
                l.setLabelFor(lxField);
                inputPanel.add(lxField);
                l = new JLabel("Width: ", JLabel.TRAILING);
                inputPanel.add(l);
                final JTextField lyField = new JTextField(threeDecimalsFormat.format(ly0), 5);
                lyField.setEditable(!hasChildren);
                l.setLabelFor(lyField);
                inputPanel.add(lyField);
                l = new JLabel("Height: ", JLabel.TRAILING);
                inputPanel.add(l);
                final JTextField lzField = new JTextField(threeDecimalsFormat.format(lz0), 5);
                l.setLabelFor(lzField);
                inputPanel.add(lzField);
                SpringUtilities.makeCompactGrid(inputPanel, 3, 2, 6, 6, 6, 6);
                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(), "Foundation Size");
                while (true) {
                    dialog.setVisible(true);
                    final Object choice = optionPane.getValue();
                    if (choice == options[1] || choice == null) {
                        break;
                    } else {
                        double lx1 = lx0, ly1 = ly0, lz1 = lz0;
                        boolean ok = true;
                        try {
                            lx1 = Double.parseDouble(lxField.getText());
                            ly1 = Double.parseDouble(lyField.getText());
                            lz1 = Double.parseDouble(lzField.getText());
                        } catch (final NumberFormatException exception) {
                            JOptionPane.showMessageDialog(MainFrame.getInstance(), "Invalid input!", "Error", JOptionPane.ERROR_MESSAGE);
                            ok = false;
                        }
                        if (ok) {
                            if (lx1 < 1) {
                                JOptionPane.showMessageDialog(MainFrame.getInstance(), "Length must be greater than one.", "Range Error", JOptionPane.ERROR_MESSAGE);
                            } else if (ly1 < 1) {
                                JOptionPane.showMessageDialog(MainFrame.getInstance(), "Width must be greater than one.", "Range Error", JOptionPane.ERROR_MESSAGE);
                            } else if (lz1 < 0.01) {
                                JOptionPane.showMessageDialog(MainFrame.getInstance(), "Height must be greater than 0.01.", "Range Error", JOptionPane.ERROR_MESSAGE);
                            } else {
                                if (lx1 != lx0 || ly1 != ly0 || lz1 != lz0) {
                                    f.rescale(lx1 / lx0, ly1 / ly0, 1);
                                    f.setHeight(lz1 / Scene.getInstance().getAnnotationScale());
                                    f.draw();
                                    f.drawChildren();
                                    SceneManager.getInstance().refresh();
                                    SceneManager.getInstance().getUndoManager().addEdit(new ChangeFoundationSizeCommand(f, lx0, lx1, ly0, ly1, lz0, lz1));
                                    updateAfterEdit();
                                    lx0 = lx1;
                                    ly0 = ly1;
                                    lz0 = lz1;
                                }
                                if (choice == options[0]) {
                                    break;
                                }
                            }
                        }
                    }
                }
            }
        });
        final JMenuItem miResize = new JMenuItem("Resize Structure Above");
        miResize.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (!(selectedPart instanceof Foundation)) {
                    return;
                }
                final Foundation f = (Foundation) selectedPart;
                if (f.getChildren().isEmpty()) {
                    return;
                }
                for (final HousePart p : Scene.getInstance().getParts()) {
                    if (p instanceof Foundation) {
                        if (p != f) {
                            ((Foundation) p).setResizeHouseMode(false);
                        }
                    }
                }
                f.setResizeHouseMode(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 Foundation) {
                        final Foundation f = (Foundation) selectedPart;
                        final SetFoundationLabelCommand c = new SetFoundationLabelCommand(f);
                        f.clearLabels();
                        f.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 Foundation) {
                    final Foundation f = (Foundation) selectedPart;
                    final SetFoundationLabelCommand c = new SetFoundationLabelCommand(f);
                    f.setLabelCustom(miLabelCustom.isSelected());
                    if (f.getLabelCustom()) {
                        f.setLabelCustomText(JOptionPane.showInputDialog(MainFrame.getInstance(), "Custom Text", f.getLabelCustomText()));
                    }
                    f.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 Foundation) {
                    final Foundation f = (Foundation) selectedPart;
                    final SetFoundationLabelCommand c = new SetFoundationLabelCommand(f);
                    f.setLabelId(miLabelId.isSelected());
                    f.draw();
                    SceneManager.getInstance().getUndoManager().addEdit(c);
                    Scene.getInstance().setEdited(true);
                    SceneManager.getInstance().refresh();
                }
            }
        });
        labelMenu.add(miLabelId);
        final JCheckBoxMenuItem miLabelNumberOfSolarPanels = new JCheckBoxMenuItem("Number of Solar Panels");
        miLabelNumberOfSolarPanels.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (selectedPart instanceof Foundation) {
                    final Foundation f = (Foundation) selectedPart;
                    final SetFoundationLabelCommand c = new SetFoundationLabelCommand(f);
                    f.setLabelNumberOfSolarPanels(miLabelNumberOfSolarPanels.isSelected());
                    f.draw();
                    SceneManager.getInstance().getUndoManager().addEdit(c);
                    Scene.getInstance().setEdited(true);
                    SceneManager.getInstance().refresh();
                }
            }
        });
        labelMenu.add(miLabelNumberOfSolarPanels);
        final JCheckBoxMenuItem miLabelPvEnergy = new JCheckBoxMenuItem("Photovoltaic Output");
        miLabelPvEnergy.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (selectedPart instanceof Foundation) {
                    final Foundation f = (Foundation) selectedPart;
                    final SetFoundationLabelCommand c = new SetFoundationLabelCommand(f);
                    f.setLabelPvEnergy(miLabelPvEnergy.isSelected());
                    f.draw();
                    SceneManager.getInstance().getUndoManager().addEdit(c);
                    Scene.getInstance().setEdited(true);
                    SceneManager.getInstance().refresh();
                }
            }
        });
        labelMenu.add(miLabelPvEnergy);
        final JCheckBoxMenuItem miLabelSolarPotential = new JCheckBoxMenuItem("Solar Potential");
        miLabelSolarPotential.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (selectedPart instanceof Foundation) {
                    final Foundation f = (Foundation) selectedPart;
                    final SetFoundationLabelCommand c = new SetFoundationLabelCommand(f);
                    f.setLabelSolarPotential(miLabelSolarPotential.isSelected());
                    f.draw();
                    SceneManager.getInstance().getUndoManager().addEdit(c);
                    Scene.getInstance().setEdited(true);
                    SceneManager.getInstance().refresh();
                }
            }
        });
        labelMenu.add(miLabelSolarPotential);
        final JCheckBoxMenuItem miLabelBuildingEnergy = new JCheckBoxMenuItem("Building Energy");
        miLabelBuildingEnergy.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (selectedPart instanceof Foundation) {
                    final Foundation f = (Foundation) selectedPart;
                    final SetFoundationLabelCommand c = new SetFoundationLabelCommand(f);
                    f.setLabelBuildingEnergy(miLabelBuildingEnergy.isSelected());
                    f.draw();
                    SceneManager.getInstance().getUndoManager().addEdit(c);
                    Scene.getInstance().setEdited(true);
                    SceneManager.getInstance().refresh();
                }
            }
        });
        labelMenu.add(miLabelBuildingEnergy);
        final JMenu powerTowerLabelMenu = new JMenu("Power Tower");
        labelMenu.add(powerTowerLabelMenu);
        final JCheckBoxMenuItem miLabelNumberOfHeliostats = new JCheckBoxMenuItem("Number of Heliostats");
        miLabelNumberOfHeliostats.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (selectedPart instanceof Foundation) {
                    final Foundation f = (Foundation) selectedPart;
                    final SetFoundationLabelCommand c = new SetFoundationLabelCommand(f);
                    f.setLabelNumberOfMirrors(miLabelNumberOfHeliostats.isSelected());
                    f.draw();
                    SceneManager.getInstance().getUndoManager().addEdit(c);
                    Scene.getInstance().setEdited(true);
                    SceneManager.getInstance().refresh();
                }
            }
        });
        powerTowerLabelMenu.add(miLabelNumberOfHeliostats);
        final JCheckBoxMenuItem miLabelPowerTowerHeight = new JCheckBoxMenuItem("Tower Height");
        miLabelPowerTowerHeight.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (selectedPart instanceof Foundation) {
                    final Foundation f = (Foundation) selectedPart;
                    final SetFoundationLabelCommand c = new SetFoundationLabelCommand(f);
                    f.setLabelPowerTowerHeight(miLabelPowerTowerHeight.isSelected());
                    f.draw();
                    SceneManager.getInstance().getUndoManager().addEdit(c);
                    Scene.getInstance().setEdited(true);
                    SceneManager.getInstance().refresh();
                }
            }
        });
        powerTowerLabelMenu.add(miLabelPowerTowerHeight);
        final JCheckBoxMenuItem miLabelPowerTowerOutput = new JCheckBoxMenuItem("Energy Output");
        miLabelPowerTowerOutput.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (selectedPart instanceof Foundation) {
                    final Foundation f = (Foundation) selectedPart;
                    final SetFoundationLabelCommand c = new SetFoundationLabelCommand(f);
                    f.setLabelPowerTowerOutput(miLabelPowerTowerOutput.isSelected());
                    f.draw();
                    SceneManager.getInstance().getUndoManager().addEdit(c);
                    Scene.getInstance().setEdited(true);
                    SceneManager.getInstance().refresh();
                }
            }
        });
        powerTowerLabelMenu.add(miLabelPowerTowerOutput);
        popupMenuForFoundation = createPopupMenu(false, true, new Runnable() {

            @Override
            public void run() {
                final HousePart p = SceneManager.getInstance().getSelectedPart();
                if (p instanceof Foundation) {
                    final Foundation f = (Foundation) p;
                    if (Scene.getInstance().isStudentMode()) {
                        miDisableEditPoints.setEnabled(false);
                        miThermostat.setEnabled(false);
                    } else {
                        miDisableEditPoints.setEnabled(true);
                        miThermostat.setEnabled(true);
                    }
                    miDeleteUtilityBill.setEnabled(f.getUtilityBill() != null);
                    Util.selectSilently(miGroupMaster, f.isGroupMaster());
                    Util.selectSilently(miDisableEditPoints, f.getLockEdit());
                    Util.selectSilently(miEnableInset, f.getPolygon().isVisible());
                    Util.selectSilently(miLabelNone, !f.isLabelVisible());
                    Util.selectSilently(miLabelCustom, f.getLabelCustom());
                    Util.selectSilently(miLabelId, f.getLabelId());
                    Util.selectSilently(miLabelPowerTowerOutput, f.getLabelPowerTowerOutput());
                    Util.selectSilently(miLabelPowerTowerHeight, f.getLabelPowerTowerHeight());
                    Util.selectSilently(miLabelNumberOfHeliostats, f.getLabelNumberOfMirrors());
                    Util.selectSilently(miLabelSolarPotential, f.getLabelSolarPotential());
                    Util.selectSilently(miLabelPvEnergy, f.getLabelPvEnergy());
                    Util.selectSilently(miLabelNumberOfSolarPanels, f.getLabelNumberOfSolarPanels());
                    Util.selectSilently(miLabelBuildingEnergy, f.getLabelBuildingEnergy());
                    powerTowerLabelMenu.setEnabled(f.hasSolarReceiver());
                    switch(f.getProjectType()) {
                        case Foundation.TYPE_BUILDING:
                            Util.selectSilently(rbmiTypeBuilding, true);
                            break;
                        case Foundation.TYPE_PV_PROJECT:
                            Util.selectSilently(rbmiTypePvStation, true);
                            break;
                        case Foundation.TYPE_CSP_PROJECT:
                            Util.selectSilently(rbmiTypeCspStation, true);
                            break;
                        default:
                            Util.selectSilently(rbmiTypeAutoDetected, true);
                    }
                    miResize.setEnabled(!f.getChildren().isEmpty());
                    for (final HousePart x : Scene.getInstance().getParts()) {
                        if (x instanceof Foundation) {
                            if (x != f) {
                                ((Foundation) x).setResizeHouseMode(false);
                            }
                        }
                    }
                }
                final HousePart copyBuffer = Scene.getInstance().getCopyBuffer();
                final Node copyNode = Scene.getInstance().getCopyNode();
                miPaste.setEnabled(copyBuffer instanceof SolarCollector || copyNode != null);
            }
        });
        popupMenuForFoundation.add(miPaste);
        popupMenuForFoundation.add(miCopy);
        popupMenuForFoundation.addSeparator();
        popupMenuForFoundation.add(miImportCollada);
        popupMenuForFoundation.add(miResize);
        popupMenuForFoundation.add(miSize);
        popupMenuForFoundation.add(miRescale);
        popupMenuForFoundation.add(rotateMenu);
        popupMenuForFoundation.add(clearMenu);
        popupMenuForFoundation.add(layoutMenu);
        popupMenuForFoundation.addSeparator();
        popupMenuForFoundation.add(miDisableEditPoints);
        popupMenuForFoundation.add(miEnableInset);
        popupMenuForFoundation.add(miGroupMaster);
        popupMenuForFoundation.add(optionsMenu);
        popupMenuForFoundation.add(labelMenu);
        popupMenuForFoundation.addSeparator();
        popupMenuForFoundation.add(colorAction);
        // floor insulation only for the first floor, so this U-value is associated with the Foundation class, not the Floor class
        popupMenuForFoundation.add(createInsulationMenuItem(false));
        popupMenuForFoundation.add(createVolumetricHeatCapacityMenuItem());
        popupMenuForFoundation.add(miThermostat);
        popupMenuForFoundation.addSeparator();
        popupMenuForFoundation.add(miAddUtilityBill);
        popupMenuForFoundation.add(miDeleteUtilityBill);
        popupMenuForFoundation.addSeparator();
        final JMenu analysisMenu = new JMenu("Analysis");
        popupMenuForFoundation.add(analysisMenu);
        JMenu subMenu = new JMenu("Buildings");
        analysisMenu.add(subMenu);
        JMenuItem mi = new JMenuItem("Daily Building Energy Analysis...");
        mi.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(final ActionEvent e) {
                if (EnergyPanel.getInstance().adjustCellSize()) {
                    return;
                }
                if (SceneManager.getInstance().getSelectedPart() instanceof Foundation) {
                    final EnergyDailyAnalysis analysis = new EnergyDailyAnalysis();
                    if (SceneManager.getInstance().getSolarHeatMap()) {
                        analysis.updateGraph();
                    }
                    analysis.show("Daily Building Energy");
                }
            }
        });
        subMenu.add(mi);
        mi = new JMenuItem("Annual Building Energy Analysis...");
        mi.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(final ActionEvent e) {
                if (EnergyPanel.getInstance().adjustCellSize()) {
                    return;
                }
                if (SceneManager.getInstance().getSelectedPart() instanceof Foundation) {
                    new EnergyAnnualAnalysis().show("Annual Building Energy");
                }
            }
        });
        subMenu.add(mi);
        subMenu = new JMenu("Solar Panels");
        analysisMenu.add(subMenu);
        mi = new JMenuItem("Daily Solar Panel Yield Analysis...");
        mi.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(final ActionEvent e) {
                if (SceneManager.getInstance().getSelectedPart() instanceof Foundation) {
                    final Foundation f = (Foundation) SceneManager.getInstance().getSelectedPart();
                    if (f.countParts(new Class[] { SolarPanel.class, Rack.class }) <= 0) {
                        JOptionPane.showMessageDialog(MainFrame.getInstance(), "There is no solar panel on this foundation to analyze.", "Error", JOptionPane.ERROR_MESSAGE);
                        return;
                    }
                    if (EnergyPanel.getInstance().adjustCellSize()) {
                        return;
                    }
                    final PvDailyAnalysis a = new PvDailyAnalysis();
                    if (SceneManager.getInstance().getSolarHeatMap()) {
                        a.updateGraph();
                    }
                    a.show();
                }
            }
        });
        subMenu.add(mi);
        mi = new JMenuItem("Annual Solar Panel Yield Analysis...");
        mi.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (selectedPart instanceof Foundation) {
                    final Foundation f = (Foundation) selectedPart;
                    if (f.countParts(new Class[] { SolarPanel.class, Rack.class }) <= 0) {
                        JOptionPane.showMessageDialog(MainFrame.getInstance(), "There is no solar panel on this foundation to analyze.", "Error", JOptionPane.ERROR_MESSAGE);
                        return;
                    }
                    if (EnergyPanel.getInstance().adjustCellSize()) {
                        return;
                    }
                    final PvAnnualAnalysis a = new PvAnnualAnalysis();
                    if (f.getUtilityBill() != null) {
                        a.setUtilityBill(f.getUtilityBill());
                    }
                    a.show();
                }
            }
        });
        subMenu.add(mi);
        subMenu = new JMenu("Heliostats");
        analysisMenu.add(subMenu);
        mi = new JMenuItem("Daily Heliostat Yield Analysis...");
        mi.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(final ActionEvent e) {
                if (SceneManager.getInstance().getSelectedPart() instanceof Foundation) {
                    final Foundation f = (Foundation) SceneManager.getInstance().getSelectedPart();
                    if (f.countParts(Mirror.class) <= 0) {
                        JOptionPane.showMessageDialog(MainFrame.getInstance(), "There is no heliostat on this foundation to analyze.", "Error", JOptionPane.ERROR_MESSAGE);
                        return;
                    }
                    if (EnergyPanel.getInstance().adjustCellSize()) {
                        return;
                    }
                    final HeliostatDailyAnalysis a = new HeliostatDailyAnalysis();
                    if (SceneManager.getInstance().getSolarHeatMap()) {
                        a.updateGraph();
                    }
                    a.show();
                }
            }
        });
        subMenu.add(mi);
        mi = new JMenuItem("Annual Heliostat Yield Analysis...");
        mi.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (selectedPart instanceof Foundation) {
                    final Foundation f = (Foundation) selectedPart;
                    if (f.countParts(Mirror.class) <= 0) {
                        JOptionPane.showMessageDialog(MainFrame.getInstance(), "There is no heliostat on this foundation to analyze.", "Error", JOptionPane.ERROR_MESSAGE);
                        return;
                    }
                    if (EnergyPanel.getInstance().adjustCellSize()) {
                        return;
                    }
                    new HeliostatAnnualAnalysis().show();
                }
            }
        });
        subMenu.add(mi);
        subMenu = new JMenu("Parabolic Troughs");
        analysisMenu.add(subMenu);
        mi = new JMenuItem("Daily Parabolic Trough Yield Analysis...");
        mi.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(final ActionEvent e) {
                if (SceneManager.getInstance().getSelectedPart() instanceof Foundation) {
                    final Foundation f = (Foundation) SceneManager.getInstance().getSelectedPart();
                    if (f.countParts(ParabolicTrough.class) <= 0) {
                        JOptionPane.showMessageDialog(MainFrame.getInstance(), "There is no parabolic trough on this foundation to analyze.", "Error", JOptionPane.ERROR_MESSAGE);
                        return;
                    }
                    if (EnergyPanel.getInstance().adjustCellSize()) {
                        return;
                    }
                    final ParabolicTroughDailyAnalysis a = new ParabolicTroughDailyAnalysis();
                    if (SceneManager.getInstance().getSolarHeatMap()) {
                        a.updateGraph();
                    }
                    a.show();
                }
            }
        });
        subMenu.add(mi);
        mi = new JMenuItem("Annual Parabolic Trough Yield Analysis...");
        mi.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (selectedPart instanceof Foundation) {
                    final Foundation f = (Foundation) selectedPart;
                    if (f.countParts(ParabolicTrough.class) <= 0) {
                        JOptionPane.showMessageDialog(MainFrame.getInstance(), "There is no parabolic trough on this foundation to analyze.", "Error", JOptionPane.ERROR_MESSAGE);
                        return;
                    }
                    if (EnergyPanel.getInstance().adjustCellSize()) {
                        return;
                    }
                    new ParabolicTroughAnnualAnalysis().show();
                }
            }
        });
        subMenu.add(mi);
        subMenu = new JMenu("Parabolic Dishes");
        analysisMenu.add(subMenu);
        mi = new JMenuItem("Daily Parabolic Dish Yield Analysis...");
        mi.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(final ActionEvent e) {
                if (SceneManager.getInstance().getSelectedPart() instanceof Foundation) {
                    final Foundation f = (Foundation) SceneManager.getInstance().getSelectedPart();
                    if (f.countParts(ParabolicDish.class) <= 0) {
                        JOptionPane.showMessageDialog(MainFrame.getInstance(), "There is no parabolic dish on this foundation to analyze.", "Error", JOptionPane.ERROR_MESSAGE);
                        return;
                    }
                    if (EnergyPanel.getInstance().adjustCellSize()) {
                        return;
                    }
                    final ParabolicDishDailyAnalysis a = new ParabolicDishDailyAnalysis();
                    if (SceneManager.getInstance().getSolarHeatMap()) {
                        a.updateGraph();
                    }
                    a.show();
                }
            }
        });
        subMenu.add(mi);
        mi = new JMenuItem("Annual Parabolic Dish Yield Analysis...");
        mi.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (selectedPart instanceof Foundation) {
                    final Foundation f = (Foundation) selectedPart;
                    if (f.countParts(ParabolicDish.class) <= 0) {
                        JOptionPane.showMessageDialog(MainFrame.getInstance(), "There is no parabolic dish on this foundation to analyze.", "Error", JOptionPane.ERROR_MESSAGE);
                        return;
                    }
                    if (EnergyPanel.getInstance().adjustCellSize()) {
                        return;
                    }
                    new ParabolicDishAnnualAnalysis().show();
                }
            }
        });
        subMenu.add(mi);
        subMenu = new JMenu("Linear Fresnel Reflectors");
        analysisMenu.add(subMenu);
        mi = new JMenuItem("Daily Fresnel Reflector Yield Analysis...");
        mi.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(final ActionEvent e) {
                if (SceneManager.getInstance().getSelectedPart() instanceof Foundation) {
                    final Foundation f = (Foundation) SceneManager.getInstance().getSelectedPart();
                    if (f.countParts(FresnelReflector.class) <= 0) {
                        JOptionPane.showMessageDialog(MainFrame.getInstance(), "There is no Fresnel reflector on this foundation to analyze.", "Error", JOptionPane.ERROR_MESSAGE);
                        return;
                    }
                    if (EnergyPanel.getInstance().adjustCellSize()) {
                        return;
                    }
                    final FresnelReflectorDailyAnalysis a = new FresnelReflectorDailyAnalysis();
                    if (SceneManager.getInstance().getSolarHeatMap()) {
                        a.updateGraph();
                    }
                    a.show();
                }
            }
        });
        subMenu.add(mi);
        mi = new JMenuItem("Annual Fresnel Reflector Yield Analysis...");
        mi.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (selectedPart instanceof Foundation) {
                    final Foundation f = (Foundation) selectedPart;
                    if (f.countParts(FresnelReflector.class) <= 0) {
                        JOptionPane.showMessageDialog(MainFrame.getInstance(), "There is no Fresnel reflector on this foundation to analyze.", "Error", JOptionPane.ERROR_MESSAGE);
                        return;
                    }
                    if (EnergyPanel.getInstance().adjustCellSize()) {
                        return;
                    }
                    new FresnelReflectorAnnualAnalysis().show();
                }
            }
        });
        subMenu.add(mi);
    }
    return popupMenuForFoundation;
}
Also used : JPanel(javax.swing.JPanel) UtilityBill(org.concord.energy3d.simulation.UtilityBill) SetGroupMasterCommand(org.concord.energy3d.undo.SetGroupMasterCommand) ActionEvent(java.awt.event.ActionEvent) Node(com.ardor3d.scenegraph.Node) FresnelReflectorAnnualAnalysis(org.concord.energy3d.simulation.FresnelReflectorAnnualAnalysis) PvAnnualAnalysis(org.concord.energy3d.simulation.PvAnnualAnalysis) BorderLayout(java.awt.BorderLayout) FresnelReflectorDailyAnalysis(org.concord.energy3d.simulation.FresnelReflectorDailyAnalysis) ParabolicDishAnnualAnalysis(org.concord.energy3d.simulation.ParabolicDishAnnualAnalysis) Foundation(org.concord.energy3d.model.Foundation) ChangeFoundationSizeCommand(org.concord.energy3d.undo.ChangeFoundationSizeCommand) HousePart(org.concord.energy3d.model.HousePart) HeliostatDailyAnalysis(org.concord.energy3d.simulation.HeliostatDailyAnalysis) ShowFoundationInsetCommand(org.concord.energy3d.undo.ShowFoundationInsetCommand) JRadioButtonMenuItem(javax.swing.JRadioButtonMenuItem) JOptionPane(javax.swing.JOptionPane) JCheckBoxMenuItem(javax.swing.JCheckBoxMenuItem) ActionListener(java.awt.event.ActionListener) SolarPanel(org.concord.energy3d.model.SolarPanel) SpringLayout(javax.swing.SpringLayout) File(java.io.File) Map(java.util.Map) Mirror(org.concord.energy3d.model.Mirror) JDialog(javax.swing.JDialog) EnergyAnnualAnalysis(org.concord.energy3d.simulation.EnergyAnnualAnalysis) ItemEvent(java.awt.event.ItemEvent) HeliostatAnnualAnalysis(org.concord.energy3d.simulation.HeliostatAnnualAnalysis) JTextField(javax.swing.JTextField) Callable(java.util.concurrent.Callable) Rack(org.concord.energy3d.model.Rack) SolarCollector(org.concord.energy3d.model.SolarCollector) DeleteUtilityBillCommand(org.concord.energy3d.undo.DeleteUtilityBillCommand) PvDailyAnalysis(org.concord.energy3d.simulation.PvDailyAnalysis) JMenuItem(javax.swing.JMenuItem) PvModuleSpecs(org.concord.energy3d.simulation.PvModuleSpecs) ParabolicTroughAnnualAnalysis(org.concord.energy3d.simulation.ParabolicTroughAnnualAnalysis) JComboBox(javax.swing.JComboBox) ParabolicTroughDailyAnalysis(org.concord.energy3d.simulation.ParabolicTroughDailyAnalysis) JLabel(javax.swing.JLabel) Vector3(com.ardor3d.math.Vector3) AddNodeCommand(org.concord.energy3d.undo.AddNodeCommand) ParabolicDishDailyAnalysis(org.concord.energy3d.simulation.ParabolicDishDailyAnalysis) ButtonGroup(javax.swing.ButtonGroup) EnergyDailyAnalysis(org.concord.energy3d.simulation.EnergyDailyAnalysis) ItemListener(java.awt.event.ItemListener) SetFoundationLabelCommand(org.concord.energy3d.undo.SetFoundationLabelCommand) JMenu(javax.swing.JMenu)

Aggregations

Foundation (org.concord.energy3d.model.Foundation)5 HousePart (org.concord.energy3d.model.HousePart)5 Rack (org.concord.energy3d.model.Rack)5 SolarPanel (org.concord.energy3d.model.SolarPanel)5 PvDailyAnalysis (org.concord.energy3d.simulation.PvDailyAnalysis)5 ActionEvent (java.awt.event.ActionEvent)4 ActionListener (java.awt.event.ActionListener)4 JMenuItem (javax.swing.JMenuItem)4 PvAnnualAnalysis (org.concord.energy3d.simulation.PvAnnualAnalysis)4 BorderLayout (java.awt.BorderLayout)3 ItemEvent (java.awt.event.ItemEvent)3 ItemListener (java.awt.event.ItemListener)3 Map (java.util.Map)3 Callable (java.util.concurrent.Callable)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