Search in sources :

Example 1 with Foundation

use of org.concord.energy3d.model.Foundation in project energy3d by concord-consortium.

the class PopupMenuForParabolicDish method getPopupMenu.

static JPopupMenu getPopupMenu() {
    if (popupMenuForParabolicDish == null) {
        final JMenuItem miMesh = new JMenuItem("Mesh...");
        miMesh.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 ParabolicDish)) {
                    return;
                }
                final ParabolicDish d = (ParabolicDish) selectedPart;
                final Foundation foundation = d.getTopContainer();
                final String partInfo = d.toString().substring(0, selectedPart.toString().indexOf(')') + 1);
                final JPanel gui = new JPanel(new BorderLayout());
                final JPanel inputPanel = new JPanel(new GridLayout(2, 2, 5, 5));
                gui.add(inputPanel, BorderLayout.CENTER);
                inputPanel.add(new JLabel("Radial Direction: "));
                final JTextField nRadialField = new JTextField("" + d.getNRadialSections());
                inputPanel.add(nRadialField);
                inputPanel.add(new JLabel("Axial Direction: "));
                final JTextField nAxialField = new JTextField("" + d.getNAxialSections());
                inputPanel.add(nAxialField);
                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 Parabolic Dish", true);
                final JRadioButton rb2 = new JRadioButton("All Parabolic Dishes on this Foundation");
                final JRadioButton rb3 = new JRadioButton("All Parabolic Dishes");
                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 mesh for " + partInfo, gui }, JOptionPane.QUESTION_MESSAGE, JOptionPane.YES_NO_CANCEL_OPTION, null, options, options[2]);
                final JDialog dialog = optionPane.createDialog(MainFrame.getInstance(), "Parabolic Dish Mesh");
                while (true) {
                    dialog.setVisible(true);
                    final Object choice = optionPane.getValue();
                    if (choice == options[1] || choice == null) {
                        break;
                    } else {
                        int nRadialSections = 0, nAxialSections = 0;
                        boolean ok = true;
                        try {
                            nRadialSections = Integer.parseInt(nRadialField.getText());
                            nAxialSections = Integer.parseInt(nAxialField.getText());
                        } catch (final NumberFormatException nfe) {
                            JOptionPane.showMessageDialog(MainFrame.getInstance(), "Invalid input!", "Error", JOptionPane.ERROR_MESSAGE);
                            ok = false;
                        }
                        if (ok) {
                            if (nRadialSections < 4) {
                                JOptionPane.showMessageDialog(MainFrame.getInstance(), "Number of radial sections must be at least 4.", "Range Error", JOptionPane.ERROR_MESSAGE);
                            } else if (nAxialSections < 4) {
                                JOptionPane.showMessageDialog(MainFrame.getInstance(), "Number of axial sections mesh must be at least 4.", "Range Error", JOptionPane.ERROR_MESSAGE);
                            } else if (!Util.isPowerOfTwo(nRadialSections) || !Util.isPowerOfTwo(nAxialSections)) {
                                JOptionPane.showMessageDialog(MainFrame.getInstance(), "Number of parabolic dish mesh sections in x or y direction must be power of two.", "Range Error", JOptionPane.ERROR_MESSAGE);
                            } else {
                                if (rb1.isSelected()) {
                                    // final SetPartSizeCommand c = new SetPartSizeCommand(t);
                                    d.setNRadialSections(nRadialSections);
                                    d.setNAxialSections(nAxialSections);
                                    d.draw();
                                    SceneManager.getInstance().refresh();
                                    // SceneManager.getInstance().getUndoManager().addEdit(c);
                                    selectedScopeIndex = 0;
                                } else if (rb2.isSelected()) {
                                    // final SetShapeForParabolicTroughsOnFoundationCommand c = new SetShapeForParabolicTroughsOnFoundationCommand(foundation);
                                    foundation.setSectionsForParabolicDishes(nRadialSections, nAxialSections);
                                    // SceneManager.getInstance().getUndoManager().addEdit(c);
                                    selectedScopeIndex = 1;
                                } else if (rb3.isSelected()) {
                                    // final SetShapeForAllParabolicTroughsCommand c = new SetShapeForAllParabolicTroughsCommand();
                                    Scene.getInstance().setSectionsForAllParabolicDishes(nRadialSections, nAxialSections);
                                    // SceneManager.getInstance().getUndoManager().addEdit(c);
                                    selectedScopeIndex = 2;
                                }
                                updateAfterEdit();
                                if (choice == options[0]) {
                                    break;
                                }
                            }
                        }
                    }
                }
            }
        });
        final JMenuItem miRib = new JMenuItem("Ribs...");
        miRib.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 ParabolicDish)) {
                    return;
                }
                final ParabolicDish d = (ParabolicDish) selectedPart;
                final Foundation foundation = d.getTopContainer();
                final String partInfo = d.toString().substring(0, selectedPart.toString().indexOf(')') + 1);
                final JPanel gui = new JPanel(new BorderLayout());
                final JPanel inputPanel = new JPanel(new GridLayout(2, 2, 5, 5));
                gui.add(inputPanel, BorderLayout.CENTER);
                inputPanel.add(new JLabel("Rib Lines: "));
                final JTextField nribField = new JTextField("" + d.getNumberOfRibs());
                inputPanel.add(nribField);
                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 Parabolic Dish", true);
                final JRadioButton rb2 = new JRadioButton("All Parabolic Dishes on this Foundation");
                final JRadioButton rb3 = new JRadioButton("All Parabolic Dishes");
                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 rib lines for " + partInfo, gui }, JOptionPane.QUESTION_MESSAGE, JOptionPane.YES_NO_CANCEL_OPTION, null, options, options[2]);
                final JDialog dialog = optionPane.createDialog(MainFrame.getInstance(), "Parabolic Dish Ribs");
                while (true) {
                    dialog.setVisible(true);
                    final Object choice = optionPane.getValue();
                    if (choice == options[1] || choice == null) {
                        break;
                    } else {
                        int nrib = 0;
                        boolean ok = true;
                        try {
                            nrib = Integer.parseInt(nribField.getText());
                        } catch (final NumberFormatException nfe) {
                            JOptionPane.showMessageDialog(MainFrame.getInstance(), "Invalid input!", "Error", JOptionPane.ERROR_MESSAGE);
                            ok = false;
                        }
                        if (ok) {
                            if (nrib < 0) {
                                JOptionPane.showMessageDialog(MainFrame.getInstance(), "Number of ribs cannot be negative.", "Range Error", JOptionPane.ERROR_MESSAGE);
                            } else {
                                boolean changed = nrib != d.getNumberOfRibs();
                                if (rb1.isSelected()) {
                                    if (changed) {
                                        final SetParabolicDishRibsCommand c = new SetParabolicDishRibsCommand(d);
                                        d.setNumberOfRibs(nrib);
                                        d.draw();
                                        SceneManager.getInstance().refresh();
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 0;
                                } else if (rb2.isSelected()) {
                                    if (!changed) {
                                        for (final ParabolicDish x : foundation.getParabolicDishes()) {
                                            if (x.getNumberOfRibs() != nrib) {
                                                changed = true;
                                                break;
                                            }
                                        }
                                    }
                                    if (changed) {
                                        final SetRibsForParabolicDishesOnFoundationCommand c = new SetRibsForParabolicDishesOnFoundationCommand(foundation);
                                        foundation.setNumberOfRibsForParabolicDishes(nrib);
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 1;
                                } else if (rb3.isSelected()) {
                                    if (!changed) {
                                        for (final ParabolicDish x : Scene.getInstance().getAllParabolicDishes()) {
                                            if (x.getNumberOfRibs() != nrib) {
                                                changed = true;
                                                break;
                                            }
                                        }
                                    }
                                    if (changed) {
                                        final SetRibsForAllParabolicDishesCommand c = new SetRibsForAllParabolicDishesCommand();
                                        Scene.getInstance().setNumberOfRibsForAllParabolicDishes(nrib);
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 2;
                                }
                                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 ParabolicDish)) {
                    return;
                }
                final boolean disabled = cbmiDisableEditPoint.isSelected();
                final ParabolicDish dish = (ParabolicDish) selectedPart;
                final String partInfo = dish.toString().substring(0, dish.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 Parabolic Dish", true);
                final JRadioButton rb2 = new JRadioButton("All Parabolic Dishes on this Foundation");
                final JRadioButton rb3 = new JRadioButton("All Parabolic Dishes");
                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 parabolic dish 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(dish);
                        dish.setLockEdit(disabled);
                        SceneManager.getInstance().getUndoManager().addEdit(c);
                        selectedScopeIndex = 0;
                    } else if (rb2.isSelected()) {
                        final Foundation foundation = dish.getTopContainer();
                        final LockEditPointsOnFoundationCommand c = new LockEditPointsOnFoundationCommand(foundation, dish.getClass());
                        foundation.setLockEditForClass(disabled, dish.getClass());
                        SceneManager.getInstance().getUndoManager().addEdit(c);
                        selectedScopeIndex = 1;
                    } else if (rb3.isSelected()) {
                        final LockEditPointsForClassCommand c = new LockEditPointsForClassCommand(dish);
                        Scene.getInstance().setLockEditForClass(disabled, dish.getClass());
                        SceneManager.getInstance().getUndoManager().addEdit(c);
                        selectedScopeIndex = 2;
                    }
                    SceneManager.getInstance().refresh();
                    Scene.getInstance().setEdited(true);
                }
            }
        });
        final JCheckBoxMenuItem cbmiDrawSunBeams = new JCheckBoxMenuItem("Draw Sun Beams");
        cbmiDrawSunBeams.addItemListener(new ItemListener() {

            @Override
            public void itemStateChanged(final ItemEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (!(selectedPart instanceof ParabolicDish)) {
                    return;
                }
                final ParabolicDish d = (ParabolicDish) selectedPart;
                final ShowSunBeamCommand c = new ShowSunBeamCommand(d);
                d.setSunBeamVisible(cbmiDrawSunBeams.isSelected());
                d.drawSunBeam();
                d.draw();
                SceneManager.getInstance().refresh();
                SceneManager.getInstance().getUndoManager().addEdit(c);
                Scene.getInstance().setEdited(true);
            }
        });
        final JMenuItem miRimRadius = new JMenuItem("Rim Radius...");
        miRimRadius.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 ParabolicDish)) {
                    return;
                }
                final ParabolicDish d = (ParabolicDish) selectedPart;
                final Foundation foundation = d.getTopContainer();
                final String partInfo = d.toString().substring(0, selectedPart.toString().indexOf(')') + 1);
                final JPanel gui = new JPanel(new BorderLayout());
                final JPanel inputPanel = new JPanel(new GridLayout(1, 2, 5, 5));
                gui.add(inputPanel, BorderLayout.CENTER);
                inputPanel.add(new JLabel("Rim Radius (m): "));
                final JTextField apertureRadiusField = new JTextField(threeDecimalsFormat.format(d.getRimRadius()));
                inputPanel.add(apertureRadiusField);
                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 Parabolic Dish", true);
                final JRadioButton rb2 = new JRadioButton("All Parabolic Dishes on this Foundation");
                final JRadioButton rb3 = new JRadioButton("All Parabolic Dishes");
                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 rim radius for " + partInfo, gui }, JOptionPane.QUESTION_MESSAGE, JOptionPane.YES_NO_CANCEL_OPTION, null, options, options[2]);
                final JDialog dialog = optionPane.createDialog(MainFrame.getInstance(), "Parabolic Dish Rim Radius");
                while (true) {
                    dialog.setVisible(true);
                    final Object choice = optionPane.getValue();
                    if (choice == options[1] || choice == null) {
                        break;
                    } else {
                        double r = 0;
                        boolean ok = true;
                        try {
                            r = Double.parseDouble(apertureRadiusField.getText());
                        } catch (final NumberFormatException x) {
                            JOptionPane.showMessageDialog(MainFrame.getInstance(), "Invalid input!", "Error", JOptionPane.ERROR_MESSAGE);
                            ok = false;
                        }
                        if (ok) {
                            if (r < 1 || r > 10) {
                                JOptionPane.showMessageDialog(MainFrame.getInstance(), "Parabolic dish rim radius must be between 1 and 10 m.", "Range Error", JOptionPane.ERROR_MESSAGE);
                            } else {
                                boolean changed = Math.abs(r - d.getRimRadius()) > 0.000001;
                                if (rb1.isSelected()) {
                                    if (changed) {
                                        final SetPartSizeCommand c = new SetPartSizeCommand(d);
                                        d.setRimRadius(r);
                                        d.draw();
                                        SceneManager.getInstance().refresh();
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 0;
                                } else if (rb2.isSelected()) {
                                    if (!changed) {
                                        for (final ParabolicDish x : foundation.getParabolicDishes()) {
                                            if (Math.abs(r - x.getRimRadius()) > 0.000001) {
                                                changed = true;
                                                break;
                                            }
                                        }
                                    }
                                    if (changed) {
                                        final SetRimRadiusForParabolicDishesOnFoundationCommand c = new SetRimRadiusForParabolicDishesOnFoundationCommand(foundation);
                                        foundation.setRimRadiusForParabolicDishes(r);
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 1;
                                } else if (rb3.isSelected()) {
                                    if (!changed) {
                                        for (final ParabolicDish x : Scene.getInstance().getAllParabolicDishes()) {
                                            if (Math.abs(r - x.getRimRadius()) > 0.000001) {
                                                changed = true;
                                                break;
                                            }
                                        }
                                    }
                                    if (changed) {
                                        final SetRimRadiusForAllParabolicDishesCommand c = new SetRimRadiusForAllParabolicDishesCommand();
                                        Scene.getInstance().setRimRadiusForAllParabolicDishes(r);
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 2;
                                }
                                if (changed) {
                                    updateAfterEdit();
                                }
                                if (choice == options[0]) {
                                    break;
                                }
                            }
                        }
                    }
                }
            }
        });
        final JMenuItem miFocalLength = new JMenuItem("Focal Length...");
        miFocalLength.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 ParabolicDish)) {
                    return;
                }
                final ParabolicDish d = (ParabolicDish) selectedPart;
                final Foundation foundation = d.getTopContainer();
                final String partInfo = d.toString().substring(0, selectedPart.toString().indexOf(')') + 1);
                final JPanel gui = new JPanel(new BorderLayout());
                final JPanel inputPanel = new JPanel(new GridLayout(1, 2, 5, 5));
                gui.add(inputPanel, BorderLayout.CENTER);
                inputPanel.add(new JLabel("Focal Length (m): "));
                final JTextField focalLengthField = new JTextField(threeDecimalsFormat.format(d.getFocalLength()));
                inputPanel.add(focalLengthField);
                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 Parabolic Dish", true);
                final JRadioButton rb2 = new JRadioButton("All Parabolic Dishes on this Foundation");
                final JRadioButton rb3 = new JRadioButton("All Parabolic Dishes");
                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 focal length for " + partInfo, gui }, JOptionPane.QUESTION_MESSAGE, JOptionPane.YES_NO_CANCEL_OPTION, null, options, options[2]);
                final JDialog dialog = optionPane.createDialog(MainFrame.getInstance(), "Focal Length");
                while (true) {
                    dialog.setVisible(true);
                    final Object choice = optionPane.getValue();
                    if (choice == options[1] || choice == null) {
                        break;
                    } else {
                        double fl = 0;
                        boolean ok = true;
                        try {
                            fl = Double.parseDouble(focalLengthField.getText());
                        } catch (final NumberFormatException nfe) {
                            JOptionPane.showMessageDialog(MainFrame.getInstance(), "Invalid input!", "Error", JOptionPane.ERROR_MESSAGE);
                            ok = false;
                        }
                        if (ok) {
                            if (fl < 0.5 || fl > 10) {
                                JOptionPane.showMessageDialog(MainFrame.getInstance(), "Focal length must be between 0.5 and 10 m.", "Range Error", JOptionPane.ERROR_MESSAGE);
                            } else {
                                boolean changed = Math.abs(fl - d.getFocalLength()) > 0.000001;
                                if (rb1.isSelected()) {
                                    if (changed) {
                                        final SetParabolicDishFocalLengthCommand c = new SetParabolicDishFocalLengthCommand(d);
                                        d.setFocalLength(fl);
                                        d.draw();
                                        SceneManager.getInstance().refresh();
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 0;
                                } else if (rb2.isSelected()) {
                                    if (!changed) {
                                        for (final ParabolicDish x : foundation.getParabolicDishes()) {
                                            if (Math.abs(fl - x.getFocalLength()) > 0.000001) {
                                                changed = true;
                                                break;
                                            }
                                        }
                                    }
                                    if (changed) {
                                        final SetFocalLengthForParabolicDishesOnFoundationCommand c = new SetFocalLengthForParabolicDishesOnFoundationCommand(foundation);
                                        foundation.setFocalLengthForParabolicDishes(fl);
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 1;
                                } else if (rb3.isSelected()) {
                                    if (!changed) {
                                        for (final ParabolicDish x : Scene.getInstance().getAllParabolicDishes()) {
                                            if (Math.abs(fl - x.getFocalLength()) > 0.000001) {
                                                changed = true;
                                                break;
                                            }
                                        }
                                    }
                                    if (changed) {
                                        final SetFocalLengthForAllParabolicDishesCommand c = new SetFocalLengthForAllParabolicDishesCommand();
                                        Scene.getInstance().setFocalLengthForAllParabolicDishes(fl);
                                        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 ParabolicDish)) {
                    return;
                }
                final String partInfo = selectedPart.toString().substring(0, selectedPart.toString().indexOf(')') + 1);
                final ParabolicDish d = (ParabolicDish) selectedPart;
                final Foundation foundation = d.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 Parabolic Dish", true);
                final JRadioButton rb2 = new JRadioButton("All Parabolic Dishes on this Foundation");
                final JRadioButton rb3 = new JRadioButton("All Parabolic Dishes");
                panel.add(rb1);
                panel.add(rb2);
                panel.add(rb3);
                final ButtonGroup bg = new ButtonGroup();
                bg.add(rb1);
                bg.add(rb2);
                bg.add(rb3);
                switch(selectedScopeIndex) {
                    case 0:
                        rb1.setSelected(true);
                        break;
                    case 1:
                        rb2.setSelected(true);
                        break;
                    case 2:
                        rb3.setSelected(true);
                        break;
                }
                final JTextField inputField = new JTextField(EnergyPanel.TWO_DECIMALS.format(d.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(), "Parabolic Dish 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 - d.getBaseHeight()) > 0.000001;
                            if (rb1.isSelected()) {
                                if (changed) {
                                    final ChangeBaseHeightCommand c = new ChangeBaseHeightCommand(d);
                                    d.setBaseHeight(val);
                                    d.draw();
                                    SceneManager.getInstance().refresh();
                                    SceneManager.getInstance().getUndoManager().addEdit(c);
                                }
                                selectedScopeIndex = 0;
                            } else if (rb2.isSelected()) {
                                if (!changed) {
                                    for (final ParabolicDish x : foundation.getParabolicDishes()) {
                                        if (Math.abs(val - x.getBaseHeight()) > 0.000001) {
                                            changed = true;
                                            break;
                                        }
                                    }
                                }
                                if (changed) {
                                    final ChangeFoundationSolarCollectorBaseHeightCommand c = new ChangeFoundationSolarCollectorBaseHeightCommand(foundation, d.getClass());
                                    foundation.setBaseHeightForParabolicDishes(val);
                                    SceneManager.getInstance().getUndoManager().addEdit(c);
                                }
                                selectedScopeIndex = 1;
                            } else if (rb3.isSelected()) {
                                if (!changed) {
                                    for (final ParabolicDish x : Scene.getInstance().getAllParabolicDishes()) {
                                        if (Math.abs(val - x.getBaseHeight()) > 0.000001) {
                                            changed = true;
                                            break;
                                        }
                                    }
                                }
                                if (changed) {
                                    final ChangeBaseHeightForAllSolarCollectorsCommand c = new ChangeBaseHeightForAllSolarCollectorsCommand(d.getClass());
                                    Scene.getInstance().setBaseHeightForAllParabolicDishes(val);
                                    SceneManager.getInstance().getUndoManager().addEdit(c);
                                }
                                selectedScopeIndex = 2;
                            }
                            if (changed) {
                                updateAfterEdit();
                            }
                            if (choice == options[0]) {
                                break;
                            }
                        }
                    }
                }
            }
        });
        final JMenuItem miStructureType = new JMenuItem("Structure Type...");
        miStructureType.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 ParabolicDish)) {
                    return;
                }
                final String partInfo = selectedPart.toString().substring(0, selectedPart.toString().indexOf(')') + 1);
                final ParabolicDish d = (ParabolicDish) selectedPart;
                final Foundation foundation = d.getTopContainer();
                final String title = "<html>Structure Type 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 Parabolic Dish", true);
                final JRadioButton rb2 = new JRadioButton("All Parabolic Dishes on this Foundation");
                final JRadioButton rb3 = new JRadioButton("All Parabolic Dishes");
                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 JComboBox<String> comboBox = new JComboBox<String>(new String[] { "Central Pole", "Tripod" });
                comboBox.setSelectedIndex(d.getStructureType());
                gui.add(comboBox, 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(), "Structure Type");
                while (true) {
                    dialog.setVisible(true);
                    final Object choice = optionPane.getValue();
                    if (choice == options[1] || choice == null) {
                        break;
                    } else {
                        final int structureType = comboBox.getSelectedIndex();
                        boolean changed = structureType != d.getStructureType();
                        if (rb1.isSelected()) {
                            if (changed) {
                                final SetParabolicDishStructureTypeCommand c = new SetParabolicDishStructureTypeCommand(d);
                                d.setStructureType(structureType);
                                d.draw();
                                SceneManager.getInstance().refresh();
                                SceneManager.getInstance().getUndoManager().addEdit(c);
                            }
                            selectedScopeIndex = 0;
                        } else if (rb2.isSelected()) {
                            if (!changed) {
                                for (final ParabolicDish x : foundation.getParabolicDishes()) {
                                    if (structureType != x.getStructureType()) {
                                        changed = true;
                                        break;
                                    }
                                }
                            }
                            if (changed) {
                                final ChangeFoundationParabolicDishStructureTypeCommand c = new ChangeFoundationParabolicDishStructureTypeCommand(foundation);
                                foundation.setStructureTypeForParabolicDishes(structureType);
                                SceneManager.getInstance().getUndoManager().addEdit(c);
                            }
                            selectedScopeIndex = 1;
                        } else if (rb3.isSelected()) {
                            if (!changed) {
                                for (final ParabolicDish x : Scene.getInstance().getAllParabolicDishes()) {
                                    if (structureType != x.getStructureType()) {
                                        changed = true;
                                        break;
                                    }
                                }
                            }
                            if (changed) {
                                final ChangeStructureTypeForAllParabolicDishesCommand c = new ChangeStructureTypeForAllParabolicDishesCommand();
                                Scene.getInstance().setStructureTypeForAllParabolicDishes(structureType);
                                SceneManager.getInstance().getUndoManager().addEdit(c);
                            }
                            selectedScopeIndex = 2;
                        }
                        if (changed) {
                            updateAfterEdit();
                        }
                        if (choice == options[0]) {
                            break;
                        }
                    }
                }
            }
        });
        final JMenu labelMenu = new JMenu("Label");
        final JCheckBoxMenuItem miLabelNone = new JCheckBoxMenuItem("None", true);
        miLabelNone.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(final ActionEvent e) {
                if (miLabelNone.isSelected()) {
                    final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                    if (selectedPart instanceof ParabolicDish) {
                        final ParabolicDish d = (ParabolicDish) selectedPart;
                        final SetParabolicDishLabelCommand c = new SetParabolicDishLabelCommand(d);
                        d.clearLabels();
                        d.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 ParabolicDish) {
                    final ParabolicDish d = (ParabolicDish) selectedPart;
                    final SetParabolicDishLabelCommand c = new SetParabolicDishLabelCommand(d);
                    d.setLabelCustom(miLabelCustom.isSelected());
                    if (d.getLabelCustom()) {
                        d.setLabelCustomText(JOptionPane.showInputDialog(MainFrame.getInstance(), "Custom Text", d.getLabelCustomText()));
                    }
                    d.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 ParabolicDish) {
                    final ParabolicDish d = (ParabolicDish) selectedPart;
                    final SetParabolicDishLabelCommand c = new SetParabolicDishLabelCommand(d);
                    d.setLabelId(miLabelId.isSelected());
                    d.draw();
                    SceneManager.getInstance().getUndoManager().addEdit(c);
                    Scene.getInstance().setEdited(true);
                    SceneManager.getInstance().refresh();
                }
            }
        });
        labelMenu.add(miLabelId);
        final JCheckBoxMenuItem miLabelEnergyOutput = new JCheckBoxMenuItem("Energy Output");
        miLabelEnergyOutput.addActionListener(new ActionListener() {

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

            @Override
            public void run() {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (!(selectedPart instanceof ParabolicDish)) {
                    return;
                }
                final ParabolicDish d = (ParabolicDish) selectedPart;
                Util.selectSilently(miLabelNone, !d.isLabelVisible());
                Util.selectSilently(miLabelCustom, d.getLabelCustom());
                Util.selectSilently(miLabelId, d.getLabelId());
                Util.selectSilently(miLabelEnergyOutput, d.getLabelEnergyOutput());
                Util.selectSilently(cbmiDrawSunBeams, d.isSunBeamVisible());
                Util.selectSilently(cbmiDisableEditPoint, d.getLockEdit());
            }
        });
        final JMenuItem miReflectance = new JMenuItem("Mirror Reflectance...");
        miReflectance.addActionListener(new ActionListener() {

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

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (!(selectedPart instanceof ParabolicDish)) {
                    return;
                }
                final String partInfo = selectedPart.toString().substring(0, selectedPart.toString().indexOf(')') + 1);
                final ParabolicDish d = (ParabolicDish) selectedPart;
                final String title = "<html>Reflectance (%) of " + partInfo + "</html>";
                final String footnote = "<html><hr><font size=2>Reflectance can be affected by pollen and dust.<hr></html>";
                final JPanel gui = new JPanel(new BorderLayout());
                final JPanel panel = new JPanel();
                gui.add(panel, BorderLayout.CENTER);
                panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
                panel.setBorder(BorderFactory.createTitledBorder("Apply to:"));
                final JRadioButton rb1 = new JRadioButton("Only this Parabolic Dish", true);
                final JRadioButton rb2 = new JRadioButton("All Parabolic Dishes on this Foundation");
                final JRadioButton rb3 = new JRadioButton("All Parabolic Dishes");
                panel.add(rb1);
                panel.add(rb2);
                panel.add(rb3);
                final ButtonGroup bg = new ButtonGroup();
                bg.add(rb1);
                bg.add(rb2);
                bg.add(rb3);
                switch(selectedScopeIndex) {
                    case 0:
                        rb1.setSelected(true);
                        break;
                    case 1:
                        rb2.setSelected(true);
                        break;
                    case 2:
                        rb3.setSelected(true);
                        break;
                }
                final JTextField inputField = new JTextField(EnergyPanel.TWO_DECIMALS.format(d.getReflectance() * 100));
                gui.add(inputField, BorderLayout.SOUTH);
                final Object[] options = new Object[] { "OK", "Cancel", "Apply" };
                final JOptionPane optionPane = new JOptionPane(new Object[] { title, footnote, gui }, JOptionPane.QUESTION_MESSAGE, JOptionPane.YES_NO_CANCEL_OPTION, null, options, options[2]);
                final JDialog dialog = optionPane.createDialog(MainFrame.getInstance(), "Parabolic Dish Mirror Reflectance");
                while (true) {
                    inputField.selectAll();
                    inputField.requestFocusInWindow();
                    dialog.setVisible(true);
                    final Object choice = optionPane.getValue();
                    if (choice == options[1] || choice == null) {
                        break;
                    } else {
                        double val = 0;
                        boolean ok = true;
                        try {
                            val = Double.parseDouble(inputField.getText());
                        } catch (final NumberFormatException exception) {
                            JOptionPane.showMessageDialog(MainFrame.getInstance(), inputField.getText() + " is an invalid value!", "Error", JOptionPane.ERROR_MESSAGE);
                            ok = false;
                        }
                        if (ok) {
                            if (val < 50 || val > 99) {
                                JOptionPane.showMessageDialog(MainFrame.getInstance(), "Parabolic dish reflectance must be between 50% and 99%.", "Range Error", JOptionPane.ERROR_MESSAGE);
                            } else {
                                boolean changed = Math.abs(val * 0.01 - d.getReflectance()) > 0.000001;
                                if (rb1.isSelected()) {
                                    if (changed) {
                                        final ChangeSolarReflectorReflectanceCommand c = new ChangeSolarReflectorReflectanceCommand(d);
                                        d.setReflectance(val * 0.01);
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 0;
                                } else if (rb2.isSelected()) {
                                    final Foundation foundation = d.getTopContainer();
                                    if (!changed) {
                                        for (final ParabolicDish x : foundation.getParabolicDishes()) {
                                            if (Math.abs(val * 0.01 - x.getReflectance()) > 0.000001) {
                                                changed = true;
                                                break;
                                            }
                                        }
                                    }
                                    if (changed) {
                                        final ChangeFoundationSolarReflectorReflectanceCommand c = new ChangeFoundationSolarReflectorReflectanceCommand(foundation, d.getClass());
                                        foundation.setReflectanceForSolarReflectors(val * 0.01, d.getClass());
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 1;
                                } else if (rb3.isSelected()) {
                                    if (!changed) {
                                        for (final ParabolicDish x : Scene.getInstance().getAllParabolicDishes()) {
                                            if (Math.abs(val * 0.01 - x.getReflectance()) > 0.000001) {
                                                changed = true;
                                                break;
                                            }
                                        }
                                    }
                                    if (changed) {
                                        final ChangeReflectanceForAllSolarReflectorsCommand c = new ChangeReflectanceForAllSolarReflectorsCommand(d.getClass());
                                        Scene.getInstance().setReflectanceForAllSolarReflectors(val * 0.01, d.getClass());
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 2;
                                }
                                if (changed) {
                                    updateAfterEdit();
                                }
                                if (choice == options[0]) {
                                    break;
                                }
                            }
                        }
                    }
                }
            }
        });
        final JMenuItem miAbsorptance = new JMenuItem("Receiver Absorptance...");
        miAbsorptance.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 ParabolicDish)) {
                    return;
                }
                final String partInfo = selectedPart.toString().substring(0, selectedPart.toString().indexOf(')') + 1);
                final ParabolicDish d = (ParabolicDish) selectedPart;
                final String title = "<html>Absorptance (%) of " + partInfo + "</html>";
                final String footnote = "<html><hr><font size=2><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 Parabolic Dish", true);
                final JRadioButton rb2 = new JRadioButton("All Parabolic Dishes on this Foundation");
                final JRadioButton rb3 = new JRadioButton("All Parabolic Dishes");
                panel.add(rb1);
                panel.add(rb2);
                panel.add(rb3);
                final ButtonGroup bg = new ButtonGroup();
                bg.add(rb1);
                bg.add(rb2);
                bg.add(rb3);
                switch(selectedScopeIndex) {
                    case 0:
                        rb1.setSelected(true);
                        break;
                    case 1:
                        rb2.setSelected(true);
                        break;
                    case 2:
                        rb3.setSelected(true);
                        break;
                }
                final JTextField inputField = new JTextField(EnergyPanel.TWO_DECIMALS.format(d.getAbsorptance() * 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(), "Parabolic Dish Receiver Absorptance");
                while (true) {
                    inputField.selectAll();
                    inputField.requestFocusInWindow();
                    dialog.setVisible(true);
                    final Object choice = optionPane.getValue();
                    if (choice == options[1] || choice == null) {
                        break;
                    } else {
                        double val = 0;
                        boolean ok = true;
                        try {
                            val = Double.parseDouble(inputField.getText());
                        } catch (final NumberFormatException exception) {
                            JOptionPane.showMessageDialog(MainFrame.getInstance(), inputField.getText() + " is an invalid value!", "Error", JOptionPane.ERROR_MESSAGE);
                            ok = false;
                        }
                        if (ok) {
                            if (val < 50 || val > 99) {
                                JOptionPane.showMessageDialog(MainFrame.getInstance(), "Parabolic dish absorptance must be between 50% and 99%.", "Range Error", JOptionPane.ERROR_MESSAGE);
                            } else {
                                boolean changed = Math.abs(val * 0.01 - d.getAbsorptance()) > 0.000001;
                                if (rb1.isSelected()) {
                                    if (changed) {
                                        final ChangeSolarReflectorAbsorptanceCommand c = new ChangeSolarReflectorAbsorptanceCommand(d);
                                        d.setAbsorptance(val * 0.01);
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 0;
                                } else if (rb2.isSelected()) {
                                    final Foundation foundation = d.getTopContainer();
                                    if (!changed) {
                                        for (final ParabolicDish x : foundation.getParabolicDishes()) {
                                            if (Math.abs(val * 0.01 - x.getAbsorptance()) > 0.000001) {
                                                changed = true;
                                                break;
                                            }
                                        }
                                    }
                                    if (changed) {
                                        final ChangeFoundationSolarReflectorAbsorptanceCommand c = new ChangeFoundationSolarReflectorAbsorptanceCommand(foundation, d.getClass());
                                        foundation.setAbsorptanceForSolarReflectors(val * 0.01, d.getClass());
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 1;
                                } else if (rb3.isSelected()) {
                                    if (!changed) {
                                        for (final ParabolicDish x : Scene.getInstance().getAllParabolicDishes()) {
                                            if (Math.abs(val * 0.01 - x.getAbsorptance()) > 0.000001) {
                                                changed = true;
                                                break;
                                            }
                                        }
                                    }
                                    if (changed) {
                                        final ChangeAbsorptanceForAllSolarReflectorsCommand c = new ChangeAbsorptanceForAllSolarReflectorsCommand(d.getClass());
                                        Scene.getInstance().setAbsorptanceForAllSolarReflectors(val * 0.01, d.getClass());
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 2;
                                }
                                if (changed) {
                                    updateAfterEdit();
                                }
                                if (choice == options[0]) {
                                    break;
                                }
                            }
                        }
                    }
                }
            }
        });
        final JMenuItem miOpticalEfficiency = new JMenuItem("Optical Efficiency...");
        miOpticalEfficiency.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 ParabolicDish)) {
                    return;
                }
                final String partInfo = selectedPart.toString().substring(0, selectedPart.toString().indexOf(')') + 1);
                final ParabolicDish d = (ParabolicDish) selectedPart;
                final String title = "<html>Opitical efficiency (%) of " + partInfo + "</html>";
                final String footnote = "<html><hr><font size=2>For example, the percentage of the effective area for reflection after deducting<br>the area of facet gaps, module frames, absorber shadow, etc.<hr></html>";
                final JPanel gui = new JPanel(new BorderLayout());
                final JPanel panel = new JPanel();
                gui.add(panel, BorderLayout.CENTER);
                panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
                panel.setBorder(BorderFactory.createTitledBorder("Apply to:"));
                final JRadioButton rb1 = new JRadioButton("Only this Parabolic Dish", true);
                final JRadioButton rb2 = new JRadioButton("All Parabolic Dishes on this Foundation");
                final JRadioButton rb3 = new JRadioButton("All Parabolic Dishes");
                panel.add(rb1);
                panel.add(rb2);
                panel.add(rb3);
                final ButtonGroup bg = new ButtonGroup();
                bg.add(rb1);
                bg.add(rb2);
                bg.add(rb3);
                switch(selectedScopeIndex) {
                    case 0:
                        rb1.setSelected(true);
                        break;
                    case 1:
                        rb2.setSelected(true);
                        break;
                    case 2:
                        rb3.setSelected(true);
                        break;
                }
                final JTextField inputField = new JTextField(EnergyPanel.TWO_DECIMALS.format(d.getOpticalEfficiency() * 100));
                gui.add(inputField, BorderLayout.SOUTH);
                final Object[] options = new Object[] { "OK", "Cancel", "Apply" };
                final JOptionPane optionPane = new JOptionPane(new Object[] { title, footnote, gui }, JOptionPane.QUESTION_MESSAGE, JOptionPane.YES_NO_CANCEL_OPTION, null, options, options[2]);
                final JDialog dialog = optionPane.createDialog(MainFrame.getInstance(), "Parabolic Dish Optical 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 < 50 || val > 100) {
                                JOptionPane.showMessageDialog(MainFrame.getInstance(), "Parabolic dish optical efficiency must be between 50% and 100%.", "Range Error", JOptionPane.ERROR_MESSAGE);
                            } else {
                                boolean changed = Math.abs(val * 0.01 - d.getOpticalEfficiency()) > 0.000001;
                                if (rb1.isSelected()) {
                                    if (changed) {
                                        final ChangeSolarReflectorOpticalEfficiencyCommand c = new ChangeSolarReflectorOpticalEfficiencyCommand(d);
                                        d.setOpticalEfficiency(val * 0.01);
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 0;
                                } else if (rb2.isSelected()) {
                                    final Foundation foundation = d.getTopContainer();
                                    if (!changed) {
                                        for (final ParabolicDish x : foundation.getParabolicDishes()) {
                                            if (Math.abs(val * 0.01 - x.getOpticalEfficiency()) > 0.000001) {
                                                changed = true;
                                                break;
                                            }
                                        }
                                    }
                                    if (changed) {
                                        final ChangeFoundationSolarReflectorOpticalEfficiencyCommand c = new ChangeFoundationSolarReflectorOpticalEfficiencyCommand(foundation, d.getClass());
                                        foundation.setOpticalEfficiencyForSolarReflectors(val * 0.01, d.getClass());
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 1;
                                } else if (rb3.isSelected()) {
                                    if (!changed) {
                                        for (final ParabolicDish x : Scene.getInstance().getAllParabolicDishes()) {
                                            if (Math.abs(val * 0.01 - x.getOpticalEfficiency()) > 0.000001) {
                                                changed = true;
                                                break;
                                            }
                                        }
                                    }
                                    if (changed) {
                                        final ChangeOpticalEfficiencyForAllSolarReflectorsCommand c = new ChangeOpticalEfficiencyForAllSolarReflectorsCommand(d.getClass());
                                        Scene.getInstance().setOpticalEfficiencyForAllSolarReflectors(val * 0.01, d.getClass());
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 2;
                                }
                                if (changed) {
                                    updateAfterEdit();
                                }
                                if (choice == options[0]) {
                                    break;
                                }
                            }
                        }
                    }
                }
            }
        });
        final JMenuItem miThermalEfficiency = new JMenuItem("Thermal Efficiency...");
        miThermalEfficiency.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 ParabolicDish)) {
                    return;
                }
                final String partInfo = selectedPart.toString().substring(0, selectedPart.toString().indexOf(')') + 1);
                final ParabolicDish d = (ParabolicDish) selectedPart;
                final String title = "<html>Thermal efficiency (%) of " + partInfo + "</html>";
                final String footnote = "<html><hr><font size=2><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 Parabolic Dish", true);
                final JRadioButton rb2 = new JRadioButton("All Parabolic Dishes on this Foundation");
                final JRadioButton rb3 = new JRadioButton("All Parabolic Dishes");
                panel.add(rb1);
                panel.add(rb2);
                panel.add(rb3);
                final ButtonGroup bg = new ButtonGroup();
                bg.add(rb1);
                bg.add(rb2);
                bg.add(rb3);
                switch(selectedScopeIndex) {
                    case 0:
                        rb1.setSelected(true);
                        break;
                    case 1:
                        rb2.setSelected(true);
                        break;
                    case 2:
                        rb3.setSelected(true);
                        break;
                }
                final JTextField inputField = new JTextField(EnergyPanel.TWO_DECIMALS.format(d.getThermalEfficiency() * 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(), "Parabolic Dish Thermal Efficiency");
                while (true) {
                    inputField.selectAll();
                    inputField.requestFocusInWindow();
                    dialog.setVisible(true);
                    final Object choice = optionPane.getValue();
                    if (choice == options[1] || choice == null) {
                        break;
                    } else {
                        double val = 0;
                        boolean ok = true;
                        try {
                            val = Double.parseDouble(inputField.getText());
                        } catch (final NumberFormatException exception) {
                            JOptionPane.showMessageDialog(MainFrame.getInstance(), inputField.getText() + " is an invalid value!", "Error", JOptionPane.ERROR_MESSAGE);
                            ok = false;
                        }
                        if (ok) {
                            if (val < 5 || val > 80) {
                                JOptionPane.showMessageDialog(MainFrame.getInstance(), "Parabolic dish thermal efficiency must be between 5% and 80%.", "Range Error", JOptionPane.ERROR_MESSAGE);
                            } else {
                                boolean changed = Math.abs(val * 0.01 - d.getThermalEfficiency()) > 0.000001;
                                if (rb1.isSelected()) {
                                    if (changed) {
                                        final ChangeSolarReflectorThermalEfficiencyCommand c = new ChangeSolarReflectorThermalEfficiencyCommand(d);
                                        d.setThermalEfficiency(val * 0.01);
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 0;
                                } else if (rb2.isSelected()) {
                                    final Foundation foundation = d.getTopContainer();
                                    if (!changed) {
                                        for (final ParabolicDish x : foundation.getParabolicDishes()) {
                                            if (Math.abs(val * 0.01 - x.getThermalEfficiency()) > 0.000001) {
                                                changed = true;
                                                break;
                                            }
                                        }
                                    }
                                    if (changed) {
                                        final ChangeFoundationSolarReflectorThermalEfficiencyCommand c = new ChangeFoundationSolarReflectorThermalEfficiencyCommand(foundation, d.getClass());
                                        foundation.setThermalEfficiencyForSolarReflectors(val * 0.01, d.getClass());
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 1;
                                } else if (rb3.isSelected()) {
                                    if (!changed) {
                                        for (final ParabolicDish x : Scene.getInstance().getAllParabolicDishes()) {
                                            if (Math.abs(val * 0.01 - x.getThermalEfficiency()) > 0.000001) {
                                                changed = true;
                                                break;
                                            }
                                        }
                                    }
                                    if (changed) {
                                        final ChangeThermalEfficiencyForAllSolarReflectorsCommand c = new ChangeThermalEfficiencyForAllSolarReflectorsCommand(d.getClass());
                                        Scene.getInstance().setThermalEfficiencyForAllSolarReflectors(val * 0.01, d.getClass());
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 2;
                                }
                                if (changed) {
                                    updateAfterEdit();
                                }
                                if (choice == options[0]) {
                                    break;
                                }
                            }
                        }
                    }
                }
            }
        });
        popupMenuForParabolicDish.addSeparator();
        popupMenuForParabolicDish.add(cbmiDisableEditPoint);
        popupMenuForParabolicDish.add(cbmiDrawSunBeams);
        popupMenuForParabolicDish.add(labelMenu);
        popupMenuForParabolicDish.addSeparator();
        popupMenuForParabolicDish.add(miRimRadius);
        popupMenuForParabolicDish.add(miFocalLength);
        popupMenuForParabolicDish.add(miBaseHeight);
        popupMenuForParabolicDish.add(miStructureType);
        popupMenuForParabolicDish.addSeparator();
        popupMenuForParabolicDish.add(miReflectance);
        popupMenuForParabolicDish.add(miAbsorptance);
        popupMenuForParabolicDish.add(miOpticalEfficiency);
        popupMenuForParabolicDish.add(miThermalEfficiency);
        popupMenuForParabolicDish.addSeparator();
        popupMenuForParabolicDish.add(miMesh);
        popupMenuForParabolicDish.add(miRib);
        popupMenuForParabolicDish.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 ParabolicDish) {
                    new ParabolicDishDailyAnalysis().show();
                }
            }
        });
        popupMenuForParabolicDish.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 ParabolicDish) {
                    new ParabolicDishAnnualAnalysis().show();
                }
            }
        });
        popupMenuForParabolicDish.add(mi);
    }
    return popupMenuForParabolicDish;
}
Also used : JPanel(javax.swing.JPanel) ActionEvent(java.awt.event.ActionEvent) SetPartSizeCommand(org.concord.energy3d.undo.SetPartSizeCommand) ChangeSolarReflectorOpticalEfficiencyCommand(org.concord.energy3d.undo.ChangeSolarReflectorOpticalEfficiencyCommand) SetRibsForParabolicDishesOnFoundationCommand(org.concord.energy3d.undo.SetRibsForParabolicDishesOnFoundationCommand) ChangeBaseHeightForAllSolarCollectorsCommand(org.concord.energy3d.undo.ChangeBaseHeightForAllSolarCollectorsCommand) SetFocalLengthForParabolicDishesOnFoundationCommand(org.concord.energy3d.undo.SetFocalLengthForParabolicDishesOnFoundationCommand) BorderLayout(java.awt.BorderLayout) ChangeAbsorptanceForAllSolarReflectorsCommand(org.concord.energy3d.undo.ChangeAbsorptanceForAllSolarReflectorsCommand) ParabolicDishAnnualAnalysis(org.concord.energy3d.simulation.ParabolicDishAnnualAnalysis) Foundation(org.concord.energy3d.model.Foundation) LockEditPointsOnFoundationCommand(org.concord.energy3d.undo.LockEditPointsOnFoundationCommand) ChangeFoundationSolarReflectorThermalEfficiencyCommand(org.concord.energy3d.undo.ChangeFoundationSolarReflectorThermalEfficiencyCommand) SetParabolicDishStructureTypeCommand(org.concord.energy3d.undo.SetParabolicDishStructureTypeCommand) ChangeReflectanceForAllSolarReflectorsCommand(org.concord.energy3d.undo.ChangeReflectanceForAllSolarReflectorsCommand) HousePart(org.concord.energy3d.model.HousePart) JOptionPane(javax.swing.JOptionPane) JCheckBoxMenuItem(javax.swing.JCheckBoxMenuItem) ChangeSolarReflectorAbsorptanceCommand(org.concord.energy3d.undo.ChangeSolarReflectorAbsorptanceCommand) ChangeFoundationParabolicDishStructureTypeCommand(org.concord.energy3d.undo.ChangeFoundationParabolicDishStructureTypeCommand) ActionListener(java.awt.event.ActionListener) ChangeFoundationSolarCollectorBaseHeightCommand(org.concord.energy3d.undo.ChangeFoundationSolarCollectorBaseHeightCommand) JDialog(javax.swing.JDialog) SetRimRadiusForAllParabolicDishesCommand(org.concord.energy3d.undo.SetRimRadiusForAllParabolicDishesCommand) ChangeStructureTypeForAllParabolicDishesCommand(org.concord.energy3d.undo.ChangeStructureTypeForAllParabolicDishesCommand) ItemEvent(java.awt.event.ItemEvent) JRadioButton(javax.swing.JRadioButton) ChangeSolarReflectorReflectanceCommand(org.concord.energy3d.undo.ChangeSolarReflectorReflectanceCommand) BoxLayout(javax.swing.BoxLayout) JTextField(javax.swing.JTextField) ChangeFoundationSolarReflectorAbsorptanceCommand(org.concord.energy3d.undo.ChangeFoundationSolarReflectorAbsorptanceCommand) GridLayout(java.awt.GridLayout) SetParabolicDishRibsCommand(org.concord.energy3d.undo.SetParabolicDishRibsCommand) JMenuItem(javax.swing.JMenuItem) SetRimRadiusForParabolicDishesOnFoundationCommand(org.concord.energy3d.undo.SetRimRadiusForParabolicDishesOnFoundationCommand) LockEditPointsCommand(org.concord.energy3d.undo.LockEditPointsCommand) JComboBox(javax.swing.JComboBox) JLabel(javax.swing.JLabel) ShowSunBeamCommand(org.concord.energy3d.undo.ShowSunBeamCommand) ChangeBaseHeightCommand(org.concord.energy3d.undo.ChangeBaseHeightCommand) ChangeOpticalEfficiencyForAllSolarReflectorsCommand(org.concord.energy3d.undo.ChangeOpticalEfficiencyForAllSolarReflectorsCommand) SetParabolicDishLabelCommand(org.concord.energy3d.undo.SetParabolicDishLabelCommand) ParabolicDish(org.concord.energy3d.model.ParabolicDish) LockEditPointsForClassCommand(org.concord.energy3d.undo.LockEditPointsForClassCommand) ChangeFoundationSolarReflectorReflectanceCommand(org.concord.energy3d.undo.ChangeFoundationSolarReflectorReflectanceCommand) ChangeSolarReflectorThermalEfficiencyCommand(org.concord.energy3d.undo.ChangeSolarReflectorThermalEfficiencyCommand) ChangeFoundationSolarReflectorOpticalEfficiencyCommand(org.concord.energy3d.undo.ChangeFoundationSolarReflectorOpticalEfficiencyCommand) ParabolicDishDailyAnalysis(org.concord.energy3d.simulation.ParabolicDishDailyAnalysis) SetRibsForAllParabolicDishesCommand(org.concord.energy3d.undo.SetRibsForAllParabolicDishesCommand) ButtonGroup(javax.swing.ButtonGroup) ChangeThermalEfficiencyForAllSolarReflectorsCommand(org.concord.energy3d.undo.ChangeThermalEfficiencyForAllSolarReflectorsCommand) SetFocalLengthForAllParabolicDishesCommand(org.concord.energy3d.undo.SetFocalLengthForAllParabolicDishesCommand) ItemListener(java.awt.event.ItemListener) SetParabolicDishFocalLengthCommand(org.concord.energy3d.undo.SetParabolicDishFocalLengthCommand) JMenu(javax.swing.JMenu)

