1 /*
2  * FurnitureController.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.awt.geom.Line2D;
23 import java.beans.PropertyChangeEvent;
24 import java.beans.PropertyChangeListener;
25 import java.util.ArrayList;
26 import java.util.Arrays;
27 import java.util.Collections;
28 import java.util.Comparator;
29 import java.util.HashMap;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.TreeMap;
33 
34 import javax.swing.undo.CannotRedoException;
35 import javax.swing.undo.CannotUndoException;
36 import javax.swing.undo.UndoableEditSupport;
37 
38 import com.eteks.sweethome3d.model.CollectionEvent;
39 import com.eteks.sweethome3d.model.CollectionListener;
40 import com.eteks.sweethome3d.model.DoorOrWindow;
41 import com.eteks.sweethome3d.model.Home;
42 import com.eteks.sweethome3d.model.HomeDoorOrWindow;
43 import com.eteks.sweethome3d.model.HomeFurnitureGroup;
44 import com.eteks.sweethome3d.model.HomeLight;
45 import com.eteks.sweethome3d.model.HomePieceOfFurniture;
46 import com.eteks.sweethome3d.model.HomePieceOfFurniture.SortableProperty;
47 import com.eteks.sweethome3d.model.Level;
48 import com.eteks.sweethome3d.model.Light;
49 import com.eteks.sweethome3d.model.PieceOfFurniture;
50 import com.eteks.sweethome3d.model.Selectable;
51 import com.eteks.sweethome3d.model.SelectionEvent;
52 import com.eteks.sweethome3d.model.SelectionListener;
53 import com.eteks.sweethome3d.model.UserPreferences;
54 
55 /**
56  * A MVC controller for the home furniture table.
57  * @author Emmanuel Puybaret
58  */
59 public class FurnitureController implements Controller {
60   private final Home                home;
61   private final UserPreferences     preferences;
62   private final ViewFactory         viewFactory;
63   private final ContentManager      contentManager;
64   private final UndoableEditSupport undoSupport;
65   private View                      furnitureView;
66   private HomePieceOfFurniture      leadSelectedPieceOfFurniture;
67 
68   /**
69    * Creates the controller of home furniture view.
70    * @param home the home edited by this controller and its view
71    * @param preferences the preferences of the application
72    * @param viewFactory a factory able to create the furniture view managed by this controller
73    */
FurnitureController(Home home, UserPreferences preferences, ViewFactory viewFactory)74   public FurnitureController(Home home,
75                              UserPreferences preferences,
76                              ViewFactory viewFactory) {
77     this(home, preferences, viewFactory, null, null);
78   }
79 
80   /**
81    * Creates the controller of home furniture view with undo support.
82    */
FurnitureController(final Home home, UserPreferences preferences, ViewFactory viewFactory, ContentManager contentManager, UndoableEditSupport undoSupport)83   public FurnitureController(final Home home,
84                              UserPreferences preferences,
85                              ViewFactory viewFactory,
86                              ContentManager contentManager,
87                              UndoableEditSupport undoSupport) {
88     this.home = home;
89     this.preferences = preferences;
90     this.viewFactory = viewFactory;
91     this.undoSupport = undoSupport;
92     this.contentManager = contentManager;
93 
94     addModelListeners();
95   }
96 
97   /**
98    * Returns the view associated with this controller.
99    */
getView()100   public View getView() {
101     // Create view lazily only once it's needed
102     if (this.furnitureView == null) {
103       this.furnitureView = this.viewFactory.createFurnitureView(this.home, this.preferences, this);
104     }
105     return this.furnitureView;
106   }
107 
addModelListeners()108   private void addModelListeners() {
109     // Add a selection listener that gets the lead selected piece in home
110     this.home.addSelectionListener(new SelectionListener() {
111         public void selectionChanged(SelectionEvent ev) {
112           List<HomePieceOfFurniture> selectedFurniture =
113               Home.getFurnitureSubList(home.getSelectedItems());
114           if (selectedFurniture.isEmpty()) {
115             leadSelectedPieceOfFurniture = null;
116           } else if (leadSelectedPieceOfFurniture == null
117                      || selectedFurniture.size() == 1
118                      || selectedFurniture.indexOf(leadSelectedPieceOfFurniture) == -1) {
119             leadSelectedPieceOfFurniture = selectedFurniture.get(0);
120           }
121         }
122       });
123 
124     // Add listener to update base plan lock when furniture movability changes
125     final PropertyChangeListener furnitureChangeListener = new PropertyChangeListener() {
126         public void propertyChange(PropertyChangeEvent ev) {
127           if (HomePieceOfFurniture.Property.MOVABLE.name().equals(ev.getPropertyName())) {
128             // Remove non movable pieces from selection when base plan is locked
129             HomePieceOfFurniture piece = (HomePieceOfFurniture)ev.getSource();
130             if (home.isBasePlanLocked()
131                 && isPieceOfFurniturePartOfBasePlan(piece)) {
132               List<Selectable> selectedItems = home.getSelectedItems();
133               if (selectedItems.contains(piece)) {
134                 selectedItems = new ArrayList<Selectable>(selectedItems);
135                 selectedItems.remove(piece);
136                 home.setSelectedItems(selectedItems);
137               }
138             }
139           }
140         }
141       };
142     for (HomePieceOfFurniture piece : home.getFurniture()) {
143       piece.addPropertyChangeListener(furnitureChangeListener);
144       if (piece instanceof HomeFurnitureGroup) {
145         for (HomePieceOfFurniture childPiece : ((HomeFurnitureGroup)piece).getAllFurniture()) {
146           childPiece.addPropertyChangeListener(furnitureChangeListener);
147         }
148       }
149     }
150     this.home.addFurnitureListener(new CollectionListener<HomePieceOfFurniture> () {
151         public void collectionChanged(CollectionEvent<HomePieceOfFurniture> ev) {
152           HomePieceOfFurniture piece = ev.getItem();
153           if (ev.getType() == CollectionEvent.Type.ADD) {
154             piece.addPropertyChangeListener(furnitureChangeListener);
155             if (piece instanceof HomeFurnitureGroup) {
156               for (HomePieceOfFurniture childPiece : ((HomeFurnitureGroup)piece).getAllFurniture()) {
157                 childPiece.addPropertyChangeListener(furnitureChangeListener);
158               }
159             }
160           } else if (ev.getType() == CollectionEvent.Type.DELETE) {
161             piece.removePropertyChangeListener(furnitureChangeListener);
162             if (piece instanceof HomeFurnitureGroup) {
163               for (HomePieceOfFurniture childPiece : ((HomeFurnitureGroup)piece).getAllFurniture()) {
164                 childPiece.removePropertyChangeListener(furnitureChangeListener);
165               }
166             }
167           }
168         }
169       });
170   }
171 
172   /**
173    * Controls new furniture added to home.
174    * Once added the furniture will be selected in view
175    * and undo support will receive a new undoable edit.
176    * @param furniture the furniture to add.
177    */
addFurniture(List<HomePieceOfFurniture> furniture)178   public void addFurniture(List<HomePieceOfFurniture> furniture) {
179     addFurniture(furniture, null, null, null);
180   }
181 
182   /**
183    * Controls new furniture added to home.
184    * Once added the furniture will be selected in view
185    * and undo support will receive a new undoable edit.
186    * @param furniture the furniture to add.
187    * @param beforePiece the piece before which the furniture will be added
188    * @since 6.3
189    */
addFurniture(List<HomePieceOfFurniture> furniture, HomePieceOfFurniture beforePiece)190   public void addFurniture(List<HomePieceOfFurniture> furniture, HomePieceOfFurniture beforePiece) {
191     addFurniture(furniture, null, null, beforePiece);
192   }
193 
194   /**
195    * Controls new furniture added to the given group.
196    * Once added the furniture will be selected in view
197    * and undo support will receive a new undoable edit.
198    * @param furniture the furniture to add.
199    * @param group     the group to which furniture will be added.
200    */
addFurnitureToGroup(List<HomePieceOfFurniture> furniture, HomeFurnitureGroup group)201   public void addFurnitureToGroup(List<HomePieceOfFurniture> furniture, HomeFurnitureGroup group) {
202     if (group == null) {
203       throw new IllegalArgumentException("Group shouldn't be null");
204     }
205     addFurniture(furniture, null, group, null);
206   }
207 
addFurniture(List<HomePieceOfFurniture> furniture, Level [] furnitureLevels, HomeFurnitureGroup group, HomePieceOfFurniture beforePiece)208   private void addFurniture(List<HomePieceOfFurniture> furniture, Level [] furnitureLevels,
209                             HomeFurnitureGroup group, HomePieceOfFurniture beforePiece) {
210     final boolean oldBasePlanLocked = this.home.isBasePlanLocked();
211     final boolean allLevelsSelection = this.home.isAllLevelsSelection();
212     final List<Selectable> oldSelection = this.home.getSelectedItems();
213     final HomePieceOfFurniture [] newFurniture =
214         furniture.toArray(new HomePieceOfFurniture [furniture.size()]);
215     // Get indices of added furniture
216     final int [] newFurnitureIndex = new int [furniture.size()];
217     int insertIndex = group == null
218         ? this.home.getFurniture().size()
219         : group.getFurniture().size();
220     if (beforePiece != null) {
221       List<HomePieceOfFurniture> parentFurniture = this.home.getFurniture();
222       group = getPieceOfFurnitureGroup(beforePiece, null, parentFurniture);
223       if (group != null) {
224         parentFurniture = group.getFurniture();
225       }
226       insertIndex = parentFurniture.indexOf(beforePiece);
227     }
228     final HomeFurnitureGroup [] newFurnitureGroups = group != null
229         ? new HomeFurnitureGroup [furniture.size()]
230         : null;
231     boolean basePlanLocked = oldBasePlanLocked;
232     boolean levelUpdated = group != null || furnitureLevels == null;
233     for (int i = 0; i < newFurnitureIndex.length; i++) {
234       newFurnitureIndex [i] = insertIndex++;
235       // Unlock base plan if the piece is a part of it
236       basePlanLocked &= !isPieceOfFurniturePartOfBasePlan(newFurniture [i]);
237       if (furnitureLevels != null) {
238         levelUpdated |= furnitureLevels [i] == null;
239       }
240       if (newFurnitureGroups != null) {
241         newFurnitureGroups [i] = group;
242       }
243     }
244     final Level [] newFurnitureLevels = levelUpdated ? null : furnitureLevels;
245     final boolean newBasePlanLocked = basePlanLocked;
246     final Level furnitureLevel = group != null
247         ? group.getLevel()
248         : this.home.getSelectedLevel();
249 
250     doAddFurniture(this.home, newFurniture, newFurnitureGroups, newFurnitureIndex, furnitureLevel, newFurnitureLevels, newBasePlanLocked, false);
251     if (this.undoSupport != null) {
252       this.undoSupport.postEdit(new FurnitureAdditionUndoableEdit(this.home, this.preferences,
253           oldSelection.toArray(new Selectable [oldSelection.size()]), oldBasePlanLocked, allLevelsSelection, newFurniture,
254           newFurnitureIndex, newFurnitureGroups, newFurnitureLevels, furnitureLevel, newBasePlanLocked));
255     }
256   }
257 
258   /**
259    * Undoable edit for furniture added to home.
260    */
261   private static class FurnitureAdditionUndoableEdit extends LocalizedUndoableEdit {
262     private final Home                    home;
263     private final boolean                 allLevelsSelection;
264     private final Selectable []           oldSelection;
265     private final boolean                 oldBasePlanLocked;
266     private final HomePieceOfFurniture [] newFurniture;
267     private final int []                  newFurnitureIndex;
268     private final HomeFurnitureGroup []   newFurnitureGroups;
269     private final Level []                newFurnitureLevels;
270     private final Level                   furnitureLevel;
271     private final boolean                 newBasePlanLocked;
272 
FurnitureAdditionUndoableEdit(Home home, UserPreferences preferences, Selectable[] oldSelection, boolean oldBasePlanLocked, boolean allLevelsSelection, HomePieceOfFurniture [] newFurniture, int [] newFurnitureIndex, HomeFurnitureGroup [] newFurnitureGroups, Level [] newFurnitureLevels, Level furnitureLevel, boolean newBasePlanLocked)273     public FurnitureAdditionUndoableEdit(Home home, UserPreferences preferences, Selectable[] oldSelection,
274                                          boolean oldBasePlanLocked, boolean allLevelsSelection,
275                                          HomePieceOfFurniture [] newFurniture, int [] newFurnitureIndex,
276                                          HomeFurnitureGroup [] newFurnitureGroups, Level [] newFurnitureLevels,
277                                          Level furnitureLevel, boolean newBasePlanLocked) {
278       super(preferences, FurnitureController.class, "undoAddFurnitureName");
279       this.home = home;
280       this.oldSelection = oldSelection;
281       this.oldBasePlanLocked = oldBasePlanLocked;
282       this.allLevelsSelection = allLevelsSelection;
283       this.newFurniture = newFurniture;
284       this.newFurnitureIndex = newFurnitureIndex;
285       this.newFurnitureGroups = newFurnitureGroups;
286       this.newFurnitureLevels = newFurnitureLevels;
287       this.furnitureLevel = furnitureLevel;
288       this.newBasePlanLocked = newBasePlanLocked;
289     }
290 
291     @Override
undo()292     public void undo() throws CannotUndoException {
293       super.undo();
294       doDeleteFurniture(this.home, this.newFurniture, this.oldBasePlanLocked, this.allLevelsSelection);
295       this.home.setSelectedItems(Arrays.asList(this.oldSelection));
296     }
297 
298     @Override
redo()299     public void redo() throws CannotRedoException {
300       super.redo();
301       doAddFurniture(this.home, this.newFurniture, this.newFurnitureGroups, this.newFurnitureIndex, this.furnitureLevel,
302           this.newFurnitureLevels, this.newBasePlanLocked, false);
303     }
304   }
305 
doAddFurniture(Home home, HomePieceOfFurniture [] furniture, HomeFurnitureGroup [] furnitureGroups, int [] furnitureIndex, Level furnitureLevel, Level [] furnitureLevels, boolean basePlanLocked, boolean allLevelsSelection)306   private static void doAddFurniture(Home home,
307                                      HomePieceOfFurniture [] furniture,
308                                      HomeFurnitureGroup [] furnitureGroups,
309                                      int [] furnitureIndex,
310                                      Level furnitureLevel,
311                                      Level [] furnitureLevels,
312                                      boolean basePlanLocked,
313                                      boolean allLevelsSelection) {
314     for (int i = 0; i < furnitureIndex.length; i++) {
315       if (furnitureGroups != null && furnitureGroups [i] != null) {
316         home.addPieceOfFurnitureToGroup(furniture [i], furnitureGroups [i], furnitureIndex [i]);
317         furniture [i].setVisible(furnitureGroups [i].isVisible());
318       } else {
319         home.addPieceOfFurniture(furniture [i], furnitureIndex [i]);
320       }
321       furniture [i].setLevel(furnitureLevels != null ? furnitureLevels [i] : furnitureLevel);
322     }
323     home.setBasePlanLocked(basePlanLocked);
324     home.setSelectedItems(Arrays.asList(furniture));
325     home.setAllLevelsSelection(allLevelsSelection);
326   }
327 
328   /**
329    * Controls the deletion of the current selected furniture in home.
330    * Once the selected furniture is deleted, undo support will receive a new undoable edit.
331    */
deleteSelection()332   public void deleteSelection() {
333     deleteFurniture(Home.getFurnitureSubList(this.home.getSelectedItems()));
334   }
335 
336   /**
337    * Deletes the furniture of <code>deletedFurniture</code> from home.
338    * Once the selected furniture is deleted, undo support will receive a new undoable edit.
339    */
deleteFurniture(List<HomePieceOfFurniture> deletedFurniture)340   public void deleteFurniture(List<HomePieceOfFurniture> deletedFurniture) {
341     final boolean basePlanLocked = this.home.isBasePlanLocked();
342     final boolean allLevelsSelection = this.home.isAllLevelsSelection();
343     final List<Selectable> oldSelection = this.home.getSelectedItems();
344     List<HomePieceOfFurniture> homeFurniture = this.home.getFurniture();
345 
346     // Replace pieces by their group when they have to be all deleted
347     deletedFurniture = new ArrayList<HomePieceOfFurniture>(deletedFurniture);
348     List<HomeFurnitureGroup> homeGroups = new ArrayList<HomeFurnitureGroup>();
349     searchGroups(homeFurniture, homeGroups);
350     boolean updated;
351     do {
352       updated = false;
353       for (HomeFurnitureGroup group : homeGroups) {
354         List<HomePieceOfFurniture> groupFurniture = group.getFurniture();
355         if (deletedFurniture.containsAll(groupFurniture)) {
356           deletedFurniture.removeAll(groupFurniture);
357           deletedFurniture.add(group);
358           updated = true;
359         }
360       }
361     } while (updated);
362 
363     // Sort the deletable furniture in the ascending order of their index in home or their group
364     Map<HomeFurnitureGroup, TreeMap<Integer, HomePieceOfFurniture>> deletedFurnitureMap =
365         new HashMap<HomeFurnitureGroup, TreeMap<Integer, HomePieceOfFurniture>>();
366     int deletedFurnitureCount = 0;
367     for (HomePieceOfFurniture piece : deletedFurniture) {
368       // Check piece is deletable and doesn't belong to a group
369       if (isPieceOfFurnitureDeletable(piece)) {
370         HomeFurnitureGroup group = getPieceOfFurnitureGroup(piece, null, homeFurniture);
371         TreeMap<Integer, HomePieceOfFurniture> sortedMap = deletedFurnitureMap.get(group);
372         if (sortedMap == null) {
373           sortedMap = new TreeMap<Integer, HomePieceOfFurniture>();
374           deletedFurnitureMap.put(group, sortedMap);
375         }
376         if (group == null) {
377           sortedMap.put(homeFurniture.indexOf(piece), piece);
378         } else {
379           sortedMap.put(group.getFurniture().indexOf(piece), piece);
380         }
381         deletedFurnitureCount++;
382       }
383     }
384     final HomePieceOfFurniture [] furniture = new HomePieceOfFurniture [deletedFurnitureCount];
385     final int [] furnitureIndex = new int [furniture.length];
386     final Level [] furnitureLevels = new Level [furniture.length];
387     final HomeFurnitureGroup [] furnitureGroups = new HomeFurnitureGroup [furniture.length];
388     int i = 0;
389     for (Map.Entry<HomeFurnitureGroup, TreeMap<Integer, HomePieceOfFurniture>> sortedMapEntry : deletedFurnitureMap.entrySet()) {
390       for (Map.Entry<Integer, HomePieceOfFurniture> pieceEntry : sortedMapEntry.getValue().entrySet()) {
391         furniture [i] = pieceEntry.getValue();
392         furnitureIndex [i] = pieceEntry.getKey();
393         furnitureLevels [i] = furniture [i].getLevel();
394         furnitureGroups [i++] = sortedMapEntry.getKey();
395       }
396     }
397     doDeleteFurniture(this.home, furniture, basePlanLocked, false);
398     if (this.undoSupport != null) {
399       this.undoSupport.postEdit(new FurnitureDeletionUndoableEdit(this.home, this.preferences,
400           oldSelection.toArray(new Selectable [oldSelection.size()]), basePlanLocked, allLevelsSelection,
401           furniture, furnitureIndex, furnitureGroups, furnitureLevels));
402     }
403   }
404 
405   /**
406    * Undoable edit for furniture deleted from home.
407    */
408   private static class FurnitureDeletionUndoableEdit extends LocalizedUndoableEdit {
409     private final Home                    home;
410     private final Selectable []           oldSelection;
411     private final boolean                 basePlanLocked;
412     private final boolean                 allLevelsSelection;
413     private final HomePieceOfFurniture [] furniture;
414     private final int []                  furnitureIndex;
415     private final HomeFurnitureGroup []   furnitureGroups;
416     private final Level []                furnitureLevels;
417 
FurnitureDeletionUndoableEdit(Home home, UserPreferences preferences, Selectable [] oldSelection, boolean basePlanLocked, boolean allLevelsSelection, HomePieceOfFurniture [] furniture, int [] furnitureIndex, HomeFurnitureGroup [] furnitureGroups, Level [] furnitureLevels)418     public FurnitureDeletionUndoableEdit(Home home, UserPreferences preferences,
419                                          Selectable [] oldSelection, boolean basePlanLocked,
420                                          boolean allLevelsSelection, HomePieceOfFurniture [] furniture,
421                                          int [] furnitureIndex, HomeFurnitureGroup [] furnitureGroups,
422                                          Level [] furnitureLevels) {
423       super(preferences, FurnitureController.class, "undoDeleteSelectionName");
424       this.home = home;
425       this.oldSelection = oldSelection;
426       this.basePlanLocked = basePlanLocked;
427       this.allLevelsSelection = allLevelsSelection;
428       this.furniture = furniture;
429       this.furnitureIndex = furnitureIndex;
430       this.furnitureGroups = furnitureGroups;
431       this.furnitureLevels = furnitureLevels;
432     }
433 
434     @Override
undo()435     public void undo() throws CannotUndoException {
436       super.undo();
437       doAddFurniture(this.home, this.furniture, this.furnitureGroups, this.furnitureIndex, null,
438           this.furnitureLevels, this.basePlanLocked, this.allLevelsSelection);
439       this.home.setSelectedItems(Arrays.asList(this.oldSelection));
440     }
441 
442     @Override
redo()443     public void redo() throws CannotRedoException {
444       super.redo();
445       this.home.setSelectedItems(Arrays.asList(this.furniture));
446       doDeleteFurniture(this.home, this.furniture, this.basePlanLocked, false);
447     }
448   }
449 
doDeleteFurniture(Home home, HomePieceOfFurniture [] furniture, boolean basePlanLocked, boolean allLevelsSelection)450   private static void doDeleteFurniture(Home home,
451                                         HomePieceOfFurniture [] furniture,
452                                         boolean basePlanLocked,
453                                         boolean allLevelsSelection) {
454     for (HomePieceOfFurniture piece : furniture) {
455       home.deletePieceOfFurniture(piece);
456     }
457     home.setBasePlanLocked(basePlanLocked);
458     home.setAllLevelsSelection(allLevelsSelection);
459   }
460 
461   /**
462    * Searches all the groups among furniture and its children.
463    */
searchGroups(List<HomePieceOfFurniture> furniture, List<HomeFurnitureGroup> groups)464   private static void searchGroups(List<HomePieceOfFurniture> furniture,
465                                    List<HomeFurnitureGroup> groups) {
466     for (HomePieceOfFurniture piece : furniture) {
467       if (piece instanceof HomeFurnitureGroup) {
468         groups.add((HomeFurnitureGroup)piece);
469         searchGroups(((HomeFurnitureGroup)piece).getFurniture(), groups);
470       }
471     }
472   }
473 
474   /**
475    * Returns the furniture group that contains the given <code>piece</code> or <code>null</code> if it can't be found.
476    */
getPieceOfFurnitureGroup(HomePieceOfFurniture piece, HomeFurnitureGroup furnitureGroup, List<HomePieceOfFurniture> furniture)477   private static HomeFurnitureGroup getPieceOfFurnitureGroup(HomePieceOfFurniture piece,
478                                                              HomeFurnitureGroup furnitureGroup,
479                                                              List<HomePieceOfFurniture> furniture) {
480     for (HomePieceOfFurniture homePiece : furniture) {
481       if (homePiece.equals(piece)) {
482         return furnitureGroup;
483       } else if (homePiece instanceof HomeFurnitureGroup) {
484         HomeFurnitureGroup group = getPieceOfFurnitureGroup(piece,
485             (HomeFurnitureGroup)homePiece, ((HomeFurnitureGroup)homePiece).getFurniture());
486         if (group != null) {
487           return group;
488         }
489       }
490     }
491     return null;
492   }
493 
494   /**
495    * Reorders the selected furniture in home to place it before the given piece.
496    * @since 6.3
497    */
moveSelectedFurnitureBefore(HomePieceOfFurniture beforePiece)498   public void moveSelectedFurnitureBefore(HomePieceOfFurniture beforePiece) {
499     List<HomePieceOfFurniture> movedFurniture = Home.getFurnitureSubList(this.home.getSelectedItems());
500     if (!movedFurniture.isEmpty()) {
501       // Store current level of the furniture
502       final Level [] furnitureLevels = new Level [movedFurniture.size()];
503       for (int i = 0; i < furnitureLevels.length; i++) {
504         furnitureLevels [i] = movedFurniture.get(i).getLevel();
505       }
506       this.undoSupport.beginUpdate();
507       deleteFurniture(movedFurniture);
508       addFurniture(movedFurniture, furnitureLevels, null, beforePiece);
509       undoSupport.postEdit(new LocalizedUndoableEdit(this.preferences, FurnitureController.class, "undoReorderName"));
510       // End compound edit
511       undoSupport.endUpdate();
512     }
513   }
514 
515   /**
516    * Updates the selected furniture in home.
517    */
setSelectedFurniture(List<HomePieceOfFurniture> selectedFurniture)518   public void setSelectedFurniture(List<HomePieceOfFurniture> selectedFurniture) {
519     setSelectedFurniture(selectedFurniture, true);
520   }
521 
522   /**
523    * Updates the selected furniture in home, unselecting all other kinds of selected objects
524    * when <code>resetSelection</code> is <code>true</code>.
525    * @since 6.1
526    */
setSelectedFurniture(List<HomePieceOfFurniture> selectedFurniture, boolean resetSelection)527   public void setSelectedFurniture(List<HomePieceOfFurniture> selectedFurniture, boolean resetSelection) {
528     if (this.home.isBasePlanLocked()) {
529       selectedFurniture = getFurnitureNotPartOfBasePlan(selectedFurniture);
530     }
531     if (resetSelection) {
532       this.home.setSelectedItems(selectedFurniture);
533       this.home.setAllLevelsSelection(false);
534     } else {
535       List<Selectable> selectedItems = new ArrayList<Selectable>(this.home.getSelectedItems());
536       selectedFurniture = new ArrayList<HomePieceOfFurniture>(selectedFurniture);
537       for (int i = selectedItems.size() - 1; i >= 0; i--) {
538         Selectable item = selectedItems.get(i);
539         if (item instanceof HomePieceOfFurniture) {
540           int index = selectedFurniture.indexOf((HomePieceOfFurniture)item);
541           if (index >= 0) {
542             selectedFurniture.remove(index);
543           } else {
544             selectedItems.remove(i);
545           }
546         }
547       }
548       selectedItems.addAll(selectedFurniture);
549       this.home.setSelectedItems(selectedItems);
550     }
551   }
552 
553   /**
554    * Selects all furniture in home.
555    */
selectAll()556   public void selectAll() {
557     setSelectedFurniture(this.home.getFurniture());
558   }
559 
560   /**
561    * Returns <code>true</code> if the given <code>piece</code> isn't movable.
562    */
isPieceOfFurniturePartOfBasePlan(HomePieceOfFurniture piece)563   protected boolean isPieceOfFurniturePartOfBasePlan(HomePieceOfFurniture piece) {
564     return !piece.isMovable() || piece.isDoorOrWindow();
565   }
566 
567   /**
568    * Returns <code>true</code> if the given <code>piece</code> may be moved.
569    * Default implementation always returns <code>true</code>.
570    */
isPieceOfFurnitureMovable(HomePieceOfFurniture piece)571   protected boolean isPieceOfFurnitureMovable(HomePieceOfFurniture piece) {
572     return true;
573   }
574 
575   /**
576    * Returns <code>true</code> if the given <code>piece</code> may be deleted.
577    * Default implementation always returns <code>true</code>.
578    */
isPieceOfFurnitureDeletable(HomePieceOfFurniture piece)579   protected boolean isPieceOfFurnitureDeletable(HomePieceOfFurniture piece) {
580     return true;
581   }
582 
583   /**
584    * Returns a new home piece of furniture created from an other given <code>piece</code> of furniture.
585    */
createHomePieceOfFurniture(PieceOfFurniture piece)586   public HomePieceOfFurniture createHomePieceOfFurniture(PieceOfFurniture piece) {
587     if (piece instanceof DoorOrWindow) {
588       return new HomeDoorOrWindow((DoorOrWindow)piece);
589     } else if (piece instanceof Light) {
590       return new HomeLight((Light)piece);
591     } else {
592       return new HomePieceOfFurniture(piece);
593     }
594   }
595 
596   /**
597    * Returns the furniture among the given list that are not part of the base plan.
598    */
getFurnitureNotPartOfBasePlan(List<HomePieceOfFurniture> furniture)599   private List<HomePieceOfFurniture> getFurnitureNotPartOfBasePlan(List<HomePieceOfFurniture> furniture) {
600     List<HomePieceOfFurniture> furnitureNotPartOfBasePlan = new ArrayList<HomePieceOfFurniture>();
601     for (HomePieceOfFurniture piece : furniture) {
602       if (!isPieceOfFurniturePartOfBasePlan(piece)) {
603         furnitureNotPartOfBasePlan.add(piece);
604       }
605     }
606     return furnitureNotPartOfBasePlan;
607   }
608 
609   /**
610    * Uses <code>furnitureProperty</code> to sort home furniture
611    * or cancels home furniture sort if home is already sorted on <code>furnitureProperty</code>
612    * @param furnitureProperty a property of {@link HomePieceOfFurniture HomePieceOfFurniture} class.
613    */
toggleFurnitureSort(HomePieceOfFurniture.SortableProperty furnitureProperty)614   public void toggleFurnitureSort(HomePieceOfFurniture.SortableProperty furnitureProperty) {
615     if (furnitureProperty.equals(this.home.getFurnitureSortedProperty())) {
616       this.home.setFurnitureSortedProperty(null);
617     } else {
618       this.home.setFurnitureSortedProperty(furnitureProperty);
619     }
620   }
621 
622   /**
623    * Toggles home furniture sort order.
624    */
toggleFurnitureSortOrder()625   public void toggleFurnitureSortOrder() {
626     this.home.setFurnitureDescendingSorted(!this.home.isFurnitureDescendingSorted());
627   }
628 
629   /**
630    * Controls the sort of the furniture in home. If home furniture isn't sorted
631    * or is sorted on an other property, it will be sorted on the given
632    * <code>furnitureProperty</code> in ascending order. If home furniture is already
633    * sorted on the given <code>furnitureProperty</code>, it will be sorted in descending
634    * order, if the sort is in ascending order, otherwise it won't be sorted at all
635    * and home furniture will be listed in insertion order.
636     * @param furnitureProperty  the furniture property on which the view wants
637    *          to sort the furniture it displays.
638    */
sortFurniture(HomePieceOfFurniture.SortableProperty furnitureProperty)639   public void sortFurniture(HomePieceOfFurniture.SortableProperty furnitureProperty) {
640     // Compute sort algorithm described in javadoc
641     final HomePieceOfFurniture.SortableProperty  oldProperty =
642         this.home.getFurnitureSortedProperty();
643     final boolean oldDescending = this.home.isFurnitureDescendingSorted();
644     boolean descending = false;
645     if (furnitureProperty.equals(oldProperty)) {
646       if (oldDescending) {
647         furnitureProperty = null;
648       } else {
649         descending = true;
650       }
651     }
652     this.home.setFurnitureSortedProperty(furnitureProperty);
653     this.home.setFurnitureDescendingSorted(descending);
654   }
655 
656   /**
657    * Updates the furniture visible properties in home.
658    */
setFurnitureVisibleProperties(List<HomePieceOfFurniture.SortableProperty> furnitureVisibleProperties)659   public void setFurnitureVisibleProperties(List<HomePieceOfFurniture.SortableProperty> furnitureVisibleProperties) {
660     this.home.setFurnitureVisibleProperties(furnitureVisibleProperties);
661   }
662 
663   /**
664    * Toggles furniture property visibility in home.
665    */
toggleFurnitureVisibleProperty(HomePieceOfFurniture.SortableProperty furnitureProperty)666   public void toggleFurnitureVisibleProperty(HomePieceOfFurniture.SortableProperty furnitureProperty) {
667     List<SortableProperty> furnitureVisibleProperties =
668         new ArrayList<SortableProperty>(this.home.getFurnitureVisibleProperties());
669     if (furnitureVisibleProperties.contains(furnitureProperty)) {
670       furnitureVisibleProperties.remove(furnitureProperty);
671       // Ensure at least one column is visible
672       if (furnitureVisibleProperties.isEmpty()) {
673         furnitureVisibleProperties.add(HomePieceOfFurniture.SortableProperty.NAME);
674       }
675     } else {
676       // Add furniture property after the visible property that has the previous index in
677       // the following list
678       List<HomePieceOfFurniture.SortableProperty> propertiesOrder =
679           Arrays.asList(new HomePieceOfFurniture.SortableProperty [] {
680               HomePieceOfFurniture.SortableProperty.CATALOG_ID,
681               HomePieceOfFurniture.SortableProperty.NAME,
682               HomePieceOfFurniture.SortableProperty.CREATOR,
683               HomePieceOfFurniture.SortableProperty.WIDTH,
684               HomePieceOfFurniture.SortableProperty.DEPTH,
685               HomePieceOfFurniture.SortableProperty.HEIGHT,
686               HomePieceOfFurniture.SortableProperty.X,
687               HomePieceOfFurniture.SortableProperty.Y,
688               HomePieceOfFurniture.SortableProperty.ELEVATION,
689               HomePieceOfFurniture.SortableProperty.ANGLE,
690               HomePieceOfFurniture.SortableProperty.LEVEL,
691               HomePieceOfFurniture.SortableProperty.MODEL_SIZE,
692               HomePieceOfFurniture.SortableProperty.COLOR,
693               HomePieceOfFurniture.SortableProperty.TEXTURE,
694               HomePieceOfFurniture.SortableProperty.MOVABLE,
695               HomePieceOfFurniture.SortableProperty.DOOR_OR_WINDOW,
696               HomePieceOfFurniture.SortableProperty.VISIBLE,
697               HomePieceOfFurniture.SortableProperty.PRICE,
698               HomePieceOfFurniture.SortableProperty.VALUE_ADDED_TAX_PERCENTAGE,
699               HomePieceOfFurniture.SortableProperty.VALUE_ADDED_TAX,
700               HomePieceOfFurniture.SortableProperty.PRICE_VALUE_ADDED_TAX_INCLUDED});
701       int propertyIndex = propertiesOrder.indexOf(furnitureProperty) - 1;
702       if (propertyIndex > 0) {
703         while (propertyIndex > 0) {
704           int visiblePropertyIndex = furnitureVisibleProperties.indexOf(propertiesOrder.get(propertyIndex));
705           if (visiblePropertyIndex >= 0) {
706             propertyIndex = visiblePropertyIndex + 1;
707             break;
708           } else {
709             propertyIndex--;
710           }
711         }
712       }
713       if (propertyIndex < 0) {
714         propertyIndex = 0;
715       }
716       furnitureVisibleProperties.add(propertyIndex, furnitureProperty);
717     }
718     this.home.setFurnitureVisibleProperties(furnitureVisibleProperties);
719   }
720 
721   /**
722    * Controls the modification of selected furniture.
723    */
modifySelectedFurniture()724   public void modifySelectedFurniture() {
725     if (!Home.getFurnitureSubList(this.home.getSelectedItems()).isEmpty()) {
726       new HomeFurnitureController(this.home, this.preferences,
727           this.viewFactory, this.contentManager, this.undoSupport).displayView(getView());
728     }
729   }
730 
731   /**
732    * Controls the modification of the visibility of the selected piece of furniture.
733    */
toggleSelectedFurnitureVisibility()734   public void toggleSelectedFurnitureVisibility() {
735     if (Home.getFurnitureSubList(this.home.getSelectedItems()).size() == 1) {
736       HomeFurnitureController controller = new HomeFurnitureController(this.home, this.preferences,
737           this.viewFactory, this.contentManager, this.undoSupport);
738       controller.setVisible(!controller.getVisible());
739       controller.modifyFurniture();
740     }
741   }
742 
743   /**
744    * Groups the selected furniture as one piece of furniture.
745    */
groupSelectedFurniture()746   public void groupSelectedFurniture() {
747     HomePieceOfFurniture [] selectedFurniture = getMovableSelectedFurniture();
748     if (selectedFurniture.length > 0) {
749       final boolean basePlanLocked = this.home.isBasePlanLocked();
750       final boolean allLevelsSelection = this.home.isAllLevelsSelection();
751       final List<Selectable> oldSelection = this.home.getSelectedItems();
752       List<HomePieceOfFurniture> homeFurniture = this.home.getFurniture();
753       // Sort the grouped furniture in the ascending order of their index in home or their group
754       Map<HomeFurnitureGroup, TreeMap<Integer, HomePieceOfFurniture>> groupedFurnitureMap =
755           new HashMap<HomeFurnitureGroup, TreeMap<Integer, HomePieceOfFurniture>>();
756       int groupedFurnitureCount = 0;
757       for (HomePieceOfFurniture piece : selectedFurniture) {
758         HomeFurnitureGroup group = getPieceOfFurnitureGroup(piece, null, homeFurniture);
759         TreeMap<Integer, HomePieceOfFurniture> sortedMap = groupedFurnitureMap.get(group);
760         if (sortedMap == null) {
761           sortedMap = new TreeMap<Integer, HomePieceOfFurniture>();
762           groupedFurnitureMap.put(group, sortedMap);
763         }
764         if (group == null) {
765           sortedMap.put(homeFurniture.indexOf(piece), piece);
766         } else {
767           sortedMap.put(group.getFurniture().indexOf(piece), piece);
768         }
769         groupedFurnitureCount++;
770       }
771       final HomePieceOfFurniture [] groupedPieces = new HomePieceOfFurniture [groupedFurnitureCount];
772       final int [] groupedPiecesIndex = new int [groupedPieces.length];
773       final Level [] groupedPiecesLevel = new Level [groupedPieces.length];
774       final float [] groupedPiecesElevation = new float [groupedPieces.length];
775       final boolean [] groupedPiecesVisible = new boolean [groupedPieces.length];
776       final HomeFurnitureGroup [] groupedPiecesGroups = new HomeFurnitureGroup [groupedPieces.length];
777       Level minLevel = this.home.getSelectedLevel();
778       int i = 0;
779       for (Map.Entry<HomeFurnitureGroup, TreeMap<Integer, HomePieceOfFurniture>> sortedMapEntry : groupedFurnitureMap.entrySet()) {
780         for (Map.Entry<Integer, HomePieceOfFurniture> pieceEntry : sortedMapEntry.getValue().entrySet()) {
781           HomePieceOfFurniture piece = pieceEntry.getValue();
782           groupedPieces [i] = piece;
783           groupedPiecesIndex [i] = pieceEntry.getKey();
784           groupedPiecesLevel [i] = piece.getLevel();
785           groupedPiecesElevation [i] = piece.getElevation();
786           groupedPiecesVisible [i] = piece.isVisible();
787           groupedPiecesGroups [i] = sortedMapEntry.getKey();
788           if (groupedPiecesLevel [i] != null) {
789             if (minLevel == null
790                 || groupedPiecesLevel [i].getElevation() < minLevel.getElevation()) {
791               minLevel = groupedPiecesLevel [i];
792             }
793           }
794           i++;
795         }
796       }
797       final HomeFurnitureGroup newGroup;
798       List<HomePieceOfFurniture> groupedFurniture = Arrays.asList(groupedPieces);
799       if (groupedFurniture.indexOf(this.leadSelectedPieceOfFurniture) > 0) {
800         newGroup = createHomeFurnitureGroup(groupedFurniture, this.leadSelectedPieceOfFurniture);
801       } else {
802         newGroup = createHomeFurnitureGroup(groupedFurniture);
803       }
804       // Store piece elevation that could have been updated during grouping
805       final float [] groupPiecesNewElevation = new float [groupedPieces.length];
806       i = 0;
807       for (HomePieceOfFurniture piece : groupedPieces) {
808         groupPiecesNewElevation [i++] = piece.getElevation();
809       }
810       TreeMap<Integer, HomePieceOfFurniture> homeSortedMap = groupedFurnitureMap.get(null);
811       final int groupIndex = homeSortedMap != null
812           ? homeSortedMap.lastKey() + 1 - groupedPieces.length
813           : homeFurniture.size();
814       final boolean movable = newGroup.isMovable();
815       final Level groupLevel = minLevel;
816 
817       doGroupFurniture(this.home, groupedPieces, new HomeFurnitureGroup [] {newGroup},
818           null, new int [] {groupIndex}, new Level [] {groupLevel}, basePlanLocked, false);
819       if (this.undoSupport != null) {
820         this.undoSupport.postEdit(new FurnitureGroupingUndoableEdit(this.home, this.preferences,
821             oldSelection.toArray(new Selectable [oldSelection.size()]), basePlanLocked, allLevelsSelection,
822             groupedPieces, groupedPiecesIndex, groupedPiecesGroups, groupedPiecesLevel, groupedPiecesElevation, groupedPiecesVisible,
823             newGroup, groupIndex, groupLevel, groupPiecesNewElevation, movable));
824       }
825     }
826   }
827 
828   /**
829    * Undoable edit for furniture grouping.
830    */
831   private static class FurnitureGroupingUndoableEdit extends LocalizedUndoableEdit {
832     private final Home                    home;
833     private final Selectable []           oldSelection;
834     private final boolean                 basePlanLocked;
835     private final boolean                 allLevelsSelection;
836     private final HomePieceOfFurniture [] groupedPieces;
837     private final int []                  groupedPiecesIndex;
838     private final HomeFurnitureGroup []   groupedPiecesGroups;
839     private final Level []                groupedPiecesLevel;
840     private final float []                groupedPiecesElevation;
841     private final boolean []              groupedPiecesVisible;
842     private final HomeFurnitureGroup      newGroup;
843     private final int                     groupIndex;
844     private final Level                   groupLevel;
845     private final float []                groupPiecesNewElevation;
846     private final boolean                 movable;
847 
FurnitureGroupingUndoableEdit(Home home, UserPreferences preferences, Selectable[] oldSelection, boolean basePlanLocked, boolean allLevelsSelection, HomePieceOfFurniture [] groupedPieces, int [] groupedPiecesIndex, HomeFurnitureGroup [] groupedPiecesGroups, Level [] groupedPiecesLevel, float [] groupedPiecesElevation, boolean [] groupedPiecesVisible, HomeFurnitureGroup newGroup, int groupIndex, Level groupLevel, float [] groupPiecesNewElevation, boolean movable)848     public FurnitureGroupingUndoableEdit(Home home, UserPreferences preferences,
849                                          Selectable[] oldSelection, boolean basePlanLocked, boolean allLevelsSelection,
850                                          HomePieceOfFurniture [] groupedPieces, int [] groupedPiecesIndex,
851                                          HomeFurnitureGroup [] groupedPiecesGroups, Level [] groupedPiecesLevel,
852                                          float [] groupedPiecesElevation, boolean [] groupedPiecesVisible,
853                                          HomeFurnitureGroup newGroup, int groupIndex,
854                                          Level groupLevel, float [] groupPiecesNewElevation, boolean movable) {
855       super(preferences, FurnitureController.class, "undoGroupName");
856       this.home = home;
857       this.basePlanLocked = basePlanLocked;
858       this.oldSelection = oldSelection;
859       this.allLevelsSelection = allLevelsSelection;
860       this.groupedPieces = groupedPieces;
861       this.groupedPiecesIndex = groupedPiecesIndex;
862       this.groupedPiecesGroups = groupedPiecesGroups;
863       this.groupedPiecesLevel = groupedPiecesLevel;
864       this.groupedPiecesElevation = groupedPiecesElevation;
865       this.groupedPiecesVisible = groupedPiecesVisible;
866       this.newGroup = newGroup;
867       this.groupIndex = groupIndex;
868       this.groupLevel = groupLevel;
869       this.groupPiecesNewElevation = groupPiecesNewElevation;
870       this.movable = movable;
871     }
872 
873     @Override
undo()874     public void undo() throws CannotUndoException {
875       super.undo();
876       doUngroupFurniture(this.home, new HomeFurnitureGroup [] {this.newGroup}, this.groupedPieces,
877           this.groupedPiecesGroups, this.groupedPiecesIndex, this.groupedPiecesLevel, this.basePlanLocked, this.allLevelsSelection);
878       for (int i = 0; i < this.groupedPieces.length; i++) {
879         this.groupedPieces [i].setElevation(this.groupedPiecesElevation [i]);
880         this.groupedPieces [i].setVisible(this.groupedPiecesVisible [i]);
881       }
882       this.home.setSelectedItems(Arrays.asList(this.oldSelection));
883     }
884 
885     @Override
redo()886     public void redo() throws CannotRedoException {
887       super.redo();
888       for (int i = 0; i < this.groupedPieces.length; i++) {
889         this.groupedPieces [i].setElevation(this.groupPiecesNewElevation [i]);
890         this.groupedPieces [i].setLevel(null);
891       }
892       this.newGroup.setMovable(this.movable);
893       this.newGroup.setVisible(true);
894       doGroupFurniture(this.home, this.groupedPieces, new HomeFurnitureGroup [] {this.newGroup},
895           null, new int [] {this.groupIndex}, new Level [] {this.groupLevel}, this.basePlanLocked, false);
896     }
897   }
898 
899   /**
900    * Returns a new furniture group for the given furniture list.
901    */
createHomeFurnitureGroup(List<HomePieceOfFurniture> furniture)902   protected HomeFurnitureGroup createHomeFurnitureGroup(List<HomePieceOfFurniture> furniture) {
903     return createHomeFurnitureGroup(furniture, furniture.get(0));
904   }
905 
906   /**
907    * Returns a new furniture group for the given furniture list.
908    * @since 4.5
909    */
createHomeFurnitureGroup(List<HomePieceOfFurniture> furniture, HomePieceOfFurniture leadingPiece)910   protected HomeFurnitureGroup createHomeFurnitureGroup(List<HomePieceOfFurniture> furniture, HomePieceOfFurniture leadingPiece) {
911     String furnitureGroupName = this.preferences.getLocalizedString(
912         FurnitureController.class, "groupName", getFurnitureGroupCount(this.home.getFurniture()) + 1);
913     final HomeFurnitureGroup furnitureGroup = new HomeFurnitureGroup(furniture, leadingPiece, furnitureGroupName);
914     return furnitureGroup;
915   }
916 
917   /**
918    * Returns the count of furniture groups among the given list.
919    */
getFurnitureGroupCount(List<HomePieceOfFurniture> furniture)920   private static int getFurnitureGroupCount(List<HomePieceOfFurniture> furniture) {
921     int i = 0;
922     for (HomePieceOfFurniture piece : furniture) {
923       if (piece instanceof HomeFurnitureGroup) {
924         i += 1 + getFurnitureGroupCount(((HomeFurnitureGroup)piece).getFurniture());
925       }
926     }
927     return i;
928   }
929 
doGroupFurniture(Home home, HomePieceOfFurniture [] groupedPieces, HomeFurnitureGroup [] groups, HomeFurnitureGroup [] groupsGroups, int [] groupsIndex, Level [] groupsLevels, boolean basePlanLocked, boolean allLevelsSelection)930   private static void doGroupFurniture(Home home,
931                                        HomePieceOfFurniture [] groupedPieces,
932                                        HomeFurnitureGroup [] groups,
933                                        HomeFurnitureGroup [] groupsGroups,
934                                        int [] groupsIndex,
935                                        Level [] groupsLevels,
936                                        boolean basePlanLocked,
937                                        boolean allLevelsSelection) {
938     doDeleteFurniture(home, groupedPieces, basePlanLocked, allLevelsSelection);
939     doAddFurniture(home, groups, groupsGroups, groupsIndex, null, groupsLevels, basePlanLocked, allLevelsSelection);
940   }
941 
doUngroupFurniture(Home home, HomeFurnitureGroup [] groups, HomePieceOfFurniture [] ungroupedPieces, HomeFurnitureGroup [] ungroupedPiecesGroups, int [] ungroupedPiecesIndex, Level [] ungroupedPiecesLevels, boolean basePlanLocked, boolean allLevelsSelection)942   private static void doUngroupFurniture(Home home,
943                                          HomeFurnitureGroup [] groups,
944                                          HomePieceOfFurniture [] ungroupedPieces,
945                                          HomeFurnitureGroup [] ungroupedPiecesGroups,
946                                          int [] ungroupedPiecesIndex,
947                                          Level [] ungroupedPiecesLevels,
948                                          boolean basePlanLocked,
949                                          boolean allLevelsSelection) {
950     doDeleteFurniture(home, groups, basePlanLocked, allLevelsSelection);
951     doAddFurniture(home, ungroupedPieces, ungroupedPiecesGroups, ungroupedPiecesIndex, null, ungroupedPiecesLevels, basePlanLocked, allLevelsSelection);
952   }
953 
954   /**
955    * Ungroups the selected groups of furniture.
956    */
ungroupSelectedFurniture()957   public void ungroupSelectedFurniture() {
958     List<HomeFurnitureGroup> movableSelectedFurnitureGroups = new ArrayList<HomeFurnitureGroup>();
959     for (Selectable item : this.home.getSelectedItems()) {
960       if (item instanceof HomeFurnitureGroup) {
961         HomeFurnitureGroup group = (HomeFurnitureGroup)item;
962         if (isPieceOfFurnitureMovable(group)) {
963           movableSelectedFurnitureGroups.add(group);
964         }
965       }
966     }
967     if (!movableSelectedFurnitureGroups.isEmpty()) {
968       List<HomePieceOfFurniture> homeFurniture = this.home.getFurniture();
969       final boolean oldBasePlanLocked = this.home.isBasePlanLocked();
970       final boolean allLevelsSelection = this.home.isAllLevelsSelection();
971       final List<Selectable> oldSelection = this.home.getSelectedItems();
972       // Sort the groups in the ascending order of their index in home or their group
973       Map<HomeFurnitureGroup, TreeMap<Integer, HomeFurnitureGroup>> groupsMap =
974           new HashMap<HomeFurnitureGroup, TreeMap<Integer, HomeFurnitureGroup>>();
975       int groupsCount = 0;
976       for (HomeFurnitureGroup piece : movableSelectedFurnitureGroups) {
977         HomeFurnitureGroup groupGroup = getPieceOfFurnitureGroup(piece, null, homeFurniture);
978         TreeMap<Integer, HomeFurnitureGroup> sortedMap = groupsMap.get(groupGroup);
979         if (sortedMap == null) {
980           sortedMap = new TreeMap<Integer, HomeFurnitureGroup>();
981           groupsMap.put(groupGroup, sortedMap);
982         }
983         if (groupGroup == null) {
984           sortedMap.put(homeFurniture.indexOf(piece), piece);
985         } else {
986           sortedMap.put(groupGroup.getFurniture().indexOf(piece), piece);
987         }
988         groupsCount++;
989       }
990       final HomeFurnitureGroup [] groups = new HomeFurnitureGroup [groupsCount];
991       final HomeFurnitureGroup [] groupsGroups = new HomeFurnitureGroup [groups.length];
992       final int [] groupsIndex = new int [groups.length];
993       final Level [] groupsLevels = new Level [groups.length];
994       int i = 0;
995       List<HomePieceOfFurniture> ungroupedPiecesList = new ArrayList<HomePieceOfFurniture>();
996       List<Integer> ungroupedPiecesIndexList = new ArrayList<Integer>();
997       List<HomeFurnitureGroup> ungroupedPiecesGroupsList = new ArrayList<HomeFurnitureGroup>();
998       for (Map.Entry<HomeFurnitureGroup, TreeMap<Integer, HomeFurnitureGroup>> sortedMapEntry : groupsMap.entrySet()) {
999         TreeMap<Integer, HomeFurnitureGroup> sortedMap = sortedMapEntry.getValue();
1000         int endIndex = sortedMap.lastKey() + 1 - sortedMap.size();
1001         for (Map.Entry<Integer, HomeFurnitureGroup> groupEntry : sortedMap.entrySet()) {
1002           HomeFurnitureGroup group = groupEntry.getValue();
1003           groups [i] = group;
1004           groupsGroups [i] = sortedMapEntry.getKey();
1005           groupsIndex [i] = groupEntry.getKey();
1006           groupsLevels [i++] = group.getLevel();
1007           for (HomePieceOfFurniture groupPiece : group.getFurniture()) {
1008             ungroupedPiecesList.add(groupPiece);
1009             ungroupedPiecesGroupsList.add(sortedMapEntry.getKey());
1010             ungroupedPiecesIndexList.add(endIndex++);
1011           }
1012         }
1013       }
1014       final HomePieceOfFurniture [] ungroupedPieces =
1015           ungroupedPiecesList.toArray(new HomePieceOfFurniture [ungroupedPiecesList.size()]);
1016       final HomeFurnitureGroup [] ungroupedPiecesGroups =
1017           ungroupedPiecesGroupsList.toArray(new HomeFurnitureGroup [ungroupedPiecesGroupsList.size()]);
1018       final int [] ungroupedPiecesIndex = new int [ungroupedPieces.length];
1019       final Level [] ungroupedPiecesLevels = new Level [ungroupedPieces.length];
1020       boolean basePlanLocked = oldBasePlanLocked;
1021       for (i = 0; i < ungroupedPieces.length; i++) {
1022         ungroupedPiecesIndex [i] = ungroupedPiecesIndexList.get(i);
1023         ungroupedPiecesLevels [i] = ungroupedPieces [i].getLevel();
1024         // Unlock base plan if the piece is a part of it
1025         basePlanLocked &= !isPieceOfFurniturePartOfBasePlan(ungroupedPieces [i]);
1026       }
1027       final boolean newBasePlanLocked = basePlanLocked;
1028 
1029       doUngroupFurniture(this.home, groups, ungroupedPieces, ungroupedPiecesGroups,
1030           ungroupedPiecesIndex, ungroupedPiecesLevels, newBasePlanLocked, false);
1031       if (this.undoSupport != null) {
1032         this.undoSupport.postEdit(new FurnitureUngroupingUndoableEdit(this.home, this.preferences,
1033             oldSelection.toArray(new Selectable [oldSelection.size()]), oldBasePlanLocked, allLevelsSelection,
1034             groups, groupsIndex, groupsGroups, groupsLevels, ungroupedPieces, ungroupedPiecesIndex,
1035             ungroupedPiecesGroups, ungroupedPiecesLevels, newBasePlanLocked));
1036       }
1037     }
1038   }
1039 
1040   /**
1041    * Undoable edit for furniture ungrouping.
1042    */
1043   private static class FurnitureUngroupingUndoableEdit extends LocalizedUndoableEdit {
1044     private final Home                    home;
1045     private final boolean                 oldBasePlanLocked;
1046     private final Selectable []           oldSelection;
1047     private final boolean                 allLevelsSelection;
1048     private final HomeFurnitureGroup []   groups;
1049     private final int []                  groupsIndex;
1050     private final HomeFurnitureGroup []   groupsGroups;
1051     private final Level []                groupsLevels;
1052     private final HomePieceOfFurniture [] ungroupedPieces;
1053     private final int []                  ungroupedPiecesIndex;
1054     private final HomeFurnitureGroup []   ungroupedPiecesGroups;
1055     private final Level []                ungroupedPiecesLevels;
1056     private final boolean                 newBasePlanLocked;
1057 
FurnitureUngroupingUndoableEdit(Home home, UserPreferences preferences, Selectable [] oldSelection, boolean oldBasePlanLocked, boolean allLevelsSelection, HomeFurnitureGroup [] groups, int [] groupsIndex, HomeFurnitureGroup [] groupsGroups, Level [] groupsLevels, HomePieceOfFurniture [] ungroupedPieces, int [] ungroupedPiecesIndex, HomeFurnitureGroup [] ungroupedPiecesGroups, Level [] ungroupedPiecesLevels, boolean newBasePlanLocked)1058     public FurnitureUngroupingUndoableEdit(Home home, UserPreferences preferences,
1059                                            Selectable [] oldSelection, boolean oldBasePlanLocked, boolean allLevelsSelection,
1060                                            HomeFurnitureGroup [] groups, int [] groupsIndex,
1061                                            HomeFurnitureGroup [] groupsGroups, Level [] groupsLevels,
1062                                            HomePieceOfFurniture [] ungroupedPieces, int [] ungroupedPiecesIndex,
1063                                            HomeFurnitureGroup [] ungroupedPiecesGroups,
1064                                            Level [] ungroupedPiecesLevels, boolean newBasePlanLocked) {
1065       super(preferences, FurnitureController.class, "undoUngroupName");
1066       this.home = home;
1067       this.oldSelection = oldSelection;
1068       this.oldBasePlanLocked = oldBasePlanLocked;
1069       this.allLevelsSelection = allLevelsSelection;
1070       this.groups = groups;
1071       this.groupsIndex = groupsIndex;
1072       this.groupsGroups = groupsGroups;
1073       this.groupsLevels = groupsLevels;
1074       this.ungroupedPieces = ungroupedPieces;
1075       this.ungroupedPiecesIndex = ungroupedPiecesIndex;
1076       this.ungroupedPiecesGroups = ungroupedPiecesGroups;
1077       this.ungroupedPiecesLevels = ungroupedPiecesLevels;
1078       this.newBasePlanLocked = newBasePlanLocked;
1079     }
1080 
1081     @Override
undo()1082     public void undo() throws CannotUndoException {
1083       super.undo();
1084       doGroupFurniture(this.home, this.ungroupedPieces, this.groups, this.groupsGroups, this.groupsIndex, this.groupsLevels,
1085           this.oldBasePlanLocked, this.allLevelsSelection);
1086       this.home.setSelectedItems(Arrays.asList(this.oldSelection));
1087     }
1088 
1089     @Override
redo()1090     public void redo() throws CannotRedoException {
1091       super.redo();
1092       doUngroupFurniture(this.home, this.groups, this.ungroupedPieces, this.ungroupedPiecesGroups, this.ungroupedPiecesIndex,
1093           this.ungroupedPiecesLevels, this.newBasePlanLocked, false);
1094     }
1095   }
1096 
1097   /**
1098    * Displays the wizard that helps to import furniture to home.
1099    */
importFurniture()1100   public void importFurniture() {
1101     new ImportedFurnitureWizardController(this.home, this.preferences, this, this.viewFactory,
1102         this.contentManager, this.undoSupport).displayView(getView());
1103   }
1104 
1105   /**
1106    * Displays the wizard that helps to import furniture to home with a
1107    * given model name.
1108    */
importFurniture(String modelName)1109   public void importFurniture(String modelName) {
1110     new ImportedFurnitureWizardController(this.home, modelName, this.preferences, this,
1111         this.viewFactory, this.contentManager, this.undoSupport).displayView(getView());
1112   }
1113 
1114   /**
1115    * Controls the alignment of selected furniture on top of the first selected piece.
1116    */
alignSelectedFurnitureOnTop()1117   public void alignSelectedFurnitureOnTop() {
1118     List<Selectable> oldSelection = this.home.getSelectedItems();
1119     alignSelectedFurniture(new FurnitureTopAlignmentUndoableEdit(this.home, this.preferences,
1120         oldSelection.toArray(new Selectable [oldSelection.size()]),
1121         getMovableSelectedFurniture(), this.leadSelectedPieceOfFurniture));
1122   }
1123 
1124   private static class FurnitureTopAlignmentUndoableEdit extends FurnitureAlignmentUndoableEdit {
FurnitureTopAlignmentUndoableEdit(Home home, UserPreferences preferences, Selectable [] oldSelection, HomePieceOfFurniture [] selectedFurniture, HomePieceOfFurniture leadPiece)1125     public FurnitureTopAlignmentUndoableEdit(Home home, UserPreferences preferences,
1126                                              Selectable [] oldSelection, HomePieceOfFurniture [] selectedFurniture,
1127                                              HomePieceOfFurniture leadPiece) {
1128       super(home, preferences, oldSelection, selectedFurniture, leadPiece);
1129     }
1130 
1131     @Override
alignFurniture(HomePieceOfFurniture [] alignedFurniture, HomePieceOfFurniture leadPiece)1132     void alignFurniture(HomePieceOfFurniture [] alignedFurniture, HomePieceOfFurniture leadPiece) {
1133       float minYLeadPiece = getMinY(leadPiece);
1134       for (HomePieceOfFurniture piece : alignedFurniture) {
1135         float minY = getMinY(piece);
1136         piece.setY(piece.getY() + minYLeadPiece - minY);
1137       }
1138     }
1139   }
1140 
1141   /**
1142    * Controls the alignment of selected furniture on bottom of the first selected piece.
1143    */
alignSelectedFurnitureOnBottom()1144   public void alignSelectedFurnitureOnBottom() {
1145     List<Selectable> oldSelection = this.home.getSelectedItems();
1146     alignSelectedFurniture(new FurnitureBottomAlignmentUndoableEdit(this.home, this.preferences,
1147         oldSelection.toArray(new Selectable [oldSelection.size()]),
1148         getMovableSelectedFurniture(), this.leadSelectedPieceOfFurniture));
1149   }
1150 
1151   private static class FurnitureBottomAlignmentUndoableEdit extends FurnitureAlignmentUndoableEdit {
FurnitureBottomAlignmentUndoableEdit(Home home, UserPreferences preferences, Selectable [] oldSelection, HomePieceOfFurniture [] selectedFurniture, HomePieceOfFurniture leadPiece)1152     public FurnitureBottomAlignmentUndoableEdit(Home home, UserPreferences preferences,
1153                                                 Selectable [] oldSelection, HomePieceOfFurniture [] selectedFurniture,
1154                                                 HomePieceOfFurniture leadPiece) {
1155       super(home, preferences, oldSelection, selectedFurniture, leadPiece);
1156     }
1157 
1158     @Override
alignFurniture(HomePieceOfFurniture [] alignedFurniture, HomePieceOfFurniture leadPiece)1159     void alignFurniture(HomePieceOfFurniture [] alignedFurniture, HomePieceOfFurniture leadPiece) {
1160       float maxYLeadPiece = getMaxY(leadPiece);
1161       for (HomePieceOfFurniture piece : alignedFurniture) {
1162         float maxY = getMaxY(piece);
1163         piece.setY(piece.getY() + maxYLeadPiece - maxY);
1164       }
1165     }
1166   }
1167 
1168   /**
1169    * Controls the alignment of selected furniture on left of the first selected piece.
1170    */
alignSelectedFurnitureOnLeft()1171   public void alignSelectedFurnitureOnLeft() {
1172     List<Selectable> oldSelection = this.home.getSelectedItems();
1173     alignSelectedFurniture(new FurnitureLeftAlignmentUndoableEdit(this.home, this.preferences,
1174         oldSelection.toArray(new Selectable [oldSelection.size()]),
1175         getMovableSelectedFurniture(), this.leadSelectedPieceOfFurniture));
1176   }
1177 
1178   private static class FurnitureLeftAlignmentUndoableEdit extends FurnitureAlignmentUndoableEdit {
FurnitureLeftAlignmentUndoableEdit(Home home, UserPreferences preferences, Selectable [] oldSelection, HomePieceOfFurniture [] selectedFurniture, HomePieceOfFurniture leadPiece)1179     public FurnitureLeftAlignmentUndoableEdit(Home home, UserPreferences preferences,
1180                                               Selectable [] oldSelection, HomePieceOfFurniture [] selectedFurniture,
1181                                               HomePieceOfFurniture leadPiece) {
1182       super(home, preferences, oldSelection, selectedFurniture, leadPiece);
1183     }
1184 
1185     @Override
alignFurniture(HomePieceOfFurniture [] alignedFurniture, HomePieceOfFurniture leadPiece)1186     void alignFurniture(HomePieceOfFurniture [] alignedFurniture, HomePieceOfFurniture leadPiece) {
1187       float minXLeadPiece = getMinX(leadPiece);
1188       for (HomePieceOfFurniture piece : alignedFurniture) {
1189         float minX = getMinX(piece);
1190         piece.setX(piece.getX() + minXLeadPiece - minX);
1191       }
1192     }
1193   }
1194 
1195   /**
1196    * Controls the alignment of selected furniture on right of the first selected piece.
1197    */
alignSelectedFurnitureOnRight()1198   public void alignSelectedFurnitureOnRight() {
1199     List<Selectable> oldSelection = this.home.getSelectedItems();
1200     alignSelectedFurniture(new FurnitureRightAlignmentUndoableEdit(this.home, this.preferences,
1201         oldSelection.toArray(new Selectable [oldSelection.size()]),
1202         getMovableSelectedFurniture(), this.leadSelectedPieceOfFurniture));
1203   }
1204 
1205   private static class FurnitureRightAlignmentUndoableEdit extends FurnitureAlignmentUndoableEdit {
FurnitureRightAlignmentUndoableEdit(Home home, UserPreferences preferences, Selectable [] oldSelection, HomePieceOfFurniture [] selectedFurniture, HomePieceOfFurniture leadPiece)1206     public FurnitureRightAlignmentUndoableEdit(Home home, UserPreferences preferences,
1207                                                Selectable [] oldSelection, HomePieceOfFurniture [] selectedFurniture,
1208                                               HomePieceOfFurniture leadPiece) {
1209       super(home, preferences, oldSelection, selectedFurniture, leadPiece);
1210     }
1211 
1212     @Override
alignFurniture(HomePieceOfFurniture [] alignedFurniture, HomePieceOfFurniture leadPiece)1213     void alignFurniture(HomePieceOfFurniture [] alignedFurniture, HomePieceOfFurniture leadPiece) {
1214       float maxXLeadPiece = getMaxX(leadPiece);
1215       for (HomePieceOfFurniture piece : alignedFurniture) {
1216         float maxX = getMaxX(piece);
1217         piece.setX(piece.getX() + maxXLeadPiece - maxX);
1218       }
1219     }
1220   }
1221 
1222   /**
1223    * Controls the alignment of selected furniture on the front side of the first selected piece.
1224    */
alignSelectedFurnitureOnFrontSide()1225   public void alignSelectedFurnitureOnFrontSide() {
1226     List<Selectable> oldSelection = this.home.getSelectedItems();
1227     alignSelectedFurniture(new FurnitureFrontSideAlignmentUndoableEdit(this.home, this.preferences,
1228         oldSelection.toArray(new Selectable [oldSelection.size()]),
1229         getMovableSelectedFurniture(), this.leadSelectedPieceOfFurniture));
1230   }
1231 
1232   private static class FurnitureFrontSideAlignmentUndoableEdit extends FurnitureAlignmentUndoableEdit {
FurnitureFrontSideAlignmentUndoableEdit(Home home, UserPreferences preferences, Selectable [] oldSelection, HomePieceOfFurniture [] selectedFurniture, HomePieceOfFurniture leadPiece)1233     public FurnitureFrontSideAlignmentUndoableEdit(Home home, UserPreferences preferences,
1234                                                    Selectable [] oldSelection, HomePieceOfFurniture [] selectedFurniture,
1235                                                    HomePieceOfFurniture leadPiece) {
1236       super(home, preferences, oldSelection, selectedFurniture, leadPiece);
1237     }
1238 
1239     @Override
alignFurniture(HomePieceOfFurniture [] alignedFurniture, HomePieceOfFurniture leadPiece)1240     void alignFurniture(HomePieceOfFurniture [] alignedFurniture, HomePieceOfFurniture leadPiece) {
1241       float [][] points = leadPiece.getPoints();
1242       Line2D frontLine = new Line2D.Float(points [2][0], points [2][1], points [3][0], points [3][1]);
1243       for (HomePieceOfFurniture piece : alignedFurniture) {
1244         alignPieceOfFurnitureAlongSides(piece, leadPiece, frontLine, true, null, 0);
1245       }
1246     }
1247   }
1248 
1249   /**
1250    * Controls the alignment of selected furniture on the back side of the first selected piece.
1251    */
alignSelectedFurnitureOnBackSide()1252   public void alignSelectedFurnitureOnBackSide() {
1253     List<Selectable> oldSelection = this.home.getSelectedItems();
1254     alignSelectedFurniture(new FurnitureBackSideAlignmentUndoableEdit(this.home, this.preferences,
1255         oldSelection.toArray(new Selectable [oldSelection.size()]),
1256         getMovableSelectedFurniture(), this.leadSelectedPieceOfFurniture));
1257   }
1258 
1259   private static class FurnitureBackSideAlignmentUndoableEdit extends FurnitureAlignmentUndoableEdit {
FurnitureBackSideAlignmentUndoableEdit(Home home, UserPreferences preferences, Selectable [] oldSelection, HomePieceOfFurniture [] selectedFurniture, HomePieceOfFurniture leadPiece)1260     public FurnitureBackSideAlignmentUndoableEdit(Home home, UserPreferences preferences,
1261                                                   Selectable [] oldSelection, HomePieceOfFurniture [] selectedFurniture,
1262                                                   HomePieceOfFurniture leadPiece) {
1263       super(home, preferences, oldSelection, selectedFurniture, leadPiece);
1264     }
1265 
1266     @Override
alignFurniture(HomePieceOfFurniture [] alignedFurniture, HomePieceOfFurniture leadPiece)1267     void alignFurniture(HomePieceOfFurniture [] alignedFurniture, HomePieceOfFurniture leadPiece) {
1268       float [][] points = leadPiece.getPoints();
1269       Line2D backLine = new Line2D.Float(points [0][0], points [0][1], points [1][0], points [1][1]);
1270       for (HomePieceOfFurniture piece : alignedFurniture) {
1271         alignPieceOfFurnitureAlongSides(piece, leadPiece, backLine, false, null, 0);
1272       }
1273     }
1274   }
1275 
1276   /**
1277    * Controls the alignment of selected furniture on the left side of the first selected piece.
1278    */
alignSelectedFurnitureOnLeftSide()1279   public void alignSelectedFurnitureOnLeftSide() {
1280     List<Selectable> oldSelection = this.home.getSelectedItems();
1281     alignSelectedFurniture(new FurnitureLeftSideAlignmentUndoableEdit(this.home, this.preferences,
1282         oldSelection.toArray(new Selectable [oldSelection.size()]),
1283         getMovableSelectedFurniture(), this.leadSelectedPieceOfFurniture));
1284   }
1285 
1286   private static class FurnitureLeftSideAlignmentUndoableEdit extends FurnitureAlignmentUndoableEdit {
FurnitureLeftSideAlignmentUndoableEdit(Home home, UserPreferences preferences, Selectable [] oldSelection, HomePieceOfFurniture [] selectedFurniture, HomePieceOfFurniture leadPiece)1287     public FurnitureLeftSideAlignmentUndoableEdit(Home home, UserPreferences preferences,
1288                                                   Selectable [] oldSelection, HomePieceOfFurniture [] selectedFurniture,
1289                                                   HomePieceOfFurniture leadPiece) {
1290       super(home, preferences, oldSelection, selectedFurniture, leadPiece);
1291     }
1292 
1293     @Override
alignFurniture(HomePieceOfFurniture [] alignedFurniture, HomePieceOfFurniture leadPiece)1294     void alignFurniture(HomePieceOfFurniture [] alignedFurniture, HomePieceOfFurniture leadPiece) {
1295       float [][] points = leadPiece.getPoints();
1296       Line2D leftLine = new Line2D.Float(points [3][0], points [3][1], points [0][0], points [0][1]);
1297       for (HomePieceOfFurniture piece : alignedFurniture) {
1298         alignPieceOfFurnitureAlongLeftOrRightSides(piece, leadPiece, leftLine, false);
1299       }
1300     }
1301   }
1302 
1303   /**
1304    * Controls the alignment of selected furniture on the right side of the first selected piece.
1305    */
alignSelectedFurnitureOnRightSide()1306   public void alignSelectedFurnitureOnRightSide() {
1307     List<Selectable> oldSelection = this.home.getSelectedItems();
1308     alignSelectedFurniture(new FurnitureRightSideAlignmentUndoableEdit(this.home, this.preferences,
1309         oldSelection.toArray(new Selectable [oldSelection.size()]),
1310         getMovableSelectedFurniture(), this.leadSelectedPieceOfFurniture));
1311   }
1312 
1313   private static class FurnitureRightSideAlignmentUndoableEdit extends FurnitureAlignmentUndoableEdit {
FurnitureRightSideAlignmentUndoableEdit(Home home, UserPreferences preferences, Selectable [] oldSelection, HomePieceOfFurniture [] selectedFurniture, HomePieceOfFurniture leadPiece)1314     public FurnitureRightSideAlignmentUndoableEdit(Home home, UserPreferences preferences,
1315                                                    Selectable [] oldSelection, HomePieceOfFurniture [] selectedFurniture,
1316                                                    HomePieceOfFurniture leadPiece) {
1317       super(home, preferences, oldSelection, selectedFurniture, leadPiece);
1318     }
1319 
1320     @Override
alignFurniture(HomePieceOfFurniture [] alignedFurniture, HomePieceOfFurniture leadPiece)1321     void alignFurniture(HomePieceOfFurniture [] alignedFurniture, HomePieceOfFurniture leadPiece) {
1322       float [][] points = leadPiece.getPoints();
1323       Line2D rightLine = new Line2D.Float(points [1][0], points [1][1], points [2][0], points [2][1]);
1324       for (HomePieceOfFurniture alignedPiece : alignedFurniture) {
1325         alignPieceOfFurnitureAlongLeftOrRightSides(alignedPiece, leadPiece, rightLine, true);
1326       }
1327     }
1328   }
1329 
1330   /**
1331    * Controls the alignment of selected furniture on the sides of the first selected piece.
1332    */
alignSelectedFurnitureSideBySide()1333   public void alignSelectedFurnitureSideBySide() {
1334     List<Selectable> oldSelection = this.home.getSelectedItems();
1335     alignSelectedFurniture(new FurnitureSideBySideAlignmentUndoableEdit(this.home, this.preferences,
1336         oldSelection.toArray(new Selectable [oldSelection.size()]),
1337         getMovableSelectedFurniture(), this.leadSelectedPieceOfFurniture));
1338   }
1339 
1340   private static class FurnitureSideBySideAlignmentUndoableEdit extends FurnitureAlignmentUndoableEdit {
FurnitureSideBySideAlignmentUndoableEdit(Home home, UserPreferences preferences, Selectable [] oldSelection, HomePieceOfFurniture [] selectedFurniture, HomePieceOfFurniture leadPiece)1341     public FurnitureSideBySideAlignmentUndoableEdit(Home home, UserPreferences preferences,
1342                                                     Selectable [] oldSelection, HomePieceOfFurniture [] selectedFurniture,
1343                                                     HomePieceOfFurniture leadPiece) {
1344       super(home, preferences, oldSelection, selectedFurniture, leadPiece);
1345     }
1346 
1347     @Override
alignFurniture(HomePieceOfFurniture [] alignedFurniture, HomePieceOfFurniture leadPiece)1348     void alignFurniture(HomePieceOfFurniture [] alignedFurniture, HomePieceOfFurniture leadPiece) {
1349       float [][] points = leadPiece.getPoints();
1350       final Line2D centerLine = new Line2D.Float(leadPiece.getX(), leadPiece.getY(),
1351           (points [0][0] + points [1][0]) / 2, (points [0][1] + points [1][1]) / 2);
1352       List<HomePieceOfFurniture> furnitureSortedAlongBackLine = sortFurniture(alignedFurniture, leadPiece, centerLine);
1353 
1354       int leadPieceIndex = furnitureSortedAlongBackLine.indexOf(leadPiece);
1355       Line2D backLine = new Line2D.Float(points [0][0], points [0][1], points [1][0], points [1][1]);
1356       float sideDistance = leadPiece.getWidthInPlan() / 2;
1357       for (int i = leadPieceIndex + 1; i < furnitureSortedAlongBackLine.size(); i++) {
1358         sideDistance += alignPieceOfFurnitureAlongSides(furnitureSortedAlongBackLine.get(i),
1359             leadPiece, backLine, false, centerLine, sideDistance);
1360       }
1361       sideDistance = -leadPiece.getWidthInPlan() / 2;
1362       for (int i = leadPieceIndex - 1; i >= 0; i--) {
1363         sideDistance -= alignPieceOfFurnitureAlongSides(furnitureSortedAlongBackLine.get(i),
1364             leadPiece, backLine, false, centerLine, sideDistance);
1365       }
1366     }
1367   }
1368 
1369   /**
1370    * Returns a list containing aligned furniture and lead piece sorted in the order of their distribution along
1371    * a line orthogonal to the given axis.
1372    */
sortFurniture(HomePieceOfFurniture [] furniture, HomePieceOfFurniture leadPiece, final Line2D orthogonalAxis)1373   private static List<HomePieceOfFurniture> sortFurniture(HomePieceOfFurniture [] furniture,
1374                                                           HomePieceOfFurniture leadPiece,
1375                                                           final Line2D orthogonalAxis) {
1376     List<HomePieceOfFurniture> sortedFurniture = new ArrayList<HomePieceOfFurniture>(furniture.length + 1);
1377     if (leadPiece != null) {
1378       sortedFurniture.add(leadPiece);
1379     }
1380     sortedFurniture.addAll(Arrays.asList(furniture));
1381     Collections.sort(sortedFurniture, new Comparator<HomePieceOfFurniture>() {
1382         public int compare(HomePieceOfFurniture p1, HomePieceOfFurniture p2) {
1383           return Double.compare(orthogonalAxis.ptLineDistSq(p2.getX(), p2.getY()) * orthogonalAxis.relativeCCW(p2.getX(), p2.getY()),
1384               orthogonalAxis.ptLineDistSq(p1.getX(), p1.getY()) * orthogonalAxis.relativeCCW(p1.getX(), p1.getY()));
1385         }
1386       });
1387     return sortedFurniture;
1388   }
1389 
1390   /**
1391    * Aligns the given <code>piece</code> along the front or back side of the lead piece and its left or right side
1392    * at a distance equal to <code>sideDistance</code>, and returns the width of the bounding box of
1393    * the <code>piece</code> along the back side axis.
1394    */
alignPieceOfFurnitureAlongSides(HomePieceOfFurniture piece, HomePieceOfFurniture leadPiece, Line2D frontOrBackLine, boolean frontLine, Line2D centerLine, float sideDistance)1395   private static double alignPieceOfFurnitureAlongSides(HomePieceOfFurniture piece, HomePieceOfFurniture leadPiece,
1396                                                         Line2D frontOrBackLine, boolean frontLine,
1397                                                         Line2D centerLine, float sideDistance) {
1398     // Search the distance required to align piece on the front or back side
1399     double distance = frontOrBackLine.relativeCCW(piece.getX(), piece.getY()) * frontOrBackLine.ptLineDist(piece.getX(), piece.getY())
1400         + getPieceBoundingRectangleHeight(piece, -leadPiece.getAngle()) / 2;
1401     if (frontLine) {
1402       distance = -distance;
1403     }
1404     double sinLeadPieceAngle = Math.sin(leadPiece.getAngle());
1405     double cosLeadPieceAngle = Math.cos(leadPiece.getAngle());
1406     float deltaX = (float)(-distance * sinLeadPieceAngle);
1407     float deltaY = (float)(distance * cosLeadPieceAngle);
1408 
1409     double rotatedBoundingBoxWidth = getPieceBoundingRectangleWidth(piece, -leadPiece.getAngle());
1410     if (centerLine != null) {
1411       // Search the distance required to align piece on the side of the previous piece
1412       int location = centerLine.relativeCCW(piece.getX(), piece.getY());
1413       if (location == 0) {
1414         location = frontLine ? 1 : -1;
1415       }
1416       distance = sideDistance + location
1417           * (centerLine.ptLineDist(piece.getX(), piece.getY()) - rotatedBoundingBoxWidth / 2);
1418       deltaX += (float)(distance * cosLeadPieceAngle);
1419       deltaY += (float)(distance * sinLeadPieceAngle);
1420     }
1421 
1422     piece.move(deltaX, deltaY);
1423     return rotatedBoundingBoxWidth;
1424   }
1425 
1426   /**
1427    * Aligns the given <code>piece</code> along the left or right side of the lead piece.
1428    */
alignPieceOfFurnitureAlongLeftOrRightSides(HomePieceOfFurniture piece, HomePieceOfFurniture leadPiece, Line2D leftOrRightLine, boolean rightLine)1429   private static void alignPieceOfFurnitureAlongLeftOrRightSides(HomePieceOfFurniture piece, HomePieceOfFurniture leadPiece,
1430                                                                  Line2D leftOrRightLine, boolean rightLine) {
1431     // Search the distance required to align piece on the side of the lead piece
1432     double distance = leftOrRightLine.relativeCCW(piece.getX(), piece.getY()) * leftOrRightLine.ptLineDist(piece.getX(), piece.getY())
1433         + getPieceBoundingRectangleWidth(piece, -leadPiece.getAngle()) / 2;
1434     if (rightLine) {
1435       distance = -distance;
1436     }
1437     piece.move((float)(distance * Math.cos(leadPiece.getAngle())), (float)(distance * Math.sin(leadPiece.getAngle())));
1438   }
1439 
1440   /**
1441    * Returns the bounding box width of the given piece when it's rotated of an additional angle.
1442    */
getPieceBoundingRectangleWidth(HomePieceOfFurniture piece, float additionalAngle)1443   private static double getPieceBoundingRectangleWidth(HomePieceOfFurniture piece, float additionalAngle) {
1444     return Math.abs(piece.getWidthInPlan() * Math.cos(additionalAngle + piece.getAngle()))
1445         + Math.abs(piece.getDepthInPlan() * Math.sin(additionalAngle + piece.getAngle()));
1446   }
1447 
1448   /**
1449    * Returns the bounding box height of the given piece when it's rotated of an additional angle.
1450    */
getPieceBoundingRectangleHeight(HomePieceOfFurniture piece, float additionalAngle)1451   private static double getPieceBoundingRectangleHeight(HomePieceOfFurniture piece, float additionalAngle) {
1452     return Math.abs(piece.getWidthInPlan() * Math.sin(additionalAngle + piece.getAngle()))
1453         + Math.abs(piece.getDepthInPlan() * Math.cos(additionalAngle + piece.getAngle()));
1454   }
1455 
1456   /**
1457    * Controls the alignment of selected furniture.
1458    */
alignSelectedFurniture(final FurnitureAlignmentUndoableEdit alignmentEdit)1459   private void alignSelectedFurniture(final FurnitureAlignmentUndoableEdit alignmentEdit) {
1460     HomePieceOfFurniture [] selectedFurniture = getMovableSelectedFurniture();
1461     if (selectedFurniture.length >= 2) {
1462       this.home.setSelectedItems(Arrays.asList(selectedFurniture));
1463       alignmentEdit.alignFurniture();
1464       if (this.undoSupport != null) {
1465         this.undoSupport.postEdit(alignmentEdit);
1466       }
1467     }
1468   }
1469 
1470   /**
1471    * Undoable edit for furniture alignment.
1472    */
1473   private static abstract class FurnitureAlignmentUndoableEdit extends LocalizedUndoableEdit {
1474     private final Home                    home;
1475     private final Selectable []           oldSelection;
1476     private final HomePieceOfFurniture [] selectedFurniture;
1477     private final HomePieceOfFurniture    leadPiece;
1478     private final HomePieceOfFurniture [] alignedFurniture;
1479     private final float []                oldX;
1480     private final float []                oldY;
1481 
FurnitureAlignmentUndoableEdit(Home home, UserPreferences preferences, Selectable [] oldSelection, HomePieceOfFurniture [] selectedFurniture, HomePieceOfFurniture leadPiece)1482     public FurnitureAlignmentUndoableEdit(Home home, UserPreferences preferences,
1483                                           Selectable [] oldSelection, HomePieceOfFurniture [] selectedFurniture,
1484                                           HomePieceOfFurniture leadPiece) {
1485       super(preferences, FurnitureController.class, "undoAlignName");
1486       this.home = home;
1487       this.oldSelection = oldSelection;
1488       this.selectedFurniture = selectedFurniture;
1489       this.leadPiece = leadPiece;
1490       this.alignedFurniture = new HomePieceOfFurniture[leadPiece == null  ? selectedFurniture.length  : selectedFurniture.length - 1];
1491       this.oldX = new float [this.alignedFurniture.length];
1492       this.oldY = new float [this.alignedFurniture.length];
1493       int i = 0;
1494       for (HomePieceOfFurniture piece : selectedFurniture) {
1495         if (piece != leadPiece) {
1496           this.alignedFurniture [i] = piece;
1497           this.oldX [i] = piece.getX();
1498           this.oldY [i] = piece.getY();
1499           i++;
1500         }
1501       }
1502     }
1503 
1504     @Override
undo()1505     public void undo() throws CannotUndoException {
1506       super.undo();
1507       undoAlignFurniture(this.alignedFurniture, this.oldX, this.oldY);
1508       this.home.setSelectedItems(Arrays.asList(this.oldSelection));
1509     }
1510 
1511     @Override
redo()1512     public void redo() throws CannotRedoException {
1513       super.redo();
1514       this.home.setSelectedItems(Arrays.asList(this.selectedFurniture));
1515       alignFurniture();
1516     }
1517 
alignFurniture()1518     public void alignFurniture() {
1519       alignFurniture(this.alignedFurniture, this.leadPiece);
1520     }
1521 
alignFurniture(HomePieceOfFurniture [] alignedFurniture, HomePieceOfFurniture leadPiece)1522     abstract void alignFurniture(HomePieceOfFurniture [] alignedFurniture,
1523                                  HomePieceOfFurniture leadPiece);
1524   }
1525 
getMovableSelectedFurniture()1526   private HomePieceOfFurniture [] getMovableSelectedFurniture() {
1527     List<HomePieceOfFurniture> movableSelectedFurniture = new ArrayList<HomePieceOfFurniture>();
1528     for (Selectable item : this.home.getSelectedItems()) {
1529       if (item instanceof HomePieceOfFurniture) {
1530         HomePieceOfFurniture piece = (HomePieceOfFurniture)item;
1531         if (isPieceOfFurnitureMovable(piece)) {
1532           movableSelectedFurniture.add(piece);
1533         }
1534       }
1535     }
1536     return movableSelectedFurniture.toArray(new HomePieceOfFurniture [movableSelectedFurniture.size()]);
1537   }
1538 
undoAlignFurniture(HomePieceOfFurniture [] alignedFurniture, float [] x, float [] y)1539   private static void undoAlignFurniture(HomePieceOfFurniture [] alignedFurniture, float [] x, float [] y) {
1540     for (int i = 0; i < alignedFurniture.length; i++) {
1541       HomePieceOfFurniture piece = alignedFurniture [i];
1542       piece.setX(x [i]);
1543       piece.setY(y [i]);
1544     }
1545   }
1546 
1547   /**
1548    * Returns the minimum abscissa of the vertices of <code>piece</code>.
1549    */
getMinX(HomePieceOfFurniture piece)1550   private static float getMinX(HomePieceOfFurniture piece) {
1551     float [][] points = piece.getPoints();
1552     float minX = Float.POSITIVE_INFINITY;
1553     for (float [] point : points) {
1554       minX = Math.min(minX, point [0]);
1555     }
1556     return minX;
1557   }
1558 
1559   /**
1560    * Returns the maximum abscissa of the vertices of <code>piece</code>.
1561    */
getMaxX(HomePieceOfFurniture piece)1562   private static float getMaxX(HomePieceOfFurniture piece) {
1563     float [][] points = piece.getPoints();
1564     float maxX = Float.NEGATIVE_INFINITY;
1565     for (float [] point : points) {
1566       maxX = Math.max(maxX, point [0]);
1567     }
1568     return maxX;
1569   }
1570 
1571   /**
1572    * Returns the minimum ordinate of the vertices of <code>piece</code>.
1573    */
getMinY(HomePieceOfFurniture piece)1574   private static float getMinY(HomePieceOfFurniture piece) {
1575     float [][] points = piece.getPoints();
1576     float minY = Float.POSITIVE_INFINITY;
1577     for (float [] point : points) {
1578       minY = Math.min(minY, point [1]);
1579     }
1580     return minY;
1581   }
1582 
1583   /**
1584    * Returns the maximum ordinate of the vertices of <code>piece</code>.
1585    */
getMaxY(HomePieceOfFurniture piece)1586   private static float getMaxY(HomePieceOfFurniture piece) {
1587     float [][] points = piece.getPoints();
1588     float maxY = Float.NEGATIVE_INFINITY;
1589     for (float [] point : points) {
1590       maxY = Math.max(maxY, point [1]);
1591     }
1592     return maxY;
1593   }
1594 
1595   /**
1596    * Controls the distribution of the selected furniture along horizontal axis.
1597    */
distributeSelectedFurnitureHorizontally()1598   public void distributeSelectedFurnitureHorizontally() {
1599     distributeSelectedFurniture(true);
1600   }
1601 
1602   /**
1603    * Controls the distribution of the selected furniture along vertical axis.
1604    */
distributeSelectedFurnitureVertically()1605   public void distributeSelectedFurnitureVertically() {
1606     distributeSelectedFurniture(false);
1607   }
1608 
1609   /**
1610    * Controls the distribution of the selected furniture along the axis orthogonal to the given one.
1611    */
distributeSelectedFurniture(final boolean horizontal)1612   public void distributeSelectedFurniture(final boolean horizontal) {
1613     final HomePieceOfFurniture [] alignedFurniture = getMovableSelectedFurniture();
1614     if (alignedFurniture.length >= 3) {
1615       final List<Selectable> oldSelection = this.home.getSelectedItems();
1616       final float [] oldX = new float [alignedFurniture.length];
1617       final float [] oldY = new float [alignedFurniture.length];
1618       for (int i = 0; i < alignedFurniture.length; i++) {
1619         oldX [i] = alignedFurniture [i].getX();
1620         oldY [i] = alignedFurniture [i].getY();
1621       }
1622       this.home.setSelectedItems(Arrays.asList(alignedFurniture));
1623       doDistributeFurnitureAlongAxis(alignedFurniture, horizontal);
1624       if (this.undoSupport != null) {
1625         this.undoSupport.postEdit(new FurnitureDistributionUndoableEdit(this.home, this.preferences,
1626             oldSelection.toArray(new Selectable [oldSelection.size()]), oldX, oldY, alignedFurniture, horizontal));
1627       }
1628     }
1629   }
1630 
1631   /**
1632    * Undoable edit for furniture distribution.
1633    */
1634   private static class FurnitureDistributionUndoableEdit extends LocalizedUndoableEdit {
1635     private final Home                    home;
1636     private final Selectable []           oldSelection;
1637     private float []                      oldX;
1638     private float []                      oldY;
1639     private final HomePieceOfFurniture [] alignedFurniture;
1640     private final boolean                 horizontal;
1641 
FurnitureDistributionUndoableEdit(Home home, UserPreferences preferences, Selectable [] oldSelection, float [] oldX, float[] oldY, HomePieceOfFurniture [] alignedFurniture, boolean horizontal)1642     public FurnitureDistributionUndoableEdit(Home home,
1643                                              UserPreferences preferences,
1644                                              Selectable [] oldSelection, float [] oldX, float[] oldY,
1645                                              HomePieceOfFurniture [] alignedFurniture, boolean horizontal) {
1646       super(preferences, FurnitureController.class, "undoDistributeName");
1647       this.home = home;
1648       this.oldSelection = oldSelection;
1649       this.oldX = oldX;
1650       this.oldY = oldY;
1651       this.alignedFurniture = alignedFurniture;
1652       this.horizontal = horizontal;
1653     }
1654 
1655     @Override
undo()1656     public void undo() throws CannotUndoException {
1657       super.undo();
1658       undoAlignFurniture(this.alignedFurniture, this.oldX, this.oldY);
1659       this.home.setSelectedItems(Arrays.asList(this.oldSelection));
1660     }
1661 
1662     @Override
redo()1663     public void redo() throws CannotRedoException {
1664       super.redo();
1665       this.home.setSelectedItems(Arrays.asList(this.alignedFurniture));
1666       doDistributeFurnitureAlongAxis(this.alignedFurniture, this.horizontal);
1667     }
1668   }
1669 
doDistributeFurnitureAlongAxis(HomePieceOfFurniture [] alignedFurniture, boolean horizontal)1670   private static void doDistributeFurnitureAlongAxis(HomePieceOfFurniture [] alignedFurniture,
1671                                                      boolean horizontal) {
1672     Line2D orthogonalAxis = horizontal ? new Line2D.Float(0, 0, 0, -1) : new Line2D.Float(0, 0, 1, 0);
1673     List<HomePieceOfFurniture> furnitureHorizontallySorted = sortFurniture(alignedFurniture, null, orthogonalAxis);
1674     float axisAngle = (float)(horizontal ? 0 : Math.PI / 2);
1675     HomePieceOfFurniture firstPiece = furnitureHorizontallySorted.get(0);
1676     double firstPieceBoundingRectangleHalfWidth = getPieceBoundingRectangleWidth(firstPiece, axisAngle) / 2;
1677     HomePieceOfFurniture lastPiece = furnitureHorizontallySorted.get(furnitureHorizontallySorted.size() - 1);
1678     double lastPieceBoundingRectangleHalfWidth = getPieceBoundingRectangleWidth(lastPiece, axisAngle) / 2;
1679     double gap = Math.abs(
1680           orthogonalAxis.ptLineDist(lastPiece.getX(), lastPiece.getY())
1681           * orthogonalAxis.relativeCCW(lastPiece.getX(), lastPiece.getY())
1682         - orthogonalAxis.ptLineDist(firstPiece.getX(), firstPiece.getY())
1683           * orthogonalAxis.relativeCCW(firstPiece.getX(), firstPiece.getY()))
1684         - lastPieceBoundingRectangleHalfWidth
1685         - firstPieceBoundingRectangleHalfWidth;
1686     double [] furnitureWidthsAlongAxis = new double [furnitureHorizontallySorted.size() - 2];
1687     for (int i = 1; i < furnitureHorizontallySorted.size() - 1; i++) {
1688       HomePieceOfFurniture piece = furnitureHorizontallySorted.get(i);
1689       furnitureWidthsAlongAxis [i - 1] = getPieceBoundingRectangleWidth(piece, axisAngle);
1690       gap -= furnitureWidthsAlongAxis [i - 1];
1691     }
1692     gap /= furnitureHorizontallySorted.size() - 1;
1693     float xOrY = (horizontal ? firstPiece.getX() : firstPiece.getY())
1694         + (float)(firstPieceBoundingRectangleHalfWidth + gap);
1695     for (int i = 1; i < furnitureHorizontallySorted.size() - 1; i++) {
1696       HomePieceOfFurniture piece = furnitureHorizontallySorted.get(i);
1697       if (horizontal) {
1698         piece.setX((float)(xOrY + furnitureWidthsAlongAxis [i - 1] / 2));
1699       } else {
1700         piece.setY((float)(xOrY + furnitureWidthsAlongAxis [i - 1] / 2));
1701       }
1702       xOrY += gap + furnitureWidthsAlongAxis [i - 1];
1703     }
1704   }
1705 
1706   /**
1707    * Resets the elevation of the selected furniture to its default elevation.
1708    * @since 4.4
1709    */
resetFurnitureElevation()1710   public void resetFurnitureElevation() {
1711     final HomePieceOfFurniture [] selectedFurniture = getMovableSelectedFurniture();
1712     if (selectedFurniture.length >= 1) {
1713       final List<Selectable> oldSelection = this.home.getSelectedItems();
1714       final float [] furnitureOldElevation = new float [selectedFurniture.length];
1715       final float [] furnitureNewElevation = new float [selectedFurniture.length];
1716       for (int i = 0; i < selectedFurniture.length; i++) {
1717         HomePieceOfFurniture piece = selectedFurniture [i];
1718         furnitureOldElevation [i] = piece.getElevation();
1719         HomePieceOfFurniture highestSurroundingPiece = getHighestSurroundingPieceOfFurniture(piece, Arrays.asList(selectedFurniture));
1720         if (highestSurroundingPiece != null) {
1721           float elevation = highestSurroundingPiece.getElevation()
1722               + highestSurroundingPiece.getHeightInPlan() * highestSurroundingPiece.getDropOnTopElevation();
1723           if (highestSurroundingPiece.getLevel() != null) {
1724             elevation += highestSurroundingPiece.getLevel().getElevation() - piece.getLevel().getElevation();
1725           }
1726           furnitureNewElevation [i] = Math.max(0, elevation);
1727         } else {
1728           furnitureNewElevation [i] = 0;
1729         }
1730       }
1731       this.home.setSelectedItems(Arrays.asList(selectedFurniture));
1732       doSetFurnitureElevation(selectedFurniture, furnitureNewElevation);
1733       if (this.undoSupport != null) {
1734         this.undoSupport.postEdit(new FurnitureElevationResetUndoableEdit(this.home, this.preferences,
1735             oldSelection.toArray(new Selectable [oldSelection.size()]), furnitureOldElevation, selectedFurniture, furnitureNewElevation));
1736       }
1737     }
1738   }
1739 
1740   /**
1741    * Undoable edit for furniture elevation reset.
1742    */
1743   private static class FurnitureElevationResetUndoableEdit extends LocalizedUndoableEdit {
1744     private final Home                    home;
1745     private final Selectable []           oldSelection;
1746     private final float []                furnitureOldElevation;
1747     private final HomePieceOfFurniture [] selectedFurniture;
1748     private float []                      furnitureNewElevation;
1749 
FurnitureElevationResetUndoableEdit(Home home, UserPreferences preferences, Selectable [] oldSelection, float [] furnitureOldElevation, HomePieceOfFurniture [] selectedFurniture, float [] furnitureNewElevation)1750     public FurnitureElevationResetUndoableEdit(Home home, UserPreferences preferences,
1751                                                Selectable [] oldSelection, float [] furnitureOldElevation,
1752                                                HomePieceOfFurniture [] selectedFurniture, float [] furnitureNewElevation) {
1753       super(preferences, FurnitureController.class, "undoResetElevation");
1754       this.home = home;
1755       this.oldSelection = oldSelection;
1756       this.furnitureOldElevation = furnitureOldElevation;
1757       this.selectedFurniture = selectedFurniture;
1758       this.furnitureNewElevation = furnitureNewElevation;
1759     }
1760 
1761     @Override
undo()1762     public void undo() throws CannotUndoException {
1763       super.undo();
1764       doSetFurnitureElevation(this.selectedFurniture, this.furnitureOldElevation);
1765       this.home.setSelectedItems(Arrays.asList(this.oldSelection));
1766     }
1767 
1768     @Override
redo()1769     public void redo() throws CannotRedoException {
1770       super.redo();
1771       this.home.setSelectedItems(Arrays.asList(this.selectedFurniture));
1772       doSetFurnitureElevation(this.selectedFurniture, this.furnitureNewElevation);
1773     }
1774   }
1775 
doSetFurnitureElevation(HomePieceOfFurniture [] selectedFurniture, float [] furnitureNewElevation)1776   private static void doSetFurnitureElevation(HomePieceOfFurniture [] selectedFurniture, float [] furnitureNewElevation) {
1777     for (int i = 0; i < selectedFurniture.length; i++) {
1778       selectedFurniture [i].setElevation(furnitureNewElevation [i]);
1779     }
1780   }
1781 
1782   /**
1783    * Returns the highest piece of furniture that includes the given <code>piece</code>
1784    * with a margin error of 5% of the smallest side length.
1785    * @since 4.4
1786    */
getHighestSurroundingPieceOfFurniture(HomePieceOfFurniture piece)1787   protected HomePieceOfFurniture getHighestSurroundingPieceOfFurniture(HomePieceOfFurniture piece) {
1788     List<HomePieceOfFurniture> ignoredFurniture = Collections.emptyList();
1789     return getHighestSurroundingPieceOfFurniture(piece, ignoredFurniture);
1790   }
1791 
getHighestSurroundingPieceOfFurniture(HomePieceOfFurniture piece, List<HomePieceOfFurniture> ignoredFurniture)1792   private HomePieceOfFurniture getHighestSurroundingPieceOfFurniture(HomePieceOfFurniture piece,
1793                                                                      List<HomePieceOfFurniture> ignoredFurniture) {
1794     float [][] piecePoints = piece.getPoints();
1795     float margin = Math.min(piece.getWidthInPlan(), piece.getDepthInPlan()) * 0.05f;
1796     HomePieceOfFurniture highestSurroundingPiece = null;
1797     float highestElevation = Float.MIN_VALUE;
1798     for (HomePieceOfFurniture homePiece : getFurnitureInSameGroup(piece)) {
1799       if (homePiece != piece
1800           && !ignoredFurniture.contains(homePiece)
1801           && isPieceOfFurnitureVisibleAtSelectedLevel(homePiece)
1802           && homePiece.getDropOnTopElevation() >= 0) {
1803         boolean surroundingPieceContainsPiece = true;
1804         for (float [] point : piecePoints) {
1805           if (!homePiece.containsPoint(point [0], point [1], margin)) {
1806             surroundingPieceContainsPiece = false;
1807             break;
1808           }
1809         }
1810         if (surroundingPieceContainsPiece) {
1811           float elevation = homePiece.getElevation()
1812               + homePiece.getHeightInPlan() * homePiece.getDropOnTopElevation();
1813           if (elevation > highestElevation) {
1814             highestElevation = elevation;
1815             highestSurroundingPiece = homePiece;
1816           }
1817         }
1818       }
1819     }
1820     return highestSurroundingPiece;
1821   }
1822 
1823   /**
1824    * Returns the furniture list of the given <code>piece</code> which belongs to same group
1825    * or home furniture if it doesn't belong to home furniture.
1826    * @since 5.0
1827    */
getFurnitureInSameGroup(HomePieceOfFurniture piece)1828   protected List<HomePieceOfFurniture> getFurnitureInSameGroup(HomePieceOfFurniture piece) {
1829     List<HomePieceOfFurniture> homeFurniture = this.home.getFurniture();
1830     List<HomePieceOfFurniture> furnitureInSameGroup = getFurnitureInSameGroup(piece, homeFurniture);
1831     if (furnitureInSameGroup != null) {
1832       return furnitureInSameGroup;
1833     } else {
1834       return homeFurniture;
1835     }
1836   }
1837 
getFurnitureInSameGroup(HomePieceOfFurniture piece, List<HomePieceOfFurniture> furniture)1838   private static List<HomePieceOfFurniture> getFurnitureInSameGroup(HomePieceOfFurniture piece, List<HomePieceOfFurniture> furniture) {
1839     for (HomePieceOfFurniture piece2 : furniture) {
1840       if (piece2 == piece) {
1841         return furniture;
1842       } else if (piece2 instanceof HomeFurnitureGroup) {
1843         List<HomePieceOfFurniture> siblingFurniture = getFurnitureInSameGroup(piece, ((HomeFurnitureGroup)piece2).getFurniture());
1844         if (siblingFurniture != null) {
1845           return siblingFurniture;
1846         }
1847       }
1848     }
1849     return null;
1850   }
1851 
1852   /**
1853    * Returns <code>true</code> if the given piece is viewable and
1854    * its height and elevation make it viewable at the selected level in home.
1855    * @since 4.4
1856    */
isPieceOfFurnitureVisibleAtSelectedLevel(HomePieceOfFurniture piece)1857   protected boolean isPieceOfFurnitureVisibleAtSelectedLevel(HomePieceOfFurniture piece) {
1858     Level selectedLevel = this.home.getSelectedLevel();
1859     return piece.isVisible()
1860         && (piece.getLevel() == null
1861             || piece.getLevel().isViewable())
1862         && (piece.getLevel() == selectedLevel
1863             || piece.isAtLevel(selectedLevel));
1864   }
1865 
1866   /**
1867    * Controls the change of value of a visual property in home.
1868    * @deprecated {@link #setVisualProperty(String, Object) setVisualProperty} should be replaced by a call to
1869    * {@link #setHomeProperty(String, String)} to ensure the property can be easily saved and read.
1870    * @since 5.0
1871    */
setVisualProperty(String propertyName, Object propertyValue)1872   public void setVisualProperty(String propertyName,
1873                                 Object propertyValue) {
1874     this.home.setVisualProperty(propertyName, propertyValue);
1875   }
1876 
1877   /**
1878    * Controls the change of value of a property in home.
1879    * @since 5.2
1880    */
setHomeProperty(String propertyName, String propertyValue)1881   public void setHomeProperty(String propertyName,
1882                                 String propertyValue) {
1883     this.home.setProperty(propertyName, propertyValue);
1884   }
1885 }
1886