Package com.eteks.sweethome3d.viewcontroller

Source Code of com.eteks.sweethome3d.viewcontroller.HomeController

/*
* HomeController.java 15 mai 2006
*
* Sweet Home 3D, Copyright (c) 2006 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.lang.ref.WeakReference;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Callable;

import javax.swing.event.UndoableEditEvent;
import javax.swing.event.UndoableEditListener;
import javax.swing.undo.AbstractUndoableEdit;
import javax.swing.undo.CannotRedoException;
import javax.swing.undo.CannotUndoException;
import javax.swing.undo.CompoundEdit;
import javax.swing.undo.UndoManager;
import javax.swing.undo.UndoableEdit;
import javax.swing.undo.UndoableEditSupport;

import com.eteks.sweethome3d.model.AspectRatio;
import com.eteks.sweethome3d.model.BackgroundImage;
import com.eteks.sweethome3d.model.Camera;
import com.eteks.sweethome3d.model.CatalogDoorOrWindow;
import com.eteks.sweethome3d.model.CatalogLight;
import com.eteks.sweethome3d.model.CatalogPieceOfFurniture;
import com.eteks.sweethome3d.model.CatalogTexture;
import com.eteks.sweethome3d.model.CollectionEvent;
import com.eteks.sweethome3d.model.CollectionListener;
import com.eteks.sweethome3d.model.Compass;
import com.eteks.sweethome3d.model.DimensionLine;
import com.eteks.sweethome3d.model.DoorOrWindow;
import com.eteks.sweethome3d.model.FurnitureCatalog;
import com.eteks.sweethome3d.model.Home;
import com.eteks.sweethome3d.model.HomeApplication;
import com.eteks.sweethome3d.model.HomeDoorOrWindow;
import com.eteks.sweethome3d.model.HomeEnvironment;
import com.eteks.sweethome3d.model.HomeFurnitureGroup;
import com.eteks.sweethome3d.model.HomeLight;
import com.eteks.sweethome3d.model.HomePieceOfFurniture;
import com.eteks.sweethome3d.model.HomeRecorder;
import com.eteks.sweethome3d.model.InterruptedRecorderException;
import com.eteks.sweethome3d.model.Label;
import com.eteks.sweethome3d.model.Light;
import com.eteks.sweethome3d.model.RecorderException;
import com.eteks.sweethome3d.model.Room;
import com.eteks.sweethome3d.model.Selectable;
import com.eteks.sweethome3d.model.SelectionEvent;
import com.eteks.sweethome3d.model.SelectionListener;
import com.eteks.sweethome3d.model.TexturesCatalog;
import com.eteks.sweethome3d.model.UserPreferences;
import com.eteks.sweethome3d.model.Wall;
import com.eteks.sweethome3d.plugin.Plugin;
import com.eteks.sweethome3d.plugin.PluginManager;
import com.eteks.sweethome3d.viewcontroller.PlanController.Mode;

/**
* A MVC controller for the home view.
* @author Emmanuel Puybaret
*/
public class HomeController implements Controller {
  private final Home                  home;
  private final UserPreferences       preferences;
  private final HomeApplication       application;
  private final ViewFactory           viewFactory;
  private final ContentManager        contentManager;
  private final PluginManager         pluginManager;
  private final UndoableEditSupport   undoSupport;
  private final UndoManager           undoManager;
  private HomeView                    homeView;
  private FurnitureCatalogController  furnitureCatalogController;
  private FurnitureController         furnitureController;
  private PlanController              planController;
  private HomeController3D            homeController3D;
  private static HelpController       helpController;  // Only one help controller
  private int                         saveUndoLevel;
  private boolean                     notUndoableModifications;
  private View                        focusedView;

  /**
   * Creates the controller of home view.
   * @param home the home edited by this controller and its view.
   * @param application the instance of current application.
   * @param viewFactory a factory able to create views.
   * @param contentManager the content manager of the application.
   * @param pluginManager  the plug-in manager of the application.
   */
  public HomeController(Home home,
                        HomeApplication application,
                        ViewFactory    viewFactory,
                        ContentManager contentManager,
                        PluginManager pluginManager) {
    this(home, application.getUserPreferences(), viewFactory,
        contentManager, application, pluginManager);
  }

  /**
   * Creates the controller of home view.
   * @param home the home edited by this controller and its view.
   * @param application the instance of current application.
   * @param viewFactory a factory able to create views.
   */
  public HomeController(Home home,
                        HomeApplication application,
                        ViewFactory viewFactory) {
    this(home, application.getUserPreferences(), viewFactory, null, application, null);
  }

  /**
   * Creates the controller of home view.
   * @param home        the home edited by this controller and its view.
   * @param preferences the preferences of the application.
   * @param viewFactory a factory able to create views.
   */
  public HomeController(Home home,
                        UserPreferences preferences,
                        ViewFactory viewFactory) {
    this(home, preferences, viewFactory, null, null, null);
  }

  /**
   * Creates the controller of home view.
   * @param home        the home edited by this controller and its view.
   * @param preferences the preferences of the application.
   * @param viewFactory a factory able to create views.
   * @param contentManager the content manager of the application.
   */
  public HomeController(Home home,
                        UserPreferences preferences,
                        ViewFactory    viewFactory,
                        ContentManager contentManager) {
    this(home, preferences, viewFactory, contentManager, null, null);
  }

  private HomeController(final Home home,
                         final UserPreferences preferences,
                         ViewFactory    viewFactory,
                         ContentManager contentManager,
                         HomeApplication application,
                         PluginManager pluginManager) {
    this.home = home;
    this.preferences = preferences;
    this.viewFactory = viewFactory;
    this.contentManager = contentManager;
    this.application = application;
    this.pluginManager = pluginManager;
    this.undoSupport = new UndoableEditSupport() {
        @Override
        protected void _postEdit(UndoableEdit edit) {
          // Ignore not significant compound edit
          if (!(edit instanceof CompoundEdit)
              || edit.isSignificant()) {
            super._postEdit(edit);
          }
        }
      };
    this.undoManager = new UndoManager();
    this.undoSupport.addUndoableEditListener(this.undoManager);
   
    // Update recent homes list
    if (home.getName() != null) {
      List<String> recentHomes = new ArrayList<String>(this.preferences.getRecentHomes());
      recentHomes.remove(home.getName());
      recentHomes.add(0, home.getName());
      updateUserPreferencesRecentHomes(recentHomes);
     
      // If home version is more recent than current version
      if (home.getVersion() > Home.CURRENT_VERSION) {
        // Warn the user that view will display a home created with a more recent version
        getView().invokeLater(new Runnable() {
            public void run() {
              String message = preferences.getLocalizedString(HomeController.class,
                  "moreRecentVersionHome", home.getName());
              getView().showMessage(message);
            }
          });
      }
    }
  }

