/*
* LevelController.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.viewcontroller;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.List;
import javax.swing.undo.AbstractUndoableEdit;
import javax.swing.undo.CannotRedoException;
import javax.swing.undo.CannotUndoException;
import javax.swing.undo.UndoableEdit;
import javax.swing.undo.UndoableEditSupport;
import com.eteks.sweethome3d.model.Home;
import com.eteks.sweethome3d.model.Level;
import com.eteks.sweethome3d.model.UserPreferences;
/**
* A MVC controller for home levels view.
* @author Emmanuel Puybaret
*/
public class LevelController implements Controller {
/**
* The properties that may be edited by the view associated to this controller.
*/
public enum Property {NAME, ELEVATION, FLOOR_THICKNESS, HEIGHT, LEVELS, SELECT_LEVEL_INDEX}
private final Home home;
private final UserPreferences preferences;
private final ViewFactory viewFactory;
private final UndoableEditSupport undoSupport;
private final PropertyChangeSupport propertyChangeSupport;
private DialogView homeLevelView;
private String name;
private Float elevation;
private Float floorThickness;
private Float height;
private Level [] levels;
private Integer selectedLevelIndex;
/**
* Creates the controller of home levels view with undo support.
*/
public LevelController(Home home,
UserPreferences preferences,
ViewFactory viewFactory,
UndoableEditSupport undoSupport) {
this.home = home;
this.preferences = preferences;
this.viewFactory = viewFactory;
this.undoSupport = undoSupport;
this.propertyChangeSupport = new PropertyChangeSupport(this);
updateProperties();
}
/**
* Returns the view associated with this controller.
*/
public DialogView getView() {
// Create view lazily only once it's needed
if (this.homeLevelView == null) {
this.homeLevelView = this.viewFactory.createLevelView(this.preferences, this);
}
return this.homeLevelView;
}
/**
* Displays the view controlled by this controller.
*/
public void displayView(View parentView) {
getView().displayView(parentView);
}
/**
* Adds the property change <code>listener</code> in parameter to this controller.
*/
public void addPropertyChangeListener(Property property, PropertyChangeListener listener) {
this.propertyChangeSupport.addPropertyChangeListener(property.name(), listener);
}
/**
* Removes the property change <code>listener</code> in parameter from this controller.
*/
public void removePropertyChangeListener(Property property, PropertyChangeListener listener) {
this.propertyChangeSupport.removePropertyChangeListener(property.name(), listener);
}
/**
* Updates edited properties from selected level in the home edited by this controller.
*/
protected void updateProperties() {
Level selectedLevel = this.home.getSelectedLevel();
if (selectedLevel == null) {
setName(null); // Nothing to edit
setElevation(null);
setFloorThickness(null);
setHeight(null);
setSelectedLevelIndex(null);
} else {
setName(selectedLevel.getName());
setElevation(selectedLevel.getElevation());
setFloorThickness(selectedLevel.getFloorThickness());
setHeight(selectedLevel.getHeight());
setSelectedLevelIndex(this.home.getLevels().indexOf(selectedLevel));
}
setLevels(duplicate(this.home.getLevels().toArray(new Level [0])));
}
private Level [] duplicate(Level[] levels) {
for (int i = 0; i < levels.length; i++) {
levels [i] = levels [i].clone();
}
return levels;
}
/**
* Returns <code>true</code> if the given <code>property</code> is editable.
* Depending on whether a property is editable or not, the view associated to this controller
* may render it differently.
* The implementation of this method always returns <code>true</code> except
* for <code>FLOOR_THICKNESS</code> if the selected level is the first level
* and <code>HEIGHT</code> if the selected level is the last level.
*/
public boolean isPropertyEditable(Property property) {
switch (property) {
case FLOOR_THICKNESS :
List<Level> levels = this.home.getLevels();
Level selectedLevel = this.home.getSelectedLevel();
int selectedLevelIndex;
return selectedLevel != null
&& (selectedLevel.getElevation() > 0
|| ((selectedLevelIndex = levels.indexOf(selectedLevel)) != 0
&& levels.get(selectedLevelIndex - 1).getElevation() != selectedLevel.getElevation()));
default :
return true;
}
}
/**
* Sets the edited name.
*/
public void setName(String name) {
if (name != this.name) {
String oldName = this.name;
this.name = name;
this.propertyChangeSupport.firePropertyChange(Property.NAME.name(), oldName, name);
}
}
/**
* Returns the edited name.
*/
public String getName() {
return this.name;
}
/**
* Sets the edited elevation.
*/
public void setElevation(Float elevation) {
if (elevation != this.elevation) {
Float oldElevation = this.elevation;
this.elevation = elevation;
this.propertyChangeSupport.firePropertyChange(Property.ELEVATION.name(), oldElevation, elevation);
}
}
/**
* Returns the edited elevation.
*/
public Float getElevation() {
return this.elevation;
}
/**
* Sets the edited floor thickness.
*/
public void setFloorThickness(Float floorThickness) {
if (floorThickness != this.floorThickness) {
Float oldFloorThickness = this.floorThickness;
this.floorThickness = floorThickness;
this.propertyChangeSupport.firePropertyChange(Property.FLOOR_THICKNESS.name(), oldFloorThickness, floorThickness);
}
}
/**
* Returns the edited floor thickness.
*/
public Float getFloorThickness() {
return this.floorThickness;
}
/**
* Sets the edited height.
*/
public void setHeight(Float height) {
if (height != this.height) {
Float oldHeight = this.height;
this.height = height;
this.propertyChangeSupport.firePropertyChange(Property.HEIGHT.name(), oldHeight, height);
}
}
/**
* Returns the edited height.
*/
public Float getHeight() {
return this.height;
}
/**
* Sets home levels.
*/
private void setLevels(Level [] levels) {
if (levels != this.levels) {
Level [] oldLevels = this.levels;
this.levels = levels;
this.propertyChangeSupport.firePropertyChange(Property.LEVELS.name(), oldLevels, levels);
}
}
/**
* Returns a copy of home levels.
*/
public Level [] getLevels() {
return this.levels.clone();
}
/**
* Sets the selected level index.
*/
private void setSelectedLevelIndex(Integer selectedLevelIndex) {
if (selectedLevelIndex != this.selectedLevelIndex) {
Integer oldSelectedLevelIndex = this.selectedLevelIndex;
this.selectedLevelIndex = selectedLevelIndex;
this.propertyChangeSupport.firePropertyChange(Property.SELECT_LEVEL_INDEX.name(), oldSelectedLevelIndex, selectedLevelIndex);
}
}
/**
* Returns the selected level index.
*/
public Integer getSelectedLevelIndex() {
return this.selectedLevelIndex;
}
/**
* Controls the modification of selected level in the edited home.
*/
public void modifyLevels() {
Level selectedLevel = this.home.getSelectedLevel();
if (selectedLevel != null) {
String name = getName();
Float elevation = getElevation();
Float floorThickness = getFloorThickness();
Float height = getHeight();
ModifiedLevel modifiedLevel = new ModifiedLevel(selectedLevel);
// Apply modification
doModifyLevel(modifiedLevel, name, elevation, floorThickness, height);
if (this.undoSupport != null) {
UndoableEdit undoableEdit = new LevelModificationUndoableEdit(
this.preferences, modifiedLevel, name, elevation, floorThickness, height);
this.undoSupport.postEdit(undoableEdit);
}
if (name != null) {
this.preferences.addAutoCompletionString("LevelName", name);
}
}
}
/**
* Undoable edit for level modification. This class isn't anonymous to avoid
* being bound to controller and its view.
*/
private static class LevelModificationUndoableEdit extends AbstractUndoableEdit {
private final UserPreferences preferences;
private final ModifiedLevel modifiedLevel;
private final String name;
private final Float elevation;
private final Float floorThickness;
private final Float height;
private LevelModificationUndoableEdit(UserPreferences preferences,
ModifiedLevel modifiedLevel,
String name,
Float elevation,
Float floorThickness,
Float height) {
this.preferences = preferences;
this.modifiedLevel = modifiedLevel;
this.name = name;
this.elevation = elevation;
this.floorThickness = floorThickness;
this.height = height;
}
@Override
public void undo() throws CannotUndoException {
super.undo();
undoModifyLevel(this.modifiedLevel);
}
@Override
public void redo() throws CannotRedoException {
super.redo();
doModifyLevel(this.modifiedLevel, this.name, this.elevation, this.floorThickness, this.height);
}
@Override
public String getPresentationName() {
return this.preferences.getLocalizedString(LevelController.class, "undoModifyLevelName");
}
}
/**
* Modifies level properties with the values in parameter.
*/
private static void doModifyLevel(ModifiedLevel modifiedLevel,
String name, Float elevation,
Float floorThickness, Float height) {
Level level = modifiedLevel.getLevel();
if (name != null) {
level.setName(name);
}
if (elevation != null) {
level.setElevation(elevation);
}
if (floorThickness != null) {
level.setFloorThickness(floorThickness);
}
if (height != null) {
level.setHeight(height);
}
}
/**
* Restores level properties from the values stored in <code>modifiedLevel</code>.
*/
private static void undoModifyLevel(ModifiedLevel modifiedLevel) {
modifiedLevel.reset();
}
/**
* Stores the current properties values of a modified level.
*/
private static class ModifiedLevel {
private final Level level;
private final String name;
private final float elevation;
private final float floorThickness;
private final float height;
public ModifiedLevel(Level level) {
this.level = level;
this.name = level.getName();
this.elevation = level.getElevation();
this.floorThickness = level.getFloorThickness();
this.height = level.getHeight();
}
public Level getLevel() {
return this.level;
}
public void reset() {
this.level.setName(this.name);
this.level.setElevation(this.elevation);
this.level.setFloorThickness(this.floorThickness);
this.level.setHeight(this.height);
}
}
}