001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.awt.BorderLayout;
007import java.awt.Component;
008import java.awt.Container;
009import java.awt.Image;
010import java.awt.Rectangle;
011import java.awt.Toolkit;
012import java.awt.event.ComponentEvent;
013import java.awt.event.ComponentListener;
014import java.awt.event.WindowAdapter;
015import java.awt.event.WindowEvent;
016import java.beans.PropertyChangeEvent;
017import java.beans.PropertyChangeListener;
018import java.util.LinkedList;
019import java.util.List;
020
021import javax.swing.JFrame;
022import javax.swing.JPanel;
023
024import org.openstreetmap.josm.Main;
025import org.openstreetmap.josm.gui.layer.LayerManager.LayerAddEvent;
026import org.openstreetmap.josm.gui.layer.LayerManager.LayerChangeListener;
027import org.openstreetmap.josm.gui.layer.LayerManager.LayerOrderChangeEvent;
028import org.openstreetmap.josm.gui.layer.LayerManager.LayerRemoveEvent;
029import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeEvent;
030import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeListener;
031import org.openstreetmap.josm.gui.layer.OsmDataLayer;
032import org.openstreetmap.josm.gui.layer.OsmDataLayer.LayerStateChangeListener;
033import org.openstreetmap.josm.tools.ImageProvider;
034import org.openstreetmap.josm.tools.WindowGeometry;
035
036/**
037 * This is the JOSM main window. It updates it's title.
038 * @author Michael Zangl
039 * @since 10340
040 */
041public class MainFrame extends JFrame {
042    private final transient LayerStateChangeListener updateTitleOnLayerStateChange = new LayerStateChangeListener() {
043        @Override
044        public void uploadDiscouragedChanged(OsmDataLayer layer, boolean newValue) {
045            onLayerChange(layer);
046        }
047    };
048
049    private final transient PropertyChangeListener updateTitleOnSaveChange = new PropertyChangeListener() {
050        @Override
051        public void propertyChange(PropertyChangeEvent evt) {
052            if (evt.getPropertyName().equals(OsmDataLayer.REQUIRES_SAVE_TO_DISK_PROP)
053                    || evt.getPropertyName().equals(OsmDataLayer.REQUIRES_UPLOAD_TO_SERVER_PROP)) {
054                OsmDataLayer layer = (OsmDataLayer) evt.getSource();
055                onLayerChange(layer);
056            }
057        }
058    };
059
060    protected transient WindowGeometry geometry;
061    protected int windowState = JFrame.NORMAL;
062    private MainMenu menu;
063
064    /**
065     * Create a new main window.
066     */
067    public MainFrame() {
068        this(new JPanel(), new MainPanel(Main.getLayerManager()), new WindowGeometry(new Rectangle(10, 10, 500, 500)));
069    }
070
071    /**
072     * Create a new main window. The parameters will be removed in the future.
073     * @param contentPanePrivate The content
074     * @param mainPanel The main panel.
075     * @param geometry The inital geometry to use.
076     */
077    public MainFrame(Container contentPanePrivate, MainPanel mainPanel, WindowGeometry geometry) {
078        super();
079        this.geometry = geometry;
080        setContentPane(contentPanePrivate);
081    }
082
083    /**
084     * Initializes the content of the window and get the current status panel.
085     */
086    public void initialize() {
087        menu = new MainMenu();
088        addComponentListener(new WindowPositionSizeListener());
089        addWindowStateListener(new WindowPositionSizeListener());
090
091        setJMenuBar(menu);
092        geometry.applySafe(this);
093        List<Image> l = new LinkedList<>();
094        l.add(ImageProvider.get("logo_16x16x32").getImage());
095        l.add(ImageProvider.get("logo_16x16x8").getImage());
096        l.add(ImageProvider.get("logo_32x32x32").getImage());
097        l.add(ImageProvider.get("logo_32x32x8").getImage());
098        l.add(ImageProvider.get("logo_48x48x32").getImage());
099        l.add(ImageProvider.get("logo_48x48x8").getImage());
100        l.add(ImageProvider.get("logo").getImage());
101        setIconImages(l);
102        addWindowListener(new WindowAdapter() {
103            @Override
104            public void windowClosing(final WindowEvent arg0) {
105                Main.exitJosm(true, 0);
106            }
107        });
108        setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
109
110        // This listener is never removed, since the main frame exists forever.
111        Main.getLayerManager().addActiveLayerChangeListener(new ActiveLayerChangeListener() {
112            @Override
113            public void activeOrEditLayerChanged(ActiveLayerChangeEvent e) {
114                refreshTitle();
115            }
116        });
117        Main.getLayerManager().addLayerChangeListener(new ManageLayerListeners(), true);
118
119        refreshTitle();
120
121        getContentPane().add(Main.panel, BorderLayout.CENTER);
122        menu.initialize();
123    }
124
125    /**
126     * Stores the current state of the main frame.
127     */
128    public void storeState() {
129        if (geometry != null) {
130            geometry.remember("gui.geometry");
131        }
132        Main.pref.put("gui.maximized", (windowState & JFrame.MAXIMIZED_BOTH) != 0);
133    }
134
135    /**
136     * Gets the main menu used for this window.
137     * @return The main menu.
138     */
139    public MainMenu getMenu() {
140        if (menu == null) {
141            throw new IllegalStateException("Not initialized.");
142        }
143        return menu;
144    }
145
146    /**
147     * Sets this frame to be maximized.
148     * @param maximized <code>true</code> if the window should be maximized.
149     */
150    public void setMaximized(boolean maximized) {
151        if (maximized) {
152            if (Toolkit.getDefaultToolkit().isFrameStateSupported(JFrame.MAXIMIZED_BOTH)) {
153                windowState = JFrame.MAXIMIZED_BOTH;
154                setExtendedState(windowState);
155            } else {
156                Main.debug("Main window: maximizing not supported");
157            }
158        } else {
159            throw new UnsupportedOperationException("Unimplemented.");
160        }
161    }
162
163    /**
164     * Update the title of the window to reflect the current content.
165     */
166    public void refreshTitle() {
167        OsmDataLayer editLayer = Main.getLayerManager().getEditLayer();
168        boolean dirty = editLayer != null && (editLayer.requiresSaveToFile()
169                || (editLayer.requiresUploadToServer() && !editLayer.isUploadDiscouraged()));
170        setTitle((dirty ? "* " : "") + tr("Java OpenStreetMap Editor"));
171        getRootPane().putClientProperty("Window.documentModified", dirty);
172    }
173
174    private void onLayerChange(OsmDataLayer layer) {
175        if (layer == Main.getLayerManager().getEditLayer()) {
176            refreshTitle();
177        }
178    }
179
180    /**
181     * Manages the layer listeners, adds them to every layer.
182     */
183    private final class ManageLayerListeners implements LayerChangeListener {
184        @Override
185        public void layerAdded(LayerAddEvent e) {
186            if (e.getAddedLayer() instanceof OsmDataLayer) {
187                OsmDataLayer osmDataLayer = (OsmDataLayer) e.getAddedLayer();
188                osmDataLayer.addLayerStateChangeListener(updateTitleOnLayerStateChange);
189            }
190            e.getAddedLayer().addPropertyChangeListener(updateTitleOnSaveChange);
191        }
192
193        @Override
194        public void layerRemoving(LayerRemoveEvent e) {
195            if (e.getRemovedLayer() instanceof OsmDataLayer) {
196                OsmDataLayer osmDataLayer = (OsmDataLayer) e.getRemovedLayer();
197                osmDataLayer.removeLayerStateChangeListener(updateTitleOnLayerStateChange);
198            }
199            e.getRemovedLayer().removePropertyChangeListener(updateTitleOnSaveChange);
200        }
201
202        @Override
203        public void layerOrderChanged(LayerOrderChangeEvent e) {
204            // not used
205        }
206    }
207
208    private class WindowPositionSizeListener extends WindowAdapter implements ComponentListener {
209        @Override
210        public void windowStateChanged(WindowEvent e) {
211            windowState = e.getNewState();
212        }
213
214        @Override
215        public void componentHidden(ComponentEvent e) {
216            // Do nothing
217        }
218
219        @Override
220        public void componentMoved(ComponentEvent e) {
221            handleComponentEvent(e);
222        }
223
224        @Override
225        public void componentResized(ComponentEvent e) {
226            handleComponentEvent(e);
227        }
228
229        @Override
230        public void componentShown(ComponentEvent e) {
231            // Do nothing
232        }
233
234        private void handleComponentEvent(ComponentEvent e) {
235            Component c = e.getComponent();
236            if (c instanceof JFrame && c.isVisible()) {
237                if (windowState == JFrame.NORMAL) {
238                    geometry = new WindowGeometry((JFrame) c);
239                } else {
240                    geometry.fixScreen((JFrame) c);
241                }
242            }
243        }
244    }
245
246}