  /**
   * Enables actions at controller instantiation.
   */
  private void enableDefaultActions(HomeView homeView) {
    boolean applicationExists = this.application != null;
   
    homeView.setEnabled(HomeView.ActionType.NEW_HOME, applicationExists);
    homeView.setEnabled(HomeView.ActionType.OPEN, applicationExists);
    homeView.setEnabled(HomeView.ActionType.DELETE_RECENT_HOMES,
        applicationExists && !this.preferences.getRecentHomes().isEmpty());
    homeView.setEnabled(HomeView.ActionType.CLOSE, applicationExists);
    homeView.setEnabled(HomeView.ActionType.SAVE, applicationExists);
    homeView.setEnabled(HomeView.ActionType.SAVE_AS, applicationExists);
    homeView.setEnabled(HomeView.ActionType.SAVE_AND_COMPRESS, applicationExists);
    homeView.setEnabled(HomeView.ActionType.PAGE_SETUP, true);
    homeView.setEnabled(HomeView.ActionType.PRINT_PREVIEW, true);
    homeView.setEnabled(HomeView.ActionType.PRINT, true);
    homeView.setEnabled(HomeView.ActionType.PRINT_TO_PDF, true);
    homeView.setEnabled(HomeView.ActionType.PREFERENCES, true);
    homeView.setEnabled(HomeView.ActionType.EXIT, applicationExists);
    homeView.setEnabled(HomeView.ActionType.IMPORT_FURNITURE, true);
    homeView.setEnabled(HomeView.ActionType.IMPORT_FURNITURE_LIBRARY, true);
    homeView.setEnabled(HomeView.ActionType.IMPORT_TEXTURES_LIBRARY, true);
    homeView.setEnabled(HomeView.ActionType.SORT_HOME_FURNITURE_BY_CATALOG_ID, true);
    homeView.setEnabled(HomeView.ActionType.SORT_HOME_FURNITURE_BY_NAME, true);
    homeView.setEnabled(HomeView.ActionType.SORT_HOME_FURNITURE_BY_WIDTH, true);
    homeView.setEnabled(HomeView.ActionType.SORT_HOME_FURNITURE_BY_HEIGHT, true);
    homeView.setEnabled(HomeView.ActionType.SORT_HOME_FURNITURE_BY_DEPTH, true);
    homeView.setEnabled(HomeView.ActionType.SORT_HOME_FURNITURE_BY_X, true);
    homeView.setEnabled(HomeView.ActionType.SORT_HOME_FURNITURE_BY_Y, true);
    homeView.setEnabled(HomeView.ActionType.SORT_HOME_FURNITURE_BY_ELEVATION, true);
    homeView.setEnabled(HomeView.ActionType.SORT_HOME_FURNITURE_BY_ANGLE, true);
    homeView.setEnabled(HomeView.ActionType.SORT_HOME_FURNITURE_BY_COLOR, true);
    homeView.setEnabled(HomeView.ActionType.SORT_HOME_FURNITURE_BY_TEXTURE, true);
    homeView.setEnabled(HomeView.ActionType.SORT_HOME_FURNITURE_BY_MOVABILITY, true);
    homeView.setEnabled(HomeView.ActionType.SORT_HOME_FURNITURE_BY_TYPE, true);
    homeView.setEnabled(HomeView.ActionType.SORT_HOME_FURNITURE_BY_VISIBILITY, true);
    homeView.setEnabled(HomeView.ActionType.SORT_HOME_FURNITURE_BY_PRICE, true);
    homeView.setEnabled(HomeView.ActionType.SORT_HOME_FURNITURE_BY_VALUE_ADDED_TAX_PERCENTAGE, true);
    homeView.setEnabled(HomeView.ActionType.SORT_HOME_FURNITURE_BY_VALUE_ADDED_TAX, true);
    homeView.setEnabled(HomeView.ActionType.SORT_HOME_FURNITURE_BY_PRICE_VALUE_ADDED_TAX_INCLUDED, true);
    homeView.setEnabled(HomeView.ActionType.SORT_HOME_FURNITURE_BY_DESCENDING_ORDER,
        this.home.getFurnitureSortedProperty() != null);
    homeView.setEnabled(HomeView.ActionType.DISPLAY_HOME_FURNITURE_CATALOG_ID, true);
    homeView.setEnabled(HomeView.ActionType.DISPLAY_HOME_FURNITURE_NAME, true);
    homeView.setEnabled(HomeView.ActionType.DISPLAY_HOME_FURNITURE_WIDTH, true);
    homeView.setEnabled(HomeView.ActionType.DISPLAY_HOME_FURNITURE_DEPTH, true);
    homeView.setEnabled(HomeView.ActionType.DISPLAY_HOME_FURNITURE_HEIGHT, true);
    homeView.setEnabled(HomeView.ActionType.DISPLAY_HOME_FURNITURE_X, true);
    homeView.setEnabled(HomeView.ActionType.DISPLAY_HOME_FURNITURE_Y, true);
    homeView.setEnabled(HomeView.ActionType.DISPLAY_HOME_FURNITURE_ELEVATION, true);
    homeView.setEnabled(HomeView.ActionType.DISPLAY_HOME_FURNITURE_ANGLE, true);
    homeView.setEnabled(HomeView.ActionType.DISPLAY_HOME_FURNITURE_COLOR, true);
    homeView.setEnabled(HomeView.ActionType.DISPLAY_HOME_FURNITURE_TEXTURE, true);
    homeView.setEnabled(HomeView.ActionType.DISPLAY_HOME_FURNITURE_MOVABLE, true);
    homeView.setEnabled(HomeView.ActionType.DISPLAY_HOME_FURNITURE_DOOR_OR_WINDOW, true);
    homeView.setEnabled(HomeView.ActionType.DISPLAY_HOME_FURNITURE_VISIBLE, true);
    homeView.setEnabled(HomeView.ActionType.DISPLAY_HOME_FURNITURE_PRICE, true);
    homeView.setEnabled(HomeView.ActionType.DISPLAY_HOME_FURNITURE_VALUE_ADDED_TAX_PERCENTAGE, true);
    homeView.setEnabled(HomeView.ActionType.DISPLAY_HOME_FURNITURE_VALUE_ADDED_TAX, true);
    homeView.setEnabled(HomeView.ActionType.DISPLAY_HOME_FURNITURE_PRICE_VALUE_ADDED_TAX_INCLUDED, true);
    homeView.setEnabled(HomeView.ActionType.SELECT, true);
    homeView.setEnabled(HomeView.ActionType.PAN, true);
    homeView.setEnabled(HomeView.ActionType.CREATE_WALLS, true);
    homeView.setEnabled(HomeView.ActionType.CREATE_ROOMS, true);
    homeView.setEnabled(HomeView.ActionType.CREATE_DIMENSION_LINES, true);
    homeView.setEnabled(HomeView.ActionType.CREATE_LABELS, true);
    homeView.setEnabled(HomeView.ActionType.LOCK_BASE_PLAN, true);
    homeView.setEnabled(HomeView.ActionType.UNLOCK_BASE_PLAN, true);
    homeView.setEnabled(HomeView.ActionType.MODIFY_COMPASS, true);
    homeView.setEnabled(HomeView.ActionType.IMPORT_BACKGROUND_IMAGE, true);
    BackgroundImage backgroundImage = this.home.getBackgroundImage();
    boolean homeHasBackgroundImage = backgroundImage != null;
    homeView.setEnabled(HomeView.ActionType.MODIFY_BACKGROUND_IMAGE, homeHasBackgroundImage);
    homeView.setEnabled(HomeView.ActionType.HIDE_BACKGROUND_IMAGE,
        homeHasBackgroundImage && backgroundImage.isVisible());
    homeView.setEnabled(HomeView.ActionType.SHOW_BACKGROUND_IMAGE,
        homeHasBackgroundImage && !backgroundImage.isVisible());
    homeView.setEnabled(HomeView.ActionType.DELETE_BACKGROUND_IMAGE, homeHasBackgroundImage);
    homeView.setEnabled(HomeView.ActionType.ZOOM_IN, true);
    homeView.setEnabled(HomeView.ActionType.ZOOM_OUT, true);
    homeView.setEnabled(HomeView.ActionType.EXPORT_TO_SVG, true);
    homeView.setEnabled(HomeView.ActionType.VIEW_FROM_TOP, true);
    homeView.setEnabled(HomeView.ActionType.VIEW_FROM_OBSERVER, true);
    homeView.setEnabled(HomeView.ActionType.STORE_POINT_OF_VIEW, true);
    homeView.setEnabled(HomeView.ActionType.DETACH_3D_VIEW, true);
    homeView.setEnabled(HomeView.ActionType.ATTACH_3D_VIEW, true);
    homeView.setEnabled(HomeView.ActionType.VIEW_FROM_OBSERVER, true);
    homeView.setEnabled(HomeView.ActionType.MODIFY_3D_ATTRIBUTES, true);
    homeView.setEnabled(HomeView.ActionType.CREATE_PHOTO, true);
    homeView.setEnabled(HomeView.ActionType.CREATE_VIDEO, true);
    homeView.setEnabled(HomeView.ActionType.EXPORT_TO_OBJ, true);
    homeView.setEnabled(HomeView.ActionType.HELP, true);
    homeView.setEnabled(HomeView.ActionType.ABOUT, true);
    homeView.setTransferEnabled(true);
  }

  /**
   * Returns the view associated with this controller.
   */
  public HomeView getView() {
    if (this.homeView == null) {
      this.homeView = this.viewFactory.createHomeView(this.home, this.preferences, this);
      enableDefaultActions(this.homeView);
      addListeners();
    }
    return this.homeView;
  }

  /**
   * Returns the plug-ins available with this controller.
   */
  public List<Plugin> getPlugins() {
    if (this.application != null && this.pluginManager != null) {
      // Retrieve home plug-ins
      return this.pluginManager.getPlugins(
          this.application, this.home, this.preferences, getUndoableEditSupport());
    } else {
      List<Plugin> plugins = Collections.emptyList();
      return plugins;
    }
  }

  /**
   * Returns the content manager of this controller.
   */
  public ContentManager getContentManager() {
    return this.contentManager;
  }

  /**
   * Returns the furniture catalog controller managed by this controller.
   */
  public FurnitureCatalogController getFurnitureCatalogController() {
    // Create sub controller lazily only once it's needed
    if (this.furnitureCatalogController == null) {
      this.furnitureCatalogController = new FurnitureCatalogController(
          this.preferences.getFurnitureCatalog(), this.preferences, this.viewFactory, this.contentManager);
    }
    return this.furnitureCatalogController;
  }

  /**
   * Returns the furniture controller managed by this controller.
   */
  public FurnitureController getFurnitureController() {
    // Create sub controller lazily only once it's needed
    if (this.furnitureController == null) {
      this.furnitureController = new FurnitureController(
          this.home, this.preferences, this.viewFactory, this.contentManager, getUndoableEditSupport());
    }
    return this.furnitureController;
  }

  /**
   * Returns the controller of home plan.
   */
  public PlanController getPlanController() {
    // Create sub controller lazily only once it's needed
    if (this.planController == null) {
      this.planController = new PlanController(
          this.home, this.preferences, this.viewFactory, this.contentManager, getUndoableEditSupport());
    }
    return this.planController;
  }

  /**
   * Returns the controller of home 3D view.
   */
  public HomeController3D getHomeController3D() {
    // Create sub controller lazily only once it's needed
    if (this.homeController3D == null) {
      this.homeController3D = new HomeController3D(
          this.home, this.preferences, this.viewFactory, this.contentManager, getUndoableEditSupport());
    }
    return this.homeController3D;
  }

  /**
   * Returns the undoable edit support managed by this controller.
   */
  protected final UndoableEditSupport getUndoableEditSupport() {
    return this.undoSupport;
  }
 
  /**
   * Adds listeners that updates the enabled / disabled state of actions.
   */
  private void addListeners() {
    // Save preferences when they change
    this.preferences.getFurnitureCatalog().addFurnitureListener(
        new FurnitureCatalogChangeListener(this));
    this.preferences.getTexturesCatalog().addTexturesListener(
        new TexturesCatalogChangeListener(this));
    UserPreferencesPropertiesChangeListener listener =
        new UserPreferencesPropertiesChangeListener(this);
    for (UserPreferences.Property property : UserPreferences.Property.values()) {
      this.preferences.addPropertyChangeListener(property, listener);
    }
     
    addCatalogSelectionListener();
    addHomeBackgroundImageListener();
    addNotUndoableModificationListeners();
    addHomeSelectionListener();
    addFurnitureSortListener();
    addUndoSupportListener();
    addHomeItemsListener();
    addPlanControllerListeners();
    addLanguageListener();
  }

  /**
   * Super class of catalog listeners that writes preferences each time a piece of furniture or a texture
   * is deleted or added in furniture or textures catalog.
   */
  private abstract static class UserPreferencesChangeListener {
    // Stores the currently writing preferences
    private static Set<UserPreferences> writingPreferences = new HashSet<UserPreferences>();
   
