Re: Generate roof plugin

by byDMA » Wed Jun 19, 2024 6:33 pm

Hi Dorin, thank you, the grids are very well.

try this and SEND me privat the compiled plugin for test.
The width of the edge panel we create with list width, not spaces.

Code: Select all

package generateroof;

import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Event;
import java.awt.GridBagConstraints;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.HashSet;
import java.util.ResourceBundle;
import java.util.Set;

import javax.swing.AbstractAction;
import javax.swing.AbstractButton;
import javax.swing.ButtonGroup;
import javax.swing.DefaultListCellRenderer;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JScrollPane;
import javax.swing.JSlider;
import javax.swing.JSpinner;
import javax.swing.JTextField;
import javax.swing.ListSelectionModel;
import javax.swing.SpinnerNumberModel;
import javax.swing.SwingUtilities;
import javax.swing.border.TitledBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

import com.eteks.sweethome3d.model.HomeMaterial;
import com.eteks.sweethome3d.model.UserPreferences;
import com.eteks.sweethome3d.swing.AutoCommitSpinner;
import com.eteks.sweethome3d.swing.ModelPreviewComponent;
import com.eteks.sweethome3d.swing.NullableCheckBox;
import com.eteks.sweethome3d.swing.NullableSpinner;
import com.eteks.sweethome3d.swing.SwingTools;
import com.eteks.sweethome3d.swing.NullableSpinner.NullableSpinnerLengthModel;
import com.eteks.sweethome3d.tools.OperatingSystem;
import com.eteks.sweethome3d.viewcontroller.HomeView;

public class GenrateRoofPluginView {
	private final GenerateRoofPlugin plugin;

	public GenrateRoofPluginView(GenerateRoofPlugin generateRoofPlugin) {
		this.plugin = generateRoofPlugin;
	}

	float resolutionScale = SwingTools.getResolutionScale();
	ModelPreviewComponent roofPreview;
	ChangeListener commonListener;
	ListSelectionListener edgesListener;
	NullableCheckBox invisibilityCheck, showHome, showTexture, reverseFaces;
	JSlider faceAngleSlider;
	JRadioButton verticalBorder, orthoBorder;
	ImageIcon vertIcon, vertSelIcon, orthoIcon, orthoSelIcon;
	ButtonGroup borderGroupBtn;
	JButton resetButton, aboutButton;
	NullableSpinnerLengthModel thickModel, roofElevationModel;
	NullableSpinner thick, roofElevation;
	JSpinner fineSlope;
	JList<SlopedEdge> edgesList;
	JScrollPane scrollEdgesPane;
	JTextField creator;
	JLabel edgesLabel, invisibilityLabel;
	JPanel invisibilityPane, slopePane, bordersPane, finePane, thickPane, elevationPane, creatorPane;

