Package net.sf.jiga.xtended.ui

Source Code of net.sf.jiga.xtended.ui.ImageCollection

/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package net.sf.jiga.xtended.ui;

import java.awt.*;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.*;
import java.util.List;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.ImageWriter;
import javax.imageio.event.IIOReadProgressListener;
import javax.imageio.event.IIOWriteProgressListener;
import javax.imageio.stream.ImageInputStream;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JLabel;
import net.sf.jiga.xtended.JXAException;
import net.sf.jiga.xtended.impl.Sprite;
import net.sf.jiga.xtended.impl.SpriteIO;
import net.sf.jiga.xtended.impl.game.gl.GLFX;
import net.sf.jiga.xtended.impl.system.SfMediaTracker;
import net.sf.jiga.xtended.kernel.*;

/**
* ImageCollection is the most advanced implementation of the JXA caching system
* for Swing. it's up to handle a collection of for an unlimited amount of
* pictures, digital photography, etc. Heap memory is handled at it's top-most
* level, using a SpritesCacheManager and the GarbageCollector, for a real-time
* access to the collection. Loading images is done with a single call to
* addImage() and then it can be requested the picture as a thumbnail or
* full-size, as well as wrapped in a renderable JComponent. ImageIO extra
* codecs are available with this class, as for the Sprite class. NEW : big
* sized pictures may be tiled and file-cached. To define a different tile size,
* change the static variable {@linkplain Sprite#_WRITE_TILES_DIMENSION}.
* SERIALIZING THIS CLASS : if you plan to serialize this class (e.g. store in a
* file), then you should be aware of the {@linkplain #getState() status} of the
* instance you run.
*
* @author www.b23prodtm.info
*/
public class ImageCollection implements Externalizable, Resource, ImageCollectionRenderer {

        public final static ResourceBundle rb = ResourceBundle.getBundle("net.sf.jiga.xtended.impl.ImcBrowser");
        private final static Set<ImagePluginDescriptor> fileFilters = Collections.synchronizedSet(new HashSet<ImagePluginDescriptor>());
        private static boolean pluginsLoaded = false;

        public static Set<ImagePluginDescriptor> _getPluginsMap() {
                return pluginsLoaded ? fileFilters : _loadPluginsMap();
        }

        private static Set<ImagePluginDescriptor> _loadPluginsMap() {
                Enumeration<String> e = rb.getKeys();
                for (; e.hasMoreElements();) {
                        String key = e.nextElement();
                        String plugin = key.split("_")[0];
                        Properties p = new Properties();
                        p.setProperty(ImagePluginDescriptor.get.id.toString(), plugin);
                        synchronized (fileFilters) {
                                for (ImagePluginDescriptor ipd : fileFilters) {
                                        if (ipd.p.getProperty(ImagePluginDescriptor.get.id.toString()).equals(plugin)) {
                                                p = ipd.p;
                                                break;
                                        }
                                }
                        }
                        boolean add = true;
                        if (key.matches(".*_class_[rw]")) {
                                String mode = key.substring(key.length() - 1);
                                p.setProperty(ImagePluginDescriptor.get.valueOf("class_" + mode).toString(), rb.getString(key));
                        } else if (key.matches(".*_ext")) {
                                p.setProperty(ImagePluginDescriptor.get.extension.toString(), rb.getString(key));
                        } else if (key.matches("ImageReader\\d*")) {
                                p.setProperty(ImagePluginDescriptor.get.mime.toString(), rb.getString(key));
                        } else if (key.matches(".*_rw")) {
                                p.setProperty(ImagePluginDescriptor.get.support.toString(), rb.getString(key));
                        } else if (key.matches(".*_desc")) {
                                p.setProperty(ImagePluginDescriptor.get.description.toString(), rb.getString(key));
                        } else if (key.matches(".*_type")) {
                                p.setProperty(ImagePluginDescriptor.get.buffered_type.toString(), rb.getString(key));
                        } else if (key.matches(".*_css")) {
                                p.setProperty(ImagePluginDescriptor.get.compression_type.toString(), rb.getString(key));
                        } else {
                                add = false;
                        }
                        if (add) {
                                fileFilters.add(new ImagePluginDescriptor(p));
                        }
                }
                pluginsLoaded = true;
                return fileFilters;
        }

        public static Properties _getPluginProperties(String mime) {
                if (!pluginsLoaded) {
                        _loadPluginsMap();
                }
                synchronized (fileFilters) {
                        for (ImagePluginDescriptor ipd : fileFilters) {
                                Properties p = ipd.p;
                                if (mime.equals(p.getProperty(ImagePluginDescriptor.get.mime.toString()))) {
                                        return p;
                                }
                        }
                }
                return new Properties();

        }

        /**
         * @param fileMode "r" for read "w" for write or "rw" for both
         * @return mime type. if fileMode equals or contains "r* then an attempt
         * to open a stream is made and ImageIO detects the reader that is
         * appropriate. Otherwise , the file extension is checked up.
         */
        public static String _getImageMimeType(File f, String fileMode) {
                if (!pluginsLoaded) {
                        _loadPluginsMap();
                }
                String mime = null;
                /**
                 * check file extension first
                 */
                synchronized (fileFilters) {
                        for (ImagePluginDescriptor ipd : fileFilters) {
                                Properties p = ipd.p;
                                String[] exts = p.getProperty(ImagePluginDescriptor.get.extension.toString()).split(" ");
                                if (p.getProperty(ImagePluginDescriptor.get.support.toString()) != null) {
                                        if (p.getProperty(ImagePluginDescriptor.get.support.toString()).contains(fileMode)) {
                                                for (String ext : exts) {
                                                        if (f.getName().toLowerCase().endsWith("." + ext.toLowerCase())) {
                                                                mime = p.getProperty(ImagePluginDescriptor.get.mime.toString());
                                                                break;
                                                        }
                                                }
                                        }
                                }
                                if (mime != null) {
                                        break;
                                }
                        }
                }
                if (mime == null) {
                        /**
                         * try image header
                         */
                        try {
                                if (f.exists() && fileMode.contains("r")) {
                                        ImageInputStream iis = ImageIO.createImageInputStream(f);
                                        for (Iterator<ImageReader> itr = ImageIO.getImageReaders(iis); itr.hasNext() && mime == null;) {
                                                ImageReader r = itr.next();
                                                synchronized (fileFilters) {
                                                        for (ImagePluginDescriptor ipd : fileFilters) {
                                                                Properties p = ipd.p;
                                                                if (p.getProperty(ImagePluginDescriptor.get.support.toString()) != null) {
                                                                        if (p.getProperty(ImagePluginDescriptor.get.support.toString()).contains(fileMode)) {
                                                                                for (String m : r.getOriginatingProvider().getMIMETypes()) {
                                                                                        if (m.equals(p.getProperty(ImagePluginDescriptor.get.mime.toString()))) {
                                                                                                mime = m;
                                                                                                break;
                                                                                        }
                                                                                }
                                                                        }
                                                                }
                                                        }
                                                }
                                        }
                                        iis.flush();
                                        iis.close();
                                }
                        } catch (IOException ex) {
                                if (JXAenvUtils._debug) {
                                        ex.printStackTrace();
                                }
                        }
                }
                if (mime == null) {
                        mime = "UNKNOWN_MIME";
                }
                return mime;
        }