    public void writePreferences(final HomeController controller) {
      if (!writingPreferences.contains(controller.preferences)) {
        writingPreferences.add(controller.preferences);
        // Write preferences later once all catalog modifications are notified
        controller.getView().invokeLater(new Runnable() {
            public void run() {
              try {
                controller.preferences.write();
                writingPreferences.remove(controller.preferences);
              } catch (RecorderException ex) {
                controller.getView().showError(controller.preferences.getLocalizedString(
                    HomeController.class, "savePreferencesError"));
              }
            }
          });
      }
    }
  }

  /**
   * Furniture catalog listener that writes preferences each time a piece of furniture
   * is deleted or added in furniture catalog. This listener is bound to this controller
   * with a weak reference to avoid strong link between catalog and this controller. 
   */
  private static class FurnitureCatalogChangeListener extends UserPreferencesChangeListener
                                                      implements CollectionListener<CatalogPieceOfFurniture> {
    private WeakReference<HomeController> homeController;
   
    public FurnitureCatalogChangeListener(HomeController homeController) {
      this.homeController = new WeakReference<HomeController>(homeController);
    }
   
    public void collectionChanged(CollectionEvent<CatalogPieceOfFurniture> ev) {
      // If controller was garbage collected, remove this listener from catalog
      final HomeController controller = this.homeController.get();
      if (controller == null) {
        ((FurnitureCatalog)ev.getSource()).removeFurnitureListener(this);
      } else {
        writePreferences(controller);
      }
    }
  }

  /**
   * Textures catalog listener that writes preferences each time a texture
   * is deleted or added in textures catalog. This listener is bound to this controller
   * with a weak reference to avoid strong link between catalog and this controller. 
   */
  private static class TexturesCatalogChangeListener extends UserPreferencesChangeListener
                                                     implements CollectionListener<CatalogTexture> {
    private WeakReference<HomeController> homeController;
   
    public TexturesCatalogChangeListener(HomeController homeController) {
      this.homeController = new WeakReference<HomeController>(homeController);
    }
   
    public void collectionChanged(CollectionEvent<CatalogTexture> ev) {
      // If controller was garbage collected, remove this listener from catalog
      final HomeController controller = this.homeController.get();
      if (controller == null) {
        ((TexturesCatalog)ev.getSource()).removeTexturesListener(this);
      } else {
        writePreferences(controller);
      }
    }
  }

  /**
   * Properties listener that writes preferences each time the value of one of its properties changes.
   * This listener is bound to this controller with a weak reference to avoid strong link
   * between catalog and this controller. 
   */
  private static class UserPreferencesPropertiesChangeListener extends UserPreferencesChangeListener
                                                               implements PropertyChangeListener {
    private WeakReference<HomeController> homeController;
   
    public UserPreferencesPropertiesChangeListener(HomeController homeController) {
      this.homeController = new WeakReference<HomeController>(homeController);
    }
   
    public void propertyChange(PropertyChangeEvent ev) {
      // If controller was garbage collected, remove this listener from catalog
      final HomeController controller = this.homeController.get();
      if (controller == null) {
        ((UserPreferences)ev.getSource()).removePropertyChangeListener(
            UserPreferences.Property.valueOf(ev.getPropertyName()), this);
      } else {
        writePreferences(controller);
      }
    }
  }

  /**
   * Adds a selection listener to catalog that enables / disables Add Furniture action.
   */
  private void addCatalogSelectionListener() {
    getFurnitureCatalogController().addSelectionListener(new SelectionListener() {
          public void selectionChanged(SelectionEvent ev) {
            enableActionsBoundToSelection();
          }
        });
  }

  /**
   * Adds a property change listener to <code>preferences</code> to update
   * undo and redo presentation names when preferred language changes.
   */
  private void addLanguageListener() {
    this.preferences.addPropertyChangeListener(UserPreferences.Property.LANGUAGE,
        new LanguageChangeListener(this));
  }

  /**
   * Preferences property listener bound to this component with a weak reference to avoid
   * strong link between preferences and this component. 
   */
  private static class LanguageChangeListener implements PropertyChangeListener {
    private WeakReference<HomeController> homeController;

    public LanguageChangeListener(HomeController homeController) {
      this.homeController = new WeakReference<HomeController>(homeController);
    }
   
    public void propertyChange(PropertyChangeEvent ev) {
      // If home pane was garbage collected, remove this listener from preferences
      HomeController homeController = this.homeController.get();
      if (homeController == null) {
        ((UserPreferences)ev.getSource()).removePropertyChangeListener(
            UserPreferences.Property.LANGUAGE, this);
      } else {
        // Update undo and redo name
        homeController.getView().setUndoRedoName(
            homeController.undoManager.canUndo()
                ? homeController.undoManager.getUndoPresentationName()
                : null,
            homeController.undoManager.canRedo()
                ? homeController.undoManager.getRedoPresentationName()
                : null);
      }
    }
  }
 
  /**
   *  Adds a selection listener to home that enables / disables actions on selection.
   */
  private void addHomeSelectionListener() {
    if (this.home != null) {
      this.home.addSelectionListener(new SelectionListener() {
        public void selectionChanged(SelectionEvent ev) {
          enableActionsBoundToSelection();
        }
      });
    }
  }

  /**
   *  Adds a property change listener to home that enables / disables sort order action.
   */
  private void addFurnitureSortListener() {
    if (this.home != null) {
      this.home.addPropertyChangeListener(Home.Property.FURNITURE_SORTED_PROPERTY,
        new PropertyChangeListener() {
          public void propertyChange(PropertyChangeEvent ev) {
            getView().setEnabled(HomeView.ActionType.SORT_HOME_FURNITURE_BY_DESCENDING_ORDER,
                ev.getNewValue() != null);
          }
        });
    }
  }

  /**
   *  Adds a property change listener to home that enables / disables background image actions.
   */
  private void addHomeBackgroundImageListener() {
    if (this.home != null) {
      this.home.addPropertyChangeListener(Home.Property.BACKGROUND_IMAGE,
          new PropertyChangeListener() {
            public void propertyChange(PropertyChangeEvent ev) {
              BackgroundImage backgroundImage = (BackgroundImage)ev.getNewValue();
              boolean homeHasBackgroundImage = backgroundImage != null;
              getView().setEnabled(HomeView.ActionType.MODIFY_BACKGROUND_IMAGE, homeHasBackgroundImage);
              getView().setEnabled(HomeView.ActionType.HIDE_BACKGROUND_IMAGE,
                  homeHasBackgroundImage && backgroundImage.isVisible());
              getView().setEnabled(HomeView.ActionType.SHOW_BACKGROUND_IMAGE,
                  homeHasBackgroundImage && !backgroundImage.isVisible());
              getView().setEnabled(HomeView.ActionType.DELETE_BACKGROUND_IMAGE, homeHasBackgroundImage);
            }
          });
    }
  }

  /**
   * Adds listeners to track property changes that are not undoable.
   */
  private void addNotUndoableModificationListeners() {
    if (this.home != null) {
      final PropertyChangeListener notUndoableModificationListener = new PropertyChangeListener() {
          public void propertyChange(PropertyChangeEvent ev) {
            notUndoableModifications = true;
            home.setModified(true);
          }
        };
      this.home.addPropertyChangeListener(Home.Property.STORED_CAMERAS, notUndoableModificationListener);
      this.home.getEnvironment().addPropertyChangeListener(HomeEnvironment.Property.VIDEO_WIDTH, notUndoableModificationListener);
      this.home.getEnvironment().addPropertyChangeListener(HomeEnvironment.Property.VIDEO_ASPECT_RATIO, notUndoableModificationListener);
      this.home.getEnvironment().addPropertyChangeListener(HomeEnvironment.Property.VIDEO_FRAME_RATE, notUndoableModificationListener);
      this.home.getEnvironment().addPropertyChangeListener(HomeEnvironment.Property.VIDEO_QUALITY, notUndoableModificationListener);
      this.home.getEnvironment().addPropertyChangeListener(HomeEnvironment.Property.VIDEO_CAMERA_PATH, notUndoableModificationListener);
      this.home.getEnvironment().addPropertyChangeListener(HomeEnvironment.Property.CEILING_LIGHT_COLOR, notUndoableModificationListener);
      this.home.getEnvironment().addPropertyChangeListener(HomeEnvironment.Property.PHOTO_QUALITY, notUndoableModificationListener);
      this.home.getEnvironment().addPropertyChangeListener(HomeEnvironment.Property.PHOTO_ASPECT_RATIO, notUndoableModificationListener);
      PropertyChangeListener photoSizeModificationListener = new PropertyChangeListener() {
          public void propertyChange(PropertyChangeEvent ev) {
            if (home.getEnvironment().getPhotoAspectRatio() != AspectRatio.VIEW_3D_RATIO) {
              // Ignore photo size modification with 3D view aspect ratio since it can change for various reasons
              notUndoableModificationListener.propertyChange(ev);
            }
          }
        };
      this.home.getEnvironment().addPropertyChangeListener(HomeEnvironment.Property.PHOTO_WIDTH, photoSizeModificationListener);
      this.home.getEnvironment().addPropertyChangeListener(HomeEnvironment.Property.PHOTO_HEIGHT, photoSizeModificationListener);
      PropertyChangeListener timeOrLensModificationListener = new PropertyChangeListener() {
          public void propertyChange(PropertyChangeEvent ev) {
            if (ev.getPropertyName().equals(Camera.Property.TIME.name())
                || ev.getPropertyName().equals(Camera.Property.LENS.name())) {
              notUndoableModificationListener.propertyChange(ev);
            }
          }
        };
      this.home.getObserverCamera().addPropertyChangeListener(timeOrLensModificationListener);
      this.home.getTopCamera().addPropertyChangeListener(timeOrLensModificationListener);
    }
  }
 