	/**
	 * Creates and layout the components shown in the dialog box of this plug-in.
	 * 
	 * @author Daniels118
	 * 
	 * @param homeView
	 * @param resource
	 */
	protected void createComponents(final HomeView homeView, final ResourceBundle resource) {
		UserPreferences preferences = plugin.getUserPreferences();
		String unitName = preferences.getLengthUnit().getName();

		// Create a common change listener for some components.
		commonListener = new ChangeListener() {
			@Override
			public void stateChanged(ChangeEvent ev) {
				if (ev.getSource().equals(showHome)) {
					if (showHome.getValue()) {
						plugin.homeItems.removeAllChildren();
						plugin.homeItems = plugin.createHomeNode(plugin.getHome());
					}
					plugin.refreshRoof(homeView, resource);

				} else if (ev.getSource().equals(faceAngleSlider)) {
					int slope = faceAngleSlider.getValue();
					int maxAngle = faceAngleSlider.getMaximum() - 1;
					// Limit the slope in range 1 to 179 degrees
					slope = slope > 1 ? slope : 1;
					slope = slope < maxAngle ? slope : maxAngle;
					// Update the angle for the selected faces
					for (SlopedEdge edge : edgesList.getSelectedValuesList())
						edge.setSlope(slope);
					fineSlope.removeChangeListener(commonListener);
					fineSlope.setValue(0.0);
					fineSlope.addChangeListener(commonListener);
					if (!faceAngleSlider.getValueIsAdjusting() && faceAngleSlider.hasFocus()) {
						edgesList.updateUI();
						plugin.updateSelectedFacesFromList(homeView, resource);
					}
				} else if (ev.getSource().equals(fineSlope)) {
					// Add the fine tuning to edge slope
					float fineSlopeVal = ((Number) fineSlope.getValue()).floatValue();
					float angle = faceAngleSlider.getValue();
					float maxAngle = faceAngleSlider.getMaximum() - 1;
					float slope = angle + fineSlopeVal;
					slope = slope > 1f ? slope : 1.0f;
					slope = slope < maxAngle ? slope : maxAngle;
					// Update the angle for the selected faces
					for (SlopedEdge edge : edgesList.getSelectedValuesList())
						edge.setSlope(slope);
					edgesList.updateUI();
					plugin.updateSelectedFacesFromList(homeView, resource);
				} else if (ev.getSource().equals(invisibilityCheck)) {
					if (invisibilityCheck.getValue() != null) {
						boolean visible = !invisibilityCheck.getValue();
						// Update the face visibility
						for (SlopedEdge edge : edgesList.getSelectedValuesList())
							edge.setFaceVisible(visible);
						edgesList.updateUI();
						plugin.updateSelectedFacesFromList(homeView, resource);
					}
				} else {
					plugin.refreshRoof(homeView, resource);
				}
			}
		};

		// Create edges list selection listener.
		edgesListener = new ListSelectionListener() {
			@Override
			public void valueChanged(ListSelectionEvent ev) {
				int edgeSelection = edgesList.getSelectedIndices().length;
				// Limit selection to have at least one face with slope < 90 deg
				int maxAngle = edgeSelection < plugin.allRoofEdges.length ? 180 : 76;
				if (faceAngleSlider.getMaximum() != maxAngle)
				   faceAngleSlider.setMaximum(maxAngle);
				SlopedEdge edge = edgesList.getSelectedValue();
				if (edge != null) {
					// Set angle
					faceAngleSlider.removeChangeListener(commonListener);
					faceAngleSlider.setValue((int) edge.getSlope());
					faceAngleSlider.addChangeListener(commonListener);
					// Set fine slope
					fineSlope.removeChangeListener(commonListener);
					fineSlope.setValue(edge.getSlope() - faceAngleSlider.getValue());
					fineSlope.addChangeListener(commonListener);
				}
				if (!ev.getValueIsAdjusting() && edgesList.hasFocus())
				   plugin.updateSelectedFacesFromList(homeView, resource);
			}
		};
		// Create preview
		roofPreview = new ModelPreviewComponent(true, true, true, true);
		roofPreview.setFocusable(false);
		roofPreview.setPreferredSize(new Dimension((int) (800 * resolutionScale), (int) (600 * resolutionScale)));// @byDMA resizing window to 800x600
		// Add a mouse listener to get selected faces
		roofPreview.addMouseListener(new MouseAdapter() {
			private Set<SlopedEdge> selectedEdges = new HashSet<SlopedEdge>();// Using a set avoids duplicates

			@Override
			public void mouseClicked(MouseEvent ev) {
				HomeMaterial pickedMaterial = roofPreview.getPickedMaterial();
				boolean sInterval = false;
				if (pickedMaterial != null) {
					String faceName = pickedMaterial.getName();
					for (RoofFace face : plugin.faces) {
						if (faceName.equals(face.getFaceName())) {
							if ((ev.getModifiers() & Event.CTRL_MASK) != 0 || (ev.getModifiers() & Event.SHIFT_MASK) != 0) {
								for (SlopedEdge sEdge : face.getGeneratingEdges()) {
									if (!selectedEdges.add(sEdge))
									   selectedEdges.remove(sEdge);// Toggle selection
								}
								if ((ev.getModifiers() & Event.SHIFT_MASK) != 0)
								   sInterval = true;
							} else {
								selectedEdges.clear();
								for (SlopedEdge sEdge : face.getGeneratingEdges())
									selectedEdges.add(sEdge);
							}
							break;// There can be a single face matching the picked material
						}
					}
					int[] edgeIndices = new int[selectedEdges.size()];
					int i = 0;
					for (SlopedEdge sEdge : selectedEdges)
						edgeIndices[i++] = sEdge.getEdge();
					edgesList.setSelectedIndices(edgeIndices);
					int sMin = edgesList.getMinSelectionIndex();
					int sMax = edgesList.getMaxSelectionIndex();
					if (sInterval)
					   edgesList.addSelectionInterval(sMin, sMax);
					if (i > 0)
					   edgesList.ensureIndexIsVisible(edgeIndices[i - 1]);
				} else if ((ev.getModifiers() & Event.CTRL_MASK) == 0) {
					edgesList.setSelectedIndices(new int[0]);// Deselect when click the background
				}
				edgesList.updateUI();
				plugin.updateSelectedFacesFromList(homeView, resource);
			}
		});

		// Create edges list
//		String edgesLabel = resource.getString("edges.txt");// Edges: Slope
		edgesList = new JList<SlopedEdge>();
		edgesList.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
		edgesList.setListData(plugin.allRoofEdges);// Add edges to the list
		edgesList.setCellRenderer(new EdgesCellRenderer());
		edgesList.addListSelectionListener(edgesListener);
		edgesList.setFixedCellWidth(140);// byDMA resize width of the List in the JScrollPane
		// Put edges list into scroll panel
		String edgesLab = resource.getString("edges.txt");// Edges: Slope
		edgesLabel = new JLabel(edgesLab);// Edges: Slope
//		scrollEdges = SwingTools.createScrollPane(edgesList);
		scrollEdgesPane = new JScrollPane(edgesList);
		if (OperatingSystem.isMacOSX()) {
			Dimension preferredSize = scrollEdgesPane.getPreferredSize();
			scrollEdgesPane.setPreferredSize(new Dimension(Math.min(200, preferredSize.width), preferredSize.height));
		} else {
			
			scrollEdgesPane.setBorder(new TitledBorder(edgesLabel.getText()));
		}

		// Create slope slider
		String slopeSliderLabel = resource.getString("slope.txt");// Slope
		faceAngleSlider = new JSlider(JSlider.VERTICAL, 0, 180, 40);
		faceAngleSlider.setPaintTicks(true);
		faceAngleSlider.setPaintLabels(true);
		faceAngleSlider.setMajorTickSpacing(30);
		faceAngleSlider.setMinorTickSpacing(5);
		faceAngleSlider.setBorder(new TitledBorder(slopeSliderLabel));
		faceAngleSlider.addChangeListener(commonListener);

		// Create fine slope angle spinner
		SpinnerNumberModel fineSlopeModel = new SpinnerNumberModel(.0, -0.9, 0.9, .1);
		fineSlope = new AutoCommitSpinner(fineSlopeModel);
		fineSlope.addChangeListener(commonListener);
		// Fine angle panel
		String fineSlopeShort = resource.getString("fineSlope.txt");// Fine slope
		finePane = SwingTools.createTitledPanel(fineSlopeShort);
		finePane.add(fineSlope);

		// Create face invisibility check box
		String invisibility = resource.getString("invisibility.txt");// Invisible
		invisibilityCheck = new NullableCheckBox(invisibility);
		invisibilityCheck.setNullable(false);
		invisibilityCheck.setValue(false);
		invisibilityCheck.addChangeListener(commonListener);
		// Invisibility check panel
		String faceVisible = resource.getString("face.txt");// Face
		invisibilityLabel = new JLabel(faceVisible);
		if (OperatingSystem.isMacOSX()) {
			invisibilityPane = SwingTools.createTitledPanel(null);
		} else {
			invisibilityPane = SwingTools.createTitledPanel(invisibilityLabel.getText());
		}
		invisibilityPane.add(invisibilityCheck);

		// Create roof border thickness spinner
		String thickLong = String.format(resource.getString("thickness.txt"), unitName);// Roof thickness
		thickModel = new NullableSpinnerLengthModel(preferences, 7.6f, 2.5f, 30.5f);
		thick = new NullableSpinner(thickModel);
		thick.setToolTipText(thickLong);
		// Thickness panel
		String thickShort = String.format(resource.getString("thick.txt"), unitName);// Thick
		thickPane = SwingTools.createTitledPanel(thickShort);
		thickPane.add(thick);
		if (plugin.isRoofSelected()) {
			String sThickness = plugin.currentRoof.getProperty(GenerateRoofPlugin.ROOF_THICKNESS);
			if (sThickness != null) {
				float currentThick = Float.valueOf(sThickness);
				try {
					thickModel.setLength(currentThick);
				} catch (Exception e) {
					/**
					 * @author Daniels118 No need to translate this message, only "hackers" have
					 *         chances to see it
					 */
					plugin.getHomeController().getView().showError("Cannot retrieve thickModel: " + e.getMessage());
				}
			}
		}
		thickModel.addChangeListener(commonListener);

		// Create roof elevation spinner
		String elevationLong = String.format(resource.getString("roofelevation.txt"), unitName);// Elevate roof at needed position
		roofElevationModel = new NullableSpinnerLengthModel(preferences, 0.0f, 0.0f, 9999f);
		roofElevation = new NullableSpinner(roofElevationModel);
		roofElevation.setToolTipText(elevationLong);
		roofElevationModel.setLength(plugin.getRoofElevation(plugin.currentLevel));
		roofElevationModel.addChangeListener(commonListener);
		// Elevation panel
		String elevationShort = String.format(resource.getString("elevation.txt"), unitName);// Elevation(%s)
		elevationPane = SwingTools.createTitledPanel(elevationShort);
		elevationPane.add(roofElevation);

		// Create home visibility check box
		String displayHome = resource.getString("displayHomeCheckBox.txt");// Show home
		showHome = new NullableCheckBox(displayHome);
		showHome.setValue(false);
		showHome.addChangeListener(commonListener);

		// Create texture visibility check box
		String useTexture = resource.getString("displayTextureCheckBox.txt");// Use roof texture
		showTexture = new NullableCheckBox(useTexture);
		showTexture.setValue(false);
		showTexture.setEnabled(plugin.isRoofSelected());
		showTexture.addChangeListener(commonListener);

		// Create reverse faces check box
		String reverseFacesTxt = resource.getString("reverseFaces.txt");// Reverse faces
		reverseFaces = new NullableCheckBox(reverseFacesTxt);
		reverseFaces.setValue(false);
		reverseFaces.addChangeListener(commonListener);

		// Create creator field
		String author = System.getProperty("user.name");
		if (plugin.isRoofSelected())
		   author = plugin.currentRoof.getCreator();
		creator = new JTextField(author);
		creator.setEditable(true);
		// @byDMA 2. store default height for TextField creator
		int defaultHeight = creator.getSize().getHeight();
		// @byDMA 3. set custom width and default height
		creator.setSize(new Dimension(250, defaultHeight));

		// Creator panel
		String authorText = resource.getString("authorText.txt");// Creator
		creatorPane = SwingTools.createTitledPanel(authorText);
		creatorPane.add(creator);

		// Create borders radio buttons
		vertIcon = new ImageIcon(GenerateRoofPlugin.class.getResource("/generateroof/resources/vert.png"));
		vertSelIcon = new ImageIcon(GenerateRoofPlugin.class.getResource("/generateroof/resources/vertSel.png"));
		orthoIcon = new ImageIcon(GenerateRoofPlugin.class.getResource("/generateroof/resources/ortho.png"));
		orthoSelIcon = new ImageIcon(GenerateRoofPlugin.class.getResource("/generateroof/resources/orthoSel.png"));
		// Vertical border button
		verticalBorder = new JRadioButton(vertIcon, true);// default selected
		verticalBorder.setActionCommand("vertical");
		verticalBorder.setSelectedIcon(vertSelIcon);
		// Orthogonal border button
		orthoBorder = new JRadioButton(orthoIcon);
		orthoBorder.setActionCommand("orthoUp");
		orthoBorder.setSelectedIcon(orthoSelIcon);
		// Group border direction buttons
		borderGroupBtn = new ButtonGroup();
		borderGroupBtn.add(verticalBorder);
		borderGroupBtn.add(orthoBorder);
		// Border orientation panel
		String borders = resource.getString("borderText.txt");// Border
		bordersPane = SwingTools.createTitledPanel(borders);
		bordersPane.add(verticalBorder, 0);
		bordersPane.add(orthoBorder, 1);
		// Set initial border
		if (plugin.isRoofSelected()) {
			String dirThick = plugin.getSelectedRoof().getProperty(GenerateRoofPlugin.ROOF_THICKNESS_DIRECTION);
			if (dirThick != null) {
				try {
					if (orthoBorder.getActionCommand().equals(dirThick))
					   borderGroupBtn.setSelected(orthoBorder.getModel(), true);
				} catch (Exception e) {
					/**
					 * @author Daniels118 No need to translate this message, only "hackers" have
					 *         chances to see it
					 */
					plugin.getHomeController().getView().showError("Cannot retrieve direction: " + e.getMessage());
				}
			}
		}
		// Create a listener for the border buttons.
		ActionListener borderActionListener = new ActionListener() {
			public void actionPerformed(ActionEvent actionEvent) {
				AbstractButton aButton = (AbstractButton) actionEvent.getSource();
				if (aButton.isSelected())
				   plugin.refreshRoof(homeView, resource);
			}
		};
		// Register a listener for the borders buttons.
		verticalBorder.addActionListener(borderActionListener);
		orthoBorder.addActionListener(borderActionListener);

		// Create reset button
		resetButton = new JButton(new AbstractAction(resource.getString("resetButton.txt")) {
			private static final long serialVersionUID = 1L;

			public void actionPerformed(ActionEvent ev) {
				faceAngleSlider.setValue(40);
				fineSlope.setValue(0.0);
				thickModel.setLength(7.6f);
				edgesList.setSelectedIndices(new int[0]);
				invisibilityCheck.setNullable(false);
				invisibilityCheck.setValue(false);
				invisibilityCheck.setValue(false);
				// Reset the angle for all faces to 40 deg
				int slope = faceAngleSlider.getValue();
				for (int i = 0; i < plugin.allRoofEdges.length; i++) {
					plugin.allRoofEdges[i].setSlope(slope);
					plugin.allRoofEdges[i].setFaceVisible(true);
				}
				// Reset the author
				String author = System.getProperty("user.name");
				creator.setText(author);
				// Reset the borders
				borderGroupBtn.setSelected(verticalBorder.getModel(), true);
				edgesList.updateUI();
				plugin.updateSelectedFacesFromList(homeView, resource);
			}
		});
		resetButton.setToolTipText(resource.getString("resetButtonInf.txt"));

		// About button
		aboutButton = new JButton(new AbstractAction(resource.getString("aboutButton.txt")) {
			private static final long serialVersionUID = 1L;

			@Override
			public void actionPerformed(ActionEvent ev) {
				HTMLViewer viewer = new HTMLViewer();
				viewer.addErrorListener(new HTMLViewer.ErrorListener() {
					@Override
					public void onError(Exception e) {
						e.printStackTrace();
						SwingUtilities.invokeLater(new Runnable() {
							@Override
							public void run() {
								try {
									viewer.setPage(GenerateRoofPlugin.class.getResource("/generateroof/resources/about.html"));
								} catch (IOException e1) {
									e1.printStackTrace();
								}
							}
						});
					}
				});
				try {
					String lang = resource.getLocale().getLanguage();
					String version = plugin.getVersion();
					String url = "https://daniels118.altervista.org/sh3d/roofgenerator/about/" + "?lang=" + lang + "&ver="
					      + URLEncoder.encode(version, "UTF8");
					viewer.setPage(url);
				} catch (IOException e) {
					e.printStackTrace();
				}
				JScrollPane scrollPane = new JScrollPane(viewer);
				JOptionPane helpPanel = new JOptionPane(scrollPane, JOptionPane.PLAIN_MESSAGE, JOptionPane.DEFAULT_OPTION);
				JDialog aboutDialog = helpPanel.createDialog((JComponent) homeView, resource.getString("ROOF_GEN.title"));
				aboutDialog.applyComponentOrientation(((JComponent) homeView).getComponentOrientation());
				aboutDialog.setLocationByPlatform(true);
				aboutDialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
				aboutDialog.setResizable(true);
				aboutDialog.setMinimumSize(new Dimension(600, 700));
				aboutDialog.setVisible(true);
			}
		});
		//aboutButton.setBackground(Color.green);  //standard button, not green
		aboutButton.setToolTipText(resource.getString("aboutButton_Warn.txt"));
	}

