001/*
002 * Copyright 2010-2015 Institut Pasteur.
003 * 
004 * This file is part of Icy.
005 * 
006 * Icy is free software: you can redistribute it and/or modify
007 * it under the terms of the GNU General Public License as published by
008 * the Free Software Foundation, either version 3 of the License, or
009 * (at your option) any later version.
010 * 
011 * Icy is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014 * GNU General Public License for more details.
015 * 
016 * You should have received a copy of the GNU General Public License
017 * along with Icy. If not, see <http://www.gnu.org/licenses/>.
018 */
019package icy.common;
020
021import icy.common.listener.ChangeListener;
022import icy.system.thread.ThreadUtil;
023
024import java.util.ArrayList;
025import java.util.Collection;
026import java.util.LinkedHashMap;
027import java.util.List;
028
029/**
030 * Utility class to handle <code>Update</code> type event.
031 * 
032 * @author stephane
033 */
034public class UpdateEventHandler
035{
036    ChangeListener parent;
037
038    /**
039     * dispatch in AWT dispatch thread
040     */
041    private boolean awtDispatch;
042    /**
043     * internal update counter
044     */
045    private int updateCnt;
046    /**
047     * internal pending change events
048     */
049    private final LinkedHashMap<CollapsibleEvent, CollapsibleEvent> pendingChanges;
050
051    /**
052     * 
053     */
054    public UpdateEventHandler(ChangeListener parent, boolean awtDispatch)
055    {
056        super();
057
058        this.parent = parent;
059        this.awtDispatch = awtDispatch;
060
061        updateCnt = 0;
062        pendingChanges = new LinkedHashMap<CollapsibleEvent, CollapsibleEvent>();
063    }
064
065    /**
066     * 
067     */
068    public UpdateEventHandler(ChangeListener parent)
069    {
070        this(parent, false);
071    }
072
073    /**
074     * @return the awtDispatch
075     */
076    public boolean isAwtDispatch()
077    {
078        return awtDispatch;
079    }
080
081    /**
082     * @param awtDispatch
083     *        the awtDispatch to set
084     */
085    public void setAwtDispatch(boolean awtDispatch)
086    {
087        this.awtDispatch = awtDispatch;
088    }
089
090    public Collection<CollapsibleEvent> getPendingChanges()
091    {
092        return pendingChanges.values();
093    }
094
095    public void beginUpdate()
096    {
097        updateCnt++;
098    }
099
100    public void endUpdate()
101    {
102        updateCnt--;
103        if (updateCnt <= 0)
104        {
105            final List<CollapsibleEvent> events;
106
107            synchronized (pendingChanges)
108            {
109                events = new ArrayList<CollapsibleEvent>(pendingChanges.values());
110                pendingChanges.clear();
111            }
112
113            // dispatch all contained events (use copy to avoid concurrent changes)
114            for (CollapsibleEvent event : events)
115                dispatchOnChanged(event);
116        }
117    }
118
119    public boolean isUpdating()
120    {
121        return updateCnt > 0;
122    }
123
124    public boolean hasPendingChanges()
125    {
126        return !pendingChanges.isEmpty();
127    }
128
129    protected void addPendingChange(CollapsibleEvent change)
130    {
131        final CollapsibleEvent previousChange;
132
133        // TODO: can take sometime (select all on many ROI)
134        // TODO: check how fast is it now...
135        synchronized (pendingChanges)
136        {
137            // search in pending changes if we have an equivalent change
138            previousChange = pendingChanges.get(change);
139
140            // not already existing ? --> just add the new change
141            if (previousChange == null)
142                pendingChanges.put(change, change);
143        }
144
145        // found an equivalent previous change ? --> collapse the new change into the old one
146        if (previousChange != null)
147            previousChange.collapse(change);
148    }
149
150    public void changed(CollapsibleEvent event)
151    {
152        if (isUpdating())
153            addPendingChange(event);
154        else
155            dispatchOnChanged(event);
156    }
157
158    protected void dispatchOnChanged(CollapsibleEvent event)
159    {
160        final CollapsibleEvent e = event;
161
162        if (awtDispatch)
163        {
164            // dispatch on AWT Dispatch Thread now
165            ThreadUtil.invokeNow(new Runnable()
166            {
167                @Override
168                public void run()
169                {
170                    parent.onChanged(e);
171                }
172            });
173        }
174        else
175            parent.onChanged(e);
176    }
177}