  /**
   * Enables or disables action bound to selection.
   * This method will be called when selection in plan or in catalog changes and when
   * focused component or modification state in plan changes.
   */
  protected void enableActionsBoundToSelection() {
    boolean modificationState = getPlanController().isModificationState();
   
    // Search if catalog selection contains at least one piece
    List<CatalogPieceOfFurniture> catalogSelectedItems =
        getFurnitureCatalogController().getSelectedFurniture();   
    boolean catalogSelectionContainsFurniture = !catalogSelectedItems.isEmpty();
    boolean catalogSelectionContainsOneModifiablePiece = catalogSelectedItems.size() == 1
        && catalogSelectedItems.get(0).isModifiable();
   
    // Search if home selection contains at least one piece, one wall or one dimension line
    List<Selectable> selectedItems = this.home.getSelectedItems();
    boolean homeSelectionContainsDeletableItems = false;
    boolean homeSelectionContainsFurniture = false;
    boolean homeSelectionContainsDeletableFurniture = false;
    boolean homeSelectionContainsOneCopiableItemOrMore = false;
    boolean homeSelectionContainsTwoMovablePiecesOfFurnitureOrMore = false;
    boolean homeSelectionContainsFurnitureGroup = false;
    boolean homeSelectionContainsWalls = false;
    boolean homeSelectionContainsRooms = false;
    boolean homeSelectionContainsOneWall = false;
    boolean homeSelectionContainsOneLabel = false;
    boolean homeSelectionContainsItemsWithText = false;
    boolean homeSelectionContainsCompass = false;
    FurnitureController furnitureController = getFurnitureController();
    if (!modificationState) {
      for (Selectable item : selectedItems) {
        if (getPlanController().isItemDeletable(item)) {
          homeSelectionContainsDeletableItems = true;
          break;
        }
      }
      List<HomePieceOfFurniture> selectedFurniture = Home.getFurnitureSubList(selectedItems);
      homeSelectionContainsFurniture = !selectedFurniture.isEmpty();
      for (HomePieceOfFurniture piece : selectedFurniture) {
        if (furnitureController.isPieceOfFurnitureDeletable(piece)) {
          homeSelectionContainsDeletableFurniture = true;
          break;
        }
      }
      for (HomePieceOfFurniture piece : selectedFurniture) {
        if (piece instanceof HomeFurnitureGroup) {
          homeSelectionContainsFurnitureGroup = true;
          break;
        }
      }
      int movablePiecesOfFurnitureCount = 0;
      for (HomePieceOfFurniture piece : selectedFurniture) {
        if (furnitureController.isPieceOfFurnitureMovable(piece)) {
          movablePiecesOfFurnitureCount++;
          if (movablePiecesOfFurnitureCount >= 2) {
            homeSelectionContainsTwoMovablePiecesOfFurnitureOrMore = true;
            break;
          }
        }
      }
      List<Wall> selectedWalls = Home.getWallsSubList(selectedItems);
      homeSelectionContainsWalls = !selectedWalls.isEmpty();
      homeSelectionContainsOneWall = selectedWalls.size() == 1;
      homeSelectionContainsRooms = !Home.getRoomsSubList(selectedItems).isEmpty();
      boolean homeSelectionContainsDimensionLines = !Home.getDimensionLinesSubList(selectedItems).isEmpty();
      final List<Label> selectedLabels = Home.getLabelsSubList(selectedItems);
      boolean homeSelectionContainsLabels = !selectedLabels.isEmpty();
      homeSelectionContainsCompass = selectedItems.contains(this.home.getCompass());
      homeSelectionContainsOneLabel = selectedLabels.size() == 1;
      homeSelectionContainsOneCopiableItemOrMore =
          homeSelectionContainsFurniture || homeSelectionContainsWalls
          || homeSelectionContainsRooms || homeSelectionContainsDimensionLines
          || homeSelectionContainsLabels || homeSelectionContainsCompass;
      homeSelectionContainsItemsWithText =
          homeSelectionContainsFurniture || homeSelectionContainsRooms
          || homeSelectionContainsDimensionLines || homeSelectionContainsLabels;
    }

    HomeView view = getView();
    if (this.focusedView == getFurnitureCatalogController().getView()) {
      view.setEnabled(HomeView.ActionType.COPY,
          !modificationState && catalogSelectionContainsFurniture);
      view.setEnabled(HomeView.ActionType.CUT, false);
      view.setEnabled(HomeView.ActionType.DELETE, false);
      for (CatalogPieceOfFurniture piece : catalogSelectedItems) {
        if (piece.isModifiable()) {
          // Only modifiable catalog furniture may be deleted
          view.setEnabled(HomeView.ActionType.DELETE, true);
          break;
        }
      }
    } else if (this.focusedView == furnitureController.getView()) {
      view.setEnabled(HomeView.ActionType.COPY, homeSelectionContainsFurniture);
      view.setEnabled(HomeView.ActionType.CUT, homeSelectionContainsDeletableFurniture);
      view.setEnabled(HomeView.ActionType.DELETE, homeSelectionContainsDeletableFurniture);
    } else if (this.focusedView == getPlanController().getView()) {
      view.setEnabled(HomeView.ActionType.COPY, homeSelectionContainsOneCopiableItemOrMore);
      view.setEnabled(HomeView.ActionType.CUT, homeSelectionContainsDeletableItems);
      view.setEnabled(HomeView.ActionType.DELETE, homeSelectionContainsDeletableItems);
    } else {
      view.setEnabled(HomeView.ActionType.COPY, false);
      view.setEnabled(HomeView.ActionType.CUT, false);
      view.setEnabled(HomeView.ActionType.DELETE, false);
    }

    view.setEnabled(HomeView.ActionType.ADD_HOME_FURNITURE, catalogSelectionContainsFurniture);
    // In creation mode all actions bound to selection are disabled
    view.setEnabled(HomeView.ActionType.DELETE_HOME_FURNITURE,
        homeSelectionContainsDeletableFurniture);
    view.setEnabled(HomeView.ActionType.DELETE_SELECTION,
        (catalogSelectionContainsFurniture
            && this.focusedView == getFurnitureCatalogController().getView())
        || (homeSelectionContainsDeletableItems
            && (this.focusedView == furnitureController.getView()
                || this.focusedView == getPlanController().getView()
                || this.focusedView == getHomeController3D().getView())));
    view.setEnabled(HomeView.ActionType.MODIFY_FURNITURE,
        (catalogSelectionContainsOneModifiablePiece
             && this.focusedView == getFurnitureCatalogController().getView())
        || (homeSelectionContainsFurniture
             && (this.focusedView == furnitureController.getView()
                 || this.focusedView == getPlanController().getView()
                 || this.focusedView == getHomeController3D().getView())));
    view.setEnabled(HomeView.ActionType.MODIFY_WALL,
        homeSelectionContainsWalls);
    view.setEnabled(HomeView.ActionType.REVERSE_WALL_DIRECTION,
        homeSelectionContainsWalls);
    view.setEnabled(HomeView.ActionType.SPLIT_WALL,
        homeSelectionContainsOneWall);
    view.setEnabled(HomeView.ActionType.MODIFY_ROOM,
        homeSelectionContainsRooms);
    view.setEnabled(HomeView.ActionType.MODIFY_LABEL,
        homeSelectionContainsOneLabel);
    view.setEnabled(HomeView.ActionType.TOGGLE_BOLD_STYLE,
        homeSelectionContainsItemsWithText);
    view.setEnabled(HomeView.ActionType.TOGGLE_ITALIC_STYLE,
        homeSelectionContainsItemsWithText);
    view.setEnabled(HomeView.ActionType.INCREASE_TEXT_SIZE,
        homeSelectionContainsItemsWithText);
    view.setEnabled(HomeView.ActionType.DECREASE_TEXT_SIZE,
        homeSelectionContainsItemsWithText);
    view.setEnabled(HomeView.ActionType.ALIGN_FURNITURE_ON_TOP,
        homeSelectionContainsTwoMovablePiecesOfFurnitureOrMore);
    view.setEnabled(HomeView.ActionType.ALIGN_FURNITURE_ON_BOTTOM,
        homeSelectionContainsTwoMovablePiecesOfFurnitureOrMore);
    view.setEnabled(HomeView.ActionType.ALIGN_FURNITURE_ON_LEFT,
        homeSelectionContainsTwoMovablePiecesOfFurnitureOrMore);
    view.setEnabled(HomeView.ActionType.ALIGN_FURNITURE_ON_RIGHT,
        homeSelectionContainsTwoMovablePiecesOfFurnitureOrMore);
    view.setEnabled(HomeView.ActionType.GROUP_FURNITURE,
        homeSelectionContainsTwoMovablePiecesOfFurnitureOrMore);
    view.setEnabled(HomeView.ActionType.UNGROUP_FURNITURE,
        homeSelectionContainsFurnitureGroup);
  }

  /**
   * Enables clipboard paste action if clipboard isn't empty.
   */
  public void enablePasteAction() {
    HomeView view = getView();
    if (this.focusedView == getFurnitureController().getView()
        || this.focusedView == getPlanController().getView()) {
      view.setEnabled(HomeView.ActionType.PASTE,
          !getPlanController().isModificationState() && !view.isClipboardEmpty());
    } else {
      view.setEnabled(HomeView.ActionType.PASTE, false);
    }
  }

  /**
   * Enables select all action if home isn't empty.
   */
  protected void enableSelectAllAction() {
    HomeView view = getView();
    boolean modificationState = getPlanController().isModificationState();
    if (this.focusedView == getFurnitureController().getView()) {
      view.setEnabled(HomeView.ActionType.SELECT_ALL,
          !modificationState
          && this.home.getFurniture().size() > 0);
    } else if (this.focusedView == getPlanController().getView()
               || this.focusedView == getHomeController3D().getView()) {
      boolean homeContainsOneSelectableItemOrMore = !this.home.isEmpty()
          || this.home.getCompass().isVisible();
      view.setEnabled(HomeView.ActionType.SELECT_ALL,
          !modificationState && homeContainsOneSelectableItemOrMore);
    } else {
      view.setEnabled(HomeView.ActionType.SELECT_ALL, false);
    }
  }

