org.photovault.swingui.PhotoViewController.java Source code

Java tutorial

Introduction

Here is the source code for org.photovault.swingui.PhotoViewController.java

Source

/*
  Copyright (c) 2007, Harri Kaimio
      
  This file is part of Photovault.
    
  Photovault is free software; you can redistribute it and/or modify it
  under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.
    
  Photovault is distributed in the hope that it will be useful, but
  WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  General Public License for more details.
    
  You should have received a copy of the GNU General Public License
  along with Photovault; if not, write to the Free Software Foundation,
  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA 
 */

package org.photovault.swingui;

import abbot.tester.JSpinnerTester;
import java.awt.Color;
import java.awt.Container;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Toolkit;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.KeyEvent;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;
import java.util.TreeSet;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.AbstractAction;
import javax.swing.BoxLayout;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JLayeredPane;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.KeyStroke;
import javax.swing.ScrollPaneConstants;
import org.jdesktop.jxlayer.JXLayer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.photovault.command.ApplyChangeCommand;
import org.photovault.command.CommandExecutedEvent;
import org.photovault.command.DataAccessCommand;
import org.photovault.folder.PhotoFolder;
import org.photovault.folder.PhotoFolderDAO;
import org.photovault.imginfo.ChangePhotoInfoCommand;
import org.photovault.imginfo.CreateCopyImageCommand;
import org.photovault.imginfo.FileLocation;
import org.photovault.imginfo.PhotoCollection;
import org.photovault.imginfo.PhotoInfo;
import org.photovault.imginfo.PhotoInfoDAO;
import org.photovault.imginfo.indexer.IndexFileTask;
import org.photovault.imginfo.indexer.IndexingResult;
import org.photovault.replication.Change;
import org.photovault.replication.ChangeDAO;
import org.photovault.replication.ChangeDTO;
import org.photovault.replication.ChangeFactory;
import org.photovault.replication.DTOResolverFactory;
import org.photovault.replication.ObjectHistoryDTO;
import org.photovault.replication.VersionedObjectEditor;
import org.photovault.swingui.framework.AbstractController;
import org.photovault.swingui.framework.DataAccessAction;
import org.photovault.swingui.framework.DefaultEvent;
import org.photovault.swingui.framework.DefaultEventListener;
import org.photovault.swingui.framework.PersistenceController;
import org.photovault.swingui.taskscheduler.SwingWorkerTaskScheduler;
import org.photovault.swingui.taskscheduler.TaskFinishedEvent;
import org.photovault.swingui.volumetree.ExtDirPhotos;
import org.photovault.taskscheduler.BackgroundTask;

/**
 Controller for the componenst actually used for viewing or editing photos. This 
 controller handles interaction between {@link PhotoCollectionThumbView} that shows
 all photos in certain collection as thumbnails, {@link JAIPhotoViewer} that 
 displays the selected photo as a preview and related editing dialogs (property, 
 cropping, color editing etc.)
 */

public class PhotoViewController extends PersistenceController {

    private static Log log = LogFactory.getLog(PhotoViewController.class.getName());

    PhotoInfoDAO photoDAO = null;

    PhotoFolderDAO folderDAO = null;

    private PhotoCollectionThumbView thumbPane;

    private JAIPhotoViewer previewPane;

    private JScrollPane thumbScroll;

    private JPanel collectionPane;

    private JSplitPane splitPane;

    private JLayeredPane layeredPane;

    private JXLayer<JScrollPane> scrollLayer;

    private ProgressIndicatorLayer progressLayer;

    /**
     Photos currently in model.
     */
    private List<PhotoInfo> photos = new ArrayList<PhotoInfo>();

