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}