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}