001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.mappaint; 003 004import static org.openstreetmap.josm.tools.I18n.trn; 005 006import java.awt.Color; 007import java.io.File; 008import java.io.IOException; 009import java.io.InputStream; 010import java.util.ArrayList; 011import java.util.Collection; 012import java.util.Collections; 013import java.util.HashMap; 014import java.util.List; 015import java.util.Map; 016import java.util.Set; 017import java.util.concurrent.CopyOnWriteArrayList; 018import java.util.concurrent.CopyOnWriteArraySet; 019 020import javax.swing.ImageIcon; 021 022import org.openstreetmap.josm.data.osm.OsmPrimitive; 023import org.openstreetmap.josm.data.preferences.sources.SourceEntry; 024import org.openstreetmap.josm.data.preferences.sources.SourceType; 025import org.openstreetmap.josm.gui.mappaint.MapPaintStyles.IconReference; 026import org.openstreetmap.josm.io.CachedFile; 027import org.openstreetmap.josm.tools.ImageOverlay; 028import org.openstreetmap.josm.tools.ImageProvider; 029import org.openstreetmap.josm.tools.Utils; 030 031/** 032 * A mappaint style (abstract class). 033 * 034 * Handles everything from parsing the style definition to application 035 * of the style to an osm primitive. 036 */ 037public abstract class StyleSource extends SourceEntry { 038 039 private final List<Throwable> errors = new CopyOnWriteArrayList<>(); 040 private final Set<String> warnings = new CopyOnWriteArraySet<>(); 041 /** 042 * The zip file containing the icons for this style 043 */ 044 public File zipIcons; 045 046 /** image provider returning the icon for this style */ 047 private ImageProvider imageIconProvider; 048 049 /** image provider returning the default icon */ 050 private static ImageProvider defaultIconProvider; 051 052 /** 053 * The following fields is additional information found in the header of the source file. 054 */ 055 public String icon; 056 057 /** 058 * List of settings for user customization. 059 */ 060 public final List<StyleSetting> settings = new ArrayList<>(); 061 /** 062 * Values of the settings for efficient lookup. 063 */ 064 public Map<String, Object> settingValues = new HashMap<>(); 065 066 /** 067 * Constructs a new, active {@link StyleSource}. 068 * @param url URL that {@link org.openstreetmap.josm.io.CachedFile} understands 069 * @param name The name for this StyleSource 070 * @param title The title that can be used as menu entry 071 */ 072 public StyleSource(String url, String name, String title) { 073 super(SourceType.MAP_PAINT_STYLE, url, name, title, true); 074 } 075 076 /** 077 * Constructs a new {@link StyleSource} 078 * @param entry The entry to copy the data (url, name, ...) from. 079 */ 080 public StyleSource(SourceEntry entry) { 081 super(entry); 082 } 083 084 /** 085 * Apply style to osm primitive. 086 * 087 * Adds properties to a MultiCascade. All active {@link StyleSource}s add 088 * their properties on after the other. At a later stage, concrete painting 089 * primitives (lines, icons, text, ...) are derived from the MultiCascade. 090 * @param mc the current MultiCascade, empty for the first StyleSource 091 * @param osm the primitive 092 * @param scale the map scale 093 * @param pretendWayIsClosed For styles that require the way to be closed, 094 * we pretend it is. This is useful for generating area styles from the (segmented) 095 * outer ways of a multipolygon. 096 */ 097 public abstract void apply(MultiCascade mc, OsmPrimitive osm, double scale, boolean pretendWayIsClosed); 098 099 /** 100 * Loads the style source. 101 */ 102 public abstract void loadStyleSource(); 103 104 /** 105 * Returns a new {@code InputStream} to the style source. When finished, {@link #closeSourceInputStream(InputStream)} must be called. 106 * @return A new {@code InputStream} to the style source that must be closed by the caller 107 * @throws IOException if any I/O error occurs. 108 * @see #closeSourceInputStream(InputStream) 109 */ 110 public abstract InputStream getSourceInputStream() throws IOException; 111 112 /** 113 * Returns a new {@code CachedFile} to the local file containing style source (can be a text file or an archive). 114 * @return A new {@code CachedFile} to the local file containing style source 115 * @throws IOException if any I/O error occurs. 116 * @since 7081 117 */ 118 public abstract CachedFile getCachedFile() throws IOException; 119 120 /** 121 * Closes the source input stream previously returned by {@link #getSourceInputStream()} and other linked resources, if applicable. 122 * @param is The source input stream that must be closed 123 * @see #getSourceInputStream() 124 * @since 6289 125 */ 126 public void closeSourceInputStream(InputStream is) { 127 Utils.close(is); 128 } 129 130 /** 131 * Log an error that occured with this style. 132 * @param e error 133 */ 134 public void logError(Throwable e) { 135 errors.add(e); 136 } 137 138 /** 139 * Log a warning that occured with this style. 140 * @param w warnings 141 */ 142 public void logWarning(String w) { 143 warnings.add(w); 144 } 145 146 /** 147 * Replies the collection of errors that occured with this style. 148 * @return collection of errors 149 */ 150 public Collection<Throwable> getErrors() { 151 return Collections.unmodifiableCollection(errors); 152 } 153 154 /** 155 * Replies the collection of warnings that occured with this style. 156 * @return collection of warnings 157 */ 158 public Collection<String> getWarnings() { 159 return Collections.unmodifiableCollection(warnings); 160 } 161 162 /** 163 * Determines if this style is valid (no error, no warning). 164 * @return {@code true} if this style has 0 errors and 0 warnings 165 */ 166 public boolean isValid() { 167 return errors.isEmpty() && warnings.isEmpty(); 168 } 169 170 /** 171 * Initialize the class. 172 */ 173 protected void init() { 174 errors.clear(); 175 imageIconProvider = null; 176 icon = null; 177 } 178 179 /** 180 * Image provider for default icon. 181 * 182 * @return image provider for default styles icon 183 * @see #getIconProvider() 184 * @since 8097 185 */ 186 private static synchronized ImageProvider getDefaultIconProvider() { 187 if (defaultIconProvider == null) { 188 defaultIconProvider = new ImageProvider("dialogs/mappaint", "pencil"); 189 } 190 return defaultIconProvider; 191 } 192 193 /** 194 * Image provider for source icon. Uses default icon, when not else available. 195 * 196 * @return image provider for styles icon 197 * @see #getIconProvider() 198 * @since 8097 199 */ 200 protected ImageProvider getSourceIconProvider() { 201 if (imageIconProvider == null) { 202 if (icon != null) { 203 imageIconProvider = MapPaintStyles.getIconProvider(new IconReference(icon, this), true); 204 } 205 if (imageIconProvider == null) { 206 imageIconProvider = getDefaultIconProvider(); 207 } 208 } 209 return imageIconProvider; 210 } 211 212 /** 213 * Image provider for source icon. 214 * 215 * @return image provider for styles icon 216 * @since 8097 217 */ 218 public final ImageProvider getIconProvider() { 219 ImageProvider i = getSourceIconProvider(); 220 if (!getErrors().isEmpty()) { 221 i = new ImageProvider(i).addOverlay(new ImageOverlay(new ImageProvider("misc", "error"), 0.5, 0.5, 1, 1)); 222 } else if (!getWarnings().isEmpty()) { 223 i = new ImageProvider(i).addOverlay(new ImageOverlay(new ImageProvider("warning-small"), 0.5, 0.5, 1, 1)); 224 } 225 return i; 226 } 227 228 /** 229 * Image for source icon. 230 * 231 * @return styles icon for display 232 */ 233 public final ImageIcon getIcon() { 234 return getIconProvider().setMaxSize(ImageProvider.ImageSizes.MENU).get(); 235 } 236 237 /** 238 * Return text to display as ToolTip. 239 * 240 * @return tooltip text containing error status 241 */ 242 public String getToolTipText() { 243 if (errors.isEmpty() && warnings.isEmpty()) 244 return null; 245 int n = errors.size() + warnings.size(); 246 return trn("There was an error when loading this style. Select ''Info'' from the right click menu for details.", 247 "There were {0} errors when loading this style. Select ''Info'' from the right click menu for details.", 248 n, n); 249 } 250 251 /** 252 * Gets the background color that was set in this style 253 * @return The color or <code>null</code> if it was not set 254 */ 255 public Color getBackgroundColorOverride() { 256 return null; 257 } 258}