/* * (c) Copyright 2010-2011 AgileBirds * * This file is part of OpenFlexo. * * OpenFlexo 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 3 of the License, or * (at your option) any later version. * * OpenFlexo 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 OpenFlexo. If not, see <http://www.gnu.org/licenses/>. * */ package org.openflexo.view; import java.awt.Dimension; import java.awt.Frame; import java.awt.GraphicsEnvironment; import java.awt.Rectangle; import java.awt.Toolkit; import java.awt.Window; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; import java.awt.event.ComponentListener; import java.awt.event.MouseListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.awt.event.WindowListener; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; import java.util.List; import java.util.Vector; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.JFrame; import javax.swing.SwingUtilities; import javax.swing.WindowConstants; import org.openflexo.FlexoCst; import org.openflexo.GeneralPreferences; import org.openflexo.ch.FCH; import org.openflexo.components.ProgressWindow; import org.openflexo.foundation.DataModification; import org.openflexo.foundation.FlexoEditor; import org.openflexo.foundation.FlexoModelObject; import org.openflexo.foundation.FlexoObservable; import org.openflexo.foundation.GraphicalFlexoObserver; import org.openflexo.foundation.NameChanged; import org.openflexo.foundation.action.FlexoActionSource; import org.openflexo.foundation.rm.ResourceStatusModification; import org.openflexo.icon.IconLibrary; import org.openflexo.module.FlexoModule; import org.openflexo.module.ModuleLoader; import org.openflexo.module.ModuleLoadingException; import org.openflexo.prefs.FlexoPreferences; import org.openflexo.toolbox.ToolBox; import org.openflexo.view.controller.FlexoController; import org.openflexo.view.controller.model.ControllerModel; /** * Abstract main frame used in the context of an application module * * @author sguerin */ public final class FlexoFrame extends JFrame implements GraphicalFlexoObserver, FlexoActionSource, PropertyChangeListener { private final class FlexoModuleWindowListener extends WindowAdapter { @Override public void windowActivated(WindowEvent e) { if (!(e.getOppositeWindow() instanceof ProgressWindow) && getModuleLoader().isLoaded(getModule().getModule())) { switchToModule(); } } @Override public void windowClosing(WindowEvent event) { close(); } } static final Logger logger = Logger.getLogger(FlexoFrame.class.getPackage().getName()); private FlexoController _controller; private List<FlexoRelativeWindow> _relativeWindows; private List<FlexoRelativeWindow> _displayedRelativeWindows; private ComponentListener windowResizeListener; private MouseListener mouseListener; private WindowListener windowListener; public static FlexoFrame getActiveFrame() { return getActiveFrame(true); } public static FlexoFrame getActiveFrame(boolean createDefaultIfNull) { for (Frame frame : getFrames()) { if (frame.isActive()) { if (frame instanceof FlexoFrame) { return (FlexoFrame) frame; } else if (frame instanceof FlexoRelativeWindow) { ((FlexoRelativeWindow) frame).getParentFrame(); } else { if (logger.isLoggable(Level.WARNING)) { logger.warning("Found active frame " + frame.getTitle() + " which is not a FlexoFrame nor a Relative Window."); } } // We break since there won't be any other active frame. break; } else if (frame instanceof FlexoFrame) { if (hasActiveOwnedWindows(frame)) { return (FlexoFrame) frame; } } } for (Frame frame : getFrames()) { if (frame instanceof FlexoFrame) { return (FlexoFrame) frame; } } return createDefaultIfNull ? getDefaultFrame() : null; } protected static boolean hasActiveOwnedWindows(Window window) { for (Window w : window.getOwnedWindows()) { if (w.isActive()) { return true; } else if (hasActiveOwnedWindows(w)) { return true; } } return false; } public static Frame getOwner(Frame owner) { return owner != null ? owner : getActiveFrame(); } private static FlexoFrame defaultFrame; private static FlexoFrame getDefaultFrame() { if (defaultFrame == null) { defaultFrame = new FlexoFrame(); defaultFrame.setUndecorated(true); defaultFrame.setBounds(Toolkit.getDefaultToolkit().getScreenSize().width / 2, Toolkit.getDefaultToolkit().getScreenSize().height / 2, 0, 0); defaultFrame.setResizable(false); defaultFrame.setVisible(true); } return defaultFrame; } private static void disposeDefaultFrameWhenPossible() { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { disposeDefaultFrame(); } }); } private static void disposeDefaultFrame() { Frame f = defaultFrame; if (f != null) { boolean isDisposable = true; for (Window w : f.getOwnedWindows()) { if (w.isVisible()) { // a bit clumsy if two dialogs are visible but eventually the frame should get disposed. w.addWindowListener(new WindowAdapter() { @Override public void windowClosed(WindowEvent e) { disposeDefaultFrameWhenPossible(); } }); isDisposable = false; } } if (isDisposable) { f.setVisible(false); f.dispose(); defaultFrame = null; } } } private FlexoFrame() { super(FlexoCst.BUSINESS_APPLICATION_VERSION_NAME); setIconImage(IconLibrary.OPENFLEXO_NOTEXT_128.getImage()); } public FlexoFrame(FlexoController controller) { super(); _controller = controller; _relativeWindows = new Vector<FlexoRelativeWindow>(); _displayedRelativeWindows = new Vector<FlexoRelativeWindow>(); Rectangle bounds = GeneralPreferences.getBoundForFrameWithID(getController().getModule().getShortName() + "Frame"); if (bounds != null) { // In case we remove a screen (if you go from 3 to 2 screen, go to hell, that's all you deserve ;-)) if (GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices().length == 1) { Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); if (screen.width <= bounds.x) { bounds.x = 0; } else if (screen.height <= bounds.y) { bounds.y = 0; } } setBounds(bounds); } else { setSize(3 * Toolkit.getDefaultToolkit().getScreenSize().width / 4, 3 * Toolkit.getDefaultToolkit().getScreenSize().height / 4); setLocationByPlatform(true); } int state = GeneralPreferences.getFrameStateForFrameWithID(getController().getModule().getShortName() + "Frame"); if (state != -1 && ((state & Frame.MAXIMIZED_BOTH) == Frame.MAXIMIZED_BOTH || (state & Frame.MAXIMIZED_HORIZ) == Frame.MAXIMIZED_HORIZ || (state & Frame.MAXIMIZED_VERT) == Frame.MAXIMIZED_VERT)) { setExtendedState(GeneralPreferences.getFrameStateForFrameWithID(getController().getModule().getShortName() + "Frame")); } _controller.getControllerModel().getPropertyChangeSupport().addPropertyChangeListener(ControllerModel.CURRENT_EDITOR, this); if (defaultFrame != null) { disposeDefaultFrameWhenPossible(); } if (ToolBox.getPLATFORM() != ToolBox.WINDOWS) { setIconImage(controller.getModule().getModule().getBigIcon().getImage()); } else { setIconImage(IconLibrary.OPENFLEXO_NOTEXT_128.getImage()); } setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); setResizable(true); setFocusable(true); /** * Listeners */ addWindowListener(windowListener = new FlexoModuleWindowListener()); addComponentListener(windowResizeListener = new ComponentAdapter() { @Override public void componentMoved(ComponentEvent e) { saveBoundsInPreferenceWhenPossible(); } @Override public void componentResized(ComponentEvent e) { saveBoundsInPreferenceWhenPossible(); } }); } public FlexoModule getModule() { if (getController() != null) { return getController().getModule(); } else { return null; } } protected void switchToModule() { if (getModule() != null) { try { getModuleLoader().switchToModule(getModule().getModule()); } catch (ModuleLoadingException e1) { e1.printStackTrace(); } } } private ModuleLoader getModuleLoader() { return getController().getModuleLoader(); } /** * @return Returns the controller. */ public FlexoController getController() { return _controller; } public String getLocalizedName() { return getModule().getName(); } public void addToRelativeWindows(FlexoRelativeWindow aRelativeWindow) { if (!_relativeWindows.contains(aRelativeWindow)) { _relativeWindows.add(aRelativeWindow); } } public void removeFromRelativeWindows(FlexoRelativeWindow aRelativeWindow) { if (_relativeWindows.contains(aRelativeWindow)) { _relativeWindows.remove(aRelativeWindow); } removeFromDisplayedRelativeWindows(aRelativeWindow); } public void disposeAll() { for (FlexoRelativeWindow next : new ArrayList<FlexoRelativeWindow>(_relativeWindows)) { next.dispose(); } _relativeWindows.clear(); if (_controller != null) { _controller.getControllerModel().getPropertyChangeSupport() .removePropertyChangeListener(ControllerModel.CURRENT_LOCATION, this); if (_controller.getProject() != null) { _controller.getProject().deleteObserver(this); } _controller = null; } if (windowListener != null) { removeWindowListener(windowListener); windowListener = null; } if (mouseListener != null) { removeMouseListener(mouseListener); } setJMenuBar(null); if (getContentPane() != null) { getContentPane().removeAll(); } if (logger.isLoggable(Level.INFO)) { logger.info("Disposing " + this); } if (ProgressWindow.hasInstance()) { ProgressWindow.hideProgressWindow(); } dispose(); } public void addToDisplayedRelativeWindows(FlexoRelativeWindow aRelativeWindow) { if (!_displayedRelativeWindows.contains(aRelativeWindow)) { _displayedRelativeWindows.add(aRelativeWindow); } if (logger.isLoggable(Level.FINE)) { logger.finer("addToRelativeWindows with " + aRelativeWindow); } } public void removeFromDisplayedRelativeWindows(FlexoRelativeWindow aRelativeWindow) { if (_displayedRelativeWindows.contains(aRelativeWindow)) { _displayedRelativeWindows.remove(aRelativeWindow); } if (logger.isLoggable(Level.FINE)) { logger.finer("removeFromRelativeWindows with " + aRelativeWindow); } } private static final String WINDOW_MODIFIED = "windowModified"; @Override public void update(final FlexoObservable observable, final DataModification dataModification) { if (getController() == null) { observable.deleteObserver(this); return; } if (getController().getProject() == null) { return; } if (!SwingUtilities.isEventDispatchThread()) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { update(observable, dataModification); } }); return; } if (dataModification instanceof NameChanged) { updateTitle(); } else if ("projectDirectory".equals(dataModification.propertyName())) { updateTitle(); } if (ToolBox.getPLATFORM() == ToolBox.MACOS && dataModification instanceof ResourceStatusModification) { getRootPane().putClientProperty(WINDOW_MODIFIED, getController().getProject().hasUnsaveStorageResources()); } } @Override public void propertyChange(PropertyChangeEvent evt) { if (evt.getPropertyName().equals(ControllerModel.CURRENT_EDITOR)) { FlexoEditor oldEditor = (FlexoEditor) evt.getOldValue(); FlexoEditor newEditor = (FlexoEditor) evt.getNewValue(); if (oldEditor != newEditor) { if (oldEditor != null && oldEditor.getProject() != null) { oldEditor.getProject().deleteObserver(this); } if (newEditor != null && newEditor.getProject() != null) { newEditor.getProject().addObserver(this); } updateTitle(); } } } @Override public void setVisible(boolean mainFrameIsVisible) { if (getController() != null) { if (mainFrameIsVisible && getModule() != null && getModule().isActive() || !mainFrameIsVisible) { setRelativeVisible(mainFrameIsVisible); } } super.setVisible(mainFrameIsVisible); } public void setRelativeVisible(boolean relativeWindowsAreVisible) { if (relativeWindowsAreVisible) { showRelativeWindows(); } else { hideRelativeWindows(); } } private int showRelativeWindows() { int returned = 0; if (_displayedRelativeWindows != null) { for (FlexoRelativeWindow next : _displayedRelativeWindows) { if (!next.isShowing()) { next.setVisibleNoParentFrameNotification(true); returned++; } } } return returned; } private void hideRelativeWindows() { if (_displayedRelativeWindows != null) { for (FlexoRelativeWindow next : _displayedRelativeWindows) { if (next.isShowing()) { next.setVisibleNoParentFrameNotification(false); } } } } public void updateTitle() { setTitle(getController().getWindowTitle()); } public List<FlexoRelativeWindow> getRelativeWindows() { return _relativeWindows; } @Override public void validate() { super.validate(); FCH.validateWindow(this); } private Thread boundsSaver; protected synchronized void saveBoundsInPreferenceWhenPossible() { if (!isVisible()) { return; } if (boundsSaver != null) { boundsSaver.interrupt();// Resets thread sleep return; } boundsSaver = new Thread(new Runnable() { @Override public void run() { boolean go = true; while (go) { try { go = false; Thread.sleep(1000); } catch (InterruptedException e) { go = true;// interruption is used to reset sleep. } } saveBoundsInPreference(); } }); boundsSaver.start(); } protected void saveBoundsInPreference() { if (getController() == null) { return; } int state = getExtendedState(); if (state == -1 || (state & Frame.MAXIMIZED_BOTH) != Frame.MAXIMIZED_BOTH && (state & Frame.MAXIMIZED_HORIZ) != Frame.MAXIMIZED_HORIZ && (state & Frame.MAXIMIZED_VERT) != Frame.MAXIMIZED_VERT) { GeneralPreferences.setBoundForFrameWithID(getController().getModule().getShortName() + "Frame", getBounds()); } GeneralPreferences.setFrameStateForFrameWithID(getController().getModule().getShortName() + "Frame", getExtendedState()); FlexoPreferences.savePreferences(true); boundsSaver = null; } @Override public FlexoEditor getEditor() { return getController().getEditor(); } @Override public FlexoModelObject getFocusedObject() { return getController().getSelectionManager().getFocusedObject(); } @Override public Vector<FlexoModelObject> getGlobalSelection() { return getController().getSelectionManager().getSelection(); } public void close() { if (getModule().close()) { dispose(); } } }