  /**
   * Enables zoom actions depending on current scale.
   */
  private void enableZoomActions() {
    PlanController planController = getPlanController();
    float scale = planController.getScale();
    HomeView view = getView();
    view.setEnabled(HomeView.ActionType.ZOOM_IN, scale < planController.getMaximumScale());
    view.setEnabled(HomeView.ActionType.ZOOM_OUT, scale > planController.getMinimumScale());   
  }
 
  /**
   * Adds undoable edit listener to undo support that enables Undo action.
   */
  private void addUndoSupportListener() {
    getUndoableEditSupport().addUndoableEditListener(
      new UndoableEditListener () {
        public void undoableEditHappened(UndoableEditEvent ev) {
          HomeView view = getView();
          view.setEnabled(HomeView.ActionType.UNDO,
              !getPlanController().isModificationState());
          view.setEnabled(HomeView.ActionType.REDO, false);
          view.setUndoRedoName(ev.getEdit().getUndoPresentationName(), null);
          saveUndoLevel++;
          home.setModified(true);
        }
      });
   home.addPropertyChangeListener(Home.Property.MODIFIED, new PropertyChangeListener() {
      public void propertyChange(PropertyChangeEvent ev) {
        if (!home.isModified()) {
          // Change undo level and modification flag if home is set as unmodified
          saveUndoLevel = 0;
          notUndoableModifications = false;
        }
      }
    });
  }

  /**
   * Adds a furniture listener to home that enables / disables actions on furniture list change.
   */
  @SuppressWarnings("unchecked")
  private void addHomeItemsListener() {
    CollectionListener homeItemsListener =
        new CollectionListener() {
          public void collectionChanged(CollectionEvent ev) {
            if (ev.getType() == CollectionEvent.Type.ADD
                || ev.getType() == CollectionEvent.Type.DELETE) {
              enableSelectAllAction();
            }
          }
        };
    this.home.addFurnitureListener((CollectionListener<HomePieceOfFurniture>)homeItemsListener);
    this.home.addWallsListener((CollectionListener<Wall>)homeItemsListener);
    this.home.addRoomsListener((CollectionListener<Room>)homeItemsListener);
    this.home.addDimensionLinesListener((CollectionListener<DimensionLine>)homeItemsListener);
    this.home.addLabelsListener((CollectionListener<Label>)homeItemsListener);
    this.home.getCompass().addPropertyChangeListener(new PropertyChangeListener() {
        public void propertyChange(PropertyChangeEvent ev) {
          if (Compass.Property.VISIBLE.equals(ev.getPropertyName())) {
            enableSelectAllAction();
          }
        }
      });
  }

  /**
   * Adds a property change listener to plan controller to
   * enable/disable authorized actions according to its modification state and the plan scale.
   */
  private void addPlanControllerListeners() {
    getPlanController().addPropertyChangeListener(PlanController.Property.MODIFICATION_STATE,
        new PropertyChangeListener() {
          public void propertyChange(PropertyChangeEvent ev) {
            enableActionsBoundToSelection();
            enableSelectAllAction();
            HomeView view = getView();
            if (getPlanController().isModificationState()) {
              view.setEnabled(HomeView.ActionType.PASTE, false);
              view.setEnabled(HomeView.ActionType.UNDO, false);
              view.setEnabled(HomeView.ActionType.REDO, false);
            } else {
              enablePasteAction();
              view.setEnabled(HomeView.ActionType.UNDO, undoManager.canUndo());
              view.setEnabled(HomeView.ActionType.REDO, undoManager.canRedo());
            }
          }
        });
    getPlanController().addPropertyChangeListener(PlanController.Property.SCALE,
        new PropertyChangeListener() {
          public void propertyChange(PropertyChangeEvent ev) {
            enableZoomActions();
          }
        });
  }
 
  /**
   * Adds the selected furniture in catalog to home and selects it. 
   */
  public void addHomeFurniture() {
    // Use automatically selection mode 
    getPlanController().setMode(PlanController.Mode.SELECTION);
    List<CatalogPieceOfFurniture> selectedFurniture =
      getFurnitureCatalogController().getSelectedFurniture();
    if (!selectedFurniture.isEmpty()) {
      List<HomePieceOfFurniture> newFurniture =
          new ArrayList<HomePieceOfFurniture>();
      for (CatalogPieceOfFurniture piece : selectedFurniture) {
        HomePieceOfFurniture homePiece;
        if (piece instanceof CatalogDoorOrWindow) {
          homePiece = new HomeDoorOrWindow((DoorOrWindow)piece);
        } else if (piece instanceof CatalogLight) {
          homePiece = new HomeLight((Light)piece);
        } else {
          homePiece = new HomePieceOfFurniture(piece);
        }
        // If magnetism is enabled, adjust piece size and elevation
        if (this.preferences.isMagnetismEnabled()) {
          if (homePiece.isResizable()) {
            homePiece.setWidth(this.preferences.getLengthUnit().getMagnetizedLength(homePiece.getWidth(), 0.1f));
            homePiece.setDepth(this.preferences.getLengthUnit().getMagnetizedLength(homePiece.getDepth(), 0.1f));
            homePiece.setHeight(this.preferences.getLengthUnit().getMagnetizedLength(homePiece.getHeight(), 0.1f));
          }
          homePiece.setElevation(this.preferences.getLengthUnit().getMagnetizedLength(homePiece.getElevation(), 0.1f));
        }
        newFurniture.add(homePiece);
      }
      // Add newFurniture to home with furnitureController
      getFurnitureController().addFurniture(newFurniture);
    }
  }
 
  /**
   * Modifies the selected furniture of the focused view. 
   */
  public void modifySelectedFurniture() {
    if (this.focusedView == getFurnitureCatalogController().getView()) {
      getFurnitureCatalogController().modifySelectedFurniture();
    } else if (this.focusedView == getFurnitureController().getView()
               || this.focusedView == getPlanController().getView()
               || this.focusedView == getHomeController3D().getView()) {
      getFurnitureController().modifySelectedFurniture();
    }   
  }
 
  /**
   * Imports a given language library.
   */
  public void importLanguageLibrary(String languageLibraryName) {
    try {
      if (!this.preferences.languageLibraryExists(languageLibraryName)
          || getView().confirmReplaceLanguageLibrary(languageLibraryName)) {
        this.preferences.addLanguageLibrary(languageLibraryName);
      }
    } catch (RecorderException ex) {
      String message = this.preferences.getLocalizedString(HomeController.class,
          "importLanguageLibraryError", languageLibraryName);
      getView().showError(message);
    }
  }

  /**
   * Imports furniture to the catalog or home depending on the focused view. 
   */
  public void importFurniture() {
    // Always use selection mode after an import furniture operation
    getPlanController().setMode(PlanController.Mode.SELECTION);
    if (this.focusedView == getFurnitureCatalogController().getView()) {
      getFurnitureCatalogController().importFurniture();
    } else {
      getFurnitureController().importFurniture();
    }   
  }

  /**
   * Imports a furniture library chosen by the user. 
   */
  public void importFurnitureLibrary() {
    getView().invokeLater(new Runnable() {
        public void run() {
          final String furnitureLibraryName = getView().showImportFurnitureLibraryDialog();
          if (furnitureLibraryName != null) {
            importFurnitureLibrary(furnitureLibraryName);
          }
        }
      });
  }

  /**
   * Imports a given furniture library.
   */
  public void importFurnitureLibrary(String furnitureLibraryName) {
    try {
      if (!this.preferences.furnitureLibraryExists(furnitureLibraryName)
          || getView().confirmReplaceFurnitureLibrary(furnitureLibraryName)) {
        this.preferences.addFurnitureLibrary(furnitureLibraryName);
      }
    } catch (RecorderException ex) {
      String message = this.preferences.getLocalizedString(HomeController.class,
          "importFurnitureLibraryError", furnitureLibraryName);
      getView().showError(message);
    }
  }

  /**
   * Imports a textures library chosen by the user. 
   */
  public void importTexturesLibrary() {
    getView().invokeLater(new Runnable() {
        public void run() {
          final String texturesLibraryName = getView().showImportTexturesLibraryDialog();
          if (texturesLibraryName != null) {
            importTexturesLibrary(texturesLibraryName);
          }
        }
      });
  }

  /**
   * Imports a given textures library.
   */
  public void importTexturesLibrary(String texturesLibraryName) {
    try {
      if (!this.preferences.texturesLibraryExists(texturesLibraryName)
          || getView().confirmReplaceTexturesLibrary(texturesLibraryName)) {
        this.preferences.addTexturesLibrary(texturesLibraryName);
      }
    } catch (RecorderException ex) {
      String message = this.preferences.getLocalizedString(HomeController.class,
          "importTexturesLibraryError", texturesLibraryName);
      getView().showError(message);
    }
  }

  /**
   * Imports a given plugin.
   */
  public void importPlugin(String pluginName) {
    if (this.pluginManager != null) {
      try {
        if (!this.pluginManager.pluginExists(pluginName)
            || getView().confirmReplacePlugin(pluginName)) {
          this.pluginManager.addPlugin(pluginName);
          getView().showMessage(this.preferences.getLocalizedString(HomeController.class,
              "importedPluginMessage"));
        }
      } catch (RecorderException ex) {
        String message = this.preferences.getLocalizedString(HomeController.class,
            "importPluginError", pluginName);
        getView().showError(message);
      }
    }
  }
 