    /** Creates a new instance of PhotoViewController */
    public PhotoViewController(Container view, AbstractController parentController) {
        super(view, parentController);
        photoDAO = getDAOFactory().getPhotoInfoDAO();
        folderDAO = getDAOFactory().getPhotoFolderDAO();
        ImageIcon rotateCWIcon = getIcon("rotate_cw.png");
        ImageIcon rotateCCWIcon = getIcon("rotate_ccw.png");
        ImageIcon rotate180DegIcon = getIcon("rotate_180.png");

        registerAction("rotate_cw", new RotateSelectedPhotoAction(this, 90, "Rotate CW", rotateCWIcon,
                "Rotates the selected photo 90 degrees clockwise", KeyEvent.VK_R));
        registerAction("rotate_ccw", new RotateSelectedPhotoAction(this, 270, "Rotate CCW", rotateCCWIcon,
                "Rotates the selected photo 90 degrees counterclockwise", KeyEvent.VK_L));
        registerAction("rotate_180", new RotateSelectedPhotoAction(this, 180, "Rotate 180 degrees",
                rotate180DegIcon, "Rotates the selected photo 180 degrees counterclockwise", KeyEvent.VK_T));
        registerAction("rotate_180", new RotateSelectedPhotoAction(this, 180, "Rotate 180 degrees",
                rotate180DegIcon, "Rotates the selected photo 180 degrees counterclockwise", KeyEvent.VK_T));
        String qualityStrings[] = { "Unevaluated", "Top", "Good", "OK", "Poor", "Unusable" };
        String qualityIconnames[] = { "quality_unevaluated.png", "quality_top.png", "quality_good.png",
                "quality_ok.png", "quality_poor.png", "quality_unusable.png" };
        KeyStroke qualityAccelerators[] = { null,
                KeyStroke.getKeyStroke(KeyEvent.VK_5, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()),
                KeyStroke.getKeyStroke(KeyEvent.VK_4, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()),
                KeyStroke.getKeyStroke(KeyEvent.VK_3, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()),
                KeyStroke.getKeyStroke(KeyEvent.VK_2, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()),
                KeyStroke.getKeyStroke(KeyEvent.VK_1, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()), };
        ImageIcon[] qualityIcons = new ImageIcon[qualityStrings.length];
        for (int n = 0; n < qualityStrings.length; n++) {
            qualityIcons[n] = getIcon(qualityIconnames[n]);
            DataAccessAction qualityAction = new SetPhotoQualityAction(this, n, qualityStrings[n], qualityIcons[n],
                    "Set quality of selected phots to \"" + qualityStrings[n] + "\"", null);
            qualityAction.putValue(AbstractAction.ACCELERATOR_KEY, qualityAccelerators[n]);
            registerAction("quality_" + n, qualityAction);

            registerEventListener(TaskFinishedEvent.class, new DefaultEventListener<BackgroundTask>() {

                public void handleEvent(DefaultEvent<BackgroundTask> event) {
                    BackgroundTask task = event.getPayload();
                    if (task instanceof IndexFileTask) {
                        IndexFileTask ifTask = (IndexFileTask) task;
                        if (ifTask.getResult() == IndexingResult.ERROR
                                || ifTask.getResult() == IndexingResult.NOT_IMAGE) {
                            return;
                        }
                        UUID volId = ifTask.getVolume().getId();
                        FileLocation loc = ifTask.getFileLocation();
                        Set<PhotoInfo> photos = ifTask.getPhotosFound();
                        log.debug("Found file " + loc.getFile() + " in volume " + volId);
                        for (PhotoInfo p : photos) {
                            log.debug("   linked to photo " + p.getUuid());
                        }
                        if (collection instanceof ExtDirPhotos) {
                            ExtDirPhotos dir = (ExtDirPhotos) collection;
                            if (loc.getVolume().getId().equals(dir.getVolId())
                                    && loc.getDirName().equals(dir.getDirPath())) {
                                addPhotos(photos);
                                updateThumbView();
                            }
                        }
                    }
                }
            });
        }

        // Create the UI controls
        thumbPane = new PhotoCollectionThumbView(this, null);
        thumbPane.addSelectionChangeListener(new SelectionChangeListener() {

            public void selectionChanged(SelectionChangeEvent e) {
                thumbSelectionChanged(e);
            }
        });
        previewPane = new JAIPhotoViewer(this);
        previewPane.getActionMap().put("hide_fullwindow_preview", new HidePhotoPreviewAction(this));
        previewPane.getActionMap().put("move_next", thumbPane.getSelectNextAction());
        previewPane.getActionMap().put("move_prev", thumbPane.getSelectPreviousAction());

        // Create the split pane to display both of these components

        thumbScroll = new JScrollPane(thumbPane);
        thumbPane.setBackground(Color.WHITE);
        thumbScroll.getViewport().setBackground(Color.WHITE);
        thumbScroll.addComponentListener(new ComponentAdapter() {
            @Override
            public void componentResized(ComponentEvent e) {
                handleThumbAreaResize();
            }
        });

        scrollLayer = new JXLayer<JScrollPane>(thumbScroll);
        progressLayer = new ProgressIndicatorLayer();
        scrollLayer.setUI(progressLayer);

        collectionPane = new JPanel();
        splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
        layeredPane = new JLayeredPane();
        layeredPane.setLayout(new StackLayout());
        collectionPane.add(splitPane);
        collectionPane.add(layeredPane);
        GridBagLayout layout = new GridBagLayout();
        collectionPane.setLayout(layout);

        GridBagConstraints c = new GridBagConstraints();
        c.fill = GridBagConstraints.BOTH;
        c.weighty = 1.0;
        c.weightx = 1.0;
        c.gridy = 0;
        // collectionPane.add( scrollLayer );
        layout.setConstraints(splitPane, c);
        layout.setConstraints(layeredPane, c);
        //        collectionPane.add( previewPane );
        thumbPane.setRowHeight(200);
        setLayout(Layout.ONLY_THUMBS);

        /*
        Register action so that we are notified of changes to currently
        displayed folder
         */
        registerEventListener(CommandExecutedEvent.class, new DefaultEventListener<DataAccessCommand>() {

            public void handleEvent(DefaultEvent<DataAccessCommand> event) {
                DataAccessCommand cmd = event.getPayload();
                if (cmd instanceof ChangePhotoInfoCommand) {
                    photoChangeCommandExecuted((ChangePhotoInfoCommand) cmd);
                } else if (cmd instanceof CreateCopyImageCommand) {
                    imageCreated((CreateCopyImageCommand) cmd);
                } else if (cmd instanceof ApplyChangeCommand) {
                    changeApplied((ApplyChangeCommand) cmd);
                }
            }
        });
    }

