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.util;
020
021import icy.common.listener.weak.WeakListener;
022import icy.image.ImageUtil;
023import icy.preferences.GeneralPreferences;
024import icy.system.IcyExceptionHandler;
025import icy.system.SystemUtil;
026import icy.system.thread.ThreadUtil;
027import icy.util.ClassUtil;
028import icy.util.ReflectionUtil;
029import icy.util.StringUtil;
030
031import java.awt.Color;
032import java.awt.Component;
033import java.awt.Image;
034import java.awt.Window;
035import java.awt.event.ActionEvent;
036import java.awt.event.ActionListener;
037import java.util.ArrayList;
038import java.util.Map;
039
040import javax.swing.JDialog;
041import javax.swing.JFrame;
042import javax.swing.JInternalFrame;
043import javax.swing.PopupFactory;
044import javax.swing.UIManager;
045import javax.swing.UIManager.LookAndFeelInfo;
046import javax.swing.plaf.ColorUIResource;
047import javax.swing.plaf.InternalFrameUI;
048
049import org.pushingpixels.flamingo.api.common.CommandToggleButtonGroup;
050import org.pushingpixels.flamingo.api.common.JCommandToggleMenuButton;
051import org.pushingpixels.flamingo.api.common.popup.JCommandPopupMenu;
052import org.pushingpixels.substance.api.ColorSchemeAssociationKind;
053import org.pushingpixels.substance.api.ComponentState;
054import org.pushingpixels.substance.api.DecorationAreaType;
055import org.pushingpixels.substance.api.SubstanceColorScheme;
056import org.pushingpixels.substance.api.SubstanceLookAndFeel;
057import org.pushingpixels.substance.api.SubstanceSkin;
058import org.pushingpixels.substance.api.colorscheme.LightAquaColorScheme;
059import org.pushingpixels.substance.api.fonts.FontPolicy;
060import org.pushingpixels.substance.api.fonts.FontSet;
061import org.pushingpixels.substance.api.fonts.SubstanceFontUtilities;
062import org.pushingpixels.substance.api.skin.SkinChangeListener;
063import org.pushingpixels.substance.api.skin.SkinInfo;
064import org.pushingpixels.substance.internal.ui.SubstanceInternalFrameUI;
065import org.pushingpixels.substance.internal.utils.SubstanceColorSchemeUtilities;
066import org.pushingpixels.substance.internal.utils.SubstanceInternalFrameTitlePane;
067import org.pushingpixels.substance.internal.utils.SubstanceTitlePane;
068
069import ij.util.Java2;
070
071/**
072 * @author Stephane
073 */
074public class LookAndFeelUtil
075{
076    /**
077     * Weak listener wrapper for SkinChangeListener.
078     * 
079     * @author Stephane
080     */
081    public static class WeakSkinChangeListener extends WeakListener<SkinChangeListener> implements SkinChangeListener
082    {
083        public WeakSkinChangeListener(SkinChangeListener listener)
084        {
085            super(listener);
086        }
087
088        @Override
089        public void removeListener(Object source)
090        {
091            removeSkinChangeListener(this);
092        }
093
094        @Override
095        public void skinChanged()
096        {
097            final SkinChangeListener listener = getListener();
098
099            if (listener != null)
100                listener.skinChanged();
101        }
102    }
103
104    static int defaultFontSize;
105    static Map<String, SkinInfo> map;
106    private static ArrayList<SkinInfo> skins;
107
108    static int currentFontSize;
109
110    public static void init()
111    {
112        try
113        {
114            // so ImageJ won't change look and feel later
115            ReflectionUtil.getField(Java2.class, "lookAndFeelSet", true).set(null, Boolean.valueOf(true));
116        }
117        catch (Exception e)
118        {
119            // do it in another way (slower)
120            Java2.setSystemLookAndFeel();
121        }
122
123        // enable or not EDT checking in substance
124        SystemUtil.setProperty("insubstantial.checkEDT", "false");
125        SystemUtil.setProperty("insubstantial.logEDT", "true");
126
127        // funny features of substance
128
129        // AnimationConfigurationManager.getInstance().allowAnimations(AnimationFacet.FOCUS_LOOP_ANIMATION);
130        // AnimationConfigurationManager.getInstance().allowAnimations(AnimationFacet.GHOSTING_BUTTON_PRESS);
131        // AnimationConfigurationManager.getInstance().allowAnimations(AnimationFacet.GHOSTING_ICON_ROLLOVER);
132        // AnimationConfigurationManager.getInstance().allowAnimations(AnimationFacet.ICON_GLOW);
133
134        // UIManager.put(SubstanceLookAndFeel.USE_THEMED_DEFAULT_ICONS, Boolean.TRUE);
135        // UIManager.put(SubstanceLookAndFeel.SHOW_EXTRA_WIDGETS, Boolean.TRUE);
136        // UIManager.put(SubstanceLookAndFeel.WATERMARK_VISIBLE, Boolean.TRUE);
137
138        // SubstanceLookAndFeel.setWidgetVisible(null, true, SubstanceWidgetType.MENU_SEARCH);
139        // SubstanceLookAndFeel.setWidgetVisible(null, false,
140        // SubstanceWidgetType.TITLE_PANE_HEAP_STATUS);
141
142        // enabled LAF decoration instead of native ones
143        JFrame.setDefaultLookAndFeelDecorated(true);
144        JDialog.setDefaultLookAndFeelDecorated(true);
145
146        map = SubstanceLookAndFeel.getAllSkins();
147        skins = new ArrayList<SkinInfo>(map.values());
148
149        final LookAndFeelInfo[] lafInfos = new LookAndFeelInfo[skins.size()];
150
151        // install Substance look and feel
152        for (int i = 0; i < skins.size(); i++)
153        {
154            final SkinInfo skin = skins.get(i);
155            final String className = skin.getClassName();
156            final String simpleName = ClassUtil.getSimpleClassName(className);
157            final int len = simpleName.length();
158            // remove the "skin" suffix from simple name
159            final String lafClassName = ClassUtil.getPackageName(className) + ".Substance"
160                    + simpleName.substring(0, len - 4) + "LookAndFeel";
161
162            lafInfos[i] = new LookAndFeelInfo(skin.getDisplayName(), lafClassName);
163        }
164
165        // replace system LAF by substance LAF
166        UIManager.setInstalledLookAndFeels(lafInfos);
167        // set default skin
168        setSkin(GeneralPreferences.getGuiSkin());
169
170        // get default font size
171        final FontPolicy fontPolicy = SubstanceFontUtilities.getDefaultFontPolicy();
172        final FontSet fontSet = fontPolicy.getFontSet("Substance", null);
173
174        defaultFontSize = fontSet.getMessageFont().getSize();
175        currentFontSize = defaultFontSize;
176
177        // set default font size
178        setFontSize(GeneralPreferences.getGuiFontSize());
179
180        // Define custom PopupFactory :
181        // That fix Ribbon repaint bugs on Medium Weight components for OSX
182        PopupFactory.setSharedInstance(new CustomPopupFactory());
183    }
184
185    /**
186     * Add skin change listener (automatically add weak listener)
187     */
188    public static void addSkinChangeListener(SkinChangeListener listener)
189    {
190        SubstanceLookAndFeel.registerSkinChangeListener(listener);
191    }
192
193    /**
194     * Remove skin change listener
195     */
196    public static void removeSkinChangeListener(SkinChangeListener listener)
197    {
198        SubstanceLookAndFeel.unregisterSkinChangeListener(listener);
199    }
200
201    /**
202     * get current Substance skin
203     */
204    public static SubstanceSkin getCurrentSkin()
205    {
206        return SubstanceLookAndFeel.getCurrentSkin();
207    }
208
209    /**
210     * get current Substance skin display name
211     */
212    public static String getCurrentSkinName()
213    {
214        final SubstanceSkin skin = getCurrentSkin();
215
216        if (skin != null)
217            return skin.getDisplayName();
218
219        return null;
220    }
221
222    // /**
223    // * get current Look And Feel
224    // */
225    // public static LookAndFeel getLookAndFeel()
226    // {
227    // return UIManager.getLookAndFeel();
228    // }
229    //
230    // /**
231    // * get current Look And Feel name
232    // */
233    // public static String getLookAndFeelName()
234    // {
235    // final LookAndFeel laf = getLookAndFeel();
236    //
237    // if (laf instanceof SubstanceLookAndFeel)
238    // return SubstanceLookAndFeel.getCurrentSkin().getDisplayName();
239    //
240    // return UIManager.getLookAndFeel().getName();
241    // }
242
243    public static JCommandPopupMenu getLookAndFeelMenu()
244    {
245        final JCommandPopupMenu result = new JCommandPopupMenu();
246
247        final CommandToggleButtonGroup buttonGroup = new CommandToggleButtonGroup();
248        final String currentSkinName = getCurrentSkinName();
249
250        for (SkinInfo skin : skins)
251        {
252            final String skinName = skin.getDisplayName();
253
254            final JCommandToggleMenuButton button = new JCommandToggleMenuButton(skinName, null);
255
256            button.addActionListener(new ActionListener()
257            {
258                @Override
259                public void actionPerformed(ActionEvent e)
260                {
261                    // set LAF
262                    LookAndFeelUtil.setSkin(skinName);
263                    // and save to preferences
264                    GeneralPreferences.setGuiSkin(skinName);
265                }
266            });
267
268            result.addMenuButton(button);
269
270            // TODO: why this is needed ? ribbon bug ?
271            button.getUI().installUI(button);
272
273            buttonGroup.add(button);
274            buttonGroup.setSelected(button, button.getText().equals(currentSkinName));
275        }
276
277        return result;
278    }
279
280    /**
281     * get default Look And Feel font size
282     */
283    public static int getDefaultFontSize()
284    {
285        return defaultFontSize;
286    }
287
288    /**
289     * get default Substance skin
290     */
291    public static String getDefaultSkin()
292    {
293        // should be Cerulean
294        return skins.get(4).getDisplayName();
295    }
296
297    // /**
298    // * get default Look And Feel
299    // */
300    // public static String getDefaultLookAndFeel()
301    // {
302    // return "org.pushingpixels.substance.api.skin.SubstanceCremeCoffeeLookAndFeel";
303    // // return UIManager.getSystemLookAndFeelClassName();
304    // }
305
306    /**
307     * Get current LookAndFeel font size
308     */
309    public static int getFontSize()
310    {
311        return currentFontSize;
312    }
313
314    /**
315     * Set LookAndFeel font size
316     */
317    public static void setFontSize(final int size)
318    {
319        if (size != currentFontSize)
320        {
321            ThreadUtil.invokeLater(new Runnable()
322            {
323                @Override
324                public void run()
325                {
326                    final float scaleFactor = (float) size / (float) defaultFontSize;
327
328                    try
329                    {
330                        // will fail here if look and feel is not a substance one
331                        SubstanceLookAndFeel.setFontPolicy(SubstanceFontUtilities.getScaledFontPolicy(scaleFactor));
332                        currentFontSize = size;
333                    }
334                    catch (Exception e)
335                    {
336                        System.err.println("LookAndFeelUtil.setFontSize(...) error :");
337                        IcyExceptionHandler.showErrorMessage(e, false);
338                    }
339                }
340            });
341        }
342    }
343
344    /**
345     * Set the specified LookAndFeel skin (skin display name)
346     */
347    public static void setSkin(final String skinName)
348    {
349        if (!StringUtil.equals(skinName, getCurrentSkinName()))
350        {
351            ThreadUtil.invokeLater(new Runnable()
352            {
353                @Override
354                public void run()
355                {
356                    try
357                    {
358                        SubstanceLookAndFeel.setSkin(map.get(skinName).getClassName());
359                    }
360                    catch (Exception e)
361                    {
362                        System.err.println("LookAndFeelUtil.setSkin(...) error :");
363                        IcyExceptionHandler.showErrorMessage(e, false);
364                    }
365                }
366            });
367        }
368    }
369
370    /**
371     * Return a foreground component color image depending original alpha intensity image
372     */
373    public static Image getForegroundImageFromAlphaImage(Component c, Image alphaImage)
374    {
375        return paintForegroundImageFromAlphaImage(c, alphaImage, null);
376    }
377
378    /**
379     * Return a background component color image depending original alpha intensity image
380     */
381    public static Image getBackgroundImageFromAlphaImage(Component c, Image alphaImage)
382    {
383        return paintBackgroundImageFromAlphaImage(c, alphaImage, null);
384    }
385
386    /**
387     * Paint foreground component color in 'out' image<br>
388     * depending original alpha intensity from 'alphaImage'
389     */
390    public static Image paintForegroundImageFromAlphaImage(Component c, Image alphaImage, Image out)
391    {
392        return ImageUtil.paintColorImageFromAlphaImage(alphaImage, out, getForeground(c));
393    }
394
395    /**
396     * Paint background component color in 'out' image<br>
397     * depending original alpha intensity from 'alphaImage'
398     */
399    public static Image paintBackgroundImageFromAlphaImage(Component c, Image alphaImage, Image out)
400    {
401        return ImageUtil.paintColorImageFromAlphaImage(alphaImage, out, getBackground(c));
402    }
403
404    public static SubstanceColorScheme getActiveColorScheme(DecorationAreaType d)
405    {
406        return getCurrentSkin().getActiveColorScheme(d);
407    }
408
409    /**
410     * @deprecated Use {@link #getActiveColorScheme(DecorationAreaType)} instead.
411     */
412    @Deprecated
413    public static SubstanceColorScheme getActiveColorSheme(DecorationAreaType d)
414    {
415        return getActiveColorScheme(d);
416    }
417
418    public static SubstanceColorScheme getBackgroundColorScheme(DecorationAreaType d)
419    {
420        final SubstanceSkin skin = getCurrentSkin();
421        if (skin != null) return skin.getBackgroundColorScheme(d);
422
423        // Arrive only when using designer --> use a random color theme, we don't care
424        return new LightAquaColorScheme();
425    }
426
427    public static SubstanceColorScheme getDisabledColorScheme(DecorationAreaType d)
428    {
429        final SubstanceSkin skin = getCurrentSkin();
430        if (skin != null) return skin.getDisabledColorScheme(d);
431        
432        // Arrive only when using designer --> use a random color theme, we don't care
433        return new LightAquaColorScheme();
434    }
435
436    public static SubstanceColorScheme getEnabledColorScheme(DecorationAreaType d)
437    {
438        final SubstanceSkin skin = getCurrentSkin();
439        if (skin != null) return skin.getEnabledColorScheme(d);
440        
441        // Arrive only when using designer --> use a random color theme, we don't care
442        return new LightAquaColorScheme();
443    }
444
445    public static SubstanceSkin getSkin()
446    {
447        return SubstanceLookAndFeel.getCurrentSkin();
448    }
449
450    public static SubstanceSkin getSkin(Component c)
451    {
452        return SubstanceLookAndFeel.getCurrentSkin(c);
453    }
454
455    public static DecorationAreaType getDecoration(Component c)
456    {
457        return SubstanceLookAndFeel.getDecorationType(c);
458    }
459
460    public static SubstanceColorScheme getColorScheme(Component c, ComponentState state)
461    {
462        return getSkin().getColorScheme(c, state);
463    }
464
465    public static SubstanceColorScheme getColorScheme(Component c, ColorSchemeAssociationKind kind, ComponentState state)
466    {
467        return getSkin().getColorScheme(c, kind, state);
468    }
469
470    public static SubstanceColorScheme getActiveColorScheme(Component c)
471    {
472        final SubstanceSkin skin = getSkin(c);
473        final DecorationAreaType decoration = getDecoration(c);
474
475        if ((skin != null) && (decoration != null))
476            return skin.getActiveColorScheme(decoration);
477
478        return null;
479    }
480
481    public static SubstanceColorScheme getActiveColorScheme(Component c, ComponentState state)
482    {
483        return SubstanceColorSchemeUtilities.getActiveColorScheme(c, state);
484    }
485
486    /**
487     * @deprecated Use {@link #getActiveColorScheme(Component)} instead.
488     */
489    @Deprecated
490    public static SubstanceColorScheme getActiveColorSheme(Component c)
491    {
492        return getActiveColorScheme(c);
493    }
494
495    /**
496     * @deprecated Use {@link #getActiveColorScheme(Component, ComponentState)} instead.
497     */
498    @Deprecated
499    public static SubstanceColorScheme getActiveColorSheme(Component c, ComponentState state)
500    {
501        return getActiveColorScheme(c, state);
502    }
503
504    public static SubstanceColorScheme getBackgroundColorScheme(Component c)
505    {
506        final SubstanceSkin skin = getSkin(c);
507        final DecorationAreaType decoration = getDecoration(c);
508
509        if ((skin != null) && (decoration != null))
510            return skin.getBackgroundColorScheme(decoration);
511
512        return null;
513    }
514
515    public static SubstanceColorScheme getDisabledColorScheme(Component c)
516    {
517        final SubstanceSkin skin = getSkin(c);
518        final DecorationAreaType decoration = getDecoration(c);
519
520        if ((skin != null) && (decoration != null))
521            return skin.getDisabledColorScheme(decoration);
522
523        return null;
524    }
525
526    public static SubstanceColorScheme getEnabledColorScheme(Component c)
527    {
528        final SubstanceSkin skin = getSkin(c);
529        final DecorationAreaType decoration = getDecoration(c);
530
531        if ((skin != null) && (decoration != null))
532            return skin.getEnabledColorScheme(decoration);
533
534        return null;
535    }
536
537    /**
538     * Return the foreground color for the specified component
539     */
540    public static Color getForeground(Component c)
541    {
542        SubstanceColorScheme colorScheme;
543
544        if (c == null)
545            colorScheme = getEnabledColorScheme(DecorationAreaType.GENERAL);
546        else if (c.isEnabled())
547            colorScheme = getEnabledColorScheme(c);
548        else
549            colorScheme = getDisabledColorScheme(c);
550
551        if (colorScheme == null)
552            getEnabledColorScheme(DecorationAreaType.GENERAL);
553
554        if (colorScheme != null)
555            return new ColorUIResource(colorScheme.getForegroundColor());
556
557        if (c != null)
558            return c.getForeground();
559
560        return Color.white;
561    }
562
563    /**
564     * Return the selected foreground color for the specified component
565     */
566    public static Color getSelectedForeground(Component c)
567    {
568        SubstanceColorScheme colorScheme;
569
570        if (c == null)
571            colorScheme = getEnabledColorScheme(DecorationAreaType.GENERAL);
572        else if (c.isEnabled())
573            colorScheme = getEnabledColorScheme(c);
574        else
575            colorScheme = getDisabledColorScheme(c);
576
577        if (colorScheme == null)
578            getEnabledColorScheme(DecorationAreaType.GENERAL);
579
580        if (colorScheme != null)
581            return new ColorUIResource(colorScheme.getSelectionForegroundColor());
582
583        if (c != null)
584            return c.getForeground();
585
586        return Color.gray;
587    }
588
589    /**
590     * Return the background color for the specified component
591     */
592    public static Color getBackground(Component c)
593    {
594        SubstanceColorScheme colorScheme;
595
596        if (c == null)
597            colorScheme = getEnabledColorScheme(DecorationAreaType.GENERAL);
598        else if (c.isEnabled())
599            colorScheme = getEnabledColorScheme(c);
600        else
601            colorScheme = getDisabledColorScheme(c);
602
603        if (colorScheme == null)
604            getEnabledColorScheme(DecorationAreaType.GENERAL);
605
606        if (colorScheme != null)
607            return new ColorUIResource(colorScheme.getBackgroundFillColor());
608
609        if (c != null)
610            return c.getBackground();
611
612        return Color.lightGray;
613    }
614
615    /**
616     * Return the selected background color for the specified component
617     */
618    public static Color getSelectedBackground(Component c)
619    {
620        SubstanceColorScheme colorScheme;
621
622        if (c == null)
623            colorScheme = getEnabledColorScheme(DecorationAreaType.GENERAL);
624        else if (c.isEnabled())
625            colorScheme = getEnabledColorScheme(c);
626        else
627            colorScheme = getDisabledColorScheme(c);
628
629        if (colorScheme == null)
630            getEnabledColorScheme(DecorationAreaType.GENERAL);
631
632        if (colorScheme != null)
633            return new ColorUIResource(colorScheme.getSelectionBackgroundColor());
634
635        if (c != null)
636            return c.getBackground();
637
638        return Color.darkGray;
639    }
640
641    public static SubstanceTitlePane getTitlePane(Window window)
642    {
643        return (SubstanceTitlePane) SubstanceLookAndFeel.getTitlePaneComponent(window);
644    }
645
646    public static SubstanceInternalFrameTitlePane getTitlePane(JInternalFrame frame)
647    {
648        final InternalFrameUI ui = frame.getUI();
649
650        if (ui instanceof SubstanceInternalFrameUI)
651            return ((SubstanceInternalFrameUI) ui).getTitlePane();
652
653        return null;
654    }
655
656    public static void setTitlePane(JInternalFrame frame, SubstanceInternalFrameTitlePane titlePane)
657    {
658        final InternalFrameUI ui = frame.getUI();
659
660        if (ui instanceof SubstanceInternalFrameUI)
661            ((SubstanceInternalFrameUI) ui).setNorthPane(titlePane);
662    }
663}