        /**
         * prints all supported storeMime-types on the System std output
         *
         * @see ImageIO#getReaderMIMETypes()
         * @see ImageIO#getUseCache()
         */
        public static void _printAllMimeTypes() {
                String sprint = "";
                if (ImageIO.getUseCache()) {
                        sprint = "ImageIO reader/writer will use cache directory " + ImageIO.getCacheDirectory() + net.sf.jiga.xtended.kernel.Console.newLine;
                } else {
                        sprint = "Warning ! ImageIO doesn't have any cache enabled. This may result in unexpected behaviours. (ImageIO.setUseCache())" + net.sf.jiga.xtended.kernel.Console.newLine;
                }
                String[] mimes = ImageIO.getReaderMIMETypes();
                for (int i = 0; i < mimes.length; i++) {
                        for (Iterator<ImageReader> it = ImageIO.getImageReadersByMIMEType(mimes[i]); it.hasNext();) {
                                ImageReader r = it.next();
                                sprint += "ImageReader " + mimes[i] + " : " + r.toString() + net.sf.jiga.xtended.kernel.Console.newLine;
                        }
                }
                mimes = ImageIO.getWriterMIMETypes();
                for (int i = 0; i < mimes.length; i++) {
                        String ctype = _getCompressionType(mimes[i], _getBufferedType(mimes[i]));
                        for (Iterator<ImageWriter> it = ImageIO.getImageWritersByMIMEType(mimes[i]); it.hasNext();) {
                                ImageWriter w = it.next();
                                boolean tiles = w.getDefaultWriteParam().canWriteTiles();
                                sprint += "ImageWriter " + mimes[i] + " : " + w.toString() + " tiling : " + tiles + net.sf.jiga.xtended.kernel.Console.newLine;
                                if (w.getDefaultWriteParam().canWriteCompressed()) {
                                        sprint += "ImageWriter " + mimes[i] + " compression types : " + net.sf.jiga.xtended.kernel.Console.newLine;
                                        String sep = "";
                                        for (String compress : w.getDefaultWriteParam().getCompressionTypes()) {
                                                String s = compress.equals(ctype) ? "*" + compress + "*" : compress;
                                                sprint += sep + s;
                                                sep = ", ";
                                        }
                                        sprint += net.sf.jiga.xtended.kernel.Console.newLine;
                                }
                        }
                }
                System.out.println(JXAenvUtils.log(sprint, JXAenvUtils.LVL.SYS_NOT));
        }

        /**
         * will return the selected compression type in
         * jxa.ImcBrowser.properties file for that storeMime type, if
         * bufferedType differs from the type value in the properties file, then
         * null is returned for safety.
         *
         * @return the compression type or null if none is found or it is
         * incompatible with the specified bufferedType or storeMime
         */
        public static String _getCompressionType(String outputMime, int bufferedType) {
                if (!pluginsLoaded) {
                        _loadPluginsMap();
                }
                Properties plugin = _getPluginProperties(outputMime);
                String cssType = plugin.getProperty(ImagePluginDescriptor.get.compression_type.toString());
                int bType = _getBufferedType(outputMime);
                /*if (bType != bufferedType) {
                 cssType = null;
                 }*/
                if (cssType != null) {
                        for (String t : ImageIO.getImageWritersByMIMEType(outputMime).next().getDefaultWriteParam().getCompressionTypes()) {
                                if (cssType.equals(t)) {
                                        return cssType;
                                }
                        }
                }
                if (DebugMap._getInstance().isDebugLevelEnabled(Sprite.DBUG_RENDER_LOW)) {
                        System.err.println(outputMime + " with no compression type selected, image buffer is " + BUFFERED_TYPE.valueOf(bufferedType));
                }
                return null;

        }

        @Override
        public void writeExternal(ObjectOutput out) throws IOException {
                out.writeObject(_collectionCache);
                out.writeObject(_tbCollectionCache);
                out.writeBoolean(buffered);
                out.writeLong(hash);
                out.writeBoolean(multiThreading);
                out.writeInt(state);
                out.writeInt(thumbnailSize.width);
                out.writeInt(thumbnailSize.height);
                out.flush();
        }

