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.gui.component.sequence;
020
021import icy.common.listener.AcceptListener;
022import icy.gui.main.GlobalSequenceListener;
023import icy.gui.util.ComponentUtil;
024import icy.main.Icy;
025import icy.sequence.Sequence;
026import icy.util.StringUtil;
027
028import java.awt.Component;
029import java.awt.event.ActionEvent;
030import java.lang.ref.WeakReference;
031import java.util.ArrayList;
032import java.util.List;
033
034import javax.swing.DefaultComboBoxModel;
035import javax.swing.JComboBox;
036import javax.swing.JLabel;
037import javax.swing.JList;
038import javax.swing.ListCellRenderer;
039
040/**
041 * The sequence chooser is a component derived from JComboBox. <br>
042 * The combo auto refresh its content regarding to the sequence opened in ICY.<br>
043 * You can get it with getSequenceSelected()
044 * 
045 * @author Fabrice de Chaumont & Stephane<br>
046 */
047
048public class SequenceChooser extends JComboBox implements GlobalSequenceListener
049{
050    public interface SequenceChooserListener
051    {
052        /**
053         * Called when the sequence chooser selection changed for specified sequence.
054         */
055        public void sequenceChanged(Sequence sequence);
056    }
057
058    private class SequenceComboModel extends DefaultComboBoxModel
059    {
060        /**
061         * 
062         */
063        private static final long serialVersionUID = -1402261337279171323L;
064
065        /**
066         * cached items list
067         */
068        final List<WeakReference<Sequence>> cachedList;
069
070        public SequenceComboModel()
071        {
072            super();
073
074            cachedList = new ArrayList<WeakReference<Sequence>>();
075            updateList();
076        }
077
078        public void updateList()
079        {
080            // save selected item
081            final Object selected = getSelectedItem();
082
083            final int oldSize = cachedList.size();
084
085            cachedList.clear();
086
087            // add null entry at first position
088            if (nullEntryName != null)
089                cachedList.add(new WeakReference<Sequence>(null));
090
091            final List<Sequence> sequences = Icy.getMainInterface().getSequences();
092
093            // add active sequence entry at second position
094            if ((sequences.size() > 0) && (activeSequence != null))
095                cachedList.add(new WeakReference<Sequence>(activeSequence));
096
097            // add others sequence
098            for (Sequence seq : sequences)
099                if ((filter == null) || filter.accept(seq))
100                    cachedList.add(new WeakReference<Sequence>(seq));
101
102            final int newSize = cachedList.size();
103
104            // some elements has been removed
105            if (newSize < oldSize)
106                fireIntervalRemoved(this, newSize, oldSize - 1);
107            // some elements has been added
108            else if (newSize > oldSize)
109                fireIntervalAdded(this, oldSize, newSize - 1);
110
111            // and some elements changed
112            fireContentsChanged(this, 0, newSize - 1);
113
114            // restore selected item
115            setSelectedItem(selected);
116        }
117
118        @Override
119        public Object getElementAt(int index)
120        {
121            return cachedList.get(index).get();
122        }
123
124        @Override
125        public int getSize()
126        {
127            return cachedList.size();
128        }
129    }
130
131    private static final long serialVersionUID = -6108163762809540675L;
132
133    public static final String SEQUENCE_SELECT_CMD = "sequence_select";
134
135    /**
136     * var
137     */
138    AcceptListener filter;
139    final String nullEntryName;
140
141    /**
142     * listeners
143     */
144    protected final List<SequenceChooserListener> listeners;
145
146    /**
147     * internals
148     */
149    protected WeakReference<Sequence> previousSelectedSequence;
150    protected final SequenceComboModel model;
151    protected final Sequence activeSequence;
152
153    /**
154     * Create a new Sequence chooser component (JComboBox for sequence selection).
155     * 
156     * @param activeSequenceEntry
157     *        If true the combobox will display an <i>Active Sequence</i> entry so when we select it
158     *        the {@link #getSelectedSequence()} method returns the current active sequence.
159     * @param nullEntryName
160     *        If this parameter is not <code>null</code> the combobox will display an extra entry
161     *        with the given string to define <code>null</code> sequence selection so when this
162     *        entry will be selected the {@link #getSelectedSequence()} will return <code>null</code>.
163     * @param nameMaxLength
164     *        Maximum authorized length for the sequence name display in the combobox (extra
165     *        characters are truncated).<br>
166     *        That prevent the combobox to be resized to very large width.
167     */
168    public SequenceChooser(final boolean activeSequenceEntry, final String nullEntryName, final int nameMaxLength)
169    {
170        super();
171
172        this.nullEntryName = nullEntryName;
173
174        if (activeSequenceEntry)
175            activeSequence = new Sequence("active sequence");
176        else
177            activeSequence = null;
178
179        model = new SequenceComboModel();
180        setModel(model);
181        setRenderer(new ListCellRenderer()
182        {
183            @Override
184            public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected,
185                    boolean cellHasFocus)
186            {
187                final JLabel result = new JLabel();
188
189                if (value instanceof Sequence)
190                {
191                    final String name = ((Sequence) value).getName();
192
193                    result.setText(StringUtil.limit(name, nameMaxLength));
194                    result.setToolTipText(name);
195                }
196                else if (value == null)
197                    result.setText(nullEntryName);
198
199                return result;
200            }
201        });
202
203        addActionListener(this);
204
205        // default
206        listeners = new ArrayList<SequenceChooserListener>();
207        setActionCommand(SEQUENCE_SELECT_CMD);
208        previousSelectedSequence = new WeakReference<Sequence>(null);
209        setSelectedItem(null);
210
211        // fix height
212        ComponentUtil.setFixedHeight(this, 26);
213    }
214
215    /**
216     * @deprecated Use {@link #SequenceChooser(boolean, String, int)} instead.
217     */
218    @SuppressWarnings("unused")
219    @Deprecated
220    public SequenceChooser(final int sequenceNameMaxLength, final boolean nullEntry, final boolean autoSelectIfNull,
221            final String nullEntryName)
222    {
223        this(false, nullEntry ? nullEntryName : null, sequenceNameMaxLength);
224    }
225
226    /**
227     * @deprecated Use {@link #SequenceChooser(boolean, String, int)} instead.
228     */
229    @SuppressWarnings("unused")
230    @Deprecated
231    public SequenceChooser(int maxLength, boolean nullEntry, boolean autoSelectIfNull)
232    {
233        this(false, nullEntry ? "no sequence" : null, maxLength);
234    }
235
236    /**
237     * @deprecated Use {@link #SequenceChooser(boolean, String, int)} instead.
238     */
239    @Deprecated
240    public SequenceChooser(int maxLength, boolean nullEntry)
241    {
242        this(false, nullEntry ? "no sequence" : null, maxLength);
243    }
244
245    /**
246     * Create a new Sequence chooser component (JComboBox for sequence selection).
247     * 
248     * @param activeSequenceEntry
249     *        If true the combobox will display an <i>Active Sequence</i> entry so when we select it
250     *        the {@link #getSelectedSequence()} method returns the current active sequence.
251     * @param nullEntryName
252     *        If this parameter is not <code>null</code> the combobox will display an extra entry
253     *        with the given string to define <code>null</code> sequence selection so when this
254     *        entry will be selected the {@link #getSelectedSequence()} will return <code>null</code>.
255     */
256    public SequenceChooser(boolean activeSequenceEntry, String nullEntryName)
257    {
258        this(activeSequenceEntry, nullEntryName, 64);
259    }
260
261    /**
262     * @deprecated Use {@link #SequenceChooser(boolean, String, int)} instead.
263     */
264    @Deprecated
265    public SequenceChooser(int nameMaxLength)
266    {
267        this(false, "no sequence", nameMaxLength);
268    }
269
270    /**
271     * Create a new Sequence chooser component (JComboBox for sequence selection).
272     */
273    public SequenceChooser()
274    {
275        this(true, null, 64);
276    }
277
278    @Override
279    public void addNotify()
280    {
281        super.addNotify();
282
283        Icy.getMainInterface().addGlobalSequenceListener(this);
284    }
285
286    @Override
287    public void removeNotify()
288    {
289        Icy.getMainInterface().removeGlobalSequenceListener(this);
290
291        super.removeNotify();
292    }
293
294    /**
295     * @return the filter
296     */
297    public AcceptListener getFilter()
298    {
299        return filter;
300    }
301
302    /**
303     * Set a filter for sequence display.<br>
304     * Only Sequence accepted by the filter will appear in the combobox.
305     */
306    public void setFilter(AcceptListener filter)
307    {
308        if (this.filter != filter)
309        {
310            this.filter = filter;
311            model.updateList();
312        }
313    }
314
315    /**
316     * @return current selected sequence.
317     */
318    public Sequence getSelectedSequence()
319    {
320        final Sequence result = (Sequence) getSelectedItem();
321
322        // special case for active sequence
323        if (result == activeSequence)
324            return Icy.getMainInterface().getActiveSequence();
325
326        return result;
327    }
328
329    /**
330     * Select the <i>Active sequence</i> entry if enable.
331     */
332    public void setActiveSequenceSelected()
333    {
334        if (activeSequence != null)
335            setSelectedItem(activeSequence);
336    }
337
338    /**
339     * @param sequence
340     *        The sequence to select in the combo box
341     */
342    public void setSelectedSequence(Sequence sequence)
343    {
344        if (sequence != getSelectedSequence())
345            setSelectedItem(sequence);
346    }
347
348    /**
349     * @deprecated
350     *             use {@link #setSelectedSequence(Sequence)} instead
351     */
352    @Deprecated
353    public void setSequenceSelected(Sequence sequence)
354    {
355        setSelectedSequence(sequence);
356    }
357
358    // called when sequence selection has changed
359    private void sequenceChanged(Sequence sequence)
360    {
361        fireSequenceChanged(sequence);
362    }
363
364    private void fireSequenceChanged(Sequence sequence)
365    {
366        for (SequenceChooserListener listener : getListeners())
367            listener.sequenceChanged(sequence);
368    }
369
370    public ArrayList<SequenceChooserListener> getListeners()
371    {
372        return new ArrayList<SequenceChooserListener>(listeners);
373    }
374
375    public void addListener(SequenceChooserListener listener)
376    {
377        if (!listeners.contains(listener))
378            listeners.add(listener);
379    }
380
381    public void removeListener(SequenceChooserListener listener)
382    {
383        listeners.remove(listener);
384    }
385
386    @Override
387    public void actionPerformed(ActionEvent e)
388    {
389        final Sequence selected = getSelectedSequence();
390
391        if (previousSelectedSequence.get() != selected)
392        {
393            previousSelectedSequence = new WeakReference<Sequence>(selected);
394            // sequence changed
395            sequenceChanged(selected);
396        }
397    }
398
399    @Override
400    public void sequenceOpened(Sequence sequence)
401    {
402        model.updateList();
403    }
404
405    @Override
406    public void sequenceClosed(Sequence sequence)
407    {
408        model.updateList();
409    }
410}