/* * LevelPanel.java 27 oct 2011 * * Sweet Home 3D, Copyright (c) 2011 Emmanuel PUYBARET / eTeks <info@eteks.com> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package com.eteks.sweethome3d.swing; import java.awt.Component; import java.awt.Dimension; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import javax.swing.DefaultListSelectionModel; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JSeparator; import javax.swing.JSpinner; import javax.swing.JTable; import javax.swing.JTextField; import javax.swing.KeyStroke; import javax.swing.event.AncestorEvent; import javax.swing.event.AncestorListener; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import javax.swing.table.AbstractTableModel; import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableColumnModel; import com.eteks.sweethome3d.model.Level; import com.eteks.sweethome3d.model.UserPreferences; import com.eteks.sweethome3d.tools.OperatingSystem; import com.eteks.sweethome3d.viewcontroller.DialogView; import com.eteks.sweethome3d.viewcontroller.LevelController; import com.eteks.sweethome3d.viewcontroller.View; /** * Level editing panel. * @author Emmanuel Puybaret */ public class LevelPanel extends JPanel implements DialogView { private final LevelController controller; private JLabel nameLabel; private JTextField nameTextField; private JLabel elevationLabel; private JSpinner elevationSpinner; private JLabel floorThicknessLabel; private JSpinner floorThicknessSpinner; private JLabel heightLabel; private JSpinner heightSpinner; private JLabel levelsSummaryLabel; private JTable levelsSummaryTable; private String dialogTitle; /** * Creates a panel that displays home levels data according to the units * set in <code>preferences</code>. * @param preferences user preferences * @param controller the controller of this panel */ public LevelPanel(UserPreferences preferences, LevelController controller) { super(new GridBagLayout()); this.controller = controller; createComponents(preferences, controller); setMnemonics(preferences); layoutComponents(preferences, controller); } /** * Creates and initializes components and spinners model. */ private void createComponents(final UserPreferences preferences, final LevelController controller) { // Get unit name matching current unit String unitName = preferences.getLengthUnit().getName(); if (controller.isPropertyEditable(LevelController.Property.NAME)) { // Create name label and its text field bound to NAME controller property this.nameLabel = new JLabel(SwingTools.getLocalizedLabelText(preferences, LevelPanel.class, "nameLabel.text")); this.nameTextField = new AutoCompleteTextField(controller.getName(), 15, preferences.getAutoCompletionStrings("LevelName")); if (!OperatingSystem.isMacOSXLeopardOrSuperior()) { SwingTools.addAutoSelectionOnFocusGain(this.nameTextField); } final PropertyChangeListener nameChangeListener = new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent ev) { nameTextField.setText(controller.getName()); } }; controller.addPropertyChangeListener(LevelController.Property.NAME, nameChangeListener); this.nameTextField.getDocument().addDocumentListener(new DocumentListener() { public void changedUpdate(DocumentEvent ev) { controller.removePropertyChangeListener(LevelController.Property.NAME, nameChangeListener); String name = nameTextField.getText(); if (name == null || name.trim().length() == 0) { controller.setName(null); } else { controller.setName(name); } controller.addPropertyChangeListener(LevelController.Property.NAME, nameChangeListener); } public void insertUpdate(DocumentEvent ev) { changedUpdate(ev); } public void removeUpdate(DocumentEvent ev) { changedUpdate(ev); } }); } final float maximumLength = preferences.getLengthUnit().getMaximumLength(); if (controller.isPropertyEditable(LevelController.Property.ELEVATION)) { // Create elevation label and its spinner bound to ELEVATION controller property this.elevationLabel = new JLabel(SwingTools.getLocalizedLabelText(preferences, LevelPanel.class, "elevationLabel.text", unitName)); final NullableSpinner.NullableSpinnerLengthModel elevationSpinnerModel = new NullableSpinner.NullableSpinnerLengthModel(preferences, -1000f, preferences.getLengthUnit().getMaximumElevation()); this.elevationSpinner = new NullableSpinner(elevationSpinnerModel); elevationSpinnerModel.setNullable(controller.getElevation() == null); elevationSpinnerModel.setLength(controller.getElevation()); final PropertyChangeListener elevationChangeListener = new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent ev) { elevationSpinnerModel.setNullable(ev.getNewValue() == null); elevationSpinnerModel.setLength((Float)ev.getNewValue()); } }; controller.addPropertyChangeListener(LevelController.Property.ELEVATION, elevationChangeListener); elevationSpinnerModel.addChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent ev) { controller.removePropertyChangeListener(LevelController.Property.ELEVATION, elevationChangeListener); controller.setElevation(elevationSpinnerModel.getLength()); controller.addPropertyChangeListener(LevelController.Property.ELEVATION, elevationChangeListener); } }); } final float minimumLength = preferences.getLengthUnit().getMinimumLength(); if (controller.isPropertyEditable(LevelController.Property.FLOOR_THICKNESS)) { // Create floor thickness label and its spinner bound to FLOOR_THICKNESS controller property this.floorThicknessLabel = new JLabel(SwingTools.getLocalizedLabelText(preferences, LevelPanel.class, "floorThicknessLabel.text", unitName)); final NullableSpinner.NullableSpinnerLengthModel floorThicknessSpinnerModel = new NullableSpinner.NullableSpinnerLengthModel(preferences, minimumLength, maximumLength / 10); this.floorThicknessSpinner = new NullableSpinner(floorThicknessSpinnerModel); final PropertyChangeListener floorThicknessChangeListener = new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent ev) { Float floorThickness = controller.getFloorThickness(); floorThicknessSpinnerModel.setNullable(floorThickness == null); floorThicknessSpinnerModel.setLength(floorThickness); } }; floorThicknessChangeListener.propertyChange(null); controller.addPropertyChangeListener(LevelController.Property.FLOOR_THICKNESS, floorThicknessChangeListener); floorThicknessSpinnerModel.addChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent ev) { controller.removePropertyChangeListener(LevelController.Property.FLOOR_THICKNESS, floorThicknessChangeListener); controller.setFloorThickness(floorThicknessSpinnerModel.getLength()); controller.addPropertyChangeListener(LevelController.Property.FLOOR_THICKNESS, floorThicknessChangeListener); } }); } if (controller.isPropertyEditable(LevelController.Property.HEIGHT)) { // Create floor thickness label and its spinner bound to HEIGHT controller property this.heightLabel = new JLabel(SwingTools.getLocalizedLabelText(preferences, LevelPanel.class, "heightLabel.text", unitName)); final NullableSpinner.NullableSpinnerLengthModel heightSpinnerModel = new NullableSpinner.NullableSpinnerLengthModel(preferences, minimumLength, maximumLength); this.heightSpinner = new NullableSpinner(heightSpinnerModel); final PropertyChangeListener heightChangeListener = new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent ev) { Float height = controller.getHeight(); heightSpinnerModel.setNullable(height == null); heightSpinnerModel.setLength(height); } }; heightChangeListener.propertyChange(null); controller.addPropertyChangeListener(LevelController.Property.HEIGHT, heightChangeListener); heightSpinnerModel.addChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent ev) { controller.removePropertyChangeListener(LevelController.Property.HEIGHT, heightChangeListener); controller.setHeight(heightSpinnerModel.getLength()); controller.addPropertyChangeListener(LevelController.Property.HEIGHT, heightChangeListener); } }); } this.levelsSummaryLabel = new JLabel(SwingTools.getLocalizedLabelText(preferences, LevelPanel.class, "levelsSummaryLabel.text", unitName)); final String [] columnNames = { SwingTools.getLocalizedLabelText(preferences, LevelPanel.class, "nameColumn"), SwingTools.getLocalizedLabelText(preferences, LevelPanel.class, "elevationColumn"), SwingTools.getLocalizedLabelText(preferences, LevelPanel.class, "floorThicknessColumn"), SwingTools.getLocalizedLabelText(preferences, LevelPanel.class, "heightColumn")}; this.levelsSummaryTable = new JTable(new LevelsTableModel(controller.getLevels(), columnNames)); // Display lengths according to the current length unit TableColumnModel columnModel = this.levelsSummaryTable.getColumnModel(); TableCellRenderer lengthRenderer = new DefaultTableCellRenderer() { public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { if (value != null) { value = preferences.getLengthUnit().getFormat().format((Float)value); setHorizontalAlignment(JLabel.RIGHT); } return super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); } }; columnModel.getColumn(1).setCellRenderer(lengthRenderer); columnModel.getColumn(2).setCellRenderer(lengthRenderer); columnModel.getColumn(3).setCellRenderer(lengthRenderer); // Ensure only selected level is selected in the table this.levelsSummaryTable.setSelectionModel(new DefaultListSelectionModel() { public void setSelectionInterval(int index0, int index1) { } public void setLeadSelectionIndex(int index) { } public void setAnchorSelectionIndex(int index) { } public boolean isSelectionEmpty() { return controller.getSelectedLevelIndex() != null; } public boolean isSelectedIndex(int index) { return controller.getSelectedLevelIndex() == index; } public void insertIndexInterval(int index, int length, boolean before) { } }); this.levelsSummaryTable.setFocusable(false); this.levelsSummaryTable.addAncestorListener(new AncestorListener() { public void ancestorAdded(AncestorEvent ev) { // Ensure selected row is visible when table is displayed Integer selectedRow = controller.getSelectedLevelIndex(); if (selectedRow != null) { levelsSummaryTable.scrollRectToVisible(levelsSummaryTable.getCellRect(selectedRow, 0, true)); } ev.getComponent().removeAncestorListener(this); } public void ancestorRemoved(AncestorEvent ev) { } public void ancestorMoved(AncestorEvent ev) { } }); this.dialogTitle = preferences.getLocalizedString(LevelPanel.class, "level.title"); } /** * Sets components mnemonics and label / component associations. */ private void setMnemonics(UserPreferences preferences) { if (!OperatingSystem.isMacOSX()) { if (this.nameLabel != null) { this.nameLabel.setDisplayedMnemonic(KeyStroke.getKeyStroke(preferences.getLocalizedString( LevelPanel.class, "nameLabel.mnemonic")).getKeyCode()); this.nameLabel.setLabelFor(this.nameTextField); } if (this.elevationLabel != null) { this.elevationLabel.setDisplayedMnemonic(KeyStroke.getKeyStroke(preferences.getLocalizedString( LevelPanel.class, "elevationLabel.mnemonic")).getKeyCode()); this.elevationLabel.setLabelFor(this.elevationSpinner); } if (this.floorThicknessLabel != null) { this.floorThicknessLabel.setDisplayedMnemonic(KeyStroke.getKeyStroke(preferences.getLocalizedString( LevelPanel.class, "floorThicknessLabel.mnemonic")).getKeyCode()); this.floorThicknessLabel.setLabelFor(this.floorThicknessSpinner); } if (this.heightLabel != null) { this.heightLabel.setDisplayedMnemonic(KeyStroke.getKeyStroke(preferences.getLocalizedString( LevelPanel.class, "heightLabel.mnemonic")).getKeyCode()); this.heightLabel.setLabelFor(this.heightSpinner); } } } /** * Layouts panel components in panel with their labels. */ private void layoutComponents(UserPreferences preferences, final LevelController controller) { int labelAlignment = OperatingSystem.isMacOSX() ? GridBagConstraints.LINE_END : GridBagConstraints.LINE_START; Insets labelInsets = new Insets(0, 0, 5, 5); Insets rightComponentInsets = new Insets(0, 0, 5, 0); if (this.nameLabel != null) { // First row add(this.nameLabel, new GridBagConstraints( 0, 0, 1, 1, 0, 0, labelAlignment, GridBagConstraints.NONE, labelInsets, 0, 0)); add(this.nameTextField, new GridBagConstraints( 1, 0, 2, 1, 0, 0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, rightComponentInsets, 0, 0)); } if (this.elevationLabel != null) { // Second row add(this.elevationLabel, new GridBagConstraints( 0, 1, 1, 1, 0, 0, labelAlignment, GridBagConstraints.NONE, labelInsets, 0, 0)); add(this.elevationSpinner, new GridBagConstraints( 1, 1, 1, 1, 0.1, 0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, rightComponentInsets, -15, 0)); add(new JLabel(), new GridBagConstraints( 2, 1, 1, 1, 0.2, 0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, rightComponentInsets, 0, 0)); } if (this.floorThicknessLabel != null) { // Third row add(this.floorThicknessLabel, new GridBagConstraints( 0, 2, 1, 1, 0, 0, labelAlignment, GridBagConstraints.NONE, labelInsets, 0, 0)); add(this.floorThicknessSpinner, new GridBagConstraints( 1, 2, 1, 1, 0.1, 0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, rightComponentInsets, -15, 0)); add(new JLabel(), new GridBagConstraints( 2, 3, 1, 1, 0.2, 0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, rightComponentInsets, 0, 0)); } if (this.heightLabel != null) { // Last row add(this.heightLabel, new GridBagConstraints( 0, 3, 1, 1, 0, 0, labelAlignment, GridBagConstraints.NONE, new Insets(0, 0, 0, 5), 0, 0)); add(this.heightSpinner, new GridBagConstraints( 1, 3, 1, 1, 0.1, 0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), -15, 0)); add(new JLabel(), new GridBagConstraints( 2, 3, 1, 1, 0.2, 0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, rightComponentInsets, 0, 0)); } add(new JSeparator(), new GridBagConstraints( 0, 4, 3, 1, 0, 0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(5, 0, 5, 0), 0, 0)); add(this.levelsSummaryLabel, new GridBagConstraints( 0, 5, 3, 1, 0, 0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(0, 0, 5, 0), 0, 0)); JScrollPane levelsSummaryPane = new JScrollPane(this.levelsSummaryTable); levelsSummaryPane.setPreferredSize(new Dimension(320, 150)); add(levelsSummaryPane, new GridBagConstraints( 0, 6, 4, 1, 1, 1, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0)); } /** * Displays this panel in a modal dialog box. */ public void displayView(View parentView) { if (SwingTools.showConfirmDialog((JComponent)parentView, this, this.dialogTitle, this.nameTextField) == JOptionPane.OK_OPTION) { this.controller.modifyLevels(); } } /** * The model of the table used to show levels information. */ private static final class LevelsTableModel extends AbstractTableModel { private final Level [] levels; private final String [] columnNames; private LevelsTableModel(Level [] levels, String [] columnNames) { this.levels = levels; this.columnNames = columnNames; } public int getRowCount() { return this.levels.length; } public int getColumnCount() { return 4; } public Object getValueAt(int rowIndex, int columnIndex) { Level level = this.levels [rowIndex]; switch (columnIndex) { case 0 : return level.getName(); case 1 : return level.getElevation(); case 2 : for ( ; rowIndex > 0; rowIndex--) { if (this.levels [rowIndex - 1].getElevation() < level.getElevation()) { break; } } if (rowIndex == 0) { return null; // Don't display floor thickness of first levels } else { return level.getFloorThickness(); } case 3 : return level.getHeight(); } return null; } @Override public String getColumnName(int column) { return this.columnNames [column]; } } }