/* * ImportedFurnitureWizardStepsController.java 4 juil. 07 * * Sweet Home 3D, Copyright (c) 2007 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.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.net.URL; import java.util.Arrays; 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.CatalogDoorOrWindow; import com.eteks.sweethome3d.model.CatalogPieceOfFurniture; import com.eteks.sweethome3d.model.Content; import com.eteks.sweethome3d.model.FurnitureCatalog; import com.eteks.sweethome3d.model.FurnitureCategory; import com.eteks.sweethome3d.model.Home; import com.eteks.sweethome3d.model.HomePieceOfFurniture; import com.eteks.sweethome3d.model.Sash; import com.eteks.sweethome3d.model.Selectable; import com.eteks.sweethome3d.model.UserPreferences; /** * Wizard controller to manage furniture importation. * @author Emmanuel Puybaret */ public class ImportedFurnitureWizardController extends WizardController implements Controller { public enum Property {STEP, NAME, MODEL, WIDTH, DEPTH, HEIGHT, ELEVATION, MOVABLE, DOOR_OR_WINDOW, COLOR, CATEGORY, BACK_FACE_SHOWN, MODEL_ROTATION, STAIRCASE_CUT_OUT_SHAPE, ICON_YAW, PROPORTIONAL} public enum Step {MODEL, ROTATION, ATTRIBUTES, ICON}; private final Home home; private final CatalogPieceOfFurniture piece; private final String modelName; private final UserPreferences preferences; private final FurnitureController furnitureController; private final ContentManager contentManager; private final UndoableEditSupport undoSupport; private final PropertyChangeSupport propertyChangeSupport; private final ImportedFurnitureWizardStepState furnitureModelStepState; private final ImportedFurnitureWizardStepState furnitureOrientationStepState; private final ImportedFurnitureWizardStepState furnitureAttributesStepState; private final ImportedFurnitureWizardStepState furnitureIconStepState; private ImportedFurnitureWizardStepsView stepsView; private Step step; private String name; private Content model; private float width; private float proportionalWidth; private float depth; private float proportionalDepth; private float height; private float proportionalHeight; private float elevation; private boolean movable; private boolean doorOrWindow; private String staircaseCutOutShape; private Integer color; private FurnitureCategory category; private boolean backFaceShown; private float [][] modelRotation; private float iconYaw; private boolean proportional; private final ViewFactory viewFactory; /** * Creates a controller that edits a new catalog piece of furniture. */ public ImportedFurnitureWizardController(UserPreferences preferences, ViewFactory viewFactory, ContentManager contentManager) { this(null, null, null, preferences, null, viewFactory, contentManager, null); } /** * Creates a controller that edits a new catalog piece of furniture with a given * <code>modelName</code>. */ public ImportedFurnitureWizardController(String modelName, UserPreferences preferences, ViewFactory viewFactory, ContentManager contentManager) { this(null, null, modelName, preferences, null, viewFactory, contentManager, null); } /** * Creates a controller that edits <code>piece</code> values. */ public ImportedFurnitureWizardController(CatalogPieceOfFurniture piece, UserPreferences preferences, ViewFactory viewFactory, ContentManager contentManager) { this(null, piece, null, preferences, null, viewFactory, contentManager, null); } /** * Creates a controller that edits a new imported home piece of furniture. */ public ImportedFurnitureWizardController(Home home, UserPreferences preferences, FurnitureController furnitureController, ViewFactory viewFactory, ContentManager contentManager, UndoableEditSupport undoSupport) { this(home, null, null, preferences, furnitureController, viewFactory, contentManager, undoSupport); } /** * Creates a controller that edits a new imported home piece of furniture * with a given <code>modelName</code>. */ public ImportedFurnitureWizardController(Home home, String modelName, UserPreferences preferences, FurnitureController furnitureController, ViewFactory viewFactory, ContentManager contentManager, UndoableEditSupport undoSupport) { this(home, null, modelName, preferences, furnitureController, viewFactory, contentManager, undoSupport); } /** * Creates a controller that edits <code>piece</code> values. */ private ImportedFurnitureWizardController(Home home, CatalogPieceOfFurniture piece, String modelName, UserPreferences preferences, FurnitureController furnitureController, ViewFactory viewFactory, ContentManager contentManager, UndoableEditSupport undoSupport) { super(preferences, viewFactory); this.home = home; this.piece = piece; this.modelName = modelName; this.preferences = preferences; this.furnitureController = furnitureController; this.viewFactory = viewFactory; this.undoSupport = undoSupport; this.contentManager = contentManager; this.propertyChangeSupport = new PropertyChangeSupport(this); setTitle(this.preferences.getLocalizedString(ImportedFurnitureWizardController.class, piece == null ? "importFurnitureWizard.title" : "modifyFurnitureWizard.title")); // Initialize states this.furnitureModelStepState = new FurnitureModelStepState(); this.furnitureOrientationStepState = new FurnitureOrientationStepState(); this.furnitureAttributesStepState = new FurnitureAttributesStepState(); this.furnitureIconStepState = new FurnitureIconStepState(); setStepState(this.furnitureModelStepState); } /** * Imports piece in catalog and/or home and posts an undoable operation. */ @Override public void finish() { CatalogPieceOfFurniture newPiece; if (isDoorOrWindow()) { newPiece = new CatalogDoorOrWindow(getName(), getIcon(), getModel(), getWidth(), getDepth(), getHeight(), getElevation(), isMovable(), 1, 0, new Sash [0], getColor(), getModelRotation(), isBackFaceShown(), getIconYaw(), isProportional()); } else { newPiece = new CatalogPieceOfFurniture(getName(), getIcon(), getModel(), getWidth(), getDepth(), getHeight(), getElevation(), isMovable(), getStaircaseCutOutShape(), getColor(), getModelRotation(), isBackFaceShown(), getIconYaw(), isProportional()); } if (this.home != null) { // Add new piece to home addPieceOfFurniture(this.furnitureController.createHomePieceOfFurniture(newPiece)); } // Remove the edited piece from catalog FurnitureCatalog catalog = this.preferences.getFurnitureCatalog(); if (this.piece != null) { catalog.delete(this.piece); } // If a category exists, add new piece to catalog if (this.category != null) { catalog.add(this.category, newPiece); } } /** * Controls new piece added to home. * Once added the furniture will be selected in view * and undo support will receive a new undoable edit. * @param piece the piece of furniture to add. */ public void addPieceOfFurniture(HomePieceOfFurniture piece) { List<Selectable> oldSelection = this.home.getSelectedItems(); // Get index of the piece added to home int pieceIndex = this.home.getFurniture().size(); this.home.addPieceOfFurniture(piece, pieceIndex); this.home.setSelectedItems(Arrays.asList(piece)); if (this.undoSupport != null) { UndoableEdit undoableEdit = new PieceOfFurnitureImportationUndoableEdit( this.home, this.preferences, oldSelection, piece, pieceIndex); this.undoSupport.postEdit(undoableEdit); } } /** * Undoable edit for piece importation. This class isn't anonymous to avoid * being bound to controller and its view. */ private static class PieceOfFurnitureImportationUndoableEdit extends AbstractUndoableEdit { private final Home home; private final UserPreferences preferences; private final List<Selectable> oldSelection; private final HomePieceOfFurniture piece; private final int pieceIndex; private PieceOfFurnitureImportationUndoableEdit(Home home, UserPreferences preferences, List<Selectable> oldSelection, HomePieceOfFurniture piece, int pieceIndex) { this.home = home; this.preferences = preferences; this.oldSelection = oldSelection; this.piece = piece; this.pieceIndex = pieceIndex; } @Override public void undo() throws CannotUndoException { super.undo(); this.home.deletePieceOfFurniture(this.piece); this.home.setSelectedItems(this.oldSelection); } @Override public void redo() throws CannotRedoException { super.redo(); this.home.addPieceOfFurniture(this.piece, this.pieceIndex); this.home.setSelectedItems(Arrays.asList(this.piece)); } @Override public String getPresentationName() { return this.preferences.getLocalizedString(ImportedFurnitureWizardController.class, "undoImportFurnitureName"); } } /** * Returns the content manager of this controller. */ public ContentManager getContentManager() { return this.contentManager; } /** * Returns the current step state. */ @Override protected ImportedFurnitureWizardStepState getStepState() { return (ImportedFurnitureWizardStepState)super.getStepState(); } /** * Returns the furniture choice step state. */ protected ImportedFurnitureWizardStepState getFurnitureModelStepStatee() { return this.furnitureModelStepState; } /** * Returns the furniture orientation step state. */ protected ImportedFurnitureWizardStepState getFurnitureOrientationStepState() { return this.furnitureOrientationStepState; } /** * Returns the furniture attributes step state. */ protected ImportedFurnitureWizardStepState getFurnitureAttributesStepState() { return this.furnitureAttributesStepState; } /** * Returns the furniture icon step state. */ protected ImportedFurnitureWizardStepState getFurnitureIconStepState() { return this.furnitureIconStepState; } /** * Returns the unique wizard view used for all steps. */ protected ImportedFurnitureWizardStepsView getStepsView() { // Create view lazily only once it's needed if (this.stepsView == null) { this.stepsView = this.viewFactory.createImportedFurnitureWizardStepsView( this.piece, this.modelName, this.home != null, this.preferences, this); } return this.stepsView; } /** * Switch in the wizard view to the given <code>step</code>. */ protected void setStep(Step step) { if (step != this.step) { Step oldStep = this.step; this.step = step; this.propertyChangeSupport.firePropertyChange(Property.STEP.name(), oldStep, step); } } /** * Returns the current step in wizard view. */ public Step getStep() { return this.step; } /** * Adds the property change <code>listener</code> in parameter to this home. */ public void addPropertyChangeListener(Property property, PropertyChangeListener listener) { this.propertyChangeSupport.addPropertyChangeListener(property.name(), listener); } /** * Removes the property change <code>listener</code> in parameter from this home. */ public void removePropertyChangeListener(Property property, PropertyChangeListener listener) { this.propertyChangeSupport.removePropertyChangeListener(property.name(), listener); } /** * Returns the model content of the imported piece. */ public Content getModel() { return this.model; } /** * Sets the model content of the imported piece. */ public void setModel(Content model) { if (model != this.model) { Content oldModel = this.model; this.model = model; this.propertyChangeSupport.firePropertyChange(Property.MODEL.name(), oldModel, model); } } /** * Returns <code>true</code> if imported piece back face should be shown. */ public boolean isBackFaceShown() { return this.backFaceShown; } /** * Sets whether imported piece back face should be shown. */ public void setBackFaceShown(boolean backFaceShown) { if (backFaceShown != this.backFaceShown) { this.backFaceShown = backFaceShown; this.propertyChangeSupport.firePropertyChange(Property.BACK_FACE_SHOWN.name(), !backFaceShown, backFaceShown); } } /** * Returns the pitch angle of the imported piece model. */ public float [][] getModelRotation() { return this.modelRotation; } /** * Sets the orientation pitch angle of the imported piece model. */ public void setModelRotation(float [][] modelRotation) { if (modelRotation != this.modelRotation) { float [][] oldModelRotation = this.modelRotation; this.modelRotation = modelRotation; this.propertyChangeSupport.firePropertyChange(Property.MODEL_ROTATION.name(), oldModelRotation, modelRotation); } } /** * Returns the name of the imported piece. */ public String getName() { return this.name; } /** * Sets the name of the imported piece. */ public void setName(String name) { if (name != this.name) { String oldName = this.name; this.name = name; if (this.propertyChangeSupport != null) { this.propertyChangeSupport.firePropertyChange(Property.NAME.name(), oldName, name); } } } /** * Returns the width. */ public float getWidth() { return this.width; } /** * Sets the width of the imported piece. */ public void setWidth(float width) { setWidth(width, false); } /** * Sets the width of the imported piece. */ private void setWidth(float width, boolean keepProportionalWidthUnchanged) { float adjustedWidth = Math.max(width, 0.001f); if (adjustedWidth == width || !keepProportionalWidthUnchanged) { this.proportionalWidth = width; } if (adjustedWidth != this.width) { float oldWidth = this.width; this.width = adjustedWidth; this.propertyChangeSupport.firePropertyChange(Property.WIDTH.name(), oldWidth, adjustedWidth); } } /** * Returns the depth of the imported piece. */ public float getDepth() { return this.depth; } /** * Sets the depth of the imported piece. */ public void setDepth(float depth) { setDepth(depth, false); } /** * Sets the depth of the imported piece. */ private void setDepth(float depth, boolean keepProportionalDepthUnchanged) { float adjustedDepth = Math.max(depth, 0.001f); if (adjustedDepth == depth || !keepProportionalDepthUnchanged) { this.proportionalDepth = depth; } if (adjustedDepth != this.depth) { float oldDepth = this.depth; this.depth = adjustedDepth; this.propertyChangeSupport.firePropertyChange(Property.DEPTH.name(), oldDepth, adjustedDepth); } } /** * Returns the height. */ public float getHeight() { return this.height; } /** * Sets the size of the imported piece. */ public void setHeight(float height) { setHeight(height, false); } /** * Sets the size of the imported piece. */ private void setHeight(float height, boolean keepProportionalHeightUnchanged) { float adjustedHeight = Math.max(height, 0.001f); if (adjustedHeight == height || !keepProportionalHeightUnchanged) { this.proportionalHeight = height; } if (adjustedHeight != this.height) { float oldHeight = this.height; this.height = adjustedHeight; this.propertyChangeSupport.firePropertyChange(Property.HEIGHT.name(), oldHeight, adjustedHeight); } } /** * Returns the elevation of the imported piece. */ public float getElevation() { return this.elevation; } /** * Sets the elevation of the imported piece. */ 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 <code>true</code> if imported piece is movable. */ public boolean isMovable() { return this.movable; } /** * Sets whether imported piece is movable. */ public void setMovable(boolean movable) { if (movable != this.movable) { this.movable = movable; this.propertyChangeSupport.firePropertyChange(Property.MOVABLE.name(), !movable, movable); } } /** * Returns <code>true</code> if imported piece is a door or a window. */ public boolean isDoorOrWindow() { return this.doorOrWindow; } /** * Sets whether imported piece is a door or a window. */ public void setDoorOrWindow(boolean doorOrWindow) { if (doorOrWindow != this.doorOrWindow) { this.doorOrWindow = doorOrWindow; this.propertyChangeSupport.firePropertyChange(Property.DOOR_OR_WINDOW.name(), !doorOrWindow, doorOrWindow); if (doorOrWindow) { setStaircaseCutOutShape(null); setMovable(false); } } } /** * Returns the shape used to cut out upper levels at its intersection with a staircase. */ public String getStaircaseCutOutShape() { return this.staircaseCutOutShape; } /** * Sets the shape used to cut out upper levels at its intersection with a staircase. */ public void setStaircaseCutOutShape(String staircaseCutOutShape) { if (staircaseCutOutShape != this.staircaseCutOutShape) { String oldStaircaseCutOutShape = this.staircaseCutOutShape; this.staircaseCutOutShape = staircaseCutOutShape; if (this.propertyChangeSupport != null) { this.propertyChangeSupport.firePropertyChange(Property.STAIRCASE_CUT_OUT_SHAPE.name(), oldStaircaseCutOutShape, staircaseCutOutShape); } if (this.staircaseCutOutShape != null) { setDoorOrWindow(false); setMovable(false); } } } /** * Returns the color of the imported piece. */ public Integer getColor() { return this.color; } /** * Sets the color of the imported piece. */ public void setColor(Integer color) { if (color != this.color) { Integer oldColor = this.color; this.color = color; this.propertyChangeSupport.firePropertyChange(Property.COLOR.name(), oldColor, color); } } /** * Returns the category of the imported piece. */ public FurnitureCategory getCategory() { return this.category; } /** * Sets the category of the imported piece. */ public void setCategory(FurnitureCategory category) { if (category != this.category) { FurnitureCategory oldCategory = this.category; this.category = category; this.propertyChangeSupport.firePropertyChange(Property.CATEGORY.name(), oldCategory, category); } } /** * Returns the icon of the imported piece. */ private Content getIcon() { return getStepsView().getIcon(); } /** * Returns the yaw of the piece icon. */ public float getIconYaw() { return this.iconYaw; } /** * Sets the yaw angle of the piece icon. */ public void setIconYaw(float iconYaw) { if (iconYaw != this.iconYaw) { float oldIconYaw = this.iconYaw; this.iconYaw = iconYaw; this.propertyChangeSupport.firePropertyChange(Property.ICON_YAW.name(), oldIconYaw, iconYaw); } } /** * Returns <code>true</code> if piece proportions should be kept. */ public boolean isProportional() { return this.proportional; } /** * Sets whether piece proportions should be kept or not. */ public void setProportional(boolean proportional) { if (proportional != this.proportional) { this.proportional = proportional; this.propertyChangeSupport.firePropertyChange(Property.PROPORTIONAL.name(), !proportional, proportional); } } /** * Returns <code>true</code> if piece name is valid. */ public boolean isPieceOfFurnitureNameValid() { return this.name != null && this.name.length() > 0; } /** * Step state superclass. All step state share the same step view, * that will display a different component depending on their class name. */ protected abstract class ImportedFurnitureWizardStepState extends WizardControllerStepState { private URL icon = ImportedFurnitureWizardController.class.getResource("resources/importedFurnitureWizard.png"); public abstract Step getStep(); @Override public void enter() { setStep(getStep()); } @Override public View getView() { return getStepsView(); } @Override public URL getIcon() { return this.icon; } } /** * Furniture model step state (first step). */ private class FurnitureModelStepState extends ImportedFurnitureWizardStepState { private PropertyChangeListener modelChangeListener = new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent ev) { setNextStepEnabled(getModel() != null); } }; @Override public void enter() { super.enter(); setFirstStep(true); // First step is valid once a model is available setNextStepEnabled(getModel() != null); addPropertyChangeListener(Property.MODEL, this.modelChangeListener); } @Override public Step getStep() { return Step.MODEL; } @Override public void goToNextStep() { setStepState(getFurnitureOrientationStepState()); } @Override public void exit() { removePropertyChangeListener(Property.MODEL, this.modelChangeListener); } } /** * Furniture orientation step state (second step). */ private class FurnitureOrientationStepState extends ImportedFurnitureWizardStepState { @Override public void enter() { super.enter(); // Step always valid by default setNextStepEnabled(true); } @Override public Step getStep() { return Step.ROTATION; } @Override public void goBackToPreviousStep() { setStepState(getFurnitureModelStepStatee()); } @Override public void goToNextStep() { setStepState(getFurnitureAttributesStepState()); } } /** * Furniture attributes step state (third step). */ private class FurnitureAttributesStepState extends ImportedFurnitureWizardStepState { PropertyChangeListener widthChangeListener = new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent ev) { if (isProportional()) { ImportedFurnitureWizardController.this.removePropertyChangeListener(Property.DEPTH, depthChangeListener); ImportedFurnitureWizardController.this.removePropertyChangeListener(Property.HEIGHT, heightChangeListener); // If proportions should be kept, update depth and height float ratio = (Float)ev.getNewValue() / (Float)ev.getOldValue(); setDepth(proportionalDepth * ratio, true); setHeight(proportionalHeight * ratio, true); ImportedFurnitureWizardController.this.addPropertyChangeListener(Property.DEPTH, depthChangeListener); ImportedFurnitureWizardController.this.addPropertyChangeListener(Property.HEIGHT, heightChangeListener); } } }; PropertyChangeListener depthChangeListener = new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent ev) { if (isProportional()) { ImportedFurnitureWizardController.this.removePropertyChangeListener(Property.WIDTH, widthChangeListener); ImportedFurnitureWizardController.this.removePropertyChangeListener(Property.HEIGHT, heightChangeListener); // If proportions should be kept, update width and height float ratio = (Float)ev.getNewValue() / (Float)ev.getOldValue(); setWidth(proportionalWidth * ratio, true); setHeight(proportionalHeight * ratio, true); ImportedFurnitureWizardController.this.addPropertyChangeListener(Property.WIDTH, widthChangeListener); ImportedFurnitureWizardController.this.addPropertyChangeListener(Property.HEIGHT, heightChangeListener); } } }; PropertyChangeListener heightChangeListener = new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent ev) { if (isProportional()) { ImportedFurnitureWizardController.this.removePropertyChangeListener(Property.WIDTH, widthChangeListener); ImportedFurnitureWizardController.this.removePropertyChangeListener(Property.DEPTH, depthChangeListener); // If proportions should be kept, update width and depth float ratio = (Float)ev.getNewValue() / (Float)ev.getOldValue(); setWidth(proportionalWidth * ratio, true); setDepth(proportionalDepth * ratio, true); ImportedFurnitureWizardController.this.addPropertyChangeListener(Property.WIDTH, widthChangeListener); ImportedFurnitureWizardController.this.addPropertyChangeListener(Property.DEPTH, depthChangeListener); } } }; PropertyChangeListener nameAndCategoryChangeListener = new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent ev) { checkPieceOfFurnitureNameInCategory(); } }; @Override public void enter() { super.enter(); ImportedFurnitureWizardController.this.addPropertyChangeListener(Property.WIDTH, this.widthChangeListener); ImportedFurnitureWizardController.this.addPropertyChangeListener(Property.DEPTH, this.depthChangeListener); ImportedFurnitureWizardController.this.addPropertyChangeListener(Property.HEIGHT, this.heightChangeListener); ImportedFurnitureWizardController.this.addPropertyChangeListener(Property.NAME, this.nameAndCategoryChangeListener); ImportedFurnitureWizardController.this.addPropertyChangeListener(Property.CATEGORY, this.nameAndCategoryChangeListener); checkPieceOfFurnitureNameInCategory(); } private void checkPieceOfFurnitureNameInCategory() { setNextStepEnabled(isPieceOfFurnitureNameValid()); } @Override public Step getStep() { return Step.ATTRIBUTES; } @Override public void goBackToPreviousStep() { setStepState(getFurnitureOrientationStepState()); } @Override public void goToNextStep() { setStepState(getFurnitureIconStepState()); } @Override public void exit() { ImportedFurnitureWizardController.this.removePropertyChangeListener(Property.WIDTH, this.widthChangeListener); ImportedFurnitureWizardController.this.removePropertyChangeListener(Property.DEPTH, this.depthChangeListener); ImportedFurnitureWizardController.this.removePropertyChangeListener(Property.HEIGHT, this.heightChangeListener); ImportedFurnitureWizardController.this.removePropertyChangeListener(Property.NAME, this.nameAndCategoryChangeListener); ImportedFurnitureWizardController.this.removePropertyChangeListener(Property.CATEGORY, this.nameAndCategoryChangeListener); } } /** * Furniture icon step state (last step). */ private class FurnitureIconStepState extends ImportedFurnitureWizardStepState { @Override public void enter() { super.enter(); setLastStep(true); // Step always valid by default setNextStepEnabled(true); } @Override public Step getStep() { return Step.ICON; } @Override public void goBackToPreviousStep() { setStepState(getFurnitureAttributesStepState()); } } }