~ubuntu-branches/ubuntu/utopic/sweethome3d/utopic-proposed

« back to all changes in this revision

Viewing changes to .pc/03novideo/src/com/eteks/sweethome3d/viewcontroller/HomeController.java

  • Committer: Package Import Robot
  • Author(s): Gabriele Giacone
  • Date: 2012-11-21 00:41:38 UTC
  • Revision ID: package-import@ubuntu.com-20121121004138-zr3l8v909ckvnoia
Tags: 3.7+dfsg-2
* Fix home path added to application folder property (Closes: #695528).
* Make video feature removal more explicit (LP: #996714).

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * HomeController.java 15 mai 2006
 
3
 *
 
4
 * Sweet Home 3D, Copyright (c) 2006 Emmanuel PUYBARET / eTeks <info@eteks.com>
 
5
 *
 
6
 * This program is free software; you can redistribute it and/or modify
 
7
 * it under the terms of the GNU General Public License as published by
 
8
 * the Free Software Foundation; either version 2 of the License, or
 
9
 * (at your option) any later version.
 
10
 *
 
11
 * This program is distributed in the hope that it will be useful,
 
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
14
 * GNU General Public License for more details.
 
15
 *
 
16
 * You should have received a copy of the GNU General Public License
 
17
 * along with this program; if not, write to the Free Software
 
18
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
19
 */
 
20
package com.eteks.sweethome3d.viewcontroller;
 
21
 
 
22
import java.beans.PropertyChangeEvent;
 
23
import java.beans.PropertyChangeListener;
 
24
import java.lang.ref.WeakReference;
 
25
import java.text.DateFormat;
 
26
import java.util.ArrayList;
 
27
import java.util.Arrays;
 
28
import java.util.Collections;
 
29
import java.util.Date;
 
30
import java.util.HashSet;
 
31
import java.util.Iterator;
 
32
import java.util.List;
 
33
import java.util.Set;
 
34
import java.util.concurrent.Callable;
 
35
 
 
36
import javax.swing.event.UndoableEditEvent;
 
37
import javax.swing.event.UndoableEditListener;
 
38
import javax.swing.undo.AbstractUndoableEdit;
 
39
import javax.swing.undo.CannotRedoException;
 
40
import javax.swing.undo.CannotUndoException;
 
41
import javax.swing.undo.CompoundEdit;
 
42
import javax.swing.undo.UndoManager;
 
43
import javax.swing.undo.UndoableEdit;
 
44
import javax.swing.undo.UndoableEditSupport;
 
45
 
 
46
import com.eteks.sweethome3d.model.AspectRatio;
 
47
import com.eteks.sweethome3d.model.BackgroundImage;
 
48
import com.eteks.sweethome3d.model.Camera;
 
49
import com.eteks.sweethome3d.model.CatalogPieceOfFurniture;
 
50
import com.eteks.sweethome3d.model.CatalogTexture;
 
51
import com.eteks.sweethome3d.model.CollectionEvent;
 
52
import com.eteks.sweethome3d.model.CollectionListener;
 
53
import com.eteks.sweethome3d.model.Compass;
 
54
import com.eteks.sweethome3d.model.DimensionLine;
 
55
import com.eteks.sweethome3d.model.Elevatable;
 
56
import com.eteks.sweethome3d.model.FurnitureCatalog;
 
57
import com.eteks.sweethome3d.model.Home;
 
58
import com.eteks.sweethome3d.model.HomeApplication;
 
59
import com.eteks.sweethome3d.model.HomeDoorOrWindow;
 
60
import com.eteks.sweethome3d.model.HomeEnvironment;
 
61
import com.eteks.sweethome3d.model.HomeFurnitureGroup;
 
62
import com.eteks.sweethome3d.model.HomePieceOfFurniture;
 
63
import com.eteks.sweethome3d.model.HomeRecorder;
 
64
import com.eteks.sweethome3d.model.InterruptedRecorderException;
 
65
import com.eteks.sweethome3d.model.Label;
 
66
import com.eteks.sweethome3d.model.Level;
 
67
import com.eteks.sweethome3d.model.RecorderException;
 
68
import com.eteks.sweethome3d.model.Room;
 
69
import com.eteks.sweethome3d.model.Selectable;
 
70
import com.eteks.sweethome3d.model.SelectionEvent;
 
71
import com.eteks.sweethome3d.model.SelectionListener;
 
72
import com.eteks.sweethome3d.model.TexturesCatalog;
 
73
import com.eteks.sweethome3d.model.UserPreferences;
 
74
import com.eteks.sweethome3d.model.Wall;
 
75
import com.eteks.sweethome3d.viewcontroller.PlanController.Mode;
 
76
 
 
77
/**
 
78
 * A MVC controller for the home view.
 
79
 * @author Emmanuel Puybaret
 
80
 */
 
81
public class HomeController implements Controller {
 
82
  private final Home                  home;
 
83
  private final UserPreferences       preferences;
 
84
  private final HomeApplication       application;
 
85
  private final ViewFactory           viewFactory;
 
86
  private final ContentManager        contentManager;
 
87
  private final UndoableEditSupport   undoSupport;
 
88
  private final UndoManager           undoManager;
 
89
  private HomeView                    homeView;
 
90
  private FurnitureCatalogController  furnitureCatalogController;
 
91
  private FurnitureController         furnitureController;
 
92
  private PlanController              planController;
 
93
  private HomeController3D            homeController3D;
 
94
  private static HelpController       helpController;  // Only one help controller
 
95
  private int                         saveUndoLevel;
 
96
  private boolean                     notUndoableModifications;
 
97
  private View                        focusedView;
 
98
 
 
99
  /**
 
100
   * Creates the controller of home view.
 
101
   * @param home the home edited by this controller and its view.
 
102
   * @param application the instance of current application.
 
103
   * @param viewFactory a factory able to create views.
 
104
   * @param contentManager the content manager of the application.
 
105
   */
 
106
  public HomeController(Home home, 
 
107
                        HomeApplication application,
 
108
                        ViewFactory    viewFactory, 
 
109
                        ContentManager contentManager) {
 
110
    this(home, application.getUserPreferences(), viewFactory, 
 
111
        contentManager, application);
 
112
  }
 
113
 
 
114
  /**
 
115
   * Creates the controller of home view.
 
116
   * @param home the home edited by this controller and its view.
 
117
   * @param application the instance of current application.
 
118
   * @param viewFactory a factory able to create views.
 
119
   */
 
120
  public HomeController(Home home, 
 
121
                        HomeApplication application,
 
122
                        ViewFactory viewFactory) {
 
123
    this(home, application.getUserPreferences(), viewFactory, null, application);
 
124
  }
 
125
 
 
126
  /**
 
127
   * Creates the controller of home view. 
 
128
   * @param home        the home edited by this controller and its view.
 
129
   * @param preferences the preferences of the application.
 
130
   * @param viewFactory a factory able to create views.
 
131
   */
 
132
  public HomeController(Home home, 
 
133
                        UserPreferences preferences,
 
134
                        ViewFactory viewFactory) {
 
135
    this(home, preferences, viewFactory, null, null);
 
136
  }
 
137
 
 
138
  /**
 
139
   * Creates the controller of home view. 
 
140
   * @param home        the home edited by this controller and its view.
 
141
   * @param preferences the preferences of the application.
 
142
   * @param viewFactory a factory able to create views.
 
143
   * @param contentManager the content manager of the application.
 
144
   */
 
145
  public HomeController(Home home, 
 
146
                        UserPreferences preferences,
 
147
                        ViewFactory    viewFactory,
 
148
                        ContentManager contentManager) {
 
149
    this(home, preferences, viewFactory, contentManager, null);
 
150
  }
 
151
 
 
152
  private HomeController(final Home home, 
 
153
                         final UserPreferences preferences,
 
154
                         ViewFactory    viewFactory,
 
155
                         ContentManager contentManager,
 
156
                         HomeApplication application) {
 
157
    this.home = home;
 
158
    this.preferences = preferences;
 
159
    this.viewFactory = viewFactory;
 
160
    this.contentManager = contentManager;
 
161
    this.application = application;
 
162
    this.undoSupport = new UndoableEditSupport() {
 
163
        @Override
 
164
        protected void _postEdit(UndoableEdit edit) {
 
165
          // Ignore not significant compound edit
 
166
          if (!(edit instanceof CompoundEdit)
 
167
              || edit.isSignificant()) {
 
168
            super._postEdit(edit);
 
169
          }
 
170
        }
 
171
      };
 
172
    this.undoManager = new UndoManager();
 
173
    this.undoSupport.addUndoableEditListener(this.undoManager);
 
174
    
 
175
    // Update recent homes list
 
176
    if (home.getName() != null) {
 
177
      List<String> recentHomes = new ArrayList<String>(this.preferences.getRecentHomes());
 
178
      recentHomes.remove(home.getName());
 
179
      recentHomes.add(0, home.getName());
 
180
      updateUserPreferencesRecentHomes(recentHomes);
 
181
      
 
182
      // If home version is more recent than current version
 
183
      if (home.getVersion() > Home.CURRENT_VERSION) {
 
184
        // Warn the user that view will display a home created with a more recent version 
 
185
        getView().invokeLater(new Runnable() { 
 
186
            public void run() {
 
187
              String message = preferences.getLocalizedString(HomeController.class, 
 
188
                  "moreRecentVersionHome", home.getName());
 
189
              getView().showMessage(message);
 
190
            }
 
191
          });
 
192
      }
 
193
    }
 
194
  }
 
195
 
 
196
  /**
 
197
   * Enables actions at controller instantiation. 
 
198
   */
 
199
  private void enableDefaultActions(HomeView homeView) {
 
200
    boolean applicationExists = this.application != null;
 
201
    
 
202
    homeView.setEnabled(HomeView.ActionType.NEW_HOME, applicationExists);
 
203
    homeView.setEnabled(HomeView.ActionType.OPEN, applicationExists);
 
204
    homeView.setEnabled(HomeView.ActionType.DELETE_RECENT_HOMES, 
 
205
        applicationExists && !this.preferences.getRecentHomes().isEmpty());
 
206
    homeView.setEnabled(HomeView.ActionType.CLOSE, applicationExists);
 
207
    homeView.setEnabled(HomeView.ActionType.SAVE, applicationExists);
 
208
    homeView.setEnabled(HomeView.ActionType.SAVE_AS, applicationExists);
 
209
    homeView.setEnabled(HomeView.ActionType.SAVE_AND_COMPRESS, applicationExists);
 
210
    homeView.setEnabled(HomeView.ActionType.PAGE_SETUP, true);
 
211
    homeView.setEnabled(HomeView.ActionType.PRINT_PREVIEW, true);
 
212
    homeView.setEnabled(HomeView.ActionType.PRINT, true);
 
213
    homeView.setEnabled(HomeView.ActionType.PRINT_TO_PDF, true);
 
214
    homeView.setEnabled(HomeView.ActionType.PREFERENCES, true);
 
215
    homeView.setEnabled(HomeView.ActionType.EXIT, applicationExists);
 
216
    homeView.setEnabled(HomeView.ActionType.IMPORT_FURNITURE, true);
 
217
    homeView.setEnabled(HomeView.ActionType.IMPORT_FURNITURE_LIBRARY, true);
 
218
    homeView.setEnabled(HomeView.ActionType.IMPORT_TEXTURES_LIBRARY, true);
 
219
    homeView.setEnabled(HomeView.ActionType.SORT_HOME_FURNITURE_BY_CATALOG_ID, true);
 
220
    homeView.setEnabled(HomeView.ActionType.SORT_HOME_FURNITURE_BY_NAME, true);
 
221
    homeView.setEnabled(HomeView.ActionType.SORT_HOME_FURNITURE_BY_WIDTH, true);
 
222
    homeView.setEnabled(HomeView.ActionType.SORT_HOME_FURNITURE_BY_HEIGHT, true);
 
223
    homeView.setEnabled(HomeView.ActionType.SORT_HOME_FURNITURE_BY_DEPTH, true);
 
224
    homeView.setEnabled(HomeView.ActionType.SORT_HOME_FURNITURE_BY_X, true);
 
225
    homeView.setEnabled(HomeView.ActionType.SORT_HOME_FURNITURE_BY_Y, true);
 
226
    homeView.setEnabled(HomeView.ActionType.SORT_HOME_FURNITURE_BY_ELEVATION, true);
 
227
    homeView.setEnabled(HomeView.ActionType.SORT_HOME_FURNITURE_BY_ANGLE, true);
 
228
    homeView.setEnabled(HomeView.ActionType.SORT_HOME_FURNITURE_BY_LEVEL, true);
 
229
    homeView.setEnabled(HomeView.ActionType.SORT_HOME_FURNITURE_BY_COLOR, true);
 
230
    homeView.setEnabled(HomeView.ActionType.SORT_HOME_FURNITURE_BY_TEXTURE, true);
 
231
    homeView.setEnabled(HomeView.ActionType.SORT_HOME_FURNITURE_BY_MOVABILITY, true);
 
232
    homeView.setEnabled(HomeView.ActionType.SORT_HOME_FURNITURE_BY_TYPE, true);
 
233
    homeView.setEnabled(HomeView.ActionType.SORT_HOME_FURNITURE_BY_VISIBILITY, true);
 
234
    homeView.setEnabled(HomeView.ActionType.SORT_HOME_FURNITURE_BY_PRICE, true);
 
235
    homeView.setEnabled(HomeView.ActionType.SORT_HOME_FURNITURE_BY_VALUE_ADDED_TAX_PERCENTAGE, true);
 
236
    homeView.setEnabled(HomeView.ActionType.SORT_HOME_FURNITURE_BY_VALUE_ADDED_TAX, true);
 
237
    homeView.setEnabled(HomeView.ActionType.SORT_HOME_FURNITURE_BY_PRICE_VALUE_ADDED_TAX_INCLUDED, true);
 
238
    homeView.setEnabled(HomeView.ActionType.SORT_HOME_FURNITURE_BY_DESCENDING_ORDER, 
 
239
        this.home.getFurnitureSortedProperty() != null);
 
240
    homeView.setEnabled(HomeView.ActionType.DISPLAY_HOME_FURNITURE_CATALOG_ID, true); 
 
241
    homeView.setEnabled(HomeView.ActionType.DISPLAY_HOME_FURNITURE_NAME, true); 
 
242
    homeView.setEnabled(HomeView.ActionType.DISPLAY_HOME_FURNITURE_WIDTH, true); 
 
243
    homeView.setEnabled(HomeView.ActionType.DISPLAY_HOME_FURNITURE_DEPTH, true); 
 
244
    homeView.setEnabled(HomeView.ActionType.DISPLAY_HOME_FURNITURE_HEIGHT, true); 
 
245
    homeView.setEnabled(HomeView.ActionType.DISPLAY_HOME_FURNITURE_X, true); 
 
246
    homeView.setEnabled(HomeView.ActionType.DISPLAY_HOME_FURNITURE_Y, true); 
 
247
    homeView.setEnabled(HomeView.ActionType.DISPLAY_HOME_FURNITURE_ELEVATION, true); 
 
248
    homeView.setEnabled(HomeView.ActionType.DISPLAY_HOME_FURNITURE_ANGLE, true); 
 
249
    homeView.setEnabled(HomeView.ActionType.DISPLAY_HOME_FURNITURE_LEVEL, true); 
 
250
    homeView.setEnabled(HomeView.ActionType.DISPLAY_HOME_FURNITURE_COLOR, true); 
 
251
    homeView.setEnabled(HomeView.ActionType.DISPLAY_HOME_FURNITURE_TEXTURE, true); 
 
252
    homeView.setEnabled(HomeView.ActionType.DISPLAY_HOME_FURNITURE_MOVABLE, true); 
 
253
    homeView.setEnabled(HomeView.ActionType.DISPLAY_HOME_FURNITURE_DOOR_OR_WINDOW, true); 
 
254
    homeView.setEnabled(HomeView.ActionType.DISPLAY_HOME_FURNITURE_VISIBLE, true);
 
255
    homeView.setEnabled(HomeView.ActionType.DISPLAY_HOME_FURNITURE_PRICE, true);
 
256
    homeView.setEnabled(HomeView.ActionType.DISPLAY_HOME_FURNITURE_VALUE_ADDED_TAX_PERCENTAGE, true);
 
257
    homeView.setEnabled(HomeView.ActionType.DISPLAY_HOME_FURNITURE_VALUE_ADDED_TAX, true);
 
258
    homeView.setEnabled(HomeView.ActionType.DISPLAY_HOME_FURNITURE_PRICE_VALUE_ADDED_TAX_INCLUDED, true);
 
259
    homeView.setEnabled(HomeView.ActionType.SELECT, true);
 
260
    homeView.setEnabled(HomeView.ActionType.PAN, true);
 
261
    homeView.setEnabled(HomeView.ActionType.CREATE_WALLS, true);
 
262
    homeView.setEnabled(HomeView.ActionType.CREATE_ROOMS, true);
 
263
    homeView.setEnabled(HomeView.ActionType.CREATE_DIMENSION_LINES, true);
 
264
    homeView.setEnabled(HomeView.ActionType.CREATE_LABELS, true);
 
265
    homeView.setEnabled(HomeView.ActionType.LOCK_BASE_PLAN, true);
 
266
    homeView.setEnabled(HomeView.ActionType.UNLOCK_BASE_PLAN, true);
 
267
    homeView.setEnabled(HomeView.ActionType.MODIFY_COMPASS, true);
 
268
    Level selectedLevel = this.home.getSelectedLevel();
 
269
    enableBackgroungImageActions(homeView, selectedLevel != null
 
270
        ? selectedLevel.getBackgroundImage()
 
271
        : this.home.getBackgroundImage());
 
272
    homeView.setEnabled(HomeView.ActionType.ADD_LEVEL, true);
 
273
    List<Level> levels = this.home.getLevels();
 
274
    boolean homeContainsOneSelectedLevel = levels.size() > 1 && selectedLevel != null;
 
275
    homeView.setEnabled(HomeView.ActionType.MODIFY_LEVEL, homeContainsOneSelectedLevel);
 
276
    homeView.setEnabled(HomeView.ActionType.DELETE_LEVEL, homeContainsOneSelectedLevel);
 
277
    homeView.setEnabled(HomeView.ActionType.ZOOM_IN, true);
 
278
    homeView.setEnabled(HomeView.ActionType.ZOOM_OUT, true);
 
279
    homeView.setEnabled(HomeView.ActionType.EXPORT_TO_SVG, true); 
 
280
    homeView.setEnabled(HomeView.ActionType.VIEW_FROM_TOP, true);
 
281
    homeView.setEnabled(HomeView.ActionType.VIEW_FROM_OBSERVER, true);
 
282
    homeView.setEnabled(HomeView.ActionType.MODIFY_OBSERVER, this.home.getCamera() == this.home.getObserverCamera());
 
283
    homeView.setEnabled(HomeView.ActionType.STORE_POINT_OF_VIEW, true);
 
284
    homeView.setEnabled(HomeView.ActionType.DISPLAY_ALL_LEVELS, levels.size() > 1);
 
285
    homeView.setEnabled(HomeView.ActionType.DISPLAY_SELECTED_LEVEL, levels.size() > 1);
 
286
    homeView.setEnabled(HomeView.ActionType.DETACH_3D_VIEW, true);
 
287
    homeView.setEnabled(HomeView.ActionType.ATTACH_3D_VIEW, true);
 
288
    homeView.setEnabled(HomeView.ActionType.VIEW_FROM_OBSERVER, true);
 
289
    homeView.setEnabled(HomeView.ActionType.MODIFY_3D_ATTRIBUTES, true);
 
290
    homeView.setEnabled(HomeView.ActionType.CREATE_PHOTO, true);
 
291
    homeView.setEnabled(HomeView.ActionType.CREATE_VIDEO, true);
 
292
    homeView.setEnabled(HomeView.ActionType.EXPORT_TO_OBJ, true);
 
293
    homeView.setEnabled(HomeView.ActionType.HELP, true);
 
294
    homeView.setEnabled(HomeView.ActionType.ABOUT, true);
 
295
    homeView.setTransferEnabled(true);
 
296
  }
 
297
 
 
298
  /**
 
299
   * Returns the view associated with this controller.
 
300
   */
 
301
  public HomeView getView() {
 
302
    if (this.homeView == null) {
 
303
      this.homeView = this.viewFactory.createHomeView(this.home, this.preferences, this);
 
304
      enableDefaultActions(this.homeView);
 
305
      addListeners();
 
306
    }
 
307
    return this.homeView;
 
308
  }
 
309
 
 
310
  /**
 
311
   * Returns the content manager of this controller.
 
312
   */
 
313
  public ContentManager getContentManager() {
 
314
    return this.contentManager;
 
315
  }
 
316
 
 
317
  /**
 
318
   * Returns the furniture catalog controller managed by this controller.
 
319
   */
 
320
  public FurnitureCatalogController getFurnitureCatalogController() {
 
321
    // Create sub controller lazily only once it's needed
 
322
    if (this.furnitureCatalogController == null) {
 
323
      this.furnitureCatalogController = new FurnitureCatalogController(
 
324
          this.preferences.getFurnitureCatalog(), this.preferences, this.viewFactory, this.contentManager);
 
325
    }
 
326
    return this.furnitureCatalogController;
 
327
  }
 
328
 
 
329
  /**
 
330
   * Returns the furniture controller managed by this controller.
 
331
   */
 
332
  public FurnitureController getFurnitureController() {
 
333
    // Create sub controller lazily only once it's needed
 
334
    if (this.furnitureController == null) {
 
335
      this.furnitureController = new FurnitureController(
 
336
          this.home, this.preferences, this.viewFactory, this.contentManager, getUndoableEditSupport());
 
337
    }
 
338
    return this.furnitureController;
 
339
  }
 
340
 
 
341
  /**
 
342
   * Returns the controller of home plan.
 
343
   */
 
344
  public PlanController getPlanController() {
 
345
    // Create sub controller lazily only once it's needed
 
346
    if (this.planController == null) {
 
347
      this.planController = new PlanController(
 
348
          this.home, this.preferences, this.viewFactory, this.contentManager, getUndoableEditSupport());
 
349
    }
 
350
    return this.planController;
 
351
  }
 
352
 
 
353
  /**
 
354
   * Returns the controller of home 3D view.
 
355
   */
 
356
  public HomeController3D getHomeController3D() {
 
357
    // Create sub controller lazily only once it's needed
 
358
    if (this.homeController3D == null) {
 
359
      this.homeController3D = new HomeController3D(
 
360
          this.home, this.preferences, this.viewFactory, this.contentManager, getUndoableEditSupport());
 
361
    }
 
362
    return this.homeController3D;
 
363
  }
 
364
 
 
365
  /**
 
366
   * Returns the undoable edit support managed by this controller.
 
367
   */
 
368
  protected final UndoableEditSupport getUndoableEditSupport() {
 
369
    return this.undoSupport;
 
370
  }
 
371
  
 
372
  /**
 
373
   * Adds listeners that updates the enabled / disabled state of actions.
 
374
   */
 
375
  private void addListeners() {
 
376
    // Save preferences when they change
 
377
    this.preferences.getFurnitureCatalog().addFurnitureListener(
 
378
        new FurnitureCatalogChangeListener(this));
 
379
    this.preferences.getTexturesCatalog().addTexturesListener(
 
380
        new TexturesCatalogChangeListener(this));
 
381
    UserPreferencesPropertiesChangeListener listener = 
 
382
        new UserPreferencesPropertiesChangeListener(this);
 
383
    for (UserPreferences.Property property : UserPreferences.Property.values()) {
 
384
      this.preferences.addPropertyChangeListener(property, listener);
 
385
    }
 
386
      
 
387
    addCatalogSelectionListener();
 
388
    addHomeBackgroundImageListener();
 
389
    addNotUndoableModificationListeners();
 
390
    addHomeSelectionListener();
 
391
    addFurnitureSortListener();
 
392
    addUndoSupportListener();
 
393
    addHomeItemsListener();
 
394
    addLevelListeners();
 
395
    addPlanControllerListeners();
 
396
    addLanguageListener();
 
397
  }
 
398
 
 
399
  /**
 
400
   * Super class of catalog listeners that writes preferences each time a piece of furniture or a texture
 
401
   * is deleted or added in furniture or textures catalog.
 
402
   */
 
403
  private abstract static class UserPreferencesChangeListener {
 
404
    // Stores the currently writing preferences 
 
405
    private static Set<UserPreferences> writingPreferences = new HashSet<UserPreferences>();
 
406
    
 
407
    public void writePreferences(final HomeController controller) {
 
408
      if (!writingPreferences.contains(controller.preferences)) {
 
409
        writingPreferences.add(controller.preferences);
 
410
        // Write preferences later once all catalog modifications are notified 
 
411
        controller.getView().invokeLater(new Runnable() {
 
412
            public void run() {
 
413
              try {
 
414
                controller.preferences.write();
 
415
                writingPreferences.remove(controller.preferences);
 
416
              } catch (RecorderException ex) {
 
417
                controller.getView().showError(controller.preferences.getLocalizedString(
 
418
                    HomeController.class, "savePreferencesError"));
 
419
              }
 
420
            }
 
421
          });
 
422
      }
 
423
    }
 
424
  }
 
425
 
 
426
  /**
 
427
   * Furniture catalog listener that writes preferences each time a piece of furniture 
 
428
   * is deleted or added in furniture catalog. This listener is bound to this controller 
 
429
   * with a weak reference to avoid strong link between catalog and this controller.  
 
430
   */
 
431
  private static class FurnitureCatalogChangeListener extends UserPreferencesChangeListener 
 
432
                                                      implements CollectionListener<CatalogPieceOfFurniture> {
 
433
    private WeakReference<HomeController> homeController;
 
434
    
 
435
    public FurnitureCatalogChangeListener(HomeController homeController) {
 
436
      this.homeController = new WeakReference<HomeController>(homeController);
 
437
    }
 
438
    
 
439
    public void collectionChanged(CollectionEvent<CatalogPieceOfFurniture> ev) {
 
440
      // If controller was garbage collected, remove this listener from catalog
 
441
      final HomeController controller = this.homeController.get();
 
442
      if (controller == null) {
 
443
        ((FurnitureCatalog)ev.getSource()).removeFurnitureListener(this);
 
444
      } else {
 
445
        writePreferences(controller);
 
446
      }
 
447
    }
 
448
  }
 
449
 
 
450
  /**
 
451
   * Textures catalog listener that writes preferences each time a texture 
 
452
   * is deleted or added in textures catalog. This listener is bound to this controller 
 
453
   * with a weak reference to avoid strong link between catalog and this controller.  
 
454
   */
 
455
  private static class TexturesCatalogChangeListener extends UserPreferencesChangeListener
 
456
                                                     implements CollectionListener<CatalogTexture> { 
 
457
    private WeakReference<HomeController> homeController;
 
458
    
 
459
    public TexturesCatalogChangeListener(HomeController homeController) {
 
460
      this.homeController = new WeakReference<HomeController>(homeController);
 
461
    }
 
462
    
 
463
    public void collectionChanged(CollectionEvent<CatalogTexture> ev) {
 
464
      // If controller was garbage collected, remove this listener from catalog
 
465
      final HomeController controller = this.homeController.get();
 
466
      if (controller == null) {
 
467
        ((TexturesCatalog)ev.getSource()).removeTexturesListener(this);
 
468
      } else {
 
469
        writePreferences(controller);
 
470
      }
 
471
    }
 
472
  }
 
473
 
 
474
  /**
 
475
   * Properties listener that writes preferences each time the value of one of its properties changes. 
 
476
   * This listener is bound to this controller with a weak reference to avoid strong link 
 
477
   * between catalog and this controller.  
 
478
   */
 
479
  private static class UserPreferencesPropertiesChangeListener extends UserPreferencesChangeListener
 
480
                                                               implements PropertyChangeListener { 
 
481
    private WeakReference<HomeController> homeController;
 
482
    
 
483
    public UserPreferencesPropertiesChangeListener(HomeController homeController) {
 
484
      this.homeController = new WeakReference<HomeController>(homeController);
 
485
    }
 
486
    
 
487
    public void propertyChange(PropertyChangeEvent ev) {
 
488
      // If controller was garbage collected, remove this listener from catalog
 
489
      final HomeController controller = this.homeController.get();
 
490
      if (controller == null) {
 
491
        ((UserPreferences)ev.getSource()).removePropertyChangeListener(
 
492
            UserPreferences.Property.valueOf(ev.getPropertyName()), this);
 
493
      } else {
 
494
        writePreferences(controller);
 
495
      }
 
496
    }
 
497
  }
 
498
 
 
499
  /**
 
500
   * Adds a selection listener to catalog that enables / disables Add Furniture action.
 
501
   */
 
502
  private void addCatalogSelectionListener() {
 
503
    getFurnitureCatalogController().addSelectionListener(new SelectionListener() {
 
504
          public void selectionChanged(SelectionEvent ev) {
 
505
            enableActionsBoundToSelection();
 
506
          }
 
507
        });
 
508
  }
 
509
 
 
510
  /**
 
511
   * Adds a property change listener to <code>preferences</code> to update
 
512
   * undo and redo presentation names when preferred language changes.
 
513
   */
 
514
  private void addLanguageListener() {
 
515
    this.preferences.addPropertyChangeListener(UserPreferences.Property.LANGUAGE, 
 
516
        new LanguageChangeListener(this));
 
517
  }
 
518
 
 
519
  /**
 
520
   * Preferences property listener bound to this component with a weak reference to avoid
 
521
   * strong link between preferences and this component.  
 
522
   */
 
523
  private static class LanguageChangeListener implements PropertyChangeListener {
 
524
    private WeakReference<HomeController> homeController;
 
525
 
 
526
    public LanguageChangeListener(HomeController homeController) {
 
527
      this.homeController = new WeakReference<HomeController>(homeController);
 
528
    }
 
529
    
 
530
    public void propertyChange(PropertyChangeEvent ev) {
 
531
      // If home pane was garbage collected, remove this listener from preferences
 
532
      HomeController homeController = this.homeController.get();
 
533
      if (homeController == null) {
 
534
        ((UserPreferences)ev.getSource()).removePropertyChangeListener(
 
535
            UserPreferences.Property.LANGUAGE, this);
 
536
      } else {
 
537
        // Update undo and redo name
 
538
        homeController.getView().setUndoRedoName(
 
539
            homeController.undoManager.canUndo() 
 
540
                ? homeController.undoManager.getUndoPresentationName()
 
541
                : null,
 
542
            homeController.undoManager.canRedo() 
 
543
                ? homeController.undoManager.getRedoPresentationName()
 
544
                : null);
 
545
      }
 
546
    }
 
547
  }
 
548
  
 
549
  /**
 
550
   *  Adds a selection listener to home that enables / disables actions on selection.
 
551
   */
 
552
  private void addHomeSelectionListener() {
 
553
    if (this.home != null) {
 
554
      this.home.addSelectionListener(new SelectionListener() {
 
555
        public void selectionChanged(SelectionEvent ev) {
 
556
          enableActionsBoundToSelection();
 
557
        }
 
558
      });
 
559
    }
 
560
  }
 
561
 
 
562
  /**
 
563
   *  Adds a property change listener to home that enables / disables sort order action.
 
564
   */
 
565
  private void addFurnitureSortListener() {
 
566
    if (this.home != null) {
 
567
      this.home.addPropertyChangeListener(Home.Property.FURNITURE_SORTED_PROPERTY, 
 
568
        new PropertyChangeListener() {
 
569
          public void propertyChange(PropertyChangeEvent ev) {
 
570
            getView().setEnabled(HomeView.ActionType.SORT_HOME_FURNITURE_BY_DESCENDING_ORDER, 
 
571
                ev.getNewValue() != null);
 
572
          }
 
573
        });
 
574
    }
 
575
  }
 
576
 
 
577
  /**
 
578
   *  Adds a property change listener to home that enables / disables background image actions.
 
579
   */
 
580
  private void addHomeBackgroundImageListener() {
 
581
    if (this.home != null) {
 
582
      this.home.addPropertyChangeListener(Home.Property.BACKGROUND_IMAGE, 
 
583
          new PropertyChangeListener() {
 
584
            public void propertyChange(PropertyChangeEvent ev) {
 
585
              enableBackgroungImageActions(getView(), (BackgroundImage)ev.getNewValue());
 
586
            }
 
587
          });
 
588
    }
 
589
  }
 
590
 
 
591
  /**
 
592
   * Enables background image actions.
 
593
   */
 
594
  private void enableBackgroungImageActions(HomeView homeView, BackgroundImage backgroundImage) {
 
595
    boolean homeHasBackgroundImage = backgroundImage != null;
 
596
    getView().setEnabled(HomeView.ActionType.IMPORT_BACKGROUND_IMAGE, !homeHasBackgroundImage);
 
597
    getView().setEnabled(HomeView.ActionType.MODIFY_BACKGROUND_IMAGE, homeHasBackgroundImage);
 
598
    getView().setEnabled(HomeView.ActionType.HIDE_BACKGROUND_IMAGE, 
 
599
        homeHasBackgroundImage && backgroundImage.isVisible());
 
600
    getView().setEnabled(HomeView.ActionType.SHOW_BACKGROUND_IMAGE, 
 
601
        homeHasBackgroundImage && !backgroundImage.isVisible());
 
602
    getView().setEnabled(HomeView.ActionType.DELETE_BACKGROUND_IMAGE, homeHasBackgroundImage);
 
603
  }
 
604
 
 
605
  /**
 
606
   * Adds listeners to track property changes that are not undoable.
 
607
   */
 
608
  private void addNotUndoableModificationListeners() {
 
609
    if (this.home != null) {
 
610
      final PropertyChangeListener notUndoableModificationListener = new PropertyChangeListener() {
 
611
          public void propertyChange(PropertyChangeEvent ev) {
 
612
            notUndoableModifications = true;
 
613
            home.setModified(true);
 
614
          }
 
615
        };
 
616
      this.home.addPropertyChangeListener(Home.Property.STORED_CAMERAS, notUndoableModificationListener);
 
617
      this.home.getEnvironment().addPropertyChangeListener(HomeEnvironment.Property.OBSERVER_CAMERA_ELEVATION_ADJUSTED, notUndoableModificationListener);
 
618
      this.home.getEnvironment().addPropertyChangeListener(HomeEnvironment.Property.VIDEO_WIDTH, notUndoableModificationListener);
 
619
      this.home.getEnvironment().addPropertyChangeListener(HomeEnvironment.Property.VIDEO_ASPECT_RATIO, notUndoableModificationListener);
 
620
      this.home.getEnvironment().addPropertyChangeListener(HomeEnvironment.Property.VIDEO_FRAME_RATE, notUndoableModificationListener);
 
621
      this.home.getEnvironment().addPropertyChangeListener(HomeEnvironment.Property.VIDEO_QUALITY, notUndoableModificationListener);
 
622
      this.home.getEnvironment().addPropertyChangeListener(HomeEnvironment.Property.VIDEO_CAMERA_PATH, notUndoableModificationListener);
 
623
      this.home.getEnvironment().addPropertyChangeListener(HomeEnvironment.Property.CEILING_LIGHT_COLOR, notUndoableModificationListener);
 
624
      this.home.getEnvironment().addPropertyChangeListener(HomeEnvironment.Property.PHOTO_QUALITY, notUndoableModificationListener);
 
625
      this.home.getEnvironment().addPropertyChangeListener(HomeEnvironment.Property.PHOTO_ASPECT_RATIO, notUndoableModificationListener);
 
626
      PropertyChangeListener photoSizeModificationListener = new PropertyChangeListener() {
 
627
          public void propertyChange(PropertyChangeEvent ev) {
 
628
            if (home.getEnvironment().getPhotoAspectRatio() != AspectRatio.VIEW_3D_RATIO) {
 
629
              // Ignore photo size modification with 3D view aspect ratio since it can change for various reasons
 
630
              notUndoableModificationListener.propertyChange(ev);
 
631
            }
 
632
          }
 
633
        };
 
634
      this.home.getEnvironment().addPropertyChangeListener(HomeEnvironment.Property.PHOTO_WIDTH, photoSizeModificationListener);
 
635
      this.home.getEnvironment().addPropertyChangeListener(HomeEnvironment.Property.PHOTO_HEIGHT, photoSizeModificationListener);
 
636
      PropertyChangeListener timeOrLensModificationListener = new PropertyChangeListener() {
 
637
          public void propertyChange(PropertyChangeEvent ev) {
 
638
            if (ev.getPropertyName().equals(Camera.Property.TIME.name())
 
639
                || ev.getPropertyName().equals(Camera.Property.LENS.name())) {
 
640
              notUndoableModificationListener.propertyChange(ev);
 
641
            }
 
642
          }
 
643
        };
 
644
      this.home.getObserverCamera().addPropertyChangeListener(timeOrLensModificationListener);
 
645
      this.home.getTopCamera().addPropertyChangeListener(timeOrLensModificationListener);
 
646
    }
 
647
  }
 
648
  
 
649
  /**
 
650
   * Enables or disables action bound to selection. 
 
651
   * This method will be called when selection in plan or in catalog changes and when 
 
652
   * focused component or modification state in plan changes. 
 
653
   */
 
654
  protected void enableActionsBoundToSelection() {
 
655
    boolean modificationState = getPlanController().isModificationState();
 
656
    
 
657
    // Search if catalog selection contains at least one piece
 
658
    List<CatalogPieceOfFurniture> catalogSelectedItems = 
 
659
        getFurnitureCatalogController().getSelectedFurniture();    
 
660
    boolean catalogSelectionContainsFurniture = !catalogSelectedItems.isEmpty();
 
661
    boolean catalogSelectionContainsOneModifiablePiece = catalogSelectedItems.size() == 1
 
662
        && catalogSelectedItems.get(0).isModifiable();
 
663
    
 
664
    // Search if home selection contains at least one piece, one wall or one dimension line
 
665
    List<Selectable> selectedItems = this.home.getSelectedItems();
 
666
    boolean homeSelectionContainsDeletableItems = false;
 
667
    boolean homeSelectionContainsFurniture = false;
 
668
    boolean homeSelectionContainsDeletableFurniture = false;
 
669
    boolean homeSelectionContainsOneCopiableItemOrMore = false;
 
670
    boolean homeSelectionContainsTwoMovablePiecesOfFurnitureOrMore = false;
 
671
    boolean homeSelectionContainsThreeMovablePiecesOfFurnitureOrMore = false;
 
672
    boolean homeSelectionContainsFurnitureGroup = false;
 
673
    boolean homeSelectionContainsWalls = false;
 
674
    boolean homeSelectionContainsRooms = false;
 
675
    boolean homeSelectionContainsOneWall = false;
 
676
    boolean homeSelectionContainsOneLabel = false;
 
677
    boolean homeSelectionContainsItemsWithText = false;
 
678
    boolean homeSelectionContainsCompass = false;
 
679
    FurnitureController furnitureController = getFurnitureController();
 
680
    if (!modificationState) {
 
681
      for (Selectable item : selectedItems) {
 
682
        if (getPlanController().isItemDeletable(item)) {
 
683
          homeSelectionContainsDeletableItems = true;
 
684
          break;
 
685
        }
 
686
      }
 
687
      List<HomePieceOfFurniture> selectedFurniture = Home.getFurnitureSubList(selectedItems);
 
688
      homeSelectionContainsFurniture = !selectedFurniture.isEmpty();
 
689
      for (HomePieceOfFurniture piece : selectedFurniture) {
 
690
        if (furnitureController.isPieceOfFurnitureDeletable(piece)) {
 
691
          homeSelectionContainsDeletableFurniture = true;
 
692
          break;
 
693
        }
 
694
      }
 
695
      for (HomePieceOfFurniture piece : selectedFurniture) {
 
696
        if (piece instanceof HomeFurnitureGroup) {
 
697
          homeSelectionContainsFurnitureGroup = true;
 
698
          break;
 
699
        }
 
700
      }
 
701
      int movablePiecesOfFurnitureCount = 0;
 
702
      for (HomePieceOfFurniture piece : selectedFurniture) {
 
703
        if (furnitureController.isPieceOfFurnitureMovable(piece)) {
 
704
          movablePiecesOfFurnitureCount++;
 
705
          if (movablePiecesOfFurnitureCount >= 2) {
 
706
            homeSelectionContainsTwoMovablePiecesOfFurnitureOrMore = true;
 
707
          } 
 
708
          if (movablePiecesOfFurnitureCount >= 3) {
 
709
            homeSelectionContainsThreeMovablePiecesOfFurnitureOrMore = true;
 
710
            break;
 
711
          }
 
712
        }
 
713
      }
 
714
      List<Wall> selectedWalls = Home.getWallsSubList(selectedItems);
 
715
      homeSelectionContainsWalls = !selectedWalls.isEmpty();
 
716
      homeSelectionContainsOneWall = selectedWalls.size() == 1;
 
717
      homeSelectionContainsRooms = !Home.getRoomsSubList(selectedItems).isEmpty();
 
718
      boolean homeSelectionContainsDimensionLines = !Home.getDimensionLinesSubList(selectedItems).isEmpty();
 
719
      final List<Label> selectedLabels = Home.getLabelsSubList(selectedItems);
 
720
      boolean homeSelectionContainsLabels = !selectedLabels.isEmpty();
 
721
      homeSelectionContainsCompass = selectedItems.contains(this.home.getCompass());
 
722
      homeSelectionContainsOneLabel = selectedLabels.size() == 1;
 
723
      homeSelectionContainsOneCopiableItemOrMore = 
 
724
          homeSelectionContainsFurniture || homeSelectionContainsWalls 
 
725
          || homeSelectionContainsRooms || homeSelectionContainsDimensionLines
 
726
          || homeSelectionContainsLabels || homeSelectionContainsCompass; 
 
727
      homeSelectionContainsItemsWithText = 
 
728
          homeSelectionContainsFurniture || homeSelectionContainsRooms 
 
729
          || homeSelectionContainsDimensionLines || homeSelectionContainsLabels;
 
730
    }
 
731
 
 
732
    HomeView view = getView();
 
733
    if (this.focusedView == getFurnitureCatalogController().getView()) {
 
734
      view.setEnabled(HomeView.ActionType.COPY,
 
735
          !modificationState && catalogSelectionContainsFurniture);
 
736
      view.setEnabled(HomeView.ActionType.CUT, false);
 
737
      view.setEnabled(HomeView.ActionType.DELETE, false);
 
738
      for (CatalogPieceOfFurniture piece : catalogSelectedItems) {
 
739
        if (piece.isModifiable()) {
 
740
          // Only modifiable catalog furniture may be deleted
 
741
          view.setEnabled(HomeView.ActionType.DELETE, true);
 
742
          break;
 
743
        }
 
744
      }
 
745
    } else if (this.focusedView == furnitureController.getView()) {
 
746
      view.setEnabled(HomeView.ActionType.COPY, homeSelectionContainsFurniture);
 
747
      view.setEnabled(HomeView.ActionType.CUT, homeSelectionContainsDeletableFurniture);
 
748
      view.setEnabled(HomeView.ActionType.DELETE, homeSelectionContainsDeletableFurniture);
 
749
    } else if (this.focusedView == getPlanController().getView()) {
 
750
      view.setEnabled(HomeView.ActionType.COPY, homeSelectionContainsOneCopiableItemOrMore);
 
751
      view.setEnabled(HomeView.ActionType.CUT, homeSelectionContainsDeletableItems);
 
752
      view.setEnabled(HomeView.ActionType.DELETE, homeSelectionContainsDeletableItems);
 
753
    } else {
 
754
      view.setEnabled(HomeView.ActionType.COPY, false);
 
755
      view.setEnabled(HomeView.ActionType.CUT, false);
 
756
      view.setEnabled(HomeView.ActionType.DELETE, false);
 
757
    }
 
758
 
 
759
    view.setEnabled(HomeView.ActionType.ADD_HOME_FURNITURE, catalogSelectionContainsFurniture);
 
760
    // In creation mode all actions bound to selection are disabled
 
761
    view.setEnabled(HomeView.ActionType.DELETE_HOME_FURNITURE,
 
762
        homeSelectionContainsDeletableFurniture);
 
763
    view.setEnabled(HomeView.ActionType.DELETE_SELECTION,
 
764
        (catalogSelectionContainsFurniture
 
765
            && this.focusedView == getFurnitureCatalogController().getView())
 
766
        || (homeSelectionContainsDeletableItems 
 
767
            && (this.focusedView == furnitureController.getView()
 
768
                || this.focusedView == getPlanController().getView()
 
769
                || this.focusedView == getHomeController3D().getView())));
 
770
    view.setEnabled(HomeView.ActionType.MODIFY_FURNITURE,
 
771
        (catalogSelectionContainsOneModifiablePiece
 
772
             && this.focusedView == getFurnitureCatalogController().getView())
 
773
        || (homeSelectionContainsFurniture 
 
774
             && (this.focusedView == furnitureController.getView()
 
775
                 || this.focusedView == getPlanController().getView()
 
776
                 || this.focusedView == getHomeController3D().getView())));
 
777
    view.setEnabled(HomeView.ActionType.MODIFY_WALL,
 
778
        homeSelectionContainsWalls);
 
779
    view.setEnabled(HomeView.ActionType.REVERSE_WALL_DIRECTION,
 
780
        homeSelectionContainsWalls);
 
781
    view.setEnabled(HomeView.ActionType.SPLIT_WALL,
 
782
        homeSelectionContainsOneWall);
 
783
    view.setEnabled(HomeView.ActionType.MODIFY_ROOM,
 
784
        homeSelectionContainsRooms);
 
785
    view.setEnabled(HomeView.ActionType.MODIFY_LABEL,
 
786
        homeSelectionContainsOneLabel);
 
787
    view.setEnabled(HomeView.ActionType.TOGGLE_BOLD_STYLE, 
 
788
        homeSelectionContainsItemsWithText);
 
789
    view.setEnabled(HomeView.ActionType.TOGGLE_ITALIC_STYLE, 
 
790
        homeSelectionContainsItemsWithText);
 
791
    view.setEnabled(HomeView.ActionType.INCREASE_TEXT_SIZE, 
 
792
        homeSelectionContainsItemsWithText);
 
793
    view.setEnabled(HomeView.ActionType.DECREASE_TEXT_SIZE, 
 
794
        homeSelectionContainsItemsWithText);
 
795
    view.setEnabled(HomeView.ActionType.ALIGN_FURNITURE_ON_TOP,
 
796
        homeSelectionContainsTwoMovablePiecesOfFurnitureOrMore);
 
797
    view.setEnabled(HomeView.ActionType.ALIGN_FURNITURE_ON_BOTTOM,
 
798
        homeSelectionContainsTwoMovablePiecesOfFurnitureOrMore);
 
799
    view.setEnabled(HomeView.ActionType.ALIGN_FURNITURE_ON_LEFT,
 
800
        homeSelectionContainsTwoMovablePiecesOfFurnitureOrMore);
 
801
    view.setEnabled(HomeView.ActionType.ALIGN_FURNITURE_ON_RIGHT,
 
802
        homeSelectionContainsTwoMovablePiecesOfFurnitureOrMore);
 
803
    view.setEnabled(HomeView.ActionType.ALIGN_FURNITURE_ON_FRONT_SIDE,
 
804
        homeSelectionContainsTwoMovablePiecesOfFurnitureOrMore);
 
805
    view.setEnabled(HomeView.ActionType.ALIGN_FURNITURE_ON_BACK_SIDE,
 
806
        homeSelectionContainsTwoMovablePiecesOfFurnitureOrMore);
 
807
    view.setEnabled(HomeView.ActionType.ALIGN_FURNITURE_ON_LEFT_SIDE,
 
808
        homeSelectionContainsTwoMovablePiecesOfFurnitureOrMore);
 
809
    view.setEnabled(HomeView.ActionType.ALIGN_FURNITURE_ON_RIGHT_SIDE,
 
810
        homeSelectionContainsTwoMovablePiecesOfFurnitureOrMore);
 
811
    view.setEnabled(HomeView.ActionType.ALIGN_FURNITURE_SIDE_BY_SIDE,
 
812
        homeSelectionContainsTwoMovablePiecesOfFurnitureOrMore);
 
813
    view.setEnabled(HomeView.ActionType.DISTRIBUTE_FURNITURE_HORIZONTALLY,
 
814
        homeSelectionContainsThreeMovablePiecesOfFurnitureOrMore);
 
815
    view.setEnabled(HomeView.ActionType.DISTRIBUTE_FURNITURE_VERTICALLY,
 
816
        homeSelectionContainsThreeMovablePiecesOfFurnitureOrMore);
 
817
    view.setEnabled(HomeView.ActionType.GROUP_FURNITURE,
 
818
        homeSelectionContainsTwoMovablePiecesOfFurnitureOrMore);
 
819
    view.setEnabled(HomeView.ActionType.UNGROUP_FURNITURE,
 
820
        homeSelectionContainsFurnitureGroup);
 
821
  }
 
822
 
 
823
  /**
 
824
   * Enables clipboard paste action if clipboard isn't empty.
 
825
   */
 
826
  public void enablePasteAction() {
 
827
    HomeView view = getView();
 
828
    if (this.focusedView == getFurnitureController().getView()
 
829
        || this.focusedView == getPlanController().getView()) {
 
830
      view.setEnabled(HomeView.ActionType.PASTE,
 
831
          !getPlanController().isModificationState() && !view.isClipboardEmpty());
 
832
    } else {
 
833
      view.setEnabled(HomeView.ActionType.PASTE, false);
 
834
    }
 
835
  }
 
836
 
 
837
  /**
 
838
   * Enables select all action if home isn't empty.
 
839
   */
 
840
  protected void enableSelectAllAction() {
 
841
    HomeView view = getView();
 
842
    boolean modificationState = getPlanController().isModificationState();
 
843
    if (this.focusedView == getFurnitureController().getView()) {
 
844
      view.setEnabled(HomeView.ActionType.SELECT_ALL,
 
845
          !modificationState 
 
846
          && this.home.getFurniture().size() > 0);
 
847
    } else if (this.focusedView == getPlanController().getView()
 
848
               || this.focusedView == getHomeController3D().getView()) {
 
849
      boolean homeContainsOneSelectableItemOrMore = !this.home.isEmpty()
 
850
          || this.home.getCompass().isVisible();
 
851
      view.setEnabled(HomeView.ActionType.SELECT_ALL,
 
852
          !modificationState && homeContainsOneSelectableItemOrMore);
 
853
    } else {
 
854
      view.setEnabled(HomeView.ActionType.SELECT_ALL, false);
 
855
    }
 
856
  }
 
857
 
 
858
  /**
 
859
   * Enables zoom actions depending on current scale.
 
860
   */
 
861
  private void enableZoomActions() {
 
862
    PlanController planController = getPlanController();
 
863
    float scale = planController.getScale();
 
864
    HomeView view = getView();
 
865
    view.setEnabled(HomeView.ActionType.ZOOM_IN, scale < planController.getMaximumScale());
 
866
    view.setEnabled(HomeView.ActionType.ZOOM_OUT, scale > planController.getMinimumScale());    
 
867
  }
 
868
  
 
869
  /**
 
870
   * Adds undoable edit listener to undo support that enables Undo action.
 
871
   */
 
872
  private void addUndoSupportListener() {
 
873
    getUndoableEditSupport().addUndoableEditListener(
 
874
      new UndoableEditListener () {
 
875
        public void undoableEditHappened(UndoableEditEvent ev) {
 
876
          HomeView view = getView();
 
877
          view.setEnabled(HomeView.ActionType.UNDO, 
 
878
              !getPlanController().isModificationState());
 
879
          view.setEnabled(HomeView.ActionType.REDO, false);
 
880
          view.setUndoRedoName(ev.getEdit().getUndoPresentationName(), null);
 
881
          saveUndoLevel++;
 
882
          home.setModified(true);
 
883
        }
 
884
      });
 
885
   home.addPropertyChangeListener(Home.Property.MODIFIED, new PropertyChangeListener() {
 
886
      public void propertyChange(PropertyChangeEvent ev) {
 
887
        if (!home.isModified()) {
 
888
          // Change undo level and modification flag if home is set as unmodified
 
889
          saveUndoLevel = 0;
 
890
          notUndoableModifications = false;
 
891
        }
 
892
      }
 
893
    });
 
894
  }
 
895
 
 
896
  /**
 
897
   * Adds a furniture listener to home that enables / disables actions on furniture list change.
 
898
   */
 
899
  @SuppressWarnings("unchecked")
 
900
  private void addHomeItemsListener() {
 
901
    CollectionListener homeItemsListener = 
 
902
        new CollectionListener() {
 
903
          public void collectionChanged(CollectionEvent ev) {
 
904
            if (ev.getType() == CollectionEvent.Type.ADD 
 
905
                || ev.getType() == CollectionEvent.Type.DELETE) {
 
906
              enableSelectAllAction();
 
907
            }
 
908
          }
 
909
        };
 
910
    this.home.addFurnitureListener((CollectionListener<HomePieceOfFurniture>)homeItemsListener);
 
911
    this.home.addWallsListener((CollectionListener<Wall>)homeItemsListener);
 
912
    this.home.addRoomsListener((CollectionListener<Room>)homeItemsListener);
 
913
    this.home.addDimensionLinesListener((CollectionListener<DimensionLine>)homeItemsListener);
 
914
    this.home.addLabelsListener((CollectionListener<Label>)homeItemsListener);
 
915
    this.home.getCompass().addPropertyChangeListener(new PropertyChangeListener() {
 
916
        public void propertyChange(PropertyChangeEvent ev) {
 
917
          if (Compass.Property.VISIBLE.equals(ev.getPropertyName())) {
 
918
            enableSelectAllAction();
 
919
          }
 
920
        }
 
921
      });
 
922
    this.home.addPropertyChangeListener(Home.Property.CAMERA, new PropertyChangeListener() {
 
923
        public void propertyChange(PropertyChangeEvent ev) {
 
924
          getView().setEnabled(HomeView.ActionType.MODIFY_OBSERVER, home.getCamera() == home.getObserverCamera());
 
925
        }
 
926
      });
 
927
  }
 
928
 
 
929
  /**
 
930
   * Adds a property change listener to home to
 
931
   * enable/disable authorized actions according to selected level.
 
932
   */
 
933
  private void addLevelListeners() {
 
934
    final PropertyChangeListener selectedLevelListener = new PropertyChangeListener() {
 
935
        public void propertyChange(PropertyChangeEvent ev) {
 
936
          // Keep in selection only items that are at this level
 
937
          List<Selectable> selectedItemsAtLevel = new ArrayList<Selectable>();
 
938
          Level selectedLevel = home.getSelectedLevel();
 
939
          for (Selectable item : home.getSelectedItems()) {
 
940
            if (!(item instanceof Elevatable)
 
941
                || ((Elevatable)item).isAtLevel(selectedLevel)) {
 
942
              selectedItemsAtLevel.add(item);
 
943
            }
 
944
          }
 
945
          home.setSelectedItems(selectedItemsAtLevel);
 
946
          enableBackgroungImageActions(getView(), selectedLevel == null 
 
947
              ? home.getBackgroundImage()
 
948
              : selectedLevel.getBackgroundImage());
 
949
          List<Level> levels = home.getLevels();
 
950
          boolean homeContainsOneSelectedLevel = levels.size() > 1 && selectedLevel != null;
 
951
          getView().setEnabled(HomeView.ActionType.MODIFY_LEVEL, homeContainsOneSelectedLevel);
 
952
          getView().setEnabled(HomeView.ActionType.DELETE_LEVEL, homeContainsOneSelectedLevel);
 
953
          getView().setEnabled(HomeView.ActionType.DISPLAY_ALL_LEVELS, levels.size() > 1);
 
954
          getView().setEnabled(HomeView.ActionType.DISPLAY_SELECTED_LEVEL, levels.size() > 1);
 
955
        }
 
956
      };
 
957
    this.home.addPropertyChangeListener(Home.Property.SELECTED_LEVEL, selectedLevelListener);
 
958
    final PropertyChangeListener backgroundImageChangeListener = new PropertyChangeListener() {
 
959
        public void propertyChange(PropertyChangeEvent ev) {
 
960
          if (Level.Property.BACKGROUND_IMAGE.name().equals(ev.getPropertyName())) {
 
961
            enableBackgroungImageActions(getView(), (BackgroundImage)ev.getNewValue());
 
962
          }
 
963
        }
 
964
      };
 
965
    for (Level level : home.getLevels()) {
 
966
      level.addPropertyChangeListener(backgroundImageChangeListener);
 
967
    }
 
968
    this.home.addLevelsListener(new CollectionListener<Level>() {
 
969
        public void collectionChanged(CollectionEvent<Level> ev) {
 
970
          switch (ev.getType()) {
 
971
            case ADD :
 
972
              home.setSelectedLevel(ev.getItem());
 
973
              ev.getItem().addPropertyChangeListener(backgroundImageChangeListener);
 
974
              break;
 
975
            case DELETE :
 
976
              selectedLevelListener.propertyChange(null);
 
977
              ev.getItem().removePropertyChangeListener(backgroundImageChangeListener);
 
978
              break;
 
979
          }
 
980
        }
 
981
      });
 
982
  }
 
983
 
 
984
  /**
 
985
   * Adds a property change listener to plan controller to 
 
986
   * enable/disable authorized actions according to its modification state and the plan scale.
 
987
   */
 
988
  private void addPlanControllerListeners() {
 
989
    getPlanController().addPropertyChangeListener(PlanController.Property.MODIFICATION_STATE, 
 
990
        new PropertyChangeListener() {
 
991
          public void propertyChange(PropertyChangeEvent ev) {
 
992
            enableActionsBoundToSelection();
 
993
            enableSelectAllAction();
 
994
            HomeView view = getView();
 
995
            if (getPlanController().isModificationState()) {
 
996
              view.setEnabled(HomeView.ActionType.PASTE, false);
 
997
              view.setEnabled(HomeView.ActionType.UNDO, false);
 
998
              view.setEnabled(HomeView.ActionType.REDO, false);
 
999
            } else {
 
1000
              enablePasteAction();
 
1001
              view.setEnabled(HomeView.ActionType.UNDO, undoManager.canUndo());
 
1002
              view.setEnabled(HomeView.ActionType.REDO, undoManager.canRedo());
 
1003
            }
 
1004
          }
 
1005
        });
 
1006
    getPlanController().addPropertyChangeListener(PlanController.Property.SCALE, 
 
1007
        new PropertyChangeListener() {
 
1008
          public void propertyChange(PropertyChangeEvent ev) {
 
1009
            enableZoomActions();
 
1010
          }
 
1011
        });
 
1012
  }
 
1013
  
 
1014
  /**
 
1015
   * Adds the selected furniture in catalog to home and selects it.  
 
1016
   */
 
1017
  public void addHomeFurniture() {
 
1018
    // Use automatically selection mode  
 
1019
    getPlanController().setMode(PlanController.Mode.SELECTION);
 
1020
    List<CatalogPieceOfFurniture> selectedFurniture = 
 
1021
      getFurnitureCatalogController().getSelectedFurniture();
 
1022
    if (!selectedFurniture.isEmpty()) {
 
1023
      List<HomePieceOfFurniture> newFurniture = 
 
1024
          new ArrayList<HomePieceOfFurniture>();
 
1025
      for (CatalogPieceOfFurniture piece : selectedFurniture) {
 
1026
        HomePieceOfFurniture homePiece = getFurnitureController().createHomePieceOfFurniture(piece);
 
1027
        // If magnetism is enabled, adjust piece size and elevation
 
1028
        if (this.preferences.isMagnetismEnabled()) {
 
1029
          if (homePiece.isResizable()) {
 
1030
            homePiece.setWidth(this.preferences.getLengthUnit().getMagnetizedLength(homePiece.getWidth(), 0.1f));
 
1031
            homePiece.setDepth(this.preferences.getLengthUnit().getMagnetizedLength(homePiece.getDepth(), 0.1f));
 
1032
            homePiece.setHeight(this.preferences.getLengthUnit().getMagnetizedLength(homePiece.getHeight(), 0.1f));
 
1033
          }
 
1034
          homePiece.setElevation(this.preferences.getLengthUnit().getMagnetizedLength(homePiece.getElevation(), 0.1f));
 
1035
        }
 
1036
        newFurniture.add(homePiece);
 
1037
      }
 
1038
      // Add newFurniture to home with furnitureController
 
1039
      getFurnitureController().addFurniture(newFurniture);
 
1040
    }
 
1041
  }
 
1042
  
 
1043
  /**
 
1044
   * Modifies the selected furniture of the focused view.  
 
1045
   */
 
1046
  public void modifySelectedFurniture() {
 
1047
    if (this.focusedView == getFurnitureCatalogController().getView()) {
 
1048
      getFurnitureCatalogController().modifySelectedFurniture();
 
1049
    } else if (this.focusedView == getFurnitureController().getView()
 
1050
               || this.focusedView == getPlanController().getView()
 
1051
               || this.focusedView == getHomeController3D().getView()) {
 
1052
      getFurnitureController().modifySelectedFurniture();
 
1053
    }    
 
1054
  }
 
1055
  
 
1056
  /**
 
1057
   * Imports a language library chosen by the user.  
 
1058
   */
 
1059
  public void importLanguageLibrary() {
 
1060
    getView().invokeLater(new Runnable() {
 
1061
        public void run() {
 
1062
          final String languageLibraryName = getView().showImportLanguageLibraryDialog();
 
1063
          if (languageLibraryName != null) {
 
1064
            importLanguageLibrary(languageLibraryName);
 
1065
          }
 
1066
        }
 
1067
      });
 
1068
  }
 
1069
 
 
1070
  /**
 
1071
   * Imports a given language library. 
 
1072
   */
 
1073
  public void importLanguageLibrary(String languageLibraryName) {
 
1074
    try {
 
1075
      if (!this.preferences.languageLibraryExists(languageLibraryName) 
 
1076
          || getView().confirmReplaceLanguageLibrary(languageLibraryName)) {
 
1077
        this.preferences.addLanguageLibrary(languageLibraryName);
 
1078
      }
 
1079
    } catch (RecorderException ex) {
 
1080
      String message = this.preferences.getLocalizedString(HomeController.class, 
 
1081
          "importLanguageLibraryError", languageLibraryName);
 
1082
      getView().showError(message);
 
1083
    }
 
1084
  }
 
1085
 
 
1086
  /**
 
1087
   * Imports furniture to the catalog or home depending on the focused view.  
 
1088
   */
 
1089
  public void importFurniture() {
 
1090
    // Always use selection mode after an import furniture operation
 
1091
    getPlanController().setMode(PlanController.Mode.SELECTION);
 
1092
    if (this.focusedView == getFurnitureCatalogController().getView()) {
 
1093
      getFurnitureCatalogController().importFurniture();
 
1094
    } else {
 
1095
      getFurnitureController().importFurniture();
 
1096
    }    
 
1097
  }
 
1098
 
 
1099
  /**
 
1100
   * Imports a furniture library chosen by the user.  
 
1101
   */
 
1102
  public void importFurnitureLibrary() {
 
1103
    getView().invokeLater(new Runnable() {
 
1104
        public void run() {
 
1105
          final String furnitureLibraryName = getView().showImportFurnitureLibraryDialog();
 
1106
          if (furnitureLibraryName != null) {
 
1107
            importFurnitureLibrary(furnitureLibraryName);
 
1108
          }
 
1109
        }
 
1110
      });
 
1111
  }
 
1112
 
 
1113
  /**
 
1114
   * Imports a given furniture library. 
 
1115
   */
 
1116
  public void importFurnitureLibrary(String furnitureLibraryName) {
 
1117
    try {
 
1118
      if (!this.preferences.furnitureLibraryExists(furnitureLibraryName) 
 
1119
          || getView().confirmReplaceFurnitureLibrary(furnitureLibraryName)) {
 
1120
        this.preferences.addFurnitureLibrary(furnitureLibraryName);
 
1121
      }
 
1122
    } catch (RecorderException ex) {
 
1123
      String message = this.preferences.getLocalizedString(HomeController.class, 
 
1124
          "importFurnitureLibraryError", furnitureLibraryName);
 
1125
      getView().showError(message);
 
1126
    }
 
1127
  }
 
1128
 
 
1129
  /**
 
1130
   * Imports a textures library chosen by the user.  
 
1131
   */
 
1132
  public void importTexturesLibrary() {
 
1133
    getView().invokeLater(new Runnable() {
 
1134
        public void run() {
 
1135
          final String texturesLibraryName = getView().showImportTexturesLibraryDialog();
 
1136
          if (texturesLibraryName != null) {
 
1137
            importTexturesLibrary(texturesLibraryName);
 
1138
          }
 
1139
        }
 
1140
      });
 
1141
  }
 
1142
 
 
1143
  /**
 
1144
   * Imports a given textures library. 
 
1145
   */
 
1146
  public void importTexturesLibrary(String texturesLibraryName) {
 
1147
    try {
 
1148
      if (!this.preferences.texturesLibraryExists(texturesLibraryName) 
 
1149
          || getView().confirmReplaceTexturesLibrary(texturesLibraryName)) {
 
1150
        this.preferences.addTexturesLibrary(texturesLibraryName);
 
1151
      }
 
1152
    } catch (RecorderException ex) {
 
1153
      String message = this.preferences.getLocalizedString(HomeController.class, 
 
1154
          "importTexturesLibraryError", texturesLibraryName);
 
1155
      getView().showError(message);
 
1156
    }
 
1157
  }
 
1158
 
 
1159
  /**
 
1160
   * Undoes last operation.
 
1161
   */
 
1162
  public void undo() {
 
1163
    this.undoManager.undo();
 
1164
    HomeView view = getView();
 
1165
    boolean moreUndo = this.undoManager.canUndo();
 
1166
    view.setEnabled(HomeView.ActionType.UNDO, moreUndo);
 
1167
    view.setEnabled(HomeView.ActionType.REDO, true);
 
1168
    if (moreUndo) {
 
1169
      view.setUndoRedoName(this.undoManager.getUndoPresentationName(),
 
1170
          this.undoManager.getRedoPresentationName());
 
1171
    } else {
 
1172
      view.setUndoRedoName(null, this.undoManager.getRedoPresentationName());
 
1173
    }
 
1174
    this.saveUndoLevel--;
 
1175
    this.home.setModified(this.saveUndoLevel != 0 || this.notUndoableModifications);
 
1176
  }
 
1177
  
 
1178
  /**
 
1179
   * Redoes last undone operation.
 
1180
   */
 
1181
  public void redo() {
 
1182
    this.undoManager.redo();
 
1183
    HomeView view = getView();
 
1184
    boolean moreRedo = this.undoManager.canRedo();
 
1185
    view.setEnabled(HomeView.ActionType.UNDO, true);
 
1186
    view.setEnabled(HomeView.ActionType.REDO, moreRedo);
 
1187
    if (moreRedo) {
 
1188
      view.setUndoRedoName(this.undoManager.getUndoPresentationName(),
 
1189
          this.undoManager.getRedoPresentationName());
 
1190
    } else {
 
1191
      view.setUndoRedoName(this.undoManager.getUndoPresentationName(), null);
 
1192
    }
 
1193
    this.saveUndoLevel++;
 
1194
    this.home.setModified(this.saveUndoLevel != 0 || this.notUndoableModifications);
 
1195
  }
 
1196
 
 
1197
  /**
 
1198
   * Deletes items and post a cut operation to undo support.
 
1199
   */
 
1200
  public void cut(List<? extends Selectable> items) {
 
1201
    // Start a compound edit that deletes items and changes presentation name
 
1202
    UndoableEditSupport undoSupport = getUndoableEditSupport();
 
1203
    undoSupport.beginUpdate();
 
1204
    getPlanController().deleteItems(items);
 
1205
    // Add a undoable edit to change presentation name
 
1206
    undoSupport.postEdit(new AbstractUndoableEdit() { 
 
1207
        @Override
 
1208
        public String getPresentationName() {
 
1209
          return preferences.getLocalizedString(HomeController.class, "undoCutName");
 
1210
        }      
 
1211
      });
 
1212
    // End compound edit
 
1213
    undoSupport.endUpdate();
 
1214
  }
 
1215
  
 
1216
  /**
 
1217
   * Adds items to home and posts a paste operation to undo support.
 
1218
   */
 
1219
  public void paste(final List<? extends Selectable> items) {
 
1220
    // Check if pasted items and currently selected items overlap 
 
1221
    List<Selectable> selectedItems = this.home.getSelectedItems();
 
1222
    float pastedItemsDelta = 0;
 
1223
    if (items.size() == selectedItems.size()) {
 
1224
      // The default delta used to be able to distinguish dropped items from previous selection
 
1225
      pastedItemsDelta = 20; 
 
1226
      for (Selectable pastedItem : items) {      
 
1227
        // Search which item of selected items it may overlap
 
1228
        float [][] pastedItemPoints = pastedItem.getPoints();
 
1229
        boolean pastedItemOverlapSelectedItem = false;
 
1230
        for (Selectable selectedItem : selectedItems) {
 
1231
          if (Arrays.deepEquals(pastedItemPoints, selectedItem.getPoints())) {
 
1232
            pastedItemOverlapSelectedItem = true;
 
1233
            break;
 
1234
          }
 
1235
        }
 
1236
        if (!pastedItemOverlapSelectedItem) {
 
1237
          pastedItemsDelta = 0;
 
1238
          break;
 
1239
        }
 
1240
      }
 
1241
    }
 
1242
    addPastedItems(items, pastedItemsDelta, pastedItemsDelta, false, "undoPasteName");
 
1243
  }
 
1244
 
 
1245
  /**
 
1246
   * Adds items to home, moves them of (dx, dy) 
 
1247
   * and posts a drop operation to undo support.
 
1248
   */
 
1249
  public void drop(final List<? extends Selectable> items, float dx, float dy) {
 
1250
    drop(items, null, dx, dy);
 
1251
  }
 
1252
 
 
1253
  /**
 
1254
   * Adds items to home, moves them of (dx, dy) 
 
1255
   * and posts a drop operation to undo support.
 
1256
   */
 
1257
  public void drop(final List<? extends Selectable> items, View destinationView, float dx, float dy) {
 
1258
    addPastedItems(items, dx, dy, destinationView == getPlanController().getView(), "undoDropName");
 
1259
  }
 
1260
 
 
1261
  /**
 
1262
   * Adds items to home.
 
1263
   */
 
1264
  private void addPastedItems(final List<? extends Selectable> items, 
 
1265
                              float dx, float dy, final boolean isDropInPlanView, 
 
1266
                              final String presentationNameKey) {
 
1267
    if (items.size() > 1
 
1268
        || (items.size() == 1
 
1269
            && !(items.get(0) instanceof Compass))) {
 
1270
      // Always use selection mode after a drop or a paste operation
 
1271
      getPlanController().setMode(PlanController.Mode.SELECTION);
 
1272
      // Start a compound edit that adds walls, furniture, rooms, dimension lines and labels to home
 
1273
      UndoableEditSupport undoSupport = getUndoableEditSupport();
 
1274
      undoSupport.beginUpdate();
 
1275
      List<HomePieceOfFurniture> addedFurniture = Home.getFurnitureSubList(items);
 
1276
      // If magnetism is enabled, adjust furniture size and elevation
 
1277
      if (this.preferences.isMagnetismEnabled()) {
 
1278
        for (HomePieceOfFurniture piece : addedFurniture) {
 
1279
          if (piece.isResizable()) {
 
1280
            piece.setWidth(this.preferences.getLengthUnit().getMagnetizedLength(piece.getWidth(), 0.1f));
 
1281
            // Don't adjust depth of doors or windows otherwise they may be misplaced in a wall 
 
1282
            if (!(piece instanceof HomeDoorOrWindow) || dx != 0 || dy != 0) {
 
1283
              piece.setDepth(this.preferences.getLengthUnit().getMagnetizedLength(piece.getDepth(), 0.1f));
 
1284
            }
 
1285
            piece.setHeight(this.preferences.getLengthUnit().getMagnetizedLength(piece.getHeight(), 0.1f));
 
1286
          }
 
1287
          piece.setElevation(this.preferences.getLengthUnit().getMagnetizedLength(piece.getElevation(), 0.1f));
 
1288
        }
 
1289
      }
 
1290
      getPlanController().moveItems(items, dx, dy);
 
1291
      if (isDropInPlanView 
 
1292
          && this.preferences.isMagnetismEnabled()
 
1293
          && items.size() == 1
 
1294
          && addedFurniture.size() == 1) {
 
1295
        // Adjust piece when it's dropped in plan view  
 
1296
        getPlanController().adjustMagnetizedPieceOfFurniture((HomePieceOfFurniture)items.get(0), dx, dy);
 
1297
      } 
 
1298
      getPlanController().addItems(items);
 
1299
      undoSupport.postEdit(new AbstractUndoableEdit() {      
 
1300
          @Override
 
1301
          public String getPresentationName() {
 
1302
            return preferences.getLocalizedString(HomeController.class, presentationNameKey);
 
1303
          }      
 
1304
        });
 
1305
     
 
1306
      // End compound edit
 
1307
      undoSupport.endUpdate();
 
1308
    }
 
1309
  }
 
1310
 
 
1311
  /**
 
1312
   * Adds imported models to home, moves them of (dx, dy) 
 
1313
   * and post a drop operation to undo support.
 
1314
   */
 
1315
  public void dropFiles(final List<String> importableModels, float dx, float dy) {
 
1316
    // Always use selection mode after a drop operation
 
1317
    getPlanController().setMode(PlanController.Mode.SELECTION);
 
1318
    // Add to home a listener to track imported furniture 
 
1319
    final List<HomePieceOfFurniture> importedFurniture = 
 
1320
        new ArrayList<HomePieceOfFurniture>(importableModels.size());
 
1321
    CollectionListener<HomePieceOfFurniture> addedFurnitureListener = 
 
1322
        new CollectionListener<HomePieceOfFurniture>() {
 
1323
          public void collectionChanged(CollectionEvent<HomePieceOfFurniture> ev) {
 
1324
            importedFurniture.add(ev.getItem());
 
1325
          }
 
1326
        };
 
1327
    this.home.addFurnitureListener(addedFurnitureListener);
 
1328
    
 
1329
    // Start a compound edit that adds furniture to home
 
1330
    UndoableEditSupport undoSupport = getUndoableEditSupport();
 
1331
    undoSupport.beginUpdate();
 
1332
    // Import furniture
 
1333
    for (String model : importableModels) {
 
1334
      getFurnitureController().importFurniture(model);
 
1335
    }
 
1336
    this.home.removeFurnitureListener(addedFurnitureListener);
 
1337
    
 
1338
    if (importedFurniture.size() > 0) {
 
1339
      getPlanController().moveItems(importedFurniture, dx, dy);
 
1340
      this.home.setSelectedItems(importedFurniture);
 
1341
      
 
1342
      // Add a undoable edit that will select the imported furniture at redo
 
1343
      undoSupport.postEdit(new AbstractUndoableEdit() {      
 
1344
          @Override
 
1345
          public void redo() throws CannotRedoException {
 
1346
            super.redo();
 
1347
            home.setSelectedItems(importedFurniture);
 
1348
          }
 
1349
  
 
1350
          @Override
 
1351
          public String getPresentationName() {
 
1352
            return preferences.getLocalizedString(HomeController.class, "undoDropName");
 
1353
          }      
 
1354
        });
 
1355
    }
 
1356
   
 
1357
    // End compound edit
 
1358
    undoSupport.endUpdate();
 
1359
  }
 
1360
 
 
1361
  /**
 
1362
   * Deletes the selection in the focused component.
 
1363
   */
 
1364
  public void delete() {
 
1365
    if (this.focusedView == getFurnitureCatalogController().getView()) {
 
1366
      if (getView().confirmDeleteCatalogSelection()) {
 
1367
        getFurnitureCatalogController().deleteSelection();
 
1368
      }
 
1369
    } else if (this.focusedView == getFurnitureController().getView()) {
 
1370
      getFurnitureController().deleteSelection();
 
1371
    } else if (this.focusedView == getPlanController().getView()) {
 
1372
      getPlanController().deleteSelection();
 
1373
    }
 
1374
  }
 
1375
  
 
1376
  /**
 
1377
   * Updates actions when focused view changed.
 
1378
   */
 
1379
  public void focusedViewChanged(View focusedView) {
 
1380
    this.focusedView = focusedView;
 
1381
    enableActionsBoundToSelection();
 
1382
    enablePasteAction();
 
1383
    enableSelectAllAction();
 
1384
  }
 
1385
  
 
1386
  /**
 
1387
   * Selects everything in the focused component.
 
1388
   */
 
1389
  public void selectAll() {
 
1390
    if (this.focusedView == getFurnitureController().getView()) {
 
1391
      getFurnitureController().selectAll();
 
1392
    } else if (this.focusedView == getPlanController().getView()
 
1393
               || this.focusedView == getHomeController3D().getView()) {
 
1394
      getPlanController().selectAll();
 
1395
    }
 
1396
  }
 
1397
 
 
1398
  /**
 
1399
   * Creates a new home and adds it to application home list.
 
1400
   */
 
1401
  public void newHome() {
 
1402
    Home home;
 
1403
    if (this.application != null) {
 
1404
      home = this.application.createHome();
 
1405
    } else {
 
1406
      home = new Home(this.preferences.getNewWallHeight());
 
1407
    }
 
1408
    this.application.addHome(home);
 
1409
  }
 
1410
 
 
1411
  /**
 
1412
   * Opens a home. This method displays an {@link HomeView#showOpenDialog() open dialog} 
 
1413
   * in view, reads the home from the chosen name and adds it to application home list.
 
1414
   */
 
1415
  public void open() {
 
1416
    getView().invokeLater(new Runnable() {
 
1417
      public void run() {
 
1418
        final String homeName = getView().showOpenDialog();
 
1419
        if (homeName != null) {
 
1420
          open(homeName);
 
1421
        }
 
1422
      }
 
1423
    });
 
1424
  }
 
1425
 
 
1426
  /**
 
1427
   * Opens a given <code>homeName</code>home.
 
1428
   */
 
1429
  public void open(final String homeName) {
 
1430
    // Check if requested home isn't already opened
 
1431
    for (Home home : this.application.getHomes()) {
 
1432
      if (homeName.equals(home.getName())) {
 
1433
        String message = this.preferences.getLocalizedString(
 
1434
            HomeController.class, "alreadyOpen", homeName);
 
1435
        getView().showMessage(message);
 
1436
        return;
 
1437
      }
 
1438
    }
 
1439
    
 
1440
    // Read home in a threaded task
 
1441
    Callable<Void> openTask = new Callable<Void>() {
 
1442
          public Void call() throws RecorderException {
 
1443
            // Read home with application recorder
 
1444
            Home openedHome = application.getHomeRecorder().readHome(homeName);
 
1445
            openedHome.setName(homeName); 
 
1446
            addHomeToApplication(openedHome);
 
1447
            return null;
 
1448
          }
 
1449
        };
 
1450
    ThreadedTaskController.ExceptionHandler exceptionHandler = 
 
1451
        new ThreadedTaskController.ExceptionHandler() {
 
1452
          public void handleException(Exception ex) {
 
1453
            if (!(ex instanceof InterruptedRecorderException)) {
 
1454
              if (ex instanceof RecorderException) {
 
1455
                String message = preferences.getLocalizedString(
 
1456
                    HomeController.class, "openError", homeName);
 
1457
                getView().showError(message);
 
1458
              } else {
 
1459
                ex.printStackTrace();
 
1460
              }
 
1461
            }
 
1462
          }
 
1463
        };
 
1464
    new ThreadedTaskController(openTask, 
 
1465
        this.preferences.getLocalizedString(HomeController.class, "openMessage"), exceptionHandler, 
 
1466
        this.preferences, this.viewFactory).executeTask(getView());
 
1467
  }
 
1468
  
 
1469
  /**
 
1470
   * Adds the given home to application.
 
1471
   */
 
1472
  private void addHomeToApplication(final Home home) {
 
1473
    getView().invokeLater(new Runnable() {
 
1474
        public void run() {
 
1475
          application.addHome(home);
 
1476
        }
 
1477
      });
 
1478
  }
 
1479
  
 
1480
  /**
 
1481
   * Updates user preferences <code>recentHomes</code> and write preferences. 
 
1482
   */
 
1483
  private void updateUserPreferencesRecentHomes(List<String> recentHomes) {
 
1484
    if (this.application != null) {
 
1485
      // Check every recent home exists
 
1486
      for (Iterator<String> it = recentHomes.iterator(); it.hasNext(); ) {
 
1487
        try {
 
1488
          if (!this.application.getHomeRecorder().exists(it.next())) {
 
1489
            it.remove();
 
1490
          }
 
1491
        } catch (RecorderException ex) {
 
1492
          // If homeName can't be checked ignore it
 
1493
        }
 
1494
      }
 
1495
      this.preferences.setRecentHomes(recentHomes);
 
1496
    }
 
1497
  }
 
1498
 
 
1499
  /**
 
1500
   * Returns a list of displayable recent homes. 
 
1501
   */
 
1502
  public List<String> getRecentHomes() {
 
1503
    if (this.application != null) {
 
1504
      List<String> recentHomes = new ArrayList<String>();
 
1505
      for (String homeName : this.preferences.getRecentHomes()) {
 
1506
        try {
 
1507
          if (this.application.getHomeRecorder().exists(homeName)) {
 
1508
            recentHomes.add(homeName);
 
1509
            if (recentHomes.size() == this.preferences.getRecentHomesMaxCount()) {
 
1510
              break;
 
1511
            }
 
1512
          }
 
1513
        } catch (RecorderException ex) {
 
1514
          // If homeName can't be checked ignore it
 
1515
        }
 
1516
      }
 
1517
      getView().setEnabled(HomeView.ActionType.DELETE_RECENT_HOMES, 
 
1518
          !recentHomes.isEmpty());
 
1519
      return Collections.unmodifiableList(recentHomes);
 
1520
    } else {
 
1521
      return new ArrayList<String>();
 
1522
    }
 
1523
  }
 
1524
  
 
1525
  /**
 
1526
   * Returns the version of the application.
 
1527
   */
 
1528
  public String getVersion() {
 
1529
    if (this.application != null) {
 
1530
      return this.application.getVersion();
 
1531
    } else {
 
1532
      return "";
 
1533
    }
 
1534
  }
 
1535
  
 
1536
  /**
 
1537
   * Deletes the list of recent homes in user preferences. 
 
1538
   */
 
1539
  public void deleteRecentHomes() {
 
1540
    updateUserPreferencesRecentHomes(new ArrayList<String>());
 
1541
    getView().setEnabled(HomeView.ActionType.DELETE_RECENT_HOMES, false);
 
1542
  }
 
1543
  
 
1544
  /**
 
1545
   * Manages home close operation. If the home managed by this controller is modified,
 
1546
   * this method will {@link HomeView#confirmSave(String) confirm} 
 
1547
   * in view whether home should be saved. Once home is actually saved,
 
1548
   * home is removed from application homes list.
 
1549
   */
 
1550
  public void close() {
 
1551
    close(null);
 
1552
  }
 
1553
 
 
1554
  
 
1555
  /**
 
1556
   * Manages home close operation. If the home managed by this controller is modified,
 
1557
   * this method will {@link HomeView#confirmSave(String) confirm} 
 
1558
   * in view whether home should be saved. Once home is actually saved,
 
1559
   * home is removed from application homes list and postCloseTask is called if
 
1560
   * it's not <code>null</code>.
 
1561
   */
 
1562
  protected void close(final Runnable postCloseTask) {
 
1563
    // Create a task that deletes home and run postCloseTask
 
1564
    Runnable closeTask = new Runnable() {
 
1565
        public void run() {
 
1566
          home.setRecovered(false);
 
1567
          application.deleteHome(home);
 
1568
          if (postCloseTask != null) {
 
1569
            postCloseTask.run();
 
1570
          }
 
1571
        }
 
1572
      };
 
1573
      
 
1574
    if (this.home.isModified()  || this.home.isRecovered()) {
 
1575
      switch (getView().confirmSave(this.home.getName())) {
 
1576
        case SAVE   : save(HomeRecorder.Type.DEFAULT, closeTask); // Falls through
 
1577
        case CANCEL : return;
 
1578
      }  
 
1579
    }
 
1580
    closeTask.run();
 
1581
  }
 
1582
  
 
1583
  /**
 
1584
   * Saves the home managed by this controller. If home name doesn't exist, 
 
1585
   * this method will act as {@link #saveAs() saveAs} method.
 
1586
   */
 
1587
  public void save() {
 
1588
    save(HomeRecorder.Type.DEFAULT, null);
 
1589
  }
 
1590
 
 
1591
  /**
 
1592
   * Saves the home managed by this controller and executes <code>postSaveTask</code> 
 
1593
   * if it's not <code>null</code>.
 
1594
   */
 
1595
  private void save(HomeRecorder.Type recorderType, Runnable postSaveTask) {
 
1596
    if (this.home.getName() == null) {
 
1597
      saveAs(recorderType, postSaveTask);
 
1598
    } else {
 
1599
      save(this.home.getName(), recorderType, postSaveTask);
 
1600
    }
 
1601
  }
 
1602
  
 
1603
  /**
 
1604
   * Saves the home managed by this controller with a different name. 
 
1605
   * This method displays a {@link HomeView#showSaveDialog(String) save dialog} in  view, 
 
1606
   * and saves home with the chosen name if any. 
 
1607
   */
 
1608
  public void saveAs() {
 
1609
    saveAs(HomeRecorder.Type.DEFAULT, null);
 
1610
  }
 
1611
 
 
1612
  /**
 
1613
   * Saves the home managed by this controller with a different name. 
 
1614
   */
 
1615
  private void saveAs(HomeRecorder.Type recorderType, Runnable postSaveTask) {
 
1616
    String newName = getView().showSaveDialog(this.home.getName());
 
1617
    if (newName != null) {
 
1618
      save(newName, recorderType, postSaveTask);
 
1619
    }
 
1620
  }
 
1621
 
 
1622
  /**
 
1623
   * Saves the home managed by this controller and compresses it. If home name doesn't exist, 
 
1624
   * this method will prompt user to choose a home name.
 
1625
   */
 
1626
  public void saveAndCompress() {
 
1627
    save(HomeRecorder.Type.COMPRESSED, null);
 
1628
  }
 
1629
  
 
1630
  /**
 
1631
   * Actually saves the home managed by this controller and executes <code>postSaveTask</code> 
 
1632
   * if it's not <code>null</code>.
 
1633
   */
 
1634
  private void save(final String homeName, 
 
1635
                    final HomeRecorder.Type recorderType, 
 
1636
                    final Runnable postSaveTask) {
 
1637
    // If home version is older than current version
 
1638
    // or if home name is changed
 
1639
    // or if user confirms to save a home created with a newer version
 
1640
    if (this.home.getVersion() <= Home.CURRENT_VERSION
 
1641
        || !homeName.equals(this.home.getName()) 
 
1642
        || getView().confirmSaveNewerHome(homeName)) {
 
1643
      // Save home in a threaded task
 
1644
      Callable<Void> saveTask = new Callable<Void>() {
 
1645
            public Void call() throws RecorderException {
 
1646
              // Write home with application recorder
 
1647
              application.getHomeRecorder(recorderType).writeHome(home, homeName);
 
1648
              updateSavedHome(homeName, postSaveTask);
 
1649
              return null;
 
1650
            }
 
1651
          };
 
1652
      ThreadedTaskController.ExceptionHandler exceptionHandler = 
 
1653
          new ThreadedTaskController.ExceptionHandler() {
 
1654
            public void handleException(Exception ex) {
 
1655
              if (!(ex instanceof InterruptedRecorderException)) {
 
1656
                if (ex instanceof RecorderException) {
 
1657
                  String message = preferences.getLocalizedString(
 
1658
                      HomeController.class, "saveError", homeName);
 
1659
                  getView().showError(message);
 
1660
                } else {
 
1661
                  ex.printStackTrace();
 
1662
                }
 
1663
              }
 
1664
            }
 
1665
          };
 
1666
      new ThreadedTaskController(saveTask, 
 
1667
          this.preferences.getLocalizedString(HomeController.class, "saveMessage"), exceptionHandler, 
 
1668
          this.preferences, this.viewFactory).executeTask(getView());
 
1669
    }
 
1670
  }
 
1671
  
 
1672
  /**
 
1673
   * Updates the saved home and executes <code>postSaveTask</code> 
 
1674
   * if it's not <code>null</code>.
 
1675
   */
 
1676
  private void updateSavedHome(final String homeName,
 
1677
                               final Runnable postSaveTask) {
 
1678
    getView().invokeLater(new Runnable() {
 
1679
        public void run() {
 
1680
          home.setName(homeName);
 
1681
          home.setModified(false);
 
1682
          home.setRecovered(false);
 
1683
          // Update recent homes list
 
1684
          List<String> recentHomes = new ArrayList<String>(preferences.getRecentHomes());
 
1685
          int homeNameIndex = recentHomes.indexOf(homeName);
 
1686
          if (homeNameIndex >= 0) {
 
1687
            recentHomes.remove(homeNameIndex);
 
1688
          }
 
1689
          recentHomes.add(0, homeName);
 
1690
          updateUserPreferencesRecentHomes(recentHomes);
 
1691
          
 
1692
          if (postSaveTask != null) {
 
1693
            postSaveTask.run();
 
1694
          }
 
1695
        }
 
1696
      });
 
1697
  }
 
1698
 
 
1699
  /**
 
1700
   * Controls the export of the 3D view of current home to a SVG file.
 
1701
   */
 
1702
  public void exportToSVG() {
 
1703
    final String svgName = getView().showExportToSVGDialog(this.home.getName());    
 
1704
    if (svgName != null) {
 
1705
      // Export 3D view in a threaded task
 
1706
      Callable<Void> exportToSvgTask = new Callable<Void>() {
 
1707
            public Void call() throws RecorderException {
 
1708
              getView().exportToSVG(svgName);
 
1709
              return null;
 
1710
            }
 
1711
          };
 
1712
      ThreadedTaskController.ExceptionHandler exceptionHandler = 
 
1713
          new ThreadedTaskController.ExceptionHandler() {
 
1714
            public void handleException(Exception ex) {
 
1715
              if (!(ex instanceof InterruptedRecorderException)) {
 
1716
                if (ex instanceof RecorderException) {
 
1717
                  String message = preferences.getLocalizedString(
 
1718
                      HomeController.class, "exportToSVGError", svgName);
 
1719
                  getView().showError(message);
 
1720
                } else {
 
1721
                  ex.printStackTrace();
 
1722
                }
 
1723
              }
 
1724
            }
 
1725
          };
 
1726
      new ThreadedTaskController(exportToSvgTask, 
 
1727
          this.preferences.getLocalizedString(HomeController.class, "exportToSVGMessage"), exceptionHandler, 
 
1728
          this.preferences, this.viewFactory).executeTask(getView());
 
1729
    }
 
1730
  }
 
1731
  
 
1732
  /**
 
1733
   * Controls the export of the 3D view of current home to an OBJ file.
 
1734
   */
 
1735
  public void exportToOBJ() {
 
1736
    final String objName = getView().showExportToOBJDialog(this.home.getName());    
 
1737
    if (objName != null) {
 
1738
      // Export 3D view in a threaded task
 
1739
      Callable<Void> exportToObjTask = new Callable<Void>() {
 
1740
            public Void call() throws RecorderException {
 
1741
              getView().exportToOBJ(objName);
 
1742
              return null;
 
1743
            }
 
1744
          };
 
1745
      ThreadedTaskController.ExceptionHandler exceptionHandler = 
 
1746
          new ThreadedTaskController.ExceptionHandler() {
 
1747
            public void handleException(Exception ex) {
 
1748
              if (!(ex instanceof InterruptedRecorderException)) {
 
1749
                if (ex instanceof RecorderException) {
 
1750
                  String message = preferences.getLocalizedString(
 
1751
                      HomeController.class, "exportToOBJError", objName);
 
1752
                  getView().showError(message);
 
1753
                } else {
 
1754
                  ex.printStackTrace();
 
1755
                }
 
1756
              }
 
1757
            }
 
1758
          };
 
1759
      new ThreadedTaskController(exportToObjTask, 
 
1760
          this.preferences.getLocalizedString(HomeController.class, "exportToOBJMessage"), exceptionHandler, 
 
1761
          this.preferences, this.viewFactory).executeTask(getView());
 
1762
    }
 
1763
  }
 
1764
  
 
1765
  /**
 
1766
   * Controls the creation of photo-realistic images.
 
1767
   */
 
1768
  public void createPhoto() {
 
1769
    PhotoController photoController = new PhotoController(this.home, this.preferences, 
 
1770
        getHomeController3D().getView(), this.viewFactory, this.contentManager);
 
1771
    photoController.displayView(getView());
 
1772
  }
 
1773
  
 
1774
  /**
 
1775
   * Controls the creation of 3D videos.
 
1776
   */
 
1777
  public void createVideo() {
 
1778
    getPlanController().setMode(PlanController.Mode.SELECTION);
 
1779
    getHomeController3D().viewFromObserver();
 
1780
    VideoController videoController = new VideoController(this.home, this.preferences, 
 
1781
        this.viewFactory, this.contentManager);
 
1782
    videoController.displayView(getView());
 
1783
  }
 
1784
  
 
1785
  /**
 
1786
   * Controls page setup.
 
1787
   */
 
1788
  public void setupPage() {
 
1789
    new PageSetupController(this.home, this.preferences, 
 
1790
        this.viewFactory, getUndoableEditSupport()).displayView(getView());
 
1791
  }
 
1792
 
 
1793
  /**
 
1794
   * Controls the print preview.
 
1795
   */
 
1796
  public void previewPrint() {
 
1797
    new PrintPreviewController(this.home, this.preferences, 
 
1798
        this, this.viewFactory).displayView(getView());
 
1799
  }
 
1800
 
 
1801
  /**
 
1802
   * Controls the print of this home.
 
1803
   */
 
1804
  public void print() {
 
1805
    final Callable<Void> printTask = getView().showPrintDialog();    
 
1806
    if (printTask != null) {
 
1807
      // Print in a threaded task
 
1808
      ThreadedTaskController.ExceptionHandler exceptionHandler = 
 
1809
          new ThreadedTaskController.ExceptionHandler() {
 
1810
            public void handleException(Exception ex) {
 
1811
              if (!(ex instanceof InterruptedRecorderException)) {
 
1812
                if (ex instanceof RecorderException) {
 
1813
                  String message = preferences.getLocalizedString(
 
1814
                      HomeController.class, "printError", home.getName());
 
1815
                  getView().showError(message);
 
1816
                } else {
 
1817
                  ex.printStackTrace();
 
1818
                }
 
1819
              }
 
1820
            }
 
1821
          };
 
1822
      new ThreadedTaskController(printTask, 
 
1823
          this.preferences.getLocalizedString(HomeController.class, "printMessage"), exceptionHandler, 
 
1824
          this.preferences, this.viewFactory).executeTask(getView());      
 
1825
    }
 
1826
  }
 
1827
 
 
1828
  /**
 
1829
   * Controls the print of this home in a PDF file.
 
1830
   */
 
1831
  public void printToPDF() {
 
1832
    final String pdfName = getView().showPrintToPDFDialog(this.home.getName());    
 
1833
    if (pdfName != null) {
 
1834
      // Print to PDF in a threaded task
 
1835
      Callable<Void> printToPdfTask = new Callable<Void>() {
 
1836
          public Void call() throws RecorderException {
 
1837
            getView().printToPDF(pdfName);
 
1838
            return null;
 
1839
          }
 
1840
        };
 
1841
      ThreadedTaskController.ExceptionHandler exceptionHandler = 
 
1842
          new ThreadedTaskController.ExceptionHandler() {
 
1843
            public void handleException(Exception ex) {
 
1844
              if (!(ex instanceof InterruptedRecorderException)) {
 
1845
                if (ex instanceof RecorderException) {
 
1846
                  String message = preferences.getLocalizedString(
 
1847
                      HomeController.class, "printToPDFError", pdfName);
 
1848
                  getView().showError(message);
 
1849
                } else {
 
1850
                  ex.printStackTrace();
 
1851
                }
 
1852
              }
 
1853
            }
 
1854
          };
 
1855
      new ThreadedTaskController(printToPdfTask, 
 
1856
          preferences.getLocalizedString(HomeController.class, "printToPDFMessage"), exceptionHandler, 
 
1857
          this.preferences, this.viewFactory).executeTask(getView());
 
1858
    }
 
1859
  }
 
1860
 
 
1861
  /**
 
1862
   * Controls application exit. If any home in application homes list is modified,
 
1863
   * the user will be {@link HomeView#confirmExit() prompted} in view whether he wants
 
1864
   * to discard his modifications ot not.  
 
1865
   */
 
1866
  public void exit() {
 
1867
    for (Home home : this.application.getHomes()) {
 
1868
      if (home.isModified() || home.isRecovered()) {
 
1869
        if (getView().confirmExit()) {
 
1870
          break;
 
1871
        } else {
 
1872
          return;
 
1873
        }
 
1874
      }
 
1875
    }
 
1876
    // Remove all homes from application
 
1877
    for (Home home : this.application.getHomes()) {
 
1878
      home.setRecovered(false);
 
1879
      this.application.deleteHome(home);
 
1880
    }
 
1881
    // Let application decide what to do when there's no more home
 
1882
  }
 
1883
 
 
1884
  /**
 
1885
   * Edits preferences and changes them if user agrees.
 
1886
   */
 
1887
  public void editPreferences() {
 
1888
    new UserPreferencesController(this.preferences, 
 
1889
        this.viewFactory, this.contentManager, this).displayView(getView());
 
1890
  }
 
1891
  
 
1892
  /**
 
1893
   * Displays a tip message dialog depending on the given mode and 
 
1894
   * sets the active mode of the plan controller. 
 
1895
   */
 
1896
  public void setMode(Mode mode) {
 
1897
    if (getPlanController().getMode() != mode) {
 
1898
      final String actionKey;
 
1899
      if (mode == Mode.WALL_CREATION) {
 
1900
        actionKey = HomeView.ActionType.CREATE_WALLS.name();
 
1901
      } else if (mode == Mode.ROOM_CREATION) {
 
1902
        actionKey = HomeView.ActionType.CREATE_ROOMS.name();
 
1903
      } else if (mode == Mode.DIMENSION_LINE_CREATION) {
 
1904
        actionKey = HomeView.ActionType.CREATE_DIMENSION_LINES.name();
 
1905
      } else if (mode == Mode.LABEL_CREATION) {
 
1906
        actionKey = HomeView.ActionType.CREATE_LABELS.name();
 
1907
      } else {
 
1908
        actionKey = null;
 
1909
      }
 
1910
      // Display the tip message dialog matching mode
 
1911
      if (actionKey != null 
 
1912
          && !this.preferences.isActionTipIgnored(actionKey)) {
 
1913
        getView().invokeLater(new Runnable() {
 
1914
            public void run() {
 
1915
              // Show tip later to let the mode switch finish first
 
1916
              if (getView().showActionTipMessage(actionKey)) {
 
1917
                preferences.setActionTipIgnored(actionKey);
 
1918
              }
 
1919
            }
 
1920
          });
 
1921
      }
 
1922
      getPlanController().setMode(mode);
 
1923
    }
 
1924
  }
 
1925
 
 
1926
  /**
 
1927
   * Displays the wizard that helps to import home background image. 
 
1928
   */
 
1929
  public void importBackgroundImage() {
 
1930
    new BackgroundImageWizardController(this.home, this.preferences, 
 
1931
        this.viewFactory, this.contentManager, getUndoableEditSupport()).displayView(getView());
 
1932
  }
 
1933
  
 
1934
  /**
 
1935
   * Displays the wizard that helps to change home background image. 
 
1936
   */
 
1937
  public void modifyBackgroundImage() {
 
1938
    importBackgroundImage();
 
1939
  }
 
1940
  
 
1941
  /**
 
1942
   * Hides the home background image. 
 
1943
   */
 
1944
  public void hideBackgroundImage() {     
 
1945
    toggleBackgroundImageVisibility("undoHideBackgroundImageName");
 
1946
  }
 
1947
  
 
1948
  /**
 
1949
   * Shows the home background image. 
 
1950
   */
 
1951
  public void showBackgroundImage() {
 
1952
    toggleBackgroundImageVisibility("undoShowBackgroundImageName");
 
1953
  }
 
1954
  
 
1955
  /**
 
1956
   * Toggles visibility of the background image and posts an undoable operation.
 
1957
   */
 
1958
  private void toggleBackgroundImageVisibility(final String presentationName) {
 
1959
    final Level selectedLevel = this.home.getSelectedLevel();
 
1960
    doToggleBackgroundImageVisibility(); 
 
1961
    UndoableEdit undoableEdit = new AbstractUndoableEdit() {
 
1962
        @Override
 
1963
        public void undo() throws CannotUndoException {
 
1964
          super.undo();
 
1965
          home.setSelectedLevel(selectedLevel);
 
1966
          doToggleBackgroundImageVisibility(); 
 
1967
        }
 
1968
        
 
1969
        @Override
 
1970
        public void redo() throws CannotRedoException {
 
1971
          super.redo();
 
1972
          home.setSelectedLevel(selectedLevel);
 
1973
          doToggleBackgroundImageVisibility();
 
1974
        }
 
1975
        
 
1976
        @Override
 
1977
        public String getPresentationName() {
 
1978
          return preferences.getLocalizedString(HomeController.class, presentationName);
 
1979
        }
 
1980
      };
 
1981
    getUndoableEditSupport().postEdit(undoableEdit);
 
1982
  }
 
1983
 
 
1984
  /**
 
1985
   * Toggles visibility of the background image.
 
1986
   */
 
1987
  private void doToggleBackgroundImageVisibility() {
 
1988
    BackgroundImage backgroundImage = this.home.getSelectedLevel() != null
 
1989
        ? this.home.getSelectedLevel().getBackgroundImage()
 
1990
        : this.home.getBackgroundImage();
 
1991
    backgroundImage = new BackgroundImage(backgroundImage.getImage(),
 
1992
        backgroundImage.getScaleDistance(), 
 
1993
        backgroundImage.getScaleDistanceXStart(), backgroundImage.getScaleDistanceYStart(), 
 
1994
        backgroundImage.getScaleDistanceXEnd(), backgroundImage.getScaleDistanceYEnd(),
 
1995
        backgroundImage.getXOrigin(), backgroundImage.getYOrigin(), !backgroundImage.isVisible());
 
1996
    if (this.home.getSelectedLevel() != null) {
 
1997
      this.home.getSelectedLevel().setBackgroundImage(backgroundImage);
 
1998
    } else {
 
1999
      this.home.setBackgroundImage(backgroundImage);
 
2000
    }
 
2001
  }
 
2002
  
 
2003
  /**
 
2004
   * Deletes home background image and posts and posts an undoable operation. 
 
2005
   */
 
2006
  public void deleteBackgroundImage() {
 
2007
    final Level selectedLevel = this.home.getSelectedLevel();
 
2008
    final BackgroundImage oldImage;
 
2009
    if (selectedLevel != null) {
 
2010
      oldImage = selectedLevel.getBackgroundImage();
 
2011
      selectedLevel.setBackgroundImage(null);
 
2012
    } else {
 
2013
      oldImage = this.home.getBackgroundImage();
 
2014
      this.home.setBackgroundImage(null);
 
2015
    }
 
2016
    UndoableEdit undoableEdit = new AbstractUndoableEdit() {
 
2017
      @Override
 
2018
      public void undo() throws CannotUndoException {
 
2019
        super.undo();
 
2020
        home.setSelectedLevel(selectedLevel);
 
2021
        if (selectedLevel != null) {
 
2022
          selectedLevel.setBackgroundImage(oldImage);
 
2023
        } else {
 
2024
          home.setBackgroundImage(oldImage);
 
2025
        }
 
2026
      }
 
2027
      
 
2028
      @Override
 
2029
      public void redo() throws CannotRedoException {
 
2030
        super.redo();
 
2031
        home.setSelectedLevel(selectedLevel);
 
2032
        if (selectedLevel != null) {
 
2033
          selectedLevel.setBackgroundImage(null);
 
2034
        } else {
 
2035
          home.setBackgroundImage(null);
 
2036
        }
 
2037
      }
 
2038
      
 
2039
      @Override
 
2040
      public String getPresentationName() {
 
2041
        return preferences.getLocalizedString(HomeController.class, "undoDeleteBackgroundImageName");
 
2042
      }
 
2043
    };
 
2044
    getUndoableEditSupport().postEdit(undoableEdit);
 
2045
  }
 
2046
  
 
2047
  /**
 
2048
   * Zooms out in plan.
 
2049
   */
 
2050
  public void zoomOut() {
 
2051
    PlanController planController = getPlanController();
 
2052
    float newScale = planController.getScale() / 1.5f;
 
2053
    planController.setScale(newScale);
 
2054
  }
 
2055
 
 
2056
  /**
 
2057
   * Zooms in in plan.
 
2058
   */
 
2059
  public void zoomIn() {
 
2060
    PlanController planController = getPlanController();
 
2061
    float newScale = planController.getScale() * 1.5f;
 
2062
    planController.setScale(newScale);
 
2063
  }
 
2064
 
 
2065
  /**
 
2066
   * Prompts a name for the current camera and stores it in home.
 
2067
   */
 
2068
  public void storeCamera() {
 
2069
    String now = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM).format(new Date());
 
2070
    String name = getView().showStoreCameraDialog(now);
 
2071
    if (name != null) {
 
2072
      getHomeController3D().storeCamera(name);
 
2073
    }
 
2074
  }
 
2075
 
 
2076
  /**
 
2077
   * Detaches the given <code>view</code> from home view.
 
2078
   */
 
2079
  public void detachView(View view) {
 
2080
    if (view != null) {
 
2081
      getView().detachView(view);
 
2082
      this.notUndoableModifications = true;
 
2083
      home.setModified(true);
 
2084
    }
 
2085
  }
 
2086
                
 
2087
  /**
 
2088
   * Attaches the given <code>view</code> to home view.
 
2089
   */
 
2090
  public void attachView(View view) {
 
2091
    if (view != null) {
 
2092
      getView().attachView(view);
 
2093
      this.notUndoableModifications = true;
 
2094
      home.setModified(true);
 
2095
    }
 
2096
  }
 
2097
                
 
2098
  /**
 
2099
   * Displays help window.
 
2100
   */
 
2101
  public void help() {
 
2102
    if (helpController == null) {
 
2103
      helpController = new HelpController(this.preferences, this.viewFactory);
 
2104
    }
 
2105
    helpController.displayView();
 
2106
  }
 
2107
 
 
2108
  /**
 
2109
   * Displays about dialog.
 
2110
   */
 
2111
  public void about() {
 
2112
    getView().showAboutDialog();
 
2113
  }
 
2114
 
 
2115
  /**
 
2116
   * Controls the change of value of a visual property in home.
 
2117
   */
 
2118
  public void setVisualProperty(String propertyName,
 
2119
                                Object propertyValue) {
 
2120
    this.home.setVisualProperty(propertyName, propertyValue);
 
2121
  }
 
2122
}