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