Example 2 with Foundation

use of org.concord.energy3d.model.Foundation in project energy3d by concord-consortium.

the class PopupMenuForParabolicTrough method getPopupMenu.

static JPopupMenu getPopupMenu() {
    if (popupMenuForParabolicTrough == null) {
        final JMenuItem miMesh = new JMenuItem("Mesh...");
        miMesh.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 ParabolicTrough)) {
                    return;
                }
                final ParabolicTrough t = (ParabolicTrough) selectedPart;
                final Foundation foundation = t.getTopContainer();
                final String partInfo = t.toString().substring(0, selectedPart.toString().indexOf(')') + 1);
                final JPanel gui = new JPanel(new BorderLayout());
                final JPanel inputPanel = new JPanel(new GridLayout(2, 2, 5, 5));
                gui.add(inputPanel, BorderLayout.CENTER);
                inputPanel.add(new JLabel("Parabolic cross-section: "));
                final JTextField nParabolaField = new JTextField("" + t.getNSectionParabola());
                inputPanel.add(nParabolaField);
                inputPanel.add(new JLabel("Axial direction: "));
                final JTextField nAxisField = new JTextField("" + t.getNSectionAxis());
                inputPanel.add(nAxisField);
                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 Parabolic Trough", true);
                final JRadioButton rb2 = new JRadioButton("All Parabolic Troughs on this Foundation");
                final JRadioButton rb3 = new JRadioButton("All Parabolic Troughs");
                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 mesh for " + partInfo, gui }, JOptionPane.QUESTION_MESSAGE, JOptionPane.YES_NO_CANCEL_OPTION, null, options, options[2]);
                final JDialog dialog = optionPane.createDialog(MainFrame.getInstance(), "Parabolic Trough Mesh");
                while (true) {
                    dialog.setVisible(true);
                    final Object choice = optionPane.getValue();
                    if (choice == options[1] || choice == null) {
                        break;
                    } else {
                        int nSectionParabola = 0, nSectionAxis = 0;
                        boolean ok = true;
                        try {
                            nSectionParabola = Integer.parseInt(nParabolaField.getText());
                            nSectionAxis = Integer.parseInt(nAxisField.getText());
                        } catch (final NumberFormatException nfe) {
                            JOptionPane.showMessageDialog(MainFrame.getInstance(), "Invalid input!", "Error", JOptionPane.ERROR_MESSAGE);
                            ok = false;
                        }
                        if (ok) {
                            if (nSectionParabola < 4) {
                                JOptionPane.showMessageDialog(MainFrame.getInstance(), "Parabolic sections must be at least 4.", "Range Error", JOptionPane.ERROR_MESSAGE);
                            } else if (nSectionAxis < 4) {
                                JOptionPane.showMessageDialog(MainFrame.getInstance(), "Axis mesh must be at least 4.", "Range Error", JOptionPane.ERROR_MESSAGE);
                            } else if (!Util.isPowerOfTwo(nSectionParabola) || !Util.isPowerOfTwo(nSectionAxis)) {
                                JOptionPane.showMessageDialog(MainFrame.getInstance(), "Number of parabolic trough mesh sections in x or y direction must be power of two.", "Range Error", JOptionPane.ERROR_MESSAGE);
                            } else {
                                if (rb1.isSelected()) {
                                    // final SetPartSizeCommand c = new SetPartSizeCommand(t);
                                    t.setNSectionParabola(nSectionParabola);
                                    t.setNSectionAxis(nSectionAxis);
                                    t.draw();
                                    SceneManager.getInstance().refresh();
                                    // SceneManager.getInstance().getUndoManager().addEdit(c);
                                    selectedScopeIndex = 0;
                                } else if (rb2.isSelected()) {
                                    // final SetShapeForParabolicTroughsOnFoundationCommand c = new SetShapeForParabolicTroughsOnFoundationCommand(foundation);
                                    foundation.setSectionsForParabolicTroughs(nSectionParabola, nSectionAxis);
                                    // SceneManager.getInstance().getUndoManager().addEdit(c);
                                    selectedScopeIndex = 1;
                                } else if (rb3.isSelected()) {
                                    // final SetShapeForAllParabolicTroughsCommand c = new SetShapeForAllParabolicTroughsCommand();
                                    Scene.getInstance().setSectionsForAllParabolicTroughs(nSectionParabola, nSectionAxis);
                                    // SceneManager.getInstance().getUndoManager().addEdit(c);
                                    selectedScopeIndex = 2;
                                }
                                updateAfterEdit();
                                if (choice == options[0]) {
                                    break;
                                }
                            }
                        }
                    }
                }
            }
        });
        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 ParabolicTrough)) {
                    return;
                }
                final boolean disabled = cbmiDisableEditPoints.isSelected();
                final ParabolicTrough trough = (ParabolicTrough) selectedPart;
                final String partInfo = trough.toString().substring(0, trough.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 Parabolic Trough", true);
                final JRadioButton rb2 = new JRadioButton("All Parabolic Troughs on this Foundation");
                final JRadioButton rb3 = new JRadioButton("All Parabolic Troughs");
                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 parabolic trough 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(trough);
                        trough.setLockEdit(disabled);
                        SceneManager.getInstance().getUndoManager().addEdit(c);
                        selectedScopeIndex = 0;
                    } else if (rb2.isSelected()) {
                        final Foundation foundation = trough.getTopContainer();
                        final LockEditPointsOnFoundationCommand c = new LockEditPointsOnFoundationCommand(foundation, trough.getClass());
                        foundation.setLockEditForClass(disabled, trough.getClass());
                        SceneManager.getInstance().getUndoManager().addEdit(c);
                        selectedScopeIndex = 1;
                    } else if (rb3.isSelected()) {
                        final LockEditPointsForClassCommand c = new LockEditPointsForClassCommand(trough);
                        Scene.getInstance().setLockEditForClass(disabled, trough.getClass());
                        SceneManager.getInstance().getUndoManager().addEdit(c);
                        selectedScopeIndex = 2;
                    }
                    SceneManager.getInstance().refresh();
                    Scene.getInstance().setEdited(true);
                }
            }
        });
        final JCheckBoxMenuItem cbmiDrawSunBeams = new JCheckBoxMenuItem("Draw Sun Beams");
        cbmiDrawSunBeams.addItemListener(new ItemListener() {

            @Override
            public void itemStateChanged(final ItemEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (!(selectedPart instanceof ParabolicTrough)) {
                    return;
                }
                final ParabolicTrough t = (ParabolicTrough) selectedPart;
                final ShowSunBeamCommand c = new ShowSunBeamCommand(t);
                t.setSunBeamVisible(cbmiDrawSunBeams.isSelected());
                t.drawSunBeam();
                t.draw();
                SceneManager.getInstance().refresh();
                SceneManager.getInstance().getUndoManager().addEdit(c);
                Scene.getInstance().setEdited(true);
            }
        });
        final JMenuItem miTroughLength = new JMenuItem("Trough Length...");
        miTroughLength.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 ParabolicTrough)) {
                    return;
                }
                final ParabolicTrough t = (ParabolicTrough) selectedPart;
                final Foundation foundation = t.getTopContainer();
                final String partInfo = t.toString().substring(0, selectedPart.toString().indexOf(')') + 1);
                final JPanel gui = new JPanel(new BorderLayout());
                final JPanel inputPanel = new JPanel(new GridLayout(1, 2, 5, 5));
                gui.add(inputPanel, BorderLayout.CENTER);
                inputPanel.add(new JLabel("Trough Length (m): "));
                final JTextField lengthField = new JTextField(threeDecimalsFormat.format(t.getTroughLength()));
                inputPanel.add(lengthField);
                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 Parabolic Trough", true);
                final JRadioButton rb2 = new JRadioButton("All Parabolic Troughs on this Foundation");
                final JRadioButton rb3 = new JRadioButton("All Parabolic Troughs");
                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 assembly length for " + partInfo, gui }, JOptionPane.QUESTION_MESSAGE, JOptionPane.YES_NO_CANCEL_OPTION, null, options, options[2]);
                final JDialog dialog = optionPane.createDialog(MainFrame.getInstance(), "Parabolic Trough Assembly Length");
                while (true) {
                    dialog.setVisible(true);
                    final Object choice = optionPane.getValue();
                    if (choice == options[1] || choice == null) {
                        break;
                    } else {
                        double l = 0;
                        boolean ok = true;
                        try {
                            l = Double.parseDouble(lengthField.getText());
                        } catch (final NumberFormatException x) {
                            JOptionPane.showMessageDialog(MainFrame.getInstance(), "Invalid input!", "Error", JOptionPane.ERROR_MESSAGE);
                            ok = false;
                        }
                        if (ok) {
                            if (l < 1 || l > 1000) {
                                JOptionPane.showMessageDialog(MainFrame.getInstance(), "Parabolic trough length must be between 1 and 1000 m.", "Range Error", JOptionPane.ERROR_MESSAGE);
                            } else {
                                boolean changed = Math.abs(l - t.getTroughLength()) > 0.000001;
                                if (rb1.isSelected()) {
                                    if (changed) {
                                        final SetPartSizeCommand c = new SetPartSizeCommand(t);
                                        t.setTroughLength(l);
                                        t.ensureFullModules(false);
                                        t.draw();
                                        SceneManager.getInstance().refresh();
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 0;
                                } else if (rb2.isSelected()) {
                                    if (!changed) {
                                        for (final ParabolicTrough x : foundation.getParabolicTroughs()) {
                                            if (Math.abs(l - x.getTroughLength()) > 0.000001) {
                                                changed = true;
                                                break;
                                            }
                                        }
                                    }
                                    if (changed) {
                                        final SetShapeForParabolicTroughsOnFoundationCommand c = new SetShapeForParabolicTroughsOnFoundationCommand(foundation);
                                        foundation.setLengthForParabolicTroughs(l);
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 1;
                                } else if (rb3.isSelected()) {
                                    if (!changed) {
                                        for (final ParabolicTrough x : Scene.getInstance().getAllParabolicTroughs()) {
                                            if (Math.abs(l - x.getTroughLength()) > 0.000001) {
                                                changed = true;
                                                break;
                                            }
                                        }
                                    }
                                    if (changed) {
                                        final SetShapeForAllParabolicTroughsCommand c = new SetShapeForAllParabolicTroughsCommand();
                                        Scene.getInstance().setLengthForAllParabolicTroughs(l);
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 2;
                                }
                                if (changed) {
                                    updateAfterEdit();
                                }
                                if (choice == options[0]) {
                                    break;
                                }
                            }
                        }
                    }
                }
            }
        });
        final JMenuItem miApertureWidth = new JMenuItem("Aperture Width...");
        miApertureWidth.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 ParabolicTrough)) {
                    return;
                }
                final ParabolicTrough t = (ParabolicTrough) selectedPart;
                final Foundation foundation = t.getTopContainer();
                final String partInfo = t.toString().substring(0, selectedPart.toString().indexOf(')') + 1);
                final JPanel gui = new JPanel(new BorderLayout());
                final JPanel inputPanel = new JPanel(new GridLayout(1, 2, 5, 5));
                gui.add(inputPanel, BorderLayout.CENTER);
                inputPanel.add(new JLabel("Aperture Width (m): "));
                final JTextField widthField = new JTextField(threeDecimalsFormat.format(t.getApertureWidth()));
                inputPanel.add(widthField);
                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 Parabolic Trough", true);
                final JRadioButton rb2 = new JRadioButton("All Parabolic Troughs on this Foundation");
                final JRadioButton rb3 = new JRadioButton("All Parabolic Troughs");
                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 aperture width for " + partInfo, gui }, JOptionPane.QUESTION_MESSAGE, JOptionPane.YES_NO_CANCEL_OPTION, null, options, options[2]);
                final JDialog dialog = optionPane.createDialog(MainFrame.getInstance(), "Parabolic Trough Aperture Width");
                while (true) {
                    dialog.setVisible(true);
                    final Object choice = optionPane.getValue();
                    if (choice == options[1] || choice == null) {
                        break;
                    } else {
                        double w = 0;
                        boolean ok = true;
                        try {
                            w = Double.parseDouble(widthField.getText());
                        } catch (final NumberFormatException x) {
                            JOptionPane.showMessageDialog(MainFrame.getInstance(), "Invalid input!", "Error", JOptionPane.ERROR_MESSAGE);
                            ok = false;
                        }
                        if (ok) {
                            if (w < 1 || w > 20) {
                                JOptionPane.showMessageDialog(MainFrame.getInstance(), "Parabolic trough aperture width must be between 1 and 20 m.", "Range Error", JOptionPane.ERROR_MESSAGE);
                            } else {
                                boolean changed = Math.abs(w - t.getApertureWidth()) > 0.000001;
                                if (rb1.isSelected()) {
                                    if (changed) {
                                        final SetPartSizeCommand c = new SetPartSizeCommand(t);
                                        t.setApertureWidth(w);
                                        t.ensureFullModules(false);
                                        t.draw();
                                        SceneManager.getInstance().refresh();
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 0;
                                } else if (rb2.isSelected()) {
                                    if (!changed) {
                                        for (final ParabolicTrough x : foundation.getParabolicTroughs()) {
                                            if (Math.abs(w - x.getApertureWidth()) > 0.000001) {
                                                changed = true;
                                                break;
                                            }
                                        }
                                    }
                                    if (changed) {
                                        final SetShapeForParabolicTroughsOnFoundationCommand c = new SetShapeForParabolicTroughsOnFoundationCommand(foundation);
                                        foundation.setApertureForParabolicTroughs(w);
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 1;
                                } else if (rb3.isSelected()) {
                                    if (!changed) {
                                        for (final ParabolicTrough x : Scene.getInstance().getAllParabolicTroughs()) {
                                            if (Math.abs(w - x.getApertureWidth()) > 0.000001) {
                                                changed = true;
                                                break;
                                            }
                                        }
                                    }
                                    if (changed) {
                                        final SetShapeForAllParabolicTroughsCommand c = new SetShapeForAllParabolicTroughsCommand();
                                        Scene.getInstance().setApertureForAllParabolicTroughs(w);
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 2;
                                }
                                if (changed) {
                                    updateAfterEdit();
                                }
                                if (choice == options[0]) {
                                    break;
                                }
                            }
                        }
                    }
                }
            }
        });
        final JMenuItem miModuleLength = new JMenuItem("Module Length...");
        miModuleLength.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 ParabolicTrough)) {
                    return;
                }
                final ParabolicTrough t = (ParabolicTrough) selectedPart;
                final Foundation foundation = t.getTopContainer();
                final String partInfo = t.toString().substring(0, selectedPart.toString().indexOf(')') + 1);
                final JPanel gui = new JPanel(new BorderLayout());
                final JPanel inputPanel = new JPanel(new GridLayout(1, 2, 5, 5));
                gui.add(inputPanel, BorderLayout.CENTER);
                inputPanel.add(new JLabel("Module Length (m): "));
                final JTextField moduleLengthField = new JTextField(threeDecimalsFormat.format(t.getModuleLength()));
                inputPanel.add(moduleLengthField);
                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 Parabolic Trough", true);
                final JRadioButton rb2 = new JRadioButton("All Parabolic Troughs on this Foundation");
                final JRadioButton rb3 = new JRadioButton("All Parabolic Troughs");
                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 module length for " + partInfo, gui }, JOptionPane.QUESTION_MESSAGE, JOptionPane.YES_NO_CANCEL_OPTION, null, options, options[2]);
                final JDialog dialog = optionPane.createDialog(MainFrame.getInstance(), "Parabolic Trough Module Length");
                while (true) {
                    dialog.setVisible(true);
                    final Object choice = optionPane.getValue();
                    if (choice == options[1] || choice == null) {
                        break;
                    } else {
                        double u = 0;
                        boolean ok = true;
                        try {
                            u = Double.parseDouble(moduleLengthField.getText());
                        } catch (final NumberFormatException x) {
                            JOptionPane.showMessageDialog(MainFrame.getInstance(), "Invalid input!", "Error", JOptionPane.ERROR_MESSAGE);
                            ok = false;
                        }
                        if (ok) {
                            if (u < 1 || u > 100) {
                                JOptionPane.showMessageDialog(MainFrame.getInstance(), "Solar collector module length must be between 1 and 100 m.", "Range Error", JOptionPane.ERROR_MESSAGE);
                            } else {
                                boolean changed = Math.abs(u - t.getModuleLength()) > 0.000001;
                                if (rb1.isSelected()) {
                                    if (changed) {
                                        final SetPartSizeCommand c = new SetPartSizeCommand(t);
                                        t.setModuleLength(u);
                                        t.ensureFullModules(false);
                                        t.draw();
                                        SceneManager.getInstance().refresh();
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 0;
                                } else if (rb2.isSelected()) {
                                    if (!changed) {
                                        for (final ParabolicTrough x : foundation.getParabolicTroughs()) {
                                            if (Math.abs(u - x.getModuleLength()) > 0.000001) {
                                                changed = true;
                                                break;
                                            }
                                        }
                                    }
                                    if (changed) {
                                        final SetShapeForParabolicTroughsOnFoundationCommand c = new SetShapeForParabolicTroughsOnFoundationCommand(foundation);
                                        foundation.setModuleLengthForParabolicTroughs(u);
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 1;
                                } else if (rb3.isSelected()) {
                                    if (!changed) {
                                        for (final ParabolicTrough x : Scene.getInstance().getAllParabolicTroughs()) {
                                            if (Math.abs(u - x.getModuleLength()) > 0.000001) {
                                                changed = true;
                                                break;
                                            }
                                        }
                                    }
                                    if (changed) {
                                        final SetShapeForAllParabolicTroughsCommand c = new SetShapeForAllParabolicTroughsCommand();
                                        Scene.getInstance().setModuleLengthForAllParabolicTroughs(u);
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 2;
                                }
                                if (changed) {
                                    updateAfterEdit();
                                }
                                if (choice == options[0]) {
                                    break;
                                }
                            }
                        }
                    }
                }
            }
        });
        final JMenuItem miFocalLength = new JMenuItem("Focal Length...");
        miFocalLength.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 ParabolicTrough)) {
                    return;
                }
                final ParabolicTrough t = (ParabolicTrough) selectedPart;
                final Foundation foundation = t.getTopContainer();
                final String partInfo = t.toString().substring(0, selectedPart.toString().indexOf(')') + 1);
                final JPanel gui = new JPanel(new BorderLayout());
                final JPanel inputPanel = new JPanel(new GridLayout(1, 2, 5, 5));
                gui.add(inputPanel, BorderLayout.CENTER);
                inputPanel.add(new JLabel("Focal Length (m): "));
                final JTextField focalLengthField = new JTextField(threeDecimalsFormat.format(t.getSemilatusRectum() / 2));
                inputPanel.add(focalLengthField);
                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 Parabolic Trough", true);
                final JRadioButton rb2 = new JRadioButton("All Parabolic Troughs on this Foundation");
                final JRadioButton rb3 = new JRadioButton("All Parabolic Troughs");
                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 shape for " + partInfo, gui }, JOptionPane.QUESTION_MESSAGE, JOptionPane.YES_NO_CANCEL_OPTION, null, options, options[2]);
                final JDialog dialog = optionPane.createDialog(MainFrame.getInstance(), "Parabola Shape");
                while (true) {
                    dialog.setVisible(true);
                    final Object choice = optionPane.getValue();
                    if (choice == options[1] || choice == null) {
                        break;
                    } else {
                        double f = 0;
                        boolean ok = true;
                        try {
                            f = Double.parseDouble(focalLengthField.getText());
                        } catch (final NumberFormatException nfe) {
                            JOptionPane.showMessageDialog(MainFrame.getInstance(), "Invalid input!", "Error", JOptionPane.ERROR_MESSAGE);
                            ok = false;
                        }
                        if (ok) {
                            if (f < 0.5 || f > 5) {
                                JOptionPane.showMessageDialog(MainFrame.getInstance(), "Focal length must be between 0.5 and 5 m.", "Range Error", JOptionPane.ERROR_MESSAGE);
                            } else {
                                boolean changed = Math.abs(2 * f - t.getSemilatusRectum()) > 0.000001;
                                if (rb1.isSelected()) {
                                    if (changed) {
                                        final SetParabolicTroughSemilatusRectumCommand c = new SetParabolicTroughSemilatusRectumCommand(t);
                                        // semilatus rectum p = 2f
                                        t.setSemilatusRectum(2 * f);
                                        t.draw();
                                        SceneManager.getInstance().refresh();
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 0;
                                } else if (rb2.isSelected()) {
                                    if (!changed) {
                                        for (final ParabolicTrough x : foundation.getParabolicTroughs()) {
                                            if (Math.abs(2 * f - x.getSemilatusRectum()) > 0.000001) {
                                                changed = true;
                                                break;
                                            }
                                        }
                                    }
                                    if (changed) {
                                        final SetShapeForParabolicTroughsOnFoundationCommand c = new SetShapeForParabolicTroughsOnFoundationCommand(foundation);
                                        foundation.setSemilatusRectumForParabolicTroughs(2 * f);
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 1;
                                } else if (rb3.isSelected()) {
                                    if (!changed) {
                                        for (final ParabolicTrough x : Scene.getInstance().getAllParabolicTroughs()) {
                                            if (Math.abs(2 * f - x.getSemilatusRectum()) > 0.000001) {
                                                changed = true;
                                                break;
                                            }
                                        }
                                    }
                                    if (changed) {
                                        final SetShapeForAllParabolicTroughsCommand c = new SetShapeForAllParabolicTroughsCommand();
                                        Scene.getInstance().setSemilatusRectumForAllParabolicTroughs(2 * f);
                                        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 ParabolicTrough)) {
                    return;
                }
                final String partInfo = selectedPart.toString().substring(0, selectedPart.toString().indexOf(')') + 1);
                final ParabolicTrough t = (ParabolicTrough) selectedPart;
                final Foundation foundation = t.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 Parabolic Trough", true);
                final JRadioButton rb2 = new JRadioButton("All Parabolic Troughs on this Foundation");
                final JRadioButton rb3 = new JRadioButton("All Parabolic Troughs");
                panel.add(rb1);
                panel.add(rb2);
                panel.add(rb3);
                final ButtonGroup bg = new ButtonGroup();
                bg.add(rb1);
                bg.add(rb2);
                bg.add(rb3);
                switch(selectedScopeIndex) {
                    case 0:
                        rb1.setSelected(true);
                        break;
                    case 1:
                        rb2.setSelected(true);
                        break;
                    case 2:
                        rb3.setSelected(true);
                        break;
                }
                final JTextField inputField = new JTextField(EnergyPanel.TWO_DECIMALS.format(t.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(), "Parabolic Trough 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 - t.getBaseHeight()) > 0.000001;
                            if (rb1.isSelected()) {
                                if (changed) {
                                    final ChangeBaseHeightCommand c = new ChangeBaseHeightCommand(t);
                                    t.setBaseHeight(val);
                                    t.draw();
                                    SceneManager.getInstance().refresh();
                                    SceneManager.getInstance().getUndoManager().addEdit(c);
                                }
                                selectedScopeIndex = 0;
                            } else if (rb2.isSelected()) {
                                if (!changed) {
                                    for (final ParabolicTrough x : foundation.getParabolicTroughs()) {
                                        if (Math.abs(val - x.getBaseHeight()) > 0.000001) {
                                            changed = true;
                                            break;
                                        }
                                    }
                                }
                                if (changed) {
                                    final ChangeFoundationSolarCollectorBaseHeightCommand c = new ChangeFoundationSolarCollectorBaseHeightCommand(foundation, t.getClass());
                                    foundation.setBaseHeightForSolarCollectors(val, t.getClass());
                                    SceneManager.getInstance().getUndoManager().addEdit(c);
                                }
                                selectedScopeIndex = 1;
                            } else if (rb3.isSelected()) {
                                if (!changed) {
                                    for (final ParabolicTrough x : Scene.getInstance().getAllParabolicTroughs()) {
                                        if (Math.abs(val - x.getBaseHeight()) > 0.000001) {
                                            changed = true;
                                            break;
                                        }
                                    }
                                }
                                if (changed) {
                                    final ChangeBaseHeightForAllSolarCollectorsCommand c = new ChangeBaseHeightForAllSolarCollectorsCommand(t.getClass());
                                    Scene.getInstance().setBaseHeightForAllParabolicTroughs(val);
                                    SceneManager.getInstance().getUndoManager().addEdit(c);
                                }
                                selectedScopeIndex = 2;
                            }
                            if (changed) {
                                updateAfterEdit();
                            }
                            if (choice == options[0]) {
                                break;
                            }
                        }
                    }
                }
            }
        });
        final JMenuItem miAzimuth = new JMenuItem("Azimuth...");
        miAzimuth.addActionListener(new ActionListener() {

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

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (!(selectedPart instanceof ParabolicTrough)) {
                    return;
                }
                final String partInfo = selectedPart.toString().substring(0, selectedPart.toString().indexOf(')') + 1);
                final ParabolicTrough trough = (ParabolicTrough) selectedPart;
                final Foundation foundation = trough.getTopContainer();
                final String title = "<html>Azimuth Angle (&deg;) of " + partInfo + "</html>";
                final String footnote = "<html><hr><font size=2>The azimuth angle is measured clockwise from the true north.<hr></html>";
                final JPanel gui = new JPanel(new BorderLayout());
                final JPanel panel = new JPanel();
                gui.add(panel, BorderLayout.CENTER);
                panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
                panel.setBorder(BorderFactory.createTitledBorder("Apply to:"));
                final JRadioButton rb1 = new JRadioButton("Only this Parabolic Trough", true);
                final JRadioButton rb2 = new JRadioButton("All Parabolic Troughs on this Foundation");
                final JRadioButton rb3 = new JRadioButton("All Parabolic Troughs");
                panel.add(rb1);
                panel.add(rb2);
                panel.add(rb3);
                final ButtonGroup bg = new ButtonGroup();
                bg.add(rb1);
                bg.add(rb2);
                bg.add(rb3);
                switch(selectedScopeIndex) {
                    case 0:
                        rb1.setSelected(true);
                        break;
                    case 1:
                        rb2.setSelected(true);
                        break;
                    case 2:
                        rb3.setSelected(true);
                        break;
                }
                double a = trough.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(), "Parabolic Trough 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 - trough.getRelativeAzimuth()) > 0.000001;
                            if (rb1.isSelected()) {
                                if (changed) {
                                    final ChangeAzimuthCommand c = new ChangeAzimuthCommand(trough);
                                    trough.setRelativeAzimuth(a);
                                    trough.draw();
                                    SceneManager.getInstance().refresh();
                                    SceneManager.getInstance().getUndoManager().addEdit(c);
                                }
                                selectedScopeIndex = 0;
                            } else if (rb2.isSelected()) {
                                if (!changed) {
                                    for (final ParabolicTrough x : foundation.getParabolicTroughs()) {
                                        if (Math.abs(a - x.getRelativeAzimuth()) > 0.000001) {
                                            changed = true;
                                            break;
                                        }
                                    }
                                }
                                if (changed) {
                                    final ChangeFoundationParabolicTroughAzimuthCommand c = new ChangeFoundationParabolicTroughAzimuthCommand(foundation);
                                    foundation.setAzimuthForParabolicTroughs(a);
                                    SceneManager.getInstance().getUndoManager().addEdit(c);
                                }
                                selectedScopeIndex = 1;
                            } else if (rb3.isSelected()) {
                                if (!changed) {
                                    for (final ParabolicTrough x : Scene.getInstance().getAllParabolicTroughs()) {
                                        if (Math.abs(a - x.getRelativeAzimuth()) > 0.000001) {
                                            changed = true;
                                            break;
                                        }
                                    }
                                }
                                if (changed) {
                                    final ChangeAzimuthForAllParabolicTroughsCommand c = new ChangeAzimuthForAllParabolicTroughsCommand();
                                    Scene.getInstance().setAzimuthForAllParabolicTroughs(a);
                                    SceneManager.getInstance().getUndoManager().addEdit(c);
                                }
                                selectedScopeIndex = 2;
                            }
                            if (changed) {
                                updateAfterEdit();
                            }
                            if (choice == options[0]) {
                                break;
                            }
                        }
                    }
                }
            }
        });
        final JMenu labelMenu = new JMenu("Label");
        final JCheckBoxMenuItem miLabelNone = new JCheckBoxMenuItem("None", true);
        miLabelNone.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(final ActionEvent e) {
                if (miLabelNone.isSelected()) {
                    final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                    if (selectedPart instanceof ParabolicTrough) {
                        final ParabolicTrough t = (ParabolicTrough) selectedPart;
                        final SetParabolicTroughLabelCommand c = new SetParabolicTroughLabelCommand(t);
                        t.clearLabels();
                        t.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 ParabolicTrough) {
                    final ParabolicTrough t = (ParabolicTrough) selectedPart;
                    final SetParabolicTroughLabelCommand c = new SetParabolicTroughLabelCommand(t);
                    t.setLabelCustom(miLabelCustom.isSelected());
                    if (t.getLabelCustom()) {
                        t.setLabelCustomText(JOptionPane.showInputDialog(MainFrame.getInstance(), "Custom Text", t.getLabelCustomText()));
                    }
                    t.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 ParabolicTrough) {
                    final ParabolicTrough t = (ParabolicTrough) selectedPart;
                    final SetParabolicTroughLabelCommand c = new SetParabolicTroughLabelCommand(t);
                    t.setLabelId(miLabelId.isSelected());
                    t.draw();
                    SceneManager.getInstance().getUndoManager().addEdit(c);
                    Scene.getInstance().setEdited(true);
                    SceneManager.getInstance().refresh();
                }
            }
        });
        labelMenu.add(miLabelId);
        final JCheckBoxMenuItem miLabelEnergyOutput = new JCheckBoxMenuItem("Energy Output");
        miLabelEnergyOutput.addActionListener(new ActionListener() {

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

            @Override
            public void run() {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (!(selectedPart instanceof ParabolicTrough)) {
                    return;
                }
                final ParabolicTrough t = (ParabolicTrough) selectedPart;
                Util.selectSilently(miLabelNone, !t.isLabelVisible());
                Util.selectSilently(miLabelCustom, t.getLabelCustom());
                Util.selectSilently(miLabelId, t.getLabelId());
                Util.selectSilently(miLabelEnergyOutput, t.getLabelEnergyOutput());
                Util.selectSilently(cbmiDrawSunBeams, t.isSunBeamVisible());
                Util.selectSilently(cbmiDisableEditPoints, t.getLockEdit());
            }
        });
        final JMenuItem miReflectance = new JMenuItem("Mirror Reflectance...");
        miReflectance.addActionListener(new ActionListener() {

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

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (!(selectedPart instanceof ParabolicTrough)) {
                    return;
                }
                final String partInfo = selectedPart.toString().substring(0, selectedPart.toString().indexOf(')') + 1);
                final ParabolicTrough t = (ParabolicTrough) selectedPart;
                final String title = "<html>Reflectance (%) of " + partInfo + "</html>";
                final String footnote = "<html><hr><font size=2>Reflectance can be affected by pollen and dust.<hr></html>";
                final JPanel gui = new JPanel(new BorderLayout());
                final JPanel panel = new JPanel();
                gui.add(panel, BorderLayout.CENTER);
                panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
                panel.setBorder(BorderFactory.createTitledBorder("Apply to:"));
                final JRadioButton rb1 = new JRadioButton("Only this Parabolic Trough", true);
                final JRadioButton rb2 = new JRadioButton("All Parabolic Troughs on this Foundation");
                final JRadioButton rb3 = new JRadioButton("All Parabolic Troughs");
                panel.add(rb1);
                panel.add(rb2);
                panel.add(rb3);
                final ButtonGroup bg = new ButtonGroup();
                bg.add(rb1);
                bg.add(rb2);
                bg.add(rb3);
                switch(selectedScopeIndex) {
                    case 0:
                        rb1.setSelected(true);
                        break;
                    case 1:
                        rb2.setSelected(true);
                        break;
                    case 2:
                        rb3.setSelected(true);
                        break;
                }
                final JTextField inputField = new JTextField(EnergyPanel.TWO_DECIMALS.format(t.getReflectance() * 100));
                gui.add(inputField, BorderLayout.SOUTH);
                final Object[] options = new Object[] { "OK", "Cancel", "Apply" };
                final JOptionPane optionPane = new JOptionPane(new Object[] { title, footnote, gui }, JOptionPane.QUESTION_MESSAGE, JOptionPane.YES_NO_CANCEL_OPTION, null, options, options[2]);
                final JDialog dialog = optionPane.createDialog(MainFrame.getInstance(), "Parabolic Trough Mirror Reflectance");
                while (true) {
                    inputField.selectAll();
                    inputField.requestFocusInWindow();
                    dialog.setVisible(true);
                    final Object choice = optionPane.getValue();
                    if (choice == options[1] || choice == null) {
                        break;
                    } else {
                        double val = 0;
                        boolean ok = true;
                        try {
                            val = Double.parseDouble(inputField.getText());
                        } catch (final NumberFormatException exception) {
                            JOptionPane.showMessageDialog(MainFrame.getInstance(), inputField.getText() + " is an invalid value!", "Error", JOptionPane.ERROR_MESSAGE);
                            ok = false;
                        }
                        if (ok) {
                            if (val < 50 || val > 99) {
                                JOptionPane.showMessageDialog(MainFrame.getInstance(), "Parabolic trough reflectance must be between 50% and 99%.", "Range Error", JOptionPane.ERROR_MESSAGE);
                            } else {
                                boolean changed = Math.abs(val * 0.01 - t.getReflectance()) > 0.000001;
                                if (rb1.isSelected()) {
                                    if (changed) {
                                        final ChangeSolarReflectorReflectanceCommand c = new ChangeSolarReflectorReflectanceCommand(t);
                                        t.setReflectance(val * 0.01);
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 0;
                                } else if (rb2.isSelected()) {
                                    final Foundation foundation = t.getTopContainer();
                                    if (!changed) {
                                        for (final ParabolicTrough x : foundation.getParabolicTroughs()) {
                                            if (Math.abs(val * 0.01 - x.getReflectance()) > 0.000001) {
                                                changed = true;
                                                break;
                                            }
                                        }
                                    }
                                    if (changed) {
                                        final ChangeFoundationSolarReflectorReflectanceCommand c = new ChangeFoundationSolarReflectorReflectanceCommand(foundation, t.getClass());
                                        foundation.setReflectanceForSolarReflectors(val * 0.01, t.getClass());
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 1;
                                } else if (rb3.isSelected()) {
                                    if (!changed) {
                                        for (final ParabolicTrough x : Scene.getInstance().getAllParabolicTroughs()) {
                                            if (Math.abs(val * 0.01 - x.getReflectance()) > 0.000001) {
                                                changed = true;
                                                break;
                                            }
                                        }
                                    }
                                    if (changed) {
                                        final ChangeReflectanceForAllSolarReflectorsCommand c = new ChangeReflectanceForAllSolarReflectorsCommand(t.getClass());
                                        Scene.getInstance().setReflectanceForAllSolarReflectors(val * 0.01, t.getClass());
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 2;
                                }
                                if (changed) {
                                    updateAfterEdit();
                                }
                                if (choice == options[0]) {
                                    break;
                                }
                            }
                        }
                    }
                }
            }
        });
        final JMenuItem miAbsorptance = new JMenuItem("Receiver Absorptance...");
        miAbsorptance.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 ParabolicTrough)) {
                    return;
                }
                final String partInfo = selectedPart.toString().substring(0, selectedPart.toString().indexOf(')') + 1);
                final ParabolicTrough t = (ParabolicTrough) selectedPart;
                final String title = "<html>Absorptance (%) of " + partInfo + "</html>";
                final String footnote = "<html><hr><font size=2><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 Parabolic Trough", true);
                final JRadioButton rb2 = new JRadioButton("All Parabolic Troughs on this Foundation");
                final JRadioButton rb3 = new JRadioButton("All Parabolic Troughs");
                panel.add(rb1);
                panel.add(rb2);
                panel.add(rb3);
                final ButtonGroup bg = new ButtonGroup();
                bg.add(rb1);
                bg.add(rb2);
                bg.add(rb3);
                switch(selectedScopeIndex) {
                    case 0:
                        rb1.setSelected(true);
                        break;
                    case 1:
                        rb2.setSelected(true);
                        break;
                    case 2:
                        rb3.setSelected(true);
                        break;
                }
                final JTextField inputField = new JTextField(EnergyPanel.TWO_DECIMALS.format(t.getAbsorptance() * 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(), "Parabolic Trough Receiver Absorptance");
                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 < 20 || val > 100) {
                                JOptionPane.showMessageDialog(MainFrame.getInstance(), "Parabolic trough absorptance must be between 20% and 100%.", "Range Error", JOptionPane.ERROR_MESSAGE);
                            } else {
                                boolean changed = Math.abs(val * 0.01 - t.getAbsorptance()) > 0.000001;
                                if (rb1.isSelected()) {
                                    if (changed) {
                                        final ChangeSolarReflectorAbsorptanceCommand c = new ChangeSolarReflectorAbsorptanceCommand(t);
                                        t.setAbsorptance(val * 0.01);
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 0;
                                } else if (rb2.isSelected()) {
                                    final Foundation foundation = t.getTopContainer();
                                    if (!changed) {
                                        for (final ParabolicTrough x : foundation.getParabolicTroughs()) {
                                            if (Math.abs(val * 0.01 - x.getAbsorptance()) > 0.000001) {
                                                changed = true;
                                                break;
                                            }
                                        }
                                    }
                                    if (changed) {
                                        final ChangeFoundationSolarReflectorAbsorptanceCommand c = new ChangeFoundationSolarReflectorAbsorptanceCommand(foundation, t.getClass());
                                        foundation.setAbsorptanceForSolarReflectors(val * 0.01, t.getClass());
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 1;
                                } else if (rb3.isSelected()) {
                                    if (!changed) {
                                        for (final ParabolicTrough x : Scene.getInstance().getAllParabolicTroughs()) {
                                            if (Math.abs(val * 0.01 - x.getAbsorptance()) > 0.000001) {
                                                changed = true;
                                                break;
                                            }
                                        }
                                    }
                                    if (changed) {
                                        final ChangeAbsorptanceForAllSolarReflectorsCommand c = new ChangeAbsorptanceForAllSolarReflectorsCommand(t.getClass());
                                        Scene.getInstance().setAbsorptanceForAllSolarReflectors(val * 0.01, t.getClass());
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 2;
                                }
                                if (changed) {
                                    updateAfterEdit();
                                }
                                if (choice == options[0]) {
                                    break;
                                }
                            }
                        }
                    }
                }
            }
        });
        final JMenuItem miOpticalEfficiency = new JMenuItem("Optical Efficiency...");
        miOpticalEfficiency.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 ParabolicTrough)) {
                    return;
                }
                final String partInfo = selectedPart.toString().substring(0, selectedPart.toString().indexOf(')') + 1);
                final ParabolicTrough t = (ParabolicTrough) selectedPart;
                final String title = "<html>Opitical efficiency (%) of " + partInfo + "</html>";
                final String footnote = "<html><hr><font size=2>For example, the percentage of the effective area for reflection after deducting<br>the area of facet gaps, module frames, absorber shadow, etc.<hr></html>";
                final JPanel gui = new JPanel(new BorderLayout());
                final JPanel panel = new JPanel();
                gui.add(panel, BorderLayout.CENTER);
                panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
                panel.setBorder(BorderFactory.createTitledBorder("Apply to:"));
                final JRadioButton rb1 = new JRadioButton("Only this Parabolic Trough", true);
                final JRadioButton rb2 = new JRadioButton("All Parabolic Troughs on this Foundation");
                final JRadioButton rb3 = new JRadioButton("All Parabolic Troughs");
                panel.add(rb1);
                panel.add(rb2);
                panel.add(rb3);
                final ButtonGroup bg = new ButtonGroup();
                bg.add(rb1);
                bg.add(rb2);
                bg.add(rb3);
                switch(selectedScopeIndex) {
                    case 0:
                        rb1.setSelected(true);
                        break;
                    case 1:
                        rb2.setSelected(true);
                        break;
                    case 2:
                        rb3.setSelected(true);
                        break;
                }
                final JTextField inputField = new JTextField(EnergyPanel.TWO_DECIMALS.format(t.getOpticalEfficiency() * 100));
                gui.add(inputField, BorderLayout.SOUTH);
                final Object[] options = new Object[] { "OK", "Cancel", "Apply" };
                final JOptionPane optionPane = new JOptionPane(new Object[] { title, footnote, gui }, JOptionPane.QUESTION_MESSAGE, JOptionPane.YES_NO_CANCEL_OPTION, null, options, options[2]);
                final JDialog dialog = optionPane.createDialog(MainFrame.getInstance(), "Parabolic Trough Optical 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 < 50 || val > 100) {
                                JOptionPane.showMessageDialog(MainFrame.getInstance(), "Parabolic trough optical efficiency must be between 50% and 100%.", "Range Error", JOptionPane.ERROR_MESSAGE);
                            } else {
                                boolean changed = Math.abs(val * 0.01 - t.getOpticalEfficiency()) > 0.000001;
                                if (rb1.isSelected()) {
                                    if (changed) {
                                        final ChangeSolarReflectorOpticalEfficiencyCommand c = new ChangeSolarReflectorOpticalEfficiencyCommand(t);
                                        t.setOpticalEfficiency(val * 0.01);
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 0;
                                } else if (rb2.isSelected()) {
                                    final Foundation foundation = t.getTopContainer();
                                    if (!changed) {
                                        for (final ParabolicTrough x : foundation.getParabolicTroughs()) {
                                            if (Math.abs(val * 0.01 - x.getOpticalEfficiency()) > 0.000001) {
                                                changed = true;
                                                break;
                                            }
                                        }
                                    }
                                    if (changed) {
                                        final ChangeFoundationSolarReflectorOpticalEfficiencyCommand c = new ChangeFoundationSolarReflectorOpticalEfficiencyCommand(foundation, t.getClass());
                                        foundation.setOpticalEfficiencyForSolarReflectors(val * 0.01, t.getClass());
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 1;
                                } else if (rb3.isSelected()) {
                                    if (!changed) {
                                        for (final ParabolicTrough x : Scene.getInstance().getAllParabolicTroughs()) {
                                            if (Math.abs(val * 0.01 - x.getOpticalEfficiency()) > 0.000001) {
                                                changed = true;
                                                break;
                                            }
                                        }
                                    }
                                    if (changed) {
                                        final ChangeOpticalEfficiencyForAllSolarReflectorsCommand c = new ChangeOpticalEfficiencyForAllSolarReflectorsCommand(t.getClass());
                                        Scene.getInstance().setOpticalEfficiencyForAllSolarReflectors(val * 0.01, t.getClass());
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 2;
                                }
                                if (changed) {
                                    updateAfterEdit();
                                }
                                if (choice == options[0]) {
                                    break;
                                }
                            }
                        }
                    }
                }
            }
        });
        final JMenuItem miThermalEfficiency = new JMenuItem("Thermal Efficiency...");
        miThermalEfficiency.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 ParabolicTrough)) {
                    return;
                }
                final String partInfo = selectedPart.toString().substring(0, selectedPart.toString().indexOf(')') + 1);
                final ParabolicTrough t = (ParabolicTrough) selectedPart;
                final String title = "<html>Thermal efficiency (%) of " + partInfo + "</html>";
                final String footnote = "<html><hr><font size=2><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 Parabolic Trough", true);
                final JRadioButton rb2 = new JRadioButton("All Parabolic Troughs on this Foundation");
                final JRadioButton rb3 = new JRadioButton("All Parabolic Troughs");
                panel.add(rb1);
                panel.add(rb2);
                panel.add(rb3);
                final ButtonGroup bg = new ButtonGroup();
                bg.add(rb1);
                bg.add(rb2);
                bg.add(rb3);
                switch(selectedScopeIndex) {
                    case 0:
                        rb1.setSelected(true);
                        break;
                    case 1:
                        rb2.setSelected(true);
                        break;
                    case 2:
                        rb3.setSelected(true);
                        break;
                }
                final JTextField inputField = new JTextField(EnergyPanel.TWO_DECIMALS.format(t.getThermalEfficiency() * 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(), "Parabolic Trough Thermal Efficiency");
                while (true) {
                    inputField.selectAll();
                    inputField.requestFocusInWindow();
                    dialog.setVisible(true);
                    final Object choice = optionPane.getValue();
                    if (choice == options[1] || choice == null) {
                        break;
                    } else {
                        double val = 0;
                        boolean ok = true;
                        try {
                            val = Double.parseDouble(inputField.getText());
                        } catch (final NumberFormatException exception) {
                            JOptionPane.showMessageDialog(MainFrame.getInstance(), inputField.getText() + " is an invalid value!", "Error", JOptionPane.ERROR_MESSAGE);
                            ok = false;
                        }
                        if (ok) {
                            if (val < 5 || val > 80) {
                                JOptionPane.showMessageDialog(MainFrame.getInstance(), "Parabolic trough thermal efficiency must be between 5% and 80%.", "Range Error", JOptionPane.ERROR_MESSAGE);
                            } else {
                                boolean changed = Math.abs(val * 0.01 - t.getThermalEfficiency()) > 0.000001;
                                if (rb1.isSelected()) {
                                    if (changed) {
                                        final ChangeSolarReflectorThermalEfficiencyCommand c = new ChangeSolarReflectorThermalEfficiencyCommand(t);
                                        t.setThermalEfficiency(val * 0.01);
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 0;
                                } else if (rb2.isSelected()) {
                                    final Foundation foundation = t.getTopContainer();
                                    if (!changed) {
                                        for (final ParabolicTrough x : foundation.getParabolicTroughs()) {
                                            if (Math.abs(val * 0.01 - x.getThermalEfficiency()) > 0.000001) {
                                                changed = true;
                                                break;
                                            }
                                        }
                                    }
                                    if (changed) {
                                        final ChangeFoundationSolarReflectorThermalEfficiencyCommand c = new ChangeFoundationSolarReflectorThermalEfficiencyCommand(foundation, t.getClass());
                                        foundation.setThermalEfficiencyForSolarReflectors(val * 0.01, t.getClass());
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 1;
                                } else if (rb3.isSelected()) {
                                    if (!changed) {
                                        for (final ParabolicTrough x : Scene.getInstance().getAllParabolicTroughs()) {
                                            if (Math.abs(val * 0.01 - x.getThermalEfficiency()) > 0.000001) {
                                                changed = true;
                                                break;
                                            }
                                        }
                                    }
                                    if (changed) {
                                        final ChangeThermalEfficiencyForAllSolarReflectorsCommand c = new ChangeThermalEfficiencyForAllSolarReflectorsCommand(t.getClass());
                                        Scene.getInstance().setThermalEfficiencyForAllSolarReflectors(val * 0.01, t.getClass());
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 2;
                                }
                                if (changed) {
                                    updateAfterEdit();
                                }
                                if (choice == options[0]) {
                                    break;
                                }
                            }
                        }
                    }
                }
            }
        });
        popupMenuForParabolicTrough.addSeparator();
        popupMenuForParabolicTrough.add(cbmiDisableEditPoints);
        popupMenuForParabolicTrough.add(cbmiDrawSunBeams);
        popupMenuForParabolicTrough.add(labelMenu);
        popupMenuForParabolicTrough.addSeparator();
        popupMenuForParabolicTrough.add(miTroughLength);
        popupMenuForParabolicTrough.add(miApertureWidth);
        popupMenuForParabolicTrough.add(miFocalLength);
        popupMenuForParabolicTrough.add(miModuleLength);
        popupMenuForParabolicTrough.add(miBaseHeight);
        popupMenuForParabolicTrough.add(miAzimuth);
        popupMenuForParabolicTrough.addSeparator();
        popupMenuForParabolicTrough.add(miReflectance);
        popupMenuForParabolicTrough.add(miAbsorptance);
        popupMenuForParabolicTrough.add(miOpticalEfficiency);
        popupMenuForParabolicTrough.add(miThermalEfficiency);
        popupMenuForParabolicTrough.addSeparator();
        popupMenuForParabolicTrough.add(miMesh);
        popupMenuForParabolicTrough.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 ParabolicTrough) {
                    new ParabolicTroughDailyAnalysis().show();
                }
            }
        });
        popupMenuForParabolicTrough.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 ParabolicTrough) {
                    new ParabolicTroughAnnualAnalysis().show();
                }
            }
        });
        popupMenuForParabolicTrough.add(mi);
    }
    return popupMenuForParabolicTrough;
}
Also used : JPanel(javax.swing.JPanel) ParabolicTrough(org.concord.energy3d.model.ParabolicTrough) ActionEvent(java.awt.event.ActionEvent) SetPartSizeCommand(org.concord.energy3d.undo.SetPartSizeCommand) ChangeSolarReflectorOpticalEfficiencyCommand(org.concord.energy3d.undo.ChangeSolarReflectorOpticalEfficiencyCommand) ChangeBaseHeightForAllSolarCollectorsCommand(org.concord.energy3d.undo.ChangeBaseHeightForAllSolarCollectorsCommand) ChangeAzimuthForAllParabolicTroughsCommand(org.concord.energy3d.undo.ChangeAzimuthForAllParabolicTroughsCommand) ChangeFoundationParabolicTroughAzimuthCommand(org.concord.energy3d.undo.ChangeFoundationParabolicTroughAzimuthCommand) BorderLayout(java.awt.BorderLayout) ChangeAbsorptanceForAllSolarReflectorsCommand(org.concord.energy3d.undo.ChangeAbsorptanceForAllSolarReflectorsCommand) Foundation(org.concord.energy3d.model.Foundation) LockEditPointsOnFoundationCommand(org.concord.energy3d.undo.LockEditPointsOnFoundationCommand) ChangeFoundationSolarReflectorThermalEfficiencyCommand(org.concord.energy3d.undo.ChangeFoundationSolarReflectorThermalEfficiencyCommand) ChangeReflectanceForAllSolarReflectorsCommand(org.concord.energy3d.undo.ChangeReflectanceForAllSolarReflectorsCommand) HousePart(org.concord.energy3d.model.HousePart) SetParabolicTroughSemilatusRectumCommand(org.concord.energy3d.undo.SetParabolicTroughSemilatusRectumCommand) SetShapeForParabolicTroughsOnFoundationCommand(org.concord.energy3d.undo.SetShapeForParabolicTroughsOnFoundationCommand) JOptionPane(javax.swing.JOptionPane) ChangeAzimuthCommand(org.concord.energy3d.undo.ChangeAzimuthCommand) JCheckBoxMenuItem(javax.swing.JCheckBoxMenuItem) ChangeSolarReflectorAbsorptanceCommand(org.concord.energy3d.undo.ChangeSolarReflectorAbsorptanceCommand) ActionListener(java.awt.event.ActionListener) SetParabolicTroughLabelCommand(org.concord.energy3d.undo.SetParabolicTroughLabelCommand) SetShapeForAllParabolicTroughsCommand(org.concord.energy3d.undo.SetShapeForAllParabolicTroughsCommand) ChangeFoundationSolarCollectorBaseHeightCommand(org.concord.energy3d.undo.ChangeFoundationSolarCollectorBaseHeightCommand) JDialog(javax.swing.JDialog) ItemEvent(java.awt.event.ItemEvent) JRadioButton(javax.swing.JRadioButton) ChangeSolarReflectorReflectanceCommand(org.concord.energy3d.undo.ChangeSolarReflectorReflectanceCommand) BoxLayout(javax.swing.BoxLayout) JTextField(javax.swing.JTextField) ChangeFoundationSolarReflectorAbsorptanceCommand(org.concord.energy3d.undo.ChangeFoundationSolarReflectorAbsorptanceCommand) GridLayout(java.awt.GridLayout) JMenuItem(javax.swing.JMenuItem) LockEditPointsCommand(org.concord.energy3d.undo.LockEditPointsCommand) ParabolicTroughAnnualAnalysis(org.concord.energy3d.simulation.ParabolicTroughAnnualAnalysis) ParabolicTroughDailyAnalysis(org.concord.energy3d.simulation.ParabolicTroughDailyAnalysis) JLabel(javax.swing.JLabel) ShowSunBeamCommand(org.concord.energy3d.undo.ShowSunBeamCommand) ChangeBaseHeightCommand(org.concord.energy3d.undo.ChangeBaseHeightCommand) ChangeOpticalEfficiencyForAllSolarReflectorsCommand(org.concord.energy3d.undo.ChangeOpticalEfficiencyForAllSolarReflectorsCommand) LockEditPointsForClassCommand(org.concord.energy3d.undo.LockEditPointsForClassCommand) ChangeFoundationSolarReflectorReflectanceCommand(org.concord.energy3d.undo.ChangeFoundationSolarReflectorReflectanceCommand) ChangeSolarReflectorThermalEfficiencyCommand(org.concord.energy3d.undo.ChangeSolarReflectorThermalEfficiencyCommand) ChangeFoundationSolarReflectorOpticalEfficiencyCommand(org.concord.energy3d.undo.ChangeFoundationSolarReflectorOpticalEfficiencyCommand) ButtonGroup(javax.swing.ButtonGroup) ChangeThermalEfficiencyForAllSolarReflectorsCommand(org.concord.energy3d.undo.ChangeThermalEfficiencyForAllSolarReflectorsCommand) ItemListener(java.awt.event.ItemListener) JMenu(javax.swing.JMenu)