        @Override
        public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
                _collectionCache = (SpritesCacheManager<String, Map<String, Serializable>>) in.readObject();
                _tbCollectionCache = (SpritesCacheManager<String, Map<String, Serializable>>) in.readObject();
                buffered = in.readBoolean();
                hash = in.readLong();
                multiThreading = in.readBoolean();
                state = in.readInt();
                thumbnailSize.setSize(in.readInt(), in.readInt());
                endRead(in);
        }

        /**
         * Easy-to-use BufferedImage.TYPE_'s enum. it can convert from a
         * buffered type Integer to a constant that can be read by human.
         */
        public static enum BUFFERED_TYPE {

                DEFAULT(Sprite.DEFAULT_TYPE),
                TYPE_4BYTE_ABGR(BufferedImage.TYPE_4BYTE_ABGR),
                TYPE_4BYTE_ABGR_PRE(BufferedImage.TYPE_4BYTE_ABGR_PRE),
                TYPE_INT_ARGB(BufferedImage.TYPE_INT_ARGB),
                TYPE_INT_ARGB_PRE(BufferedImage.TYPE_INT_ARGB_PRE),
                CUSTOM(BufferedImage.TYPE_CUSTOM),
                TYPE_3BYTE_BGR(BufferedImage.TYPE_3BYTE_BGR),
                TYPE_INT_BGR(BufferedImage.TYPE_INT_BGR),
                TYPE_INT_RGB(BufferedImage.TYPE_INT_RGB),
                TYPE_USHORT_555_RGB(BufferedImage.TYPE_USHORT_555_RGB),
                TYPE_USHORT_565_RGB(BufferedImage.TYPE_USHORT_565_RGB),
                TYPE_BYTE_BINARY(BufferedImage.TYPE_BYTE_BINARY),
                TYPE_BYTE_INDEXED(BufferedImage.TYPE_BYTE_INDEXED),
                TYPE_BYTE_GRAY(BufferedImage.TYPE_BYTE_GRAY),
                TYPE_USHORT_GRAY(BufferedImage.TYPE_USHORT_GRAY);
                public final int type;

                BUFFERED_TYPE(int type) {
                        this.type = type;
                }

                public ImageTypeSpecifier getFeatures() {
                        return ImageTypeSpecifier.createFromBufferedImageType(type);
                }

                public static BUFFERED_TYPE valueOf(int bufferedType) {
                        for (BUFFERED_TYPE t : values()) {
                                if (t.type == bufferedType) {
                                        return t;
                                }
                        }
                        return CUSTOM;
                }
        }

        public static int _getBufferedType(String outputMime) {
                if (!pluginsLoaded) {
                        _loadPluginsMap();
                }
                ImageWriter w = ImageIO.getImageWritersByMIMEType(outputMime).next();
                Properties plugin = _getPluginProperties(outputMime);
                String n = plugin.getProperty(ImagePluginDescriptor.get.buffered_type.toString());
                BUFFERED_TYPE type = n == null ? BUFFERED_TYPE.DEFAULT : BUFFERED_TYPE.valueOf(n);
                while (!w.getOriginatingProvider().canEncodeImage(type.getFeatures())) {
                        if (JXAenvUtils._debug) {
                                new UIMessage(true, "Wrong type for this mime/type ! " + type.name() + " <> " + outputMime, null, UIMessage.ERROR_TYPE);
                                type = (BUFFERED_TYPE) UIMessage.showSelectDialog(null, "<html>Select image buffer-type for " + outputMime + ": <br><i>(to avoid this dialog, edit jxa/ImcBrowser.properties file)</i></html>", outputMime, type.values(), type);
                        } else {
                                System.err.println(JXAenvUtils.log("Selected type won't be written correctly by the " + outputMime + " ImageWriter", JXAenvUtils.LVL.SYS_WRN));
                                break;
                        }
                }
                return type.type;
        }
        long hash = System.nanoTime();
        private boolean buffered;

        @Override
        public int hashCode() {
                return (int) hash;
        }

        @Override
        public boolean equals(Object o) {
                return o == null ? false : o.hashCode() == hashCode();
        }
        /**
         *
         */
        SpritesCacheManager<String, Map<String, Serializable>> _collectionCache;
        /**
         *
         */
        SpritesCacheManager<String, Map<String, Serializable>> _tbCollectionCache;
        /**
         *
         */
        transient SortedMap<String, Map<String, Serializable>> collection;
        /**
         *
         */
        transient SortedMap<String, Map<String, Serializable>> tbCollection;
        /**
         *
         */
        transient MediaTracker mt;
        /**
         *
         */
        transient Component obs;

        /**
         * ImageObserver
         */
        public void setObs(Component obs) {
                mt = new SfMediaTracker(obs);
                this.obs = obs;
        }

        /**
         * ImageObserver
         */
        public Component getObs() {
                return obs;
        }

        /**
         * heap capacity is set to 1 living entry. No zip, no VolatileImage.
         * Caching.
         *
         * @see #ImageCollection(Component, int, boolean, boolean)
         */
        public ImageCollection() {
                this(new JLabel(), 1, false, false);
        }

        /**
         *
         * @param obs a Component that is used to make loading
         * (MediaTracker/ImageObserver issue)
         * @param initCapacity VERY important setting. Defines how the LRU_MRU
         * list will store living entries in the heap (generally 1 -> 10,
         * depending on the stored image sizes). Caching is always enabled.
         * @param zip ZipOutStream enable (EXPERIMENTAL)
         * @param hardwareAccel VolatileImage enable (EXPERIMENTAL)
         */
        public ImageCollection(Component obs, int initCapacity, boolean zip, boolean hardwareAccel) {
                this.buffered = hardwareAccel;
                collection = Collections.synchronizedSortedMap(_collectionCache = new SpritesCacheManager<String, Map<String, Serializable>>(initCapacity));
                tbCollection = Collections.synchronizedSortedMap(_tbCollectionCache = new SpritesCacheManager<String, Map<String, Serializable>>(initCapacity));
                _collectionCache.setSwapDiskEnabled(true);
                _collectionCache.setCompressionEnabled(zip);
                _tbCollectionCache.setSwapDiskEnabled(true);
                _tbCollectionCache.setCompressionEnabled(zip);
                mt = new SfMediaTracker(this.obs = obs);
                ThreadWorks.prioritizeThreadGroups(tbimageLoaders.workTG, imageLoaders.workTG);
        }
        public static SfMediaTracker _mt = SpriteIO._mt;

        private void endRead(ObjectInput in) throws IOException, ClassNotFoundException {
                collection = Collections.synchronizedSortedMap(_collectionCache);
                tbCollection = Collections.synchronizedSortedMap(_tbCollectionCache);
                /*        thumbnailComponentCollection = Collections.synchronizedSortedMap(new TreeMap<String, JComponent>());
                 componentCollection = Collections.synchronizedSortedMap(new TreeMap<String, JComponent>());*/
                synchronized (collection) {
                        for (Iterator<String> it = collection.keySet().iterator(); it.hasNext();) {
                                pendingPictures.put(it.next(), STATE_LOADED);
                        }
                }
                synchronized (tbCollection) {
                        for (Iterator<String> it = tbCollection.keySet().iterator(); it.hasNext();) {
                                String k = it.next();
                                int s = pendingPictures.get(k);
                                pendingPictures.put(k, s | STATE_THUMB_LOADED);
                        }
                }
        }


        /*public SortedMap<String, JComponent> getThumbnailComponentCollection() {
         return thumbnailComponentCollection;
         }*/
        public void setMt(MediaTracker mt) {
                this.mt = mt;
        }

        public MediaTracker getMt() {
                return mt;
        }

        private void removeCollectionSprite(String key) {
                collection.remove(key);
                tbCollection.remove(key);
                /* thumbnailComponentCollection.remove(key);
                 componentCollection.remove(key);*/
        }
        /**
         * sprite instance stored in the collection attributes map
         */
        public final static String ATTRIBUTE_SPRITE = "sprite";
        /**
         * dimension of the sprite (thumbnail is different from full sized) in
         * the collection attributes map
         */
        public final static String ATTRIBUTE_DIMENSION = "dimension";
        /**
         * mime type of the original sprite in the collection attributes map
         */
        public final static String ATTRIBUTE_MIME = "mime";
        /**
         * buffered type (=ImageTypeSpecifier) of the original sprite in the
         * collection attributes map
         */
        public final static String ATTRIBUTE_TYPE = "type";
        /**
         * original dimensions of the sprite (same for thumnails sprites and
         * full sized sprites) in the collection attributes map
         */
        public final static String ATTRIBUTE_ORIG_DIMENSION = "orig-dimension";
        private transient ImageCollectionRenderer renderer = this;

        private JComponent makeThumbnailStore(File image, final String key, String mime, Map<String, Serializable> attrs) throws IOException {
                String m = _getImageMimeType(image, "r");
                Sprite picture = null;
                ImageReader r = null;
                try {
                        Map<String, Object> s = Sprite._loadImage(image, false, m, Sprite.MODE_JAVA2D | Sprite.MODE_TILE, _getBufferedType(m), false);
                        r = (ImageReader) s.get("reader");
                        picture = new Sprite(image, m, new Dimension(r.getWidth(0), r.getHeight(0)), true);
                } catch (Exception ex) {
                        if (JXAenvUtils._debug) {
                                ex.printStackTrace();
                        }
                        IOException e = new IOException(ex.getMessage());
                        e.initCause(ex);
                        throw e;
                } finally {
                        ((ImageInputStream) r.getInput()).flush();
                        r.dispose();
                        ((ImageInputStream) r.getInput()).close();
                        picture.setStoreMode(Sprite.MODE_JAVA2D | Sprite.MODE_TILE);
                        return makeThumbnailStore(picture, key, mime, attrs);
                }

        }

        private JComponent makeThumbnailStore(BufferedImage image, final String key, String mime, Map<String, Serializable> attrs) {
                Sprite picture = new Sprite(image, mime, new Dimension(image.getWidth(), image.getHeight()));
                picture.setStoreMode(Sprite.MODE_JAVA2D);
                return makeThumbnailStore(picture, key, mime, attrs);
        }

        /**
         *
         */
        private JComponent makeThumbnailStore(Sprite thumbnail, final String key, String mime, Map<String, Serializable> attrs) {
                Dimension origDim = thumbnail.getSize();
                Dimension thumbDim;
                double scale = Math.min(thumbnailSize.getWidth() / (double) thumbnail.getWidth(), thumbnailSize.getHeight() / (double) thumbnail.getHeight());
                AffineTransform tx = AffineTransform.getScaleInstance(scale, scale);
                thumbDim = tx.createTransformedShape(new Rectangle(thumbnail.getSize())).getBounds().getSize();
                thumbnail.setSize(thumbDim);
                thumbnail.setBufferedImageEnabled(buffered);
                thumbnail.setMt(mt, obs);
                thumbnail.setMultiThreadingEnabled(multiThreading);
                thumbnail.setBufferedType(_getBufferedType(mime));
                thumbnail.setSPM(_tbCollectionCache);
                thumbnail.setStoreMime(mime);
                updateIIOListeners(thumbnail);
                Map<String, Serializable> map = Collections.synchronizedMap(new HashMap<String, Serializable>());
                map.put(ATTRIBUTE_MIME, thumbnail.getMime());
                map.put(ATTRIBUTE_TYPE, _getBufferedType(thumbnail.getMime()));
                map.put(ATTRIBUTE_SPRITE, thumbnail);
                map.put(ATTRIBUTE_DIMENSION, thumbnail.getSize());
                map.put(ATTRIBUTE_ORIG_DIMENSION, origDim);
                /**
                 * MAY OVERRIDE above settings
                 */
                if (attrs instanceof Map) {
                        map.putAll(attrs);
                }
                tbCollection.put(key, map);
                thumbnail.clearResource();
                return renderer.makeCollectionRendererComponent(this, key, true);
        }

        private JComponent makeStore(File image, final String key, String mime, Map<String, Serializable> attrs) throws IOException {
                String m = _getImageMimeType(image, "r");
                Sprite picture = null;
                ImageReader r = null;
                try {
                        Map<String, Object> s = Sprite._loadImage(image, false, m, Sprite.MODE_JAVA2D | Sprite.MODE_TILE, _getBufferedType(m), false);
                        r = (ImageReader) s.get("reader");
                        picture = new Sprite(image, m, new Dimension(r.getWidth(0), r.getHeight(0)), true);
                } catch (Exception ex) {
                        if (JXAenvUtils._debug) {
                                ex.printStackTrace();
                        }
                        IOException e = new IOException(ex.getMessage());
                        e.initCause(ex);
                        throw e;
                } finally {
                        ((ImageInputStream) r.getInput()).flush();
                        r.dispose();
                        ((ImageInputStream) r.getInput()).close();
                        picture.setStoreMode(Sprite.MODE_JAVA2D | Sprite.MODE_TILE);
                        return makeStore(picture, key, mime, attrs);
                }

        }

        private JComponent makeStore(BufferedImage image, final String key, String mime, Map<String, Serializable> attrs) {
                Sprite picture = new Sprite(image, mime, new Dimension(image.getWidth(), image.getHeight()));
                picture.setStoreMode(Sprite.MODE_JAVA2D);
                return makeStore(picture, key, mime, attrs);
        }

        private JComponent makeStore(Sprite picture, final String key, String mime, Map<String, Serializable> attrs) {
                Dimension dim = picture.getSize();
                picture.setBufferedImageEnabled(buffered);
                picture.setMt(mt, obs);
                picture.setMultiThreadingEnabled(multiThreading);
                picture.setBufferedType(_getBufferedType(mime));
                picture.setSPM(_collectionCache);
                picture.setStoreMime(mime);
                updateIIOListeners(picture);
                Map<String, Serializable> map = Collections.synchronizedMap(new HashMap<String, Serializable>());
                map.put(ATTRIBUTE_SPRITE, picture);
                map.put(ATTRIBUTE_DIMENSION, dim);
                map.put(ATTRIBUTE_ORIG_DIMENSION, dim);
                map.put(ATTRIBUTE_MIME, picture.getMime());
                map.put(ATTRIBUTE_TYPE, _getBufferedType(picture.getMime()));
                /**
                 * MAY OVERRIDE above settings
                 */
                if (attrs instanceof Map) {
                        map.putAll(attrs);
                }
                collection.put(key, map);
                picture.clearResource();
                return renderer.makeCollectionRendererComponent(this, key, false);
        }
        /**
         * *
         * private JComponent getImageComponent(SortedMap<String, JComponent>
         * components, String key) { return components.containsKey(key) ?
         * components.get(key) : Sprite._makeSpriteJComponent(new
         * Sprite(Sprite.createBufferedImage(thumbnailSize,
         * Sprite.DEFAULT_TYPE), "image/x-png", thumbnailSize)); }
         */
        private static final long serialVersionUID = 2323;

        /**
         *
         */
        protected void updateIIOListeners(Sprite picture) {
                synchronized (iiorList) {
                        for (IIOReadProgressListener iior : iiorList) {
                                picture.getIO().addIIOReadProgressListener(iior);
                        }
                }
                synchronized (iiowList) {
                        for (IIOWriteProgressListener iiow : iiowList) {
                                picture.getIO().addIIOWriteProgressListener(iiow);
                        }
                }
        }

        protected static class FX {

                int fx;
                Point fx_loc;
                Color fx_clr;

                public FX() {
                        this(0, new Point(0, 0), null);
                }

                private FX(int fx, Point fx_loc, Color fx_clr) {
                        this.fx = fx;
                        this.fx_loc = fx_loc;
                        this.fx_clr = fx_clr;
                }
        }
        private transient FX defaultFX = new FX();

        public void setFXEnabled(boolean b, int fx, Point fx_loc, Color fx_clr) {
                if (b) {
                        defaultFX = new FX(fx, fx_loc, fx_clr);
                } else {
                        fxMap.clear();
                        defaultFX = new FX();
                }
        }
        private transient Map<String, FX> fxMap = Collections.synchronizedSortedMap(new TreeMap<String, FX>());

        protected FX getFX(String renderPic) {
                return fxMap.get(renderPic);
        }

        public void setFXEnabled(String renderPic, boolean b, int fx, Point fx_loc, Color fx_clr) {
                if (b) {
                        this.fxMap.put(renderPic, new FX(fx, fx_loc, fx_clr));
                } else {
                        fxMap.remove(renderPic);
                }
        }

        public boolean isFXEnabled(String renderPic) {
                FX fx = fxMap.containsKey(renderPic) ? fxMap.get(renderPic) : defaultFX;
                return fx.fx != GLFX.gfx.FX_NONE;
        }

        public boolean isFxEnabled() {
                return !fxMap.isEmpty() || defaultFX.fx != GLFX.gfx.FX_NONE;
        }

        /**
         * will wait until all related activity have finished and loaded
         *
         * @see #hasPicture(int, boolean)
         * @throws InterruptedException if any arror occured. {@link getErrored}
         * to retrieve and clear error states.
         */
        public synchronized void waitForPicture(String key, long millis) throws InterruptedException {
                int chk = STATE_UNLOADED | STATE_LOADING | (!isEdit(key, false) ? STATE_EDITING : 0);
                if ((getState(key) & chk) != 0) {
                        wait(millis);
                }
                if ((getState(key) & STATE_ERRORED) != 0) {
                        throw new InterruptedException("error occured for " + key);
                }
        }

        /**
         * will wait until all related activity have finished and loaded
         */
        public synchronized void waitForPicture(String key) throws InterruptedException {
                int chk = STATE_UNLOADED | STATE_LOADING | (!isEdit(key, false) ? STATE_EDITING : 0);
                while ((getState(key) & chk) != 0) {
                        wait();
                }
                if ((getState(key) & STATE_ERRORED) != 0) {
                        throw new InterruptedException("error occured for " + key);
                }
        }

        /**
         * will wait until all related activity have finished and loaded
         *
         * @see #hasPicture(int, boolean)
         * @throws InterruptedException if any arror occured. {@link getErrored}
         * to retrieve and clear error states.
         */
        public synchronized void waitForThumbnail(String key, long millis) throws InterruptedException {
                int chk = STATE_THUMB_UNLOADED | STATE_THUMB_LOADING | (!isEdit(key, true) ? STATE_THUMB_EDITING : 0);
                if ((getState(key) & chk) != 0) {
                        wait(millis);
                }
                if ((getState(key) & STATE_ERRORED) != 0) {
                        throw new InterruptedException("error occured for " + key);
                }
        }

        /**
         * will wait until all related activity have finished and loaded
         *
         * @throws InterruptedException if any arror occured. {@link getErrored}
         * to retrieve and clear error states.
         */
        public synchronized void waitForThumbnail(String key) throws InterruptedException {
                int chk = STATE_THUMB_UNLOADED | STATE_THUMB_LOADING | (!isEdit(key, true) ? STATE_THUMB_EDITING : 0);
                while ((getState(key) & chk) != 0) {
                        wait();
                }
                if ((getState(key) & STATE_ERRORED) != 0) {
                        throw new InterruptedException("error occured for " + key);
                }
        }

        /**
         * will wait until all related activity have finished and loaded
         *
         * @throws InterruptedException if any arror occured. {@link getErrored}
         * to retrieve and clear error states.
         */
        public synchronized void waitForAll() throws InterruptedException {
                int chk = STATE_UNLOADED | STATE_LOADING | (!lock.isEdit() ? STATE_EDITING : 0);
                if ((getState() & chk) != 0) {
                        wait();
                }
                if ((getState() & STATE_ERRORED) != 0) {
                        throw new InterruptedException("error(s) occured");
                }
        }

        /**
         * will wait until all related activity have finished and loaded
         *
         * @throws InterruptedException if any arror occured. {@link getErrored}
         * to retrieve and clear error states.
         */
        public synchronized void waitForAllThumbnails() throws InterruptedException {
                int chk = STATE_THUMB_UNLOADED | STATE_THUMB_LOADING | (!tbLock.isEdit() ? STATE_THUMB_EDITING : 0);
                if ((getState() & chk) != 0) {
                        wait();
                }
                if ((getState() & STATE_ERRORED) != 0) {
                        throw new InterruptedException("error(s) occured");
                }
        }
        private transient Map<String, Integer> pendingPictures = Collections.synchronizedSortedMap(new TreeMap<String, Integer>());
        protected transient ThreadWorks imageLoaders = new ThreadWorks(ImageCollection.class.getSimpleName() + " " + hashCode());
        protected transient ThreadWorks tbimageLoaders = new ThreadWorks(ImageCollection.class.getSimpleName() + " tb " + hashCode());

        /**
         *
         */
        public void addImage(final File f, final String key) {
                String m = _getImageMimeType(f, "r");
                addImage(f, key, m);
        }

        /**
         * @param storeMime the storeMime type that will be used to store the
         * picture
         */
        public void addImage(final File f, final String key, final String storeMime) {
                addImage(f, key, storeMime, null);
        }

        public synchronized void addImage(final File f, final String key, final String storeMime, final Map<String, Serializable> attrs) {
                pendingPictures.put(key, STATE_UNLOADED | STATE_THUMB_LOADING);
                tbimageLoaders.doLater(new Runnable() {

                        @Override
                        public void run() {
                                synchronized (ImageCollection.this) {
                                        try {
                                                makeThumbnailStore(f, key, storeMime, attrs);
                                                pendingPictures.put(key, STATE_LOADING | STATE_THUMB_LOADED);
                                                imageLoaders.doLater(new Runnable() {

                                                        @Override
                                                        public void run() {
                                                                try {
                                                                        makeStore(f, key, storeMime, attrs);

                                                                        pendingPictures.put(key, STATE_LOADED | STATE_THUMB_LOADED);
                                                                } catch (Throwable e) {
                                                                        e.printStackTrace();
                                                                        pendingPictures.put(key, STATE_ERRORED);
                                                                } finally {
                                                                        synchronized (ImageCollection.this) {
                                                                                ImageCollection.this.notifyAll();
                                                                        }
                                                                }
                                                        }
                                                });
                                        } catch (Throwable e) {
                                                e.printStackTrace();
                                                pendingPictures.put(key, STATE_ERRORED);
                                        } finally {
                                                ImageCollection.this.notifyAll();
                                        }
                                }
                        }
                });
                notifyAll();
        }

        /**
         * CAUTION : This method is memory sensitive over the lifetime of this
         * ImageCollection. Use it with small images only !
         */
        public void addImage(final BufferedImage image, final String key) {
                addImage(image, key, "image/x-png");
        }

        /**
         * CAUTION : This method is memory sensitive over the lifetime of this
         * ImageCollection. Use it with small images only !
         *
         * @param storeMime the storeMime type that will be used to store the
         * picture
         */
        public void addImage(final BufferedImage image, final String key, final String storeMime) {
                addImage(image, key, storeMime, null);
        }

        public synchronized void addImage(final BufferedImage image, final String key, final String storeMime, final Map<String, Serializable> attrs) {
                pendingPictures.put(key, STATE_UNLOADED | STATE_THUMB_LOADING);
                imageLoaders.doLater(new Runnable() {

                        @Override
                        public void run() {
                                synchronized (ImageCollection.this) {
                                        makeThumbnailStore(image, key, storeMime, attrs);
                                        pendingPictures.put(key, STATE_LOADING | STATE_THUMB_LOADED);
                                        makeStore(image, key, storeMime, attrs);
                                        pendingPictures.put(key, STATE_LOADED | STATE_THUMB_LOADED);
                                        ImageCollection.this.notifyAll();
                                }
                        }
                });
                ImageCollection.this.notifyAll();
        }

        /**
         *
         */
        public void removeImage(final String key) {
                try {
                        imageLoaders.doAndWait(new Runnable() {

                                @Override
                                public void run() {
                                        synchronized (ImageCollection.this) {
                                                removeCollectionSprite(key);
                                                pendingPictures.remove(key);
                                                ImageCollection.this.notifyAll();
                                        }
                                }
                        });
                } catch (InterruptedException e) {
                        e.printStackTrace();
                }
        }
        /**
         *
         */
        private Dimension thumbnailSize = new Dimension(64, 64);

        /**
         *
         */
        public void setThumbnailSize(Dimension size) {
                thumbnailSize = size;
        }

        /**
         *
         */
        public Dimension getThumbnailSize() {
                return thumbnailSize;
        }

        /**
         *
         */
        public JComponent getThumbnailComponent(String key) {
                JComponent thumbnail = renderer.makeCollectionRendererComponent(this, key, true);
                thumbnail.setPreferredSize(thumbnailSize);
                return thumbnail;
        }

        /**
         *
         */
        public JComponent getRenderComponent(String key) {
                JComponent render = renderer.makeCollectionRendererComponent(this, key, false);/*getImageComponent(componentCollection, key);*/

                return render;
        }

        /*  public SortedMap<String, JComponent> getRenderComponentCollection() {
         return componentCollection;
         }
         */
        /**
         * @throws JXAException This method must be called between an
         * {@linkplain #beginEdit(boolean)} and {@linkplain #endEdit(boolean)}
         * call.
         */
        public BufferedImage getPicture(Component obs, String key) {
                if (!isEdit(key, false)) {
                        throw new JXAException("no edit-paint is current or another edit-paint is current on another Thread " + (lock.threadEdit));
                }
                try {
                        waitForPicture(key);
                        Sprite sprite;
                        if (collection.containsKey(key)) {
                                sprite = (Sprite) collection.get(key).get(ATTRIBUTE_SPRITE);
                                sprite.setSize((Dimension) collection.get(key).get(ATTRIBUTE_DIMENSION));
                        } else {
                                sprite = new Sprite();
                        }
                        return sprite.toBuffered();
                } catch (InterruptedException ex) {
                        ex.printStackTrace();
                        return null;
                }
        }

        /**
         * @throws JXAException This method must be called between an
         * {@linkplain #beginEdit(boolean)} and {@linkplain #endEdit(boolean)}
         * call.
         */
        public void setPictureStoreAttribute(String key, String attributeKey, Serializable attributeValue) {
                Map<String, Serializable> attrs = getPictureStore(key);
                attrs.put(attributeKey, attributeValue);
                collection.put(key, attrs);
        }

        /**
         * * @throws JXAException This method must be called between an
         * {@linkplain #beginEdit(boolean)} and {@linkplain #endEdit(boolean)}
         * call.
         */
        public void setThumbnailPictureStoreAttribute(String key, String attributeKey, Serializable attributeValue) {
                Map<String, Serializable> attrs = getThumbnailPictureStore(key);
                attrs.put(attributeKey, attributeValue);
                tbCollection.put(key, attrs);
        }

        /**
         * @return see ATTRIBUTES_* for the returned mapping
         * @throws JXAException This method must be called between an
         * {@linkplain #beginEdit(boolean)} and {@linkplain #endEdit(boolean)}
         * call.
         */
        public Map<String, Serializable> getPictureStoreAttributes(String key, String... attributes) {
                Map<String, Serializable> subStore = new HashMap<String, Serializable>(attributes.length), store = getPictureStore(key);
                for (String att : attributes) {
                        subStore.put(att, store.get(att));
                }
                return subStore;
        }

        /**
         * @return see ATTRIBUTES_* for the returned mapping
         * @throws JXAException This method must be called between an
         * {@linkplain #beginEdit(boolean)} and {@linkplain #endEdit(boolean)}
         * call.
         */
        public Map<String, Serializable> getPictureStore(String key) {
                if (!isEdit(key, false)) {
                        throw new JXAException("no edit-paint is current or another edit-paint is current on another Thread " + (lock.threadEdit));
                }
                return collection.get(key);
        }

        /**
         * @return see ATTRIBUTES_* for the returned mapping
         * @throws JXAException This method must be called between an
         * {@linkplain #beginEdit(boolean)} and {@linkplain #endEdit(boolean)}
         * call.
         */
        public Map<String, Serializable> getThumbnailPictureStoreAttributes(String key, String... attributes) {
                Map<String, Serializable> subStore = new HashMap<String, Serializable>(attributes.length), store = getThumbnailPictureStore(key);
                for (String att : attributes) {
                        subStore.put(att, store.get(att));
                }
                return subStore;
        }

        /**
         * @return see ATTRIBUTES_* for the returned mapping
         * @throws JXAException This method must be called between an
         * {@linkplain #beginEdit(boolean)} and {@linkplain #endEdit(boolean)}
         * call.
         */
        public Map<String, Serializable> getThumbnailPictureStore(String key) {
                if (!isEdit(key, true)) {
                        throw new JXAException("no edit-paint is current or another edit-paint is current on another Thread " + (tbLock.threadEdit));
                }
                return tbCollection.get(key);
        }

        /**
         *
         */
        public boolean hasPicture(String key, boolean thumbnail) {
                return thumbnail ? (getState(key) & STATE_THUMB_LOADED) != 0 : (getState(key) & STATE_LOADED) != 0;
        }

        /**
         * to make iteration over this sortedSet, it must be synchronized on
         * this ImageCollection instance.
         *
         * @param thumbnail may be faster to load with thumbnail set to true
         * @return a common keySet for the collection cache
         */
        public SortedSet<String> keySet(boolean thumbnail) {
                return thumbnail ? (SortedSet<String>) _tbCollectionCache.keySet() : (SortedSet<String>) _collectionCache.keySet();
        }

        /**
         * @return The cache instance backing the imagecollection. See ATTRIBUTES_* for the returned mapping <String, Serializable>
         */
        public SpritesCacheManager<String, Map<String, Serializable>> accessCache(boolean thumbnail) {
                return thumbnail ? _tbCollectionCache : _collectionCache;
        }

        /**
         *
         */
        public void addCacheListener(SpritesCacheListener scl) {
                _collectionCache.addSpritesCacheListener(scl);
                _tbCollectionCache.addSpritesCacheListener(scl);
        }

        /**
         *
         */
        public void removeCacheListener(SpritesCacheListener scl) {
                _collectionCache.removeSpritesCacheListener(scl);
                _tbCollectionCache.removeSpritesCacheListener(scl);
        }
        /**
         *
         */
        private transient List<IIOReadProgressListener> iiorList = Collections.synchronizedList(new Vector<IIOReadProgressListener>());
        /**
         *
         */
        private transient List<IIOWriteProgressListener> iiowList = Collections.synchronizedList(new Vector<IIOWriteProgressListener>());

        /**
         *
         */
        public void addIIORListener(IIOReadProgressListener iior) {
                if (!iiorList.contains(iior)) {
                        iiorList.add(iior);
                }
        }

        /**
         *
         */
        public void removeIIORListener(IIOReadProgressListener iior) {
                iiorList.remove(iior);
        }

        /**
         *
         */
        public void addIIOWListener(IIOWriteProgressListener iiow) {
                if (!iiowList.contains(iiow)) {
                        iiowList.add(iiow);
                }
        }

        /**
         *
         */
        public void removeIIOWListener(IIOWriteProgressListener iiow) {
                iiowList.remove(iiow);
        }
        private static BitStack bits = new BitStack();
        /**
         * some picture is loading
         */
        public final static int STATE_LOADING = bits._newBitRange();
        /**
         * no picture is loading
         */
        public final static int STATE_UNLOADED = bits._newBitRange();
        /**
         * some picture is errored
         */
        public final static int STATE_ERRORED = bits._newBitRange();
        /**
         * some picture is errored
         */
        public final static int STATE_EDITING = bits._newBitRange();
        /**
         * no picture-thumbnail is loading
         */
        public final static int STATE_THUMB_UNLOADED = bits._newBitRange();
        /**
         * some picture-thumbnail is loading
         */
        public final static int STATE_THUMB_LOADING = bits._newBitRange();
        /**
         * all picture-thumbnails are loaded
         */
        public final static int STATE_THUMB_LOADED = bits._newBitRange();
        /**
         * some picture is errored
         */
        public final static int STATE_THUMB_EDITING = bits._newBitRange();
        /**
         * all pictures are loaded
         */
        public final static int STATE_LOADED = bits._newBitRange();
        /**
         * @deprecated use {@linkplain  #getState()}
         */
        private int state = 0;

        /**
         * @see #STATE_LOADING
         * @see #STATE_UNLOADED
         * @see #STATE_ERRORED
         * @see #STATE_LOADED
         * @see #STATE_THUMB_UNLOADED
         * @see #STATE_THUMB_LOADED
         * @see #STATE_THUMB_LOADING
         * @see #STATE_THUMB_ERRORED
         */
        public int getState() {
                int state = 0;
                synchronized (pendingPictures) {
                        for (String k : pendingPictures.keySet()) {
                                state |= pendingPictures.get(k);
                        }
                }
                this.state = state;
                return state;
        }

        /**
         * @return current images that could not be loaded
         **/
        public String[] getErrored(boolean clearStates) {
                if ((getState() & STATE_ERRORED) != 0) {
                        Vector<String> errored = new Vector<String>();
                        synchronized (pendingPictures) {
                                for (String key : pendingPictures.keySet()) {
                                        if ((getState(key) & STATE_ERRORED) != 0) {
                                                errored.add(key);
                                        }
                                }
                        }
                        if (clearStates) {
                                pendingPictures.clear();
                        }
                        return errored.toArray(new String[]{});
                }
                return new String[]{};
        }

        public int getState(String key) {
                int s = pendingPictures.containsKey(key) ? pendingPictures.get(key) : 0;
                if (s == 0) {
                        if (tbCollection.containsKey(key)) {
                                s |= STATE_THUMB_LOADED;
                        } else {
                                s |= STATE_THUMB_UNLOADED;
                        }
                        if (collection.containsKey(key)) {
                                s |= STATE_LOADED;
                        } else {
                                s |= STATE_UNLOADED;
                        }
                }
                return s;
        }

        /**
         * waits for all thumbnails to finish loading
         */
        @Override
        public Object loadResource() {
                try {
                        waitForAllThumbnails();
                } catch (InterruptedException ex) {
                        ex.printStackTrace();
                } finally {
                        return null;
                }
        }

        public int size() {
                return keySet(true).size();
        }

        /**
         * cancels all loading pictures and clears resources used by the
         * collection
         */
        @Override
        public Object clearResource() {
                try {
                        waitForAllThumbnails();
                } catch (InterruptedException ex) {
                        if (JXAenvUtils._debug) {
                                ex.printStackTrace();
                        }
                } finally {
                        if (isResourceLoaded()) {
                                imageLoaders.cancelAllSchedules();
                                collection.clear();
                                tbCollection.clear();
                                pendingPictures.clear();
                        }
                        return null;
                }
        }

        @Override
        public boolean isResourceLoaded() {
                int state = getState();
                return (state & (STATE_THUMB_UNLOADED | STATE_THUMB_LOADING)) == 0 && state != 0;

        }

        boolean multiThreading = JXAenvUtils._multiThreading;

        /**
         *
         * @return if {@linkplain JXAenvUtils#isMultiThreadingEnabled()}
         */
        public boolean isMultiThreadingEnabled() {
                return JXAenvUtils._multiThreading;
        }

        /**
         * {@linkplain JXAenvUtils#setMultiThreadingEnabled(boolean)}. There are some threads (asynchronous) that will still be running when multi-threading is disabled. Only the low-level activity is involved by this switch.
         * @default enabled if a multi-threading CPU is detected.
         */
        public void setMultiThreadingEnabled(boolean b) {
                multiThreading = JXAenvUtils._multiThreading = b;
        }

        @Override
        public JComponent makeCollectionRendererComponent(final ImageCollection collection, final String key, final boolean thumbnail) {
                JComponent j = new JComponent() {

                        final long hash = System.nanoTime();

                        @Override
                        public final int hashCode() {
                                return (int) hash;
                        }

                        @Override
                        public final boolean equals(Object o) {
                                return o == null ? false : o.hashCode() == hashCode();
                        }

                        @Override
                        protected void paintComponent(Graphics g) {
                                super.paintComponent(g);
                                beginEdit(key, thumbnail);
                                Sprite picture = collection.getCollectionSprite(key, thumbnail);
                                boolean tiled = picture.isTileModeEnabled();
                                if (!thumbnail) {
                                        picture.setTileModeEnabled(picture.isTilesAvailable());
                                }
                                boolean bufferedOrig = picture.isBufferedImageEnabled();
                                picture.setBufferedImageEnabled(buffered);
                                Rectangle bounds = getBounds();
                                Rectangle origPicBds = picture.getBounds(), picBds = (Rectangle) origPicBds.clone();
                                /**
                                 * scale and preserve proportions
                                 */
                                double scale = Math.min((float) bounds.getWidth() / (float) picBds.getWidth(), (float) bounds.getHeight() / (float) picBds.getHeight());
                                picBds = AffineTransform.getScaleInstance(scale, scale).createTransformedShape(picBds).getBounds();
                                picBds.setLocation((int) Math.round(bounds.getCenterX() - picBds.getWidth() / 2.0), (int) Math.round(bounds.getCenterY() - picBds.getHeight() / 2.0));
                                FX fx = fxMap.containsKey(key) ? fxMap.get(key) : defaultFX;
                                if (isFXEnabled(key)) {
                                        picBds.grow(-Math.abs(fx.fx_loc.x), -Math.abs(fx.fx_loc.y));
                                }
                                picture.setBounds(picBds);
                                picture.runValidate();
                                g.translate(-bounds.x, -bounds.y);
                                picture.draw(this, (Graphics2D) g, fx.fx, fx.fx_loc, fx.fx_clr);
                                g.translate(bounds.x, bounds.y);
                                picture.setBounds(origPicBds);
                                picture.setTileModeEnabled(tiled);
                                picture.setBufferedImageEnabled(bufferedOrig);
                                endEdit(key, thumbnail);
                        }

                        @Override
                        public void addNotify() {
                                super.addNotify();
                                /* nothing here (just to add breakpoints) */
                        }

                        @Override
                        public void removeNotify() {
                                super.removeNotify();
                                /* nothing here (just to add breakpoints) */
                        }

                };

                j.addComponentListener(new ComponentAdapter() {

                        @Override
                        public void componentResized(ComponentEvent e) {
                                /* to avoid crappy affect if the component is stretched out*/
                                super.componentResized(e);
                                beginEdit(key, thumbnail);
                                collection.getCollectionSprite(key, thumbnail).clearResource();
                                endEdit(key, thumbnail);

                        }
                });
                if (thumbnail) {
                        j.setPreferredSize(thumbnailSize);
                }
                return j;
        }

        public void setRenderer(ImageCollectionRenderer renderer) {
                this.renderer = renderer;
        }

        public ImageCollectionRenderer getRenderer() {
                return renderer;
        }

        /**
         * collection sprite, cached. if it can be tiled, then the tilemode is
         * enabled
         *
         * @throws JXAException This method must be called between an
         * {@linkplain #beginEdit(boolean)} and {@linkplain #endEdit(boolean)}
         * call.
         */
        public Sprite getCollectionSprite(String key, boolean thumbnail) {
                if (!isEdit(key, thumbnail)) {
                        throw new JXAException("no edit-paint is current or another edit-paint is current on another Thread " + (thumbnail ? tbLock.threadEdit : lock.threadEdit));
                }
                Sprite picture;
                if (hasPicture(key, thumbnail)) {
                        picture = thumbnail ? (Sprite) tbCollection.get(key).get(ATTRIBUTE_SPRITE) : (Sprite) collection.get(key).get(ATTRIBUTE_SPRITE);
                } else {
                        picture = new Sprite();
                        picture.setSize(thumbnailSize);
                        picture.runValidate();
                        Image im = picture.getImage(obs);
                        Graphics2D g = Sprite.wrapRendering(Sprite._createImageGraphics(im));
                        Icon ic = UIMessage._getIcon(UIMessage.LOAD_TYPE, false);
                        ic.paintIcon(obs, g, (int) ((float) (picture.getWidth() - ic.getIconWidth()) / 2f), (int) ((float) (picture.getWidth() - ic.getIconWidth()) / 2f));
                        g.dispose();
                }
                picture.setMultiThreadingEnabled(multiThreading);
                updateIIOListeners(picture);
                picture.setMt(mt, obs);
                picture.setSPM(thumbnail ? _tbCollectionCache : _collectionCache);
                return picture;
        }

        private static class EditLock {

                final Monitor editMonitor = new Monitor();
                long threadEdit = 0;
                int threadEditLvl = 0;

                public void beginEdit() {
                        try {
                                synchronized (editMonitor) {
                                        while (threadEdit != 0 && threadEdit != Thread.currentThread().getId()) {
                                                editMonitor.wait();
                                        }
                                        threadEdit = Thread.currentThread().getId();
                                        threadEditLvl++;
                                }
                        } catch (InterruptedException ex) {
                                throw new JXAException(ex);
                        }
                }

                public boolean isEdit() {
                        return threadEdit == Thread.currentThread().getId();
                }

                public void clearEdit() {
                        synchronized (editMonitor) {
                                threadEdit = 0;
                                editMonitor.notifyAll();
                        }
                }

                public void endEdit() {
                        synchronized (editMonitor) {
                                if (threadEdit == 0) {
                                        if (JXAenvUtils._debug) {
                                                System.err.println(JXAenvUtils.log("no edit-paint is current", JXAenvUtils.LVL.APP_WRN));
                                        }
                                } else {
                                        if (threadEdit != Thread.currentThread().getId()) {
                                                throw new JXAException("another edit-paint is current on another Thread " + threadEdit);
                                        }
                                        threadEditLvl = Math.max(0, threadEditLvl - 1);
                                        threadEdit = threadEditLvl != 0 ? threadEdit : 0;
                                }
                                editMonitor.notifyAll();
                        }
                }
        }
        transient EditLock tbLock = new EditLock();
        transient EditLock lock = new EditLock();

        /**
         * use it to paint or edit, it will wait until no other thread is
         * editing on this imageCollection (another beginEdit was called). Then
         * it locks either
         * {@link #getThumbnailPictureStore(java.lang.String) thumbnails} or the
         * {@link #getCollectionSprite(java.lang.String, boolean) full picture collection}.
         * <br>
         * Many calls to this method on the same thread means as many
         * {@linkplain #endEdit()} to be called upon the next task
         * completion.<br>
         *
         * @param thumbnail if thumbnail is requested, the sync lock will not
         * affect the full sized sprite
         */
        public synchronized void beginEdit(String key, boolean thumbnail) {
                if (thumbnail) {
                        tbLock.beginEdit();
                        pendingPictures.put(key, getState(key) | STATE_THUMB_EDITING);
                } else {
                        lock.beginEdit();
                        pendingPictures.put(key, getState(key) | STATE_EDITING);
                }
                notifyAll();
        }

        /**
         * current thread is editing
         */
        public boolean isEdit(String key, boolean thumbnail) {
                boolean b = thumbnail ? tbLock.isEdit() : lock.isEdit();
                return (getState(key) & (thumbnail ? STATE_THUMB_EDITING : STATE_EDITING)) != 0 && b;
        }

        /**
         * clears edit thread state (in case of an interruption occured between
         * {@linkplain #beginEdit()} and {@linkplain #endEdit()} calls)
         */
        public synchronized void clearEdit(boolean thumbnail) {
                if (thumbnail) {
                        tbLock.clearEdit();
                } else {
                        lock.clearEdit();
                }
                synchronized (pendingPictures) {
                        for (Iterator<Map.Entry<String, Integer>> it = pendingPictures.entrySet().iterator(); it.hasNext();) {
                                Map.Entry<String, Integer> me = it.next();
                                me.setValue(me.getValue() & ~(STATE_THUMB_EDITING | STATE_EDITING));
                        }
                }
                notifyAll();
        }

        /**
         * use it to paint or edit
         *
         * @throws JXAException if another Thread has started the editing
         */
        public synchronized void endEdit(String key, boolean thumbnail) {
                if (thumbnail) {
                        tbLock.endEdit();
                        pendingPictures.put(key, getState(key) & ~STATE_THUMB_EDITING);
                } else {
                        lock.endEdit();
                        pendingPictures.put(key, getState(key) & ~STATE_EDITING);
                }
                notifyAll();
        }
}
TOP

Related Classes of net.sf.jiga.xtended.ui.ImageCollection

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.