  /**
   * Undoes last operation.
   */
  public void undo() {
    this.undoManager.undo();
    HomeView view = getView();
    boolean moreUndo = this.undoManager.canUndo();
    view.setEnabled(HomeView.ActionType.UNDO, moreUndo);
    view.setEnabled(HomeView.ActionType.REDO, true);
    if (moreUndo) {
      view.setUndoRedoName(this.undoManager.getUndoPresentationName(),
          this.undoManager.getRedoPresentationName());
    } else {
      view.setUndoRedoName(null, this.undoManager.getRedoPresentationName());
    }
    this.saveUndoLevel--;
    this.home.setModified(this.saveUndoLevel != 0 || this.notUndoableModifications);
  }
 
  /**
   * Redoes last undone operation.
   */
  public void redo() {
    this.undoManager.redo();
    HomeView view = getView();
    boolean moreRedo = this.undoManager.canRedo();
    view.setEnabled(HomeView.ActionType.UNDO, true);
    view.setEnabled(HomeView.ActionType.REDO, moreRedo);
    if (moreRedo) {
      view.setUndoRedoName(this.undoManager.getUndoPresentationName(),
          this.undoManager.getRedoPresentationName());
    } else {
      view.setUndoRedoName(this.undoManager.getUndoPresentationName(), null);
    }
    this.saveUndoLevel++;
    this.home.setModified(this.saveUndoLevel != 0 || this.notUndoableModifications);
  }

  /**
   * Deletes items and post a cut operation to undo support.
   */
  public void cut(List<? extends Selectable> items) {
    // Start a compound edit that deletes items and changes presentation name
    UndoableEditSupport undoSupport = getUndoableEditSupport();
    undoSupport.beginUpdate();
    getPlanController().deleteItems(items);
    // Add a undoable edit to change presentation name
    undoSupport.postEdit(new AbstractUndoableEdit() {
        @Override
        public String getPresentationName() {
          return preferences.getLocalizedString(HomeController.class, "undoCutName");
        }     
      });
    // End compound edit
    undoSupport.endUpdate();
  }
 
  /**
   * Adds items to home and posts a paste operation to undo support.
   */
  public void paste(final List<? extends Selectable> items) {
    // Check if pasted items and currently selected items overlap
    List<Selectable> selectedItems = this.home.getSelectedItems();
    float pastedItemsDelta = 0;
    if (items.size() == selectedItems.size()) {
      // The default delta used to be able to distinguish dropped items from previous selection
      pastedItemsDelta = 20;
      for (Selectable pastedItem : items) {     
        // Search which item of selected items it may overlap
        float [][] pastedItemPoints = pastedItem.getPoints();
        boolean pastedItemOverlapSelectedItem = false;
        for (Selectable selectedItem : selectedItems) {
          if (Arrays.deepEquals(pastedItemPoints, selectedItem.getPoints())) {
            pastedItemOverlapSelectedItem = true;
            break;
          }
        }
        if (!pastedItemOverlapSelectedItem) {
          pastedItemsDelta = 0;
          break;
        }
      }
    }
    addItems(items, pastedItemsDelta, pastedItemsDelta, false, "undoPasteName");
  }

  /**
   * Adds items to home, moves them of (dx, dy)
   * and posts a drop operation to undo support.
   */
  public void drop(final List<? extends Selectable> items, float dx, float dy) {
    drop(items, null, dx, dy);
  }

  /**
   * Adds items to home, moves them of (dx, dy)
   * and posts a drop operation to undo support.
   */
  public void drop(final List<? extends Selectable> items, View destinationView, float dx, float dy) {
    addItems(items, dx, dy, destinationView == getPlanController().getView(), "undoDropName");
  }

  /**
   * Adds items to home.
   */
  private void addItems(final List<? extends Selectable> items,
                        float dx, float dy, final boolean isDropInPlanView,
                        final String presentationNameKey) {
    if (items.size() > 1
        || (items.size() == 1
            && !(items.get(0) instanceof Compass))) {
      // Always use selection mode after a drop or a paste operation
      getPlanController().setMode(PlanController.Mode.SELECTION);
      // Start a compound edit that adds walls, furniture, rooms, dimension lines and labels to home
      UndoableEditSupport undoSupport = getUndoableEditSupport();
      undoSupport.beginUpdate();
      List<HomePieceOfFurniture> addedFurniture = Home.getFurnitureSubList(items);
      // If magnetism is enabled, adjust furniture size and elevation
      if (this.preferences.isMagnetismEnabled()) {
        for (HomePieceOfFurniture piece : addedFurniture) {
          if (piece.isResizable()) {
            piece.setWidth(this.preferences.getLengthUnit().getMagnetizedLength(piece.getWidth(), 0.1f));
            piece.setDepth(this.preferences.getLengthUnit().getMagnetizedLength(piece.getDepth(), 0.1f));
            piece.setHeight(this.preferences.getLengthUnit().getMagnetizedLength(piece.getHeight(), 0.1f));
          }
          piece.setElevation(this.preferences.getLengthUnit().getMagnetizedLength(piece.getElevation(), 0.1f));
        }
      }
      getPlanController().moveItems(items, dx, dy);
      if (isDropInPlanView
          && this.preferences.isMagnetismEnabled()
          && items.size() == 1
          && addedFurniture.size() == 1) {
        // Adjust piece when it's dropped in plan view 
        getPlanController().adjustMagnetizedPieceOfFurniture((HomePieceOfFurniture)items.get(0), dx, dy);
      }
      getPlanController().addFurniture(addedFurniture);
      getPlanController().addWalls(Home.getWallsSubList(items));
      getPlanController().addRooms(Home.getRoomsSubList(items));
      getPlanController().addDimensionLines(Home.getDimensionLinesSubList(items));
      getPlanController().addLabels(Home.getLabelsSubList(items));
      this.home.setSelectedItems(items);
 
      // Add a undoable edit that will select all the items at redo
      undoSupport.postEdit(new AbstractUndoableEdit() {     
          @Override
          public void redo() throws CannotRedoException {
            super.redo();
            home.setSelectedItems(items);
          }
 
          @Override
          public String getPresentationName() {
            return preferences.getLocalizedString(HomeController.class, presentationNameKey);
          }     
        });
    
      // End compound edit
      undoSupport.endUpdate();
    }
  }

  /**
   * Adds imported models to home, moves them of (dx, dy)
   * and post a drop operation to undo support.
   */
  public void dropFiles(final List<String> importableModels, float dx, float dy) {
    // Always use selection mode after a drop operation
    getPlanController().setMode(PlanController.Mode.SELECTION);
    // Add to home a listener to track imported furniture
    final List<HomePieceOfFurniture> importedFurniture =
        new ArrayList<HomePieceOfFurniture>(importableModels.size());
    CollectionListener<HomePieceOfFurniture> addedFurnitureListener =
        new CollectionListener<HomePieceOfFurniture>() {
          public void collectionChanged(CollectionEvent<HomePieceOfFurniture> ev) {
            importedFurniture.add(ev.getItem());
          }
        };
    this.home.addFurnitureListener(addedFurnitureListener);
   
    // Start a compound edit that adds furniture to home
    UndoableEditSupport undoSupport = getUndoableEditSupport();
    undoSupport.beginUpdate();
    // Import furniture
    for (String model : importableModels) {
      getFurnitureController().importFurniture(model);
    }
    this.home.removeFurnitureListener(addedFurnitureListener);
   
    if (importedFurniture.size() > 0) {
      getPlanController().moveItems(importedFurniture, dx, dy);
      this.home.setSelectedItems(importedFurniture);
     
      // Add a undoable edit that will select the imported furniture at redo
      undoSupport.postEdit(new AbstractUndoableEdit() {     
          @Override
          public void redo() throws CannotRedoException {
            super.redo();
            home.setSelectedItems(importedFurniture);
          }
 
          @Override
          public String getPresentationName() {
            return preferences.getLocalizedString(HomeController.class, "undoDropName");
          }     
        });
    }
  
    // End compound edit
    undoSupport.endUpdate();
  }

  /**
   * Deletes the selection in the focused component.
   */
  public void delete() {
    if (this.focusedView == getFurnitureCatalogController().getView()) {
      if (getView().confirmDeleteCatalogSelection()) {
        getFurnitureCatalogController().deleteSelection();
      }
    } else if (this.focusedView == getFurnitureController().getView()) {
      getFurnitureController().deleteSelection();
    } else if (this.focusedView == getPlanController().getView()) {
      getPlanController().deleteSelection();
    }
  }
 
  /**
   * Updates actions when focused view changed.
   */
  public void focusedViewChanged(View focusedView) {
    this.focusedView = focusedView;
    enableActionsBoundToSelection();
    enablePasteAction();
    enableSelectAllAction();
  }
 
  /**
   * Selects everything in the focused component.
   */
  public void selectAll() {
    if (this.focusedView == getFurnitureController().getView()) {
      getFurnitureController().selectAll();
    } else if (this.focusedView == getPlanController().getView()
               || this.focusedView == getHomeController3D().getView()) {
      getPlanController().selectAll();
    }
  }

  /**
   * Creates a new home and adds it to application home list.
   */
  public void newHome() {
    Home home;
    if (this.application != null) {
      home = this.application.createHome();
    } else {
      home = new Home(this.preferences.getNewWallHeight());
    }
    this.application.addHome(home);
  }

  /**
   * Opens a home. This method displays an {@link HomeView#showOpenDialog() open dialog}
   * in view, reads the home from the chosen name and adds it to application home list.
   */
  public void open() {
    getView().invokeLater(new Runnable() {
      public void run() {
        final String homeName = getView().showOpenDialog();
        if (homeName != null) {
          open(homeName);
        }
      }
    });
  }

