/*
* 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.io.IOException;
import java.io.InterruptedIOException;
import java.lang.ref.WeakReference;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.security.AccessControlException;
import java.text.DateFormat;
import java.text.DecimalFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
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 javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import com.eteks.sweethome3d.model.AspectRatio;
import com.eteks.sweethome3d.model.BackgroundImage;
import com.eteks.sweethome3d.model.Camera;
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.Elevatable;
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.HomePieceOfFurniture;
import com.eteks.sweethome3d.model.HomeRecorder;
import com.eteks.sweethome3d.model.InterruptedRecorderException;
import com.eteks.sweethome3d.model.Label;
import com.eteks.sweethome3d.model.Level;
import com.eteks.sweethome3d.model.Library;
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.tools.OperatingSystem;
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 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.
*/
public HomeController(Home home,
HomeApplication application,
ViewFactory viewFactory,
ContentManager contentManager) {
this(home, application.getUserPreferences(), viewFactory,
contentManager, application);
}
/**
* 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);
}
/**
* 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);
}
/**
* 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);
}
private HomeController(final Home home,
final UserPreferences preferences,
ViewFactory viewFactory,
ContentManager contentManager,
HomeApplication application) {
this.home = home;
this.preferences = preferences;
this.viewFactory = viewFactory;
this.contentManager = contentManager;
this.application = application;
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_TEXTURE, 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_LEVEL, 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_LEVEL, 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.EXPORT_TO_CSV, 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);
Level selectedLevel = this.home.getSelectedLevel();
enableBackgroungImageActions(homeView, selectedLevel != null
? selectedLevel.getBackgroundImage()
: this.home.getBackgroundImage());
homeView.setEnabled(HomeView.ActionType.ADD_LEVEL, true);
List<Level> levels = this.home.getLevels();
boolean homeContainsOneSelectedLevel = levels.size() > 1 && selectedLevel != null;
homeView.setEnabled(HomeView.ActionType.MODIFY_LEVEL, homeContainsOneSelectedLevel);
homeView.setEnabled(HomeView.ActionType.DELETE_LEVEL, homeContainsOneSelectedLevel);
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.MODIFY_OBSERVER, this.home.getCamera() == this.home.getObserverCamera());
homeView.setEnabled(HomeView.ActionType.STORE_POINT_OF_VIEW, true);
boolean emptyStoredCameras = home.getStoredCameras().isEmpty();
homeView.setEnabled(HomeView.ActionType.DELETE_POINTS_OF_VIEW, !emptyStoredCameras);
homeView.setEnabled(HomeView.ActionType.CREATE_PHOTOS_AT_POINTS_OF_VIEW, !emptyStoredCameras);
homeView.setEnabled(HomeView.ActionType.DISPLAY_ALL_LEVELS, levels.size() > 1);
homeView.setEnabled(HomeView.ActionType.DISPLAY_SELECTED_LEVEL, levels.size() > 1);
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 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();
addLevelListeners();
addStoredCamerasListener();
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) {
enableBackgroungImageActions(getView(), (BackgroundImage)ev.getNewValue());
}
});
}
}
/**
* Enables background image actions.
*/
private void enableBackgroungImageActions(HomeView homeView, BackgroundImage backgroundImage) {
boolean homeHasBackgroundImage = backgroundImage != null;
getView().setEnabled(HomeView.ActionType.IMPORT_BACKGROUND_IMAGE, !homeHasBackgroundImage);
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.OBSERVER_CAMERA_ELEVATION_ADJUSTED, 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 homeSelectionContainsThreeMovablePiecesOfFurnitureOrMore = 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;
}
if (movablePiecesOfFurnitureCount >= 3) {
homeSelectionContainsThreeMovablePiecesOfFurnitureOrMore = 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.ALIGN_FURNITURE_ON_FRONT_SIDE,
homeSelectionContainsTwoMovablePiecesOfFurnitureOrMore);
view.setEnabled(HomeView.ActionType.ALIGN_FURNITURE_ON_BACK_SIDE,
homeSelectionContainsTwoMovablePiecesOfFurnitureOrMore);
view.setEnabled(HomeView.ActionType.ALIGN_FURNITURE_ON_LEFT_SIDE,
homeSelectionContainsTwoMovablePiecesOfFurnitureOrMore);
view.setEnabled(HomeView.ActionType.ALIGN_FURNITURE_ON_RIGHT_SIDE,
homeSelectionContainsTwoMovablePiecesOfFurnitureOrMore);
view.setEnabled(HomeView.ActionType.ALIGN_FURNITURE_SIDE_BY_SIDE,
homeSelectionContainsTwoMovablePiecesOfFurnitureOrMore);
view.setEnabled(HomeView.ActionType.DISTRIBUTE_FURNITURE_HORIZONTALLY,
homeSelectionContainsThreeMovablePiecesOfFurnitureOrMore);
view.setEnabled(HomeView.ActionType.DISTRIBUTE_FURNITURE_VERTICALLY,
homeSelectionContainsThreeMovablePiecesOfFurnitureOrMore);
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();
}
}
});
this.home.addPropertyChangeListener(Home.Property.CAMERA, new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent ev) {
getView().setEnabled(HomeView.ActionType.MODIFY_OBSERVER, home.getCamera() == home.getObserverCamera());
}
});
}
/**
* Adds a property change listener to home to
* enable/disable authorized actions according to selected level.
*/
private void addLevelListeners() {
final PropertyChangeListener selectedLevelListener = new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent ev) {
// Keep in selection only items that are at this level
List<Selectable> selectedItemsAtLevel = new ArrayList<Selectable>();
Level selectedLevel = home.getSelectedLevel();
for (Selectable item : home.getSelectedItems()) {
if (!(item instanceof Elevatable)
|| ((Elevatable)item).isAtLevel(selectedLevel)) {
selectedItemsAtLevel.add(item);
}
}
home.setSelectedItems(selectedItemsAtLevel);
enableBackgroungImageActions(getView(), selectedLevel == null
? home.getBackgroundImage()
: selectedLevel.getBackgroundImage());
List<Level> levels = home.getLevels();
boolean homeContainsOneSelectedLevel = levels.size() > 1 && selectedLevel != null;
getView().setEnabled(HomeView.ActionType.MODIFY_LEVEL, homeContainsOneSelectedLevel);
getView().setEnabled(HomeView.ActionType.DELETE_LEVEL, homeContainsOneSelectedLevel);
getView().setEnabled(HomeView.ActionType.DISPLAY_ALL_LEVELS, levels.size() > 1);
getView().setEnabled(HomeView.ActionType.DISPLAY_SELECTED_LEVEL, levels.size() > 1);
}
};
this.home.addPropertyChangeListener(Home.Property.SELECTED_LEVEL, selectedLevelListener);
final PropertyChangeListener backgroundImageChangeListener = new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent ev) {
if (Level.Property.BACKGROUND_IMAGE.name().equals(ev.getPropertyName())) {
enableBackgroungImageActions(getView(), (BackgroundImage)ev.getNewValue());
}
}
};
for (Level level : home.getLevels()) {
level.addPropertyChangeListener(backgroundImageChangeListener);
}
this.home.addLevelsListener(new CollectionListener<Level>() {
public void collectionChanged(CollectionEvent<Level> ev) {
switch (ev.getType()) {
case ADD :
home.setSelectedLevel(ev.getItem());
ev.getItem().addPropertyChangeListener(backgroundImageChangeListener);
break;
case DELETE :
selectedLevelListener.propertyChange(null);
ev.getItem().removePropertyChangeListener(backgroundImageChangeListener);
break;
}
}
});
}
/**
* Adds a property change listener to home to
* enable/disable authorized actions according to stored cameras change.
*/
private void addStoredCamerasListener() {
this.home.addPropertyChangeListener(Home.Property.STORED_CAMERAS, new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent ev) {
boolean emptyStoredCameras = home.getStoredCameras().isEmpty();
getView().setEnabled(HomeView.ActionType.DELETE_POINTS_OF_VIEW, !emptyStoredCameras);
getView().setEnabled(HomeView.ActionType.CREATE_PHOTOS_AT_POINTS_OF_VIEW, !emptyStoredCameras);
}
});
}
/**
* 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 = getFurnitureController().createHomePieceOfFurniture(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 language library chosen by the user.
*/
public void importLanguageLibrary() {
getView().invokeLater(new Runnable() {
public void run() {
final String languageLibraryName = getView().showImportLanguageLibraryDialog();
if (languageLibraryName != null) {
importLanguageLibrary(languageLibraryName);
}
}
});
}
/**
* 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 texture to the texture catalog.
* @since 4.0
*/
public void importTexture() {
new ImportedTextureWizardController(this.preferences,
this.viewFactory, this.contentManager).displayView(getView());
}
/**
* 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);
}
}
/**
* 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;
}
}
}
addPastedItems(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) {
addPastedItems(items, dx, dy, destinationView == getPlanController().getView(), "undoDropName");
}
/**
* Adds items to home.
*/
private void addPastedItems(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));
// Don't adjust depth of doors or windows otherwise they may be misplaced in a wall
if (!(piece instanceof HomeDoorOrWindow) || dx != 0 || dy != 0) {
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().addItems(items);
undoSupport.postEdit(new AbstractUndoableEdit() {
@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 for display purpose.
*/
public String getVersion() {
if (this.application != null) {
String applicationVersion = this.application.getVersion();
try {
String deploymentInformation = System.getProperty("com.eteks.sweethome3d.deploymentInformation");
if (deploymentInformation != null) {
applicationVersion += " " + deploymentInformation;
}
} catch (AccessControlException ex) {
// Ignore com.eteks.sweethome3d.deploymentInformation property since it can't be read
}
return applicationVersion;
} 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)) {
final Home savedHome;
try {
// Clone home to save it safely in a threaded task
savedHome = this.home.clone();
} catch (RuntimeException ex) {
// If home data is corrupted some way and couldn't be cloned
// warn the user his home couldn't be saved
getView().showError(preferences.getLocalizedString(
HomeController.class, "saveError", homeName, ex));
throw ex;
}
Callable<Void> saveTask = new Callable<Void>() {
public Void call() throws RecorderException {
// Write home with application recorder
application.getHomeRecorder(recorderType).writeHome(savedHome, homeName);
updateSavedHome(homeName, postSaveTask);
return null;
}
};
ThreadedTaskController.ExceptionHandler exceptionHandler =
new ThreadedTaskController.ExceptionHandler() {
public void handleException(Exception ex) {
if (!(ex instanceof InterruptedRecorderException)) {
String cause = ex.toString();
if (ex instanceof RecorderException) {
cause = "RecorderException";
String message = ex.getMessage();
if (message != null) {
cause += ": " + message;
}
if (ex.getCause() != null) {
cause += "<br>" + ex.getCause();
}
}
ex.printStackTrace();
getView().showError(preferences.getLocalizedString(
HomeController.class, "saveError", homeName, cause));
}
}
};
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 furniture list of current home to a CSV file.
* @since 4.0
*/
public void exportToCSV() {
final String csvName = getView().showExportToCSVDialog(this.home.getName());
if (csvName != null) {
// Export furniture list in a threaded task
Callable<Void> exportToCsvTask = new Callable<Void>() {
public Void call() throws RecorderException {
getView().exportToCSV(csvName);
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, "exportToCSVError", csvName);
getView().showError(message);
} else {
ex.printStackTrace();
}
}
}
};
new ThreadedTaskController(exportToCsvTask,
this.preferences.getLocalizedString(HomeController.class, "exportToCSVMessage"), exceptionHandler,
this.preferences, this.viewFactory).executeTask(getView());
}
}
/**
* Controls the export of the current home plan to a SVG file.
*/
public void exportToSVG() {
final String svgName = getView().showExportToSVGDialog(this.home.getName());
if (svgName != null) {
// Export plan 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 multiple photo-realistic images at the stored cameras locations.
*/
public void createPhotos() {
PhotosController photosController = new PhotosController(this.home, this.preferences,
getHomeController3D().getView(), this.viewFactory, this.contentManager);
photosController.displayView(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, this).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) {
final Level selectedLevel = this.home.getSelectedLevel();
doToggleBackgroundImageVisibility();
UndoableEdit undoableEdit = new AbstractUndoableEdit() {
@Override
public void undo() throws CannotUndoException {
super.undo();
home.setSelectedLevel(selectedLevel);
doToggleBackgroundImageVisibility();
}
@Override
public void redo() throws CannotRedoException {
super.redo();
home.setSelectedLevel(selectedLevel);
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.getSelectedLevel() != null
? this.home.getSelectedLevel().getBackgroundImage()
: this.home.getBackgroundImage();
backgroundImage = new BackgroundImage(backgroundImage.getImage(),
backgroundImage.getScaleDistance(),
backgroundImage.getScaleDistanceXStart(), backgroundImage.getScaleDistanceYStart(),
backgroundImage.getScaleDistanceXEnd(), backgroundImage.getScaleDistanceYEnd(),
backgroundImage.getXOrigin(), backgroundImage.getYOrigin(), !backgroundImage.isVisible());
if (this.home.getSelectedLevel() != null) {
this.home.getSelectedLevel().setBackgroundImage(backgroundImage);
} else {
this.home.setBackgroundImage(backgroundImage);
}
}
/**
* Deletes home background image and posts and posts an undoable operation.
*/
public void deleteBackgroundImage() {
final Level selectedLevel = this.home.getSelectedLevel();
final BackgroundImage oldImage;
if (selectedLevel != null) {
oldImage = selectedLevel.getBackgroundImage();
selectedLevel.setBackgroundImage(null);
} else {
oldImage = this.home.getBackgroundImage();
this.home.setBackgroundImage(null);
}
UndoableEdit undoableEdit = new AbstractUndoableEdit() {
@Override
public void undo() throws CannotUndoException {
super.undo();
home.setSelectedLevel(selectedLevel);
if (selectedLevel != null) {
selectedLevel.setBackgroundImage(oldImage);
} else {
home.setBackgroundImage(oldImage);
}
}
@Override
public void redo() throws CannotRedoException {
super.redo();
home.setSelectedLevel(selectedLevel);
if (selectedLevel != null) {
selectedLevel.setBackgroundImage(null);
} else {
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);
}
}
/**
* Prompts stored cameras in home to be deleted and deletes the ones selected by the user.
*/
public void deleteCameras() {
List<Camera> deletedCameras = getView().showDeletedCamerasDialog();
if (deletedCameras != null) {
getHomeController3D().deleteCameras(deletedCameras);
}
}
/**
* 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);
}
/**
* Checks if some application or libraries updates are available.
* @since 4.0
*/
public void checkUpdates(final boolean displayOnlyIfNewUpdates) {
String updatesUrl = getPropertyValue("com.eteks.sweethome3d.updatesUrl", "updatesUrl");
if (updatesUrl != null && updatesUrl.length() > 0) {
final URL url;
try {
url = new URL(updatesUrl);
} catch (MalformedURLException ex) {
ex.printStackTrace();
return;
}
final List<Library> libraries = this.preferences.getLibraries();
final Long updatesMinimumDate = displayOnlyIfNewUpdates
? this.preferences.getUpdatesMinimumDate()
: null;
// Read updates from XML content in updatesUrl in a threaded task
Callable<Void> checkUpdatesTask = new Callable<Void>() {
public Void call() throws IOException, SAXException {
final Map<Library, List<Update>> availableUpdates = readAvailableUpdates(url, libraries, updatesMinimumDate,
displayOnlyIfNewUpdates ? 3000 : -1);
getView().invokeLater(new Runnable () {
public void run() {
if (availableUpdates.isEmpty()) {
if (!displayOnlyIfNewUpdates) {
getView().showMessage(preferences.getLocalizedString(HomeController.class, "noUpdateMessage"));
}
} else if (!getView().showUpdatesMessage(getUpdatesMessage(availableUpdates), !displayOnlyIfNewUpdates)) {
// Search the latest date among updates
long latestUpdateDate = Long.MIN_VALUE;
for (List<Update> libraryAvailableUpdates : availableUpdates.values()) {
for (Update update : libraryAvailableUpdates) {
latestUpdateDate = Math.max(latestUpdateDate, update.getDate().getTime());
}
}
preferences.setUpdatesMinimumDate(latestUpdateDate + 1);
}
}
});
return null;
}
};
ThreadedTaskController.ExceptionHandler exceptionHandler =
new ThreadedTaskController.ExceptionHandler() {
public void handleException(Exception ex) {
if (!displayOnlyIfNewUpdates && !(ex instanceof InterruptedIOException)) {
if (ex instanceof IOException) {
getView().showError(preferences.getLocalizedString(HomeController.class, "checkUpdatesIOError", ex));
} else if (ex instanceof SAXException) {
getView().showError(preferences.getLocalizedString(HomeController.class, "checkUpdatesXMLError", ex.getMessage()));
} else {
ex.printStackTrace();
}
}
}
};
ViewFactory dummyThreadedTaskViewFactory = new ViewFactoryAdapter() {
@Override
public ThreadedTaskView createThreadedTaskView(String taskMessage, UserPreferences preferences,
ThreadedTaskController controller) {
// Return a dummy view that doesn't do anything
return new ThreadedTaskView() {
public void setTaskRunning(boolean taskRunning, View executingView) {
}
public void invokeLater(Runnable runnable) {
getView().invokeLater(runnable);
}
};
}
};
new ThreadedTaskController(checkUpdatesTask,
this.preferences.getLocalizedString(HomeController.class, "checkUpdatesMessage"), exceptionHandler,
this.preferences, displayOnlyIfNewUpdates
? dummyThreadedTaskViewFactory
: this.viewFactory).executeTask(getView());
}
}
/**
* Returns the System property value of the given <code>propertyKey</code>, or the
* the resource property value matching <code>resourceKey</code> or <code>null</code>
* if none are defined.
*/
private String getPropertyValue(String propertyKey, String resourceKey) {
String propertyValue = System.getProperty(propertyKey);
if (propertyValue != null) {
return propertyValue;
} else {
try {
return this.preferences.getLocalizedString(HomeController.class, resourceKey);
} catch (IllegalArgumentException ex) {
return null;
}
}
}
/**
* Reads the available updates from the XML stream contained in the given <code>url</code>.
* Caution : this method is called from a separate thread.
*/
private Map<Library, List<Update>> readAvailableUpdates(URL url, List<Library> libraries, Long minDate, int timeout) throws IOException, SAXException {
try {
SAXParserFactory factory = SAXParserFactory.newInstance();
factory.setValidating(false);
SAXParser saxParser = factory.newSAXParser();
UpdatesHandler updatesHandler = new UpdatesHandler(url);
URLConnection connection = url.openConnection();
if (timeout > 0) {
connection.setConnectTimeout(timeout);
connection.setReadTimeout(timeout);
}
saxParser.parse(connection.getInputStream(), updatesHandler);
// Filter updates according to application version and libraries version
Map<Library, List<Update>> availableUpdates = new LinkedHashMap<Library, List<Update>>();
long now = System.currentTimeMillis();
if (this.application != null) {
String applicationId = this.application.getId();
List<Update> applicationUpdates = getAvailableUpdates(updatesHandler.getUpdates(applicationId),
this.application.getVersion(), minDate, now);
if (!applicationUpdates.isEmpty()) {
availableUpdates.put(null, applicationUpdates);
}
}
Set<String> updatedLibraryIds = new HashSet<String>();
for (Library library : libraries) {
if (Thread.interrupted()) {
throw new InterruptedIOException();
}
String libraryId = library.getId();
if (libraryId != null
&& !updatedLibraryIds.contains(libraryId)) {
List<Update> libraryUpdates = getAvailableUpdates(updatesHandler.getUpdates(libraryId),
library.getVersion(), minDate, now);
if (!libraryUpdates.isEmpty()) {
availableUpdates.put(library, libraryUpdates);
}
// Ignore older libraries with same ID
updatedLibraryIds.add(libraryId);
}
}
return availableUpdates;
} catch (ParserConfigurationException ex) {
throw new SAXException(ex);
} catch (SAXException ex) {
// If task was interrupted (see UpdatesHandler implementation), report the interruption
if (ex.getCause() instanceof InterruptedIOException) {
throw (InterruptedIOException)ex.getCause();
} else {
throw ex;
}
}
}
/**
* Returns the updates sublist which match the given <code>version</code>.
* If no update has a date greater that <code>minDate</code>, an empty list is returned.
* Caution : this method is called from a separate thread.
*/
private List<Update> getAvailableUpdates(List<Update> updates, String version, Long minDate, long maxDate) {
if (updates != null) {
boolean recentUpdates = false;
List<Update> availableUpdates = new ArrayList<Update>();
for (Update update : updates) {
String minVersion = update.getMinVersion();
String maxVersion = update.getMaxVersion();
String operatingSystem = update.getOperatingSystem();
if (OperatingSystem.compareVersions(version, update.getVersion()) < 0
&& (minVersion == null || OperatingSystem.compareVersions(minVersion, version) <= 0)
&& (maxVersion == null || OperatingSystem.compareVersions(version, maxVersion) < 0)
&& (operatingSystem == null || System.getProperty("os.name").matches(operatingSystem))) {
Date date = update.getDate();
if (date == null
|| ((minDate == null || date.getTime() >= minDate)
&& date.getTime() < maxDate)) {
availableUpdates.add(update);
recentUpdates = true;
}
}
}
if (recentUpdates) {
Collections.sort(availableUpdates, new Comparator<Update>() {
public int compare(Update update1, Update update2) {
return -OperatingSystem.compareVersions(update1.getVersion(), update2.getVersion());
}
});
return availableUpdates;
}
}
return Collections.emptyList();
}
/**
* Returns the message for the given updates.
*/
private String getUpdatesMessage(Map<Library, List<Update>> updates) {
if (updates.isEmpty()) {
return this.preferences.getLocalizedString(HomeController.class, "noUpdateMessage");
} else {
String message = "<html><head><style>"
+ this.preferences.getLocalizedString(HomeController.class, "updatesMessageStyleSheet")
+ " .separator { margin: 0px;}</style></head><body>"
+ this.preferences.getLocalizedString(HomeController.class, "updatesMessageTitle");
String applicationUpdateMessage = this.preferences.getLocalizedString(HomeController.class, "applicationUpdateMessage");
String libraryUpdateMessage = this.preferences.getLocalizedString(HomeController.class, "libraryUpdateMessage");
String sizeUpdateMessage = this.preferences.getLocalizedString(HomeController.class, "sizeUpdateMessage");
String downloadUpdateMessage = this.preferences.getLocalizedString(HomeController.class, "downloadUpdateMessage");
String updatesMessageSeparator = this.preferences.getLocalizedString(HomeController.class, "updatesMessageSeparator");
for (Iterator<Map.Entry<Library, List<Update>>> it = updates.entrySet().iterator(); it.hasNext(); ) {
Map.Entry<Library, List<Update>> updateEntry = it.next();
Library library = updateEntry.getKey();
if (library == null) {
// Application itself
if (this.application != null) {
message += getApplicationOrLibraryUpdateMessage(updateEntry.getValue(), this.application.getName(),
applicationUpdateMessage, sizeUpdateMessage, downloadUpdateMessage);
}
} else {
String name = library.getName();
if (name == null) {
name = library.getDescription();
if (name == null) {
name = library.getLocation();
}
}
message += getApplicationOrLibraryUpdateMessage(updateEntry.getValue(), name,
libraryUpdateMessage, sizeUpdateMessage, downloadUpdateMessage);
}
if (it.hasNext()) {
message += updatesMessageSeparator;
}
}
message += "</body></html>";
return message;
}
}
/**
* Returns the message for the update of the application or a library.
*/
private String getApplicationOrLibraryUpdateMessage(List<Update> updates,
String applicationOrLibraryName,
String applicationOrLibraryUpdateMessage,
String sizeUpdateMessage,
String downloadUpdateMessage) {
String message = "";
boolean first = true;
DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.LONG);
DecimalFormat megabyteSizeFormat = new DecimalFormat("#,##0.#");
for (Update update : updates) {
String size;
if (update.getSize() != null) {
// Format at MB format
size = String.format(sizeUpdateMessage,
megabyteSizeFormat.format(update.getSize() / (1024. * 1024.)));
} else {
size = "";
}
message += String.format(applicationOrLibraryUpdateMessage,
applicationOrLibraryName, update.getVersion(), dateFormat.format(update.getDate()), size);
if (first) {
first = false;
URL downloadPage = update.getDownloadPage();
if (downloadPage == null) {
downloadPage = update.getDefaultDownloadPage();
}
if (downloadPage != null) {
message += String.format(downloadUpdateMessage, downloadPage);
}
}
String comment = update.getComment();
if (comment == null) {
comment = update.getDefaultComment();
}
if (comment != null) {
message += "<p class='separator'/>";
message += comment;
message += "<p class='separator'/>";
}
}
return message;
}
/**
* SAX handler used to parse updates XML files.
* DTD used in updated files:<pre>
* <!ELEMENT updates (update*)>
*
* <!ELEMENT update (downloadPage*, comment*)>
* <!ATTLIST update id CDATA #REQUIRED>
* <!ATTLIST update version CDATA #REQUIRED>
* <!ATTLIST update operatingSystem CDATA #IMPLIED>
* <!ATTLIST update date CDATA #REQUIRED>
* <!ATTLIST update minVersion CDATA #IMPLIED>
* <!ATTLIST update maxVersion CDATA #IMPLIED>
* <!ATTLIST update size CDATA #IMPLIED>
* <!ATTLIST update inherits CDATA #IMPLIED>
*
* <!ELEMENT downloadPage EMPTY>
* <!ATTLIST downloadPage url CDATA #REQUIRED>
* <!ATTLIST downloadPage lang CDATA #IMPLIED>
*
* <!ELEMENT comment (#PCDATA)>
* <!ATTLIST comment lang CDATA #IMPLIED>
* </pre>
* with <code>updates</code> as root element,
* <code>operatingSystem</code> an optional regular expression for the target OS,
* <code>inherits</code> the id of an other <code>update</code> element with the same version,
* <code>date</code> using <code>yyyy-MM-ddThh:mm:ss<code> or <code>yyyy-MM-dd</code> format
* at GMT and <code>comment</code> element possibly containing XHTML.
*/
private class UpdatesHandler extends DefaultHandler {
private final URL baseUrl;
private final StringBuilder comment = new StringBuilder();
private final SimpleDateFormat dateTimeFormat;
private final SimpleDateFormat dateFormat;
private final Map<String, List<Update>> updates = new HashMap<String, List<Update>>();
private Update update;
private boolean inComment;
private boolean inUpdate;
private String language;
public UpdatesHandler(URL baseUrl) {
this.baseUrl = baseUrl;
TimeZone gmtTimeZone = TimeZone.getTimeZone("GMT");
this.dateTimeFormat = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss");
this.dateTimeFormat.setTimeZone(gmtTimeZone);
this.dateFormat = new SimpleDateFormat("yyyy-MM-dd");
this.dateFormat.setTimeZone(gmtTimeZone);
}
/**
* Returns the update matching the given <code>id</code>.
*/
private List<Update> getUpdates(String id) {
return this.updates.get(id);
}
/**
* Throws a <code>SAXException</code> exception initialized with a <code>InterruptedRecorderException</code>
* cause if current thread is interrupted. The interrupted status of the current thread
* is cleared when an exception is thrown.
*/
private void checkCurrentThreadIsntInterrupted() throws SAXException {
if (Thread.interrupted()) {
throw new SAXException(new InterruptedIOException());
}
}
@Override
public void startElement(String uri, String localName, String name, Attributes attributes) throws SAXException {
checkCurrentThreadIsntInterrupted();
if (this.inComment) {
// Reproduce comment content
this.comment.append("<" + name);
for (int i = 0; i < attributes.getLength(); i++) {
this.comment.append(" " + attributes.getQName(i) + "=\"" + attributes.getValue(i) + "\"");
}
this.comment.append(">");
} else if (this.inUpdate && "comment".equals(name)) {
this.comment.setLength(0);
this.language = attributes.getValue("lang");
if (this.language == null || preferences.getLanguage().equals(this.language)) {
this.inComment = true;
}
} else if (this.inUpdate && "downloadPage".equals(name)) {
String url = attributes.getValue("url");
if (url != null) {
try {
String language = attributes.getValue("lang");
if (language == null) {
this.update.setDefaultDownloadPage(new URL(this.baseUrl, url));
} else if (preferences.getLanguage().equals(language)) {
this.update.setDownloadPage(new URL(this.baseUrl, url));
}
} catch (MalformedURLException ex) {
// Ignore bad URLs
}
}
} else if (!this.inUpdate && "update".equals(name)) {
String id = attributes.getValue("id");
String version = attributes.getValue("version");
if (id != null
&& version != null) {
this.update = new Update(id, version);
String inheritedUpdate = attributes.getValue("inherits");
// If update inherits from an other update, search the update with the same id and version
if (inheritedUpdate != null) {
List<Update> updates = this.updates.get(inheritedUpdate);
if (updates != null) {
for (Update update : updates) {
if (version.equals(update.getVersion())) {
this.update = update.clone();
this.update.setId(id);
break;
}
}
}
}
String dateAttibute = attributes.getValue("date");
if (dateAttibute != null) {
try {
this.update.setDate(this.dateTimeFormat.parse(dateAttibute));
} catch (ParseException ex) {
try {
this.update.setDate(this.dateFormat.parse(dateAttibute));
} catch (ParseException ex1) {
}
}
}
String minVersion = attributes.getValue("minVersion");
if (minVersion != null) {
this.update.setMinVersion(minVersion);
}
String maxVersion = attributes.getValue("maxVersion");
if (maxVersion != null) {
this.update.setMaxVersion(maxVersion);
}
String size = attributes.getValue("size");
if (size != null) {
try {
this.update.setSize(new Long (size));
} catch (NumberFormatException ex) {
// Ignore malformed number
}
}
String operatingSystem = attributes.getValue("operatingSystem");
if (operatingSystem != null) {
this.update.setOperatingSystem(operatingSystem);
}
List<Update> updates = this.updates.get(id);
if (updates == null) {
updates = new ArrayList<Update>();
this.updates.put(id, updates);
}
updates.add(this.update);
this.inUpdate = true;
}
}
}
@Override
public void characters(char [] ch, int start, int length) throws SAXException {
checkCurrentThreadIsntInterrupted();
if (this.inComment) {
// Reproduce comment content
this.comment.append(ch, start, length);
}
}
@Override
public void endElement(String uri, String localName, String name) throws SAXException {
if (this.inComment) {
if ("comment".equals(name)) {
String comment = this.comment.toString().trim().replace('\n', ' ');
if (comment.length() == 0) {
comment = null;
}
if (this.language == null) {
this.update.setDefaultComment(comment);
} else {
this.update.setComment(comment);
}
this.inComment = false;
} else {
// Reproduce comment content
this.comment.append("</" + name + ">");
}
} else if (this.inUpdate && "update".equals(name)) {
this.inUpdate = false;
}
}
}
/**
* Update info.
*/
private static class Update implements Cloneable {
private String id;
private final String version;
private Date date;
private String minVersion;
private String maxVersion;
private Long size;
private String operatingSystem;
private URL defaultDownloadPage;
private URL downloadPage;
private String defaultComment;
private String comment;
public Update(String id, String version) {
this.id = id;
this.version = version;
}
public String getId() {
return this.id;
}
public void setId(String id) {
this.id = id;
}
public String getVersion() {
return this.version;
}
public Date getDate() {
return this.date;
}
public void setDate(Date date) {
this.date = date;
}
public String getMinVersion() {
return this.minVersion;
}
public void setMinVersion(String minVersion) {
this.minVersion = minVersion;
}
public String getMaxVersion() {
return this.maxVersion;
}
public void setMaxVersion(String maxVersion) {
this.maxVersion = maxVersion;
}
public Long getSize() {
return this.size;
}
public void setSize(Long size) {
this.size = size;
}
public String getOperatingSystem() {
return this.operatingSystem;
}
public void setOperatingSystem(String system) {
this.operatingSystem = system;
}
public URL getDefaultDownloadPage() {
return this.defaultDownloadPage;
}
public void setDefaultDownloadPage(URL defaultDownloadPage) {
this.defaultDownloadPage = defaultDownloadPage;
}
public URL getDownloadPage() {
return this.downloadPage;
}
public void setDownloadPage(URL downloadPage) {
this.downloadPage = downloadPage;
}
public String getDefaultComment() {
return this.defaultComment;
}
public void setDefaultComment(String defaultComment) {
this.defaultComment = defaultComment;
}
public String getComment() {
return this.comment;
}
public void setComment(String comment) {
this.comment = comment;
}
@Override
protected Update clone() {
try {
return (Update)super.clone();
} catch (CloneNotSupportedException ex) {
throw new InternalError();
}
}
}
}