Example 3 with Foundation

use of org.concord.energy3d.model.Foundation 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 4 with Foundation

use of org.concord.energy3d.model.Foundation 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 5 with Foundation

use of org.concord.energy3d.model.Foundation in project energy3d by concord-consortium.

the class PopupMenuForWindow method getPopupMenu.

static JPopupMenu getPopupMenu() {
    if (popupMenuForWindow == null) {
        final JMenu shutterMenu = new JMenu("Shutters");
        popupMenuForWindow = createPopupMenu(true, true, new Runnable() {

            @Override
            public void run() {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (selectedPart instanceof Window) {
                    shutterMenu.setEnabled(selectedPart.getContainer() instanceof Wall);
                }
            }
        });
        final JMenu muntinMenu = new JMenu("Muntins");
        final JCheckBoxMenuItem cbmiHorizontalBars = new JCheckBoxMenuItem("Horizontal Bars");
        cbmiHorizontalBars.addItemListener(new ItemListener() {

            @Override
            public void itemStateChanged(final ItemEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (selectedPart instanceof Window) {
                    final Window w = (Window) selectedPart;
                    w.setHorizontalBars(cbmiHorizontalBars.isSelected());
                    w.draw();
                    SceneManager.getInstance().refresh();
                    Scene.getInstance().setEdited(true);
                }
            }
        });
        muntinMenu.add(cbmiHorizontalBars);
        final JCheckBoxMenuItem cbmiVerticalBars = new JCheckBoxMenuItem("Vertical Bars");
        cbmiVerticalBars.addItemListener(new ItemListener() {

            @Override
            public void itemStateChanged(final ItemEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (selectedPart instanceof Window) {
                    final Window w = (Window) selectedPart;
                    w.setVerticalBars(cbmiVerticalBars.isSelected());
                    w.draw();
                    SceneManager.getInstance().refresh();
                    Scene.getInstance().setEdited(true);
                }
            }
        });
        muntinMenu.add(cbmiVerticalBars);
        muntinMenu.addSeparator();
        final ButtonGroup muntinButtonGroup = new ButtonGroup();
        final JRadioButtonMenuItem miMoreBars = new JRadioButtonMenuItem("More Bars");
        miMoreBars.addItemListener(new ItemListener() {

            @Override
            public void itemStateChanged(final ItemEvent e) {
                if (e.getStateChange() == ItemEvent.SELECTED) {
                    final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                    if (selectedPart instanceof Window) {
                        final Window w = (Window) selectedPart;
                        w.setStyle(Window.MORE_MUNTIN_BARS);
                        w.draw();
                        SceneManager.getInstance().refresh();
                        Scene.getInstance().setEdited(true);
                    }
                }
            }
        });
        muntinButtonGroup.add(miMoreBars);
        muntinMenu.add(miMoreBars);
        final JRadioButtonMenuItem miMediumBars = new JRadioButtonMenuItem("Medium Bars");
        miMediumBars.addItemListener(new ItemListener() {

            @Override
            public void itemStateChanged(final ItemEvent e) {
                if (e.getStateChange() == ItemEvent.SELECTED) {
                    final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                    if (selectedPart instanceof Window) {
                        final Window w = (Window) selectedPart;
                        w.setStyle(Window.MEDIUM_MUNTIN_BARS);
                        w.draw();
                        SceneManager.getInstance().refresh();
                        Scene.getInstance().setEdited(true);
                    }
                }
            }
        });
        muntinButtonGroup.add(miMediumBars);
        muntinMenu.add(miMediumBars);
        final JRadioButtonMenuItem miLessBars = new JRadioButtonMenuItem("Less Bars");
        miLessBars.addItemListener(new ItemListener() {

            @Override
            public void itemStateChanged(final ItemEvent e) {
                if (e.getStateChange() == ItemEvent.SELECTED) {
                    final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                    if (selectedPart instanceof Window) {
                        final Window w = (Window) selectedPart;
                        w.setStyle(Window.LESS_MUNTIN_BARS);
                        w.draw();
                        SceneManager.getInstance().refresh();
                        Scene.getInstance().setEdited(true);
                    }
                }
            }
        });
        muntinButtonGroup.add(miLessBars);
        muntinMenu.add(miLessBars);
        muntinMenu.addMenuListener(new MenuListener() {

            @Override
            public void menuSelected(final MenuEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (selectedPart instanceof Window) {
                    final Window window = (Window) selectedPart;
                    switch(window.getStyle()) {
                        case Window.MORE_MUNTIN_BARS:
                            Util.selectSilently(miMoreBars, true);
                            break;
                        case Window.MEDIUM_MUNTIN_BARS:
                            Util.selectSilently(miMediumBars, true);
                            break;
                        case Window.LESS_MUNTIN_BARS:
                            Util.selectSilently(miLessBars, true);
                            break;
                    }
                    // NO_MUNTIN_BAR backward compatibility
                    Util.selectSilently(cbmiHorizontalBars, window.getStyle() != Window.NO_MUNTIN_BAR && window.getHorizontalBars());
                    Util.selectSilently(cbmiVerticalBars, window.getStyle() != Window.NO_MUNTIN_BAR && window.getVerticalBars());
                }
            }

            @Override
            public void menuDeselected(final MenuEvent e) {
                muntinMenu.setEnabled(true);
            }

            @Override
            public void menuCanceled(final MenuEvent e) {
                muntinMenu.setEnabled(true);
            }
        });
        final JCheckBoxMenuItem cbmiBothShutters = new JCheckBoxMenuItem("Both Shutters");
        final JCheckBoxMenuItem cbmiLeftShutter = new JCheckBoxMenuItem("Left Shutter");
        final JCheckBoxMenuItem cbmiRightShutter = new JCheckBoxMenuItem("Right Shutter");
        cbmiLeftShutter.addItemListener(new ItemListener() {

            @Override
            public void itemStateChanged(final ItemEvent e) {
                if (e.getStateChange() == ItemEvent.SELECTED) {
                    final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                    if (selectedPart instanceof Window) {
                        final Window w = (Window) selectedPart;
                        final ChangeWindowShuttersCommand c = new ChangeWindowShuttersCommand(w);
                        w.setLeftShutter(cbmiLeftShutter.isSelected());
                        w.draw();
                        Scene.getInstance().setEdited(true);
                        SceneManager.getInstance().getUndoManager().addEdit(c);
                    }
                }
            }
        });
        shutterMenu.add(cbmiLeftShutter);
        cbmiRightShutter.addItemListener(new ItemListener() {

            @Override
            public void itemStateChanged(final ItemEvent e) {
                if (e.getStateChange() == ItemEvent.SELECTED) {
                    final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                    if (selectedPart instanceof Window) {
                        final Window w = (Window) selectedPart;
                        final ChangeWindowShuttersCommand c = new ChangeWindowShuttersCommand(w);
                        w.setRightShutter(cbmiRightShutter.isSelected());
                        w.draw();
                        Scene.getInstance().setEdited(true);
                        SceneManager.getInstance().getUndoManager().addEdit(c);
                    }
                }
            }
        });
        shutterMenu.add(cbmiRightShutter);
        cbmiBothShutters.addItemListener(new ItemListener() {

            @Override
            public void itemStateChanged(final ItemEvent e) {
                if (e.getStateChange() == ItemEvent.SELECTED) {
                    final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                    if (selectedPart instanceof Window) {
                        final Window w = (Window) selectedPart;
                        final ChangeWindowShuttersCommand c = new ChangeWindowShuttersCommand(w);
                        w.setLeftShutter(cbmiBothShutters.isSelected());
                        w.setRightShutter(cbmiBothShutters.isSelected());
                        w.draw();
                        Scene.getInstance().setEdited(true);
                        SceneManager.getInstance().getUndoManager().addEdit(c);
                    }
                }
            }
        });
        shutterMenu.add(cbmiBothShutters);
        shutterMenu.addSeparator();
        final JMenuItem miShutterColor = new JMenuItem("Color...");
        miShutterColor.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 Window)) {
                    return;
                }
                final Window window = (Window) selectedPart;
                final JColorChooser colorChooser = MainFrame.getInstance().getColorChooser();
                final ReadOnlyColorRGBA color = window.getShutterColor();
                if (color != null) {
                    colorChooser.setColor(new Color(color.getRed(), color.getGreen(), color.getBlue()));
                }
                final ActionListener actionListener = new ActionListener() {

                    @Override
                    public void actionPerformed(final ActionEvent e) {
                        final Color c = colorChooser.getColor();
                        final float[] newColor = c.getComponents(null);
                        final ColorRGBA color = new ColorRGBA(newColor[0], newColor[1], newColor[2], 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 Window", true);
                        final JRadioButton rb2 = new JRadioButton("All Windows on this " + (window.getContainer() instanceof Wall ? "Wall" : "Roof"));
                        final JRadioButton rb3 = new JRadioButton("All Windows of this Building");
                        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 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(), "Shutter Color");
                        while (true) {
                            dialog.setVisible(true);
                            final Object choice = optionPane.getValue();
                            if (choice == options[1] || choice == null) {
                                break;
                            } else {
                                boolean changed = !color.equals(window.getShutterColor());
                                if (rb1.isSelected()) {
                                    // apply to only this window
                                    if (changed) {
                                        final ChangeShutterColorCommand cmd = new ChangeShutterColorCommand(window);
                                        window.setShutterColor(color);
                                        window.draw();
                                        SceneManager.getInstance().getUndoManager().addEdit(cmd);
                                    }
                                    selectedScopeIndex = 0;
                                } else if (rb2.isSelected()) {
                                    if (!changed) {
                                        if (window.getContainer() instanceof Wall) {
                                            final Wall wall = (Wall) window.getContainer();
                                            for (final Window x : wall.getWindows()) {
                                                if (!color.equals(x.getShutterColor())) {
                                                    changed = true;
                                                    break;
                                                }
                                            }
                                        } else if (window.getContainer() instanceof Roof) {
                                            final Roof roof = (Roof) window.getContainer();
                                            for (final Window x : roof.getWindows()) {
                                                if (!color.equals(x.getShutterColor())) {
                                                    changed = true;
                                                    break;
                                                }
                                            }
                                        }
                                    }
                                    if (changed) {
                                        final ChangeContainerShutterColorCommand cmd = new ChangeContainerShutterColorCommand(window.getContainer());
                                        Scene.getInstance().setWindowColorInContainer(window.getContainer(), color, true);
                                        SceneManager.getInstance().getUndoManager().addEdit(cmd);
                                    }
                                    selectedScopeIndex = 1;
                                } else {
                                    final Foundation foundation = window.getTopContainer();
                                    if (!changed) {
                                        for (final Window x : foundation.getWindows()) {
                                            if (!color.equals(x.getShutterColor())) {
                                                changed = true;
                                                break;
                                            }
                                        }
                                    }
                                    if (changed) {
                                        final ChangeBuildingShutterColorCommand cmd = new ChangeBuildingShutterColorCommand(window);
                                        Scene.getInstance().setShutterColorOfBuilding(window, color);
                                        SceneManager.getInstance().getUndoManager().addEdit(cmd);
                                    }
                                    selectedScopeIndex = 2;
                                }
                                if (changed) {
                                    Scene.getInstance().setEdited(true);
                                    SceneManager.getInstance().refresh();
                                }
                                if (choice == options[0]) {
                                    break;
                                }
                            }
                        }
                    }
                };
                JColorChooser.createDialog(MainFrame.getInstance(), "Select Shutter Color", true, colorChooser, actionListener, null).setVisible(true);
            }
        });
        shutterMenu.add(miShutterColor);
        final JMenuItem miShutterLength = new JMenuItem("Relative Length...");
        miShutterLength.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 Window)) {
                    return;
                }
                final Window window = (Window) selectedPart;
                final String partInfo = window.toString().substring(0, selectedPart.toString().indexOf(')') + 1);
                final String title = "<html>Relative Length of Shutter for " + partInfo + "</html>";
                final String footnote = "<html><hr width=400></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 Window Shutter", true);
                final JRadioButton rb2 = new JRadioButton("All Window Shutters on this Wall");
                final JRadioButton rb3 = new JRadioButton("All Window Shutters of this Building");
                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(window.getShutterLength() + "");
                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(), "Shutter Length (Relative)");
                while (true) {
                    dialog.setVisible(true);
                    inputField.selectAll();
                    inputField.requestFocusInWindow();
                    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());
                        } catch (final NumberFormatException exception) {
                            JOptionPane.showMessageDialog(MainFrame.getInstance(), inputField.getText() + " is an invalid value!", "Error", JOptionPane.ERROR_MESSAGE);
                            ok = false;
                        }
                        if (ok) {
                            if (val <= 0 || val > 1) {
                                JOptionPane.showMessageDialog(MainFrame.getInstance(), "Relative shutter length must be within (0, 1).", "Error", JOptionPane.ERROR_MESSAGE);
                            } else {
                                boolean changed = Math.abs(val - window.getShutterLength()) > 0.000001;
                                if (rb1.isSelected()) {
                                    if (changed) {
                                        final ChangeShutterLengthCommand c = new ChangeShutterLengthCommand(window);
                                        window.setShutterLength(val);
                                        window.draw();
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 0;
                                } else if (rb2.isSelected()) {
                                    if (!changed) {
                                        if (window.getContainer() instanceof Wall) {
                                            final Wall wall = (Wall) window.getContainer();
                                            for (final Window x : wall.getWindows()) {
                                                if (Math.abs(val - x.getShutterLength()) > 0.000001) {
                                                    changed = true;
                                                    break;
                                                }
                                            }
                                        } else if (window.getContainer() instanceof Roof) {
                                            final Roof roof = (Roof) window.getContainer();
                                            for (final Window x : roof.getWindows()) {
                                                if (Math.abs(val - x.getShutterLength()) > 0.000001) {
                                                    changed = true;
                                                    break;
                                                }
                                            }
                                        }
                                    }
                                    if (changed) {
                                        Scene.getInstance().setShutterLengthInContainer(window.getContainer(), val);
                                    }
                                    selectedScopeIndex = 1;
                                } else if (rb3.isSelected()) {
                                    final Foundation foundation = window.getTopContainer();
                                    if (!changed) {
                                        for (final Window x : foundation.getWindows()) {
                                            if (Math.abs(val - x.getShutterLength()) > 0.000001) {
                                                changed = true;
                                                break;
                                            }
                                        }
                                    }
                                    if (changed) {
                                        Scene.getInstance().setShutterLengthOfBuilding(window, val);
                                    }
                                    selectedScopeIndex = 2;
                                }
                                if (changed) {
                                    SceneManager.getInstance().refresh();
                                    Scene.getInstance().setEdited(true);
                                }
                                if (choice == options[0]) {
                                    break;
                                }
                            }
                        }
                    }
                }
            }
        });
        shutterMenu.add(miShutterLength);
        shutterMenu.addMenuListener(new MenuListener() {

            @Override
            public void menuSelected(final MenuEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (selectedPart instanceof Window) {
                    final Window window = (Window) selectedPart;
                    Util.selectSilently(cbmiLeftShutter, window.getLeftShutter());
                    Util.selectSilently(cbmiRightShutter, window.getRightShutter());
                    Util.selectSilently(cbmiBothShutters, window.getLeftShutter() && window.getRightShutter());
                }
            }

            @Override
            public void menuDeselected(final MenuEvent e) {
                shutterMenu.setEnabled(true);
            }

            @Override
            public void menuCanceled(final MenuEvent e) {
                shutterMenu.setEnabled(true);
            }
        });
        final JMenuItem miShgc = new JMenuItem("Solar Heat Gain Coefficient...");
        miShgc.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 Window)) {
                    return;
                }
                final String partInfo = selectedPart.toString().substring(0, selectedPart.toString().indexOf(')') + 1);
                final Window window = (Window) selectedPart;
                final String title = "<html>Solar Heat Gain Coefficient of " + partInfo + "</html>";
                final String footnote = "<html><hr><font size=2>Examples:<br><table><tr><td><font size=2>Single glass (clear)</td><td><font size=2>0.66</td></tr><tr><td><font size=2>Single glass (green tint)</td><td><font size=2>0.55</td></tr><tr><td><font size=2>Triple glass (air spaces)</td><td><font size=2>0.39</td></tr></table><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 Window", true);
                final JRadioButton rb2 = new JRadioButton("All Windows on this " + (selectedPart.getContainer() instanceof Wall ? "Wall" : "Roof"));
                final JRadioButton rb3 = new JRadioButton("All Windows of this Building");
                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(window.getSolarHeatGainCoefficient() + "");
                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 Heat Gain Coefficient (SHGC)");
                while (true) {
                    dialog.setVisible(true);
                    inputField.selectAll();
                    inputField.requestFocusInWindow();
                    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());
                        } catch (final NumberFormatException exception) {
                            JOptionPane.showMessageDialog(MainFrame.getInstance(), inputField.getText() + " is an invalid value!", "Error", JOptionPane.ERROR_MESSAGE);
                            ok = false;
                        }
                        if (ok) {
                            if (val < 0 || val > 1) {
                                JOptionPane.showMessageDialog(MainFrame.getInstance(), "Solar heat gain coefficient must be between 0 and 1.", "Range Error", JOptionPane.ERROR_MESSAGE);
                            } else {
                                boolean changed = Math.abs(val - window.getSolarHeatGainCoefficient()) > 0.000001;
                                if (rb1.isSelected()) {
                                    if (changed) {
                                        final ChangeWindowShgcCommand c = new ChangeWindowShgcCommand(window);
                                        window.setSolarHeatGainCoefficient(val);
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 0;
                                } else if (rb2.isSelected()) {
                                    if (!changed) {
                                        if (window.getContainer() instanceof Wall) {
                                            final Wall wall = (Wall) window.getContainer();
                                            for (final Window x : wall.getWindows()) {
                                                if (Math.abs(val - x.getSolarHeatGainCoefficient()) > 0.000001) {
                                                    changed = true;
                                                    break;
                                                }
                                            }
                                        } else if (window.getContainer() instanceof Roof) {
                                            final Roof roof = (Roof) window.getContainer();
                                            for (final Window x : roof.getWindows()) {
                                                if (Math.abs(val - x.getSolarHeatGainCoefficient()) > 0.000001) {
                                                    changed = true;
                                                    break;
                                                }
                                            }
                                        }
                                    }
                                    if (changed) {
                                        final ChangeContainerWindowShgcCommand c = new ChangeContainerWindowShgcCommand(window.getContainer());
                                        Scene.getInstance().setWindowShgcInContainer(window.getContainer(), val);
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 1;
                                } else if (rb3.isSelected()) {
                                    final Foundation foundation = window.getTopContainer();
                                    if (!changed) {
                                        for (final Window x : foundation.getWindows()) {
                                            if (Math.abs(val - x.getSolarHeatGainCoefficient()) > 0.000001) {
                                                changed = true;
                                                break;
                                            }
                                        }
                                    }
                                    if (changed) {
                                        final ChangeBuildingWindowShgcCommand c = new ChangeBuildingWindowShgcCommand(foundation);
                                        Scene.getInstance().setWindowShgcOfBuilding(foundation, val);
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 2;
                                }
                                if (changed) {
                                    updateAfterEdit();
                                }
                                if (choice == options[0]) {
                                    break;
                                }
                            }
                        }
                    }
                }
            }
        });
        final JMenuItem miTint = new JMenuItem("Tint...");
        miTint.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 Window)) {
                    return;
                }
                final Window window = (Window) selectedPart;
                final JColorChooser colorChooser = MainFrame.getInstance().getColorChooser();
                final ReadOnlyColorRGBA color = window.getColor();
                if (color != null) {
                    colorChooser.setColor(new Color(color.getRed(), color.getGreen(), color.getBlue()));
                }
                final ActionListener actionListener = new ActionListener() {

                    @Override
                    public void actionPerformed(final ActionEvent e) {
                        final Color c = colorChooser.getColor();
                        final float[] newColor = c.getComponents(null);
                        final ColorRGBA color = new ColorRGBA(newColor[0], newColor[1], newColor[2], (float) (1.0 - window.getSolarHeatGainCoefficient()));
                        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 Window", true);
                        final JRadioButton rb2 = new JRadioButton("All Windows on this " + (window.getContainer() instanceof Wall ? "Wall" : "Roof"));
                        final JRadioButton rb3 = new JRadioButton("All Windows of this Building");
                        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 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(), "Window Tint");
                        while (true) {
                            dialog.setVisible(true);
                            final Object choice = optionPane.getValue();
                            if (choice == options[1] || choice == null) {
                                break;
                            } else {
                                boolean changed = !Util.isRGBEqual(color, window.getColor());
                                if (rb1.isSelected()) {
                                    // apply to only this window
                                    if (changed) {
                                        final ChangePartColorCommand cmd = new ChangePartColorCommand(window);
                                        window.setColor(color);
                                        SceneManager.getInstance().getUndoManager().addEdit(cmd);
                                    }
                                    selectedScopeIndex = 0;
                                } else if (rb2.isSelected()) {
                                    if (!changed) {
                                        if (window.getContainer() instanceof Wall) {
                                            final Wall wall = (Wall) window.getContainer();
                                            for (final Window x : wall.getWindows()) {
                                                if (!Util.isRGBEqual(color, x.getColor())) {
                                                    changed = true;
                                                    break;
                                                }
                                            }
                                        } else if (window.getContainer() instanceof Roof) {
                                            final Roof roof = (Roof) window.getContainer();
                                            for (final Window x : roof.getWindows()) {
                                                if (!Util.isRGBEqual(color, x.getColor())) {
                                                    changed = true;
                                                    break;
                                                }
                                            }
                                        }
                                    }
                                    if (changed) {
                                        final ChangeContainerWindowColorCommand cmd = new ChangeContainerWindowColorCommand(window.getContainer());
                                        Scene.getInstance().setWindowColorInContainer(window.getContainer(), color, false);
                                        SceneManager.getInstance().getUndoManager().addEdit(cmd);
                                    }
                                    selectedScopeIndex = 1;
                                } else if (rb3.isSelected()) {
                                    final Foundation foundation = window.getTopContainer();
                                    if (!changed) {
                                        for (final Window x : foundation.getWindows()) {
                                            if (!Util.isRGBEqual(color, x.getColor())) {
                                                changed = true;
                                                break;
                                            }
                                        }
                                    }
                                    if (changed) {
                                        final ChangeBuildingColorCommand cmd = new ChangeBuildingColorCommand(window);
                                        Scene.getInstance().setPartColorOfBuilding(window, color);
                                        SceneManager.getInstance().getUndoManager().addEdit(cmd);
                                    }
                                    selectedScopeIndex = 2;
                                }
                                if (changed) {
                                    updateAfterEdit();
                                }
                                if (choice == options[0]) {
                                    break;
                                }
                            }
                        }
                    }
                };
                JColorChooser.createDialog(MainFrame.getInstance(), "Select Tint", true, colorChooser, actionListener, null).setVisible(true);
            }
        });
        final JMenuItem miSize = new JMenuItem("Size...");
        miSize.addActionListener(new ActionListener() {

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

            @Override
            public void actionPerformed(final ActionEvent e) {
                final HousePart selectedPart = SceneManager.getInstance().getSelectedPart();
                if (!(selectedPart instanceof Window)) {
                    return;
                }
                final Window window = (Window) selectedPart;
                final HousePart container = window.getContainer();
                final Foundation foundation = window.getTopContainer();
                final String partInfo = window.toString().substring(0, selectedPart.toString().indexOf(')') + 1);
                final JPanel gui = new JPanel(new BorderLayout());
                final JPanel inputPanel = new JPanel(new GridLayout(2, 2, 5, 5));
                gui.add(inputPanel, BorderLayout.CENTER);
                inputPanel.add(new JLabel("Width (m): "));
                final JTextField widthField = new JTextField(threeDecimalsFormat.format(window.getWindowWidth()));
                inputPanel.add(widthField);
                inputPanel.add(new JLabel("Height (m): "));
                final JTextField heightField = new JTextField(threeDecimalsFormat.format(window.getWindowHeight()));
                inputPanel.add(heightField);
                inputPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
                final JPanel scopePanel = new JPanel();
                scopePanel.setLayout(new BoxLayout(scopePanel, BoxLayout.Y_AXIS));
                scopePanel.setBorder(BorderFactory.createTitledBorder("Apply to:"));
                final JRadioButton rb1 = new JRadioButton("Only this Window", true);
                final JRadioButton rb2 = new JRadioButton("All Windows on this " + (window.getContainer() instanceof Wall ? "Wall" : "Roof"));
                final JRadioButton rb3 = new JRadioButton("All Windows of this Building");
                scopePanel.add(rb1);
                scopePanel.add(rb2);
                scopePanel.add(rb3);
                final ButtonGroup bg = new ButtonGroup();
                bg.add(rb1);
                bg.add(rb2);
                bg.add(rb3);
                switch(selectedScopeIndex) {
                    case 0:
                        rb1.setSelected(true);
                        break;
                    case 1:
                        rb2.setSelected(true);
                        break;
                    case 2:
                        rb3.setSelected(true);
                        break;
                }
                gui.add(scopePanel, BorderLayout.NORTH);
                final Object[] options = new Object[] { "OK", "Cancel", "Apply" };
                final JOptionPane optionPane = new JOptionPane(new Object[] { "Set Size for " + partInfo, gui }, JOptionPane.QUESTION_MESSAGE, JOptionPane.YES_NO_CANCEL_OPTION, null, options, options[2]);
                final JDialog dialog = optionPane.createDialog(MainFrame.getInstance(), "Window Size");
                while (true) {
                    dialog.setVisible(true);
                    final Object choice = optionPane.getValue();
                    if (choice == options[1] || choice == null) {
                        break;
                    } else {
                        boolean ok = true;
                        double w = 0, h = 0;
                        try {
                            w = Double.parseDouble(widthField.getText());
                            h = Double.parseDouble(heightField.getText());
                        } catch (final NumberFormatException x) {
                            JOptionPane.showMessageDialog(MainFrame.getInstance(), "Invalid input!", "Error", JOptionPane.ERROR_MESSAGE);
                            ok = false;
                        }
                        if (ok) {
                            double wmax = 10;
                            if (container instanceof Wall) {
                                wmax = ((Wall) container).getWallWidth() * 0.99;
                            }
                            double hmax = 10;
                            if (container instanceof Wall) {
                                hmax = ((Wall) container).getWallHeight() * 0.99;
                            }
                            if (w < 0.1 || w > wmax) {
                                JOptionPane.showMessageDialog(MainFrame.getInstance(), "Width must be between 0.1 and " + (int) wmax + " m.", "Range Error", JOptionPane.ERROR_MESSAGE);
                            } else if (h < 0.1 || h > hmax) {
                                JOptionPane.showMessageDialog(MainFrame.getInstance(), "Height must be between 0.1 and " + (int) hmax + " m.", "Range Error", JOptionPane.ERROR_MESSAGE);
                            } else {
                                boolean changed = Math.abs(w - window.getWindowWidth()) > 0.000001 || Math.abs(h - window.getWindowHeight()) > 0.000001;
                                if (rb1.isSelected()) {
                                    if (changed) {
                                        final SetPartSizeCommand c = new SetPartSizeCommand(window);
                                        window.setWindowWidth(w);
                                        window.setWindowHeight(h);
                                        window.draw();
                                        window.getContainer().draw();
                                        SceneManager.getInstance().refresh();
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 0;
                                } else if (rb2.isSelected()) {
                                    if (!changed) {
                                        if (window.getContainer() instanceof Wall) {
                                            final Wall wall = (Wall) window.getContainer();
                                            for (final Window x : wall.getWindows()) {
                                                if (Math.abs(w - x.getWindowWidth()) > 0.000001 || Math.abs(h - x.getWindowHeight()) > 0.000001) {
                                                    changed = true;
                                                    break;
                                                }
                                            }
                                        } else if (window.getContainer() instanceof Roof) {
                                            final Roof roof = (Roof) window.getContainer();
                                            for (final Window x : roof.getWindows()) {
                                                if (Math.abs(w - x.getWindowWidth()) > 0.000001 || Math.abs(h - x.getWindowHeight()) > 0.000001) {
                                                    changed = true;
                                                    break;
                                                }
                                            }
                                        }
                                    }
                                    if (changed) {
                                        final ChangeContainerWindowSizeCommand c = new ChangeContainerWindowSizeCommand(window.getContainer());
                                        Scene.getInstance().setWindowSizeInContainer(window.getContainer(), w, h);
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 1;
                                } else if (rb3.isSelected()) {
                                    if (!changed) {
                                        for (final Window x : foundation.getWindows()) {
                                            if (Math.abs(w - x.getWindowWidth()) > 0.000001 || Math.abs(h - x.getWindowHeight()) > 0.000001) {
                                                changed = true;
                                                break;
                                            }
                                        }
                                    }
                                    if (changed) {
                                        final SetSizeForWindowsOnFoundationCommand c = new SetSizeForWindowsOnFoundationCommand(foundation);
                                        foundation.setSizeForWindows(w, h);
                                        SceneManager.getInstance().getUndoManager().addEdit(c);
                                    }
                                    selectedScopeIndex = 2;
                                }
                                if (changed) {
                                    updateAfterEdit();
                                }
                                if (choice == options[0]) {
                                    break;
                                }
                            }
                        }
                    }
                }
            }
        });
        popupMenuForWindow.addSeparator();
        popupMenuForWindow.add(miSize);
        popupMenuForWindow.add(miTint);
        popupMenuForWindow.add(createInsulationMenuItem(true));
        popupMenuForWindow.add(miShgc);
        popupMenuForWindow.add(muntinMenu);
        popupMenuForWindow.add(shutterMenu);
        popupMenuForWindow.addSeparator();
        JMenuItem mi = new JMenuItem("Daily Energy Analysis...");
        mi.addActionListener(new ActionListener() {

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

            @Override
            public void actionPerformed(final ActionEvent e) {
                if (EnergyPanel.getInstance().adjustCellSize()) {
                    return;
                }
                if (SceneManager.getInstance().getSelectedPart() instanceof Window) {
                    new EnergyAnnualAnalysis().show("Annual Energy for Window");
                }
            }
        });
        popupMenuForWindow.add(mi);
    }
    return popupMenuForWindow;
}
Also used : ReadOnlyColorRGBA(com.ardor3d.math.type.ReadOnlyColorRGBA) JPanel(javax.swing.JPanel) ChangeContainerShutterColorCommand(org.concord.energy3d.undo.ChangeContainerShutterColorCommand) ActionEvent(java.awt.event.ActionEvent) SetPartSizeCommand(org.concord.energy3d.undo.SetPartSizeCommand) ChangeBuildingWindowShgcCommand(org.concord.energy3d.undo.ChangeBuildingWindowShgcCommand) ChangeContainerWindowShgcCommand(org.concord.energy3d.undo.ChangeContainerWindowShgcCommand) BorderLayout(java.awt.BorderLayout) Foundation(org.concord.energy3d.model.Foundation) HousePart(org.concord.energy3d.model.HousePart) SetSizeForWindowsOnFoundationCommand(org.concord.energy3d.undo.SetSizeForWindowsOnFoundationCommand) Color(java.awt.Color) ChangeWindowShgcCommand(org.concord.energy3d.undo.ChangeWindowShgcCommand) ChangeWindowShuttersCommand(org.concord.energy3d.undo.ChangeWindowShuttersCommand) JRadioButtonMenuItem(javax.swing.JRadioButtonMenuItem) JOptionPane(javax.swing.JOptionPane) JCheckBoxMenuItem(javax.swing.JCheckBoxMenuItem) ActionListener(java.awt.event.ActionListener) ChangeBuildingColorCommand(org.concord.energy3d.undo.ChangeBuildingColorCommand) JColorChooser(javax.swing.JColorChooser) JDialog(javax.swing.JDialog) EnergyAnnualAnalysis(org.concord.energy3d.simulation.EnergyAnnualAnalysis) ItemEvent(java.awt.event.ItemEvent) Wall(org.concord.energy3d.model.Wall) JRadioButton(javax.swing.JRadioButton) MenuListener(javax.swing.event.MenuListener) BoxLayout(javax.swing.BoxLayout) JTextField(javax.swing.JTextField) Roof(org.concord.energy3d.model.Roof) ChangeContainerWindowColorCommand(org.concord.energy3d.undo.ChangeContainerWindowColorCommand) GridLayout(java.awt.GridLayout) ChangePartColorCommand(org.concord.energy3d.undo.ChangePartColorCommand) JMenuItem(javax.swing.JMenuItem) MenuEvent(javax.swing.event.MenuEvent) Window(org.concord.energy3d.model.Window) ChangeShutterColorCommand(org.concord.energy3d.undo.ChangeShutterColorCommand) JLabel(javax.swing.JLabel) ChangeShutterLengthCommand(org.concord.energy3d.undo.ChangeShutterLengthCommand) ReadOnlyColorRGBA(com.ardor3d.math.type.ReadOnlyColorRGBA) ColorRGBA(com.ardor3d.math.ColorRGBA) ButtonGroup(javax.swing.ButtonGroup) EnergyDailyAnalysis(org.concord.energy3d.simulation.EnergyDailyAnalysis) ItemListener(java.awt.event.ItemListener) ChangeBuildingShutterColorCommand(org.concord.energy3d.undo.ChangeBuildingShutterColorCommand) ChangeContainerWindowSizeCommand(org.concord.energy3d.undo.ChangeContainerWindowSizeCommand) JMenu(javax.swing.JMenu)

Aggregations

Foundation (org.concord.energy3d.model.Foundation)174 HousePart (org.concord.energy3d.model.HousePart)153 Rack (org.concord.energy3d.model.Rack)47 SolarPanel (org.concord.energy3d.model.SolarPanel)45 Window (org.concord.energy3d.model.Window)39 ActionEvent (java.awt.event.ActionEvent)38 ActionListener (java.awt.event.ActionListener)38 Roof (org.concord.energy3d.model.Roof)38 Wall (org.concord.energy3d.model.Wall)37 ArrayList (java.util.ArrayList)35 JDialog (javax.swing.JDialog)35 JMenuItem (javax.swing.JMenuItem)33 Mirror (org.concord.energy3d.model.Mirror)32 FresnelReflector (org.concord.energy3d.model.FresnelReflector)27 Door (org.concord.energy3d.model.Door)24 ParabolicTrough (org.concord.energy3d.model.ParabolicTrough)24 Tree (org.concord.energy3d.model.Tree)24 Vector3 (com.ardor3d.math.Vector3)22 ReadOnlyVector3 (com.ardor3d.math.type.ReadOnlyVector3)22 ParabolicDish (org.concord.energy3d.model.ParabolicDish)22