    /**
     * Comparator used to sort photos visible in thumbnail view
     */
    Comparator photoComparator;

    /**
     * Set the comparator used to sort photos in thumbnail view
     * @param c
     */
    void setPhotoComparator(Comparator c) {
        photoComparator = c;
        updateThumbView();
    }

    /**
     * Get the comparator used to sort photos in thumbnail view
     * @return
     */
    Comparator getPhotoComparator() {
        return photoComparator;
    }

    /**
     * Update the thumb view to show photos is the {@link #photos} collections,
     * sorted by {@link #photoComparator}
     */
    private void updateThumbView() {
        if (photoComparator != null && photos != null) {
            Collections.sort(photos, photoComparator);
        }
        thumbPane.setPhotos(photos);
    }

    /**
     This method is called after a {@link ChangePhotoInfoCommand} has been 
     executed somewhere in the application. It applies the modifications
     to current model.
     @param cmd The executed command
     */
    void photoChangeCommandExecuted(ChangePhotoInfoCommand cmd) {
        if (collection instanceof PhotoFolder) {
            // Does this command impact our current folder?
            switch (cmd.getFolderState((PhotoFolder) collection)) {
            case ADDED:
                addPhotos(cmd.getChangedPhotos());
                updateThumbView();
                break;
            case REMOVED:
                removePhotos(cmd.getChangedPhotos());
                // None of the affected photos can belong to the model anymore
                // so no need for further checks.
                updateThumbView();
                return;
            default:
                // No impact to this folder
                break;
            }
        }

        // Update photos that belong to this collection
        for (PhotoInfo p : cmd.getChangedPhotos()) {
            if (containsPhoto(p)) {
                PhotoInfo mergedPhoto = (PhotoInfo) getPersistenceContext().merge(p);
            }
        }
        thumbPane.setPhotos(photos);
    }

