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.action;
020
021import icy.gui.frame.progress.ProgressFrame;
022import icy.main.Icy;
023import icy.resource.icon.IcyIcon;
024import icy.system.thread.ThreadUtil;
025import icy.util.StringUtil;
026
027import java.awt.event.ActionEvent;
028
029import javax.swing.AbstractAction;
030import javax.swing.Action;
031import javax.swing.JComponent;
032import javax.swing.JLabel;
033import javax.swing.KeyStroke;
034
035import org.pushingpixels.flamingo.api.common.RichTooltip;
036
037/**
038 * Icy basic AbstractAction class.
039 * 
040 * @author Stephane
041 */
042public abstract class IcyAbstractAction extends AbstractAction
043{
044    /**
045     * Sets the tooltip text of a component from an Action.
046     * 
047     * @param c
048     *        the Component to set the tooltip text on
049     * @param a
050     *        the Action to set the tooltip text from, may be null
051     */
052    public static void setToolTipTextFromAction(JComponent c, Action a)
053    {
054        if (a != null)
055        {
056            final String longDesc = (String) a.getValue(Action.LONG_DESCRIPTION);
057            final String shortDesc = (String) a.getValue(Action.SHORT_DESCRIPTION);
058
059            if (StringUtil.isEmpty(longDesc))
060                c.setToolTipText(shortDesc);
061            else
062                c.setToolTipText(longDesc);
063        }
064    }
065
066    /**
067     * 
068     */
069    private static final long serialVersionUID = -8544059445777661407L;
070
071    private static final int DEFAULT_ICON_SIZE = 20;
072
073    /**
074     * The "enabled" property key.
075     */
076    public static final String ENABLED_KEY = "enabled";
077
078    /**
079     * internals
080     */
081    protected boolean bgProcess;
082    protected boolean processing;
083    protected String processMessage;
084    protected ProgressFrame progressFrame;
085
086    public IcyAbstractAction(String name, IcyIcon icon, String description, String longDescription, int keyCode,
087            int modifiers, boolean bgProcess, String processMessage)
088    {
089        super(name, icon);
090
091        // by default we use the name as Action Command
092        putValue(ACTION_COMMAND_KEY, name);
093        if (keyCode != 0)
094            putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(keyCode, modifiers));
095        if (!StringUtil.isEmpty(description))
096            putValue(SHORT_DESCRIPTION, description);
097        if (!StringUtil.isEmpty(longDescription))
098            putValue(LONG_DESCRIPTION, longDescription);
099
100        this.bgProcess = bgProcess;
101        this.processMessage = processMessage;
102        progressFrame = null;
103        processing = false;
104    }
105
106    public IcyAbstractAction(String name, IcyIcon icon, String description, String longDescription, int keyCode,
107            int modifiers)
108    {
109        this(name, icon, description, longDescription, keyCode, modifiers, false, null);
110    }
111
112    public IcyAbstractAction(String name, IcyIcon icon, String description, String longDescription, boolean bgProcess,
113            String processMessage)
114    {
115        this(name, icon, description, longDescription, 0, 0, bgProcess, processMessage);
116    }
117
118    public IcyAbstractAction(String name, IcyIcon icon, String description, boolean bgProcess, String processMessage)
119    {
120        this(name, icon, description, null, 0, 0, bgProcess, processMessage);
121    }
122
123    public IcyAbstractAction(String name, IcyIcon icon, String description, int keyCode, int modifiers)
124    {
125        this(name, icon, description, null, keyCode, modifiers, false, null);
126    }
127
128    public IcyAbstractAction(String name, IcyIcon icon, String description, int keyCode)
129    {
130        this(name, icon, description, null, keyCode, 0, false, null);
131    }
132
133    public IcyAbstractAction(String name, IcyIcon icon, String description, String longDescription)
134    {
135        this(name, icon, description, longDescription, 0, 0, false, null);
136    }
137
138    public IcyAbstractAction(String name, IcyIcon icon, String description)
139    {
140        this(name, icon, description, null, 0, 0, false, null);
141    }
142
143    public IcyAbstractAction(String name, IcyIcon icon)
144    {
145        this(name, icon, null, null, 0, 0, false, null);
146    }
147
148    public IcyAbstractAction(String name)
149    {
150        this(name, null, null, null, 0, 0, false, null);
151    }
152
153    /**
154     * @deprecated Use {@link #IcyAbstractAction(String, IcyIcon, String, int, int)} instead.
155     */
156    @Deprecated
157    public IcyAbstractAction(String name, String iconName, String description, int keyCode, int modifiers)
158    {
159        this(name, new IcyIcon(iconName, DEFAULT_ICON_SIZE), description, null, keyCode, modifiers, false, null);
160    }
161
162    /**
163     * @deprecated Use {@link #IcyAbstractAction(String, IcyIcon, String, int)} instead.
164     */
165    @Deprecated
166    public IcyAbstractAction(String name, String iconName, String description, int keyCode)
167    {
168        this(name, new IcyIcon(iconName, DEFAULT_ICON_SIZE), description, null, keyCode, 0, false, null);
169    }
170
171    /**
172     * @deprecated Use {@link #IcyAbstractAction(String, IcyIcon, String)} instead.
173     */
174    @Deprecated
175    public IcyAbstractAction(String name, String iconName, String description)
176    {
177        this(name, new IcyIcon(iconName, DEFAULT_ICON_SIZE), description, null, 0, 0, false, null);
178    }
179
180    /**
181     * @deprecated Use {@link #IcyAbstractAction(String, IcyIcon)} instead.
182     */
183    @Deprecated
184    public IcyAbstractAction(String name, String iconName)
185    {
186        this(name, new IcyIcon(iconName, DEFAULT_ICON_SIZE), null, null, 0, 0, false, null);
187    }
188
189    /**
190     * @return true if this action process is done in a background thread.
191     */
192    public boolean isBgProcess()
193    {
194        return bgProcess;
195    }
196
197    /**
198     * Set to true if you want to action to be processed in a background thread.
199     * 
200     * @see #isBgProcess()
201     * @see #setProcessMessage(String)
202     */
203    public void setBgProcess(boolean bgProcess)
204    {
205        this.bgProcess = bgProcess;
206    }
207
208    /**
209     * @return the process message to display for background action process.
210     * @see #setProcessMessage(String)
211     * @see #isBgProcess()
212     */
213    public String getProcessMessage()
214    {
215        return processMessage;
216    }
217
218    public RichTooltip getRichToolTip()
219    {
220        final String desc = getDescription();
221        final String longDesc = getLongDescription();
222        final IcyIcon icon = getIcon();
223
224        if (StringUtil.isEmpty(desc) && StringUtil.isEmpty(longDesc))
225            return null;
226
227        final RichTooltip result = new RichTooltip();
228
229        if (!StringUtil.isEmpty(desc))
230            result.setTitle(desc);
231
232        if (!StringUtil.isEmpty(longDesc))
233        {
234            for (String ld : longDesc.split("\n"))
235                result.addDescriptionSection(ld);
236        }
237
238        if (icon != null)
239            result.setMainImage(icon.getImage());
240
241        return result;
242    }
243
244    /**
245     * Set the process message to display for background action process.<br>
246     * If set to null then no message is displayed (default).
247     * 
248     * @see #setBgProcess(boolean)
249     */
250    public void setProcessMessage(String processMessage)
251    {
252        this.processMessage = processMessage;
253    }
254
255    public void setName(String value)
256    {
257        putValue(Action.NAME, value);
258    }
259
260    public String getName()
261    {
262        return (String) getValue(Action.NAME);
263    }
264
265    public void setIcon(IcyIcon value)
266    {
267        putValue(Action.SMALL_ICON, value);
268    }
269
270    public IcyIcon getIcon()
271    {
272        return (IcyIcon) getValue(Action.SMALL_ICON);
273    }
274
275    public void setDescription(String value)
276    {
277        putValue(Action.SHORT_DESCRIPTION, value);
278    }
279
280    public String getDescription()
281    {
282        return (String) getValue(Action.SHORT_DESCRIPTION);
283    }
284
285    public void setLongDescription(String value)
286    {
287        putValue(Action.LONG_DESCRIPTION, value);
288    }
289
290    public String getLongDescription()
291    {
292        return (String) getValue(Action.LONG_DESCRIPTION);
293    }
294
295    public void setAccelerator(int keyCode, int modifiers)
296    {
297        if (keyCode != 0)
298            putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(keyCode, modifiers));
299        else
300            putValue(ACCELERATOR_KEY, null);
301    }
302
303    public void setAccelerator(int keyCode)
304    {
305        setAccelerator(keyCode, 0);
306    }
307
308    /**
309     * Returns the selected state (for toggle button type).
310     */
311    public boolean isSelected()
312    {
313        return Boolean.TRUE.equals(getValue(SELECTED_KEY));
314    }
315
316    /**
317     * Sets the selected state (for toggle button type).
318     */
319    public void setSelected(boolean value)
320    {
321        putValue(SELECTED_KEY, Boolean.valueOf(value));
322    }
323
324    /**
325     * Returns the {@link KeyStroke} for this action (can be null).
326     */
327    public KeyStroke getKeyStroke()
328    {
329        return (KeyStroke) getValue(ACCELERATOR_KEY);
330    }
331
332    /**
333     * @return true if action is currently processing.<br>
334     *         Meaningful only when {@link #setBgProcess(boolean)} is set to true)
335     */
336    public boolean isProcessing()
337    {
338        return processing;
339    }
340
341    @Override
342    public boolean isEnabled()
343    {
344        return enabled && !processing;
345    }
346
347    @Override
348    public void setEnabled(boolean value)
349    {
350        if (enabled != value)
351        {
352            final boolean wasEnabled = isEnabled();
353            enabled = value;
354            final boolean isEnabled = isEnabled();
355
356            // notify enabled change
357            if (wasEnabled != isEnabled)
358                firePropertyChange(ENABLED_KEY, Boolean.valueOf(wasEnabled), Boolean.valueOf(isEnabled));
359        }
360    }
361
362    protected void setProcessing(boolean value)
363    {
364        if (processing != value)
365        {
366            final boolean wasEnabled = isEnabled();
367            processing = value;
368            final boolean isEnabled = isEnabled();
369
370            // notify enabled change
371            if (wasEnabled != isEnabled)
372                firePropertyChange(ENABLED_KEY, Boolean.valueOf(wasEnabled), Boolean.valueOf(isEnabled));
373        }
374    }
375
376    /**
377     * Helper method to fire enabled changed event (this force component refresh)
378     */
379    public void enabledChanged()
380    {
381        final boolean enabledState = isEnabled();
382
383        // notify enabled change
384        firePropertyChange(ENABLED_KEY, Boolean.valueOf(!enabledState), Boolean.valueOf(enabledState));
385    }
386
387    /**
388     * Returns a {@link JLabel} component representing the action.
389     */
390    public JLabel getLabelComponent(boolean wantIcon, boolean wantText)
391    {
392        final JLabel result = new JLabel();
393
394        if (wantIcon)
395            result.setIcon(getIcon());
396        if (wantText)
397            result.setText(getName());
398
399        final String desc = getDescription();
400
401        if (StringUtil.isEmpty(desc))
402            result.setToolTipText(getLongDescription());
403        else
404            result.setToolTipText(getDescription());
405
406        return result;
407    }
408
409    /**
410     * Returns a {@link JLabel} component representing the action.
411     */
412    public JLabel getLabelComponent()
413    {
414        return getLabelComponent(true, true);
415    }
416
417    @Override
418    public void actionPerformed(ActionEvent e)
419    {
420        setProcessing(true);
421
422        // background execution ?
423        if (isBgProcess())
424        {
425            final ActionEvent event = e;
426
427            ThreadUtil.bgRun(new Runnable()
428            {
429                @Override
430                public void run()
431                {
432                    final String mess = getProcessMessage();
433
434                    if (!StringUtil.isEmpty(mess) && !Icy.getMainInterface().isHeadLess())
435                        progressFrame = new ProgressFrame(mess);
436                    else
437                        progressFrame = null;
438
439                    try
440                    {
441                        doAction(event);
442                    }
443                    finally
444                    {
445                        if (progressFrame != null)
446                            progressFrame.close();
447
448                        // need to be done on the EDT (can change the enabled state)
449                        ThreadUtil.invokeLater(new Runnable()
450                        {
451                            @Override
452                            public void run()
453                            {
454                                setProcessing(false);
455                            }
456                        });
457                    }
458                }
459            });
460        }
461        else
462        {
463            try
464            {
465                doAction(e);
466            }
467            finally
468            {
469                setProcessing(false);
470            }
471        }
472    }
473
474    /**
475     * Execute action (delayed execution if action requires it)
476     */
477    public void execute()
478    {
479        actionPerformed(new ActionEvent(this, 0, ""));
480    }
481
482    /**
483     * @deprecated Use {@link #executeNow()} instead
484     */
485    @Deprecated
486    public boolean doAction()
487    {
488        return doAction(new ActionEvent(this, 0, ""));
489    }
490
491    /**
492     * Execute action now (wait for execution to complete)
493     */
494    public boolean executeNow()
495    {
496        return doAction(new ActionEvent(this, 0, ""));
497    }
498
499    /**
500     * Action implementation
501     */
502    protected abstract boolean doAction(ActionEvent e);
503}