001    /*
002     * EditorState
003     * 
004     * Copyright (c) 2000, 2001, 2002, 2003 Marco Schmidt.
005     * All rights reserved.
006     */
007    
008    package net.sourceforge.jiu.apps;
009    
010    import java.io.IOException;
011    import java.util.Locale;
012    import java.util.Vector;
013    import net.sourceforge.jiu.data.PixelImage;
014    import net.sourceforge.jiu.ops.Operation;
015    import net.sourceforge.jiu.ops.ProgressListener;
016    
017    /**
018     * Represents the state of the editor, including image(s), modified flag,
019     * current file name and directories and more.
020     * This class must not know GUI-specific information like Frame or JFrame objects.
021     * These GUI classes (more precisely, the JIU classes that extend them) will have to 
022     * know EditorState and update according to the information they retrieve from an 
023     * EditorState object associated with them.
024     * EditorState is a pure data container.
025     * @author Marco Schmidt
026     */
027    public class EditorState implements MenuIndexConstants
028    {
029            /**
030             * The default number of undo steps possible.
031             */
032            public static final int DEFAULT_MAX_UNDO_IMAGES = 2;
033    
034            /**
035             * The default number of redo steps possible.
036             */
037            public static final int DEFAULT_MAX_REDO_IMAGES = DEFAULT_MAX_UNDO_IMAGES;
038    
039            /**
040             * All allowed zoom levels, as percentage values in ascending order.
041             */
042            public static final int[] ZOOM_LEVELS = {5, 7, 10, 15, 20, 30, 50, 70, 100, 150, 200, 300, 500, 700, 1000, 2000, 3000, 5000};
043    
044            /**
045             * The index into the {@link #ZOOM_LEVELS} array that holds the original size zoom level (100 percent).
046             * So, ZOOM_LEVELS[ORIGINAL_SIZE_ZOOM_INDEX] must be equal to 100.
047             */
048            public static final int ORIGINAL_SIZE_ZOOM_INDEX = 8;
049    
050            /**
051             * Integer constant for <em>nearest neighbor interpolation</em>.
052             * A fast but ugly method.
053             */
054            public static final int INTERPOLATION_NEAREST_NEIGHBOR = 0;
055    
056            /**
057             * Integer constant for <em>bilinear neighbor interpolation</em>.
058             * A slow but nice method.
059             */
060            public static final int INTERPOLATION_BILINEAR = 1;
061    
062            /**
063             * Integer constant for <em>bicubic interpolation</em>.
064             * A very slow method, but with the nicest output of the three supported interpolation types.
065             */
066            public static final int INTERPOLATION_BICUBIC = 2;
067    
068            /**
069             * The default interpolation type, one of the three INTERPOLATION_xyz constants.
070             */
071            public static final int DEFAULT_INTERPOLATION = INTERPOLATION_NEAREST_NEIGHBOR;
072            private String currentDirectory;
073            private String fileName;
074            private PixelImage currentImage;
075            private int interpolation;
076            private Locale locale;
077            private int maxRedoImages;
078            private int maxUndoImages;
079            private boolean modified;
080            private Vector progressListeners;
081            private Vector redoImages;
082            private Vector redoModified;
083            private String startupImageName;
084            private Strings strings;
085            private Vector undoImages;
086            private Vector undoModified;
087            private int zoomIndex = ORIGINAL_SIZE_ZOOM_INDEX;
088            private double zoomFactorX;
089            private double zoomFactorY;
090            private boolean zoomToFit;
091    
092            /**
093             * Create new EditorState object and initialize its private fields
094             * to default values.
095             */
096            public EditorState()
097            {
098                    locale = Locale.getDefault();
099                    setStrings(null);
100                    progressListeners = new Vector();
101                    maxRedoImages = DEFAULT_MAX_REDO_IMAGES;
102                    maxUndoImages = DEFAULT_MAX_UNDO_IMAGES;
103                    redoImages = new Vector(maxRedoImages);
104                    redoModified = new Vector(maxRedoImages);
105                    undoImages = new Vector(maxUndoImages);
106                    undoModified = new Vector(maxUndoImages);
107                    zoomFactorX = 1.0;
108                    zoomFactorY = 1.0;
109                    zoomToFit = false;
110            }
111    
112            private void addImageToRedo(PixelImage image, boolean modifiedState)
113            {
114                    if (maxRedoImages < 1)
115                    {
116                            return;
117                    }
118                    if (redoImages.size() == maxRedoImages)
119                    {
120                            redoImages.setElementAt(null, 0);
121                            redoImages.removeElementAt(0);
122                            redoModified.removeElementAt(0);
123                    }
124                    redoImages.addElement(image);
125                    redoModified.addElement(new Boolean(modifiedState));
126            }
127    
128            private void addImageToUndo(PixelImage image, boolean modifiedState)
129            {
130                    if (maxUndoImages < 1)
131                    {
132                            return;
133                    }
134                    if (undoImages.size() == maxUndoImages)
135                    {
136                            undoImages.setElementAt(null, 0);
137                            undoImages.removeElementAt(0);
138                            undoModified.removeElementAt(0);
139                    }
140                    undoImages.addElement(image);
141                    undoModified.addElement(new Boolean(modifiedState));
142            }
143    
144            /**
145             * Adds the argument progress listener to the internal list of progress
146             * listeners to be notified by progress updates.
147             * @param pl object implementing ProgressListener to be added
148             */
149            public void addProgressListener(ProgressListener pl)
150            {
151                    progressListeners.addElement(pl);
152            }
153    
154            /**
155             * Returns if a redo operation is possible right now.
156             */
157            public boolean canRedo()
158            {
159                    return (redoImages.size() > 0);
160            }
161    
162            /**
163             * Returns if an undo operation is possible right now.
164             */
165            public boolean canUndo()
166            {
167                    return (undoImages.size() > 0);
168            }
169    
170            public void clearRedo()
171            {
172                    int index = 0;
173                    while (index < redoImages.size())
174                    {
175                            redoImages.setElementAt(null, index++);
176                    }
177                    redoImages.setSize(0);
178                    redoModified.setSize(0);
179            }
180    
181            public void clearUndo()
182            {
183                    int index = 0;
184                    while (index < undoImages.size())
185                    {
186                            undoImages.setElementAt(null, index++);
187                    }
188                    undoImages.setSize(0);
189                    undoModified.setSize(0);
190            }
191    
192            public void ensureStringsAvailable()
193            {
194                    if (getStrings() == null)
195                    {
196                            setStrings(Strings.DEFAULT_LANGUAGE_ISO_639_CODE);
197                    }
198            }
199    
200            /** 
201             * Returns the current directory.
202             * This directory will be used when file dialogs are opened.
203             */
204            public String getCurrentDirectory()
205            {
206                    return currentDirectory;
207            }
208    
209            /**
210             * Returns the name of the file from which the current image was loaded.
211             */
212            public String getFileName()
213            {
214                    return fileName;
215            }
216    
217            /**
218             * Returns the image object currently loaded.
219             */
220            public PixelImage getImage()
221            {
222                    return currentImage;
223            }
224    
225            /**
226             * Returns the current interpolation type, one of the INTERPOLATION_xyz constants.
227             */
228            public int getInterpolation()
229            {
230                    return interpolation;
231            }
232    
233            /**
234             * Returns the Locale object currently used.
235             */
236            public Locale getLocale()
237            {
238                    return locale;
239            }
240    
241            /**
242             * Returns the current modified state (true if image was modified and not saved 
243             * after modification, false otherwise).
244             */
245            public boolean getModified()
246            {
247                    return modified;
248            }
249    
250            /**
251             * Returns the internal list of progress listeners.
252             */
253            public Vector getProgressListeners()
254            {
255                    return progressListeners;
256            }
257    
258            public String getStartupImageName()
259            {
260                    return startupImageName;
261            }
262    
263            /**
264             * Returns the Strings object currently in use.
265             */
266            public Strings getStrings()
267            {
268                    return strings;
269            }
270    
271            /** 
272             * Returns the current zoom factor in horizontal direction.
273             * The value 1.0 means that the image is displayed at its 
274             * original size.
275             * Anything smaller means that the image is scaled down,
276             * anything larger means that the image is scaled up.
277             * The value must not be smaller than or equal to 0.0.
278             * @return zoom factor in horizontal direction
279             * @see #getZoomFactorY
280             */
281            public double getZoomFactorX()
282            {
283                    return zoomFactorX;
284            }
285    
286            /** 
287             * Returns the current zoom factor in vertical direction.
288             * The value 1.0 means that the image is displayed at its 
289             * original size.
290             * Anything smaller means that the image is scaled down,
291             * anything larger means that the image is scaled up.
292             * The value must not be smaller than or equal to 0.0.
293             * @return zoom factor in vertical direction
294             * @see #getZoomFactorX
295             */
296            public double getZoomFactorY()
297            {
298                    return zoomFactorY;
299            }
300    
301            /**
302             * Returns if image display is currently set to "zoom to fit"
303             * Zoom to fit means that the image is always zoomed to fit exactly into the window.
304             */
305            public boolean getZoomToFit()
306            {
307                    return zoomToFit;
308            }
309    
310            /**
311             * Returns if this state encapsulates an image object.
312             */
313            public boolean hasImage()
314            {
315                    return (currentImage != null);
316            }
317    
318            /**
319             * Adds all ProgressListener objects from the internal list of listeners to
320             * the argument operation.
321             */
322            public void installProgressListeners(Operation op)
323            {
324                    if (op == null)
325                    {
326                            return;
327                    }
328                    // cannot use Iterator because it's 1.2+
329                    int index = 0;
330                    while (index < progressListeners.size())
331                    {
332                            ProgressListener pl = (ProgressListener)progressListeners.elementAt(index++);
333                            op.addProgressListener(pl);
334                    }
335            }
336    
337            /**
338             * Returns if the image is displayed at maximum zoom level.
339             */
340            public boolean isMaximumZoom()
341            {
342                    return zoomIndex == ZOOM_LEVELS.length - 1;
343            }
344    
345            /**
346             * Returns if the image is displayed at minimum zoom level.
347             */
348            public boolean isMinimumZoom()
349            {
350                    return zoomIndex == 0;
351            }
352    
353            /**
354             * Returns if the current zoom level is set to original size
355             * (each image pixel is displayed as one pixel).
356             */
357            public boolean isZoomOriginalSize()
358            {
359                    return zoomIndex == ORIGINAL_SIZE_ZOOM_INDEX;
360            }
361    
362            /**
363             * Perform a redo operation, restore the state before the last undo operation.
364             * Before that is done, save the current state for an undo.
365             */
366            public void redo()
367            {
368                    if (redoImages.size() < 1)
369                    {
370                            return;
371                    }
372                    addImageToUndo(currentImage, modified);
373                    int redoIndex = redoImages.size() - 1;
374                    currentImage = (PixelImage)redoImages.elementAt(redoIndex);
375                    redoImages.setElementAt(null, redoIndex);
376                    redoImages.setSize(redoIndex);
377                    modified = ((Boolean)redoModified.elementAt(redoIndex)).booleanValue();
378                    redoModified.setSize(redoIndex);
379            }
380    
381            public void resetZoomFactors()
382            {
383                    setZoomFactors(1.0, 1.0);
384            }
385    
386            /**
387             * Sets a new current directory.
388             * @param newCurrentDirectory the directory to be used as current directory from now on
389             */
390            public void setCurrentDirectory(String newCurrentDirectory)
391            {
392                    currentDirectory = newCurrentDirectory;
393            }
394    
395            /**
396             * Sets a new file name.
397             * This is used mostly after a new image was loaded from a file or
398             * if the current image is closed (then a null value would be given to this method).
399             * @param newFileName new name of the current file
400             */
401            public void setFileName(String newFileName)
402            {
403                    fileName = newFileName;
404            }
405    
406            /**
407             * Sets image and modified state to argument values.
408             * @param image new current image
409             * @param newModifiedState new state of modified flag
410             */
411            public void setImage(PixelImage image, boolean newModifiedState)
412            {
413                    if (hasImage())
414                    {
415                            addImageToUndo(currentImage, modified);
416                    }
417                    currentImage = image;
418                    modified = newModifiedState;
419                    clearRedo();
420            }
421    
422            public void setStartupImageName(String name)
423            {
424                    startupImageName = name;
425            }
426    
427            /**
428             * Sets a new interpolation type to be used for display.
429             * @param newInterpolation an int for the interpolation type, must be one of the INTERPOLATION_xyz constants
430             */
431            public void setInterpolation(int newInterpolation)
432            {
433                    if (newInterpolation == INTERPOLATION_NEAREST_NEIGHBOR ||
434                        newInterpolation == INTERPOLATION_BILINEAR ||
435                        newInterpolation == INTERPOLATION_BICUBIC)
436                    {
437                            interpolation = newInterpolation;
438                    }
439            }
440    
441            /**
442             * Defines a new Locale to be used.
443             * @param newLocale Locale object used from now on
444             * @see #setStrings
445             */
446            public void setLocale(Locale newLocale)
447            {
448                    locale = newLocale;
449            }
450    
451            /*public void setModified(boolean modifiedState)
452            {
453                    modified = modifiedState;
454            }*/
455    
456            /**
457             * Set new Strings resource.
458             * @param iso639Code language of the new Strings resource
459             */
460            public void setStrings(String iso639Code)
461            {
462                    Strings newStrings = null;
463                    try
464                    {
465                            StringLoader loader;
466                            if (iso639Code == null)
467                            {
468                                    loader = new StringLoader();
469                            }
470                            else
471                            {
472                                    loader = new StringLoader(iso639Code);
473                            }
474                            newStrings = loader.load();
475                    }
476                    catch (IOException ioe)
477                    {
478                    }
479                    if (newStrings != null)
480                    {
481                            strings = newStrings;
482                    }
483            }
484    
485            /**
486             * Sets the zoom factors to the argument values.
487             */
488            public void setZoomFactors(double zoomX, double zoomY)
489            {
490                    zoomFactorX = zoomX;
491                    zoomFactorY = zoomY;
492            }
493    
494            /**
495             * Perform an undo step - the previous state will be set, the
496             * current state will be saved for a redo operation
497             * @see #redo
498             */
499            public void undo()
500            {
501                    if (undoImages.size() < 1)
502                    {
503                            return;
504                    }
505                    addImageToRedo(currentImage, modified);
506                    int undoIndex = undoImages.size() - 1;
507                    currentImage = (PixelImage)undoImages.elementAt(undoIndex);
508                    undoImages.setElementAt(null, undoIndex);
509                    undoImages.setSize(undoIndex);
510                    modified = ((Boolean)undoModified.elementAt(undoIndex)).booleanValue();
511                    undoModified.setSize(undoIndex);
512            }
513    
514            /**
515             * Increase the zoom level by one.
516             * @see #zoomOut
517             * @see #zoomSetOriginalSize
518             */
519            public void zoomIn()
520            {
521                    if (zoomIndex + 1 == ZOOM_LEVELS.length)
522                    {
523                            return;
524                    }
525                    zoomIndex++;
526                    zoomFactorX = 1.0 * ZOOM_LEVELS[zoomIndex] / 100;
527                    zoomFactorY = zoomFactorX;
528            }
529    
530            /**
531             * Decrease the zoom level by one.
532             * @see #zoomIn
533             * @see #zoomSetOriginalSize
534             */
535            public void zoomOut()
536            {
537                    if (zoomIndex == 0)
538                    {
539                            return;
540                    }
541                    zoomIndex--;
542                    zoomFactorX = 1.0 * ZOOM_LEVELS[zoomIndex] / 100;
543                    zoomFactorY = zoomFactorX;
544            }
545    
546            /**
547             * Set the zoom level to 100 percent (1:1).
548             * Each image pixel will be displayed as one pixel
549             * @see #zoomIn
550             * @see #zoomOut
551             */
552            public void zoomSetOriginalSize()
553            {
554                    zoomIndex = ORIGINAL_SIZE_ZOOM_INDEX;
555                    zoomFactorX = 1.0;
556                    zoomFactorY = 1.0;
557            }
558    }