    private void imageCreated(CreateCopyImageCommand cmd) {
        PhotoInfo p = cmd.getPhoto();
        log.debug("image created for photo " + p.getUuid());
        if (containsPhoto(p)) {
            PhotoInfo mergedPhoto = (PhotoInfo) getPersistenceContext().merge(p);
            log.debug("merged chages to photo " + mergedPhoto.getUuid());
            if (!mergedPhoto.hasThumbnail()) {
                log.error("he photo does not have a thumbnail!!!");
                getPersistenceContext().update(mergedPhoto);
            }
        }
    }

    private void changeApplied(ApplyChangeCommand cmd) {
        for (ChangeDTO ch : cmd.getChanges()) {
            UUID id = ch.getTargetUuid();
            for (PhotoInfo p : photos) {
                if (id.equals(p.getUuid())) {
                    DTOResolverFactory rf = getDAOFactory().getDTOResolverFactory();
                    VersionedObjectEditor e = new VersionedObjectEditor(p, rf);
                    ChangeDAO chDao = getDAOFactory().getChangeDAO();
                    Change c = chDao.findChange(ch.getChangeUuid());
                    e.changeToVersion(c);
                    break;
                }
            }
        }
        thumbPane.setPhotos(photos);
    }

    /**
     Add all photos from a collection to current model if they are not yet
     part of it.
     @param newPhotos Collection of (potentially detached) photos
     */
    private void addPhotos(Collection<PhotoInfo> newPhotos) {
        for (PhotoInfo p : newPhotos) {
            if (!containsPhoto(p)) {
                photos.add((PhotoInfo) getPersistenceContext().merge(p));
            }
        }
    }

    /**
     Remove photos from current model if they belong to it
     @param removePhotos Collection of photos that will be removed. Potentially 
     detached instances.
     */
    private void removePhotos(Collection<PhotoInfo> removePhotos) {
        Set<UUID> removeIds = new TreeSet<UUID>();
        for (PhotoInfo p : removePhotos) {
            removeIds.add(p.getUuid());
        }

        ListIterator<PhotoInfo> iter = photos.listIterator();
        while (iter.hasNext()) {
            if (removeIds.contains(iter.next().getUuid())) {
                iter.remove();
            }
        }
    }

    /**
     Check whether a given photo belongs currently to the model.
     @param photo Potentially detached photo instance
     @return true if the model contains an instance of the same photo, false
     otherwise.
     */
    private boolean containsPhoto(PhotoInfo photo) {
        return containsPhoto(photo.getUuid());
    }

    private boolean containsPhoto(UUID photoUuid) {
        for (PhotoInfo p : photos) {
            if (p.getUuid().equals(photoUuid)) {
                return true;
            }
        }
        return false;

    }

    /**
     This method is called when selection in the thumbnail view changes.
     @param e The selection event
     */
    void thumbSelectionChanged(SelectionChangeEvent e) {
        Collection selection = thumbPane.getSelection();
        if (selection.size() == 1) {
            Cursor oldCursor = getView().getCursor();
            getView().setCursor(new Cursor(Cursor.WAIT_CURSOR));
            PhotoInfo selected = (PhotoInfo) (selection.toArray())[0];
            try {
                previewPane.setPhoto(selected);
            } catch (FileNotFoundException ex) {
                JOptionPane.showMessageDialog(getView(), "Image file for this photo was not found",
                        "File not found", JOptionPane.ERROR_MESSAGE);
            }
            getView().setCursor(oldCursor);
        } else {
            try {
                previewPane.setPhoto(null);
            } catch (FileNotFoundException ex) {
                // No exception expected when calling with null
            }
        }
        this.fireEvent(e);
    }

