2
* HomeFramePane.java 1 sept. 2006
4
* Sweet Home 3D, Copyright (c) 2006 Emmanuel PUYBARET / eTeks <info@eteks.com>
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.
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.
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
20
package com.eteks.sweethome3d;
22
import java.awt.Component;
23
import java.awt.ComponentOrientation;
24
import java.awt.Dimension;
25
import java.awt.EventQueue;
26
import java.awt.Frame;
27
import java.awt.Image;
28
import java.awt.Insets;
29
import java.awt.Rectangle;
30
import java.awt.Window;
31
import java.awt.event.ActionEvent;
32
import java.awt.event.ActionListener;
33
import java.awt.event.ComponentAdapter;
34
import java.awt.event.ComponentEvent;
35
import java.awt.event.WindowAdapter;
36
import java.awt.event.WindowEvent;
37
import java.beans.PropertyChangeEvent;
38
import java.beans.PropertyChangeListener;
40
import java.lang.ref.WeakReference;
41
import java.util.ArrayList;
42
import java.util.List;
43
import java.util.Locale;
45
import javax.swing.ImageIcon;
46
import javax.swing.JComponent;
47
import javax.swing.JFrame;
48
import javax.swing.JRootPane;
49
import javax.swing.Timer;
51
import com.eteks.sweethome3d.model.CollectionEvent;
52
import com.eteks.sweethome3d.model.CollectionListener;
53
import com.eteks.sweethome3d.model.Home;
54
import com.eteks.sweethome3d.model.HomeApplication;
55
import com.eteks.sweethome3d.model.UserPreferences;
56
import com.eteks.sweethome3d.swing.SwingTools;
57
import com.eteks.sweethome3d.tools.OperatingSystem;
58
import com.eteks.sweethome3d.viewcontroller.ContentManager;
59
import com.eteks.sweethome3d.viewcontroller.HomeController;
60
import com.eteks.sweethome3d.viewcontroller.HomeView;
61
import com.eteks.sweethome3d.viewcontroller.View;
64
* A pane that displays a
65
* {@link com.eteks.sweethome3d.swing.HomePane home pane} in a frame.
66
* @author Emmanuel Puybaret
68
public class HomeFramePane extends JRootPane implements View {
69
private static final String FRAME_X_VISUAL_PROPERTY = "com.eteks.sweethome3d.SweetHome3D.FrameX";
70
private static final String FRAME_Y_VISUAL_PROPERTY = "com.eteks.sweethome3d.SweetHome3D.FrameY";
71
private static final String FRAME_WIDTH_VISUAL_PROPERTY = "com.eteks.sweethome3d.SweetHome3D.FrameWidth";
72
private static final String FRAME_HEIGHT_VISUAL_PROPERTY = "com.eteks.sweethome3d.SweetHome3D.FrameHeight";
73
private static final String FRAME_MAXIMIZED_VISUAL_PROPERTY = "com.eteks.sweethome3d.SweetHome3D.FrameMaximized";
74
private static final String SCREEN_WIDTH_VISUAL_PROPERTY = "com.eteks.sweethome3d.SweetHome3D.ScreenWidth";
75
private static final String SCREEN_HEIGHT_VISUAL_PROPERTY = "com.eteks.sweethome3d.SweetHome3D.ScreenHeight";
77
private final Home home;
78
private final HomeApplication application;
79
private final ContentManager contentManager;
80
private final HomeFrameController controller;
81
private static int newHomeCount;
82
private int newHomeNumber;
84
public HomeFramePane(Home home,
85
HomeApplication application,
86
ContentManager contentManager,
87
HomeFrameController controller) {
89
this.controller = controller;
90
this.application = application;
91
this.contentManager = contentManager;
92
// If home is unnamed, give it a number
93
if (home.getName() == null) {
94
this.newHomeNumber = ++newHomeCount;
96
// Set controller view as content pane
97
HomeView homeView = this.controller.getHomeController().getView();
98
setContentPane((JComponent)homeView);
102
* Builds and shows the frame that displays this pane.
104
public void displayView() {
105
final JFrame homeFrame = new JFrame() {
107
// Replace frame rootPane by home controller view
108
setRootPane(HomeFramePane.this);
111
// Update frame image and title
112
List<Image> frameImages = new ArrayList<Image>(3);
113
frameImages.add(new ImageIcon(HomeFramePane.class.getResource("resources/frameIcon.png")).getImage());
114
frameImages.add(new ImageIcon(HomeFramePane.class.getResource("resources/frameIcon32x32.png")).getImage());
115
if (OperatingSystem.isMacOSXLeopardOrSuperior()) {
116
frameImages.add(new ImageIcon(HomeFramePane.class.getResource("resources/frameIcon128x128.png")).getImage());
119
// Call Java 1.6 setIconImages by reflection
120
homeFrame.getClass().getMethod("setIconImages", List.class).invoke(homeFrame, frameImages);
121
} catch (Exception ex) {
122
// Call setIconImage available in previous versions
123
homeFrame.setIconImage(frameImages.get(0));
125
if (OperatingSystem.isMacOSXLionOrSuperior()) {
126
MacOSXConfiguration.installToolBar(this);
128
updateFrameTitle(homeFrame, this.home, this.application);
129
// Change component orientation
130
applyComponentOrientation(ComponentOrientation.getOrientation(Locale.getDefault()));
131
// Compute frame size and location
132
computeFrameBounds(this.home, homeFrame);
133
// Enable windows to update their content while window resizing
134
getToolkit().setDynamicLayout(true);
135
// The best MVC solution should be to avoid the following statements
136
// but Mac OS X accepts to display the menu bar of a frame in the screen
137
// menu bar only if this menu bar depends directly on its root pane
138
HomeView homeView = this.controller.getHomeController().getView();
139
if (homeView instanceof JRootPane) {
140
JRootPane homePane = (JRootPane)homeView;
141
setJMenuBar(homePane.getJMenuBar());
142
homePane.setJMenuBar(null);
145
// Add listeners to model and frame
146
addListeners(this.home, this.application, this.controller.getHomeController(), homeFrame);
148
homeFrame.setVisible(true);
149
// Request the frame to go to front again because closing waiting dialog meanwhile
150
// could put in front the already opened frame
151
EventQueue.invokeLater(new Runnable() {
159
* Adds listeners to <code>frame</code> and model objects.
161
private void addListeners(final Home home,
162
final HomeApplication application,
163
final HomeController controller,
164
final JFrame frame) {
165
// Add a listener that keeps track of window location and size
166
frame.addComponentListener(new ComponentAdapter() {
168
public void componentResized(ComponentEvent ev) {
169
// Store new size only if frame isn't maximized
170
if ((frame.getExtendedState() & JFrame.MAXIMIZED_BOTH) != JFrame.MAXIMIZED_BOTH) {
171
controller.setVisualProperty(FRAME_WIDTH_VISUAL_PROPERTY, frame.getWidth());
172
controller.setVisualProperty(FRAME_HEIGHT_VISUAL_PROPERTY, frame.getHeight());
174
Dimension userScreenSize = getUserScreenSize();
175
controller.setVisualProperty(SCREEN_WIDTH_VISUAL_PROPERTY, userScreenSize.width);
176
controller.setVisualProperty(SCREEN_HEIGHT_VISUAL_PROPERTY, userScreenSize.height);
180
public void componentMoved(ComponentEvent ev) {
181
// Store new location only if frame isn't maximized
182
if ((frame.getExtendedState() & JFrame.MAXIMIZED_BOTH) != JFrame.MAXIMIZED_BOTH) {
183
controller.setVisualProperty(FRAME_X_VISUAL_PROPERTY, frame.getX());
184
controller.setVisualProperty(FRAME_Y_VISUAL_PROPERTY, frame.getY());
188
// Control frame closing and activation
189
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
190
WindowAdapter windowListener = new WindowAdapter () {
191
private Component mostRecentFocusOwner;
194
public void windowStateChanged(WindowEvent ev) {
195
controller.setVisualProperty(FRAME_MAXIMIZED_VISUAL_PROPERTY,
196
(frame.getExtendedState() & JFrame.MAXIMIZED_BOTH) == JFrame.MAXIMIZED_BOTH);
200
public void windowClosing(WindowEvent ev) {
205
public void windowDeactivated(WindowEvent ev) {
206
// Java 3D 1.5 bug : windowDeactivated notifications should not be sent to this frame
207
// while canvases 3D are created in a child modal dialog like the one managing
208
// ImportedFurnitureWizardStepsPanel. As this makes Swing loose the most recent focus owner
209
// let's store it in a field to use it when this frame will be reactivated.
210
Component mostRecentFocusOwner = frame.getMostRecentFocusOwner();
211
if (!(mostRecentFocusOwner instanceof JFrame)
212
&& mostRecentFocusOwner != null) {
213
this.mostRecentFocusOwner = mostRecentFocusOwner;
218
public void windowActivated(WindowEvent ev) {
219
// Java 3D 1.5 bug : let's request focus in window for the most recent focus owner when
220
// this frame is reactivated
221
if (this.mostRecentFocusOwner != null) {
222
EventQueue.invokeLater(new Runnable() {
224
mostRecentFocusOwner.requestFocusInWindow();
230
frame.addWindowListener(windowListener);
231
frame.addWindowStateListener(windowListener);
232
// Add a listener to preferences to apply component orientation to frame matching current language
233
application.getUserPreferences().addPropertyChangeListener(UserPreferences.Property.LANGUAGE,
234
new LanguageChangeListener(frame, this));
235
// Dispose window when a home is deleted
236
application.addHomesListener(new CollectionListener<Home>() {
237
public void collectionChanged(CollectionEvent<Home> ev) {
238
if (ev.getItem() == home
239
&& ev.getType() == CollectionEvent.Type.DELETE) {
240
application.removeHomesListener(this);
245
// Update title when the name or the modified state of home changes
246
PropertyChangeListener frameTitleChangeListener = new PropertyChangeListener () {
247
public void propertyChange(PropertyChangeEvent ev) {
248
updateFrameTitle(frame, home, application);
251
home.addPropertyChangeListener(Home.Property.NAME, frameTitleChangeListener);
252
home.addPropertyChangeListener(Home.Property.MODIFIED, frameTitleChangeListener);
253
home.addPropertyChangeListener(Home.Property.RECOVERED, frameTitleChangeListener);
257
* Preferences property listener bound to this component with a weak reference to avoid
258
* strong link between preferences and this component.
260
private static class LanguageChangeListener implements PropertyChangeListener {
261
private WeakReference<JFrame> frame;
262
private WeakReference<HomeFramePane> homeFramePane;
264
public LanguageChangeListener(JFrame frame, HomeFramePane homeFramePane) {
265
this.frame = new WeakReference<JFrame>(frame);
266
this.homeFramePane = new WeakReference<HomeFramePane>(homeFramePane);
269
public void propertyChange(PropertyChangeEvent ev) {
270
// If frame was garbage collected, remove this listener from preferences
271
HomeFramePane homeFramePane = this.homeFramePane.get();
272
UserPreferences preferences = (UserPreferences)ev.getSource();
273
if (homeFramePane == null) {
274
preferences.removePropertyChangeListener(
275
UserPreferences.Property.LANGUAGE, this);
277
this.frame.get().applyComponentOrientation(ComponentOrientation.getOrientation(Locale.getDefault()));
278
homeFramePane.updateFrameTitle(this.frame.get(), homeFramePane.home, homeFramePane.application);
284
* Computes <code>frame</code> size and location to fit into screen.
286
private void computeFrameBounds(Home home, final JFrame frame) {
287
Integer x = (Integer)home.getVisualProperty(FRAME_X_VISUAL_PROPERTY);
288
Integer y = (Integer)home.getVisualProperty(FRAME_Y_VISUAL_PROPERTY);
289
Integer width = (Integer)home.getVisualProperty(FRAME_WIDTH_VISUAL_PROPERTY);
290
Integer height = (Integer)home.getVisualProperty(FRAME_HEIGHT_VISUAL_PROPERTY);
291
Boolean maximized = (Boolean)home.getVisualProperty(FRAME_MAXIMIZED_VISUAL_PROPERTY);
292
Integer screenWidth = (Integer)home.getVisualProperty(SCREEN_WIDTH_VISUAL_PROPERTY);
293
Integer screenHeight = (Integer)home.getVisualProperty(SCREEN_HEIGHT_VISUAL_PROPERTY);
295
Dimension screenSize = getUserScreenSize();
296
// If home frame bounds exist and screen resolution didn't reduce
297
if (x != null && y != null
298
&& width != null && height != null
299
&& screenWidth != null && screenHeight != null
300
&& screenWidth <= screenSize.width
301
&& screenHeight <= screenSize.height) {
302
final Rectangle frameBounds = new Rectangle(x, y, width, height);
303
if (maximized != null && maximized) {
304
if (OperatingSystem.isMacOSX()
305
&& OperatingSystem.isJavaVersionGreaterOrEqual("1.7")) {
306
// Display the frame at its maximum size because calling setExtendedState to maximize
307
// the frame moves it to the bottom left at its minimum size
308
Insets insets = frame.getInsets();
309
frame.setSize(screenSize.width + insets.left + insets.right,
310
screenSize.height + insets.bottom);
311
} else if (OperatingSystem.isLinux()) {
312
EventQueue.invokeLater(new Runnable() {
314
// Under Linux, maximize frame once it's displayed
315
frame.setExtendedState(JFrame.MAXIMIZED_BOTH);
319
frame.setExtendedState(JFrame.MAXIMIZED_BOTH);
321
// Add a listener that will set the normal size when the frame leaves the maximized state
322
frame.addWindowStateListener(new WindowAdapter() {
324
public void windowStateChanged(WindowEvent ev) {
325
if ((ev.getOldState() == JFrame.MAXIMIZED_BOTH
326
|| (OperatingSystem.isMacOSX()
327
&& OperatingSystem.isJavaVersionGreaterOrEqual("1.7")
328
&& ev.getOldState() == JFrame.NORMAL))
329
&& ev.getNewState() == JFrame.NORMAL) {
330
if (OperatingSystem.isMacOSXLionOrSuperior()) {
331
// Set back frame size later once frame reduce animation is finished
332
new Timer(20, new ActionListener() {
333
public void actionPerformed(ActionEvent ev) {
334
if (frame.getHeight() < 40) {
335
((Timer)ev.getSource()).stop();
336
frame.setBounds(frameBounds);
341
frame.setBounds(frameBounds);
343
frame.removeWindowStateListener(this);
349
frame.setBounds(frameBounds);
350
frame.setLocationByPlatform(!SwingTools.isRectangleVisibleAtScreen(frameBounds));
353
frame.setLocationByPlatform(true);
355
frame.setSize(Math.min(screenSize.width * 4 / 5, frame.getWidth()),
356
Math.min(screenSize.height * 4 / 5, frame.getHeight()));
357
if (OperatingSystem.isMacOSX()
358
&& OperatingSystem.isJavaVersionGreaterOrEqual("1.7")) {
359
// JFrame#setLocationByPlatform does nothing under Java 7
360
int minX = Integer.MAX_VALUE;
361
int minY = Integer.MAX_VALUE;
362
int maxX = Integer.MIN_VALUE;
363
int maxY = Integer.MIN_VALUE;
364
for (Frame applicationFrame : Frame.getFrames()) {
365
if (applicationFrame.isShowing()
366
&& applicationFrame.getBackground().getAlpha() != 0) {
367
minX = Math.min(minX, applicationFrame.getX());
368
minY = Math.min(minY, applicationFrame.getY());
369
maxX = Math.max(maxX, applicationFrame.getX());
370
maxY = Math.max(maxY, applicationFrame.getY());
374
if (minX == Integer.MAX_VALUE || minX >= 23) {
379
if (minY == Integer.MAX_VALUE || minY >= 23) {
384
frame.setLocation(x, y);
390
* Returns the screen size available to user.
392
private Dimension getUserScreenSize() {
393
Dimension screenSize = getToolkit().getScreenSize();
394
Insets screenInsets = getToolkit().getScreenInsets(getGraphicsConfiguration());
395
screenSize.width -= screenInsets.left + screenInsets.right;
396
screenSize.height -= screenInsets.top + screenInsets.bottom;
401
* Updates <code>frame</code> title from <code>home</code> and <code>application</code> name.
403
private void updateFrameTitle(JFrame frame,
405
HomeApplication application) {
406
String homeName = home.getName();
407
String homeDisplayedName;
408
if (homeName == null) {
409
homeDisplayedName = application.getUserPreferences().getLocalizedString(HomeFramePane.class, "untitled");
410
if (newHomeNumber > 1) {
411
homeDisplayedName += " " + newHomeNumber;
414
homeDisplayedName = this.contentManager.getPresentationName(
415
homeName, ContentManager.ContentType.SWEET_HOME_3D);
418
if (home.isRecovered()) {
419
homeDisplayedName += " " + application.getUserPreferences().getLocalizedString(HomeFramePane.class, "recovered");
422
String title = homeDisplayedName;
423
if (OperatingSystem.isMacOSX()) {
424
// Use black indicator in close icon for a modified home
425
Boolean homeModified = Boolean.valueOf(home.isModified() || home.isRecovered());
426
// Set Mac OS X 10.4 property for backward compatibility
427
putClientProperty("windowModified", homeModified);
429
if (OperatingSystem.isMacOSXLeopardOrSuperior()) {
430
putClientProperty("Window.documentModified", homeModified);
432
if (homeName != null) {
433
File homeFile = new File(homeName);
434
if (homeFile.exists()) {
435
// Update the home icon in window title bar for home files
436
putClientProperty("Window.documentFile", homeFile);
441
if (!frame.isVisible()
442
&& OperatingSystem.isMacOSXLionOrSuperior()) {
444
// Call Mac OS X specific FullScreenUtilities.setWindowCanFullScreen(homeFrame, true) by reflection
445
Class.forName("com.apple.eawt.FullScreenUtilities").
446
getMethod("setWindowCanFullScreen", new Class<?> [] {Window.class, boolean.class}).
447
invoke(null, frame, true);
448
} catch (Exception ex) {
449
// Full screen mode is not supported
453
title += " - " + application.getName();
454
if (home.isModified() || home.isRecovered()) {
455
title = "* " + title;
458
frame.setTitle(title);