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