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}