  /**
   * Opens a given <code>homeName</code>home.
   */
  public void open(final String homeName) {
    // Check if requested home isn't already opened
    for (Home home : this.application.getHomes()) {
      if (homeName.equals(home.getName())) {
        String message = this.preferences.getLocalizedString(
            HomeController.class, "alreadyOpen", homeName);
        getView().showMessage(message);
        return;
      }
    }
   
    // Read home in a threaded task
    Callable<Void> openTask = new Callable<Void>() {
          public Void call() throws RecorderException {
            // Read home with application recorder
            Home openedHome = application.getHomeRecorder().readHome(homeName);
            openedHome.setName(homeName);
            addHomeToApplication(openedHome);
            return null;
          }
        };
    ThreadedTaskController.ExceptionHandler exceptionHandler =
        new ThreadedTaskController.ExceptionHandler() {
          public void handleException(Exception ex) {
            if (!(ex instanceof InterruptedRecorderException)) {
              if (ex instanceof RecorderException) {
                String message = preferences.getLocalizedString(
                    HomeController.class, "openError", homeName);
                getView().showError(message);
              } else {
                ex.printStackTrace();
              }
            }
          }
        };
    new ThreadedTaskController(openTask,
        this.preferences.getLocalizedString(HomeController.class, "openMessage"), exceptionHandler,
        this.preferences, this.viewFactory).executeTask(getView());
  }
 
  /**
   * Adds the given home to application.
   */
  private void addHomeToApplication(final Home home) {
    getView().invokeLater(new Runnable() {
        public void run() {
          application.addHome(home);
        }
      });
  }
 
  /**
   * Updates user preferences <code>recentHomes</code> and write preferences.
   */
  private void updateUserPreferencesRecentHomes(List<String> recentHomes) {
    if (this.application != null) {
      // Check every recent home exists
      for (Iterator<String> it = recentHomes.iterator(); it.hasNext(); ) {
        try {
          if (!this.application.getHomeRecorder().exists(it.next())) {
            it.remove();
          }
        } catch (RecorderException ex) {
          // If homeName can't be checked ignore it
        }
      }
      this.preferences.setRecentHomes(recentHomes);
    }
  }

  /**
   * Returns a list of displayable recent homes.
   */
  public List<String> getRecentHomes() {
    if (this.application != null) {
      List<String> recentHomes = new ArrayList<String>();
      for (String homeName : this.preferences.getRecentHomes()) {
        try {
          if (this.application.getHomeRecorder().exists(homeName)) {
            recentHomes.add(homeName);
            if (recentHomes.size() == this.preferences.getRecentHomesMaxCount()) {
              break;
            }
          }
        } catch (RecorderException ex) {
          // If homeName can't be checked ignore it
        }
      }
      getView().setEnabled(HomeView.ActionType.DELETE_RECENT_HOMES,
          !recentHomes.isEmpty());
      return Collections.unmodifiableList(recentHomes);
    } else {
      return new ArrayList<String>();
    }
  }
 
  /**
   * Returns the version of the application.
   */
  public String getVersion() {
    if (this.application != null) {
      return this.application.getVersion();
    } else {
      return "";
    }
  }
 
  /**
   * Deletes the list of recent homes in user preferences.
   */
  public void deleteRecentHomes() {
    updateUserPreferencesRecentHomes(new ArrayList<String>());
    getView().setEnabled(HomeView.ActionType.DELETE_RECENT_HOMES, false);
  }
 
