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