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.map;
021
022import java.awt.BorderLayout;
023import java.awt.Dimension;
024import java.awt.Image;
025import java.beans.PropertyChangeEvent;
026import java.beans.PropertyChangeListener;
027import java.io.File;
028import java.util.prefs.Preferences;
029import javax.swing.Action;
030import javax.swing.Icon;
031import javax.swing.ImageIcon;
032import javax.swing.JButton;
033import javax.swing.JComponent;
034import javax.swing.JFileChooser;
035import javax.swing.JLabel;
036import javax.swing.JScrollPane;
037import javax.swing.SwingConstants;
038import javax.swing.filechooser.FileView;
039import net.sf.gridarta.MainControl;
040import net.sf.gridarta.gui.mapimagecache.MapImageCache;
041import net.sf.japi.swing.action.ActionBuilder;
042import net.sf.japi.swing.action.ActionBuilderFactory;
043import net.sf.japi.swing.action.ActionMethod;
044import net.sf.japi.swing.action.ToggleAction;
045import org.jetbrains.annotations.NotNull;
046import org.jetbrains.annotations.Nullable;
047
048/**
049 * Abstract base class for MapPreviewAccessories which are used for previewing
050 * maps in JFileChoosers.
051 * @author <a href="mailto:cher@riedquat.de">Christian Hujer</a>
052 */
053public class MapPreviewAccessory extends JComponent {
054
055    /**
056     * The serial version UID.
057     */
058    private static final long serialVersionUID = 1L;
059
060    /**
061     * Action Builder.
062     */
063    @NotNull
064    private static final ActionBuilder ACTION_BUILDER = ActionBuilderFactory.getInstance().getActionBuilder("net.sf.gridarta");
065
066    /**
067     * Preferences.
068     */
069    @NotNull
070    private static final Preferences preferences = Preferences.userNodeForPackage(MainControl.class);
071
072    /**
073     * Preferences key for automatic preview generation.
074     */
075    @NotNull
076    private static final String PREFERENCES_AUTO_GENERATE_PREVIEW = "autoGeneratePreviews";
077
078    /**
079     * The cache for map images.
080     */
081    @NotNull
082    private final MapImageCache<?, ?, ?> mapImageCache;
083
084    /**
085     * The preview label.
086     */
087    @NotNull
088    private final JLabel preview = new JLabel();
089
090    /**
091     * ToggleAction for auto-generate preview.
092     */
093    @NotNull
094    private final ToggleAction autoGeneratePreview = (ToggleAction) ACTION_BUILDER.createToggle(false, "autoGeneratePreviews", this);
095
096    /**
097     * Action for generating preview.
098     */
099    private final Action genPreview = ACTION_BUILDER.createAction(false, "genPreview", this);
100
101    /**
102     * The file chooser instance.
103     */
104    @NotNull
105    private final JFileChooser fileChooser;
106
107    /**
108     * The currently selected files, or <code>null</code> if no file is
109     * selected.
110     */
111    @Nullable
112    private File[] selectedFiles;
113
114    /**
115     * Whether previews should be auto-generated.
116     */
117    private boolean autoGeneratePreviews;
118
119    /**
120     * Creates an MapPreviewAccessory.
121     * @param mapImageCache the map image cache to use
122     * @param fileChooser the file chooser to attach to
123     */
124    public MapPreviewAccessory(@NotNull final MapImageCache<?, ?, ?> mapImageCache, @NotNull final JFileChooser fileChooser) {
125        this.mapImageCache = mapImageCache;
126        this.fileChooser = fileChooser;
127        buildUI();
128        setPreview("No Preview available");
129
130        final PropertyChangeListener propertyChangeListener = new PropertyChangeListener() {
131
132            @Override
133            public void propertyChange(final PropertyChangeEvent evt) {
134                final String prop = evt.getPropertyName();
135                if (JFileChooser.DIRECTORY_CHANGED_PROPERTY.equals(prop)) {
136                    setPreview("No Preview available");
137                } else if (JFileChooser.SELECTED_FILE_CHANGED_PROPERTY.equals(prop)) {
138                    final File selectedFile = (File) evt.getNewValue();
139                    if (selectedFile != null) {
140                        final Image previewImage = getMapPreview(selectedFile);
141                        if (previewImage != null) {
142                            setPreview(previewImage);
143                        } else {
144                            setPreview("No Preview available");
145                        }
146                    } else {
147                        setPreview("No Preview available");
148                    }
149                } else if (JFileChooser.SELECTED_FILES_CHANGED_PROPERTY.equals(prop)) {
150                    selectedFiles = (File[]) evt.getNewValue();
151                }
152            }
153
154        };
155        fileChooser.addPropertyChangeListener(propertyChangeListener);
156        fileChooser.setFileView(new MapFileView());
157    }
158
159    /**
160     * Build the user interface.
161     */
162    private void buildUI() {
163        setAutoGeneratePreviews(preferences.getBoolean(PREFERENCES_AUTO_GENERATE_PREVIEW, false));
164        setPreferredSize(new Dimension(240, 115));
165        setLayout(new BorderLayout());
166        add(autoGeneratePreview.createCheckBox(), BorderLayout.NORTH);
167        add(new JButton(genPreview), BorderLayout.SOUTH);
168        preview.setHorizontalAlignment(SwingConstants.CENTER);
169        add(new JScrollPane(preview), BorderLayout.CENTER);
170    }
171
172    /**
173     * Updates the text and icon of {@link #preview}.
174     * @param image the icon to set or <code>null</code>
175     */
176    @SuppressWarnings("NullableProblems")
177    private void setPreview(@Nullable final Image image) {
178        preview.setIcon(image == null ? null : new ImageIcon(image));
179        preview.setText(null);
180    }
181
182    /**
183     * Updates the text and icon of {@link #preview}.
184     * @param text the text to set
185     */
186    private void setPreview(@NotNull final String text) {
187        preview.setIcon(null);
188        preview.setText(text);
189    }
190
191    /**
192     * Switch automatic preview generation.
193     * @param autoGeneratePreviews <code>true</code> for automatically
194     * generating previews, <code>false</code> otherwise
195     */
196    @ActionMethod
197    public void setAutoGeneratePreviews(final boolean autoGeneratePreviews) {
198        this.autoGeneratePreviews = autoGeneratePreviews;
199        autoGeneratePreview.setSelected(autoGeneratePreviews);
200        preferences.putBoolean(PREFERENCES_AUTO_GENERATE_PREVIEW, autoGeneratePreviews);
201        if (autoGeneratePreviews) {
202            fileChooser.validate();
203            fileChooser.repaint();
204        }
205    }
206
207    /**
208     * Get whether to automatically generate previews.
209     * @return <code>true</code> if previews are automatically generated,
210     *         otherwise <code>false</code>
211     */
212    @ActionMethod
213    public boolean isAutoGeneratePreviews() {
214        return autoGeneratePreviews;
215    }
216
217    /**
218     * Generate a preview.
219     */
220    @ActionMethod
221    public void genPreview() {
222        final File[] files = selectedFiles;
223        if (files == null || files.length == 0) {
224            return;
225        }
226
227        for (final File file : files) {
228            mapImageCache.flush(file);
229
230            final Image image = mapImageCache.getOrCreatePreview(file);
231            setPreview(image);
232        }
233        fileChooser.validate();
234        fileChooser.repaint();
235    }
236
237    /**
238     * Get an icon.
239     * @param mapFile map file to get icon for
240     * @return icon for file mapFile or <code>null</code> if the icon was
241     *         neither cached nor loadable
242     */
243    @Nullable
244    public Image getMapIcon(@NotNull final File mapFile) {
245        return autoGeneratePreview.isSelected() ? mapImageCache.getOrCreateIcon(mapFile) : mapImageCache.getIcon(mapFile);
246    }
247
248    /**
249     * Get a preview.
250     * @param mapFile map file to get preview for
251     * @return preview for file mapFile or <code>null</code> if the preview was
252     *         neither cached nor loadable
253     */
254    @Nullable
255    private Image getMapPreview(@NotNull final File mapFile) {
256        return autoGeneratePreview.isSelected() ? mapImageCache.getOrCreatePreview(mapFile) : mapImageCache.getPreview(mapFile);
257    }
258
259    /**
260     * FileView for giving icons to map files.
261     */
262    private class MapFileView extends FileView {
263
264        @Override
265        public Icon getIcon(@NotNull final File f) {
266            @Nullable final Image image;
267            if (fileChooser.getCurrentDirectory().equals(f.getParentFile())) {
268                image = getMapIcon(f);
269            } else {
270                image = null;
271            }
272            return image != null ? new ImageIcon(image) : super.getIcon(f);
273        }
274
275    }
276
277}