001/* 002 * Gridarta MMORPG map editor for Crossfire, Daimonin and similar games. 003 * Copyright (C) 2000-2011 The Gridarta Developers. 004 * 005 * This program is free software; you can redistribute it and/or modify 006 * it under the terms of the GNU General Public License as published by 007 * the Free Software Foundation; either version 2 of the License, or 008 * (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 013 * GNU General Public License for more details. 014 * 015 * You should have received a copy of the GNU General Public License along 016 * with this program; if not, write to the Free Software Foundation, Inc., 017 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 018 */ 019 020package net.sf.gridarta.gui.panel.pickmapchooser; 021 022import java.io.File; 023import java.util.ArrayList; 024import java.util.Collection; 025import java.util.Collections; 026import java.util.HashMap; 027import java.util.Iterator; 028import java.util.LinkedList; 029import java.util.List; 030import java.util.Map; 031import java.util.Set; 032import net.sf.gridarta.gui.mapfiles.MapFile; 033import net.sf.gridarta.gui.mapfiles.MapFileNameComparator; 034import net.sf.gridarta.model.archetype.Archetype; 035import net.sf.gridarta.model.gameobject.GameObject; 036import net.sf.gridarta.model.maparchobject.MapArchObject; 037import net.sf.gridarta.model.mapcontrol.MapControl; 038import net.sf.gridarta.model.mapmodel.MapModel; 039import net.sf.gridarta.model.mapmodel.MapModelListener; 040import net.sf.gridarta.model.mapmodel.MapSquare; 041import net.sf.gridarta.model.validation.ErrorCollector; 042import net.sf.gridarta.utils.Size2D; 043import org.jetbrains.annotations.NotNull; 044import org.jetbrains.annotations.Nullable; 045 046/** 047 * Maintains loaded {@link MapFile} instances. 048 * @author Andreas Kirschbaum 049 */ 050public class PickmapChooserModel<G extends GameObject<G, A, R>, A extends MapArchObject<A>, R extends Archetype<G, A, R>> implements Iterable<MapFile<G, A, R>> { 051 052 /** 053 * All open pickmaps. 054 */ 055 @NotNull 056 private final List<MapFile<G, A, R>> mapFiles = new ArrayList<MapFile<G, A, R>>(); 057 058 /** 059 * The listeners to notify. 060 */ 061 @NotNull 062 private final Collection<PickmapChooserModelListener<G, A, R>> listeners = new LinkedList<PickmapChooserModelListener<G, A, R>>(); 063 064 /** 065 * Maps {@link MapControl} instance to attached {@link MapModelListener}. 066 */ 067 @NotNull 068 private final Map<MapControl<G, A, R>, MapModelListener<G, A, R>> mapModelListeners = new HashMap<MapControl<G, A, R>, MapModelListener<G, A, R>>(); 069 070 /** 071 * Adds a map file. 072 * @param mapFile the map file to add 073 * @return the insertion index 074 */ 075 public int addMapFile(@NotNull final MapFile<G, A, R> mapFile) { 076 final int tmp = Collections.binarySearch(mapFiles, mapFile, MapFileNameComparator.INSTANCE); 077 final int index = tmp >= 0 ? tmp : -tmp - 1; 078 mapFiles.add(index, mapFile); 079 addMapModelListener(mapFile.getPickmap()); 080 return index; 081 } 082 083 /** 084 * Removes a map file. 085 * @param mapFile the map file to remove 086 * @return the removed index or <code>-1</code> if the map file does not 087 * exist 088 */ 089 public int removeMapFile(@NotNull final MapFile<G, A, R> mapFile) { 090 final int index = mapFiles.indexOf(mapFile); 091 if (index < 0) { 092 return -1; // might have been a pickmap that could not be loaded 093 } 094 removeMapModelListener(mapFile.getPickmap()); 095 mapFiles.remove(index); 096 return index; 097 } 098 099 /** 100 * Reverts a map file. 101 * @param oldPickmap the old pickmap control 102 * @param mapFile the map file to revert 103 * @return the reverted index or <code>-1</code> if the map file does not 104 * exist 105 */ 106 public int revertMapFile(@NotNull final MapControl<G, A, R> oldPickmap, @NotNull final MapFile<G, A, R> mapFile) { 107 final int index = mapFiles.indexOf(mapFile); 108 if (index < 0) { 109 return -1; // might have been a pickmap that could not be loaded 110 } 111 removeMapModelListener(oldPickmap); 112 addMapModelListener(mapFile.getPickmap()); 113 firePickmapReverted(mapFile); 114 return index; 115 } 116 117 /** 118 * Returns the pickmap by file name. 119 * @param file the file name 120 * @return the pickmap, or <code>null</code> if the file is unknown 121 */ 122 @Nullable 123 public MapFile<G, A, R> getPickmap(@NotNull final File file) { 124 for (final MapFile<G, A, R> mapFile : mapFiles) { 125 if (mapFile.getFile().equals(file)) { 126 return mapFile; 127 } 128 } 129 return null; 130 } 131 132 /** 133 * Returns whether no pickmaps exist in the current folder. 134 * @return whether no pickmaps exist 135 */ 136 public boolean isEmpty() { 137 return mapFiles.isEmpty(); 138 } 139 140 /** 141 * Returns the index of a map file. 142 * @param mapFile the map file to search 143 * @return the index or <code>-1</code> if the map file does not exist 144 */ 145 public int indexOf(@NotNull final MapFile<G, A, R> mapFile) { 146 return mapFiles.indexOf(mapFile); 147 } 148 149 /** 150 * {@inheritDoc} 151 */ 152 @NotNull 153 @Override 154 public Iterator<MapFile<G, A, R>> iterator() { 155 return Collections.unmodifiableList(mapFiles).iterator(); 156 } 157 158 /** 159 * Returns a map file by index. 160 * @param index the index 161 * @return the map file 162 */ 163 @NotNull 164 public MapFile<G, A, R> get(final int index) { 165 return mapFiles.get(index); 166 } 167 168 /** 169 * Removes all map files. 170 */ 171 public void clear() { 172 for (final MapFile<G, A, R> mapFile : mapFiles) { 173 removeMapModelListener(mapFile.getPickmap()); 174 } 175 mapFiles.clear(); 176 } 177 178 /** 179 * Adds a {@link PickmapChooserModelListener} to be notified. 180 * @param pickmapChooserModelListener the listener to add 181 */ 182 public void addPickmapChooserListener(@NotNull final PickmapChooserModelListener<G, A, R> pickmapChooserModelListener) { 183 listeners.add(pickmapChooserModelListener); 184 } 185 186 /** 187 * Notifies all listeners that the a pickmap has been reverted. 188 * @param mapFile the reverted pickmap 189 */ 190 private void firePickmapReverted(@NotNull final MapFile<G, A, R> mapFile) { 191 for (final PickmapChooserModelListener<G, A, R> listener : listeners) { 192 listener.pickmapReverted(mapFile); 193 } 194 } 195 196 /** 197 * Notifies all listeners that the active pickmap has changes. 198 * @param mapFile the active pickmap 199 */ 200 public void fireActivePickmapChanged(@Nullable final MapFile<G, A, R> mapFile) { 201 for (final PickmapChooserModelListener<G, A, R> listener : listeners) { 202 listener.activePickmapChanged(mapFile); 203 } 204 } 205 206 /** 207 * Notifies all listeners that a pickmap has been modified. 208 * @param index the index of the modified pickmap 209 * @param mapFile the modified pickmap 210 */ 211 private void firePickmapModifiedChanged(final int index, @NotNull final MapFile<G, A, R> mapFile) { 212 for (final PickmapChooserModelListener<G, A, R> listener : listeners) { 213 listener.pickmapModifiedChanged(index, mapFile); 214 } 215 } 216 217 /** 218 * Tracks a {@link MapControl} instance for changes. The map control 219 * instance must not be tracked. 220 * @param mapControl the map control instance 221 */ 222 private void addMapModelListener(@NotNull final MapControl<G, A, R> mapControl) { 223 final MapModel<G, A, R> mapModel = mapControl.getMapModel(); 224 final MapModelListener<G, A, R> mapModelListener = new PickmapChooserMapModelListener(mapModel); 225 mapModel.addMapModelListener(mapModelListener); 226 mapModelListeners.put(mapControl, mapModelListener); 227 } 228 229 /** 230 * Stops tracking a {@link MapControl} instance for changes. The map control 231 * instance must be tracked. 232 * @param mapControl the map control instance 233 */ 234 private void removeMapModelListener(@NotNull final MapControl<G, A, R> mapControl) { 235 final MapModelListener<G, A, R> mapModelListener = mapModelListeners.remove(mapControl); 236 assert mapModelListener != null; 237 mapControl.getMapModel().removeMapModelListener(mapModelListener); 238 } 239 240 /** 241 * A {@link MapModelListener} which tracks a pickmap for modifications. 242 */ 243 private class PickmapChooserMapModelListener implements MapModelListener<G, A, R> { 244 245 /** 246 * The tracked {@link MapModel}. 247 */ 248 @NotNull 249 private final MapModel<G, A, R> mapModel; 250 251 /** 252 * Creates a new instance. 253 * @param mapModel the tracked map map model 254 */ 255 private PickmapChooserMapModelListener(@NotNull final MapModel<G, A, R> mapModel) { 256 this.mapModel = mapModel; 257 } 258 259 @Override 260 public void mapSizeChanged(@NotNull final Size2D newSize) { 261 // ignore 262 } 263 264 @Override 265 public void mapSquaresChanged(@NotNull final Set<MapSquare<G, A, R>> mapSquares) { 266 // ignore 267 } 268 269 @Override 270 public void mapObjectsChanged(@NotNull final Set<G> gameObjects, @NotNull final Set<G> transientGameObjects) { 271 // ignore 272 } 273 274 @Override 275 public void errorsChanged(@NotNull final ErrorCollector<G, A, R> errors) { 276 // ignore 277 } 278 279 @Override 280 public void mapFileChanged(@Nullable final File oldMapFile) { 281 // ignore 282 } 283 284 @Override 285 public void modifiedChanged() { 286 int index = 0; 287 for (final MapFile<G, A, R> mapFile : mapFiles) { 288 if (mapFile.getPickmap().getMapModel() == mapModel) { 289 firePickmapModifiedChanged(index, mapFile); 290 return; 291 } 292 index++; 293 } 294 } 295 296 } 297 298}