/******************************************************************************* * Copyright (c) MOBAC developers * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. ******************************************************************************/ package mobac.program.model; import java.awt.Dimension; import java.awt.Point; import java.awt.Toolkit; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.net.Authenticator; import java.net.PasswordAuthentication; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Locale; import java.util.Vector; import java.util.concurrent.TimeUnit; import javax.swing.JOptionPane; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; import javax.xml.bind.Unmarshaller; import javax.xml.bind.ValidationEvent; import javax.xml.bind.ValidationEventHandler; import javax.xml.bind.annotation.XmlAccessOrder; import javax.xml.bind.annotation.XmlAccessorOrder; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlElementWrapper; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlTransient; import mobac.gui.actions.GpxLoad; import mobac.gui.panels.JCoordinatesPanel; import mobac.mapsources.MapSourcesManager; import mobac.program.DirectoryManager; import mobac.program.ProgramInfo; import mobac.utilities.I18nUtils; import mobac.utilities.Utilities; import mobac.utilities.stream.ThrottledInputStream; import org.apache.log4j.Logger; @XmlRootElement @XmlAccessorOrder(XmlAccessOrder.ALPHABETICAL) public class Settings { private static Logger log = Logger.getLogger(Settings.class); private static Settings instance = new Settings(); public static final File FILE = new File(DirectoryManager.userSettingsDir, "settings.xml"); private static long SETTINGS_LAST_MODIFIED = 0; private static final String SYSTEM_PROXY_HOST = System.getProperty("http.proxyHost"); private static final String SYSTEM_PROXY_PORT = System.getProperty("http.proxyPort"); @XmlElement(defaultValue = "") private String version; public int maxMapSize = 65536; public int mapOverlapTiles = 0; public boolean tileStoreEnabled = true; /** * Mapview related settings */ public int mapviewZoom = 3; public int mapviewGridZoom = -1; public EastNorthCoordinate mapviewCenterCoordinate = new EastNorthCoordinate(50, 9); public Point mapviewSelectionMax = null; public Point mapviewSelectionMin = null; @XmlElementWrapper(name = "selectedZoomLevels") @XmlElement(name = "zoomLevel") public List<Integer> selectedZoomLevels = null; @XmlElement(nillable = false) public String mapviewMapSource = null; public String elementName = null; private String userAgent = null; public int downloadThreadCount = 2; public int downloadRetryCount = 1; private boolean customTileProcessing = false; private Dimension tileSize = new Dimension(256, 256); private TileImageFormat tileImageFormat = TileImageFormat.PNG; private String swapDir = null; public CoordinateStringFormat coordinateNumberFormat = CoordinateStringFormat.DEG_LOCAL; public final Directories directories = new Directories(); public static class Directories { @XmlElement private String atlasOutputDirectory = null; @XmlElement public String tileStoreDirectory; @XmlElement private String mapSourcesDirectory; } @XmlElementWrapper(name = "placeBookmarks") @XmlElement(name = "bookmark") public List<Bookmark> placeBookmarks = new ArrayList<Bookmark>(); /** * Connection timeout in seconds (default 10 seconds) */ public int httpConnectionTimeout = 10; /** * Read timeout in seconds (default 10 seconds) */ public int httpReadTimeout = 10; /** * Maximum expiration (in milliseconds) acceptable. If a server sets an expiration time larger than this value it is * truncated to this value on next download. */ public long tileMaxExpirationTime = TimeUnit.DAYS.toMillis(365); /** * Minimum expiration (in milliseconds) acceptable. If a server sets an expiration time smaller than this value it * is truncated to this value on next download. */ public long tileMinExpirationTime = TimeUnit.DAYS.toMillis(5); /** * Expiration time (in milliseconds) of a tile if the server does not provide an expiration time */ public long tileDefaultExpirationTime = TimeUnit.DAYS.toMillis(28); public String googleLanguage = "en"; public String osmHikingTicket = ""; /** * Development mode enabled/disabled * <p> * In development mode one additional map source is available for using MOBAC Debug TileServer * </p> */ @XmlElement public boolean devMode = false; /** * Saves the last used directory of the GPX file chooser dialog. Used in {@link GpxLoad}. */ public String gpxFileChooserDir = ""; public final AtlasFormatSpecificSettings atlasFormatSpecificSettings = new AtlasFormatSpecificSettings(); public static class AtlasFormatSpecificSettings { @XmlElement public Integer garminCustomMaxMapCount = 100; } public final MainWindowSettings mainWindow = new MainWindowSettings(); public static class MainWindowSettings { public Dimension size = new Dimension(); public Point position = new Point(-1, -1); public boolean maximized = true; public boolean leftPanelVisible = true; public boolean rightPanelVisible = true; @XmlElementWrapper(name = "collapsedPanels") @XmlElement(name = "collapsedPanel") public Vector<String> collapsedPanels = new Vector<String>(); } /** * Network settings */ private ProxyType proxyType = ProxyType.CUSTOM; private String customProxyHost = ""; private String customProxyPort = ""; private String customProxyUserName = ""; private String customProxyPassword = ""; private long bandwidthLimit = 0; @XmlElementWrapper(name = "mapSourcesDisabled") @XmlElement(name = "mapSource") public Vector<String> mapSourcesDisabled = new Vector<String>(); @XmlElementWrapper(name = "mapSourcesEnabled") @XmlElement(name = "mapSource") public Vector<String> mapSourcesEnabled = new Vector<String>(); @XmlElement(name = "MapSourcesUpdate") public final MapSourcesUpdate mapSourcesUpdate = new MapSourcesUpdate(); public static class MapSourcesUpdate { /** * Last ETag value retrieved while online map source update. * * @see MapSourcesManager#mapsourcesOnlineUpdate() * @see http://en.wikipedia.org/wiki/HTTP_ETag */ public String etag; public Date lastUpdate; } public transient UnitSystem unitSystem = UnitSystem.Metric; public final SettingsPaperAtlas paperAtlas = new SettingsPaperAtlas(); public final SettingsWgsGrid wgsGrid = new SettingsWgsGrid(); public boolean ignoreDlErrors = false; public String localeLanguage = null; public String localeCountry = null; private Settings() { elementName = "Layer";// no need i18n for it Dimension dScreen = Toolkit.getDefaultToolkit().getScreenSize(); mainWindow.size.width = (int) (0.9f * dScreen.width); mainWindow.size.height = (int) (0.9f * dScreen.height); mainWindow.collapsedPanels.add(JCoordinatesPanel.NAME); mainWindow.collapsedPanels.add("Gpx"); Locale defaultLocale = Locale.getDefault(); if (defaultLocale.equals(new Locale("zh", "CN"))) { localeLanguage = "zh"; localeCountry = "CN"; } else if (defaultLocale.equals(new Locale("zh", "TW"))) { localeLanguage = "zh"; localeCountry = "TW"; } else if (defaultLocale.equals(new Locale("ja", "JP"))) { localeLanguage = "ja"; localeCountry = "JP"; } else if (defaultLocale.equals(new Locale("fr", "FR"))) { localeLanguage = "fr"; localeCountry = "FR"; } else { localeLanguage = "en"; localeCountry = ""; } } public static Settings getInstance() { return instance; } public static void load() throws JAXBException { try { JAXBContext context = JAXBContext.newInstance(Settings.class); Unmarshaller um = context.createUnmarshaller(); um.setEventHandler(new ValidationEventHandler() { public boolean handleEvent(ValidationEvent event) { log.warn("Problem on loading settings.xml: " + event.getMessage()); return true; } }); instance = (Settings) um.unmarshal(FILE); instance.wgsGrid.checkValues(); instance.paperAtlas.checkValues(); SETTINGS_LAST_MODIFIED = FILE.lastModified(); // Settings 重新加载之后,必须更新语言资源 I18nUtils.updateLocalizedStringFormSettings(); } finally { Settings s = getInstance(); s.applyProxySettings(); } } public static boolean checkSettingsFileModified() { if (SETTINGS_LAST_MODIFIED == 0) return false; // Check if the settings.xml has been modified // since it has been loaded long lastModified = FILE.lastModified(); return (SETTINGS_LAST_MODIFIED != lastModified); } public static void save() throws JAXBException { getInstance().version = ProgramInfo.getVersion(); JAXBContext context = JAXBContext.newInstance(Settings.class); Marshaller m = context.createMarshaller(); m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); ByteArrayOutputStream bo = null; FileOutputStream fo = null; try { // First we write to a buffer and if that works be write the buffer // to disk. Direct writing to file may result in an defect xml file // in case of an error bo = new ByteArrayOutputStream(); m.marshal(getInstance(), bo); fo = new FileOutputStream(FILE); fo.write(bo.toByteArray()); fo.close(); SETTINGS_LAST_MODIFIED = FILE.lastModified(); } catch (IOException e) { throw new JAXBException(e); } finally { Utilities.closeStream(fo); } } public static void loadOrQuit() { try { load(); } catch (JAXBException e) { log.error(e); JOptionPane .showMessageDialog(null, I18nUtils.localizedStringForKey(I18nUtils .localizedStringForKey("msg_settings_file_can_not_parse")), I18nUtils .localizedStringForKey("Error"), JOptionPane.ERROR_MESSAGE); System.exit(0); } } public String getUserAgent() { if (userAgent != null) return userAgent; else return ProgramInfo.getUserAgent(); } public void setUserAgent(String userAgent) { if (userAgent != null) { userAgent = userAgent.trim(); if (userAgent.length() == 0) userAgent = null; } this.userAgent = userAgent; } public boolean isCustomTileSize() { return customTileProcessing; } public void setCustomTileSize(boolean customTileSize) { this.customTileProcessing = customTileSize; } public Dimension getTileSize() { return tileSize; } public void setTileSize(Dimension tileSize) { this.tileSize = tileSize; } public TileImageFormat getTileImageFormat() { return tileImageFormat; } public void setTileImageFormat(TileImageFormat tileImageFormat) { this.tileImageFormat = tileImageFormat; } public ProxyType getProxyType() { return proxyType; } public void setProxyType(ProxyType proxyType) { this.proxyType = proxyType; } public String getCustomProxyHost() { return customProxyHost; } public String getCustomProxyPort() { return customProxyPort; } public void setCustomProxyHost(String proxyHost) { this.customProxyHost = proxyHost; } public void setCustomProxyPort(String proxyPort) { this.customProxyPort = proxyPort; } public String getCustomProxyUserName() { return customProxyUserName; } public void setCustomProxyUserName(String customProxyUserName) { this.customProxyUserName = customProxyUserName; } public String getCustomProxyPassword() { return customProxyPassword; } public void setCustomProxyPassword(String customProxyPassword) { this.customProxyPassword = customProxyPassword; } public void applyProxySettings() { boolean useSystemProxies = false; String newProxyHost = null; String newProxyPort = null; Authenticator newAuthenticator = null; switch (proxyType) { case SYSTEM: log.info("Applying proxy configuration: system settings"); useSystemProxies = true; break; case APP_SETTINGS: newProxyHost = SYSTEM_PROXY_HOST; newProxyPort = SYSTEM_PROXY_PORT; log.info("Applying proxy configuration: host=" + newProxyHost + " port=" + newProxyPort); break; case CUSTOM: newProxyHost = customProxyHost; newProxyPort = customProxyPort; log.info("Applying proxy configuration: host=" + newProxyHost + " port=" + newProxyPort); break; case CUSTOM_W_AUTH: newProxyHost = customProxyHost; newProxyPort = customProxyPort; newAuthenticator = new Authenticator() { protected PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication(customProxyUserName, customProxyPassword.toCharArray()); } }; log.info("Applying proxy configuration: host=" + newProxyHost + " port=" + newProxyPort + " user=" + customProxyUserName); break; } Utilities.setHttpProxyHost(newProxyHost); Utilities.setHttpProxyPort(newProxyPort); Authenticator.setDefault(newAuthenticator); System.setProperty("java.net.useSystemProxies", Boolean.toString(useSystemProxies)); } public long getBandwidthLimit() { return bandwidthLimit; } public void setBandwidthLimit(long bandwidthLimit) { this.bandwidthLimit = bandwidthLimit; ThrottledInputStream.setBandwidth(bandwidthLimit); } @XmlElement public void setUnitSystem(UnitSystem unitSystem) { if (unitSystem == null) unitSystem = UnitSystem.Metric; this.unitSystem = unitSystem; } public UnitSystem getUnitSystem() { return unitSystem; } @XmlTransient public File getMapSourcesDirectory() { String mapSourcesDirCfg = directories.mapSourcesDirectory; File mapSourcesDir; if (mapSourcesDirCfg == null || mapSourcesDirCfg.trim().length() == 0) mapSourcesDir = DirectoryManager.mapSourcesDir; else mapSourcesDir = new File(mapSourcesDirCfg); return mapSourcesDir; } @XmlTransient public File getAtlasOutputDirectory() { if (directories.atlasOutputDirectory != null) return new File(directories.atlasOutputDirectory); return new File(DirectoryManager.currentDir, "atlases"); } public String getAtlasOutputDirectoryString() { if (directories.atlasOutputDirectory == null) return ""; return directories.atlasOutputDirectory; } /** * * @param dir * <code>null</code> or empty string resets to default directory otherwise set the new atlas output * directory. */ public void setAtlasOutputDirectory(String dir) { if (dir != null && dir.trim().length() == 0) dir = null; directories.atlasOutputDirectory = dir; } public String getVersion() { return version; } public String getSwapDir() { return swapDir; } public void setSwapDir(String swapDir) { this.swapDir = swapDir; } }