	/**
	 * Layout user interface
	 * @author Daniels118 & byDMA
	 */
	protected JPanel createUI() {
		JPanel roofPanel = SwingTools.createTitledPanel(null);
		roofPanel.setBorder(null);
		// Column 0~3 Row 0~9
		roofPanel.add(roofPreview,
				new GridBagConstraints(0, 0, 4, 9, 1, 1, GridBagConstraints.LINE_START, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
		// Column 4 Row 1
		if (OperatingSystem.isMacOSX()) {
			roofPanel.add(edgesLabel,
					new GridBagConstraints(4, 0, 2, 1, 0, 0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(0, 10, 0, 0), 0, 0));
		}
		// Column 4+5 Row 0~7
		roofPanel.add(scrollEdgesPane,
				new GridBagConstraints(4, 0, 2, 7, 0, 0, GridBagConstraints.NORTH, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
		// Column 4+5 Row 9
		roofPanel.add(resetButton,
				new GridBagConstraints(4, 9, 2, 1, 0, 0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0));
		if (OperatingSystem.isMacOSX()) {
			roofPanel.add(invisibilityLabel,
					new GridBagConstraints(5, 1, 1, 1, 0, 0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(0, 10, 0, 0), 0, 0));
		}
		// Column 6+7 Row 0
		roofPanel.add(invisibilityPane,
				new GridBagConstraints(6, 0, 2, 1, 0, 0, GridBagConstraints.NORTH, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0));
		// Column 6+7 Row 1~5
		roofPanel.add(faceAngleSlider,
				new GridBagConstraints(6, 1, 2, 5, 0, 1, GridBagConstraints.LINE_START, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
		// Column 6+7 Row 6
		roofPanel.add(finePane,
				new GridBagConstraints(6, 6, 2, 1, 0, 0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0));
		// Column 4+5 Row 7
		roofPanel.add(thickPane,
				new GridBagConstraints(4, 7, 2, 1, 0, 0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0));
		// Column 6+7 Row 7
		roofPanel.add(elevationPane,
				new GridBagConstraints(6, 7, 2, 1, 0, 0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0));
		// Column 6+7 Row 8
		roofPanel.add(creatorPane,
				new GridBagConstraints(6, 8, 2, 1, 0, 0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0));
		// Column 0 Row 9
		roofPanel.add(showHome,
				new GridBagConstraints(0, 9, 1, 1, 1, 0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0));
		// Column 2 Row 9
		roofPanel.add(showTexture,
				new GridBagConstraints(2, 9, 1, 1, 0, 0, GridBagConstraints.LINE_END, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0));
		// Column 3 Row 9
		roofPanel.add(reverseFaces,
				new GridBagConstraints(3, 9, 1, 1, 0, 0, GridBagConstraints.LINE_END, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0));		
		// Column 4+5 Row 8
		roofPanel.add(bordersPane,
				new GridBagConstraints(4, 8, 2, 1, 0, 0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0));	
		// Column 6+7 Row 9
		roofPanel.add(aboutButton,
				new GridBagConstraints(6, 9, 2, 1, 0, 0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0));
		
		// First refresh roof
		edgesList.updateUI();
		plugin.refreshRoof(plugin.homeView, plugin.resource);
		return roofPanel;
	}

	private class EdgesCellRenderer extends DefaultListCellRenderer {

		private static final long	serialVersionUID	= 1L;
		final ImageIcon				eyeCloseIcon		= new ImageIcon(GenerateRoofPlugin.class.getResource("/generateroof/resources/closed-eye.png"));
		final ImageIcon				emptyIcon			= new ImageIcon(GenerateRoofPlugin.class.getResource("/generateroof/resources/emptyIcon.png"));

		@Override
		public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
			String s = value.toString();
			setText(s);
			boolean visible = plugin.allRoofEdges[index].isFaceVisible();
			setIcon(!visible ? eyeCloseIcon : emptyIcon);

			if (isSelected) {
				setBackground(list.getSelectionBackground());
				setForeground(list.getSelectionForeground());
			} else {
				setBackground(list.getBackground());
				setForeground(list.getForeground());
			}

			setEnabled(list.isEnabled());
			setFont(list.getFont());
			setOpaque(true);
			return this;
		}

	}

}