1 /* 2 * LevelController.java 27 oct 2011 3 * 4 * Sweet Home 3D, Copyright (c) 2011 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.PropertyChangeListener; 23 import java.beans.PropertyChangeSupport; 24 import java.util.ArrayList; 25 import java.util.Arrays; 26 import java.util.List; 27 28 import javax.swing.undo.CannotRedoException; 29 import javax.swing.undo.CannotUndoException; 30 import javax.swing.undo.UndoableEdit; 31 import javax.swing.undo.UndoableEditSupport; 32 33 import com.eteks.sweethome3d.model.Elevatable; 34 import com.eteks.sweethome3d.model.Home; 35 import com.eteks.sweethome3d.model.Level; 36 import com.eteks.sweethome3d.model.Selectable; 37 import com.eteks.sweethome3d.model.UserPreferences; 38 39 /** 40 * A MVC controller for home levels view. 41 * @author Emmanuel Puybaret 42 */ 43 public class LevelController implements Controller { 44 /** 45 * The properties that may be edited by the view associated to this controller. 46 */ 47 public enum Property {VIEWABLE, NAME, ELEVATION, ELEVATION_INDEX, FLOOR_THICKNESS, HEIGHT, LEVELS, SELECT_LEVEL_INDEX} 48 49 private final Home home; 50 private final UserPreferences preferences; 51 private final ViewFactory viewFactory; 52 private final UndoableEditSupport undoSupport; 53 private final PropertyChangeSupport propertyChangeSupport; 54 private DialogView homeLevelView; 55 56 private String name; 57 private Boolean viewable; 58 private Float elevation; 59 private Integer elevationIndex; 60 private Float floorThickness; 61 private Float height; 62 private Level [] levels; 63 private Integer selectedLevelIndex; 64 65 /** 66 * Creates the controller of home levels view with undo support. 67 */ LevelController(Home home, UserPreferences preferences, ViewFactory viewFactory, UndoableEditSupport undoSupport)68 public LevelController(Home home, 69 UserPreferences preferences, 70 ViewFactory viewFactory, 71 UndoableEditSupport undoSupport) { 72 this.home = home; 73 this.preferences = preferences; 74 this.viewFactory = viewFactory; 75 this.undoSupport = undoSupport; 76 this.propertyChangeSupport = new PropertyChangeSupport(this); 77 78 updateProperties(); 79 } 80 81 /** 82 * Returns the view associated with this controller. 83 */ getView()84 public DialogView getView() { 85 // Create view lazily only once it's needed 86 if (this.homeLevelView == null) { 87 this.homeLevelView = this.viewFactory.createLevelView(this.preferences, this); 88 } 89 return this.homeLevelView; 90 } 91 92 /** 93 * Displays the view controlled by this controller. 94 */ displayView(View parentView)95 public void displayView(View parentView) { 96 getView().displayView(parentView); 97 } 98 99 /** 100 * Adds the property change <code>listener</code> in parameter to this controller. 101 */ addPropertyChangeListener(Property property, PropertyChangeListener listener)102 public void addPropertyChangeListener(Property property, PropertyChangeListener listener) { 103 this.propertyChangeSupport.addPropertyChangeListener(property.name(), listener); 104 } 105 106 /** 107 * Removes the property change <code>listener</code> in parameter from this controller. 108 */ removePropertyChangeListener(Property property, PropertyChangeListener listener)109 public void removePropertyChangeListener(Property property, PropertyChangeListener listener) { 110 this.propertyChangeSupport.removePropertyChangeListener(property.name(), listener); 111 } 112 113 /** 114 * Updates edited properties from selected level in the home edited by this controller. 115 */ updateProperties()116 protected void updateProperties() { 117 Level selectedLevel = this.home.getSelectedLevel(); 118 setLevels(clone(this.home.getLevels().toArray(new Level [0]))); 119 if (selectedLevel == null) { 120 setSelectedLevelIndex(null); 121 setName(null); // Nothing to edit 122 setViewable(Boolean.TRUE); 123 setElevation(null, false); 124 setFloorThickness(null); 125 setHeight(null); 126 setElevationIndex(null, false); 127 } else { 128 setSelectedLevelIndex(this.home.getLevels().indexOf(selectedLevel)); 129 setName(selectedLevel.getName()); 130 setViewable(selectedLevel.isViewable()); 131 setElevation(selectedLevel.getElevation(), false); 132 setFloorThickness(selectedLevel.getFloorThickness()); 133 setHeight(selectedLevel.getHeight()); 134 setElevationIndex(selectedLevel.getElevationIndex(), false); 135 } 136 } 137 clone(Level[] levels)138 private Level [] clone(Level[] levels) { 139 for (int i = 0; i < levels.length; i++) { 140 levels [i] = levels [i].clone(); 141 } 142 return levels; 143 } 144 145 /** 146 * Returns <code>true</code> if the given <code>property</code> is editable. 147 * Depending on whether a property is editable or not, the view associated to this controller 148 * may render it differently. 149 * The implementation of this method always returns <code>true</code>. 150 */ isPropertyEditable(Property property)151 public boolean isPropertyEditable(Property property) { 152 return true; 153 } 154 155 /** 156 * Sets the edited name. 157 */ setName(String name)158 public void setName(String name) { 159 if (name != this.name) { 160 String oldName = this.name; 161 this.name = name; 162 this.propertyChangeSupport.firePropertyChange(Property.NAME.name(), oldName, name); 163 if (this.selectedLevelIndex != null) { 164 this.levels [this.selectedLevelIndex].setName(name); 165 this.propertyChangeSupport.firePropertyChange(Property.LEVELS.name(), null, this.levels); 166 } 167 } 168 } 169 170 /** 171 * Returns the edited name. 172 */ getName()173 public String getName() { 174 return this.name; 175 } 176 177 /** 178 * Sets the edited viewable attribute. 179 * @since 5.0 180 */ setViewable(Boolean viewable)181 public void setViewable(Boolean viewable) { 182 if (viewable != this.viewable) { 183 Boolean oldViewable = viewable; 184 this.viewable = viewable; 185 this.propertyChangeSupport.firePropertyChange(Property.VIEWABLE.name(), oldViewable, viewable); 186 if (viewable != null && this.selectedLevelIndex != null) { 187 this.levels [this.selectedLevelIndex].setViewable(viewable); 188 this.propertyChangeSupport.firePropertyChange(Property.LEVELS.name(), null, this.levels); 189 } 190 } 191 } 192 193 /** 194 * Returns the edited viewable attribute. 195 * @since 5.0 196 */ getViewable()197 public Boolean getViewable() { 198 return this.viewable; 199 } 200 201 /** 202 * Sets the edited elevation. 203 */ setElevation(Float elevation)204 public void setElevation(Float elevation) { 205 setElevation(elevation, true); 206 } 207 setElevation(Float elevation, boolean updateLevels)208 private void setElevation(Float elevation, boolean updateLevels) { 209 if (elevation != this.elevation) { 210 Float oldElevation = this.elevation; 211 this.elevation = elevation; 212 this.propertyChangeSupport.firePropertyChange(Property.ELEVATION.name(), oldElevation, elevation); 213 214 if (updateLevels 215 && elevation != null 216 && this.selectedLevelIndex != null) { 217 int elevationIndex = updateLevelElevation(this.levels [this.selectedLevelIndex], 218 elevation, Arrays.asList(this.levels)); 219 setElevationIndex(elevationIndex, false); 220 updateLevels(); 221 } 222 } 223 } 224 225 /** 226 * Updates the elevation of the given <code>level</code> and modifies the 227 * elevation index of other levels if necessary. 228 */ updateLevelElevation(Level level, float elevation, List<Level> levels)229 private static int updateLevelElevation(Level level, float elevation, List<Level> levels) { 230 // Search biggest elevation index at the given elevation 231 // and update elevation index of other levels at the current elevation of the modified level 232 int levelIndex = levels.size(); 233 int elevationIndex = 0; 234 for (int i = 0; i < levels.size(); i++) { 235 Level homeLevel = levels.get(i); 236 if (homeLevel == level) { 237 levelIndex = i; 238 } else { 239 if (homeLevel.getElevation() == elevation) { 240 elevationIndex = homeLevel.getElevationIndex() + 1; 241 } else if (i > levelIndex 242 && homeLevel.getElevation() == level.getElevation()) { 243 homeLevel.setElevationIndex(homeLevel.getElevationIndex() - 1); 244 } 245 } 246 } 247 level.setElevation(elevation); 248 level.setElevationIndex(elevationIndex); 249 return elevationIndex; 250 } 251 252 /** 253 * Returns the edited elevation. 254 */ getElevation()255 public Float getElevation() { 256 return this.elevation; 257 } 258 259 /** 260 * Sets the edited elevation index. 261 * @since 5.0 262 */ setElevationIndex(Integer elevationIndex)263 public void setElevationIndex(Integer elevationIndex) { 264 setElevationIndex(elevationIndex, true); 265 } 266 setElevationIndex(Integer elevationIndex, boolean updateLevels)267 private void setElevationIndex(Integer elevationIndex, boolean updateLevels) { 268 if (elevationIndex != this.elevationIndex) { 269 Integer oldElevationIndex = this.elevationIndex; 270 this.elevationIndex = elevationIndex; 271 this.propertyChangeSupport.firePropertyChange(Property.ELEVATION_INDEX.name(), oldElevationIndex, elevationIndex); 272 273 if (updateLevels 274 && elevationIndex != null 275 && this.selectedLevelIndex != null) { 276 updateLevelElevationIndex(this.levels [this.selectedLevelIndex], elevationIndex, Arrays.asList(this.levels)); 277 updateLevels(); 278 } 279 } 280 } 281 282 /** 283 * Updates the elevation index of the given <code>level</code> and modifies the 284 * elevation index of other levels at same elevation if necessary. 285 */ updateLevelElevationIndex(Level level, int elevationIndex, List<Level> levels)286 private static void updateLevelElevationIndex(Level level, int elevationIndex, List<Level> levels) { 287 // Update elevation index of levels with a value between selected level index and new index 288 float elevationIndexSignum = Math.signum(elevationIndex - level.getElevationIndex()); 289 for (Level homeLevel : levels) { 290 if (homeLevel != level 291 && homeLevel.getElevation() == level.getElevation() 292 && Math.signum(homeLevel.getElevationIndex() - level.getElevationIndex()) == elevationIndexSignum 293 && Math.signum(homeLevel.getElevationIndex() - elevationIndex) != elevationIndexSignum) { 294 homeLevel.setElevationIndex(homeLevel.getElevationIndex() - (int)elevationIndexSignum); 295 } else if (homeLevel.getElevation() > level.getElevation()) { 296 break; 297 } 298 } 299 level.setElevationIndex(elevationIndex); 300 } 301 updateLevels()302 private void updateLevels() { 303 // Create a temporary home with levels to update their order 304 Home tempHome = new Home(); 305 Level selectedLevel = this.levels [this.selectedLevelIndex]; 306 for (Level homeLevel : this.levels) { 307 tempHome.addLevel(homeLevel); 308 } 309 List<Level> updatedLevels = tempHome.getLevels(); 310 setLevels(updatedLevels.toArray(new Level [updatedLevels.size()])); 311 setSelectedLevelIndex(updatedLevels.indexOf(selectedLevel)); 312 } 313 314 /** 315 * Returns the edited elevation index. 316 * @since 5.0 317 */ getElevationIndex()318 public Integer getElevationIndex() { 319 return this.elevationIndex; 320 } 321 322 /** 323 * Sets the edited floor thickness. 324 */ setFloorThickness(Float floorThickness)325 public void setFloorThickness(Float floorThickness) { 326 if (floorThickness != this.floorThickness) { 327 Float oldFloorThickness = this.floorThickness; 328 this.floorThickness = floorThickness; 329 this.propertyChangeSupport.firePropertyChange(Property.FLOOR_THICKNESS.name(), oldFloorThickness, floorThickness); 330 if (floorThickness != null && this.selectedLevelIndex != null) { 331 this.levels [this.selectedLevelIndex].setFloorThickness(floorThickness); 332 this.propertyChangeSupport.firePropertyChange(Property.LEVELS.name(), null, this.levels); 333 } 334 } 335 } 336 337 /** 338 * Returns the edited floor thickness. 339 */ getFloorThickness()340 public Float getFloorThickness() { 341 return this.floorThickness; 342 } 343 344 /** 345 * Sets the edited height. 346 */ setHeight(Float height)347 public void setHeight(Float height) { 348 if (height != this.height) { 349 Float oldHeight = this.height; 350 this.height = height; 351 this.propertyChangeSupport.firePropertyChange(Property.HEIGHT.name(), oldHeight, height); 352 if (height != null && this.selectedLevelIndex != null) { 353 this.levels [this.selectedLevelIndex].setHeight(height); 354 this.propertyChangeSupport.firePropertyChange(Property.LEVELS.name(), null, this.levels); 355 } 356 } 357 } 358 359 /** 360 * Returns the edited height. 361 */ getHeight()362 public Float getHeight() { 363 return this.height; 364 } 365 366 /** 367 * Sets home levels. 368 */ setLevels(Level [] levels)369 private void setLevels(Level [] levels) { 370 if (levels != this.levels) { 371 Level [] oldLevels = this.levels; 372 this.levels = levels; 373 this.propertyChangeSupport.firePropertyChange(Property.LEVELS.name(), oldLevels, levels); 374 } 375 } 376 377 /** 378 * Returns a copy of home levels. 379 */ getLevels()380 public Level [] getLevels() { 381 return this.levels.clone(); 382 } 383 384 /** 385 * Sets the selected level index. 386 */ setSelectedLevelIndex(Integer selectedLevelIndex)387 private void setSelectedLevelIndex(Integer selectedLevelIndex) { 388 if (selectedLevelIndex != this.selectedLevelIndex) { 389 Integer oldSelectedLevelIndex = this.selectedLevelIndex; 390 this.selectedLevelIndex = selectedLevelIndex; 391 this.propertyChangeSupport.firePropertyChange(Property.SELECT_LEVEL_INDEX.name(), oldSelectedLevelIndex, selectedLevelIndex); 392 } 393 } 394 395 /** 396 * Returns the selected level index. 397 */ getSelectedLevelIndex()398 public Integer getSelectedLevelIndex() { 399 return this.selectedLevelIndex; 400 } 401 402 /** 403 * Controls the modification of selected level in the edited home. 404 */ modifyLevels()405 public void modifyLevels() { 406 Level selectedLevel = this.home.getSelectedLevel(); 407 if (selectedLevel != null) { 408 List<Selectable> oldSelection = this.home.getSelectedItems(); 409 String name = getName(); 410 Boolean viewable = getViewable(); 411 Float elevation = getElevation(); 412 Float floorThickness = getFloorThickness(); 413 Float height = getHeight(); 414 Integer elevationIndex = getElevationIndex(); 415 416 ModifiedLevel modifiedLevel = new ModifiedLevel(selectedLevel); 417 // Apply modification 418 doModifyLevel(this.home, modifiedLevel, name, viewable, elevation, floorThickness, height, elevationIndex); 419 if (this.undoSupport != null) { 420 UndoableEdit undoableEdit = new LevelModificationUndoableEdit( 421 this.home, this.preferences, oldSelection.toArray(new Selectable [oldSelection.size()]), 422 modifiedLevel, name, viewable, elevation, floorThickness, height, elevationIndex); 423 this.undoSupport.postEdit(undoableEdit); 424 } 425 if (name != null) { 426 this.preferences.addAutoCompletionString("LevelName", name); 427 } 428 } 429 } 430 431 /** 432 * Undoable edit for level modification. This class isn't anonymous to avoid 433 * being bound to controller and its view. 434 */ 435 private static class LevelModificationUndoableEdit extends LocalizedUndoableEdit { 436 private final Home home; 437 private final Selectable [] oldSelection; 438 private final ModifiedLevel modifiedLevel; 439 private final String name; 440 private final Boolean viewable; 441 private final Float elevation; 442 private final Float floorThickness; 443 private final Float height; 444 private final Integer elevationIndex; 445 LevelModificationUndoableEdit(Home home, UserPreferences preferences, Selectable [] oldSelection, ModifiedLevel modifiedLevel, String name, Boolean viewable, Float elevation, Float floorThickness, Float height, Integer elevationIndex)446 private LevelModificationUndoableEdit(Home home, 447 UserPreferences preferences, 448 Selectable [] oldSelection, 449 ModifiedLevel modifiedLevel, 450 String name, 451 Boolean viewable, 452 Float elevation, 453 Float floorThickness, 454 Float height, 455 Integer elevationIndex) { 456 super(preferences, LevelController.class, "undoModifyLevelName"); 457 this.home = home; 458 this.oldSelection = oldSelection; 459 this.modifiedLevel = modifiedLevel; 460 this.name = name; 461 this.viewable = viewable; 462 this.elevation = elevation; 463 this.floorThickness = floorThickness; 464 this.height = height; 465 this.elevationIndex = elevationIndex; 466 } 467 468 @Override undo()469 public void undo() throws CannotUndoException { 470 super.undo(); 471 undoModifyLevel(this.home, this.modifiedLevel); 472 this.home.setSelectedLevel(this.modifiedLevel.getLevel()); 473 this.home.setSelectedItems(Arrays.asList(this.oldSelection)); 474 } 475 476 @Override redo()477 public void redo() throws CannotRedoException { 478 super.redo(); 479 this.home.setSelectedLevel(this.modifiedLevel.getLevel()); 480 doModifyLevel(this.home, this.modifiedLevel, this.name, this.viewable, 481 this.elevation, this.floorThickness, this.height, this.elevationIndex); 482 } 483 } 484 485 /** 486 * Modifies level properties with the values in parameter. 487 */ doModifyLevel(Home home, ModifiedLevel modifiedLevel, String name, Boolean viewable, Float elevation, Float floorThickness, Float height, Integer elevationIndex)488 private static void doModifyLevel(Home home, ModifiedLevel modifiedLevel, 489 String name, Boolean viewable, Float elevation, 490 Float floorThickness, Float height, 491 Integer elevationIndex) { 492 Level level = modifiedLevel.getLevel(); 493 if (name != null) { 494 level.setName(name); 495 } 496 if (viewable != null) { 497 List<Selectable> selectedItems = home.getSelectedItems(); 498 level.setViewable(viewable); 499 home.setSelectedItems(getViewableSublist(selectedItems)); 500 } 501 if (elevation != null 502 && elevation != level.getElevation()) { 503 updateLevelElevation(level, elevation, home.getLevels()); 504 } 505 if (elevationIndex != null) { 506 updateLevelElevationIndex(level, elevationIndex, home.getLevels()); 507 } 508 if (!home.getEnvironment().isAllLevelsVisible()) { 509 // Update visibility of levels 510 Level selectedLevel = home.getSelectedLevel(); 511 boolean visible = true; 512 for (Level homeLevel : home.getLevels()) { 513 homeLevel.setVisible(visible); 514 if (homeLevel == selectedLevel) { 515 visible = false; 516 } 517 } 518 } 519 if (floorThickness != null) { 520 level.setFloorThickness(floorThickness); 521 } 522 if (height != null) { 523 level.setHeight(height); 524 } 525 } 526 527 /** 528 * Returns a sub list of <code>items</code> that are at a viewable level. 529 */ getViewableSublist(List<? extends Selectable> items)530 private static List<Selectable> getViewableSublist(List<? extends Selectable> items) { 531 List<Selectable> viewableItems = new ArrayList<Selectable>(items.size()); 532 for (Selectable item : items) { 533 if (!(item instanceof Elevatable) 534 || ((Elevatable)item).getLevel().isViewable()) { 535 viewableItems.add(item); 536 } 537 } 538 return viewableItems; 539 } 540 541 /** 542 * Restores level properties from the values stored in <code>modifiedLevel</code>. 543 */ undoModifyLevel(Home home, ModifiedLevel modifiedLevel)544 private static void undoModifyLevel(Home home, ModifiedLevel modifiedLevel) { 545 modifiedLevel.reset(); 546 Level level = modifiedLevel.getLevel(); 547 if (modifiedLevel.getElevation() != level.getElevation()) { 548 updateLevelElevation(level, modifiedLevel.getElevation(), home.getLevels()); 549 } 550 if (modifiedLevel.getElevationIndex() != level.getElevationIndex()) { 551 updateLevelElevationIndex(level, modifiedLevel.getElevationIndex(), home.getLevels()); 552 } 553 } 554 555 /** 556 * Stores the current properties values of a modified level. 557 */ 558 private static class ModifiedLevel { 559 private final Level level; 560 private final String name; 561 private final boolean viewable; 562 private final float elevation; 563 private final float floorThickness; 564 private final float height; 565 private final int elevationIndex; 566 ModifiedLevel(Level level)567 public ModifiedLevel(Level level) { 568 this.level = level; 569 this.name = level.getName(); 570 this.viewable = level.isViewable(); 571 this.elevation = level.getElevation(); 572 this.floorThickness = level.getFloorThickness(); 573 this.height = level.getHeight(); 574 this.elevationIndex = level.getElevationIndex(); 575 } 576 getLevel()577 public Level getLevel() { 578 return this.level; 579 } 580 getElevation()581 public float getElevation() { 582 return this.elevation; 583 } 584 getElevationIndex()585 public int getElevationIndex() { 586 return this.elevationIndex; 587 } 588 reset()589 public void reset() { 590 this.level.setName(this.name); 591 this.level.setViewable(this.viewable); 592 this.level.setFloorThickness(this.floorThickness); 593 this.level.setHeight(this.height); 594 } 595 } 596 } 597