static {
    // Retrieve sub folders where is stored application data
    ResourceBundle resource = ResourceBundle.getBundle(OperatingSystem.class.getName());
    if (OperatingSystem.isMacOSX()) {
      EDITOR_SUB_FOLDER = resource.getString("editorSubFolder.Mac OS X");
      APPLICATION_SUB_FOLDER = resource.getString("applicationSubFolder.Mac OS X");
    } else if (OperatingSystem.isWindows()) {
      EDITOR_SUB_FOLDER = resource.getString("editorSubFolder.Windows");
      APPLICATION_SUB_FOLDER = resource.getString("applicationSubFolder.Windows");
    } else {
      EDITOR_SUB_FOLDER = resource.getString("editorSubFolder");
      APPLICATION_SUB_FOLDER = resource.getString("applicationSubFolder");
    }

    String temporarySubFolder;
    try {
      temporarySubFolder = resource.getString("temporarySubFolder");
      if (temporarySubFolder.trim().length() == 0) {
        temporarySubFolder = null;
      }
    } catch (MissingResourceException ex) {
      temporarySubFolder = "work";
    }
    try {
      temporarySubFolder =
          System.getProperty("com.eteks.sweethome3d.tools.temporarySubFolder", temporarySubFolder);
    } catch (AccessControlException ex) {
      // Don't change temporarySubFolder value
    }
    TEMPORARY_SUB_FOLDER = temporarySubFolder;
    TEMPORARY_SESSION_SUB_FOLDER = UUID.randomUUID().toString();
  }
 /** Returns a copy of a given <code>image</code>. */
 static Content copyToTemporaryContent(BufferedImage image, Content imageContent)
     throws IOException {
   if (imageContent instanceof URLContent) {
     return TemporaryURLContent.copyToTemporaryURLContent(imageContent);
   } else {
     File tempFile = OperatingSystem.createTemporaryFile("texture", "png");
     FileOutputStream out = null;
     try {
       out = new FileOutputStream(tempFile);
       ImageIO.write(image, "PNG", out);
       return new TemporaryURLContent(tempFile.toURI().toURL());
     } finally {
       if (out != null) {
         out.close();
       }
     }
   }
 }
  public void testPlanComponentWithFurniture() throws InterruptedException {
    // 1. Create a frame that displays a home view and a tool bar
    // with Mode, Add furniture, Undo and Redo buttons
    final TestFrame frame = new TestFrame();
    // Show home plan frame
    showWindow(frame);

    // 2. Use CREATE_WALLS mode
    JComponentTester tester = new JComponentTester();
    tester.click(frame.createWallsButton);
    PlanComponent planComponent =
        (PlanComponent) frame.homeController.getPlanController().getView();
    // Click at (30, 30), (220, 30), (270, 80), (270, 170), (30, 170)
    // then double click at (30, 30) with no magnetism
    tester.actionKeyPress(KeyEvent.VK_SHIFT);
    tester.actionClick(planComponent, 30, 30);
    tester.actionClick(planComponent, 220, 30);
    tester.actionClick(planComponent, 270, 80);
    tester.actionClick(planComponent, 270, 170);
    tester.actionClick(planComponent, 30, 170);
    tester.actionClick(planComponent, 30, 30, InputEvent.BUTTON1_MASK, 2);
    tester.actionKeyRelease(KeyEvent.VK_SHIFT);
    // Check 5 walls were added to home plan
    assertEquals("Wrong walls count", 5, frame.home.getWalls().size());

    // 3. Use SELECTION mode
    tester.click(frame.selectButton);
    // Select the first piece in catalog tree
    JTree catalogTree = (JTree) frame.homeController.getFurnitureCatalogController().getView();
    catalogTree.expandRow(0);
    catalogTree.addSelectionInterval(1, 1);
    // Click on Add furniture button
    tester.click(frame.addButton);
    // Check home contains one selected piece
    tester.waitForIdle();
    assertEquals("Wrong piece count", 1, frame.home.getFurniture().size());
    assertEquals("Wrong selected items count", 1, frame.home.getSelectedItems().size());

    HomePieceOfFurniture piece = frame.home.getFurniture().get(0);
    float pieceWidth = piece.getWidth();
    float pieceDepth = piece.getDepth();
    float pieceHeight = piece.getHeight();
    float pieceX = pieceWidth / 2;
    float pieceY = pieceDepth / 2;
    assertLocationAndOrientationEqualPiece(pieceX, pieceY, 0, piece);

    // 4. Press mouse button at piece center
    int widthPixel = Math.round(pieceWidth * planComponent.getScale());
    int depthPixel = Math.round(pieceDepth * planComponent.getScale());
    tester.actionMousePress(
        planComponent, new ComponentLocation(new Point(20 + widthPixel / 2, 20 + depthPixel / 2)));
    // Drag mouse to (100, 100) from piece center
    tester.actionMousePress(
        planComponent,
        new ComponentLocation(new Point(20 + widthPixel / 2 + 100, 20 + depthPixel / 2 + 100)));
    tester.actionMouseRelease();
    // Check piece moved
    pieceX += 200;
    pieceY += 200;
    assertLocationAndOrientationEqualPiece(pieceX, pieceY, 0, piece);

    // 5. Press mouse button at top left point of selected piece
    tester.actionMousePress(planComponent, new ComponentLocation(new Point(120, 120)));
    // Drag mouse to (-depthPixel / 2 - 1, widthPixel / 2) pixels from piece center
    tester.actionMouseMove(
        planComponent,
        new ComponentLocation(
            new Point(
                120 + widthPixel / 2 - depthPixel / 2 - 1, 120 + depthPixel / 2 + widthPixel / 2)));
    tester.actionMouseRelease();
    // Check piece angle is 3 * PI / 2 (=-90°)
    assertLocationAndOrientationEqualPiece(pieceX, pieceY, (float) Math.PI * 3 / 2, piece);

    // 6. Press mouse button at top left point of selected piece
    tester.actionMousePress(
        planComponent,
        new ComponentLocation(
            new Point(
                120 + widthPixel / 2 - depthPixel / 2, 120 + depthPixel / 2 + widthPixel / 2)));
    // Drag mouse to the previous position plus 1 pixel along x axis
    tester.actionMouseMove(planComponent, new ComponentLocation(new Point(121, 120)));
    // Check piece angle is 0°
    assertLocationAndOrientationEqualPiece(pieceX, pieceY, 0, piece);
    // Press Shift key
    tester.actionKeyPress(KeyEvent.VK_SHIFT);
    // Check piece angle is different from 0°
    assertFalse("Piece orientation shouldn't be magnetized", Math.abs(piece.getAngle()) < 1E-10);
    tester.actionKeyRelease(KeyEvent.VK_SHIFT);
    tester.actionKeyStroke(planComponent, KeyEvent.VK_ESCAPE);
    tester.actionMouseRelease();
    // Check piece angle is 3 * PI / 2 (=-90°)
    assertLocationAndOrientationEqualPiece(pieceX, pieceY, (float) Math.PI * 3 / 2, piece);

    // 7. Click at point (30, 160) with Shift key depressed
    tester.actionKeyPress(KeyEvent.VK_SHIFT);
    tester.actionClick(planComponent, 30, 160);
    tester.actionKeyRelease(KeyEvent.VK_SHIFT);
    // Check selected items contains the piece of furniture and the fifth wall
    List<Selectable> selectedItems = new ArrayList<Selectable>(frame.home.getSelectedItems());
    assertEquals("Wrong selected items count", 2, selectedItems.size());
    assertTrue("Piece of furniture not selected", selectedItems.contains(piece));
    // Remove piece form list to get the selected wall
    selectedItems.remove(piece);
    Wall fifthWall = (Wall) selectedItems.get(0);
    // Check piece and wall coordinates
    assertLocationAndOrientationEqualPiece(pieceX, pieceY, (float) Math.PI * 3 / 2, piece);
    assertCoordinatesEqualWallPoints(20, 300, 20, 20, fifthWall);

    // 8. Drag and drop mouse to (40, 160)
    Thread.sleep(1000); // Wait 1s to avoid double click
    tester.actionMousePress(planComponent, new ComponentLocation(new Point(30, 160)));
    tester.actionMouseMove(planComponent, new ComponentLocation(new Point(40, 160)));
    tester.actionMouseRelease();
    // Check the piece of furniture moved 20 cm along x axis
    assertLocationAndOrientationEqualPiece(pieceX + 20, pieceY, (float) Math.PI * 3 / 2, piece);
    assertCoordinatesEqualWallPoints(40, 300, 40, 20, fifthWall);

    // 9. Click twice on undo button
    tester.invokeAndWait(
        new Runnable() {
          public void run() {
            frame.undoButton.doClick();
            frame.undoButton.doClick();
          }
        });
    // Check piece orientation and location are canceled
    assertLocationAndOrientationEqualPiece(pieceX, pieceY, 0f, piece);
    assertCoordinatesEqualWallPoints(20, 300, 20, 20, fifthWall);

    // 10. Click twice on redo button
    tester.invokeAndWait(
        new Runnable() {
          public void run() {
            frame.redoButton.doClick();
            frame.redoButton.doClick();
          }
        });
    // Check piece and wall location was redone
    assertLocationAndOrientationEqualPiece(pieceX + 20, pieceY, (float) Math.PI * 3 / 2, piece);
    assertCoordinatesEqualWallPoints(40, 300, 40, 20, fifthWall);
    // Check selected items contains the piece of furniture and the fifth wall
    selectedItems = frame.home.getSelectedItems();
    assertEquals("Wrong selected items count", 2, selectedItems.size());
    assertTrue("Piece of furniture not selected", selectedItems.contains(piece));
    assertTrue("Fifth wall not selected", selectedItems.contains(fifthWall));

    // 11. Click at point (pieceXPixel + depthPixel / 2, pieceYPixel - widthPixel / 2)
    //     at width and depth resize point of the piece
    int pieceXPixel = Math.round((piece.getX() + 40) * planComponent.getScale());
    int pieceYPixel = Math.round((piece.getY() + 40) * planComponent.getScale());
    tester.actionClick(planComponent, pieceXPixel + depthPixel / 2, pieceYPixel - widthPixel / 2);

    // Check selected items contains only the piece of furniture
    selectedItems = frame.home.getSelectedItems();
    assertEquals("Wrong selected items count", 1, selectedItems.size());
    assertTrue("Piece of furniture not selected", selectedItems.contains(piece));
    // Drag mouse (4,4) pixels out of piece box with magnetism disabled
    Thread.sleep(1000); // Wait 1s to avoid double click
    tester.actionMousePress(
        planComponent,
        new ComponentLocation(
            new Point(pieceXPixel + depthPixel / 2, pieceYPixel - widthPixel / 2)));
    tester.actionKeyPress(KeyEvent.VK_SHIFT);
    tester.actionMouseMove(
        planComponent,
        new ComponentLocation(
            new Point(pieceXPixel + depthPixel / 2 + 4, pieceYPixel - widthPixel / 2 + 4)));
    tester.actionMouseRelease();
    tester.actionKeyRelease(KeyEvent.VK_SHIFT);
    // Check piece width and depth were resized (caution : piece angle is oriented at 90°)
    assertDimensionEqualPiece(
        pieceWidth - 4 / planComponent.getScale(),
        pieceDepth + 4 / planComponent.getScale(),
        pieceHeight,
        piece);

    // 12. Click at point (pieceXPixel + depthPixel / 2, pieceYPixel + widthPixel / 2)
    //     at height resize point of the piece
    pieceXPixel = Math.round((piece.getX() + 40) * planComponent.getScale());
    pieceYPixel = Math.round((piece.getY() + 40) * planComponent.getScale());
    widthPixel = Math.round((piece.getWidth()) * planComponent.getScale());
    depthPixel = Math.round((piece.getDepth()) * planComponent.getScale());
    tester.actionMouseMove(
        planComponent,
        new ComponentLocation(
            new Point(pieceXPixel + depthPixel / 2, pieceYPixel + widthPixel / 2)));
    Thread.sleep(1000);
    tester.actionMousePress(
        planComponent,
        new ComponentLocation(
            new Point(pieceXPixel + depthPixel / 2, pieceYPixel + widthPixel / 2)));
    // Drag mouse (2,4) pixels
    tester.actionMouseMove(
        planComponent,
        new ComponentLocation(
            new Point(pieceXPixel + depthPixel / 2 + 2, pieceYPixel + widthPixel / 2 + 4)));
    tester.actionMouseRelease();
    // Check piece height was resized
    assertDimensionEqualPiece(
        pieceWidth - 4 / planComponent.getScale(),
        pieceDepth + 4 / planComponent.getScale(),
        Math.round((pieceHeight - 4 / planComponent.getScale()) * 2) / 2,
        piece);

    // 13. Click at point (pieceXPixel - depthPixel / 2, pieceYPixel - widthPixel / 2)
    //     at elevation point of the piece
    float pieceElevation = piece.getElevation();
    tester.actionMousePress(
        planComponent,
        new ComponentLocation(
            new Point(pieceXPixel - depthPixel / 2, pieceYPixel - widthPixel / 2)));
    // Drag mouse (2,-4) pixels
    tester.actionMouseMove(
        planComponent,
        new ComponentLocation(
            new Point(pieceXPixel - depthPixel / 2 + 2, pieceYPixel - widthPixel / 2 - 4)));
    tester.actionMouseRelease();
    // Check piece elevation was updated
    assertElevationEqualPiece(pieceElevation + 4 / planComponent.getScale(), piece);

    // 14. Click three times on undo button
    tester.invokeAndWait(
        new Runnable() {
          public void run() {
            frame.undoButton.doClick();
            frame.undoButton.doClick();
            frame.undoButton.doClick();
          }
        });
    // Check piece dimension and elevation are canceled
    assertDimensionEqualPiece(pieceWidth, pieceDepth, pieceHeight, piece);
    assertElevationEqualPiece(pieceElevation, piece);

    // Build an ordered list of dimensions added to home
    final ArrayList<DimensionLine> orderedDimensionLines = new ArrayList<DimensionLine>();
    frame.home.addDimensionLinesListener(
        new CollectionListener<DimensionLine>() {
          public void collectionChanged(CollectionEvent<DimensionLine> ev) {
            if (ev.getType() == CollectionEvent.Type.ADD) {
              orderedDimensionLines.add(ev.getItem());
            }
          }
        });

    // 15. Use CREATE_DIMENSION_LINES mode
    tester.invokeAndWait(
        new Runnable() {
          public void run() {
            frame.createDimensionsButton.doClick();
          }
        });
    // Draw a dimension in plan
    tester.actionClick(planComponent, 280, 81);
    tester.actionClick(planComponent, 281, 169, InputEvent.BUTTON1_MASK, 2);
    // Draw a dimension with extension lines
    tester.actionClick(planComponent, 41, 175);
    tester.actionClick(planComponent, 269, 175);
    tester.actionClick(planComponent, 280, 185);
    // Check 2 dimensions were added to home plan
    assertEquals("Wrong dimensions count", 2, frame.home.getDimensionLines().size());
    // Check one dimension is selected
    assertEquals("Wrong selection", 1, frame.home.getSelectedItems().size());
    assertEquals(
        "Selection doesn't contain the second dimension",
        frame.home.getSelectedItems().get(0),
        orderedDimensionLines.get(1));
    // Check the size of the created dimension lines
    DimensionLine firstDimensionLine = orderedDimensionLines.get(0);
    assertEqualsDimensionLine(520, 122, 520, 298, 0, firstDimensionLine);
    assertEqualsDimensionLine(42, 310, 498, 310, 20, orderedDimensionLines.get(1));

    // 16. Select the first dimension line
    tester.click(frame.selectButton);
    tester.actionClick(planComponent, 280, 90);
    assertEquals("Wrong selection", 1, frame.home.getSelectedItems().size());
    assertEquals(
        "Selection doesn't contain the first dimension",
        frame.home.getSelectedItems().get(0),
        firstDimensionLine);
    // Move its end point to (330, 167)
    tester.actionMousePress(planComponent, new ComponentLocation(new Point(280, 167)));
    tester.actionMouseMove(planComponent, new ComponentLocation(new Point(320, 167)));
    // Check its coordinates while Shift key isn't pressed (with magnetism)
    assertEqualsDimensionLine(520, 122, 567.105f, 297.7985f, 0, firstDimensionLine);
    // Check its length with magnetism
    float firstDimensionLineLength =
        (float)
            Point2D.distance(
                firstDimensionLine.getXStart(), firstDimensionLine.getYStart(),
                firstDimensionLine.getXEnd(), firstDimensionLine.getYEnd());
    assertTrue(
        "Incorrect length 182 " + firstDimensionLineLength,
        Math.abs(182 - firstDimensionLineLength) < 1E-4);
    // Press Shift key
    tester.actionKeyPress(KeyEvent.VK_SHIFT);
    // Check its coordinates while Shift key is pressed (with no magnetism)
    assertEqualsDimensionLine(520, 122, 600, 298, 0, firstDimensionLine);
    // Release Shift key and mouse button
    tester.actionKeyRelease(KeyEvent.VK_SHIFT);
    tester.actionMouseRelease();
    assertEqualsDimensionLine(520, 122, 567.105f, 297.7985f, 0, firstDimensionLine);

    // 17. Click three times on undo button
    tester.invokeAndWait(
        new Runnable() {
          public void run() {
            frame.undoButton.doClick();
            frame.undoButton.doClick();
            frame.undoButton.doClick();
          }
        });
    // Check home doesn't contain any dimension
    assertEquals("Home dimensions set isn't empty", 0, frame.home.getDimensionLines().size());

    // 18. Click twice on redo button
    tester.invokeAndWait(
        new Runnable() {
          public void run() {
            frame.redoButton.doClick();
            frame.redoButton.doClick();
          }
        });
    // Check the size of the created dimension lines
    assertEqualsDimensionLine(520, 122, 520, 298f, 0, firstDimensionLine);
    assertEqualsDimensionLine(42, 310, 498, 310, 20, orderedDimensionLines.get(1));
    // Click again on redo button
    tester.invokeAndWait(
        new Runnable() {
          public void run() {
            frame.redoButton.doClick();
          }
        });
    // Check the first dimension is selected
    assertEquals("Wrong selection", 1, frame.home.getSelectedItems().size());
    assertEquals(
        "Selection doesn't contain the first dimension",
        frame.home.getSelectedItems().get(0),
        firstDimensionLine);
    // Check the coordinates of the first dimension
    assertEqualsDimensionLine(520, 122, 567.105f, 297.7985f, 0, firstDimensionLine);

    // 19. Select two walls, the piece and a dimension line
    tester.actionMousePress(planComponent, new ComponentLocation(new Point(20, 100)));
    tester.actionMouseMove(planComponent, new ComponentLocation(new Point(200, 200)));
    tester.actionMouseRelease();
    // Check selection
    selectedItems = frame.home.getSelectedItems();
    assertEquals("Selection doesn't contain 4 items", 4, selectedItems.size());
    int wallsCount = frame.home.getWalls().size();
    int furnitureCount = frame.home.getFurniture().size();
    int dimensionLinesCount = frame.home.getDimensionLines().size();
    HomePieceOfFurniture selectedPiece = Home.getFurnitureSubList(selectedItems).get(0);
    pieceX = selectedPiece.getX();
    pieceY = selectedPiece.getY();
    // Start items duplication
    tester.actionKeyPress(OperatingSystem.isMacOSX() ? KeyEvent.VK_ALT : KeyEvent.VK_CONTROL);
    tester.actionMousePress(planComponent, new ComponentLocation(new Point(50, 170)));
    tester.actionMouseMove(planComponent, new ComponentLocation(new Point(51, 170)));
    // Check selection changed
    assertFalse("Selection didn't change", selectedItems.equals(frame.home.getSelectedItems()));
    assertEquals("Selection doesn't contain 4 items", 4, frame.home.getSelectedItems().size());
    assertEquals("No new wall", wallsCount + 2, frame.home.getWalls().size());
    assertEquals("No new piece", furnitureCount + 1, frame.home.getFurniture().size());
    assertEquals(
        "No new dimension lines", dimensionLinesCount + 1, frame.home.getDimensionLines().size());

    // 20. Duplicate and move items
    tester.actionMouseMove(planComponent, new ComponentLocation(new Point(70, 200)));
    // Check the piece moved and the original piece didn't move
    HomePieceOfFurniture movedPiece =
        Home.getFurnitureSubList(frame.home.getSelectedItems()).get(0);
    assertLocationAndOrientationEqualPiece(
        pieceX + 20 / planComponent.getScale(),
        pieceY + 30 / planComponent.getScale(),
        selectedPiece.getAngle(),
        movedPiece);
    assertLocationAndOrientationEqualPiece(pieceX, pieceY, selectedPiece.getAngle(), selectedPiece);

    // 21. Release Alt key
    tester.actionKeyRelease(OperatingSystem.isMacOSX() ? KeyEvent.VK_ALT : KeyEvent.VK_CONTROL);
    // Check original items replaced duplicated items
    assertTrue("Original items not selected", selectedItems.equals(frame.home.getSelectedItems()));
    assertLocationAndOrientationEqualPiece(
        pieceX + 20 / planComponent.getScale(),
        pieceY + 30 / planComponent.getScale(),
        selectedPiece.getAngle(),
        selectedPiece);
    assertFalse("Duplicated piece still in home", frame.home.getFurniture().contains(movedPiece));
    // Press Alt key again
    tester.actionKeyPress(OperatingSystem.isMacOSX() ? KeyEvent.VK_ALT : KeyEvent.VK_CONTROL);
    // Check the duplicated piece moved and the original piece moved back to its original location
    movedPiece = Home.getFurnitureSubList(frame.home.getSelectedItems()).get(0);
    assertLocationAndOrientationEqualPiece(
        pieceX + 20 / planComponent.getScale(),
        pieceY + 30 / planComponent.getScale(),
        selectedPiece.getAngle(),
        movedPiece);
    assertLocationAndOrientationEqualPiece(pieceX, pieceY, selectedPiece.getAngle(), selectedPiece);
    // Press Escape key
    tester.actionKeyStroke(KeyEvent.VK_ESCAPE);
    // Check no items where duplicated
    assertTrue("Original items not selected", selectedItems.equals(frame.home.getSelectedItems()));
    assertLocationAndOrientationEqualPiece(pieceX, pieceY, selectedPiece.getAngle(), selectedPiece);
    assertEquals("New walls created", wallsCount, frame.home.getWalls().size());
    assertEquals("New pieces created", furnitureCount, frame.home.getFurniture().size());
    assertEquals(
        "New dimension lines created", dimensionLinesCount, frame.home.getDimensionLines().size());
    tester.actionMouseRelease();

    // 22. Duplicate items
    tester.actionMousePress(planComponent, new ComponentLocation(new Point(50, 170)));
    tester.actionMouseMove(planComponent, new ComponentLocation(new Point(50, 190)));
    tester.actionMouseRelease();
    tester.actionKeyRelease(OperatingSystem.isMacOSX() ? KeyEvent.VK_ALT : KeyEvent.VK_CONTROL);
    // Check the duplicated piece moved and the original piece didn't move
    List<Selectable> movedItems = frame.home.getSelectedItems();
    assertEquals("Selection doesn't contain 4 items", 4, movedItems.size());
    movedPiece = Home.getFurnitureSubList(movedItems).get(0);
    assertLocationAndOrientationEqualPiece(
        pieceX, pieceY + 20 / planComponent.getScale(), selectedPiece.getAngle(), movedPiece);
    assertLocationAndOrientationEqualPiece(pieceX, pieceY, selectedPiece.getAngle(), selectedPiece);
    // Check the duplicated walls are joined to each other
    List<Wall> movedWalls = Home.getWallsSubList(movedItems);
    Wall movedWall1 = movedWalls.get(0);
    Wall movedWall2 = movedWalls.get(1);
    assertFalse("First moved wall not new", selectedItems.contains(movedWall1));
    assertFalse("Second moved wall not new", selectedItems.contains(movedWall2));
    assertSame("First moved wall not joined to second one", movedWall2, movedWall1.getWallAtEnd());
    assertSame(
        "Second moved wall not joined to first one", movedWall1, movedWall2.getWallAtStart());

    // 23. Undo duplication
    tester.invokeAndWait(
        new Runnable() {
          public void run() {
            frame.undoButton.doClick();
          }
        });
    // Check piece and walls don't belong to home
    assertFalse("Piece still in home", frame.home.getFurniture().contains(movedPiece));
    assertFalse("First wall still in home", frame.home.getWalls().contains(movedWall1));
    assertFalse("Second wall still in home", frame.home.getWalls().contains(movedWall2));
    // Check original items are selected
    assertTrue("Original items not selected", selectedItems.equals(frame.home.getSelectedItems()));
    // Redo
    tester.invokeAndWait(
        new Runnable() {
          public void run() {
            frame.redoButton.doClick();
          }
        });
    // Check piece and walls belong to home
    assertTrue("Piece not in home", frame.home.getFurniture().contains(movedPiece));
    assertTrue("First wall not in home", frame.home.getWalls().contains(movedWall1));
    assertTrue("Second wall not in home", frame.home.getWalls().contains(movedWall2));
    // Check moved items are selected
    assertTrue("Original items not selected", movedItems.equals(frame.home.getSelectedItems()));
  }
  public ViewerHelper(final JApplet applet) {
    // Create default user preferences with no catalog
    final UserPreferences preferences =
        new UserPreferences() {
          @Override
          public void addLanguageLibrary(String languageLibraryName) throws RecorderException {
            throw new UnsupportedOperationException();
          }

          @Override
          public boolean languageLibraryExists(String languageLibraryName)
              throws RecorderException {
            throw new UnsupportedOperationException();
          }

          @Override
          public void addFurnitureLibrary(String furnitureLibraryName) throws RecorderException {
            throw new UnsupportedOperationException();
          }

          @Override
          public boolean furnitureLibraryExists(String furnitureLibraryName)
              throws RecorderException {
            throw new UnsupportedOperationException();
          }

          @Override
          public boolean texturesLibraryExists(String name) throws RecorderException {
            throw new UnsupportedOperationException();
          }

          @Override
          public void addTexturesLibrary(String name) throws RecorderException {
            throw new UnsupportedOperationException();
          }

          @Override
          public void write() throws RecorderException {
            throw new UnsupportedOperationException();
          }

          @Override
          public boolean isNavigationPanelVisible() {
            return "true".equalsIgnoreCase(applet.getParameter(NAVIGATION_PANEL));
          }
        };

    // Create a view factory able to instantiate only a 3D view and a threaded task view
    final ViewFactory viewFactory =
        new ViewFactory() {
          public View createBackgroundImageWizardStepsView(
              BackgroundImage backgroundImage,
              UserPreferences preferences,
              BackgroundImageWizardController backgroundImageWizardController) {
            throw new UnsupportedOperationException();
          }

          public View createFurnitureCatalogView(
              FurnitureCatalog catalog,
              UserPreferences preferences,
              FurnitureCatalogController furnitureCatalogController) {
            throw new UnsupportedOperationException();
          }

          public View createFurnitureView(
              Home home, UserPreferences preferences, FurnitureController furnitureController) {
            throw new UnsupportedOperationException();
          }

          public HelpView createHelpView(
              UserPreferences preferences, HelpController helpController) {
            throw new UnsupportedOperationException();
          }

          public DialogView createHome3DAttributesView(
              UserPreferences preferences, Home3DAttributesController home3DAttributesController) {
            throw new UnsupportedOperationException();
          }

          public DialogView createHomeFurnitureView(
              UserPreferences preferences, HomeFurnitureController homeFurnitureController) {
            throw new UnsupportedOperationException();
          }

          public HomeView createHomeView(
              Home home, UserPreferences preferences, HomeController homeController) {
            throw new UnsupportedOperationException();
          }

          public ImportedFurnitureWizardStepsView createImportedFurnitureWizardStepsView(
              CatalogPieceOfFurniture piece,
              String modelName,
              boolean importHomePiece,
              UserPreferences preferences,
              ImportedFurnitureWizardController importedFurnitureWizardController) {
            throw new UnsupportedOperationException();
          }

          public View createImportedTextureWizardStepsView(
              CatalogTexture texture,
              String textureName,
              UserPreferences preferences,
              ImportedTextureWizardController importedTextureWizardController) {
            throw new UnsupportedOperationException();
          }

          public DialogView createLabelView(
              boolean modification, UserPreferences preferences, LabelController labelController) {
            throw new UnsupportedOperationException();
          }

          public DialogView createPageSetupView(
              UserPreferences preferences, PageSetupController pageSetupController) {
            throw new UnsupportedOperationException();
          }

          public PlanView createPlanView(
              Home home, UserPreferences preferences, PlanController planController) {
            throw new UnsupportedOperationException();
          }

          public DialogView createPrintPreviewView(
              Home home,
              UserPreferences preferences,
              HomeController homeController,
              PrintPreviewController printPreviewController) {
            throw new UnsupportedOperationException();
          }

          public DialogView createRoomView(
              UserPreferences preferences, RoomController roomController) {
            throw new UnsupportedOperationException();
          }

          public TextureChoiceView createTextureChoiceView(
              UserPreferences preferences, TextureChoiceController textureChoiceController) {
            throw new UnsupportedOperationException();
          }

          public ThreadedTaskView createThreadedTaskView(
              String taskMessage, UserPreferences preferences, ThreadedTaskController controller) {
            return new ThreadedTaskPanel(taskMessage, preferences, controller) {
              private boolean taskRunning;

              public void setTaskRunning(boolean taskRunning, View executingView) {
                if (taskRunning && !this.taskRunning) {
                  // Display task panel directly at applet center if it's empty
                  this.taskRunning = taskRunning;
                  JPanel contentPane = new JPanel(new GridBagLayout());
                  contentPane.add(this, new GridBagConstraints());
                  applet.setContentPane(contentPane);
                  applet.getRootPane().revalidate();
                }
              }
            };
          }

          public DialogView createUserPreferencesView(
              UserPreferences preferences, UserPreferencesController userPreferencesController) {
            throw new UnsupportedOperationException();
          }

          public View createView3D(
              final Home home, UserPreferences preferences, final HomeController3D controller) {
            HomeComponent3D homeComponent3D = new HomeComponent3D(home, preferences, controller);
            // Add tab key to input map to change camera
            InputMap inputMap = homeComponent3D.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
            inputMap.put(KeyStroke.getKeyStroke("SPACE"), "changeCamera");
            ActionMap actionMap = homeComponent3D.getActionMap();
            actionMap.put(
                "changeCamera",
                new AbstractAction() {
                  public void actionPerformed(ActionEvent ev) {
                    if (home.getCamera() == home.getTopCamera()) {
                      controller.viewFromObserver();
                    } else {
                      controller.viewFromTop();
                    }
                  }
                });
            return homeComponent3D;
          }

          public DialogView createWallView(
              UserPreferences preferences, WallController wallController) {
            throw new UnsupportedOperationException();
          }

          public DialogView createWizardView(
              UserPreferences preferences, WizardController wizardController) {
            throw new UnsupportedOperationException();
          }

          public DialogView createPhotoView(
              Home home, UserPreferences preferences, PhotoController photoController) {
            throw new UnsupportedOperationException();
          }

          public DialogView createVideoView(
              Home home, UserPreferences preferences, VideoController videoController) {
            throw new UnsupportedOperationException();
          }

          public DialogView createCompassView(
              UserPreferences preferences, CompassController compassController) {
            throw new UnsupportedOperationException();
          }
        };

    // Force offscreen in 3D view under Plugin 2 and Mac OS X
    System.setProperty(
        "com.eteks.sweethome3d.j3d.useOffScreen3DView",
        String.valueOf(
            OperatingSystem.isMacOSX()
                && applet.getAppletContext() != null
                && applet
                    .getAppletContext()
                    .getClass()
                    .getName()
                    .startsWith("sun.plugin2.applet.Plugin2Manager")));

    initLookAndFeel();

    addComponent3DRenderingErrorObserver(applet.getRootPane(), preferences);

    // Retrieve displayed home
    String homeUrlParameter = applet.getParameter(HOME_URL_PARAMETER);
    if (homeUrlParameter == null) {
      homeUrlParameter = "default.sh3d";
    }
    // Retrieve ignoreCache parameter value
    String ignoreCacheParameter = applet.getParameter(IGNORE_CACHE_PARAMETER);
    final boolean ignoreCache =
        ignoreCacheParameter != null && "true".equalsIgnoreCase(ignoreCacheParameter);
    try {
      final URL homeUrl = new URL(applet.getDocumentBase(), homeUrlParameter);
      // Read home in a threaded task
      Callable<Void> openTask =
          new Callable<Void>() {
            public Void call() throws RecorderException {
              // Read home with application recorder
              Home openedHome = readHome(homeUrl, ignoreCache);
              displayHome(applet.getRootPane(), openedHome, preferences, viewFactory);
              return null;
            }
          };
      ThreadedTaskController.ExceptionHandler exceptionHandler =
          new ThreadedTaskController.ExceptionHandler() {
            public void handleException(Exception ex) {
              if (!(ex instanceof InterruptedRecorderException)) {
                if (ex instanceof RecorderException) {
                  showError(
                      applet.getRootPane(),
                      preferences.getLocalizedString(ViewerHelper.class, "openError", homeUrl));
                } else {
                  ex.printStackTrace();
                }
              }
            }
          };
      new ThreadedTaskController(
              openTask,
              preferences.getLocalizedString(ViewerHelper.class, "openMessage"),
              exceptionHandler,
              null,
              viewFactory)
          .executeTask(null);
    } catch (MalformedURLException ex) {
      showError(
          applet.getRootPane(),
          preferences.getLocalizedString(ViewerHelper.class, "openError", homeUrlParameter));
      return;
    }
  }