1 /* 2 * HomeController.java 15 mai 2006 3 * 4 * Sweet Home 3D, Copyright (c) 2006 Emmanuel PUYBARET / eTeks <info@eteks.com> 5 * 6 * This program is free software; you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License as published by 8 * the Free Software Foundation; either version 2 of the License, or 9 * (at your option) any later version. 10 * 11 * This program is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU General Public License for more details. 15 * 16 * You should have received a copy of the GNU General Public License 17 * along with this program; if not, write to the Free Software 18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 19 */ 20 package com.eteks.sweethome3d.viewcontroller; 21 22 import java.beans.PropertyChangeEvent; 23 import java.beans.PropertyChangeListener; 24 import java.io.IOException; 25 import java.io.InterruptedIOException; 26 import java.lang.ref.WeakReference; 27 import java.net.MalformedURLException; 28 import java.net.URL; 29 import java.net.URLConnection; 30 import java.security.AccessControlException; 31 import java.text.DateFormat; 32 import java.text.DecimalFormat; 33 import java.text.ParseException; 34 import java.text.SimpleDateFormat; 35 import java.util.ArrayList; 36 import java.util.Arrays; 37 import java.util.Collections; 38 import java.util.Comparator; 39 import java.util.Date; 40 import java.util.HashMap; 41 import java.util.HashSet; 42 import java.util.LinkedHashMap; 43 import java.util.List; 44 import java.util.Map; 45 import java.util.Set; 46 import java.util.TimeZone; 47 import java.util.concurrent.Callable; 48 49 import javax.swing.event.UndoableEditEvent; 50 import javax.swing.event.UndoableEditListener; 51 import javax.swing.undo.CannotRedoException; 52 import javax.swing.undo.CannotUndoException; 53 import javax.swing.undo.CompoundEdit; 54 import javax.swing.undo.UndoManager; 55 import javax.swing.undo.UndoableEdit; 56 import javax.swing.undo.UndoableEditSupport; 57 import javax.xml.parsers.ParserConfigurationException; 58 import javax.xml.parsers.SAXParser; 59 import javax.xml.parsers.SAXParserFactory; 60 61 import org.xml.sax.Attributes; 62 import org.xml.sax.SAXException; 63 import org.xml.sax.helpers.DefaultHandler; 64 65 import com.eteks.sweethome3d.model.AspectRatio; 66 import com.eteks.sweethome3d.model.BackgroundImage; 67 import com.eteks.sweethome3d.model.Camera; 68 import com.eteks.sweethome3d.model.CatalogPieceOfFurniture; 69 import com.eteks.sweethome3d.model.CatalogTexture; 70 import com.eteks.sweethome3d.model.CollectionEvent; 71 import com.eteks.sweethome3d.model.CollectionListener; 72 import com.eteks.sweethome3d.model.Compass; 73 import com.eteks.sweethome3d.model.Content; 74 import com.eteks.sweethome3d.model.DamagedHomeRecorderException; 75 import com.eteks.sweethome3d.model.DimensionLine; 76 import com.eteks.sweethome3d.model.Elevatable; 77 import com.eteks.sweethome3d.model.FurnitureCatalog; 78 import com.eteks.sweethome3d.model.FurnitureCategory; 79 import com.eteks.sweethome3d.model.Home; 80 import com.eteks.sweethome3d.model.HomeApplication; 81 import com.eteks.sweethome3d.model.HomeDoorOrWindow; 82 import com.eteks.sweethome3d.model.HomeEnvironment; 83 import com.eteks.sweethome3d.model.HomeFurnitureGroup; 84 import com.eteks.sweethome3d.model.HomeMaterial; 85 import com.eteks.sweethome3d.model.HomePieceOfFurniture; 86 import com.eteks.sweethome3d.model.HomeRecorder; 87 import com.eteks.sweethome3d.model.HomeTexture; 88 import com.eteks.sweethome3d.model.InterruptedRecorderException; 89 import com.eteks.sweethome3d.model.Label; 90 import com.eteks.sweethome3d.model.Level; 91 import com.eteks.sweethome3d.model.Library; 92 import com.eteks.sweethome3d.model.NotEnoughSpaceRecorderException; 93 import com.eteks.sweethome3d.model.Polyline; 94 import com.eteks.sweethome3d.model.RecorderException; 95 import com.eteks.sweethome3d.model.Room; 96 import com.eteks.sweethome3d.model.Selectable; 97 import com.eteks.sweethome3d.model.SelectionEvent; 98 import com.eteks.sweethome3d.model.SelectionListener; 99 import com.eteks.sweethome3d.model.TextStyle; 100 import com.eteks.sweethome3d.model.TextureImage; 101 import com.eteks.sweethome3d.model.TexturesCatalog; 102 import com.eteks.sweethome3d.model.UserPreferences; 103 import com.eteks.sweethome3d.model.Wall; 104 import com.eteks.sweethome3d.tools.OperatingSystem; 105 import com.eteks.sweethome3d.tools.ResourceURLContent; 106 107 /** 108 * A MVC controller for the home view. 109 * @author Emmanuel Puybaret 110 */ 111 public class HomeController implements Controller { 112 private final Home home; 113 private final UserPreferences preferences; 114 private final HomeApplication application; 115 private final ViewFactory viewFactory; 116 private final ContentManager contentManager; 117 private final UndoableEditSupport undoSupport; 118 private final UndoManager undoManager; 119 private HomeView homeView; 120 private List<Controller> childControllers; 121 private FurnitureCatalogController furnitureCatalogController; 122 private FurnitureController furnitureController; 123 private PlanController planController; 124 private HomeController3D homeController3D; 125 private static HelpController helpController; // Only one help controller 126 private int saveUndoLevel; 127 private boolean notUndoableModifications; 128 private View focusedView; 129 130 private static final Content REPAIRED_IMAGE_CONTENT = new ResourceURLContent(HomeController.class, "resources/repairedImage.png"); 131 private static final Content REPAIRED_ICON_CONTENT = new ResourceURLContent(HomeController.class, "resources/repairedIcon.png"); 132 private static final Content REPAIRED_MODEL_CONTENT = new ResourceURLContent(HomeController.class, "resources/repairedModel.obj"); 133 134 /** 135 * Creates the controller of home view. 136 * @param home the home edited by this controller and its view. 137 * @param application the instance of current application. 138 * @param viewFactory a factory able to create views. 139 * @param contentManager the content manager of the application. 140 */ HomeController(Home home, HomeApplication application, ViewFactory viewFactory, ContentManager contentManager)141 public HomeController(Home home, 142 HomeApplication application, 143 ViewFactory viewFactory, 144 ContentManager contentManager) { 145 this(home, application, application.getUserPreferences(), 146 viewFactory, contentManager); 147 } 148 149 /** 150 * Creates the controller of home view. 151 * @param home the home edited by this controller and its view. 152 * @param application the instance of current application. 153 * @param viewFactory a factory able to create views. 154 */ HomeController(Home home, HomeApplication application, ViewFactory viewFactory)155 public HomeController(Home home, 156 HomeApplication application, 157 ViewFactory viewFactory) { 158 this(home, application, application.getUserPreferences(), viewFactory, null); 159 } 160 161 /** 162 * Creates the controller of home view. 163 * @param home the home edited by this controller and its view. 164 * @param preferences the preferences of the application. 165 * @param viewFactory a factory able to create views. 166 */ HomeController(Home home, UserPreferences preferences, ViewFactory viewFactory)167 public HomeController(Home home, 168 UserPreferences preferences, 169 ViewFactory viewFactory) { 170 this(home, null, preferences, viewFactory, null); 171 } 172 173 /** 174 * Creates the controller of home view. 175 * @param home the home edited by this controller and its view. 176 * @param preferences the preferences of the application. 177 * @param viewFactory a factory able to create views. 178 * @param contentManager the content manager of the application. 179 */ HomeController(Home home, UserPreferences preferences, ViewFactory viewFactory, ContentManager contentManager)180 public HomeController(Home home, 181 UserPreferences preferences, 182 ViewFactory viewFactory, 183 ContentManager contentManager) { 184 this(home, null, preferences, viewFactory, contentManager); 185 } 186 HomeController(final Home home, HomeApplication application, final UserPreferences preferences, ViewFactory viewFactory, ContentManager contentManager)187 private HomeController(final Home home, 188 HomeApplication application, 189 final UserPreferences preferences, 190 ViewFactory viewFactory, 191 ContentManager contentManager) { 192 this.home = home; 193 this.preferences = preferences; 194 this.viewFactory = viewFactory; 195 this.contentManager = contentManager; 196 this.application = application; 197 198 this.undoSupport = new UndoableEditSupport() { 199 @Override 200 protected void _postEdit(UndoableEdit edit) { 201 // Ignore not significant compound edit 202 if (!(edit instanceof CompoundEdit) 203 || edit.isSignificant()) { 204 super._postEdit(edit); 205 } 206 } 207 }; 208 this.undoManager = new UndoManager(); 209 this.undoSupport.addUndoableEditListener(this.undoManager); 210 211 // Update recent homes list 212 if (home.getName() != null) { 213 List<String> recentHomes = new ArrayList<String>(this.preferences.getRecentHomes()); 214 recentHomes.remove(home.getName()); 215 recentHomes.add(0, home.getName()); 216 updateUserPreferencesRecentHomes(recentHomes); 217 } 218 } 219 220 /** 221 * Enables actions at controller instantiation. 222 */ enableDefaultActions(HomeView homeView)223 private void enableDefaultActions(HomeView homeView) { 224 boolean applicationExists = this.application != null; 225 226 homeView.setEnabled(HomeView.ActionType.NEW_HOME, applicationExists); 227 homeView.setEnabled(HomeView.ActionType.NEW_HOME_FROM_EXAMPLE, applicationExists); 228 homeView.setEnabled(HomeView.ActionType.OPEN, applicationExists); 229 homeView.setEnabled(HomeView.ActionType.DELETE_RECENT_HOMES, 230 applicationExists && !this.preferences.getRecentHomes().isEmpty()); 231 homeView.setEnabled(HomeView.ActionType.CLOSE, applicationExists); 232 homeView.setEnabled(HomeView.ActionType.SAVE, applicationExists); 233 homeView.setEnabled(HomeView.ActionType.SAVE_AS, applicationExists); 234 homeView.setEnabled(HomeView.ActionType.SAVE_AND_COMPRESS, applicationExists); 235 homeView.setEnabled(HomeView.ActionType.PAGE_SETUP, true); 236 homeView.setEnabled(HomeView.ActionType.PRINT_PREVIEW, true); 237 homeView.setEnabled(HomeView.ActionType.PRINT, true); 238 homeView.setEnabled(HomeView.ActionType.PRINT_TO_PDF, true); 239 homeView.setEnabled(HomeView.ActionType.PREFERENCES, true); 240 homeView.setEnabled(HomeView.ActionType.EXIT, applicationExists); 241 homeView.setEnabled(HomeView.ActionType.IMPORT_FURNITURE, true); 242 homeView.setEnabled(HomeView.ActionType.IMPORT_FURNITURE_LIBRARY, true); 243 homeView.setEnabled(HomeView.ActionType.IMPORT_TEXTURE, true); 244 homeView.setEnabled(HomeView.ActionType.IMPORT_TEXTURES_LIBRARY, true); 245 homeView.setEnabled(HomeView.ActionType.SORT_HOME_FURNITURE_BY_CATALOG_ID, true); 246 homeView.setEnabled(HomeView.ActionType.SORT_HOME_FURNITURE_BY_NAME, true); 247 homeView.setEnabled(HomeView.ActionType.SORT_HOME_FURNITURE_BY_CREATOR, true); 248 homeView.setEnabled(HomeView.ActionType.SORT_HOME_FURNITURE_BY_WIDTH, true); 249 homeView.setEnabled(HomeView.ActionType.SORT_HOME_FURNITURE_BY_HEIGHT, true); 250 homeView.setEnabled(HomeView.ActionType.SORT_HOME_FURNITURE_BY_DEPTH, true); 251 homeView.setEnabled(HomeView.ActionType.SORT_HOME_FURNITURE_BY_X, true); 252 homeView.setEnabled(HomeView.ActionType.SORT_HOME_FURNITURE_BY_Y, true); 253 homeView.setEnabled(HomeView.ActionType.SORT_HOME_FURNITURE_BY_ELEVATION, true); 254 homeView.setEnabled(HomeView.ActionType.SORT_HOME_FURNITURE_BY_ANGLE, true); 255 homeView.setEnabled(HomeView.ActionType.SORT_HOME_FURNITURE_BY_LEVEL, true); 256 homeView.setEnabled(HomeView.ActionType.SORT_HOME_FURNITURE_BY_MODEL_SIZE, true); 257 homeView.setEnabled(HomeView.ActionType.SORT_HOME_FURNITURE_BY_COLOR, true); 258 homeView.setEnabled(HomeView.ActionType.SORT_HOME_FURNITURE_BY_TEXTURE, true); 259 homeView.setEnabled(HomeView.ActionType.SORT_HOME_FURNITURE_BY_MOVABILITY, true); 260 homeView.setEnabled(HomeView.ActionType.SORT_HOME_FURNITURE_BY_TYPE, true); 261 homeView.setEnabled(HomeView.ActionType.SORT_HOME_FURNITURE_BY_VISIBILITY, true); 262 homeView.setEnabled(HomeView.ActionType.SORT_HOME_FURNITURE_BY_PRICE, true); 263 homeView.setEnabled(HomeView.ActionType.SORT_HOME_FURNITURE_BY_VALUE_ADDED_TAX_PERCENTAGE, true); 264 homeView.setEnabled(HomeView.ActionType.SORT_HOME_FURNITURE_BY_VALUE_ADDED_TAX, true); 265 homeView.setEnabled(HomeView.ActionType.SORT_HOME_FURNITURE_BY_PRICE_VALUE_ADDED_TAX_INCLUDED, true); 266 homeView.setEnabled(HomeView.ActionType.SORT_HOME_FURNITURE_BY_DESCENDING_ORDER, 267 this.home.getFurnitureSortedProperty() != null); 268 homeView.setEnabled(HomeView.ActionType.DISPLAY_HOME_FURNITURE_CATALOG_ID, true); 269 homeView.setEnabled(HomeView.ActionType.DISPLAY_HOME_FURNITURE_NAME, true); 270 homeView.setEnabled(HomeView.ActionType.DISPLAY_HOME_FURNITURE_CREATOR, true); 271 homeView.setEnabled(HomeView.ActionType.DISPLAY_HOME_FURNITURE_WIDTH, true); 272 homeView.setEnabled(HomeView.ActionType.DISPLAY_HOME_FURNITURE_DEPTH, true); 273 homeView.setEnabled(HomeView.ActionType.DISPLAY_HOME_FURNITURE_HEIGHT, true); 274 homeView.setEnabled(HomeView.ActionType.DISPLAY_HOME_FURNITURE_X, true); 275 homeView.setEnabled(HomeView.ActionType.DISPLAY_HOME_FURNITURE_Y, true); 276 homeView.setEnabled(HomeView.ActionType.DISPLAY_HOME_FURNITURE_ELEVATION, true); 277 homeView.setEnabled(HomeView.ActionType.DISPLAY_HOME_FURNITURE_ANGLE, true); 278 homeView.setEnabled(HomeView.ActionType.DISPLAY_HOME_FURNITURE_LEVEL, true); 279 homeView.setEnabled(HomeView.ActionType.DISPLAY_HOME_FURNITURE_MODEL_SIZE, true); 280 homeView.setEnabled(HomeView.ActionType.DISPLAY_HOME_FURNITURE_COLOR, true); 281 homeView.setEnabled(HomeView.ActionType.DISPLAY_HOME_FURNITURE_TEXTURE, true); 282 homeView.setEnabled(HomeView.ActionType.DISPLAY_HOME_FURNITURE_MOVABLE, true); 283 homeView.setEnabled(HomeView.ActionType.DISPLAY_HOME_FURNITURE_DOOR_OR_WINDOW, true); 284 homeView.setEnabled(HomeView.ActionType.DISPLAY_HOME_FURNITURE_VISIBLE, true); 285 homeView.setEnabled(HomeView.ActionType.DISPLAY_HOME_FURNITURE_PRICE, true); 286 homeView.setEnabled(HomeView.ActionType.DISPLAY_HOME_FURNITURE_VALUE_ADDED_TAX_PERCENTAGE, true); 287 homeView.setEnabled(HomeView.ActionType.DISPLAY_HOME_FURNITURE_VALUE_ADDED_TAX, true); 288 homeView.setEnabled(HomeView.ActionType.DISPLAY_HOME_FURNITURE_PRICE_VALUE_ADDED_TAX_INCLUDED, true); 289 homeView.setEnabled(HomeView.ActionType.EXPORT_TO_CSV, true); 290 homeView.setEnabled(HomeView.ActionType.SELECT, true); 291 homeView.setEnabled(HomeView.ActionType.PAN, true); 292 homeView.setEnabled(HomeView.ActionType.LOCK_BASE_PLAN, true); 293 homeView.setEnabled(HomeView.ActionType.UNLOCK_BASE_PLAN, true); 294 homeView.setEnabled(HomeView.ActionType.ENABLE_MAGNETISM, true); 295 homeView.setEnabled(HomeView.ActionType.DISABLE_MAGNETISM, true); 296 homeView.setEnabled(HomeView.ActionType.MODIFY_COMPASS, true); 297 Level selectedLevel = this.home.getSelectedLevel(); 298 enableBackgroungImageActions(homeView, selectedLevel != null 299 ? selectedLevel.getBackgroundImage() 300 : this.home.getBackgroundImage()); 301 enableLevelActions(homeView); 302 homeView.setEnabled(HomeView.ActionType.ZOOM_IN, true); 303 homeView.setEnabled(HomeView.ActionType.ZOOM_OUT, true); 304 homeView.setEnabled(HomeView.ActionType.EXPORT_TO_SVG, true); 305 homeView.setEnabled(HomeView.ActionType.VIEW_FROM_TOP, true); 306 homeView.setEnabled(HomeView.ActionType.VIEW_FROM_OBSERVER, true); 307 homeView.setEnabled(HomeView.ActionType.MODIFY_OBSERVER, this.home.getCamera() == this.home.getObserverCamera()); 308 homeView.setEnabled(HomeView.ActionType.STORE_POINT_OF_VIEW, true); 309 boolean emptyStoredCameras = home.getStoredCameras().isEmpty(); 310 homeView.setEnabled(HomeView.ActionType.DELETE_POINTS_OF_VIEW, !emptyStoredCameras); 311 homeView.setEnabled(HomeView.ActionType.CREATE_PHOTOS_AT_POINTS_OF_VIEW, !emptyStoredCameras); 312 homeView.setEnabled(HomeView.ActionType.DETACH_3D_VIEW, true); 313 homeView.setEnabled(HomeView.ActionType.ATTACH_3D_VIEW, true); 314 homeView.setEnabled(HomeView.ActionType.VIEW_FROM_OBSERVER, true); 315 homeView.setEnabled(HomeView.ActionType.MODIFY_3D_ATTRIBUTES, true); 316 homeView.setEnabled(HomeView.ActionType.CREATE_PHOTO, true); 317 homeView.setEnabled(HomeView.ActionType.CREATE_VIDEO, true); 318 homeView.setEnabled(HomeView.ActionType.EXPORT_TO_OBJ, true); 319 homeView.setEnabled(HomeView.ActionType.HELP, true); 320 homeView.setEnabled(HomeView.ActionType.ABOUT, true); 321 enableCreationToolsActions(homeView); 322 homeView.setTransferEnabled(true); 323 } 324 325 /** 326 * Enables actions handling levels. 327 */ enableLevelActions(HomeView homeView)328 private void enableLevelActions(HomeView homeView) { 329 boolean modificationState = getPlanController().isModificationState(); 330 homeView.setEnabled(HomeView.ActionType.ADD_LEVEL, !modificationState); 331 homeView.setEnabled(HomeView.ActionType.ADD_LEVEL_AT_SAME_ELEVATION, !modificationState); 332 List<Level> levels = this.home.getLevels(); 333 Level selectedLevel = this.home.getSelectedLevel(); 334 boolean homeContainsOneSelectedLevel = levels.size() > 1 && selectedLevel != null; 335 homeView.setEnabled(HomeView.ActionType.SELECT_ALL_AT_ALL_LEVELS, !modificationState && levels.size() > 1); 336 homeView.setEnabled(HomeView.ActionType.MAKE_LEVEL_VIEWABLE, !modificationState && homeContainsOneSelectedLevel); 337 homeView.setEnabled(HomeView.ActionType.MAKE_LEVEL_UNVIEWABLE, !modificationState && homeContainsOneSelectedLevel); 338 homeView.setEnabled(HomeView.ActionType.MAKE_LEVEL_ONLY_VIEWABLE_ONE, homeContainsOneSelectedLevel); 339 homeView.setEnabled(HomeView.ActionType.MAKE_ALL_LEVELS_VIEWABLE, levels.size() > 1); 340 homeView.setEnabled(HomeView.ActionType.MODIFY_LEVEL, homeContainsOneSelectedLevel); 341 homeView.setEnabled(HomeView.ActionType.DELETE_LEVEL, !modificationState && homeContainsOneSelectedLevel); 342 homeView.setEnabled(HomeView.ActionType.DISPLAY_ALL_LEVELS, levels.size() > 1); 343 homeView.setEnabled(HomeView.ActionType.DISPLAY_SELECTED_LEVEL, levels.size() > 1); 344 } 345 346 /** 347 * Enables plan actions depending on the selected level is viewable or not. 348 */ enableCreationToolsActions(HomeView homeView)349 private void enableCreationToolsActions(HomeView homeView) { 350 Level selectedLevel = this.home.getSelectedLevel(); 351 boolean viewableLevel = selectedLevel == null || selectedLevel.isViewable(); 352 homeView.setEnabled(HomeView.ActionType.CREATE_WALLS, viewableLevel); 353 homeView.setEnabled(HomeView.ActionType.CREATE_ROOMS, viewableLevel); 354 homeView.setEnabled(HomeView.ActionType.CREATE_POLYLINES, viewableLevel); 355 homeView.setEnabled(HomeView.ActionType.CREATE_DIMENSION_LINES, viewableLevel); 356 homeView.setEnabled(HomeView.ActionType.CREATE_LABELS, viewableLevel); 357 } 358 359 /** 360 * Returns the view associated with this controller. 361 */ getView()362 public HomeView getView() { 363 if (this.homeView == null) { 364 this.homeView = this.viewFactory.createHomeView(this.home, this.preferences, this); 365 enableDefaultActions(this.homeView); 366 addListeners(); 367 368 // If home version is more recent than current version 369 if (this.home.getName() != null 370 && this.home.getVersion() > Home.CURRENT_VERSION) { 371 // Warn the user that view will display a home created with a more recent version 372 this.homeView.invokeLater(new Runnable() { 373 public void run() { 374 String message = preferences.getLocalizedString(HomeController.class, 375 "moreRecentVersionHome", home.getName()); 376 getView().showMessage(message); 377 } 378 }); 379 } 380 } 381 return this.homeView; 382 } 383 384 /** 385 * Returns the content manager of this controller. 386 */ getContentManager()387 public ContentManager getContentManager() { 388 return this.contentManager; 389 } 390 391 /** 392 * Returns the furniture catalog controller managed by this controller. 393 */ getFurnitureCatalogController()394 public FurnitureCatalogController getFurnitureCatalogController() { 395 // Create sub controller lazily only once it's needed 396 if (this.furnitureCatalogController == null) { 397 this.furnitureCatalogController = new FurnitureCatalogController( 398 this.preferences.getFurnitureCatalog(), this.preferences, this.viewFactory, this.contentManager); 399 } 400 return this.furnitureCatalogController; 401 } 402 403 /** 404 * Returns the furniture controller managed by this controller. 405 */ getFurnitureController()406 public FurnitureController getFurnitureController() { 407 // Create sub controller lazily only once it's needed 408 if (this.furnitureController == null) { 409 this.furnitureController = new FurnitureController( 410 this.home, this.preferences, this.viewFactory, this.contentManager, getUndoableEditSupport()); 411 } 412 return this.furnitureController; 413 } 414 415 /** 416 * Returns the controller of home plan. 417 */ getPlanController()418 public PlanController getPlanController() { 419 // Create sub controller lazily only once it's needed 420 if (this.planController == null) { 421 this.planController = new PlanController( 422 this.home, this.preferences, this.viewFactory, this.contentManager, getUndoableEditSupport()); 423 } 424 return this.planController; 425 } 426 427 /** 428 * Returns the controller of home 3D view. 429 */ getHomeController3D()430 public HomeController3D getHomeController3D() { 431 // Create sub controller lazily only once it's needed 432 if (this.homeController3D == null) { 433 this.homeController3D = new HomeController3D( 434 this.home, this.preferences, this.viewFactory, this.contentManager, getUndoableEditSupport()); 435 } 436 return this.homeController3D; 437 } 438 439 /** 440 * Returns the undoable edit support managed by this controller. 441 */ getUndoableEditSupport()442 protected final UndoableEditSupport getUndoableEditSupport() { 443 return this.undoSupport; 444 } 445 446 /** 447 * Adds listeners that updates the enabled / disabled state of actions. 448 */ addListeners()449 private void addListeners() { 450 // Save preferences when they change 451 this.preferences.getFurnitureCatalog().addFurnitureListener( 452 new FurnitureCatalogChangeListener(this)); 453 this.preferences.getTexturesCatalog().addTexturesListener( 454 new TexturesCatalogChangeListener(this)); 455 // Listen to all property changes to save them when any of them change 456 this.preferences.addPropertyChangeListener(new UserPreferencesPropertiesChangeListener(this)); 457 458 addCatalogSelectionListener(); 459 addHomeBackgroundImageListener(); 460 addNotUndoableModificationListeners(); 461 addHomeSelectionListener(); 462 addFurnitureSortListener(); 463 addUndoSupportListener(); 464 addHomeItemsListener(); 465 addLevelListeners(); 466 addStoredCamerasListener(); 467 addPlanControllerListeners(); 468 addLanguageListener(); 469 } 470 471 /** 472 * Super class of catalog listeners that writes preferences each time a piece of furniture or a texture 473 * is deleted or added in furniture or textures catalog. 474 */ 475 private abstract static class UserPreferencesChangeListener { 476 // Stores the currently writing preferences 477 private static Set<UserPreferences> writingPreferences = new HashSet<UserPreferences>(); 478 writePreferences(final HomeController controller)479 public void writePreferences(final HomeController controller) { 480 if (!writingPreferences.contains(controller.preferences)) { 481 writingPreferences.add(controller.preferences); 482 // Write preferences later once all catalog modifications are notified 483 controller.getView().invokeLater(new Runnable() { 484 public void run() { 485 try { 486 controller.preferences.write(); 487 writingPreferences.remove(controller.preferences); 488 } catch (RecorderException ex) { 489 controller.getView().showError(controller.preferences.getLocalizedString( 490 HomeController.class, "savePreferencesError")); 491 } 492 } 493 }); 494 } 495 } 496 } 497 498 /** 499 * Furniture catalog listener that writes preferences each time a piece of furniture 500 * is deleted or added in furniture catalog. This listener is bound to this controller 501 * with a weak reference to avoid strong link between catalog and this controller. 502 */ 503 private static class FurnitureCatalogChangeListener extends UserPreferencesChangeListener 504 implements CollectionListener<CatalogPieceOfFurniture> { 505 private WeakReference<HomeController> homeController; 506 FurnitureCatalogChangeListener(HomeController homeController)507 public FurnitureCatalogChangeListener(HomeController homeController) { 508 this.homeController = new WeakReference<HomeController>(homeController); 509 } 510 collectionChanged(CollectionEvent<CatalogPieceOfFurniture> ev)511 public void collectionChanged(CollectionEvent<CatalogPieceOfFurniture> ev) { 512 // If controller was garbage collected, remove this listener from catalog 513 final HomeController controller = this.homeController.get(); 514 if (controller == null) { 515 ((FurnitureCatalog)ev.getSource()).removeFurnitureListener(this); 516 } else { 517 writePreferences(controller); 518 } 519 } 520 } 521 522 /** 523 * Textures catalog listener that writes preferences each time a texture 524 * is deleted or added in textures catalog. This listener is bound to this controller 525 * with a weak reference to avoid strong link between catalog and this controller. 526 */ 527 private static class TexturesCatalogChangeListener extends UserPreferencesChangeListener 528 implements CollectionListener<CatalogTexture> { 529 private WeakReference<HomeController> homeController; 530 TexturesCatalogChangeListener(HomeController homeController)531 public TexturesCatalogChangeListener(HomeController homeController) { 532 this.homeController = new WeakReference<HomeController>(homeController); 533 } 534 collectionChanged(CollectionEvent<CatalogTexture> ev)535 public void collectionChanged(CollectionEvent<CatalogTexture> ev) { 536 // If controller was garbage collected, remove this listener from catalog 537 final HomeController controller = this.homeController.get(); 538 if (controller == null) { 539 ((TexturesCatalog)ev.getSource()).removeTexturesListener(this); 540 } else { 541 writePreferences(controller); 542 } 543 } 544 } 545 546 /** 547 * Properties listener that writes preferences each time the value of one of its properties changes. 548 * This listener is bound to this controller with a weak reference to avoid strong link 549 * between catalog and this controller. 550 */ 551 private static class UserPreferencesPropertiesChangeListener extends UserPreferencesChangeListener 552 implements PropertyChangeListener { 553 private WeakReference<HomeController> homeController; 554 UserPreferencesPropertiesChangeListener(HomeController homeController)555 public UserPreferencesPropertiesChangeListener(HomeController homeController) { 556 this.homeController = new WeakReference<HomeController>(homeController); 557 } 558 propertyChange(PropertyChangeEvent ev)559 public void propertyChange(PropertyChangeEvent ev) { 560 // If controller was garbage collected, remove this listener from catalog 561 final HomeController controller = this.homeController.get(); 562 if (controller == null) { 563 ((UserPreferences)ev.getSource()).removePropertyChangeListener( 564 UserPreferences.Property.valueOf(ev.getPropertyName()), this); 565 } else { 566 writePreferences(controller); 567 } 568 } 569 } 570 571 /** 572 * Adds a selection listener to catalog that enables / disables Add Furniture action. 573 */ addCatalogSelectionListener()574 private void addCatalogSelectionListener() { 575 getFurnitureCatalogController().addSelectionListener(new SelectionListener() { 576 public void selectionChanged(SelectionEvent ev) { 577 enableActionsBoundToSelection(); 578 } 579 }); 580 } 581 582 /** 583 * Adds a property change listener to <code>preferences</code> to update 584 * undo and redo presentation names when preferred language changes. 585 */ addLanguageListener()586 private void addLanguageListener() { 587 this.preferences.addPropertyChangeListener(UserPreferences.Property.LANGUAGE, 588 new LanguageChangeListener(this)); 589 } 590 591 /** 592 * Preferences property listener bound to this component with a weak reference to avoid 593 * strong link between preferences and this component. 594 */ 595 private static class LanguageChangeListener implements PropertyChangeListener { 596 private WeakReference<HomeController> homeController; 597 LanguageChangeListener(HomeController homeController)598 public LanguageChangeListener(HomeController homeController) { 599 this.homeController = new WeakReference<HomeController>(homeController); 600 } 601 propertyChange(PropertyChangeEvent ev)602 public void propertyChange(PropertyChangeEvent ev) { 603 // If home pane was garbage collected, remove this listener from preferences 604 HomeController homeController = this.homeController.get(); 605 if (homeController == null) { 606 ((UserPreferences)ev.getSource()).removePropertyChangeListener( 607 UserPreferences.Property.LANGUAGE, this); 608 } else { 609 // Update undo and redo name 610 homeController.getView().setUndoRedoName( 611 homeController.undoManager.canUndo() 612 ? homeController.undoManager.getUndoPresentationName() 613 : null, 614 homeController.undoManager.canRedo() 615 ? homeController.undoManager.getRedoPresentationName() 616 : null); 617 } 618 } 619 } 620 621 /** 622 * Adds a selection listener to home that enables / disables actions on selection. 623 */ addHomeSelectionListener()624 private void addHomeSelectionListener() { 625 if (this.home != null) { 626 this.home.addSelectionListener(new SelectionListener() { 627 public void selectionChanged(SelectionEvent ev) { 628 enableActionsBoundToSelection(); 629 } 630 }); 631 } 632 } 633 634 /** 635 * Adds a property change listener to home that enables / disables sort order action. 636 */ addFurnitureSortListener()637 private void addFurnitureSortListener() { 638 if (this.home != null) { 639 this.home.addPropertyChangeListener(Home.Property.FURNITURE_SORTED_PROPERTY, 640 new PropertyChangeListener() { 641 public void propertyChange(PropertyChangeEvent ev) { 642 getView().setEnabled(HomeView.ActionType.SORT_HOME_FURNITURE_BY_DESCENDING_ORDER, 643 ev.getNewValue() != null); 644 } 645 }); 646 } 647 } 648 649 /** 650 * Adds a property change listener to home that enables / disables background image actions. 651 */ addHomeBackgroundImageListener()652 private void addHomeBackgroundImageListener() { 653 if (this.home != null) { 654 this.home.addPropertyChangeListener(Home.Property.BACKGROUND_IMAGE, 655 new PropertyChangeListener() { 656 public void propertyChange(PropertyChangeEvent ev) { 657 enableBackgroungImageActions(getView(), (BackgroundImage)ev.getNewValue()); 658 } 659 }); 660 } 661 } 662 663 /** 664 * Enables background image actions. 665 */ enableBackgroungImageActions(HomeView homeView, BackgroundImage backgroundImage)666 private void enableBackgroungImageActions(HomeView homeView, BackgroundImage backgroundImage) { 667 Level selectedLevel = this.home.getSelectedLevel(); 668 boolean homeHasBackgroundImage = backgroundImage != null 669 && (selectedLevel == null || selectedLevel.isViewable()); 670 getView().setEnabled(HomeView.ActionType.IMPORT_BACKGROUND_IMAGE, !homeHasBackgroundImage); 671 getView().setEnabled(HomeView.ActionType.MODIFY_BACKGROUND_IMAGE, homeHasBackgroundImage); 672 getView().setEnabled(HomeView.ActionType.HIDE_BACKGROUND_IMAGE, 673 homeHasBackgroundImage && backgroundImage.isVisible()); 674 getView().setEnabled(HomeView.ActionType.SHOW_BACKGROUND_IMAGE, 675 homeHasBackgroundImage && !backgroundImage.isVisible()); 676 getView().setEnabled(HomeView.ActionType.DELETE_BACKGROUND_IMAGE, homeHasBackgroundImage); 677 } 678 679 /** 680 * Adds listeners to track property changes that are not undoable. 681 */ addNotUndoableModificationListeners()682 private void addNotUndoableModificationListeners() { 683 if (this.home != null) { 684 final PropertyChangeListener notUndoableModificationListener = new PropertyChangeListener() { 685 public void propertyChange(PropertyChangeEvent ev) { 686 notUndoableModifications = true; 687 home.setModified(true); 688 } 689 }; 690 this.home.addPropertyChangeListener(Home.Property.STORED_CAMERAS, notUndoableModificationListener); 691 this.home.getEnvironment().addPropertyChangeListener(HomeEnvironment.Property.OBSERVER_CAMERA_ELEVATION_ADJUSTED, notUndoableModificationListener); 692 this.home.getEnvironment().addPropertyChangeListener(HomeEnvironment.Property.VIDEO_WIDTH, notUndoableModificationListener); 693 this.home.getEnvironment().addPropertyChangeListener(HomeEnvironment.Property.VIDEO_ASPECT_RATIO, notUndoableModificationListener); 694 this.home.getEnvironment().addPropertyChangeListener(HomeEnvironment.Property.VIDEO_FRAME_RATE, notUndoableModificationListener); 695 this.home.getEnvironment().addPropertyChangeListener(HomeEnvironment.Property.VIDEO_QUALITY, notUndoableModificationListener); 696 this.home.getEnvironment().addPropertyChangeListener(HomeEnvironment.Property.VIDEO_CAMERA_PATH, notUndoableModificationListener); 697 this.home.getEnvironment().addPropertyChangeListener(HomeEnvironment.Property.CEILING_LIGHT_COLOR, notUndoableModificationListener); 698 this.home.getEnvironment().addPropertyChangeListener(HomeEnvironment.Property.PHOTO_QUALITY, notUndoableModificationListener); 699 this.home.getEnvironment().addPropertyChangeListener(HomeEnvironment.Property.PHOTO_ASPECT_RATIO, notUndoableModificationListener); 700 PropertyChangeListener photoSizeModificationListener = new PropertyChangeListener() { 701 public void propertyChange(PropertyChangeEvent ev) { 702 if (home.getEnvironment().getPhotoAspectRatio() != AspectRatio.VIEW_3D_RATIO) { 703 // Ignore photo size modification with 3D view aspect ratio since it can change for various reasons 704 notUndoableModificationListener.propertyChange(ev); 705 } 706 } 707 }; 708 this.home.getEnvironment().addPropertyChangeListener(HomeEnvironment.Property.PHOTO_WIDTH, photoSizeModificationListener); 709 this.home.getEnvironment().addPropertyChangeListener(HomeEnvironment.Property.PHOTO_HEIGHT, photoSizeModificationListener); 710 PropertyChangeListener timeOrLensModificationListener = new PropertyChangeListener() { 711 public void propertyChange(PropertyChangeEvent ev) { 712 if (ev.getPropertyName().equals(Camera.Property.TIME.name()) 713 || ev.getPropertyName().equals(Camera.Property.LENS.name())) { 714 notUndoableModificationListener.propertyChange(ev); 715 } 716 } 717 }; 718 this.home.getObserverCamera().addPropertyChangeListener(timeOrLensModificationListener); 719 this.home.getTopCamera().addPropertyChangeListener(timeOrLensModificationListener); 720 } 721 } 722 723 /** 724 * Enables or disables action bound to selection. 725 * This method will be called when selection in plan or in catalog changes and when 726 * focused component or modification state in plan changes. 727 */ enableActionsBoundToSelection()728 protected void enableActionsBoundToSelection() { 729 boolean modificationState = getPlanController().isModificationState(); 730 731 // Search if catalog selection contains at least one piece 732 List<CatalogPieceOfFurniture> catalogSelectedItems = 733 getFurnitureCatalogController().getSelectedFurniture(); 734 boolean catalogSelectionContainsFurniture = !catalogSelectedItems.isEmpty(); 735 boolean catalogSelectionContainsOneModifiablePiece = catalogSelectedItems.size() == 1 736 && catalogSelectedItems.get(0).isModifiable(); 737 738 // Search if home selection contains at least one piece, one wall or one dimension line 739 List<Selectable> selectedItems = this.home.getSelectedItems(); 740 boolean homeSelectionContainsDeletableItems = false; 741 boolean homeSelectionContainsFurniture = false; 742 boolean homeSelectionContainsDeletableFurniture = false; 743 boolean homeSelectionContainsOneCopiableItemOrMore = false; 744 boolean homeSelectionContainsOneMovablePieceOfFurnitureOrMore = false; 745 boolean homeSelectionContainsTwoMovablePiecesOfFurnitureOrMore = false; 746 boolean homeSelectionContainsTwoMovableGroupablePiecesOfFurnitureOrMore = false; 747 boolean homeSelectionContainsThreeMovablePiecesOfFurnitureOrMore = false; 748 boolean homeSelectionContainsOnlyOneGroup = selectedItems.size() == 1 749 && selectedItems.get(0) instanceof HomeFurnitureGroup; 750 boolean homeSelectionContainsFurnitureGroup = false; 751 boolean homeSelectionContainsWalls = false; 752 boolean homeSelectionContainsOneWall = false; 753 boolean homeSelectionContainsOneOrTwoWallsWithOneFreeEnd = false; 754 boolean homeSelectionContainsRooms = false; 755 boolean homeSelectionContainsPolylines = false; 756 boolean homeSelectionContainsOnlyOneRoom = false; 757 boolean homeSelectionContainsOnlyOneRoomWithFourPointsOrMore = false; 758 boolean homeSelectionContainsLabels = false; 759 boolean homeSelectionContainsItemsWithText = false; 760 boolean homeSelectionContainsCompass = false; 761 FurnitureController furnitureController = getFurnitureController(); 762 if (!modificationState) { 763 for (Selectable item : selectedItems) { 764 // Check item is deletable 765 if (getPlanController().isItemDeletable(item)) { 766 homeSelectionContainsDeletableItems = true; 767 break; 768 } 769 } 770 List<HomePieceOfFurniture> selectedFurniture = Home.getFurnitureSubList(selectedItems); 771 homeSelectionContainsFurniture = !selectedFurniture.isEmpty(); 772 for (HomePieceOfFurniture piece : selectedFurniture) { 773 // Check piece is deletable 774 if (furnitureController.isPieceOfFurnitureDeletable(piece)) { 775 homeSelectionContainsDeletableFurniture = true; 776 break; 777 } 778 } 779 for (HomePieceOfFurniture piece : selectedFurniture) { 780 if (piece instanceof HomeFurnitureGroup) { 781 homeSelectionContainsFurnitureGroup = true; 782 break; 783 } 784 } 785 int movablePiecesOfFurnitureCount = 0; 786 for (HomePieceOfFurniture piece : selectedFurniture) { 787 if (furnitureController.isPieceOfFurnitureMovable(piece)) { 788 homeSelectionContainsOneMovablePieceOfFurnitureOrMore = true; 789 movablePiecesOfFurnitureCount++; 790 if (movablePiecesOfFurnitureCount >= 2) { 791 homeSelectionContainsTwoMovablePiecesOfFurnitureOrMore = true; 792 } 793 if (movablePiecesOfFurnitureCount >= 3) { 794 homeSelectionContainsThreeMovablePiecesOfFurnitureOrMore = true; 795 break; 796 } 797 } 798 } 799 if (homeSelectionContainsTwoMovablePiecesOfFurnitureOrMore) { 800 homeSelectionContainsTwoMovableGroupablePiecesOfFurnitureOrMore = true; 801 List<HomePieceOfFurniture> furniture = this.home.getFurniture(); 802 // Allow to group only furniture that are not in subgroups 803 for (HomePieceOfFurniture piece : selectedFurniture) { 804 if (!furnitureController.isPieceOfFurnitureMovable(piece) 805 || !furniture.contains(piece)) { 806 homeSelectionContainsTwoMovableGroupablePiecesOfFurnitureOrMore = false; 807 break; 808 } 809 } 810 } 811 List<Wall> selectedWalls = Home.getWallsSubList(selectedItems); 812 homeSelectionContainsWalls = !selectedWalls.isEmpty(); 813 homeSelectionContainsOneWall = selectedWalls.size() == 1; 814 if (selectedWalls.size() >= 2) { 815 Wall [] wallsWithFreeEnd = {null, null, null}; 816 for (Wall wall : selectedWalls) { 817 if ((wall.getArcExtent() == null 818 || wall.getArcExtent() == 0f) 819 && (wall.getWallAtStart() == null 820 || wall.getWallAtEnd() == null)) { 821 for (int i = 0; i < wallsWithFreeEnd.length; i++) { 822 if (wallsWithFreeEnd [i] == null) { 823 wallsWithFreeEnd [i] = wall; 824 break; 825 } 826 } 827 if (wallsWithFreeEnd [2] != null) { 828 break; 829 } 830 } 831 } 832 homeSelectionContainsOneOrTwoWallsWithOneFreeEnd = 833 wallsWithFreeEnd [2] == null 834 && wallsWithFreeEnd [0] != null 835 && (wallsWithFreeEnd [1] == null 836 && !selectedWalls.contains(wallsWithFreeEnd [0].getWallAtStart()) 837 && !selectedWalls.contains(wallsWithFreeEnd [0].getWallAtEnd()) 838 || wallsWithFreeEnd [0].getWallAtEnd() != wallsWithFreeEnd [1] 839 && wallsWithFreeEnd [0].getWallAtStart() != wallsWithFreeEnd [1]); 840 } 841 List<Room> selectedRooms = Home.getRoomsSubList(selectedItems); 842 homeSelectionContainsRooms = !selectedRooms.isEmpty(); 843 homeSelectionContainsOnlyOneRoom = selectedItems.size() == 1 844 && selectedRooms.size() == 1; 845 homeSelectionContainsOnlyOneRoomWithFourPointsOrMore = homeSelectionContainsOnlyOneRoom 846 && selectedRooms.get(0).getPointCount() >= 4; 847 boolean homeSelectionContainsDimensionLines = !Home.getDimensionLinesSubList(selectedItems).isEmpty(); 848 homeSelectionContainsPolylines = !Home.getPolylinesSubList(selectedItems).isEmpty(); 849 homeSelectionContainsLabels = !Home.getLabelsSubList(selectedItems).isEmpty(); 850 homeSelectionContainsCompass = selectedItems.contains(this.home.getCompass()); 851 homeSelectionContainsOneCopiableItemOrMore = 852 homeSelectionContainsFurniture || homeSelectionContainsWalls 853 || homeSelectionContainsRooms || homeSelectionContainsDimensionLines 854 || homeSelectionContainsPolylines || homeSelectionContainsLabels 855 || homeSelectionContainsCompass; 856 homeSelectionContainsItemsWithText = 857 homeSelectionContainsFurniture || homeSelectionContainsRooms 858 || homeSelectionContainsDimensionLines || homeSelectionContainsLabels; 859 } 860 861 HomeView view = getView(); 862 if (this.focusedView == getFurnitureCatalogController().getView()) { 863 view.setEnabled(HomeView.ActionType.COPY, 864 !modificationState && catalogSelectionContainsFurniture); 865 view.setEnabled(HomeView.ActionType.CUT, false); 866 view.setEnabled(HomeView.ActionType.DELETE, false); 867 for (CatalogPieceOfFurniture piece : catalogSelectedItems) { 868 if (piece.isModifiable()) { 869 // Only modifiable catalog furniture may be deleted 870 view.setEnabled(HomeView.ActionType.DELETE, true); 871 break; 872 } 873 } 874 } else if (this.focusedView == furnitureController.getView()) { 875 view.setEnabled(HomeView.ActionType.COPY, homeSelectionContainsFurniture); 876 view.setEnabled(HomeView.ActionType.CUT, homeSelectionContainsDeletableFurniture); 877 view.setEnabled(HomeView.ActionType.DELETE, homeSelectionContainsDeletableFurniture); 878 } else if (this.focusedView == getPlanController().getView()) { 879 view.setEnabled(HomeView.ActionType.COPY, homeSelectionContainsOneCopiableItemOrMore); 880 view.setEnabled(HomeView.ActionType.CUT, homeSelectionContainsDeletableItems); 881 view.setEnabled(HomeView.ActionType.DELETE, homeSelectionContainsDeletableItems); 882 } else { 883 view.setEnabled(HomeView.ActionType.COPY, false); 884 view.setEnabled(HomeView.ActionType.CUT, false); 885 view.setEnabled(HomeView.ActionType.DELETE, false); 886 } 887 enablePasteToGroupAction(); 888 enablePasteStyleAction(); 889 890 Level selectedLevel = this.home.getSelectedLevel(); 891 boolean viewableLevel = selectedLevel == null || selectedLevel.isViewable(); 892 view.setEnabled(HomeView.ActionType.ADD_HOME_FURNITURE, catalogSelectionContainsFurniture 893 && viewableLevel); 894 view.setEnabled(HomeView.ActionType.ADD_FURNITURE_TO_GROUP, catalogSelectionContainsFurniture 895 && viewableLevel && homeSelectionContainsOnlyOneGroup); 896 // In creation mode all actions bound to selection are disabled 897 view.setEnabled(HomeView.ActionType.DELETE_HOME_FURNITURE, 898 homeSelectionContainsDeletableFurniture); 899 view.setEnabled(HomeView.ActionType.DELETE_SELECTION, 900 (catalogSelectionContainsFurniture 901 && this.focusedView == getFurnitureCatalogController().getView()) 902 || (homeSelectionContainsDeletableItems 903 && (this.focusedView == furnitureController.getView() 904 || this.focusedView == getPlanController().getView() 905 || this.focusedView == getHomeController3D().getView()))); 906 view.setEnabled(HomeView.ActionType.MODIFY_FURNITURE, 907 (catalogSelectionContainsOneModifiablePiece 908 && this.focusedView == getFurnitureCatalogController().getView()) 909 || (homeSelectionContainsFurniture 910 && (this.focusedView == furnitureController.getView() 911 || this.focusedView == getPlanController().getView() 912 || this.focusedView == getHomeController3D().getView()))); 913 view.setEnabled(HomeView.ActionType.MODIFY_WALL, 914 homeSelectionContainsWalls); 915 view.setEnabled(HomeView.ActionType.FLIP_HORIZONTALLY, 916 homeSelectionContainsOneCopiableItemOrMore); 917 view.setEnabled(HomeView.ActionType.FLIP_VERTICALLY, 918 homeSelectionContainsOneCopiableItemOrMore); 919 view.setEnabled(HomeView.ActionType.JOIN_WALLS, 920 homeSelectionContainsOneOrTwoWallsWithOneFreeEnd); 921 view.setEnabled(HomeView.ActionType.REVERSE_WALL_DIRECTION, 922 homeSelectionContainsWalls); 923 view.setEnabled(HomeView.ActionType.SPLIT_WALL, 924 homeSelectionContainsOneWall); 925 view.setEnabled(HomeView.ActionType.MODIFY_ROOM, 926 homeSelectionContainsRooms); 927 view.setEnabled(HomeView.ActionType.MODIFY_POLYLINE, 928 homeSelectionContainsPolylines); 929 view.setEnabled(HomeView.ActionType.MODIFY_LABEL, 930 homeSelectionContainsLabels); 931 view.setEnabled(HomeView.ActionType.TOGGLE_BOLD_STYLE, 932 homeSelectionContainsItemsWithText); 933 view.setEnabled(HomeView.ActionType.TOGGLE_ITALIC_STYLE, 934 homeSelectionContainsItemsWithText); 935 view.setEnabled(HomeView.ActionType.INCREASE_TEXT_SIZE, 936 homeSelectionContainsItemsWithText); 937 view.setEnabled(HomeView.ActionType.DECREASE_TEXT_SIZE, 938 homeSelectionContainsItemsWithText); 939 view.setEnabled(HomeView.ActionType.ALIGN_FURNITURE_ON_TOP, 940 homeSelectionContainsTwoMovablePiecesOfFurnitureOrMore); 941 view.setEnabled(HomeView.ActionType.ALIGN_FURNITURE_ON_BOTTOM, 942 homeSelectionContainsTwoMovablePiecesOfFurnitureOrMore); 943 view.setEnabled(HomeView.ActionType.ALIGN_FURNITURE_ON_LEFT, 944 homeSelectionContainsTwoMovablePiecesOfFurnitureOrMore); 945 view.setEnabled(HomeView.ActionType.ALIGN_FURNITURE_ON_RIGHT, 946 homeSelectionContainsTwoMovablePiecesOfFurnitureOrMore); 947 view.setEnabled(HomeView.ActionType.ALIGN_FURNITURE_ON_FRONT_SIDE, 948 homeSelectionContainsTwoMovablePiecesOfFurnitureOrMore); 949 view.setEnabled(HomeView.ActionType.ALIGN_FURNITURE_ON_BACK_SIDE, 950 homeSelectionContainsTwoMovablePiecesOfFurnitureOrMore); 951 view.setEnabled(HomeView.ActionType.ALIGN_FURNITURE_ON_LEFT_SIDE, 952 homeSelectionContainsTwoMovablePiecesOfFurnitureOrMore); 953 view.setEnabled(HomeView.ActionType.ALIGN_FURNITURE_ON_RIGHT_SIDE, 954 homeSelectionContainsTwoMovablePiecesOfFurnitureOrMore); 955 view.setEnabled(HomeView.ActionType.ALIGN_FURNITURE_SIDE_BY_SIDE, 956 homeSelectionContainsTwoMovablePiecesOfFurnitureOrMore); 957 view.setEnabled(HomeView.ActionType.DISTRIBUTE_FURNITURE_HORIZONTALLY, 958 homeSelectionContainsThreeMovablePiecesOfFurnitureOrMore); 959 view.setEnabled(HomeView.ActionType.DISTRIBUTE_FURNITURE_VERTICALLY, 960 homeSelectionContainsThreeMovablePiecesOfFurnitureOrMore); 961 view.setEnabled(HomeView.ActionType.RESET_FURNITURE_ELEVATION, 962 homeSelectionContainsOneMovablePieceOfFurnitureOrMore); 963 view.setEnabled(HomeView.ActionType.GROUP_FURNITURE, 964 homeSelectionContainsTwoMovableGroupablePiecesOfFurnitureOrMore); 965 view.setEnabled(HomeView.ActionType.UNGROUP_FURNITURE, 966 homeSelectionContainsFurnitureGroup); 967 boolean selectionMode = getPlanController() != null 968 && getPlanController().getMode() == PlanController.Mode.SELECTION; 969 view.setEnabled(HomeView.ActionType.ADD_ROOM_POINT, homeSelectionContainsOnlyOneRoom && selectionMode); 970 // Check minimum requirement for DELETE_ROOM_POINT action 971 // and let home view check the coordinates of the deleted point 972 view.setEnabled(HomeView.ActionType.DELETE_ROOM_POINT, 973 homeSelectionContainsOnlyOneRoomWithFourPointsOrMore && selectionMode); 974 } 975 976 /** 977 * Enables clipboard paste action if clipboard isn't empty. 978 */ enablePasteAction()979 public void enablePasteAction() { 980 HomeView view = getView(); 981 boolean pasteEnabled = false; 982 if (this.focusedView == getFurnitureController().getView() 983 || this.focusedView == getPlanController().getView()) { 984 Level selectedLevel = this.home.getSelectedLevel(); 985 pasteEnabled = (selectedLevel == null || selectedLevel.isViewable()) 986 && !getPlanController().isModificationState() && !view.isClipboardEmpty(); 987 } 988 view.setEnabled(HomeView.ActionType.PASTE, pasteEnabled); 989 enablePasteToGroupAction(); 990 enablePasteStyleAction(); 991 } 992 993 /** 994 * Enables paste to group action if clipboard contains furniture and 995 * home selected item is a furniture group. 996 */ enablePasteToGroupAction()997 private void enablePasteToGroupAction() { 998 HomeView view = getView(); 999 boolean pasteToGroupEnabled = false; 1000 if (this.focusedView == getFurnitureController().getView() 1001 || this.focusedView == getPlanController().getView()) { 1002 Level selectedLevel = this.home.getSelectedLevel(); 1003 if ((selectedLevel == null || selectedLevel.isViewable()) 1004 && !getPlanController().isModificationState()) { 1005 List<Selectable> selectedItems = this.home.getSelectedItems(); 1006 if (selectedItems.size() == 1 1007 && selectedItems.get(0) instanceof HomeFurnitureGroup) { 1008 List<Selectable> clipboardItems = view.getClipboardItems(); 1009 if (clipboardItems != null) { 1010 pasteToGroupEnabled = true; 1011 for (Selectable item : clipboardItems) { 1012 if (!(item instanceof HomePieceOfFurniture)) { 1013 pasteToGroupEnabled = false; 1014 break; 1015 } 1016 } 1017 } 1018 } 1019 } 1020 } 1021 view.setEnabled(HomeView.ActionType.PASTE_TO_GROUP, pasteToGroupEnabled); 1022 } 1023 1024 /** 1025 * Enables clipboard paste style action if selection contains some items of a class 1026 * compatible with the clipboard item. 1027 */ enablePasteStyleAction()1028 private void enablePasteStyleAction() { 1029 HomeView view = getView(); 1030 boolean pasteStyleEnabled = false; 1031 if ((this.focusedView == getFurnitureController().getView() 1032 || this.focusedView == getPlanController().getView()) 1033 && !getPlanController().isModificationState()) { 1034 List<Selectable> clipboardItems = view.getClipboardItems(); 1035 if (clipboardItems != null 1036 && clipboardItems.size() == 1) { 1037 Selectable clipboardItem = clipboardItems.get(0); 1038 for (Selectable item : this.home.getSelectedItems()) { 1039 if (item instanceof HomePieceOfFurniture && clipboardItem instanceof HomePieceOfFurniture 1040 || item instanceof Wall && clipboardItem instanceof Wall 1041 || item instanceof Room && clipboardItem instanceof Room 1042 || item instanceof Polyline && clipboardItem instanceof Polyline 1043 || item instanceof Label && clipboardItem instanceof Label) { 1044 pasteStyleEnabled = true; 1045 break; 1046 } 1047 } 1048 } 1049 } 1050 view.setEnabled(HomeView.ActionType.PASTE_STYLE, pasteStyleEnabled); 1051 } 1052 1053 /** 1054 * Enables select all action if home isn't empty. 1055 */ enableSelectAllAction()1056 protected void enableSelectAllAction() { 1057 HomeView view = getView(); 1058 boolean modificationState = getPlanController().isModificationState(); 1059 if (this.focusedView == getFurnitureController().getView()) { 1060 view.setEnabled(HomeView.ActionType.SELECT_ALL, 1061 !modificationState 1062 && this.home.getFurniture().size() > 0); 1063 } else if (this.focusedView == getPlanController().getView() 1064 || this.focusedView == getHomeController3D().getView()) { 1065 boolean homeContainsOneSelectableItemOrMore = !this.home.isEmpty() 1066 || this.home.getCompass().isVisible(); 1067 view.setEnabled(HomeView.ActionType.SELECT_ALL, 1068 !modificationState && homeContainsOneSelectableItemOrMore); 1069 } else { 1070 view.setEnabled(HomeView.ActionType.SELECT_ALL, false); 1071 } 1072 } 1073 1074 /** 1075 * Enables zoom actions depending on current scale. 1076 */ enableZoomActions()1077 private void enableZoomActions() { 1078 PlanController planController = getPlanController(); 1079 float scale = planController.getScale(); 1080 HomeView view = getView(); 1081 view.setEnabled(HomeView.ActionType.ZOOM_IN, scale < planController.getMaximumScale()); 1082 view.setEnabled(HomeView.ActionType.ZOOM_OUT, scale > planController.getMinimumScale()); 1083 } 1084 1085 /** 1086 * Adds undoable edit listener to undo support that enables Undo action. 1087 */ addUndoSupportListener()1088 private void addUndoSupportListener() { 1089 getUndoableEditSupport().addUndoableEditListener( 1090 new UndoableEditListener () { 1091 public void undoableEditHappened(UndoableEditEvent ev) { 1092 HomeView view = getView(); 1093 view.setEnabled(HomeView.ActionType.UNDO, 1094 !getPlanController().isModificationState()); 1095 view.setEnabled(HomeView.ActionType.REDO, false); 1096 view.setUndoRedoName(ev.getEdit().getUndoPresentationName(), null); 1097 saveUndoLevel++; 1098 home.setModified(true); 1099 } 1100 }); 1101 home.addPropertyChangeListener(Home.Property.MODIFIED, new PropertyChangeListener() { 1102 public void propertyChange(PropertyChangeEvent ev) { 1103 if (!home.isModified()) { 1104 // Change undo level and modification flag if home is set as unmodified 1105 saveUndoLevel = 0; 1106 notUndoableModifications = false; 1107 } 1108 } 1109 }); 1110 } 1111 1112 /** 1113 * Adds a furniture listener to home that enables / disables actions on furniture list change. 1114 */ 1115 @SuppressWarnings("unchecked") addHomeItemsListener()1116 private void addHomeItemsListener() { 1117 CollectionListener homeItemsListener = 1118 new CollectionListener() { 1119 public void collectionChanged(CollectionEvent ev) { 1120 if (ev.getType() == CollectionEvent.Type.ADD 1121 || ev.getType() == CollectionEvent.Type.DELETE) { 1122 enableSelectAllAction(); 1123 } 1124 } 1125 }; 1126 this.home.addFurnitureListener((CollectionListener<HomePieceOfFurniture>)homeItemsListener); 1127 this.home.addWallsListener((CollectionListener<Wall>)homeItemsListener); 1128 this.home.addRoomsListener((CollectionListener<Room>)homeItemsListener); 1129 this.home.addPolylinesListener((CollectionListener<Polyline>)homeItemsListener); 1130 this.home.addDimensionLinesListener((CollectionListener<DimensionLine>)homeItemsListener); 1131 this.home.addLabelsListener((CollectionListener<Label>)homeItemsListener); 1132 this.home.getCompass().addPropertyChangeListener(new PropertyChangeListener() { 1133 public void propertyChange(PropertyChangeEvent ev) { 1134 if (Compass.Property.VISIBLE.name().equals(ev.getPropertyName())) { 1135 enableSelectAllAction(); 1136 } 1137 } 1138 }); 1139 this.home.addPropertyChangeListener(Home.Property.CAMERA, new PropertyChangeListener() { 1140 public void propertyChange(PropertyChangeEvent ev) { 1141 getView().setEnabled(HomeView.ActionType.MODIFY_OBSERVER, home.getCamera() == home.getObserverCamera()); 1142 } 1143 }); 1144 } 1145 1146 /** 1147 * Adds a property change listener to home to 1148 * enable/disable authorized actions according to selected level. 1149 */ addLevelListeners()1150 private void addLevelListeners() { 1151 final PropertyChangeListener selectedLevelListener = new PropertyChangeListener() { 1152 public void propertyChange(PropertyChangeEvent ev) { 1153 Level selectedLevel = home.getSelectedLevel(); 1154 if (!home.isAllLevelsSelection()) { 1155 // Keep in selection only items that are at this level 1156 List<Selectable> selectedItemsAtLevel = new ArrayList<Selectable>(); 1157 for (Selectable item : home.getSelectedItems()) { 1158 if (!(item instanceof Elevatable) 1159 || ((Elevatable)item).isAtLevel(selectedLevel)) { 1160 selectedItemsAtLevel.add(item); 1161 } 1162 } 1163 home.setSelectedItems(selectedItemsAtLevel); 1164 } 1165 HomeView view = getView(); 1166 enableCreationToolsActions(view); 1167 enableBackgroungImageActions(view, selectedLevel == null 1168 ? home.getBackgroundImage() 1169 : selectedLevel.getBackgroundImage()); 1170 enableLevelActions(view); 1171 } 1172 }; 1173 this.home.addPropertyChangeListener(Home.Property.SELECTED_LEVEL, selectedLevelListener); 1174 final PropertyChangeListener backgroundImageChangeListener = new PropertyChangeListener() { 1175 public void propertyChange(PropertyChangeEvent ev) { 1176 if (Level.Property.BACKGROUND_IMAGE.name().equals(ev.getPropertyName())) { 1177 enableBackgroungImageActions(getView(), (BackgroundImage)ev.getNewValue()); 1178 } else if (Level.Property.VIEWABLE.name().equals(ev.getPropertyName())) { 1179 enableCreationToolsActions(getView()); 1180 if (!(Boolean)ev.getNewValue()) { 1181 PlanController.Mode mode = getPlanController().getMode(); 1182 if (mode != PlanController.Mode.SELECTION 1183 && mode != PlanController.Mode.PANNING) { 1184 getPlanController().setMode(PlanController.Mode.SELECTION); 1185 } 1186 } 1187 } 1188 } 1189 }; 1190 for (Level level : home.getLevels()) { 1191 level.addPropertyChangeListener(backgroundImageChangeListener); 1192 } 1193 this.home.addLevelsListener(new CollectionListener<Level>() { 1194 public void collectionChanged(CollectionEvent<Level> ev) { 1195 switch (ev.getType()) { 1196 case ADD : 1197 home.setSelectedLevel(ev.getItem()); 1198 ev.getItem().addPropertyChangeListener(backgroundImageChangeListener); 1199 break; 1200 case DELETE : 1201 selectedLevelListener.propertyChange(null); 1202 ev.getItem().removePropertyChangeListener(backgroundImageChangeListener); 1203 break; 1204 } 1205 } 1206 }); 1207 } 1208 1209 /** 1210 * Adds a property change listener to home to 1211 * enable/disable authorized actions according to stored cameras change. 1212 */ addStoredCamerasListener()1213 private void addStoredCamerasListener() { 1214 this.home.addPropertyChangeListener(Home.Property.STORED_CAMERAS, new PropertyChangeListener() { 1215 public void propertyChange(PropertyChangeEvent ev) { 1216 boolean emptyStoredCameras = home.getStoredCameras().isEmpty(); 1217 getView().setEnabled(HomeView.ActionType.DELETE_POINTS_OF_VIEW, !emptyStoredCameras); 1218 getView().setEnabled(HomeView.ActionType.CREATE_PHOTOS_AT_POINTS_OF_VIEW, !emptyStoredCameras); 1219 } 1220 }); 1221 } 1222 1223 /** 1224 * Adds a property change listener to plan controller to 1225 * enable/disable authorized actions according to its modification state and the plan scale. 1226 */ addPlanControllerListeners()1227 private void addPlanControllerListeners() { 1228 getPlanController().addPropertyChangeListener(PlanController.Property.MODIFICATION_STATE, 1229 new PropertyChangeListener() { 1230 public void propertyChange(PropertyChangeEvent ev) { 1231 enableActionsBoundToSelection(); 1232 enableSelectAllAction(); 1233 HomeView view = getView(); 1234 enableLevelActions(view); 1235 boolean modificationState = getPlanController().isModificationState(); 1236 if (modificationState) { 1237 view.setEnabled(HomeView.ActionType.PASTE, false); 1238 view.setEnabled(HomeView.ActionType.UNDO, false); 1239 view.setEnabled(HomeView.ActionType.REDO, false); 1240 } else { 1241 enablePasteAction(); 1242 view.setEnabled(HomeView.ActionType.UNDO, undoManager.canUndo()); 1243 view.setEnabled(HomeView.ActionType.REDO, undoManager.canRedo()); 1244 } 1245 view.setEnabled(HomeView.ActionType.LOCK_BASE_PLAN, !modificationState); 1246 view.setEnabled(HomeView.ActionType.UNLOCK_BASE_PLAN, !modificationState); 1247 } 1248 }); 1249 getPlanController().addPropertyChangeListener(PlanController.Property.MODE, 1250 new PropertyChangeListener() { 1251 public void propertyChange(PropertyChangeEvent ev) { 1252 enableActionsBoundToSelection(); 1253 } 1254 }); 1255 getPlanController().addPropertyChangeListener(PlanController.Property.SCALE, 1256 new PropertyChangeListener() { 1257 public void propertyChange(PropertyChangeEvent ev) { 1258 enableZoomActions(); 1259 } 1260 }); 1261 } 1262 1263 /** 1264 * Adds the selected furniture in catalog to home and selects it. 1265 */ addHomeFurniture()1266 public void addHomeFurniture() { 1267 addFurniture(null); 1268 } 1269 1270 /** 1271 * Adds the selected furniture in catalog to the selected group and selects it. 1272 * @since 5.0 1273 */ addFurnitureToGroup()1274 public void addFurnitureToGroup() { 1275 addFurniture((HomeFurnitureGroup)this.home.getSelectedItems().get(0)); 1276 } 1277 addFurniture(HomeFurnitureGroup group)1278 private void addFurniture(HomeFurnitureGroup group) { 1279 // Use automatically selection mode 1280 getPlanController().setMode(PlanController.Mode.SELECTION); 1281 List<CatalogPieceOfFurniture> selectedFurniture = 1282 getFurnitureCatalogController().getSelectedFurniture(); 1283 if (!selectedFurniture.isEmpty()) { 1284 List<HomePieceOfFurniture> addedFurniture = new ArrayList<HomePieceOfFurniture>(); 1285 for (CatalogPieceOfFurniture piece : selectedFurniture) { 1286 addedFurniture.add(getFurnitureController().createHomePieceOfFurniture(piece)); 1287 } 1288 // Add furniture to home with furnitureController 1289 if (group != null) { 1290 getFurnitureController().addFurnitureToGroup(addedFurniture, group); 1291 } else { 1292 getFurnitureController().addFurniture(addedFurniture); 1293 } 1294 adjustFurnitureSizeAndElevation(addedFurniture, false); 1295 } 1296 } 1297 1298 /** 1299 * Modifies the selected furniture of the focused view. 1300 */ modifySelectedFurniture()1301 public void modifySelectedFurniture() { 1302 if (this.focusedView == getFurnitureCatalogController().getView()) { 1303 getFurnitureCatalogController().modifySelectedFurniture(); 1304 } else if (this.focusedView == getFurnitureController().getView() 1305 || this.focusedView == getPlanController().getView() 1306 || this.focusedView == getHomeController3D().getView()) { 1307 getFurnitureController().modifySelectedFurniture(); 1308 } 1309 } 1310 1311 /** 1312 * Imports a language library chosen by the user. 1313 */ importLanguageLibrary()1314 public void importLanguageLibrary() { 1315 getView().invokeLater(new Runnable() { 1316 public void run() { 1317 final String languageLibraryName = getView().showImportLanguageLibraryDialog(); 1318 if (languageLibraryName != null) { 1319 importLanguageLibrary(languageLibraryName); 1320 } 1321 } 1322 }); 1323 } 1324 1325 /** 1326 * Imports a given language library. 1327 */ importLanguageLibrary(String languageLibraryName)1328 public void importLanguageLibrary(String languageLibraryName) { 1329 try { 1330 if (!this.preferences.languageLibraryExists(languageLibraryName) 1331 || getView().confirmReplaceLanguageLibrary(languageLibraryName)) { 1332 this.preferences.addLanguageLibrary(languageLibraryName); 1333 } 1334 } catch (RecorderException ex) { 1335 String message = this.preferences.getLocalizedString(HomeController.class, 1336 "importLanguageLibraryError", languageLibraryName); 1337 getView().showError(message); 1338 } 1339 } 1340 1341 /** 1342 * Imports furniture to the catalog or home depending on the focused view. 1343 */ importFurniture()1344 public void importFurniture() { 1345 // Always use selection mode after an import furniture operation 1346 getPlanController().setMode(PlanController.Mode.SELECTION); 1347 if (this.focusedView == getFurnitureCatalogController().getView()) { 1348 getFurnitureCatalogController().importFurniture(); 1349 } else { 1350 getFurnitureController().importFurniture(); 1351 } 1352 } 1353 1354 /** 1355 * Imports a furniture library chosen by the user. 1356 */ importFurnitureLibrary()1357 public void importFurnitureLibrary() { 1358 getView().invokeLater(new Runnable() { 1359 public void run() { 1360 final String furnitureLibraryName = getView().showImportFurnitureLibraryDialog(); 1361 if (furnitureLibraryName != null) { 1362 importFurnitureLibrary(furnitureLibraryName); 1363 } 1364 } 1365 }); 1366 } 1367 1368 /** 1369 * Imports a given furniture library. 1370 */ importFurnitureLibrary(String furnitureLibraryName)1371 public void importFurnitureLibrary(String furnitureLibraryName) { 1372 try { 1373 if (!this.preferences.furnitureLibraryExists(furnitureLibraryName) 1374 || getView().confirmReplaceFurnitureLibrary(furnitureLibraryName)) { 1375 this.preferences.addFurnitureLibrary(furnitureLibraryName); 1376 getView().showMessage(this.preferences.getLocalizedString(HomeController.class, "importedFurnitureLibraryMessage", 1377 this.contentManager != null 1378 ? this.contentManager.getPresentationName(furnitureLibraryName, ContentManager.ContentType.FURNITURE_LIBRARY) 1379 : furnitureLibraryName)); 1380 } 1381 } catch (RecorderException ex) { 1382 String message = this.preferences.getLocalizedString(HomeController.class, 1383 "importFurnitureLibraryError", furnitureLibraryName); 1384 getView().showError(message); 1385 } 1386 } 1387 1388 /** 1389 * Imports a texture to the texture catalog. 1390 * @since 4.0 1391 */ importTexture()1392 public void importTexture() { 1393 new ImportedTextureWizardController(this.preferences, 1394 this.viewFactory, this.contentManager).displayView(getView()); 1395 } 1396 1397 /** 1398 * Imports a textures library chosen by the user. 1399 */ importTexturesLibrary()1400 public void importTexturesLibrary() { 1401 getView().invokeLater(new Runnable() { 1402 public void run() { 1403 final String texturesLibraryName = getView().showImportTexturesLibraryDialog(); 1404 if (texturesLibraryName != null) { 1405 importTexturesLibrary(texturesLibraryName); 1406 } 1407 } 1408 }); 1409 } 1410 1411 /** 1412 * Imports a given textures library. 1413 */ importTexturesLibrary(String texturesLibraryName)1414 public void importTexturesLibrary(String texturesLibraryName) { 1415 try { 1416 if (!this.preferences.texturesLibraryExists(texturesLibraryName) 1417 || getView().confirmReplaceTexturesLibrary(texturesLibraryName)) { 1418 this.preferences.addTexturesLibrary(texturesLibraryName); 1419 getView().showMessage(this.preferences.getLocalizedString(HomeController.class, "importedTexturesLibraryMessage", 1420 this.contentManager != null 1421 ? this.contentManager.getPresentationName(texturesLibraryName, ContentManager.ContentType.TEXTURES_LIBRARY) 1422 : texturesLibraryName)); 1423 } 1424 } catch (RecorderException ex) { 1425 String message = this.preferences.getLocalizedString(HomeController.class, 1426 "importTexturesLibraryError", texturesLibraryName); 1427 getView().showError(message); 1428 } 1429 } 1430 1431 /** 1432 * Undoes last operation. 1433 */ undo()1434 public void undo() { 1435 this.undoManager.undo(); 1436 HomeView view = getView(); 1437 boolean moreUndo = this.undoManager.canUndo(); 1438 view.setEnabled(HomeView.ActionType.UNDO, moreUndo); 1439 view.setEnabled(HomeView.ActionType.REDO, true); 1440 if (moreUndo) { 1441 view.setUndoRedoName(this.undoManager.getUndoPresentationName(), 1442 this.undoManager.getRedoPresentationName()); 1443 } else { 1444 view.setUndoRedoName(null, this.undoManager.getRedoPresentationName()); 1445 } 1446 this.saveUndoLevel--; 1447 this.home.setModified(this.saveUndoLevel != 0 || this.notUndoableModifications); 1448 } 1449 1450 /** 1451 * Redoes last undone operation. 1452 */ redo()1453 public void redo() { 1454 this.undoManager.redo(); 1455 HomeView view = getView(); 1456 boolean moreRedo = this.undoManager.canRedo(); 1457 view.setEnabled(HomeView.ActionType.UNDO, true); 1458 view.setEnabled(HomeView.ActionType.REDO, moreRedo); 1459 if (moreRedo) { 1460 view.setUndoRedoName(this.undoManager.getUndoPresentationName(), 1461 this.undoManager.getRedoPresentationName()); 1462 } else { 1463 view.setUndoRedoName(this.undoManager.getUndoPresentationName(), null); 1464 } 1465 this.saveUndoLevel++; 1466 this.home.setModified(this.saveUndoLevel != 0 || this.notUndoableModifications); 1467 } 1468 1469 /** 1470 * Deletes items and post a cut operation to undo support. 1471 */ cut(List<? extends Selectable> items)1472 public void cut(List<? extends Selectable> items) { 1473 // Start a compound edit that deletes items and changes presentation name 1474 UndoableEditSupport undoSupport = getUndoableEditSupport(); 1475 undoSupport.beginUpdate(); 1476 getPlanController().deleteItems(items); 1477 // Add a undoable edit to change presentation name 1478 undoSupport.postEdit(new LocalizedUndoableEdit(preferences, HomeController.class, "undoCutName")); 1479 // End compound edit 1480 undoSupport.endUpdate(); 1481 } 1482 1483 /** 1484 * Adds items to home and posts a paste operation to undo support. 1485 */ paste(final List<? extends Selectable> items)1486 public void paste(final List<? extends Selectable> items) { 1487 // Check if pasted items and currently selected items overlap 1488 List<Selectable> selectedItems = this.home.getSelectedItems(); 1489 float pastedItemsDelta = 0; 1490 if (items.size() == selectedItems.size()) { 1491 // The default delta used to be able to distinguish dropped items from previous selection 1492 pastedItemsDelta = 20; 1493 for (Selectable pastedItem : items) { 1494 // Search which item of selected items it may overlap 1495 float [][] pastedItemPoints = pastedItem.getPoints(); 1496 boolean pastedItemOverlapSelectedItem = false; 1497 for (Selectable selectedItem : selectedItems) { 1498 if (Arrays.deepEquals(pastedItemPoints, selectedItem.getPoints())) { 1499 pastedItemOverlapSelectedItem = true; 1500 break; 1501 } 1502 } 1503 if (!pastedItemOverlapSelectedItem) { 1504 pastedItemsDelta = 0; 1505 break; 1506 } 1507 } 1508 } 1509 addPastedItems(items, null, pastedItemsDelta, pastedItemsDelta, null, "undoPasteName"); 1510 } 1511 1512 /** 1513 * Adds items to home, moves them of (dx, dy) 1514 * and posts a drop operation to undo support. 1515 */ drop(final List<? extends Selectable> items, float dx, float dy)1516 public void drop(final List<? extends Selectable> items, float dx, float dy) { 1517 drop(items, null, dx, dy); 1518 } 1519 1520 /** 1521 * Adds items to home, moves them of (dx, dy) 1522 * and posts a drop operation to undo support. 1523 */ drop(final List<? extends Selectable> items, View destinationView, float dx, float dy)1524 public void drop(final List<? extends Selectable> items, View destinationView, float dx, float dy) { 1525 addPastedItems(items, destinationView, dx, dy, null, "undoDropName"); 1526 } 1527 1528 /** 1529 * Adds items to home before the given item 1530 * and posts a drop operation to undo support. 1531 * @since 6.3 1532 */ drop(List<? extends Selectable> items, View destinationView, Selectable beforeItem)1533 public void drop(List<? extends Selectable> items, View destinationView, Selectable beforeItem) { 1534 addPastedItems(items, destinationView, 0, 0, beforeItem, "undoDropName"); 1535 } 1536 1537 /** 1538 * Adds items to home. 1539 */ addPastedItems(List<? extends Selectable> items, final View destinationView, float dx, float dy, Selectable beforeItem, final String presentationNameKey)1540 private void addPastedItems(List<? extends Selectable> items, 1541 final View destinationView, 1542 float dx, float dy, Selectable beforeItem, 1543 final String presentationNameKey) { 1544 if (items.size() > 1 1545 || (items.size() == 1 1546 && !(items.get(0) instanceof Compass))) { 1547 // Remove Compass instance from copied items 1548 List<Compass> compassList = Home.getSubList(items, Compass.class); 1549 if (compassList.size() != 0) { 1550 items = new ArrayList<Selectable>(items); 1551 items.removeAll(compassList); 1552 } 1553 // Always use selection mode after a drop or a paste operation 1554 getPlanController().setMode(PlanController.Mode.SELECTION); 1555 // Start a compound edit that adds walls, furniture, rooms, dimension lines, polylines and labels to home 1556 UndoableEditSupport undoSupport = getUndoableEditSupport(); 1557 undoSupport.beginUpdate(); 1558 if (destinationView != null 1559 && destinationView == getFurnitureController().getView()) { 1560 getFurnitureController().addFurniture(Home.getFurnitureSubList(items), (HomePieceOfFurniture)beforeItem); 1561 } else { 1562 getPlanController().addItems(items); 1563 } 1564 List<HomePieceOfFurniture> addedFurniture = Home.getFurnitureSubList(items); 1565 adjustFurnitureSizeAndElevation(addedFurniture, dx == 0 && dy == 0 && destinationView == null); 1566 getPlanController().moveItems(items, dx, dy); 1567 if (destinationView == getPlanController().getView()) { 1568 if (this.preferences.isMagnetismEnabled() 1569 && items.size() == 1 1570 && addedFurniture.size() == 1) { 1571 // Adjust piece when it's dropped in plan view 1572 getPlanController().adjustMagnetizedPieceOfFurniture((HomePieceOfFurniture)items.get(0), dx, dy); 1573 } 1574 } 1575 undoSupport.postEdit(new LocalizedUndoableEdit(this.preferences, HomeController.class, presentationNameKey)); 1576 1577 // End compound edit 1578 undoSupport.endUpdate(); 1579 } 1580 } 1581 1582 /** 1583 * Adjusts furniture size and elevation if magnetism is enabled. 1584 * This method should be called after the given furniture is added to the plan, 1585 * to ensure its size in plan is adjusted too. 1586 */ adjustFurnitureSizeAndElevation(List<HomePieceOfFurniture> furniture, boolean keepDoorAndWindowDepth)1587 private void adjustFurnitureSizeAndElevation(List<HomePieceOfFurniture> furniture, boolean keepDoorAndWindowDepth) { 1588 if (this.preferences.isMagnetismEnabled()) { 1589 for (HomePieceOfFurniture piece : furniture) { 1590 if (!(piece instanceof HomeFurnitureGroup) 1591 && piece.isResizable()) { 1592 piece.setWidth(this.preferences.getLengthUnit().getMagnetizedLength(piece.getWidth(), 0.1f)); 1593 // Don't adjust depth of doors or windows otherwise they may be misplaced in a wall 1594 if (!(piece instanceof HomeDoorOrWindow) || !keepDoorAndWindowDepth) { 1595 piece.setDepth(this.preferences.getLengthUnit().getMagnetizedLength(piece.getDepth(), 0.1f)); 1596 } 1597 piece.setHeight(this.preferences.getLengthUnit().getMagnetizedLength(piece.getHeight(), 0.1f)); 1598 } 1599 piece.setElevation(this.preferences.getLengthUnit().getMagnetizedLength(piece.getElevation(), 0.1f)); 1600 } 1601 } 1602 } 1603 1604 /** 1605 * Adds imported models to home, moves them of (dx, dy) 1606 * and post a drop operation to undo support. 1607 */ dropFiles(final List<String> importableModels, float dx, float dy)1608 public void dropFiles(final List<String> importableModels, float dx, float dy) { 1609 // Always use selection mode after a drop operation 1610 getPlanController().setMode(PlanController.Mode.SELECTION); 1611 // Add to home a listener to track imported furniture 1612 final List<HomePieceOfFurniture> importedFurniture = 1613 new ArrayList<HomePieceOfFurniture>(importableModels.size()); 1614 CollectionListener<HomePieceOfFurniture> addedFurnitureListener = 1615 new CollectionListener<HomePieceOfFurniture>() { 1616 public void collectionChanged(CollectionEvent<HomePieceOfFurniture> ev) { 1617 importedFurniture.add(ev.getItem()); 1618 } 1619 }; 1620 this.home.addFurnitureListener(addedFurnitureListener); 1621 1622 // Start a compound edit that adds furniture to home 1623 UndoableEditSupport undoSupport = getUndoableEditSupport(); 1624 undoSupport.beginUpdate(); 1625 // Import furniture 1626 for (String model : importableModels) { 1627 getFurnitureController().importFurniture(model); 1628 } 1629 this.home.removeFurnitureListener(addedFurnitureListener); 1630 1631 if (importedFurniture.size() > 0) { 1632 getPlanController().moveItems(importedFurniture, dx, dy); 1633 this.home.setSelectedItems(importedFurniture); 1634 1635 // Add a undoable edit that will select the imported furniture at redo 1636 undoSupport.postEdit(new DroppingEndUndoableEdit(this.home, this.preferences, 1637 importedFurniture.toArray(new HomePieceOfFurniture [importedFurniture.size()]))); 1638 } 1639 1640 // End compound edit 1641 undoSupport.endUpdate(); 1642 } 1643 1644 /** 1645 * Undoable edit for dropping end. 1646 */ 1647 private static class DroppingEndUndoableEdit extends LocalizedUndoableEdit { 1648 private final Home home; 1649 private final HomePieceOfFurniture [] importedFurniture; 1650 DroppingEndUndoableEdit(Home home, UserPreferences preferences, HomePieceOfFurniture [] importedFurniture)1651 public DroppingEndUndoableEdit(Home home, UserPreferences preferences, 1652 HomePieceOfFurniture [] importedFurniture) { 1653 super(preferences, HomeController.class, "undoDropName"); 1654 this.home = home; 1655 this.importedFurniture = importedFurniture; 1656 } 1657 1658 @Override redo()1659 public void redo() throws CannotRedoException { 1660 super.redo(); 1661 home.setSelectedItems(Arrays.asList(this.importedFurniture)); 1662 } 1663 } 1664 1665 /** 1666 * Paste the furniture in clipboard to the selected group in home. 1667 * @since 5.0 1668 */ pasteToGroup()1669 public void pasteToGroup() { 1670 // Start a compound edit that adds furniture 1671 UndoableEditSupport undoSupport = getUndoableEditSupport(); 1672 undoSupport.beginUpdate(); 1673 List<HomePieceOfFurniture> addedFurniture = Home.getFurnitureSubList(getView().getClipboardItems()); 1674 getFurnitureController().addFurnitureToGroup(addedFurniture, 1675 (HomeFurnitureGroup)this.home.getSelectedItems().get(0)); 1676 adjustFurnitureSizeAndElevation(addedFurniture, true); 1677 undoSupport.postEdit(new LocalizedUndoableEdit(preferences, HomeController.class, "undoPasteToGroupName")); 1678 // End compound edit 1679 undoSupport.endUpdate(); 1680 } 1681 1682 /** 1683 * Paste the style of the item in clipboard on selected items compatible with it. 1684 * @since 5.0 1685 */ pasteStyle()1686 public void pasteStyle() { 1687 // Start a compound edit that modifies items with their controller 1688 UndoableEditSupport undoSupport = getUndoableEditSupport(); 1689 undoSupport.beginUpdate(); 1690 Selectable clipboardItem = getView().getClipboardItems().get(0); 1691 final List<Selectable> selectedItems = this.home.getSelectedItems(); 1692 if (clipboardItem instanceof HomePieceOfFurniture) { 1693 HomePieceOfFurniture clipboardPiece = (HomePieceOfFurniture)clipboardItem; 1694 HomeFurnitureController furnitureController = new HomeFurnitureController( 1695 this.home, this.preferences, this.viewFactory, this.contentManager, undoSupport); 1696 HomeMaterial [] materials = clipboardPiece.getModelMaterials(); 1697 if (materials != null) { 1698 furnitureController.getModelMaterialsController().setMaterials(clipboardPiece.getModelMaterials()); 1699 furnitureController.setPaint(HomeFurnitureController.FurniturePaint.MODEL_MATERIALS); 1700 } else if (clipboardPiece.getTexture() != null) { 1701 furnitureController.getTextureController().setTexture(clipboardPiece.getTexture()); 1702 furnitureController.setPaint(HomeFurnitureController.FurniturePaint.TEXTURED); 1703 } else if (clipboardPiece.getColor() != null) { 1704 furnitureController.setColor(clipboardPiece.getColor()); 1705 furnitureController.setPaint(HomeFurnitureController.FurniturePaint.COLORED); 1706 } else { 1707 furnitureController.setPaint(HomeFurnitureController.FurniturePaint.DEFAULT); 1708 } 1709 Float shininess = clipboardPiece.getShininess(); 1710 furnitureController.setShininess(shininess == null 1711 ? HomeFurnitureController.FurnitureShininess.DEFAULT 1712 : (shininess.floatValue() == 0 1713 ? HomeFurnitureController.FurnitureShininess.MATT 1714 : HomeFurnitureController.FurnitureShininess.SHINY)); 1715 furnitureController.modifyFurniture(); 1716 } else if (clipboardItem instanceof Wall) { 1717 Wall clipboardWall = (Wall)clipboardItem; 1718 WallController wallController = new WallController(this.home, this.preferences, this.viewFactory, this.contentManager, undoSupport); 1719 if (clipboardWall.getLeftSideColor() != null) { 1720 wallController.setLeftSideColor(clipboardWall.getLeftSideColor()); 1721 wallController.setLeftSidePaint(WallController.WallPaint.COLORED); 1722 } else if (clipboardWall.getLeftSideTexture() != null) { 1723 wallController.getLeftSideTextureController().setTexture(clipboardWall.getLeftSideTexture()); 1724 wallController.setLeftSidePaint(WallController.WallPaint.TEXTURED); 1725 } else { 1726 wallController.setLeftSidePaint(WallController.WallPaint.DEFAULT); 1727 } 1728 wallController.setLeftSideShininess(clipboardWall.getLeftSideShininess()); 1729 wallController.getLeftSideBaseboardController().setBaseboard(clipboardWall.getLeftSideBaseboard()); 1730 if (clipboardWall.getRightSideColor() != null) { 1731 wallController.setRightSideColor(clipboardWall.getRightSideColor()); 1732 wallController.setRightSidePaint(WallController.WallPaint.COLORED); 1733 } else if (clipboardWall.getRightSideTexture() != null) { 1734 wallController.getRightSideTextureController().setTexture(clipboardWall.getRightSideTexture()); 1735 wallController.setRightSidePaint(WallController.WallPaint.TEXTURED); 1736 } else { 1737 wallController.setRightSidePaint(WallController.WallPaint.DEFAULT); 1738 } 1739 wallController.setRightSideShininess(clipboardWall.getRightSideShininess()); 1740 wallController.getRightSideBaseboardController().setBaseboard(clipboardWall.getRightSideBaseboard()); 1741 wallController.setPattern(clipboardWall.getPattern()); 1742 wallController.setTopColor(clipboardWall.getTopColor()); 1743 wallController.setTopPaint(clipboardWall.getTopColor() != null 1744 ? WallController.WallPaint.COLORED 1745 : WallController.WallPaint.DEFAULT); 1746 wallController.modifyWalls(); 1747 } else if (clipboardItem instanceof Room) { 1748 Room clipboardRoom = (Room)clipboardItem; 1749 RoomController roomController = new RoomController(this.home, this.preferences, this.viewFactory, this.contentManager, undoSupport); 1750 if (clipboardRoom.getFloorColor() != null) { 1751 roomController.setFloorColor(clipboardRoom.getFloorColor()); 1752 roomController.setFloorPaint(RoomController.RoomPaint.COLORED); 1753 } else if (clipboardRoom.getFloorTexture() != null) { 1754 roomController.getFloorTextureController().setTexture(clipboardRoom.getFloorTexture()); 1755 roomController.setFloorPaint(RoomController.RoomPaint.TEXTURED); 1756 } else { 1757 roomController.setFloorPaint(RoomController.RoomPaint.DEFAULT); 1758 } 1759 roomController.setFloorShininess(clipboardRoom.getFloorShininess()); 1760 if (clipboardRoom.getCeilingColor() != null) { 1761 roomController.setCeilingColor(clipboardRoom.getCeilingColor()); 1762 roomController.setCeilingPaint(RoomController.RoomPaint.COLORED); 1763 } else if (clipboardRoom.getCeilingTexture() != null) { 1764 roomController.getCeilingTextureController().setTexture(clipboardRoom.getCeilingTexture()); 1765 roomController.setCeilingPaint(RoomController.RoomPaint.TEXTURED); 1766 } else { 1767 roomController.setCeilingPaint(RoomController.RoomPaint.DEFAULT); 1768 } 1769 roomController.setCeilingShininess(clipboardRoom.getCeilingShininess()); 1770 roomController.modifyRooms(); 1771 } else if (clipboardItem instanceof Polyline) { 1772 Polyline clipboardPolyline = (Polyline)clipboardItem; 1773 PolylineController polylineController = new PolylineController( 1774 this.home, this.preferences, this.viewFactory, this.contentManager, undoSupport); 1775 polylineController.setThickness(clipboardPolyline.getThickness()); 1776 polylineController.setJoinStyle(clipboardPolyline.getJoinStyle()); 1777 polylineController.setCapStyle(clipboardPolyline.getCapStyle()); 1778 polylineController.setStartArrowStyle(clipboardPolyline.getStartArrowStyle()); 1779 polylineController.setEndArrowStyle(clipboardPolyline.getEndArrowStyle()); 1780 polylineController.setDashStyle(clipboardPolyline.getDashStyle()); 1781 polylineController.setDashPattern(clipboardPolyline.getDashPattern()); 1782 polylineController.setDashOffset(clipboardPolyline.getDashOffset()); 1783 polylineController.setColor(clipboardPolyline.getColor()); 1784 polylineController.modifyPolylines(); 1785 } else if (clipboardItem instanceof Label) { 1786 Label clipboardLabel = (Label)clipboardItem; 1787 LabelController labelController = new LabelController(this.home, this.preferences, this.viewFactory, undoSupport); 1788 labelController.setColor(clipboardLabel.getColor()); 1789 TextStyle labelStyle = clipboardLabel.getStyle(); 1790 if (labelStyle != null) { 1791 labelController.setAlignment(labelStyle.getAlignment()); 1792 labelController.setFontName(labelStyle.getFontName()); 1793 labelController.setFontSize(labelStyle.getFontSize()); 1794 } else { 1795 labelController.setAlignment(null); 1796 labelController.setFontName(null); 1797 labelController.setFontSize(this.preferences.getDefaultTextStyle(Label.class).getFontSize()); 1798 } 1799 labelController.modifyLabels(); 1800 } 1801 1802 // Add a undoable edit to change presentation name 1803 undoSupport.postEdit(new PastingStyleEndUndoableEdit(this.home, this.preferences, 1804 selectedItems.toArray(new Selectable [selectedItems.size()]))); 1805 // End compound edit 1806 undoSupport.endUpdate(); 1807 } 1808 1809 /** 1810 * Undoable edit for pasting style end. 1811 */ 1812 private static class PastingStyleEndUndoableEdit extends LocalizedUndoableEdit { 1813 private final Home home; 1814 private final Selectable [] selectedItems; 1815 PastingStyleEndUndoableEdit(Home home, UserPreferences preferences, Selectable [] selectedItems)1816 public PastingStyleEndUndoableEdit(Home home, UserPreferences preferences, Selectable [] selectedItems) { 1817 super(preferences, HomeController.class, "undoPasteStyleName"); 1818 this.home = home; 1819 this.selectedItems = selectedItems; 1820 } 1821 1822 @Override redo()1823 public void redo() throws CannotRedoException { 1824 super.redo(); 1825 this.home.setSelectedItems(Arrays.asList(this.selectedItems)); 1826 } 1827 } 1828 1829 /** 1830 * Returns the transfer data matching the requested types. 1831 */ createTransferData(final TransferableView.TransferObserver observer, final TransferableView.DataType ... dataTypes)1832 public void createTransferData(final TransferableView.TransferObserver observer, 1833 final TransferableView.DataType ... dataTypes) { 1834 final List<Object> data = new ArrayList<Object>(); 1835 for (int i = 0; i < dataTypes.length; i++) { 1836 if (this.childControllers == null) { 1837 this.childControllers = new ArrayList<Controller>(); 1838 this.childControllers.add(getFurnitureCatalogController()); 1839 this.childControllers.add(getFurnitureController()); 1840 this.childControllers.add(getPlanController()); 1841 this.childControllers.add(getHomeController3D()); 1842 } 1843 for (Controller childController : this.childControllers) { 1844 if (childController.getView() instanceof TransferableView) { 1845 data.add(((TransferableView)childController.getView()).createTransferData(dataTypes [i])); 1846 } 1847 } 1848 } 1849 observer.dataReady(data.toArray()); 1850 } 1851 1852 /** 1853 * Deletes the selection in the focused component. 1854 */ delete()1855 public void delete() { 1856 if (this.focusedView == getFurnitureCatalogController().getView()) { 1857 if (getView().confirmDeleteCatalogSelection()) { 1858 getFurnitureCatalogController().deleteSelection(); 1859 } 1860 } else if (this.focusedView == getFurnitureController().getView()) { 1861 getFurnitureController().deleteSelection(); 1862 } else if (this.focusedView == getPlanController().getView()) { 1863 getPlanController().deleteSelection(); 1864 } 1865 } 1866 1867 /** 1868 * Updates actions when focused view changed. 1869 */ focusedViewChanged(View focusedView)1870 public void focusedViewChanged(View focusedView) { 1871 this.focusedView = focusedView; 1872 enableActionsBoundToSelection(); 1873 enablePasteAction(); 1874 enablePasteToGroupAction(); 1875 enablePasteStyleAction(); 1876 enableSelectAllAction(); 1877 } 1878 1879 /** 1880 * Selects everything in the focused component. 1881 */ selectAll()1882 public void selectAll() { 1883 if (this.focusedView == getFurnitureController().getView()) { 1884 getFurnitureController().selectAll(); 1885 } else if (this.focusedView == getPlanController().getView() 1886 || this.focusedView == getHomeController3D().getView()) { 1887 getPlanController().selectAll(); 1888 } 1889 } 1890 1891 /** 1892 * Creates a new home and adds it to application home list. 1893 */ newHome()1894 public void newHome() { 1895 Home home; 1896 if (this.application != null) { 1897 home = this.application.createHome(); 1898 } else { 1899 home = new Home(this.preferences.getNewWallHeight()); 1900 } 1901 this.application.addHome(home); 1902 } 1903 1904 /** 1905 * Creates a new home from an example chosen by the user. 1906 */ newHomeFromExample()1907 public void newHomeFromExample() { 1908 final String exampleName = getView().showNewHomeFromExampleDialog(); 1909 if (exampleName != null) { 1910 // Read home in a threaded task 1911 Callable<Void> openTask = new Callable<Void>() { 1912 public Void call() throws RecorderException { 1913 // Read home with application recorder 1914 Home openedHome = application.getHomeRecorder().readHome(exampleName); 1915 // Reset furniture names to their catalog one to simulate translation 1916 final Map<String, String> furnitureNames = getCatalogFurnitureNames(preferences.getFurnitureCatalog()); 1917 String groupName = preferences.getLocalizedString(HomeController.class, "defaultGroupName"); 1918 for (HomePieceOfFurniture piece : openedHome.getFurniture()) { 1919 renameToCatalogName(piece, furnitureNames, groupName); 1920 } 1921 openedHome.setName(null); 1922 addHomeToApplication(openedHome); 1923 return null; 1924 } 1925 }; 1926 ThreadedTaskController.ExceptionHandler exceptionHandler = 1927 new ThreadedTaskController.ExceptionHandler() { 1928 public void handleException(Exception ex) { 1929 if (!(ex instanceof InterruptedRecorderException)) { 1930 ex.printStackTrace(); 1931 if (ex instanceof RecorderException) { 1932 String message = preferences.getLocalizedString(HomeController.class, 1933 "openError", exampleName, ex); 1934 getView().showError(message); 1935 } 1936 } 1937 } 1938 }; 1939 new ThreadedTaskController(openTask, 1940 this.preferences.getLocalizedString(HomeController.class, "openMessage"), exceptionHandler, 1941 this.preferences, this.viewFactory).executeTask(getView()); 1942 } 1943 } 1944 1945 /** 1946 * Returns a map with entries containing furniture name associated to their id. 1947 */ getCatalogFurnitureNames(FurnitureCatalog catalog)1948 private Map<String, String> getCatalogFurnitureNames(FurnitureCatalog catalog) { 1949 Map<String, String> furnitureNames = new HashMap<String, String>(); 1950 for (FurnitureCategory category : catalog.getCategories()) { 1951 for (CatalogPieceOfFurniture piece : category.getFurniture()) { 1952 if (piece.getId() != null) { 1953 furnitureNames.put(piece.getId(), piece.getName()); 1954 } 1955 } 1956 } 1957 return furnitureNames; 1958 } 1959 1960 /** 1961 * Renames the given <code>piece</code> from the piece name with the same id in <code>furnitureNames</code>. 1962 */ renameToCatalogName(HomePieceOfFurniture piece, Map<String, String> furnitureNames, String groupName)1963 private void renameToCatalogName(HomePieceOfFurniture piece, 1964 Map<String, String> furnitureNames, 1965 String groupName) { 1966 if (piece instanceof HomeFurnitureGroup) { 1967 piece.setName(groupName); 1968 for (HomePieceOfFurniture groupPiece : ((HomeFurnitureGroup)piece).getFurniture()) { 1969 renameToCatalogName(groupPiece, furnitureNames, groupName); 1970 } 1971 } else { 1972 String id = piece.getCatalogId(); 1973 if (id != null) { 1974 piece.setName(furnitureNames.get(id)); 1975 } 1976 } 1977 } 1978 1979 /** 1980 * Opens a home. This method displays an {@link HomeView#showOpenDialog() open dialog} 1981 * in view, reads the home from the chosen name and adds it to application home list. 1982 */ open()1983 public void open() { 1984 getView().invokeLater(new Runnable() { 1985 public void run() { 1986 final String homeName = getView().showOpenDialog(); 1987 if (homeName != null) { 1988 open(homeName); 1989 } 1990 } 1991 }); 1992 } 1993 1994 /** 1995 * Opens a given <code>homeName</code>home. 1996 */ open(final String homeName)1997 public void open(final String homeName) { 1998 // Check if requested home isn't already opened 1999 for (Home home : this.application.getHomes()) { 2000 if (homeName.equals(home.getName())) { 2001 String message = this.preferences.getLocalizedString( 2002 HomeController.class, "alreadyOpen", homeName); 2003 getView().showMessage(message); 2004 return; 2005 } 2006 } 2007 2008 // Read home in a threaded task 2009 Callable<Void> openTask = new Callable<Void>() { 2010 public Void call() throws RecorderException { 2011 // Read home with application recorder 2012 Home openedHome = application.getHomeRecorder().readHome(homeName); 2013 openedHome.setName(homeName); 2014 addHomeToApplication(openedHome); 2015 if (openedHome.isRepaired()) { 2016 getView().invokeLater(new Runnable() { 2017 public void run() { 2018 String message = preferences.getLocalizedString(HomeController.class, "openRepairedHomeMessage", homeName); 2019 getView().showMessage(message); 2020 } 2021 }); 2022 } 2023 return null; 2024 } 2025 }; 2026 ThreadedTaskController.ExceptionHandler exceptionHandler = 2027 new ThreadedTaskController.ExceptionHandler() { 2028 public void handleException(Exception ex) { 2029 if (!(ex instanceof InterruptedRecorderException)) { 2030 if (ex instanceof DamagedHomeRecorderException) { 2031 DamagedHomeRecorderException ex2 = (DamagedHomeRecorderException)ex; 2032 openDamagedHome(homeName, ex2.getDamagedHome(), ex2.getInvalidContent()); 2033 } else { 2034 ex.printStackTrace(); 2035 if (ex instanceof RecorderException) { 2036 String message = preferences.getLocalizedString(HomeController.class, 2037 "openError", homeName, ex); 2038 getView().showError(message); 2039 } 2040 } 2041 } 2042 } 2043 }; 2044 new ThreadedTaskController(openTask, 2045 this.preferences.getLocalizedString(HomeController.class, "openMessage"), exceptionHandler, 2046 this.preferences, this.viewFactory).executeTask(getView()); 2047 } 2048 2049 /** 2050 * Adds the given home to application. 2051 */ addHomeToApplication(final Home home)2052 private void addHomeToApplication(final Home home) { 2053 getView().invokeLater(new Runnable() { 2054 public void run() { 2055 application.addHome(home); 2056 } 2057 }); 2058 } 2059 2060 /** 2061 * Prompts the user to choose an option to open the given damaged home, 2062 * fixes the damaged home accordingly and shows it. 2063 */ openDamagedHome(final String homeName, Home damagedHome, List<Content> invalidContent)2064 private void openDamagedHome(final String homeName, Home damagedHome, List<Content> invalidContent) { 2065 HomeView.OpenDamagedHomeAnswer answer = getView().confirmOpenDamagedHome( 2066 homeName, damagedHome, invalidContent); 2067 switch (answer) { 2068 case REMOVE_DAMAGED_ITEMS: 2069 removeDamagedItems(damagedHome, invalidContent); 2070 break; 2071 case REPLACE_DAMAGED_ITEMS: 2072 replaceDamagedItems(damagedHome, invalidContent); 2073 break; 2074 } 2075 if (answer != HomeView.OpenDamagedHomeAnswer.DO_NOT_OPEN_HOME) { 2076 damagedHome.setName(homeName); 2077 damagedHome.setRepaired(true); 2078 addHomeToApplication(damagedHome); 2079 } 2080 } 2081 2082 /** 2083 * Removes from the given <code>home</code> all the objects that reference the invalid content. 2084 */ removeDamagedItems(Home home, List<Content> invalidContent)2085 private void removeDamagedItems(Home home, List<Content> invalidContent) { 2086 for (HomePieceOfFurniture piece : home.getFurniture()) { 2087 if (referencesInvalidContent(piece, invalidContent)) { 2088 home.deletePieceOfFurniture(piece); 2089 } else { 2090 removeInvalidTextures(piece, invalidContent); 2091 } 2092 } 2093 for (Wall wall : home.getWalls()) { 2094 if (referencesInvalidContent(wall.getLeftSideTexture(), invalidContent)) { 2095 wall.setLeftSideTexture(null); 2096 } 2097 if (referencesInvalidContent(wall.getRightSideTexture(), invalidContent)) { 2098 wall.setRightSideTexture(null); 2099 } 2100 } 2101 for (Room room : home.getRooms()) { 2102 if (referencesInvalidContent(room.getFloorTexture(), invalidContent)) { 2103 room.setFloorTexture(null); 2104 } 2105 if (referencesInvalidContent(room.getCeilingTexture(), invalidContent)) { 2106 room.setCeilingTexture(null); 2107 } 2108 } 2109 HomeEnvironment environment = home.getEnvironment(); 2110 if (referencesInvalidContent(environment.getGroundTexture(), invalidContent)) { 2111 environment.setGroundTexture(null); 2112 } 2113 if (referencesInvalidContent(environment.getSkyTexture(), invalidContent)) { 2114 environment.setSkyTexture(null); 2115 } 2116 BackgroundImage backgroundImage = home.getBackgroundImage(); 2117 if (backgroundImage != null && invalidContent.contains(backgroundImage.getImage())) { 2118 home.setBackgroundImage(null); 2119 } 2120 for (Level level : home.getLevels()) { 2121 backgroundImage = level.getBackgroundImage(); 2122 if (backgroundImage != null && invalidContent.contains(backgroundImage.getImage())) { 2123 level.setBackgroundImage(null); 2124 } 2125 } 2126 } 2127 2128 /** 2129 * Returns <code>true</code> if the model of the given <code>piece</code> and its icons are not valid. 2130 */ referencesInvalidContent(HomePieceOfFurniture piece, List<Content> invalidContent)2131 private boolean referencesInvalidContent(HomePieceOfFurniture piece, List<Content> invalidContent) { 2132 if (invalidContent.contains(piece.getIcon()) 2133 || invalidContent.contains(piece.getPlanIcon()) 2134 || invalidContent.contains(piece.getModel())) { 2135 return true; 2136 } else if (piece instanceof HomeFurnitureGroup) { 2137 for (HomePieceOfFurniture groupPiece : ((HomeFurnitureGroup)piece).getFurniture()) { 2138 if (referencesInvalidContent(groupPiece, invalidContent)) { 2139 return true; 2140 } 2141 } 2142 } 2143 return false; 2144 } 2145 2146 /** 2147 * Sets to <code>null</code> the invalid textures used by the given <code>piece</code>. 2148 */ removeInvalidTextures(HomePieceOfFurniture piece, List<Content> invalidContent)2149 private void removeInvalidTextures(HomePieceOfFurniture piece, List<Content> invalidContent) { 2150 if (referencesInvalidContent(piece.getTexture(), invalidContent)) { 2151 piece.setTexture(null); 2152 } 2153 HomeMaterial [] materials = piece.getModelMaterials(); 2154 if (materials != null) { 2155 for (int i = 0; i < materials.length; i++) { 2156 if (materials [i] != null 2157 && referencesInvalidContent(materials [i].getTexture(), invalidContent)) { 2158 materials [i] = null; 2159 } 2160 piece.setModelMaterials(materials); 2161 } 2162 } 2163 if (piece instanceof HomeFurnitureGroup) { 2164 for (HomePieceOfFurniture groupPiece : ((HomeFurnitureGroup)piece).getFurniture()) { 2165 removeInvalidTextures(groupPiece, invalidContent); 2166 } 2167 } 2168 } 2169 2170 /** 2171 * Returns <code>true</code> if the given <code>texture</code> is not valid. 2172 */ referencesInvalidContent(TextureImage texture, List<Content> invalidContent)2173 private boolean referencesInvalidContent(TextureImage texture, List<Content> invalidContent) { 2174 return texture != null && invalidContent.contains(texture.getImage()); 2175 } 2176 2177 /** 2178 * Replaces all the objects that reference an invalid content in the given <code>home</code>. 2179 */ replaceDamagedItems(Home home, List<Content> invalidContent)2180 private void replaceDamagedItems(Home home, List<Content> invalidContent) { 2181 List<HomePieceOfFurniture> furniture = home.getFurniture(); 2182 for (int i = furniture.size() - 1; i >= 0; i--) { 2183 HomePieceOfFurniture piece = furniture.get(i); 2184 if (referencesInvalidContent(piece, invalidContent)) { 2185 HomePieceOfFurniture replacingPiece = getFurnitureController().createHomePieceOfFurniture( 2186 new CatalogPieceOfFurniture(piece.getCatalogId(), piece.getName(), piece.getDescription(), 2187 piece.getInformation(), new String [0], null, null, 2188 REPAIRED_ICON_CONTENT, REPAIRED_IMAGE_CONTENT, REPAIRED_MODEL_CONTENT, 2189 piece.getWidth(), piece.getDepth(), piece.getHeight(), piece.getElevation(), 1f, 2190 piece.isMovable(), piece.getStaircaseCutOutShape(), null, false, null, piece.getCreator(), 2191 piece.isResizable(), piece.isDeformable(), piece.isTexturable(), piece.isHorizontallyRotatable(), 2192 piece.getPrice(), piece.getValueAddedTaxPercentage(), piece.getCurrency())); 2193 replacingPiece.setNameVisible(piece.isNameVisible()); 2194 replacingPiece.setNameXOffset(piece.getNameXOffset()); 2195 replacingPiece.setNameYOffset(piece.getNameYOffset()); 2196 replacingPiece.setNameStyle(piece.getNameStyle()); 2197 replacingPiece.setVisible(piece.isVisible()); 2198 replacingPiece.setAngle(piece.getAngle()); 2199 replacingPiece.setX(piece.getX()); 2200 replacingPiece.setY(piece.getY()); 2201 home.addPieceOfFurniture(replacingPiece, i); 2202 if (replacingPiece.isHorizontallyRotatable()) { 2203 replacingPiece.setPitch(piece.getPitch()); 2204 replacingPiece.setRoll(piece.getRoll()); 2205 } 2206 replacingPiece.setLevel(piece.getLevel()); 2207 home.deletePieceOfFurniture(piece); 2208 } else { 2209 replaceInvalidTextures(piece, invalidContent); 2210 } 2211 } 2212 for (Wall wall : home.getWalls()) { 2213 if (referencesInvalidContent(wall.getLeftSideTexture(), invalidContent)) { 2214 wall.setLeftSideTexture(getErrorTexture(wall.getLeftSideTexture())); 2215 } 2216 if (referencesInvalidContent(wall.getRightSideTexture(), invalidContent)) { 2217 wall.setRightSideTexture(getErrorTexture(wall.getRightSideTexture())); 2218 } 2219 } 2220 for (Room room : home.getRooms()) { 2221 if (referencesInvalidContent(room.getFloorTexture(), invalidContent)) { 2222 room.setFloorTexture(getErrorTexture(room.getFloorTexture())); 2223 } 2224 if (referencesInvalidContent(room.getCeilingTexture(), invalidContent)) { 2225 room.setCeilingTexture(getErrorTexture(room.getCeilingTexture())); 2226 } 2227 } 2228 HomeEnvironment environment = home.getEnvironment(); 2229 if (referencesInvalidContent(environment.getGroundTexture(), invalidContent)) { 2230 environment.setGroundTexture(getErrorTexture(environment.getGroundTexture())); 2231 } 2232 if (referencesInvalidContent(environment.getSkyTexture(), invalidContent)) { 2233 environment.setSkyTexture(getErrorTexture(environment.getSkyTexture())); 2234 } 2235 BackgroundImage backgroundImage = home.getBackgroundImage(); 2236 if (backgroundImage != null && invalidContent.contains(backgroundImage.getImage())) { 2237 home.setBackgroundImage(getErrorBackgroundImage(backgroundImage)); 2238 } 2239 for (Level level : home.getLevels()) { 2240 backgroundImage = level.getBackgroundImage(); 2241 if (backgroundImage != null && invalidContent.contains(backgroundImage.getImage())) { 2242 level.setBackgroundImage(getErrorBackgroundImage(backgroundImage)); 2243 } 2244 } 2245 } 2246 2247 /** 2248 * Replaces the invalid textures used by the given <code>piece</code>. 2249 */ replaceInvalidTextures(HomePieceOfFurniture piece, List<Content> invalidContent)2250 private void replaceInvalidTextures(HomePieceOfFurniture piece, List<Content> invalidContent) { 2251 if (referencesInvalidContent(piece.getTexture(), invalidContent)) { 2252 piece.setTexture(getErrorTexture(piece.getTexture())); 2253 } 2254 HomeMaterial [] materials = piece.getModelMaterials(); 2255 if (materials != null) { 2256 for (int i = 0; i < materials.length; i++) { 2257 HomeMaterial material = materials [i]; 2258 if (material != null 2259 && referencesInvalidContent(material.getTexture(), invalidContent)) { 2260 materials [i] = new HomeMaterial(material.getName(), material.getColor(), 2261 getErrorTexture(material.getTexture()), material.getShininess()); 2262 } 2263 piece.setModelMaterials(materials); 2264 } 2265 } 2266 if (piece instanceof HomeFurnitureGroup) { 2267 for (HomePieceOfFurniture groupPiece : ((HomeFurnitureGroup)piece).getFurniture()) { 2268 replaceInvalidTextures(groupPiece, invalidContent); 2269 } 2270 } 2271 } 2272 2273 /** 2274 * Returns a texture referencing a correct image. 2275 */ getErrorTexture(HomeTexture texture)2276 private HomeTexture getErrorTexture(HomeTexture texture) { 2277 return new HomeTexture(new CatalogTexture(texture.getName(), 2278 REPAIRED_IMAGE_CONTENT, texture.getWidth(), texture.getHeight())); 2279 } 2280 2281 /** 2282 * Returns a background image referencing a correct image. 2283 */ getErrorBackgroundImage(BackgroundImage image)2284 private BackgroundImage getErrorBackgroundImage(BackgroundImage image) { 2285 return new BackgroundImage(REPAIRED_IMAGE_CONTENT, 2286 image.getScaleDistance(), image.getScaleDistanceXStart(), image.getScaleDistanceYStart(), 2287 image.getScaleDistanceXEnd(), image.getScaleDistanceYEnd(), 2288 image.getXOrigin(), image.getYOrigin(), image.isVisible()); 2289 } 2290 2291 /** 2292 * Updates user preferences <code>recentHomes</code> and write preferences. 2293 */ updateUserPreferencesRecentHomes(List<String> recentHomes)2294 private void updateUserPreferencesRecentHomes(List<String> recentHomes) { 2295 if (this.application != null) { 2296 // Check every recent home exists 2297 for (int i = recentHomes.size() - 1; i >= 0; i--) { 2298 try { 2299 if (!this.application.getHomeRecorder().exists(recentHomes.get(i))) { 2300 recentHomes.remove(i); 2301 } 2302 } catch (RecorderException ex) { 2303 // If homeName can't be checked ignore it 2304 } 2305 } 2306 this.preferences.setRecentHomes(recentHomes); 2307 } 2308 } 2309 2310 /** 2311 * Returns a list of displayable recent homes. 2312 */ getRecentHomes()2313 public List<String> getRecentHomes() { 2314 if (this.application != null) { 2315 List<String> recentHomes = new ArrayList<String>(); 2316 for (String homeName : this.preferences.getRecentHomes()) { 2317 try { 2318 if (this.application.getHomeRecorder().exists(homeName)) { 2319 recentHomes.add(homeName); 2320 if (recentHomes.size() == this.preferences.getRecentHomesMaxCount()) { 2321 break; 2322 } 2323 } 2324 } catch (RecorderException ex) { 2325 // If homeName can't be checked ignore it 2326 } 2327 } 2328 getView().setEnabled(HomeView.ActionType.DELETE_RECENT_HOMES, 2329 !recentHomes.isEmpty()); 2330 return Collections.unmodifiableList(recentHomes); 2331 } else { 2332 return new ArrayList<String>(); 2333 } 2334 } 2335 2336 /** 2337 * Returns the version of the application for display purpose. 2338 */ getVersion()2339 public String getVersion() { 2340 if (this.application != null) { 2341 String applicationVersion = this.application.getVersion(); 2342 try { 2343 String deploymentInformation = System.getProperty("com.eteks.sweethome3d.deploymentInformation"); 2344 if (deploymentInformation != null) { 2345 applicationVersion += " " + deploymentInformation; 2346 } 2347 } catch (AccessControlException ex) { 2348 // Ignore com.eteks.sweethome3d.deploymentInformation property since it can't be read 2349 } 2350 return applicationVersion; 2351 } else { 2352 return ""; 2353 } 2354 } 2355 2356 /** 2357 * Deletes the list of recent homes in user preferences. 2358 */ deleteRecentHomes()2359 public void deleteRecentHomes() { 2360 updateUserPreferencesRecentHomes(new ArrayList<String>()); 2361 getView().setEnabled(HomeView.ActionType.DELETE_RECENT_HOMES, false); 2362 } 2363 2364 /** 2365 * Manages home close operation. If the home managed by this controller is modified, 2366 * this method will {@link HomeView#confirmSave(String) confirm} 2367 * in view whether home should be saved. Once home is actually saved, 2368 * home is removed from application homes list. 2369 */ close()2370 public void close() { 2371 close(null); 2372 } 2373 2374 2375 /** 2376 * Manages home close operation. If the home managed by this controller is modified, 2377 * this method will {@link HomeView#confirmSave(String) confirm} 2378 * in view whether home should be saved. Once home is actually saved, 2379 * home is removed from application homes list and <code>postCloseTask</code> 2380 * is called if it's not <code>null</code>. 2381 * @since 5.0 2382 */ close(final Runnable postCloseTask)2383 public void close(final Runnable postCloseTask) { 2384 // Create a task that deletes home and run postCloseTask 2385 Runnable closeTask = new Runnable() { 2386 public void run() { 2387 home.setRecovered(false); 2388 application.deleteHome(home); 2389 if (postCloseTask != null) { 2390 postCloseTask.run(); 2391 } 2392 } 2393 }; 2394 2395 if (this.home.isModified() || this.home.isRecovered() || this.home.isRepaired()) { 2396 switch (getView().confirmSave(this.home.getName())) { 2397 case SAVE : save(HomeRecorder.Type.DEFAULT, closeTask); // Falls through 2398 case CANCEL : return; 2399 } 2400 } 2401 closeTask.run(); 2402 } 2403 2404 /** 2405 * Saves the home managed by this controller. If home name doesn't exist, 2406 * this method will act as {@link #saveAs() saveAs} method. 2407 */ save()2408 public void save() { 2409 save(HomeRecorder.Type.DEFAULT, null); 2410 } 2411 2412 /** 2413 * Saves the home managed by this controller and executes <code>postSaveTask</code> 2414 * if it's not <code>null</code>. 2415 */ save(HomeRecorder.Type recorderType, Runnable postSaveTask)2416 private void save(HomeRecorder.Type recorderType, Runnable postSaveTask) { 2417 if (this.home.getName() == null 2418 || this.home.isRepaired()) { 2419 saveAs(recorderType, postSaveTask); 2420 } else { 2421 save(this.home.getName(), recorderType, postSaveTask); 2422 } 2423 } 2424 2425 /** 2426 * Saves the home managed by this controller with a different name. 2427 * This method displays a {@link HomeView#showSaveDialog(String) save dialog} in view, 2428 * and saves home with the chosen name if any. 2429 */ saveAs()2430 public void saveAs() { 2431 saveAs(HomeRecorder.Type.DEFAULT, null); 2432 } 2433 2434 /** 2435 * Saves the home managed by this controller with a different name. 2436 * Once home is actually saved, home is removed from application homes list 2437 * and <code>postCloseTask</code> is called if it's not <code>null</code>. 2438 * @since 4.4 2439 */ saveAs(HomeRecorder.Type recorderType, Runnable postSaveTask)2440 protected void saveAs(HomeRecorder.Type recorderType, Runnable postSaveTask) { 2441 String newName = getView().showSaveDialog(this.home.getName()); 2442 if (newName != null) { 2443 save(newName, recorderType, postSaveTask); 2444 } 2445 } 2446 2447 /** 2448 * Saves the home managed by this controller and compresses it. If home name doesn't exist, 2449 * this method will prompt user to choose a home name. 2450 */ saveAndCompress()2451 public void saveAndCompress() { 2452 save(HomeRecorder.Type.COMPRESSED, null); 2453 } 2454 2455 /** 2456 * Saves the home managed by this controller with a different name and compresses it. 2457 * This method displays a {@link HomeView#showSaveDialog(String) save dialog} in view, 2458 * and saves home with the chosen name if any. 2459 * @since 4.2 2460 */ saveAsAndCompress()2461 public void saveAsAndCompress() { 2462 saveAs(HomeRecorder.Type.COMPRESSED, null); 2463 } 2464 2465 /** 2466 * Actually saves the home managed by this controller and executes <code>postSaveTask</code> 2467 * if it's not <code>null</code>. 2468 */ save(final String homeName, final HomeRecorder.Type recorderType, final Runnable postSaveTask)2469 private void save(final String homeName, 2470 final HomeRecorder.Type recorderType, 2471 final Runnable postSaveTask) { 2472 // If home version is older than current version 2473 // or if home name is changed 2474 // or if user confirms to save a home created with a newer version 2475 if (this.home.getVersion() <= Home.CURRENT_VERSION 2476 || !homeName.equals(this.home.getName()) 2477 || getView().confirmSaveNewerHome(homeName)) { 2478 final Home savedHome; 2479 try { 2480 // Clone home to save it safely in a threaded task 2481 savedHome = this.home.clone(); 2482 } catch (RuntimeException ex) { 2483 // If home data is corrupted some way and couldn't be cloned 2484 // warn the user his home couldn't be saved 2485 getView().showError(preferences.getLocalizedString( 2486 HomeController.class, "saveError", homeName, ex)); 2487 throw ex; 2488 } 2489 Callable<Void> saveTask = new Callable<Void>() { 2490 public Void call() throws RecorderException { 2491 savedHome.setName(contentManager.getPresentationName(homeName, ContentManager.ContentType.SWEET_HOME_3D)); 2492 // Write home with application recorder 2493 application.getHomeRecorder(recorderType).writeHome(savedHome, homeName); 2494 updateSavedHome(homeName, savedHome.getVersion(), postSaveTask); 2495 return null; 2496 } 2497 }; 2498 ThreadedTaskController.ExceptionHandler exceptionHandler = 2499 new ThreadedTaskController.ExceptionHandler() { 2500 public void handleException(Exception ex) { 2501 if (!(ex instanceof InterruptedRecorderException)) { 2502 String cause = ex.toString(); 2503 if (ex instanceof NotEnoughSpaceRecorderException) { 2504 long missingSpace = ((NotEnoughSpaceRecorderException)ex).getMissingSpace(); 2505 float missingSpaceMegaByte = Math.max(0.1f, missingSpace / 1048576f); 2506 cause = "Missing " + new DecimalFormat("#.#").format(missingSpaceMegaByte) + " MB to save home"; 2507 } else if (ex instanceof RecorderException) { 2508 cause = "RecorderException"; 2509 String message = ex.getMessage(); 2510 if (message != null) { 2511 cause += ": " + message; 2512 } 2513 if (ex.getCause() != null) { 2514 cause += "<br>" + ex.getCause(); 2515 } 2516 } 2517 ex.printStackTrace(); 2518 getView().showError(preferences.getLocalizedString( 2519 HomeController.class, "saveError", homeName, cause)); 2520 } 2521 } 2522 }; 2523 new ThreadedTaskController(saveTask, 2524 this.preferences.getLocalizedString(HomeController.class, "saveMessage"), exceptionHandler, 2525 this.preferences, this.viewFactory).executeTask(getView()); 2526 } 2527 } 2528 2529 /** 2530 * Updates the saved home and executes <code>postSaveTask</code> 2531 * if it's not <code>null</code>. 2532 */ updateSavedHome(final String homeName, final long savedVersion, final Runnable postSaveTask)2533 private void updateSavedHome(final String homeName, 2534 final long savedVersion, 2535 final Runnable postSaveTask) { 2536 getView().invokeLater(new Runnable() { 2537 public void run() { 2538 home.setName(homeName); 2539 home.setModified(false); 2540 home.setRecovered(false); 2541 home.setRepaired(false); 2542 home.setVersion(savedVersion); 2543 // Update recent homes list 2544 List<String> recentHomes = new ArrayList<String>(preferences.getRecentHomes()); 2545 int homeNameIndex = recentHomes.indexOf(homeName); 2546 if (homeNameIndex >= 0) { 2547 recentHomes.remove(homeNameIndex); 2548 } 2549 recentHomes.add(0, homeName); 2550 updateUserPreferencesRecentHomes(recentHomes); 2551 2552 if (postSaveTask != null) { 2553 postSaveTask.run(); 2554 } 2555 } 2556 }); 2557 } 2558 2559 /** 2560 * Controls the export of the furniture list of current home to a CSV file. 2561 * @since 4.0 2562 */ exportToCSV()2563 public void exportToCSV() { 2564 final String csvName = getView().showExportToCSVDialog(this.home.getName()); 2565 if (csvName != null) { 2566 // Export furniture list in a threaded task 2567 Callable<Void> exportToCsvTask = new Callable<Void>() { 2568 public Void call() throws RecorderException { 2569 getView().exportToCSV(csvName); 2570 return null; 2571 } 2572 }; 2573 ThreadedTaskController.ExceptionHandler exceptionHandler = 2574 new ThreadedTaskController.ExceptionHandler() { 2575 public void handleException(Exception ex) { 2576 if (!(ex instanceof InterruptedRecorderException)) { 2577 if (ex instanceof RecorderException) { 2578 String message = preferences.getLocalizedString( 2579 HomeController.class, "exportToCSVError", csvName); 2580 getView().showError(message); 2581 } else { 2582 ex.printStackTrace(); 2583 } 2584 } 2585 } 2586 }; 2587 new ThreadedTaskController(exportToCsvTask, 2588 this.preferences.getLocalizedString(HomeController.class, "exportToCSVMessage"), exceptionHandler, 2589 this.preferences, this.viewFactory).executeTask(getView()); 2590 } 2591 } 2592 2593 /** 2594 * Controls the export of the current home plan to a SVG file. 2595 */ exportToSVG()2596 public void exportToSVG() { 2597 final String svgName = getView().showExportToSVGDialog(this.home.getName()); 2598 if (svgName != null) { 2599 // Export plan in a threaded task 2600 Callable<Void> exportToSvgTask = new Callable<Void>() { 2601 public Void call() throws RecorderException { 2602 getView().exportToSVG(svgName); 2603 return null; 2604 } 2605 }; 2606 ThreadedTaskController.ExceptionHandler exceptionHandler = 2607 new ThreadedTaskController.ExceptionHandler() { 2608 public void handleException(Exception ex) { 2609 if (!(ex instanceof InterruptedRecorderException)) { 2610 if (ex instanceof RecorderException) { 2611 String message = preferences.getLocalizedString( 2612 HomeController.class, "exportToSVGError", svgName); 2613 getView().showError(message); 2614 } else { 2615 ex.printStackTrace(); 2616 } 2617 } 2618 } 2619 }; 2620 new ThreadedTaskController(exportToSvgTask, 2621 this.preferences.getLocalizedString(HomeController.class, "exportToSVGMessage"), exceptionHandler, 2622 this.preferences, this.viewFactory).executeTask(getView()); 2623 } 2624 } 2625 2626 /** 2627 * Controls the export of the 3D view of current home to an OBJ file. 2628 */ exportToOBJ()2629 public void exportToOBJ() { 2630 final String objName = getView().showExportToOBJDialog(this.home.getName()); 2631 if (objName != null) { 2632 // Export 3D view in a threaded task 2633 Callable<Void> exportToObjTask = new Callable<Void>() { 2634 public Void call() throws RecorderException { 2635 getView().exportToOBJ(objName); 2636 return null; 2637 } 2638 }; 2639 ThreadedTaskController.ExceptionHandler exceptionHandler = 2640 new ThreadedTaskController.ExceptionHandler() { 2641 public void handleException(Exception ex) { 2642 if (!(ex instanceof InterruptedRecorderException)) { 2643 if (ex instanceof RecorderException) { 2644 String message = preferences.getLocalizedString( 2645 HomeController.class, "exportToOBJError", objName); 2646 getView().showError(message); 2647 } else { 2648 ex.printStackTrace(); 2649 } 2650 } 2651 } 2652 }; 2653 new ThreadedTaskController(exportToObjTask, 2654 this.preferences.getLocalizedString(HomeController.class, "exportToOBJMessage"), exceptionHandler, 2655 this.preferences, this.viewFactory).executeTask(getView()); 2656 } 2657 } 2658 2659 /** 2660 * Controls the creation of multiple photo-realistic images at the stored cameras locations. 2661 */ createPhotos()2662 public void createPhotos() { 2663 PhotosController photosController = new PhotosController(this.home, this.preferences, 2664 getHomeController3D().getView(), this.viewFactory, this.contentManager); 2665 photosController.displayView(getView()); 2666 } 2667 2668 /** 2669 * Controls the creation of photo-realistic images. 2670 */ createPhoto()2671 public void createPhoto() { 2672 PhotoController photoController = new PhotoController(this.home, this.preferences, 2673 getHomeController3D().getView(), this.viewFactory, this.contentManager); 2674 photoController.displayView(getView()); 2675 } 2676 2677 /** 2678 * Controls the creation of 3D videos. 2679 */ createVideo()2680 public void createVideo() { 2681 getPlanController().setMode(PlanController.Mode.SELECTION); 2682 getHomeController3D().viewFromObserver(); 2683 VideoController videoController = new VideoController(this.home, this.preferences, 2684 this.viewFactory, this.contentManager); 2685 videoController.displayView(getView()); 2686 } 2687 2688 /** 2689 * Controls page setup. 2690 */ setupPage()2691 public void setupPage() { 2692 new PageSetupController(this.home, this.preferences, 2693 this.viewFactory, getUndoableEditSupport()).displayView(getView()); 2694 } 2695 2696 /** 2697 * Controls the print preview. 2698 */ previewPrint()2699 public void previewPrint() { 2700 new PrintPreviewController(this.home, this.preferences, 2701 this, this.viewFactory).displayView(getView()); 2702 } 2703 2704 /** 2705 * Controls the print of this home. 2706 */ print()2707 public void print() { 2708 final Callable<Void> printTask = getView().showPrintDialog(); 2709 if (printTask != null) { 2710 // Print in a threaded task 2711 ThreadedTaskController.ExceptionHandler exceptionHandler = 2712 new ThreadedTaskController.ExceptionHandler() { 2713 public void handleException(Exception ex) { 2714 if (!(ex instanceof InterruptedRecorderException)) { 2715 if (ex instanceof RecorderException) { 2716 String message = preferences.getLocalizedString( 2717 HomeController.class, "printError", home.getName()); 2718 getView().showError(message); 2719 } else { 2720 ex.printStackTrace(); 2721 } 2722 } 2723 } 2724 }; 2725 new ThreadedTaskController(printTask, 2726 this.preferences.getLocalizedString(HomeController.class, "printMessage"), exceptionHandler, 2727 this.preferences, this.viewFactory).executeTask(getView()); 2728 } 2729 } 2730 2731 /** 2732 * Controls the print of this home in a PDF file. 2733 */ printToPDF()2734 public void printToPDF() { 2735 final String pdfName = getView().showPrintToPDFDialog(this.home.getName()); 2736 if (pdfName != null) { 2737 // Print to PDF in a threaded task 2738 Callable<Void> printToPdfTask = new Callable<Void>() { 2739 public Void call() throws RecorderException { 2740 getView().printToPDF(pdfName); 2741 return null; 2742 } 2743 }; 2744 ThreadedTaskController.ExceptionHandler exceptionHandler = 2745 new ThreadedTaskController.ExceptionHandler() { 2746 public void handleException(Exception ex) { 2747 if (!(ex instanceof InterruptedRecorderException)) { 2748 if (ex instanceof RecorderException) { 2749 String message = preferences.getLocalizedString( 2750 HomeController.class, "printToPDFError", pdfName); 2751 getView().showError(message); 2752 } else { 2753 ex.printStackTrace(); 2754 } 2755 } 2756 } 2757 }; 2758 new ThreadedTaskController(printToPdfTask, 2759 preferences.getLocalizedString(HomeController.class, "printToPDFMessage"), exceptionHandler, 2760 this.preferences, this.viewFactory).executeTask(getView()); 2761 } 2762 } 2763 2764 /** 2765 * Controls application exit. If any home in application homes list is modified, 2766 * the user will be {@link HomeView#confirmExit() prompted} in view whether he wants 2767 * to discard his modifications or not. 2768 */ exit()2769 public void exit() { 2770 for (Home home : this.application.getHomes()) { 2771 if (home.isModified() || home.isRecovered() || home.isRepaired()) { 2772 if (getView().confirmExit()) { 2773 break; 2774 } else { 2775 return; 2776 } 2777 } 2778 } 2779 // Remove all homes from application 2780 for (Home home : this.application.getHomes()) { 2781 home.setRecovered(false); 2782 this.application.deleteHome(home); 2783 } 2784 // Let application decide what to do when there's no more home 2785 } 2786 2787 /** 2788 * Edits preferences and changes them if user agrees. 2789 */ editPreferences()2790 public void editPreferences() { 2791 new UserPreferencesController(this.preferences, 2792 this.viewFactory, this.contentManager, this).displayView(getView()); 2793 } 2794 2795 /** 2796 * Enables magnetism in preferences. 2797 */ enableMagnetism()2798 public void enableMagnetism() { 2799 this.preferences.setMagnetismEnabled(true); 2800 } 2801 2802 /** 2803 * Disables magnetism in preferences. 2804 */ disableMagnetism()2805 public void disableMagnetism() { 2806 this.preferences.setMagnetismEnabled(false); 2807 } 2808 2809 /** 2810 * Displays a tip message dialog depending on the given mode and 2811 * sets the active mode of the plan controller. 2812 */ setMode(PlanController.Mode mode)2813 public void setMode(PlanController.Mode mode) { 2814 if (getPlanController().getMode() != mode) { 2815 final String actionKey; 2816 if (mode == PlanController.Mode.WALL_CREATION) { 2817 actionKey = HomeView.ActionType.CREATE_WALLS.name(); 2818 } else if (mode == PlanController.Mode.ROOM_CREATION) { 2819 actionKey = HomeView.ActionType.CREATE_ROOMS.name(); 2820 } else if (mode == PlanController.Mode.POLYLINE_CREATION) { 2821 actionKey = HomeView.ActionType.CREATE_POLYLINES.name(); 2822 } else if (mode == PlanController.Mode.DIMENSION_LINE_CREATION) { 2823 actionKey = HomeView.ActionType.CREATE_DIMENSION_LINES.name(); 2824 } else if (mode == PlanController.Mode.LABEL_CREATION) { 2825 actionKey = HomeView.ActionType.CREATE_LABELS.name(); 2826 } else { 2827 actionKey = null; 2828 } 2829 // Display the tip message dialog matching mode 2830 if (actionKey != null 2831 && !this.preferences.isActionTipIgnored(actionKey)) { 2832 getView().invokeLater(new Runnable() { 2833 public void run() { 2834 // Show tip later to let the mode switch finish first 2835 if (getView().showActionTipMessage(actionKey)) { 2836 preferences.setActionTipIgnored(actionKey); 2837 } 2838 } 2839 }); 2840 } 2841 getPlanController().setMode(mode); 2842 } 2843 } 2844 2845 /** 2846 * Displays the wizard that helps to import home background image. 2847 */ importBackgroundImage()2848 public void importBackgroundImage() { 2849 new BackgroundImageWizardController(this.home, this.preferences, 2850 this.viewFactory, this.contentManager, getUndoableEditSupport()).displayView(getView()); 2851 } 2852 2853 /** 2854 * Displays the wizard that helps to change home background image. 2855 */ modifyBackgroundImage()2856 public void modifyBackgroundImage() { 2857 importBackgroundImage(); 2858 } 2859 2860 /** 2861 * Hides the home background image. 2862 */ hideBackgroundImage()2863 public void hideBackgroundImage() { 2864 toggleBackgroundImageVisibility("undoHideBackgroundImageName"); 2865 } 2866 2867 /** 2868 * Shows the home background image. 2869 */ showBackgroundImage()2870 public void showBackgroundImage() { 2871 toggleBackgroundImageVisibility("undoShowBackgroundImageName"); 2872 } 2873 2874 /** 2875 * Toggles visibility of the background image and posts an undoable operation. 2876 */ toggleBackgroundImageVisibility(final String presentationName)2877 private void toggleBackgroundImageVisibility(final String presentationName) { 2878 final Level selectedLevel = this.home.getSelectedLevel(); 2879 doToggleBackgroundImageVisibility(this.home); 2880 getUndoableEditSupport().postEdit(new BackgroundImageVisibilityTogglingUndoableEdit( 2881 this.home, this.preferences, presentationName, selectedLevel)); 2882 } 2883 2884 /** 2885 * Undoable edit for toggling background image visibility. 2886 */ 2887 private static class BackgroundImageVisibilityTogglingUndoableEdit extends LocalizedUndoableEdit { 2888 private final Home home; 2889 private final Level selectedLevel; 2890 BackgroundImageVisibilityTogglingUndoableEdit(Home home, UserPreferences preferences, String presentationName, Level selectedLevel)2891 private BackgroundImageVisibilityTogglingUndoableEdit(Home home, UserPreferences preferences, String presentationName, 2892 Level selectedLevel) { 2893 super(preferences, HomeController.class, presentationName); 2894 this.home = home; 2895 this.selectedLevel = selectedLevel; 2896 } 2897 2898 @Override undo()2899 public void undo() throws CannotUndoException { 2900 super.undo(); 2901 this.home.setSelectedLevel(this.selectedLevel); 2902 doToggleBackgroundImageVisibility(this.home); 2903 } 2904 2905 @Override redo()2906 public void redo() throws CannotRedoException { 2907 super.redo(); 2908 this.home.setSelectedLevel(this.selectedLevel); 2909 doToggleBackgroundImageVisibility(this.home); 2910 } 2911 } 2912 2913 /** 2914 * Toggles visibility of the background image. 2915 */ doToggleBackgroundImageVisibility(Home home)2916 private static void doToggleBackgroundImageVisibility(Home home) { 2917 BackgroundImage backgroundImage = home.getSelectedLevel() != null 2918 ? home.getSelectedLevel().getBackgroundImage() 2919 : home.getBackgroundImage(); 2920 backgroundImage = new BackgroundImage(backgroundImage.getImage(), 2921 backgroundImage.getScaleDistance(), 2922 backgroundImage.getScaleDistanceXStart(), backgroundImage.getScaleDistanceYStart(), 2923 backgroundImage.getScaleDistanceXEnd(), backgroundImage.getScaleDistanceYEnd(), 2924 backgroundImage.getXOrigin(), backgroundImage.getYOrigin(), !backgroundImage.isVisible()); 2925 if (home.getSelectedLevel() != null) { 2926 home.getSelectedLevel().setBackgroundImage(backgroundImage); 2927 } else { 2928 home.setBackgroundImage(backgroundImage); 2929 } 2930 } 2931 2932 /** 2933 * Deletes home background image and posts and posts an undoable operation. 2934 */ deleteBackgroundImage()2935 public void deleteBackgroundImage() { 2936 final Level selectedLevel = this.home.getSelectedLevel(); 2937 final BackgroundImage oldImage; 2938 if (selectedLevel != null) { 2939 oldImage = selectedLevel.getBackgroundImage(); 2940 selectedLevel.setBackgroundImage(null); 2941 } else { 2942 oldImage = this.home.getBackgroundImage(); 2943 this.home.setBackgroundImage(null); 2944 } 2945 getUndoableEditSupport().postEdit(new BackgroundImageDeletionUndoableEdit(this.home, this.preferences, 2946 selectedLevel, oldImage)); 2947 } 2948 2949 /** 2950 * Undoable edit for background image deletion. 2951 */ 2952 private static class BackgroundImageDeletionUndoableEdit extends LocalizedUndoableEdit { 2953 private final Home home; 2954 private final Level selectedLevel; 2955 private final BackgroundImage oldImage; 2956 BackgroundImageDeletionUndoableEdit(Home home, UserPreferences preferences, Level selectedLevel, BackgroundImage oldImage)2957 private BackgroundImageDeletionUndoableEdit(Home home, UserPreferences preferences, 2958 Level selectedLevel, BackgroundImage oldImage) { 2959 super(preferences, HomeController.class, "undoDeleteBackgroundImageName"); 2960 this.home = home; 2961 this.oldImage = oldImage; 2962 this.selectedLevel = selectedLevel; 2963 } 2964 2965 @Override undo()2966 public void undo() throws CannotUndoException { 2967 super.undo(); 2968 this.home.setSelectedLevel(this.selectedLevel); 2969 if (this.selectedLevel != null) { 2970 this.selectedLevel.setBackgroundImage(this.oldImage); 2971 } else { 2972 this.home.setBackgroundImage(this.oldImage); 2973 } 2974 } 2975 2976 @Override redo()2977 public void redo() throws CannotRedoException { 2978 super.redo(); 2979 this.home.setSelectedLevel(this.selectedLevel); 2980 if (this.selectedLevel != null) { 2981 this.selectedLevel.setBackgroundImage(null); 2982 } else { 2983 this.home.setBackgroundImage(null); 2984 } 2985 } 2986 } 2987 2988 /** 2989 * Zooms out in plan. 2990 */ zoomOut()2991 public void zoomOut() { 2992 PlanController planController = getPlanController(); 2993 float newScale = planController.getScale() / 1.5f; 2994 planController.setScale(newScale); 2995 planController.getView().makeSelectionVisible(); 2996 } 2997 2998 /** 2999 * Zooms in in plan. 3000 */ zoomIn()3001 public void zoomIn() { 3002 PlanController planController = getPlanController(); 3003 float newScale = planController.getScale() * 1.5f; 3004 planController.setScale(newScale); 3005 planController.getView().makeSelectionVisible(); 3006 } 3007 3008 /** 3009 * Prompts a name for the current camera and stores it in home. 3010 */ storeCamera()3011 public void storeCamera() { 3012 String now = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM).format(new Date()); 3013 String name = getView().showStoreCameraDialog(now); 3014 if (name != null) { 3015 getHomeController3D().storeCamera(name); 3016 } 3017 } 3018 3019 /** 3020 * Prompts stored cameras in home to be deleted and deletes the ones selected by the user. 3021 */ deleteCameras()3022 public void deleteCameras() { 3023 List<Camera> deletedCameras = getView().showDeletedCamerasDialog(); 3024 if (deletedCameras != null) { 3025 getHomeController3D().deleteCameras(deletedCameras); 3026 } 3027 } 3028 3029 /** 3030 * Detaches the given <code>view</code> from home view. 3031 */ detachView(View view)3032 public void detachView(View view) { 3033 if (view != null) { 3034 getView().detachView(view); 3035 this.notUndoableModifications = true; 3036 home.setModified(true); 3037 } 3038 } 3039 3040 /** 3041 * Attaches the given <code>view</code> to home view. 3042 */ attachView(View view)3043 public void attachView(View view) { 3044 if (view != null) { 3045 getView().attachView(view); 3046 this.notUndoableModifications = true; 3047 home.setModified(true); 3048 } 3049 } 3050 3051 /** 3052 * Displays help window. 3053 */ help()3054 public void help() { 3055 if (helpController == null) { 3056 helpController = new HelpController(this.preferences, this.viewFactory); 3057 } 3058 helpController.displayView(); 3059 } 3060 3061 /** 3062 * Displays about dialog. 3063 */ about()3064 public void about() { 3065 getView().showAboutDialog(); 3066 } 3067 3068 /** 3069 * Controls the change of value of a visual property in home. 3070 * @deprecated {@link #setVisualProperty(String, Object) setVisualProperty} should be replaced by a call to 3071 * {@link #setHomeProperty(String, String)} to ensure the property can be easily saved and read. 3072 */ setVisualProperty(String propertyName, Object propertyValue)3073 public void setVisualProperty(String propertyName, 3074 Object propertyValue) { 3075 this.home.setVisualProperty(propertyName, propertyValue); 3076 } 3077 3078 /** 3079 * Controls the change of value of a property in home. 3080 * @since 5.2 3081 */ setHomeProperty(String propertyName, String propertyValue)3082 public void setHomeProperty(String propertyName, 3083 String propertyValue) { 3084 this.home.setProperty(propertyName, propertyValue); 3085 } 3086 3087 /** 3088 * Checks if some application or libraries updates are available. 3089 * @since 4.0 3090 */ checkUpdates(final boolean displayOnlyIfNewUpdates)3091 public void checkUpdates(final boolean displayOnlyIfNewUpdates) { 3092 String updatesUrl = getPropertyValue("com.eteks.sweethome3d.updatesUrl", "updatesUrl"); 3093 if (updatesUrl != null && updatesUrl.length() > 0) { 3094 final URL url; 3095 try { 3096 url = new URL(updatesUrl); 3097 } catch (MalformedURLException ex) { 3098 ex.printStackTrace(); 3099 return; 3100 } 3101 3102 final List<Library> libraries = this.preferences.getLibraries(); 3103 final Long updatesMinimumDate = displayOnlyIfNewUpdates 3104 ? this.preferences.getUpdatesMinimumDate() 3105 : null; 3106 3107 // Read updates from XML content in updatesUrl in a threaded task 3108 Callable<Void> checkUpdatesTask = new Callable<Void>() { 3109 public Void call() throws IOException, SAXException { 3110 final Map<Library, List<Update>> availableUpdates = readAvailableUpdates(url, libraries, updatesMinimumDate, 3111 displayOnlyIfNewUpdates ? 3000 : -1); 3112 getView().invokeLater(new Runnable () { 3113 public void run() { 3114 if (availableUpdates.isEmpty()) { 3115 if (!displayOnlyIfNewUpdates) { 3116 getView().showMessage(preferences.getLocalizedString(HomeController.class, "noUpdateMessage")); 3117 } 3118 } else if (!getView().showUpdatesMessage(getUpdatesMessage(availableUpdates), !displayOnlyIfNewUpdates)) { 3119 // Search the latest date among updates 3120 long latestUpdateDate = Long.MIN_VALUE; 3121 for (List<Update> libraryAvailableUpdates : availableUpdates.values()) { 3122 for (Update update : libraryAvailableUpdates) { 3123 latestUpdateDate = Math.max(latestUpdateDate, update.getDate().getTime()); 3124 } 3125 } 3126 preferences.setUpdatesMinimumDate(latestUpdateDate + 1); 3127 } 3128 } 3129 }); 3130 return null; 3131 } 3132 }; 3133 ThreadedTaskController.ExceptionHandler exceptionHandler = 3134 new ThreadedTaskController.ExceptionHandler() { 3135 public void handleException(Exception ex) { 3136 if (!displayOnlyIfNewUpdates && !(ex instanceof InterruptedIOException)) { 3137 if (ex instanceof IOException) { 3138 getView().showError(preferences.getLocalizedString(HomeController.class, "checkUpdatesIOError", ex)); 3139 } else if (ex instanceof SAXException) { 3140 getView().showError(preferences.getLocalizedString(HomeController.class, "checkUpdatesXMLError", ex.getMessage())); 3141 } else { 3142 ex.printStackTrace(); 3143 } 3144 } 3145 } 3146 }; 3147 3148 ViewFactory dummyThreadedTaskViewFactory = new ViewFactoryAdapter() { 3149 @Override 3150 public ThreadedTaskView createThreadedTaskView(String taskMessage, UserPreferences preferences, 3151 ThreadedTaskController controller) { 3152 // Return a dummy view that doesn't do anything 3153 return new ThreadedTaskView() { 3154 public void setTaskRunning(boolean taskRunning, View executingView) { 3155 } 3156 3157 public void invokeLater(Runnable runnable) { 3158 getView().invokeLater(runnable); 3159 } 3160 }; 3161 } 3162 }; 3163 new ThreadedTaskController(checkUpdatesTask, 3164 this.preferences.getLocalizedString(HomeController.class, "checkUpdatesMessage"), exceptionHandler, 3165 this.preferences, displayOnlyIfNewUpdates 3166 ? dummyThreadedTaskViewFactory 3167 : this.viewFactory).executeTask(getView()); 3168 } 3169 } 3170 3171 /** 3172 * Returns the System property value of the given <code>propertyKey</code>, or the 3173 * the resource property value matching <code>resourceKey</code> or <code>null</code> 3174 * if none are defined. 3175 */ getPropertyValue(String propertyKey, String resourceKey)3176 private String getPropertyValue(String propertyKey, String resourceKey) { 3177 String propertyValue = System.getProperty(propertyKey); 3178 if (propertyValue != null) { 3179 return propertyValue; 3180 } else { 3181 try { 3182 return this.preferences.getLocalizedString(HomeController.class, resourceKey); 3183 } catch (IllegalArgumentException ex) { 3184 return null; 3185 } 3186 } 3187 } 3188 3189 /** 3190 * Reads the available updates from the XML stream contained in the given <code>url</code>. 3191 * Caution : this method is called from a separate thread. 3192 */ readAvailableUpdates(URL url, List<Library> libraries, Long minDate, int timeout)3193 private Map<Library, List<Update>> readAvailableUpdates(URL url, List<Library> libraries, Long minDate, int timeout) throws IOException, SAXException { 3194 try { 3195 SAXParserFactory factory = SAXParserFactory.newInstance(); 3196 factory.setValidating(false); 3197 SAXParser saxParser = factory.newSAXParser(); 3198 UpdatesHandler updatesHandler = new UpdatesHandler(url); 3199 URLConnection connection = url.openConnection(); 3200 if (timeout > 0) { 3201 connection.setConnectTimeout(timeout); 3202 connection.setReadTimeout(timeout); 3203 } 3204 saxParser.parse(connection.getInputStream(), updatesHandler); 3205 3206 // Filter updates according to application version and libraries version 3207 Map<Library, List<Update>> availableUpdates = new LinkedHashMap<Library, List<Update>>(); 3208 long now = System.currentTimeMillis(); 3209 if (this.application != null) { 3210 String applicationId = this.application.getId(); 3211 List<Update> applicationUpdates = getAvailableUpdates(updatesHandler.getUpdates(applicationId), 3212 this.application.getVersion(), minDate, now); 3213 if (!applicationUpdates.isEmpty()) { 3214 availableUpdates.put(null, applicationUpdates); 3215 } 3216 } 3217 Set<String> updatedLibraryIds = new HashSet<String>(); 3218 for (Library library : libraries) { 3219 if (Thread.interrupted()) { 3220 throw new InterruptedIOException(); 3221 } 3222 String libraryId = library.getId(); 3223 if (libraryId != null 3224 && !updatedLibraryIds.contains(libraryId)) { 3225 List<Update> libraryUpdates = getAvailableUpdates(updatesHandler.getUpdates(libraryId), 3226 library.getVersion(), minDate, now); 3227 if (!libraryUpdates.isEmpty()) { 3228 availableUpdates.put(library, libraryUpdates); 3229 } 3230 // Ignore older libraries with same ID 3231 updatedLibraryIds.add(libraryId); 3232 } 3233 } 3234 return availableUpdates; 3235 } catch (ParserConfigurationException ex) { 3236 throw new SAXException(ex); 3237 } catch (SAXException ex) { 3238 // If task was interrupted (see UpdatesHandler implementation), report the interruption 3239 if (ex.getCause() instanceof InterruptedIOException) { 3240 throw (InterruptedIOException)ex.getCause(); 3241 } else { 3242 throw ex; 3243 } 3244 } 3245 } 3246 3247 /** 3248 * Returns the updates sublist which match the given <code>version</code>. 3249 * If no update has a date greater that <code>minDate</code>, an empty list is returned. 3250 * Caution : this method is called from a separate thread. 3251 */ getAvailableUpdates(List<Update> updates, String version, Long minDate, long maxDate)3252 private List<Update> getAvailableUpdates(List<Update> updates, String version, Long minDate, long maxDate) { 3253 if (updates != null) { 3254 boolean recentUpdates = false; 3255 List<Update> availableUpdates = new ArrayList<Update>(); 3256 for (Update update : updates) { 3257 String minVersion = update.getMinVersion(); 3258 String maxVersion = update.getMaxVersion(); 3259 String operatingSystem = update.getOperatingSystem(); 3260 if (OperatingSystem.compareVersions(version, update.getVersion()) < 0 3261 && (minVersion == null || OperatingSystem.compareVersions(minVersion, version) <= 0) 3262 && (maxVersion == null || OperatingSystem.compareVersions(version, maxVersion) < 0) 3263 && (operatingSystem == null || System.getProperty("os.name").matches(operatingSystem))) { 3264 Date date = update.getDate(); 3265 if (date == null 3266 || ((minDate == null || date.getTime() >= minDate) 3267 && date.getTime() < maxDate)) { 3268 availableUpdates.add(update); 3269 recentUpdates = true; 3270 } 3271 } 3272 } 3273 if (recentUpdates) { 3274 Collections.sort(availableUpdates, new Comparator<Update>() { 3275 public int compare(Update update1, Update update2) { 3276 return -OperatingSystem.compareVersions(update1.getVersion(), update2.getVersion()); 3277 } 3278 }); 3279 return availableUpdates; 3280 } 3281 } 3282 return Collections.emptyList(); 3283 } 3284 3285 /** 3286 * Returns the message for the given updates. 3287 */ getUpdatesMessage(Map<Library, List<Update>> updates)3288 private String getUpdatesMessage(Map<Library, List<Update>> updates) { 3289 if (updates.isEmpty()) { 3290 return this.preferences.getLocalizedString(HomeController.class, "noUpdateMessage"); 3291 } else { 3292 String message = "<html><head><style>" 3293 + this.preferences.getLocalizedString(HomeController.class, "updatesMessageStyleSheet") 3294 + " .separator { margin: 0px;}</style></head><body>" 3295 + this.preferences.getLocalizedString(HomeController.class, "updatesMessageTitle"); 3296 String applicationUpdateMessage = this.preferences.getLocalizedString(HomeController.class, "applicationUpdateMessage"); 3297 String libraryUpdateMessage = this.preferences.getLocalizedString(HomeController.class, "libraryUpdateMessage"); 3298 String sizeUpdateMessage = this.preferences.getLocalizedString(HomeController.class, "sizeUpdateMessage"); 3299 String downloadUpdateMessage = this.preferences.getLocalizedString(HomeController.class, "downloadUpdateMessage"); 3300 String updatesMessageSeparator = this.preferences.getLocalizedString(HomeController.class, "updatesMessageSeparator"); 3301 boolean firstUpdate = true; 3302 for (Map.Entry<Library, List<Update>> updateEntry : updates.entrySet()) { 3303 if (firstUpdate) { 3304 firstUpdate = false; 3305 } else { 3306 message += updatesMessageSeparator; 3307 } 3308 Library library = updateEntry.getKey(); 3309 if (library == null) { 3310 // Application itself 3311 if (this.application != null) { 3312 message += getApplicationOrLibraryUpdateMessage(updateEntry.getValue(), this.application.getName(), 3313 applicationUpdateMessage, sizeUpdateMessage, downloadUpdateMessage); 3314 } 3315 } else { 3316 String name = library.getName(); 3317 if (name == null) { 3318 name = library.getDescription(); 3319 if (name == null) { 3320 name = library.getLocation(); 3321 } 3322 } 3323 message += getApplicationOrLibraryUpdateMessage(updateEntry.getValue(), name, 3324 libraryUpdateMessage, sizeUpdateMessage, downloadUpdateMessage); 3325 } 3326 } 3327 3328 message += "</body></html>"; 3329 return message; 3330 } 3331 } 3332 3333 /** 3334 * Returns the message for the update of the application or a library. 3335 */ getApplicationOrLibraryUpdateMessage(List<Update> updates, String applicationOrLibraryName, String applicationOrLibraryUpdateMessage, String sizeUpdateMessage, String downloadUpdateMessage)3336 private String getApplicationOrLibraryUpdateMessage(List<Update> updates, 3337 String applicationOrLibraryName, 3338 String applicationOrLibraryUpdateMessage, 3339 String sizeUpdateMessage, 3340 String downloadUpdateMessage) { 3341 String message = ""; 3342 boolean first = true; 3343 DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.LONG); 3344 DecimalFormat megabyteSizeFormat = new DecimalFormat("#,##0.#"); 3345 for (Update update : updates) { 3346 String size; 3347 if (update.getSize() != null) { 3348 // Format at MB format 3349 size = String.format(sizeUpdateMessage, 3350 megabyteSizeFormat.format(update.getSize() / (1024. * 1024.))); 3351 } else { 3352 size = ""; 3353 } 3354 message += String.format(applicationOrLibraryUpdateMessage, 3355 applicationOrLibraryName, update.getVersion(), dateFormat.format(update.getDate()), size); 3356 if (first) { 3357 first = false; 3358 URL downloadPage = update.getDownloadPage(); 3359 if (downloadPage == null) { 3360 downloadPage = update.getDefaultDownloadPage(); 3361 } 3362 if (downloadPage != null) { 3363 message += String.format(downloadUpdateMessage, downloadPage); 3364 } 3365 } 3366 String comment = update.getComment(); 3367 if (comment == null) { 3368 comment = update.getDefaultComment(); 3369 } 3370 if (comment != null) { 3371 message += "<p class='separator'/>"; 3372 message += comment; 3373 message += "<p class='separator'/>"; 3374 } 3375 } 3376 return message; 3377 } 3378 3379 /** 3380 * SAX handler used to parse updates XML files. 3381 * DTD used in updated files:<pre> 3382 * <!ELEMENT updates (update*)> 3383 * 3384 * <!ELEMENT update (downloadPage*, comment*)> 3385 * <!ATTLIST update id CDATA #REQUIRED> 3386 * <!ATTLIST update version CDATA #REQUIRED> 3387 * <!ATTLIST update operatingSystem CDATA #IMPLIED> 3388 * <!ATTLIST update date CDATA #REQUIRED> 3389 * <!ATTLIST update minVersion CDATA #IMPLIED> 3390 * <!ATTLIST update maxVersion CDATA #IMPLIED> 3391 * <!ATTLIST update size CDATA #IMPLIED> 3392 * <!ATTLIST update inherits CDATA #IMPLIED> 3393 * 3394 * <!ELEMENT downloadPage EMPTY> 3395 * <!ATTLIST downloadPage url CDATA #REQUIRED> 3396 * <!ATTLIST downloadPage lang CDATA #IMPLIED> 3397 * 3398 * <!ELEMENT comment (#PCDATA)> 3399 * <!ATTLIST comment lang CDATA #IMPLIED> 3400 * </pre> 3401 * with <code>updates</code> as root element, 3402 * <code>operatingSystem</code> an optional regular expression for the target OS, 3403 * <code>inherits</code> the id of an other <code>update</code> element with the same version, 3404 * <code>date</code> using <code>yyyy-MM-ddThh:mm:ss<code> or <code>yyyy-MM-dd</code> format 3405 * at GMT and <code>comment</code> element possibly containing XHTML. 3406 */ 3407 private class UpdatesHandler extends DefaultHandler { 3408 private final URL baseUrl; 3409 private final StringBuilder comment = new StringBuilder(); 3410 private final SimpleDateFormat dateTimeFormat; 3411 private final SimpleDateFormat dateFormat; 3412 private final Map<String, List<Update>> updates = new HashMap<String, List<Update>>(); 3413 private Update update; 3414 private boolean inComment; 3415 private boolean inUpdate; 3416 private String language; 3417 UpdatesHandler(URL baseUrl)3418 public UpdatesHandler(URL baseUrl) { 3419 this.baseUrl = baseUrl; 3420 TimeZone gmtTimeZone = TimeZone.getTimeZone("GMT"); 3421 this.dateTimeFormat = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss"); 3422 this.dateTimeFormat.setTimeZone(gmtTimeZone); 3423 this.dateFormat = new SimpleDateFormat("yyyy-MM-dd"); 3424 this.dateFormat.setTimeZone(gmtTimeZone); 3425 } 3426 3427 /** 3428 * Returns the update matching the given <code>id</code>. 3429 */ getUpdates(String id)3430 private List<Update> getUpdates(String id) { 3431 return this.updates.get(id); 3432 } 3433 3434 /** 3435 * Throws a <code>SAXException</code> exception initialized with a <code>InterruptedRecorderException</code> 3436 * cause if current thread is interrupted. The interrupted status of the current thread 3437 * is cleared when an exception is thrown. 3438 */ checkCurrentThreadIsntInterrupted()3439 private void checkCurrentThreadIsntInterrupted() throws SAXException { 3440 if (Thread.interrupted()) { 3441 throw new SAXException(new InterruptedIOException()); 3442 } 3443 } 3444 3445 @Override startElement(String uri, String localName, String name, Attributes attributes)3446 public void startElement(String uri, String localName, String name, Attributes attributes) throws SAXException { 3447 checkCurrentThreadIsntInterrupted(); 3448 if (this.inComment) { 3449 // Reproduce comment content 3450 this.comment.append("<" + name); 3451 for (int i = 0; i < attributes.getLength(); i++) { 3452 this.comment.append(" " + attributes.getQName(i) + "=\"" + attributes.getValue(i) + "\""); 3453 } 3454 this.comment.append(">"); 3455 } else if (this.inUpdate && "comment".equals(name)) { 3456 this.comment.setLength(0); 3457 this.language = attributes.getValue("lang"); 3458 if (this.language == null || preferences.getLanguage().equals(this.language)) { 3459 this.inComment = true; 3460 } 3461 } else if (this.inUpdate && "downloadPage".equals(name)) { 3462 String url = attributes.getValue("url"); 3463 if (url != null) { 3464 try { 3465 String language = attributes.getValue("lang"); 3466 if (language == null) { 3467 this.update.setDefaultDownloadPage(new URL(this.baseUrl, url)); 3468 } else if (preferences.getLanguage().equals(language)) { 3469 this.update.setDownloadPage(new URL(this.baseUrl, url)); 3470 } 3471 } catch (MalformedURLException ex) { 3472 // Ignore bad URLs 3473 } 3474 } 3475 } else if (!this.inUpdate && "update".equals(name)) { 3476 String id = attributes.getValue("id"); 3477 String version = attributes.getValue("version"); 3478 if (id != null 3479 && version != null) { 3480 this.update = new Update(id, version); 3481 3482 String inheritedUpdate = attributes.getValue("inherits"); 3483 // If update inherits from an other update, search the update with the same id and version 3484 if (inheritedUpdate != null) { 3485 List<Update> updates = this.updates.get(inheritedUpdate); 3486 if (updates != null) { 3487 for (Update update : updates) { 3488 if (version.equals(update.getVersion())) { 3489 this.update = update.clone(); 3490 this.update.setId(id); 3491 break; 3492 } 3493 } 3494 } 3495 } 3496 3497 String dateAttibute = attributes.getValue("date"); 3498 if (dateAttibute != null) { 3499 try { 3500 this.update.setDate(this.dateTimeFormat.parse(dateAttibute)); 3501 } catch (ParseException ex) { 3502 try { 3503 this.update.setDate(this.dateFormat.parse(dateAttibute)); 3504 } catch (ParseException ex1) { 3505 } 3506 } 3507 } 3508 3509 String minVersion = attributes.getValue("minVersion"); 3510 if (minVersion != null) { 3511 this.update.setMinVersion(minVersion); 3512 } 3513 3514 String maxVersion = attributes.getValue("maxVersion"); 3515 if (maxVersion != null) { 3516 this.update.setMaxVersion(maxVersion); 3517 } 3518 3519 String size = attributes.getValue("size"); 3520 if (size != null) { 3521 try { 3522 this.update.setSize(new Long (size)); 3523 } catch (NumberFormatException ex) { 3524 // Ignore malformed number 3525 } 3526 } 3527 3528 String operatingSystem = attributes.getValue("operatingSystem"); 3529 if (operatingSystem != null) { 3530 this.update.setOperatingSystem(operatingSystem); 3531 } 3532 3533 List<Update> updates = this.updates.get(id); 3534 if (updates == null) { 3535 updates = new ArrayList<Update>(); 3536 this.updates.put(id, updates); 3537 } 3538 updates.add(this.update); 3539 this.inUpdate = true; 3540 } 3541 } 3542 } 3543 3544 @Override characters(char [] ch, int start, int length)3545 public void characters(char [] ch, int start, int length) throws SAXException { 3546 checkCurrentThreadIsntInterrupted(); 3547 if (this.inComment) { 3548 // Reproduce comment content 3549 this.comment.append(ch, start, length); 3550 } 3551 } 3552 3553 @Override endElement(String uri, String localName, String name)3554 public void endElement(String uri, String localName, String name) throws SAXException { 3555 if (this.inComment) { 3556 if ("comment".equals(name)) { 3557 String comment = this.comment.toString().trim().replace('\n', ' '); 3558 if (comment.length() == 0) { 3559 comment = null; 3560 } 3561 if (this.language == null) { 3562 this.update.setDefaultComment(comment); 3563 } else { 3564 this.update.setComment(comment); 3565 } 3566 this.inComment = false; 3567 } else { 3568 // Reproduce comment content 3569 this.comment.append("</" + name + ">"); 3570 } 3571 } else if (this.inUpdate && "update".equals(name)) { 3572 this.inUpdate = false; 3573 } 3574 } 3575 } 3576 3577 /** 3578 * Update info. 3579 */ 3580 private static class Update implements Cloneable { 3581 private String id; 3582 private final String version; 3583 private Date date; 3584 private String minVersion; 3585 private String maxVersion; 3586 private Long size; 3587 private String operatingSystem; 3588 private URL defaultDownloadPage; 3589 private URL downloadPage; 3590 private String defaultComment; 3591 private String comment; 3592 Update(String id, String version)3593 public Update(String id, String version) { 3594 this.id = id; 3595 this.version = version; 3596 } 3597 getId()3598 public String getId() { 3599 return this.id; 3600 } 3601 setId(String id)3602 public void setId(String id) { 3603 this.id = id; 3604 } 3605 getVersion()3606 public String getVersion() { 3607 return this.version; 3608 } 3609 getDate()3610 public Date getDate() { 3611 return this.date; 3612 } 3613 setDate(Date date)3614 public void setDate(Date date) { 3615 this.date = date; 3616 } 3617 getMinVersion()3618 public String getMinVersion() { 3619 return this.minVersion; 3620 } 3621 setMinVersion(String minVersion)3622 public void setMinVersion(String minVersion) { 3623 this.minVersion = minVersion; 3624 } 3625 getMaxVersion()3626 public String getMaxVersion() { 3627 return this.maxVersion; 3628 } 3629 setMaxVersion(String maxVersion)3630 public void setMaxVersion(String maxVersion) { 3631 this.maxVersion = maxVersion; 3632 } 3633 getSize()3634 public Long getSize() { 3635 return this.size; 3636 } 3637 setSize(Long size)3638 public void setSize(Long size) { 3639 this.size = size; 3640 } 3641 getOperatingSystem()3642 public String getOperatingSystem() { 3643 return this.operatingSystem; 3644 } 3645 setOperatingSystem(String system)3646 public void setOperatingSystem(String system) { 3647 this.operatingSystem = system; 3648 } 3649 getDefaultDownloadPage()3650 public URL getDefaultDownloadPage() { 3651 return this.defaultDownloadPage; 3652 } 3653 setDefaultDownloadPage(URL defaultDownloadPage)3654 public void setDefaultDownloadPage(URL defaultDownloadPage) { 3655 this.defaultDownloadPage = defaultDownloadPage; 3656 } 3657 getDownloadPage()3658 public URL getDownloadPage() { 3659 return this.downloadPage; 3660 } 3661 setDownloadPage(URL downloadPage)3662 public void setDownloadPage(URL downloadPage) { 3663 this.downloadPage = downloadPage; 3664 } 3665 getDefaultComment()3666 public String getDefaultComment() { 3667 return this.defaultComment; 3668 } 3669 setDefaultComment(String defaultComment)3670 public void setDefaultComment(String defaultComment) { 3671 this.defaultComment = defaultComment; 3672 } 3673 getComment()3674 public String getComment() { 3675 return this.comment; 3676 } 3677 setComment(String comment)3678 public void setComment(String comment) { 3679 this.comment = comment; 3680 } 3681 3682 @Override clone()3683 protected Update clone() { 3684 try { 3685 return (Update)super.clone(); 3686 } catch (CloneNotSupportedException ex) { 3687 throw new InternalError(); 3688 } 3689 } 3690 } 3691 } 3692