Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.eteks.sweethome3d.swing.PhotoPanel Maven / Gradle / Ivy
/*
* PhotoPanel.java 5 mai 2009
*
* Sweet Home 3D, Copyright (c) 2009 Emmanuel PUYBARET / eTeks
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package com.eteks.sweethome3d.swing;
import java.awt.CardLayout;
import java.awt.Component;
import java.awt.ComponentOrientation;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Window;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.event.ActionEvent;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferedImage;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.imageio.ImageIO;
import javax.swing.ActionMap;
import javax.swing.DefaultListCellRenderer;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JSeparator;
import javax.swing.JSpinner;
import javax.swing.KeyStroke;
import javax.swing.SpinnerDateModel;
import javax.swing.SwingUtilities;
import javax.swing.TransferHandler;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import com.eteks.sweethome3d.j3d.PhotoRenderer;
import com.eteks.sweethome3d.model.AspectRatio;
import com.eteks.sweethome3d.model.Camera;
import com.eteks.sweethome3d.model.Camera.Lens;
import com.eteks.sweethome3d.model.Home;
import com.eteks.sweethome3d.model.Selectable;
import com.eteks.sweethome3d.model.UserPreferences;
import com.eteks.sweethome3d.tools.OperatingSystem;
import com.eteks.sweethome3d.viewcontroller.AbstractPhotoController;
import com.eteks.sweethome3d.viewcontroller.ContentManager;
import com.eteks.sweethome3d.viewcontroller.DialogView;
import com.eteks.sweethome3d.viewcontroller.Object3DFactory;
import com.eteks.sweethome3d.viewcontroller.PhotoController;
import com.eteks.sweethome3d.viewcontroller.View;
/**
* A panel to edit photo creation.
* @author Emmanuel Puybaret
*/
public class PhotoPanel extends JPanel implements DialogView {
private enum ActionType {START_PHOTO_CREATION, STOP_PHOTO_CREATION, SAVE_PHOTO, CLOSE}
private static final String PHOTO_DIALOG_X_VISUAL_PROPERTY = "com.eteks.sweethome3d.swing.PhotoPanel.PhotoDialogX";
private static final String PHOTO_DIALOG_Y_VISUAL_PROPERTY = "com.eteks.sweethome3d.swing.PhotoPanel.PhotoDialogY";
private static final int MINIMUM_DELAY_BEFORE_DISCARDING_WITHOUT_WARNING = 30000;
private static final String WAIT_CARD = "wait";
private static final String PHOTO_CARD = "photo";
private final Home home;
private final UserPreferences preferences;
private final Object3DFactory object3dFactory;
private final PhotoController controller;
private ScaledImageComponent photoComponent;
private JLabel animatedWaitLabel;
private PhotoSizeAndQualityPanel sizeAndQualityPanel;
private Component advancedComponentsSeparator;
private JLabel dateLabel;
private JSpinner dateSpinner;
private JLabel timeLabel;
private JSpinner timeSpinner;
private JLabel dayNightLabel;
private JLabel lensLabel;
private JComboBox lensComboBox;
private JCheckBox ceilingLightEnabledCheckBox;
private String dialogTitle;
private JPanel photoPanel;
private CardLayout photoCardLayout;
private ExecutorService photoCreationExecutor;
private long photoCreationStartTime;
private JButton createButton;
private JButton saveButton;
private JButton closeButton;
private static PhotoPanel currentPhotoPanel; // Support only one photo panel opened at a time
public PhotoPanel(Home home,
UserPreferences preferences,
PhotoController controller) {
this(home, preferences, null, controller);
}
public PhotoPanel(Home home,
UserPreferences preferences,
Object3DFactory object3dFactory,
PhotoController controller) {
super(new GridBagLayout());
this.home = home;
this.preferences = preferences;
this.object3dFactory = object3dFactory;
this.controller = controller;
createActions(preferences);
createComponents(home, preferences, controller);
setMnemonics(preferences);
layoutComponents();
preferences.addPropertyChangeListener(UserPreferences.Property.LANGUAGE, new LanguageChangeListener(this));
}
/**
* Creates actions for variables.
*/
private void createActions(UserPreferences preferences) {
final ActionMap actions = getActionMap();
actions.put(ActionType.START_PHOTO_CREATION,
new ResourceAction(preferences, PhotoPanel.class, ActionType.START_PHOTO_CREATION.name(), true) {
@Override
public void actionPerformed(ActionEvent ev) {
startPhotoCreation();
}
});
actions.put(ActionType.STOP_PHOTO_CREATION,
new ResourceAction(preferences, PhotoPanel.class, ActionType.STOP_PHOTO_CREATION.name(), true) {
@Override
public void actionPerformed(ActionEvent ev) {
stopPhotoCreation(true);
}
});
actions.put(ActionType.SAVE_PHOTO,
new ResourceAction(preferences, PhotoPanel.class, ActionType.SAVE_PHOTO.name(), false) {
@Override
public void actionPerformed(ActionEvent ev) {
savePhoto();
}
});
actions.put(ActionType.CLOSE,
new ResourceAction(preferences, PhotoPanel.class, ActionType.CLOSE.name(), true) {
@Override
public void actionPerformed(ActionEvent ev) {
close();
}
});
}
/**
* Creates and initializes components.
*/
private void createComponents(final Home home,
final UserPreferences preferences,
final PhotoController controller) {
this.photoComponent = new ScaledImageComponent();
this.photoComponent.setPreferredSize(new Dimension(getToolkit().getScreenSize().width <= 1024 ? 320 : 400, 400));
// Under Mac OS X, set a transfer handler and a mouse listener on photo component
// to let the user drag and drop the created image (Windows support seems to fail)
if (OperatingSystem.isMacOSX()
&& !OperatingSystem.isJavaVersionBetween("1.7", "1.7.0_60")) {
this.photoComponent.setTransferHandler(new VisualTransferHandler() {
@Override
public int getSourceActions(JComponent component) {
return COPY_OR_MOVE;
}
@Override
protected Transferable createTransferable(JComponent component) {
return new Transferable() {
public Object getTransferData(DataFlavor flavor) {
return photoComponent.getImage();
}
public DataFlavor [] getTransferDataFlavors() {
return new DataFlavor [] {DataFlavor.imageFlavor};
}
public boolean isDataFlavorSupported(DataFlavor flavor) {
return flavor.equals(DataFlavor.imageFlavor);
}
};
}
@Override
public Icon getVisualRepresentation(Transferable transferable) {
try {
if (transferable.isDataFlavorSupported(DataFlavor.imageFlavor)) {
// Create a 128x128 icon from the transfered image
BufferedImage transferedImage =
(BufferedImage)transferable.getTransferData(DataFlavor.imageFlavor);
float scale = Math.min(1, 128f / Math.max(transferedImage.getWidth(), transferedImage.getHeight()));
BufferedImage iconImage = new BufferedImage(128, 128, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2D = (Graphics2D)iconImage.getGraphics();
g2D.scale(scale, scale);
g2D.drawRenderedImage(transferedImage, null);
g2D.dispose();
return new ImageIcon(iconImage);
}
} catch (UnsupportedFlavorException ex) {
// Use default representation
} catch (IOException ex) {
// Use default representation
}
return super.getVisualRepresentation(transferable);
}
});
this.photoComponent.addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(MouseEvent ev) {
if (SwingUtilities.isLeftMouseButton(ev)
&& photoComponent.getImage() != null
&& photoComponent.isPointInImage(ev.getX(), ev.getY())) {
photoComponent.getTransferHandler().exportAsDrag(photoComponent, ev, TransferHandler.COPY);
}
}
});
}
this.animatedWaitLabel = new JLabel(new ImageIcon(PhotoPanel.class.getResource("resources/animatedWait.gif")));
// Create size and quality panel
this.sizeAndQualityPanel = new PhotoSizeAndQualityPanel(home, preferences, controller);
controller.addPropertyChangeListener(AbstractPhotoController.Property.QUALITY,
new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent ev) {
updateAdvancedComponents();
}
});
this.advancedComponentsSeparator = new JSeparator();
// Create date and time labels and spinners bound to TIME controller property
Date time = new Date(Camera.convertTimeToTimeZone(controller.getTime(), TimeZone.getDefault().getID()));
this.dateLabel = new JLabel();
final SpinnerDateModel dateSpinnerModel = new SpinnerDateModel();
dateSpinnerModel.setValue(time);
this.dateSpinner = new JSpinner(dateSpinnerModel);
String datePattern = ((SimpleDateFormat)DateFormat.getDateInstance(DateFormat.SHORT)).toPattern();
if (datePattern.indexOf("yyyy") == -1) {
datePattern = datePattern.replace("yy", "yyyy");
}
JSpinner.DateEditor dateEditor = new JSpinner.DateEditor(this.dateSpinner, datePattern);
this.dateSpinner.setEditor(dateEditor);
SwingTools.addAutoSelectionOnFocusGain(dateEditor.getTextField());
this.timeLabel = new JLabel();
final SpinnerDateModel timeSpinnerModel = new SpinnerDateModel();
timeSpinnerModel.setValue(time);
this.timeSpinner = new JSpinner(timeSpinnerModel);
// From http://en.wikipedia.org/wiki/12-hour_clock#Use_by_country
String [] twelveHoursCountries = {
"AU", // Australia
"BD", // Bangladesh
"CA", // Canada (excluding Quebec, in French)
"CO", // Colombia
"EG", // Egypt
"HN", // Honduras
"JO", // Jordan
"MX", // Mexico
"MY", // Malaysia
"NI", // Nicaragua
"NZ", // New Zealand
"PH", // Philippines
"PK", // Pakistan
"SA", // Saudi Arabia
"SV", // El Salvador
"US", // United States
"VE"}; // Venezuela
SimpleDateFormat timeInstance;
if ("en".equals(Locale.getDefault().getLanguage())) {
if (Arrays.binarySearch(twelveHoursCountries, Locale.getDefault().getCountry()) >= 0) {
timeInstance = (SimpleDateFormat)DateFormat.getTimeInstance(DateFormat.SHORT, Locale.US); // 12 hours notation
} else {
timeInstance = (SimpleDateFormat)DateFormat.getTimeInstance(DateFormat.SHORT, Locale.UK); // 24 hours notation
}
} else {
timeInstance = (SimpleDateFormat)DateFormat.getTimeInstance(DateFormat.SHORT);
}
JSpinner.DateEditor timeEditor = new JSpinner.DateEditor(this.timeSpinner, timeInstance.toPattern());
this.timeSpinner.setEditor(timeEditor);
SwingTools.addAutoSelectionOnFocusGain(timeEditor.getTextField());
final PropertyChangeListener timeChangeListener = new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent ev) {
Date date = new Date(Camera.convertTimeToTimeZone(controller.getTime(), TimeZone.getDefault().getID()));
dateSpinnerModel.setValue(date);
timeSpinnerModel.setValue(date);
}
};
controller.addPropertyChangeListener(PhotoController.Property.TIME, timeChangeListener);
final ChangeListener dateTimeChangeListener = new ChangeListener() {
public void stateChanged(ChangeEvent ev) {
controller.removePropertyChangeListener(PhotoController.Property.TIME, timeChangeListener);
// Merge date and time
GregorianCalendar dateCalendar = new GregorianCalendar();
dateCalendar.setTime((Date)dateSpinnerModel.getValue());
GregorianCalendar timeCalendar = new GregorianCalendar();
timeCalendar.setTime((Date)timeSpinnerModel.getValue());
Calendar utcCalendar = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
utcCalendar.set(GregorianCalendar.YEAR, dateCalendar.get(GregorianCalendar.YEAR));
utcCalendar.set(GregorianCalendar.MONTH, dateCalendar.get(GregorianCalendar.MONTH));
utcCalendar.set(GregorianCalendar.DAY_OF_MONTH, dateCalendar.get(GregorianCalendar.DAY_OF_MONTH));
utcCalendar.set(GregorianCalendar.HOUR_OF_DAY, timeCalendar.get(GregorianCalendar.HOUR_OF_DAY));
utcCalendar.set(GregorianCalendar.MINUTE, timeCalendar.get(GregorianCalendar.MINUTE));
utcCalendar.set(GregorianCalendar.SECOND, timeCalendar.get(GregorianCalendar.SECOND));
controller.setTime(utcCalendar.getTimeInMillis());
controller.addPropertyChangeListener(PhotoController.Property.TIME, timeChangeListener);
}
};
dateSpinnerModel.addChangeListener(dateTimeChangeListener);
timeSpinnerModel.addChangeListener(dateTimeChangeListener);
this.dayNightLabel = new JLabel();
final ImageIcon dayIcon = SwingTools.getScaledImageIcon(PhotoPanel.class.getResource("resources/day.png"));
final ImageIcon nightIcon = SwingTools.getScaledImageIcon(PhotoPanel.class.getResource("resources/night.png"));
PropertyChangeListener dayNightListener = new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent ev) {
if (home.getCompass().getSunElevation(
Camera.convertTimeToTimeZone(controller.getTime(), home.getCompass().getTimeZone())) > 0) {
dayNightLabel.setIcon(dayIcon);
} else {
dayNightLabel.setIcon(nightIcon);
}
}
};
controller.addPropertyChangeListener(PhotoController.Property.TIME, dayNightListener);
home.getCompass().addPropertyChangeListener(dayNightListener);
dayNightListener.propertyChange(null);
// Create lens label and combo box
this.lensLabel = new JLabel();
this.lensComboBox = new JComboBox(Camera.Lens.values());
this.lensComboBox.setRenderer(new DefaultListCellRenderer() {
@Override
public Component getListCellRendererComponent(JList list, Object value,
int index, boolean isSelected, boolean cellHasFocus) {
String displayedValue;
switch ((Camera.Lens)value) {
case NORMAL :
displayedValue = preferences.getLocalizedString(PhotoPanel.class, "lensComboBox.normalLens.text");
break;
case SPHERICAL :
displayedValue = preferences.getLocalizedString(PhotoPanel.class, "lensComboBox.sphericalLens.text");
break;
case FISHEYE :
displayedValue = preferences.getLocalizedString(PhotoPanel.class, "lensComboBox.fisheyeLens.text");
break;
case PINHOLE :
default :
displayedValue = preferences.getLocalizedString(PhotoPanel.class, "lensComboBox.pinholeLens.text");
break;
}
return super.getListCellRendererComponent(list, displayedValue, index, isSelected,
cellHasFocus);
}
});
this.lensComboBox.setSelectedItem(controller.getLens());
controller.addPropertyChangeListener(PhotoController.Property.LENS,
new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent ev) {
lensComboBox.setSelectedItem(controller.getLens());
}
});
this.lensComboBox.addItemListener(new ItemListener() {
public void itemStateChanged(ItemEvent ev) {
Camera.Lens lens = (Camera.Lens)lensComboBox.getSelectedItem();
controller.setLens(lens);
if (lens == Camera.Lens.SPHERICAL) {
controller.setAspectRatio(AspectRatio.RATIO_2_1);
} else if (lens == Camera.Lens.FISHEYE) {
controller.setAspectRatio(AspectRatio.SQUARE_RATIO);
}
updateRatioComponents();
}
});
this.ceilingLightEnabledCheckBox = new JCheckBox();
this.ceilingLightEnabledCheckBox.setSelected(controller.getCeilingLightColor() > 0);
controller.addPropertyChangeListener(AbstractPhotoController.Property.CEILING_LIGHT_COLOR,
new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent ev) {
ceilingLightEnabledCheckBox.setSelected(controller.getCeilingLightColor() > 0);
}
});
this.ceilingLightEnabledCheckBox.addItemListener(new ItemListener() {
public void itemStateChanged(ItemEvent ev) {
controller.setCeilingLightColor(ceilingLightEnabledCheckBox.isSelected() ? 0xD0D0D0 : 0);
}
});
final JComponent view3D = (JComponent)controller.get3DView();
controller.set3DViewAspectRatio((float)view3D.getWidth() / view3D.getHeight());
final ActionMap actionMap = getActionMap();
this.createButton = new JButton(actionMap.get(ActionType.START_PHOTO_CREATION));
this.saveButton = new JButton(actionMap.get(ActionType.SAVE_PHOTO));
this.closeButton = new JButton(actionMap.get(ActionType.CLOSE));
setComponentTexts(preferences);
updateRatioComponents();
}
/**
* Sets the texts of the components.
*/
private void setComponentTexts(UserPreferences preferences) {
this.dateLabel.setText(SwingTools.getLocalizedLabelText(preferences,
PhotoPanel.class, "dateLabel.text"));
this.timeLabel.setText(SwingTools.getLocalizedLabelText(preferences,
PhotoPanel.class, "timeLabel.text"));
this.lensLabel.setText(SwingTools.getLocalizedLabelText(preferences,
PhotoPanel.class, "lensLabel.text"));
this.ceilingLightEnabledCheckBox.setText(SwingTools.getLocalizedLabelText(preferences,
PhotoPanel.class, "ceilingLightEnabledCheckBox.text"));
this.dialogTitle = preferences.getLocalizedString(PhotoPanel.class, "createPhoto.title");
Window window = SwingUtilities.getWindowAncestor(this);
if (window != null) {
((JDialog)window).setTitle(this.dialogTitle);
}
// Buttons text changes automatically through their action
}
/**
* Sets components mnemonics and label / component associations.
*/
private void setMnemonics(UserPreferences preferences) {
if (!OperatingSystem.isMacOSX()) {
this.dateLabel.setDisplayedMnemonic(KeyStroke.getKeyStroke(preferences.getLocalizedString(
PhotoPanel.class, "dateLabel.mnemonic")).getKeyCode());
this.dateLabel.setLabelFor(this.dateSpinner);
this.timeLabel.setDisplayedMnemonic(KeyStroke.getKeyStroke(preferences.getLocalizedString(
PhotoPanel.class, "timeLabel.mnemonic")).getKeyCode());
this.timeLabel.setLabelFor(this.timeSpinner);
this.lensLabel.setDisplayedMnemonic(KeyStroke.getKeyStroke(preferences.getLocalizedString(
PhotoPanel.class, "lensLabel.mnemonic")).getKeyCode());
this.lensLabel.setLabelFor(this.lensComboBox);
this.ceilingLightEnabledCheckBox.setMnemonic(KeyStroke.getKeyStroke(preferences.getLocalizedString(
PhotoPanel.class, "ceilingLightEnabledCheckBox.mnemonic")).getKeyCode());
}
}
/**
* Preferences property listener bound to this panel with a weak reference to avoid
* strong link between user preferences and this panel.
*/
public static class LanguageChangeListener implements PropertyChangeListener {
private final WeakReference photoPanel;
public LanguageChangeListener(PhotoPanel photoPanel) {
this.photoPanel = new WeakReference(photoPanel);
}
public void propertyChange(PropertyChangeEvent ev) {
// If photo panel was garbage collected, remove this listener from preferences
PhotoPanel photoPanel = this.photoPanel.get();
UserPreferences preferences = (UserPreferences)ev.getSource();
if (photoPanel == null) {
preferences.removePropertyChangeListener(UserPreferences.Property.LANGUAGE, this);
} else {
photoPanel.setComponentOrientation(ComponentOrientation.getOrientation(Locale.getDefault()));
photoPanel.setComponentTexts(preferences);
photoPanel.setMnemonics(preferences);
}
}
}
/**
* Layouts panel components in panel with their labels.
*/
private void layoutComponents() {
int labelAlignment = OperatingSystem.isMacOSX()
? JLabel.TRAILING
: JLabel.LEADING;
// Add animatedWaitLabel and photoComponent to a card panel
this.photoCardLayout = new CardLayout();
this.photoPanel = new JPanel(this.photoCardLayout);
photoPanel.add(this.photoComponent, PHOTO_CARD);
photoPanel.add(this.animatedWaitLabel, WAIT_CARD);
// First row
add(this.photoPanel, new GridBagConstraints(
0, 0, 3, 1, 1, 1, GridBagConstraints.CENTER,
GridBagConstraints.BOTH, new Insets(0, 0, 5, 0), 0, 0));
// Second row
// Add a dummy label at left and right
add(new JLabel(), new GridBagConstraints(
0, 1, 1, 8, 0.5f, 0, GridBagConstraints.CENTER,
GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
add(new JLabel(), new GridBagConstraints(
2, 1, 1, 8, 0.5f, 0, GridBagConstraints.CENTER,
GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
add(this.sizeAndQualityPanel, new GridBagConstraints(
1, 1, 1, 1, 0, 0, GridBagConstraints.CENTER,
GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0));
// Third row
add(this.advancedComponentsSeparator, new GridBagConstraints(
1, 2, 1, 1, 0, 0, GridBagConstraints.CENTER,
GridBagConstraints.HORIZONTAL, new Insets(3, 0, 3, 0), 0, 0));
// Forth row
JPanel advancedPanel = new JPanel(new GridBagLayout());
advancedPanel.add(this.dateLabel, new GridBagConstraints(
0, 1, 1, 1, 0, 0, GridBagConstraints.CENTER,
GridBagConstraints.HORIZONTAL, new Insets(0, 0, 5, 5), 0, 0));
this.dateLabel.setHorizontalAlignment(labelAlignment);
advancedPanel.add(this.dateSpinner, new GridBagConstraints(
1, 1, 1, 1, 1, 0, GridBagConstraints.LINE_START,
GridBagConstraints.HORIZONTAL, new Insets(0, 0, 5, 10), 0, 0));
advancedPanel.add(this.timeLabel, new GridBagConstraints(
2, 1, 1, 1, 0, 0, GridBagConstraints.CENTER,
GridBagConstraints.HORIZONTAL, new Insets(0, 0, 5, 5), 0, 0));
this.timeLabel.setHorizontalAlignment(labelAlignment);
advancedPanel.add(this.timeSpinner, new GridBagConstraints(
3, 1, 1, 1, 1, 0, GridBagConstraints.LINE_START,
GridBagConstraints.HORIZONTAL, new Insets(0, 0, 5, 5), 0, 0));
advancedPanel.add(this.dayNightLabel, new GridBagConstraints(
4, 1, 1, 1, 0, 0, GridBagConstraints.LINE_START,
GridBagConstraints.NONE, new Insets(0, 0, 5, 0), 0, 0));
// Last row
advancedPanel.add(this.lensLabel, new GridBagConstraints(
0, 2, 1, 1, 0, 0, GridBagConstraints.CENTER,
GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 5), 0, 0));
this.lensLabel.setHorizontalAlignment(labelAlignment);
advancedPanel.add(this.lensComboBox, new GridBagConstraints(
1, 2, 1, 1, 0, 0, GridBagConstraints.LINE_START,
GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 10), 0, 0));
advancedPanel.add(this.ceilingLightEnabledCheckBox, new GridBagConstraints(
2, 2, 3, 1, 0, 0, GridBagConstraints.CENTER,
GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0));
add(advancedPanel, new GridBagConstraints(
1, 3, 1, 1, 0, 0, GridBagConstraints.CENTER,
GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0));
}
private void updateAdvancedComponents() {
Component root = SwingUtilities.getRoot(this);
if (root != null) {
boolean highQuality = controller.getQuality() >= 2;
boolean advancedComponentsVisible = this.advancedComponentsSeparator.isVisible();
if (advancedComponentsVisible != highQuality) {
setAdvancedComponentsVisible(highQuality);
updateRatioComponents();
int componentsHeight = this.advancedComponentsSeparator.getPreferredSize().height + 6
+ this.dateSpinner.getPreferredSize().height + 5
+ this.lensComboBox.getPreferredSize().height;
root.setSize(root.getWidth(),
root.getHeight() + (advancedComponentsVisible ? -componentsHeight : componentsHeight));
}
}
}
private void setAdvancedComponentsVisible(boolean visible) {
this.advancedComponentsSeparator.setVisible(visible);
this.dateLabel.setVisible(visible);
this.dateSpinner.setVisible(visible);
this.timeLabel.setVisible(visible);
this.timeSpinner.setVisible(visible);
this.dayNightLabel.setVisible(visible);
this.lensLabel.setVisible(visible);
this.lensComboBox.setVisible(visible);
this.ceilingLightEnabledCheckBox.setVisible(visible);
}
/**
* Updates photo height.
*/
private void updateRatioComponents() {
Lens lens = this.controller.getLens();
boolean fixedProportions = this.lensComboBox.isVisible()
&& (lens == Camera.Lens.FISHEYE
|| lens == Camera.Lens.SPHERICAL);
this.sizeAndQualityPanel.setProportionsChoiceEnabled(!fixedProportions);
}
/**
* Displays this panel in a non modal dialog.
*/
public void displayView(View parentView) {
if (currentPhotoPanel == this) {
SwingUtilities.getWindowAncestor(PhotoPanel.this).toFront();
} else {
if (currentPhotoPanel != null) {
currentPhotoPanel.close();
}
final JOptionPane optionPane = new JOptionPane(this,
JOptionPane.PLAIN_MESSAGE, JOptionPane.OK_CANCEL_OPTION,
null, new Object [] {this.createButton, this.saveButton, this.closeButton}, this.createButton);
if (parentView != null) {
optionPane.setComponentOrientation(((JComponent)parentView).getComponentOrientation());
}
final JDialog dialog = optionPane.createDialog(SwingUtilities.getRootPane((Component)parentView), this.dialogTitle);
dialog.setModal(false);
Component homeRoot = SwingUtilities.getRoot((Component)parentView);
Point dialogLocation = null;
if (homeRoot != null) {
// Restore location if it exists
Number x = this.home.getNumericProperty(PHOTO_DIALOG_X_VISUAL_PROPERTY);
Number y = this.home.getNumericProperty(PHOTO_DIALOG_Y_VISUAL_PROPERTY);
int windowRightBorder = homeRoot.getX() + homeRoot.getWidth();
Dimension screenSize = getToolkit().getScreenSize();
Insets screenInsets = getToolkit().getScreenInsets(getGraphicsConfiguration());
int screenRightBorder = screenSize.width - screenInsets.right;
// Check dialog isn't too high
int screenHeight = screenSize.height - screenInsets.top - screenInsets.bottom;
if (OperatingSystem.isLinux() && screenHeight == screenSize.height) {
// Let's consider that under Linux at least an horizontal bar exists
screenHeight -= 30;
}
int screenBottomBorder = screenSize.height - screenInsets.bottom;
int dialogWidth = dialog.getWidth();
if (dialog.getHeight() > screenHeight) {
dialog.setSize(dialogWidth, screenHeight);
}
int dialogHeight = dialog.getHeight();
if (x != null && y != null
&& x.intValue() + dialogWidth <= screenRightBorder
&& y.intValue() + dialogHeight <= screenBottomBorder) {
dialogLocation = new Point(x.intValue(), y.intValue());
} else if (screenRightBorder - windowRightBorder > dialogWidth / 2
|| dialogHeight == screenHeight) {
// If there some space left at the right of the window,
// move the dialog to the right of window
dialogLocation = new Point(Math.min(windowRightBorder + 5, screenRightBorder - dialogWidth),
Math.max(Math.min(homeRoot.getY(), screenSize.height - dialogHeight - screenInsets.bottom), screenInsets.top));
}
}
if (dialogLocation != null
&& SwingTools.isRectangleVisibleAtScreen(new Rectangle(dialogLocation, dialog.getSize()))) {
dialog.setLocationByPlatform(false);
dialog.setLocation(dialogLocation);
} else {
dialog.setLocationByPlatform(true);
}
dialog.addWindowListener(new WindowAdapter() {
public void windowClosed(WindowEvent ev) {
stopPhotoCreation(false);
currentPhotoPanel = null;
}
});
updateAdvancedComponents();
dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
dialog.addComponentListener(new ComponentAdapter() {
@Override
public void componentHidden(ComponentEvent ev) {
if (optionPane.getValue() != null
&& optionPane.getValue() != JOptionPane.UNINITIALIZED_VALUE) {
close();
}
}
@Override
public void componentMoved(ComponentEvent ev) {
controller.setHomeProperty(PHOTO_DIALOG_X_VISUAL_PROPERTY, String.valueOf(dialog.getX()));
controller.setHomeProperty(PHOTO_DIALOG_Y_VISUAL_PROPERTY, String.valueOf(dialog.getY()));
}
});
dialog.setVisible(true);
currentPhotoPanel = this;
}
}
/**
* Creates the photo image depending on the quality requested by the user.
*/
private void startPhotoCreation() {
this.photoComponent.setImage(null);
this.sizeAndQualityPanel.setEnabled(false);
this.dateSpinner.setEnabled(false);
this.timeSpinner.setEnabled(false);
this.lensComboBox.setEnabled(false);
this.ceilingLightEnabledCheckBox.setEnabled(false);
getActionMap().get(ActionType.SAVE_PHOTO).setEnabled(false);
getRootPane().setDefaultButton(this.createButton);
this.createButton.setAction(getActionMap().get(ActionType.STOP_PHOTO_CREATION));
this.photoCardLayout.show(this.photoPanel, WAIT_CARD);
// Compute photo in an other executor thread
// Use a clone of home because the user can modify home during photo computation
final Home home = this.home.clone();
List emptySelection = Collections.emptyList();
home.setSelectedItems(emptySelection);
this.photoCreationExecutor = Executors.newSingleThreadExecutor();
this.photoCreationExecutor.execute(new Runnable() {
public void run() {
computePhoto(home);
}
});
}
/**
* Computes the photo of the given home.
* Caution : this method must be thread safe because it's called from an executor.
*/
private void computePhoto(Home home) {
this.photoCreationStartTime = System.currentTimeMillis();
BufferedImage image = null;
try {
int quality = this.controller.getQuality();
int imageWidth = this.controller.getWidth();
int imageHeight = this.controller.getHeight();
if (quality >= 2) {
// Use photo renderer
PhotoRenderer photoRenderer = new PhotoRenderer(home, this.object3dFactory,
quality == 2
? PhotoRenderer.Quality.LOW
: PhotoRenderer.Quality.HIGH);
int bestImageHeight;
// Check correct ratio if lens is fisheye or spherical
Camera camera = home.getCamera();
if (camera.getLens() == Camera.Lens.FISHEYE) {
bestImageHeight = imageWidth;
} else if (camera.getLens() == Camera.Lens.SPHERICAL) {
bestImageHeight = imageWidth / 2;
} else {
bestImageHeight = imageHeight;
}
if (photoCreationExecutor != null) {
image = new BufferedImage(imageWidth, bestImageHeight, BufferedImage.TYPE_INT_RGB);
this.photoComponent.setImage(image);
EventQueue.invokeLater(new Runnable() {
public void run() {
photoCardLayout.show(photoPanel, PHOTO_CARD);
}
});
photoRenderer.render(image, camera, this.photoComponent);
photoRenderer.dispose();
}
} else {
// Compute 3D view offscreen image
HomeComponent3D homeComponent3D = new HomeComponent3D(
home, this.preferences, this.object3dFactory, quality == 1, null);
image = homeComponent3D.getOffScreenImage(imageWidth, imageHeight);
}
} catch (OutOfMemoryError ex) {
image = getErrorImage();
throw ex;
} catch (IllegalStateException ex) {
image = getErrorImage();
throw ex;
} catch (IOException ex) {
image = getErrorImage();
} finally {
final BufferedImage photoImage = this.photoCreationExecutor != null
? image
: null;
EventQueue.invokeLater(new Runnable() {
public void run() {
getActionMap().get(ActionType.SAVE_PHOTO).setEnabled(photoImage != null);
if (photoImage != null) {
getRootPane().setDefaultButton(saveButton);
}
createButton.setAction(getActionMap().get(ActionType.START_PHOTO_CREATION));
photoComponent.setImage(photoImage);
sizeAndQualityPanel.setEnabled(true);
updateRatioComponents();
dateSpinner.setEnabled(true);
timeSpinner.setEnabled(true);
lensComboBox.setEnabled(true);
ceilingLightEnabledCheckBox.setEnabled(true);
photoCardLayout.show(photoPanel, PHOTO_CARD);
photoCreationExecutor = null;
}
});
}
}
/**
* Returns the image used in case of an error.
*/
private BufferedImage getErrorImage() {
Icon errorIcon = IconManager.getInstance().getErrorIcon(16);
BufferedImage errorImage = new BufferedImage(
errorIcon.getIconWidth(), errorIcon.getIconHeight(), BufferedImage.TYPE_INT_ARGB);
Graphics2D g2D = (Graphics2D)errorImage.getGraphics();
errorIcon.paintIcon(this, g2D, 0, 0);
g2D.dispose();
return errorImage;
}
/**
* Stops photo creation.
*/
private void stopPhotoCreation(boolean confirmStop) {
if (this.photoCreationExecutor != null
// Confirm the stop if a rendering has been running for more than 30 s
&& (!confirmStop
|| System.currentTimeMillis() - this.photoCreationStartTime < MINIMUM_DELAY_BEFORE_DISCARDING_WITHOUT_WARNING
|| JOptionPane.showConfirmDialog(getRootPane(),
this.preferences.getLocalizedString(PhotoPanel.class, "confirmStopCreation.message"),
this.preferences.getLocalizedString(PhotoPanel.class, "confirmStopCreation.title"),
JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE) == JOptionPane.YES_OPTION)) {
if (this.photoCreationExecutor != null) { // Check a second time in case rendering stopped meanwhile
// Will interrupt executor thread
this.photoCreationExecutor.shutdownNow();
this.photoCreationExecutor = null;
this.createButton.setAction(getActionMap().get(ActionType.START_PHOTO_CREATION));
}
}
}
/**
* Saves the created image.
*/
private void savePhoto() {
String pngFile = this.controller.getContentManager().showSaveDialog(this,
this.preferences.getLocalizedString(PhotoPanel.class, "savePhotoDialog.title"),
ContentManager.ContentType.PNG, this.home.getName());
try {
if (pngFile != null) {
ImageIO.write(this.photoComponent.getImage(), "PNG", new File(pngFile));
}
} catch (IOException ex) {
String messageFormat = this.preferences.getLocalizedString(PhotoPanel.class, "savePhotoError.message");
JOptionPane.showMessageDialog(SwingUtilities.getRootPane(this), String.format(messageFormat, ex.getMessage()),
this.preferences.getLocalizedString(PhotoPanel.class, "savePhotoError.title"), JOptionPane.ERROR_MESSAGE);
}
}
/**
* Manages closing of this pane.
*/
private void close() {
Window window = SwingUtilities.getWindowAncestor(this);
if (window.isDisplayable()) {
window.dispose();
}
}
}