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