    void showSelectedPhotoFullWindow() {
        if (getSelection().size() == 1) {
            if (layout != Layout.ONLY_THUMBS) {
                setLayout(Layout.ONLY_THUMBS);
            }
        }
        previewPane.setVisible(true);
    }

    void hideSelectedPhoto() {
        if (layout == Layout.ONLY_THUMBS) {
            previewPane.setVisible(false);
        }
    }

    public Collection getSelection() {
        return thumbPane.getSelection();
    }

    public enum Layout {
        PREVIEW_VERTICAL_THUMBS, PREVIEW_HORIZONTAL_THUMBS, ONLY_PREVIEW, ONLY_THUMBS
    };

    private Layout layout;

    public void setLayout(Layout layout) {
        this.layout = layout;
        switch (layout) {
        case ONLY_PREVIEW:
            setupLayoutNoThumbs();
            break;
        case ONLY_THUMBS:
            setupLayoutNoPreview();
            break;
        case PREVIEW_HORIZONTAL_THUMBS:
            setupLayoutPreviewWithHorizontalIcons();
            break;
        case PREVIEW_VERTICAL_THUMBS:
            setupLayoutPreviewWithVerticalIcons();
            break;
        }
    }

    public Layout getLayout() {
        return layout;
    }

    /**
     * Sets up the window layout so that the collection is displayed as one vertical
     * column with preview image on right
     */
    private void setupLayoutPreviewWithVerticalIcons() {
        // Minimum size is the size of one thumbnail
        int thumbColWidth = thumbPane.getColumnWidth();
        int thumbRowHeight = thumbPane.getRowHeight();
        scrollLayer.setMinimumSize(new Dimension(thumbColWidth + 20, thumbRowHeight));
        scrollLayer.setPreferredSize(new Dimension(thumbColWidth + 20, thumbRowHeight));
        thumbScroll.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
        thumbScroll.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
        scrollLayer.setVisible(true);
        thumbPane.setColumnCount(1);
        previewPane.setVisible(true);
        layeredPane.removeAll();
        layeredPane.setVisible(false);
        splitPane.setVisible(true);
        splitPane.remove(previewPane);
        splitPane.remove(scrollLayer);
        splitPane.setOrientation(JSplitPane.HORIZONTAL_SPLIT);
        splitPane.setLeftComponent(scrollLayer);
        splitPane.setRightComponent(previewPane);
        // Left component should not be resized 
        splitPane.setResizeWeight(0.0);
        Dimension minThumbDim = thumbPane.getMinimumSize();
        scrollLayer.setMinimumSize(new Dimension((int) minThumbDim.getWidth(), 0));
        previewPane.setMinimumSize(new Dimension(splitPane.getWidth() - 250 - splitPane.getInsets().left, 0));
        splitPane.validate();

        getView().validate();
    }