  /**
   * Manages home close operation. If the home managed by this controller is modified,
   * this method will {@link HomeView#confirmSave(String) confirm}
   * in view whether home should be saved. Once home is actually saved,
   * home is removed from application homes list.
   */
  public void close() {
    close(null);
  }

 
  /**
   * Manages home close operation. If the home managed by this controller is modified,
   * this method will {@link HomeView#confirmSave(String) confirm}
   * in view whether home should be saved. Once home is actually saved,
   * home is removed from application homes list and postCloseTask is called if
   * it's not <code>null</code>.
   */
  protected void close(final Runnable postCloseTask) {
    // Create a task that deletes home and run postCloseTask
    Runnable closeTask = new Runnable() {
        public void run() {
          home.setRecovered(false);
          application.deleteHome(home);
          if (postCloseTask != null) {
            postCloseTask.run();
          }
        }
      };
     
    if (this.home.isModified()  || this.home.isRecovered()) {
      switch (getView().confirmSave(this.home.getName())) {
        case SAVE   : save(HomeRecorder.Type.DEFAULT, closeTask); // Falls through
        case CANCEL : return;
     
    }
    closeTask.run();
  }
 
  /**
   * Saves the home managed by this controller. If home name doesn't exist,
   * this method will act as {@link #saveAs() saveAs} method.
   */
  public void save() {
    save(HomeRecorder.Type.DEFAULT, null);
  }

  /**
   * Saves the home managed by this controller and executes <code>postSaveTask</code>
   * if it's not <code>null</code>.
   */
  private void save(HomeRecorder.Type recorderType, Runnable postSaveTask) {
    if (this.home.getName() == null) {
      saveAs(recorderType, postSaveTask);
    } else {
      save(this.home.getName(), recorderType, postSaveTask);
    }
  }
 
  /**
   * Saves the home managed by this controller with a different name.
   * This method displays a {@link HomeView#showSaveDialog(String) save dialog} in  view,
   * and saves home with the chosen name if any.
   */
  public void saveAs() {
    saveAs(HomeRecorder.Type.DEFAULT, null);
  }

  /**
   * Saves the home managed by this controller with a different name.
   */
  private void saveAs(HomeRecorder.Type recorderType, Runnable postSaveTask) {
    String newName = getView().showSaveDialog(this.home.getName());
    if (newName != null) {
      save(newName, recorderType, postSaveTask);
    }
  }

  /**
   * Saves the home managed by this controller and compresses it. If home name doesn't exist,
   * this method will prompt user to choose a home name.
   */
  public void saveAndCompress() {
    save(HomeRecorder.Type.COMPRESSED, null);
  }
 
  /**
   * Actually saves the home managed by this controller and executes <code>postSaveTask</code>
   * if it's not <code>null</code>.
   */
  private void save(final String homeName,
                    final HomeRecorder.Type recorderType,
                    final Runnable postSaveTask) {
    // If home version is older than current version
    // or if home name is changed
    // or if user confirms to save a home created with a newer version
    if (this.home.getVersion() <= Home.CURRENT_VERSION
        || !homeName.equals(this.home.getName())
        || getView().confirmSaveNewerHome(homeName)) {
      // Save home in a threaded task
      Callable<Void> saveTask = new Callable<Void>() {
            public Void call() throws RecorderException {
              // Write home with application recorder
              application.getHomeRecorder(recorderType).writeHome(home, homeName);
              updateSavedHome(homeName, postSaveTask);
              return null;
            }
          };
      ThreadedTaskController.ExceptionHandler exceptionHandler =
          new ThreadedTaskController.ExceptionHandler() {
            public void handleException(Exception ex) {
              if (!(ex instanceof InterruptedRecorderException)) {
                if (ex instanceof RecorderException) {
                  String message = preferences.getLocalizedString(
                      HomeController.class, "saveError", homeName);
                  getView().showError(message);
                } else {
                  ex.printStackTrace();
                }
              }
            }
          };
      new ThreadedTaskController(saveTask,
          this.preferences.getLocalizedString(HomeController.class, "saveMessage"), exceptionHandler,
          this.preferences, this.viewFactory).executeTask(getView());
    }
  }
 
  /**
   * Updates the saved home and executes <code>postSaveTask</code>
   * if it's not <code>null</code>.
   */
  private void updateSavedHome(final String homeName,
                               final Runnable postSaveTask) {
    getView().invokeLater(new Runnable() {
        public void run() {
          home.setName(homeName);
          home.setModified(false);
          home.setRecovered(false);
          // Update recent homes list
          List<String> recentHomes = new ArrayList<String>(preferences.getRecentHomes());
          int homeNameIndex = recentHomes.indexOf(homeName);
          if (homeNameIndex >= 0) {
            recentHomes.remove(homeNameIndex);
          }
          recentHomes.add(0, homeName);
          updateUserPreferencesRecentHomes(recentHomes);
         
          if (postSaveTask != null) {
            postSaveTask.run();
          }
        }
      });
  }

  /**
   * Controls the export of the 3D view of current home to a SVG file.
   */
  public void exportToSVG() {
    final String svgName = getView().showExportToSVGDialog(this.home.getName());   
    if (svgName != null) {
      // Export 3D view in a threaded task
      Callable<Void> exportToSvgTask = new Callable<Void>() {
            public Void call() throws RecorderException {
              getView().exportToSVG(svgName);
              return null;
            }
          };
      ThreadedTaskController.ExceptionHandler exceptionHandler =
          new ThreadedTaskController.ExceptionHandler() {
            public void handleException(Exception ex) {
              if (!(ex instanceof InterruptedRecorderException)) {
                if (ex instanceof RecorderException) {
                  String message = preferences.getLocalizedString(
                      HomeController.class, "exportToSVGError", svgName);
                  getView().showError(message);
                } else {
                  ex.printStackTrace();
                }
              }
            }
          };
      new ThreadedTaskController(exportToSvgTask,
          this.preferences.getLocalizedString(HomeController.class, "exportToSVGMessage"), exceptionHandler,
          this.preferences, this.viewFactory).executeTask(getView());
    }
  }
 
  /**
   * Controls the export of the 3D view of current home to an OBJ file.
   */
  public void exportToOBJ() {
    final String objName = getView().showExportToOBJDialog(this.home.getName());   
    if (objName != null) {
      // Export 3D view in a threaded task
      Callable<Void> exportToObjTask = new Callable<Void>() {
            public Void call() throws RecorderException {
              getView().exportToOBJ(objName);
              return null;
            }
          };
      ThreadedTaskController.ExceptionHandler exceptionHandler =
          new ThreadedTaskController.ExceptionHandler() {
            public void handleException(Exception ex) {
              if (!(ex instanceof InterruptedRecorderException)) {
                if (ex instanceof RecorderException) {
                  String message = preferences.getLocalizedString(
                      HomeController.class, "exportToOBJError", objName);
                  getView().showError(message);
                } else {
                  ex.printStackTrace();
                }
              }
            }
          };
      new ThreadedTaskController(exportToObjTask,
          this.preferences.getLocalizedString(HomeController.class, "exportToOBJMessage"), exceptionHandler,
          this.preferences, this.viewFactory).executeTask(getView());
    }
  }
 
  /**
   * Controls the creation of photo-realistic images.
   */
  public void createPhoto() {
    PhotoController photoController = new PhotoController(this.home, this.preferences,
        getHomeController3D().getView(), this.viewFactory, this.contentManager);
    photoController.displayView(getView());
  }
 
  /**
   * Controls the creation of 3D videos.
   */
  public void createVideo() {
    getPlanController().setMode(PlanController.Mode.SELECTION);
    getHomeController3D().viewFromObserver();
    VideoController videoController = new VideoController(this.home, this.preferences,
        this.viewFactory, this.contentManager);
    videoController.displayView(getView());
  }
 
  /**
   * Controls page setup.
   */
  public void setupPage() {
    new PageSetupController(this.home, this.preferences,
        this.viewFactory, getUndoableEditSupport()).displayView(getView());
  }

  /**
   * Controls the print preview.
   */
  public void previewPrint() {
    new PrintPreviewController(this.home, this.preferences,
        this, this.viewFactory).displayView(getView());
  }

  /**
   * Controls the print of this home.
   */
  public void print() {
    final Callable<Void> printTask = getView().showPrintDialog();   
    if (printTask != null) {
      // Print in a threaded task
      ThreadedTaskController.ExceptionHandler exceptionHandler =
          new ThreadedTaskController.ExceptionHandler() {
            public void handleException(Exception ex) {
              if (!(ex instanceof InterruptedRecorderException)) {
                if (ex instanceof RecorderException) {
                  String message = preferences.getLocalizedString(
                      HomeController.class, "printError", home.getName());
                  getView().showError(message);
                } else {
                  ex.printStackTrace();
                }
              }
            }
          };
      new ThreadedTaskController(printTask,
          this.preferences.getLocalizedString(HomeController.class, "printMessage"), exceptionHandler,
          this.preferences, this.viewFactory).executeTask(getView());     
    }
  }

  /**
   * Controls the print of this home in a PDF file.
   */
  public void printToPDF() {
    final String pdfName = getView().showPrintToPDFDialog(this.home.getName());   
    if (pdfName != null) {
      // Print to PDF in a threaded task
      Callable<Void> printToPdfTask = new Callable<Void>() {
          public Void call() throws RecorderException {
            getView().printToPDF(pdfName);
            return null;
          }
        };
      ThreadedTaskController.ExceptionHandler exceptionHandler =
          new ThreadedTaskController.ExceptionHandler() {
            public void handleException(Exception ex) {
              if (!(ex instanceof InterruptedRecorderException)) {
                if (ex instanceof RecorderException) {
                  String message = preferences.getLocalizedString(
                      HomeController.class, "printToPDFError", pdfName);
                  getView().showError(message);
                } else {
                  ex.printStackTrace();
                }
              }
            }
          };
      new ThreadedTaskController(printToPdfTask,
          preferences.getLocalizedString(HomeController.class, "printToPDFMessage"), exceptionHandler,
          this.preferences, this.viewFactory).executeTask(getView());
    }
  }

  /**
   * Controls application exit. If any home in application homes list is modified,
   * the user will be {@link HomeView#confirmExit() prompted} in view whether he wants
   * to discard his modifications ot not. 
   */
  public void exit() {
    for (Home home : this.application.getHomes()) {
      if (home.isModified() || home.isRecovered()) {
        if (getView().confirmExit()) {
          break;
        } else {
          return;
        }
      }
    }
    // Remove all homes from application
    for (Home home : this.application.getHomes()) {
      home.setRecovered(false);
      this.application.deleteHome(home);
    }
    // Let application decide what to do when there's no more home
  }

  /**
   * Edits preferences and changes them if user agrees.
   */
  public void editPreferences() {
    new UserPreferencesController(this.preferences,
        this.viewFactory, this.contentManager).displayView(getView());
  }
 
  /**
   * Displays a tip message dialog depending on the given mode and
   * sets the active mode of the plan controller.
   */
  public void setMode(Mode mode) {
    if (getPlanController().getMode() != mode) {
      final String actionKey;
      if (mode == Mode.WALL_CREATION) {
        actionKey = HomeView.ActionType.CREATE_WALLS.name();
      } else if (mode == Mode.ROOM_CREATION) {
        actionKey = HomeView.ActionType.CREATE_ROOMS.name();
      } else if (mode == Mode.DIMENSION_LINE_CREATION) {
        actionKey = HomeView.ActionType.CREATE_DIMENSION_LINES.name();
      } else if (mode == Mode.LABEL_CREATION) {
        actionKey = HomeView.ActionType.CREATE_LABELS.name();
      } else {
        actionKey = null;
      }
      // Display the tip message dialog matching mode
      if (actionKey != null
          && !this.preferences.isActionTipIgnored(actionKey)) {
        getView().invokeLater(new Runnable() {
            public void run() {
              // Show tip later to let the mode switch finish first
              if (getView().showActionTipMessage(actionKey)) {
                preferences.setActionTipIgnored(actionKey);
              }
            }
          });
      }
      getPlanController().setMode(mode);
    }
  }

  /**
   * Displays the wizard that helps to import home background image.
   */
  public void importBackgroundImage() {
    new BackgroundImageWizardController(this.home, this.preferences,
        this.viewFactory, this.contentManager, getUndoableEditSupport()).displayView(getView());
  }
 
  /**
   * Displays the wizard that helps to change home background image.
   */
  public void modifyBackgroundImage() {
    importBackgroundImage();
  }
 
  /**
   * Hides the home background image.
   */
  public void hideBackgroundImage() {    
    toggleBackgroundImageVisibility("undoHideBackgroundImageName");
  }
 
  /**
   * Shows the home background image.
   */
  public void showBackgroundImage() {
    toggleBackgroundImageVisibility("undoShowBackgroundImageName");
  }
 
  /**
   * Toggles visibility of the background image and posts an undoable operation.
   */
  private void toggleBackgroundImageVisibility(final String presentationName) {
    doToggleBackgroundImageVisibility();
    UndoableEdit undoableEdit = new AbstractUndoableEdit() {
      @Override
      public void undo() throws CannotUndoException {
        super.undo();
        doToggleBackgroundImageVisibility();
      }
     
      @Override
      public void redo() throws CannotRedoException {
        super.redo();
        doToggleBackgroundImageVisibility();
      }
     
      @Override
      public String getPresentationName() {
        return preferences.getLocalizedString(HomeController.class, presentationName);
      }
    };
    getUndoableEditSupport().postEdit(undoableEdit);
  }

  /**
   * Toggles visibility of the background image.
   */
  private void doToggleBackgroundImageVisibility() {
    BackgroundImage backgroundImage = this.home.getBackgroundImage();
    this.home.setBackgroundImage(new BackgroundImage(backgroundImage.getImage(),
        backgroundImage.getScaleDistance(),
        backgroundImage.getScaleDistanceXStart(), backgroundImage.getScaleDistanceYStart(),
        backgroundImage.getScaleDistanceXEnd(), backgroundImage.getScaleDistanceYEnd(),
        backgroundImage.getXOrigin(), backgroundImage.getYOrigin(), !backgroundImage.isVisible()));
  }
 
  /**
   * Deletes home background image and posts and posts an undoable operation.
   */
  public void deleteBackgroundImage() {
    final BackgroundImage oldImage = this.home.getBackgroundImage();
    this.home.setBackgroundImage(null);
    UndoableEdit undoableEdit = new AbstractUndoableEdit() {
      @Override
      public void undo() throws CannotUndoException {
        super.undo();
        home.setBackgroundImage(oldImage);
      }
     
      @Override
      public void redo() throws CannotRedoException {
        super.redo();
        home.setBackgroundImage(null);
      }
     
      @Override
      public String getPresentationName() {
        return preferences.getLocalizedString(HomeController.class, "undoDeleteBackgroundImageName");
      }
    };
    getUndoableEditSupport().postEdit(undoableEdit);
  }
 
  /**
   * Zooms out in plan.
   */
  public void zoomOut() {
    PlanController planController = getPlanController();
    float newScale = planController.getScale() / 1.5f;
    planController.setScale(newScale);
  }

  /**
   * Zooms in in plan.
   */
  public void zoomIn() {
    PlanController planController = getPlanController();
    float newScale = planController.getScale() * 1.5f;
    planController.setScale(newScale);
  }

  /**
   * Prompts a name for the current camera and stores it in home.
   */
  public void storeCamera() {
    String now = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM).format(new Date());
    String name = getView().showStoreCameraDialog(now);
    if (name != null) {
      getHomeController3D().storeCamera(name);
    }
  }

  /**
   * Detaches the given <code>view</code> from home view.
   */
  public void detachView(View view) {
    if (view != null) {
      getView().detachView(view);
      this.notUndoableModifications = true;
      home.setModified(true);
    }
  }
         
  /**
   * Attaches the given <code>view</code> to home view.
   */
  public void attachView(View view) {
    if (view != null) {
      getView().attachView(view);
      this.notUndoableModifications = true;
      home.setModified(true);
    }
  }
               
  /**
   * Displays help window.
   */
  public void help() {
    if (helpController == null) {
      helpController = new HelpController(this.preferences, this.viewFactory);
    }
    helpController.displayView();
  }

  /**
   * Displays about dialog.
   */
  public void about() {
    getView().showAboutDialog();
  }

  /**
   * Controls the change of value of a visual property in home.
   */
  public void setVisualProperty(String propertyName,
                                Object propertyValue) {
    this.home.setVisualProperty(propertyName, propertyValue);
  }
}
TOP

Related Classes of com.eteks.sweethome3d.viewcontroller.HomeController

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.