    /**
    * Sets up the window layout so that the collection is displayed as one horizontal
    * row with preview image above it.
    */
    private void setupLayoutPreviewWithHorizontalIcons() {
        // Minimum size is the size of one thumbnail
        int thumbColWidth = thumbPane.getColumnWidth();
        int thumbRowHeight = thumbPane.getRowHeight();
        thumbScroll.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
        thumbScroll.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER);
        scrollLayer.setMinimumSize(new Dimension(thumbColWidth, thumbRowHeight + 30));
        scrollLayer.setPreferredSize(new Dimension(thumbColWidth, thumbRowHeight + 30));
        scrollLayer.setVisible(true);
        thumbPane.setRowCount(1);
        previewPane.setVisible(true);
        layeredPane.removeAll();
        layeredPane.setVisible(false);
        splitPane.setVisible(true);
        splitPane.setOrientation(JSplitPane.VERTICAL_SPLIT);
        splitPane.remove(previewPane);
        splitPane.remove(scrollLayer);
        splitPane.setTopComponent(previewPane);
        splitPane.setBottomComponent(scrollLayer);
        // Bottom component should not be resized
        splitPane.setResizeWeight(1.0);
        Dimension minThumbDim = thumbPane.getMinimumSize();
        scrollLayer.setMinimumSize(new Dimension(0, minThumbDim.height));
        previewPane.setMinimumSize(new Dimension(0, splitPane.getHeight() - 250 - splitPane.getInsets().top));
        splitPane.validate();
        getView().validate();
    }

    /**
     Hide the preview pane
     */
    private void setupLayoutNoPreview() {
        // Minimum size is the size of one thumbnail
        thumbScroll.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
        thumbScroll.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
        int thumbColWidth = thumbPane.getColumnWidth();
        int thumbRowHeight = thumbPane.getRowHeight();
        splitPane.setVisible(false);
        layeredPane.setVisible(true);
        scrollLayer.setMinimumSize(new Dimension(thumbColWidth, thumbRowHeight + 50));
        scrollLayer.setVisible(true);
        thumbPane.setRowCount(-1);
        thumbPane.setColumnCount(-1);
        splitPane.setOrientation(JSplitPane.VERTICAL_SPLIT);
        splitPane.remove(previewPane);
        splitPane.remove(scrollLayer);
        layeredPane.add(scrollLayer, new Integer(1));
        layeredPane.add(previewPane, new Integer(2));
        previewPane.setVisible(false);
        thumbScroll.setVisible(true);
        getView().validate();
    }

    /**
     Show only preview image, no thumbnail pane.
     */
    private void setupLayoutNoThumbs() {
        previewPane.setVisible(true);
    }

    void setPreviewSize(int width) {
        thumbPane.setThumbWidth(width);
        setLayout(layout);
    }

    void handleThumbAreaResize() {
        if (layout == Layout.PREVIEW_HORIZONTAL_THUMBS) {
            int newHeight = thumbScroll.getViewport().getHeight();
            thumbPane.setRowHeight(newHeight - 10);
        } else if (layout == Layout.PREVIEW_VERTICAL_THUMBS) {
            int newWidth = thumbScroll.getViewport().getWidth();
            thumbPane.setRowHeight(newWidth);
        }
    }

    public SwingWorkerTaskScheduler getBackgroundTaskScheduler() {
        return (SwingWorkerTaskScheduler) Photovault.getInstance().getTaskScheduler();
    }

    void setIndexingOngoing(boolean isOngoing) {
        progressLayer.setInProgress(isOngoing);
    }

    void setIndexingPercentComplete(int percentComplete) {
        progressLayer.setPercentComplete(percentComplete);
    }

    /**
     Get the component showing thumbnails
     */
    PhotoCollectionThumbView getThumbPane() {
        return thumbPane;
    }

    /**
     Get the preview pane component
     */
    JAIPhotoViewer getPreviewPane() {
        return previewPane;
    }

    JPanel getCollectionPane() {
        return collectionPane;
    }

    PhotoCollection collection = null;

    /**
     Set the collection that is contorlled by this controller.
     @param c The collection, this can (and most probably is) an detached 
     instance of folder or an unexecuted query.
     */
    void setCollection(PhotoCollection c) {
        collection = c;
        photos = null;
        /*
         Clear Hibernate cache to avoid memory leaks and race conditions with
         command events from already executed commands.
         */
        getPersistenceContext().clear();
        if (c != null) {
            photos = c.queryPhotos(getPersistenceContext());
        }
        updateThumbView();
    }

    PhotoCollection getCollection() {
        return collection;
    }

    /**
     Loads an icon using class loader of this class
     @param resouceName Name of the icon reosurce to load
     @return The icon or <code>null</code> if no image was found using the given
     resource name.
     */
    private ImageIcon getIcon(String resourceName) {
        ImageIcon icon = null;
        java.net.URL iconURL = PhotoViewController.class.getClassLoader().getResource(resourceName);
        if (iconURL != null) {
            icon = new ImageIcon(iconURL);
        }
        return icon;
    }

}