All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.alee.managers.settings.UISettingsManager Maven / Gradle / Ivy

The newest version!
/*
 * This file is part of WebLookAndFeel library.
 *
 * WebLookAndFeel library is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * WebLookAndFeel library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with WebLookAndFeel library.  If not, see .
 */

package com.alee.managers.settings;

import com.alee.api.jdk.BiConsumer;
import com.alee.extended.colorchooser.*;
import com.alee.extended.date.DateFieldSettingsProcessor;
import com.alee.extended.date.DateFieldState;
import com.alee.extended.date.WebDateField;
import com.alee.extended.dock.DockablePaneSettingsProcessor;
import com.alee.extended.dock.DockablePaneState;
import com.alee.extended.dock.WebDockablePane;
import com.alee.extended.dock.data.AbstractDockableElement;
import com.alee.extended.dock.data.DockableContentElement;
import com.alee.extended.dock.data.DockableFrameElement;
import com.alee.extended.dock.data.DockableListContainer;
import com.alee.extended.panel.*;
import com.alee.extended.split.MultiSplitPaneSettingsProcessor;
import com.alee.extended.split.MultiSplitState;
import com.alee.extended.split.WebMultiSplitPane;
import com.alee.extended.tab.DocumentPaneSettingsProcessor;
import com.alee.extended.tab.DocumentPaneState;
import com.alee.extended.tab.WebDocumentPane;
import com.alee.laf.WebLookAndFeel;
import com.alee.laf.button.ButtonSettingsProcessor;
import com.alee.laf.button.ButtonState;
import com.alee.laf.colorchooser.HSBColor;
import com.alee.laf.combobox.ComboBoxSettingsProcessor;
import com.alee.laf.combobox.ComboBoxState;
import com.alee.laf.rootpane.RootPaneSettingsProcessor;
import com.alee.laf.rootpane.WindowState;
import com.alee.laf.scroll.ScrollBarSettingsProcessor;
import com.alee.laf.scroll.ScrollBarState;
import com.alee.laf.scroll.ScrollPaneSettingsProcessor;
import com.alee.laf.scroll.ScrollPaneState;
import com.alee.laf.slider.SliderSettingsProcessor;
import com.alee.laf.slider.SliderState;
import com.alee.laf.splitpane.SplitPaneSettingsProcessor;
import com.alee.laf.splitpane.SplitPaneState;
import com.alee.laf.tabbedpane.TabbedPaneSettingsProcessor;
import com.alee.laf.tabbedpane.TabbedPaneState;
import com.alee.laf.text.*;
import com.alee.laf.tree.NodeState;
import com.alee.laf.tree.TreeSettingsProcessor;
import com.alee.laf.tree.TreeState;
import com.alee.laf.tree.WebTree;
import com.alee.utils.ReflectUtils;
import com.alee.utils.XmlUtils;
import com.alee.utils.swing.WeakComponentData;

import javax.swing.*;
import javax.swing.text.JTextComponent;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * {@link SettingsManager} extension that allows registering and processing {@link JComponent} settings.
 * It can save and restore settings upon any changes within or outside of the registered {@link JComponent}s.
 * Each registered {@link JComponent} settings are managed by a {@link SettingsProcessor} instance which is assigned to it.
 * {@link SettingsProcessor} instances are created for each registered {@link JComponent} and handle loading and saving settings.
 * Which {@link SettingsProcessor} implementation is used for registered {@link JComponent} depends on this manager settings.
 * You can also explicitly register a {@link SettingsProcessor} for any {@link JComponent} as long as it supports that {@link JComponent}.
 *
 * @author Mikle Garin
 * @see How to use SettingsManager
 * @see SettingsManager
 * @see Configuration
 * @see SettingsProcessor
 * @see SettingsMethods
 */
public final class UISettingsManager
{
    /**
     * todo 1. Add component-bound listeners (current ones may cause memory leaks)
     * todo 2. JListSettingsProcessor
     * todo 3. JTableSettingsProcessorw
     * todo 4. JScrollPaneSettingsProcessor
     */

    /**
     * Registered settings processor classes.
     */
    private static final Map, Class> settingsProcessorClasses =
            new LinkedHashMap, Class> ();

    /**
     * Registered component settings processors.
     */
    private static final WeakComponentData settingsProcessors =
            new WeakComponentData ( "UISettingsManager.SettingsProcessor", 10 );

    /**
     * Whether SettingsManager is initialized or not.
     */
    private static boolean initialized = false;

    /**
     * Initializes {@link UISettingsManager}.
     */
    public static synchronized void initialize ()
    {
        if ( !initialized )
        {
            initialized = true;

            // Ensure core manager is initialized
            SettingsManager.initialize ();

            // Initializing data aliases
            XmlUtils.processAnnotations ( ButtonState.class );
            XmlUtils.processAnnotations ( TextComponentState.class );
            XmlUtils.processAnnotations ( PasswordFieldState.class );
            XmlUtils.processAnnotations ( DateFieldState.class );
            XmlUtils.processAnnotations ( ComboBoxState.class );
            XmlUtils.processAnnotations ( SliderState.class );
            XmlUtils.processAnnotations ( SplitPaneState.class );
            XmlUtils.processAnnotations ( ScrollBarState.class );
            XmlUtils.processAnnotations ( ScrollPaneState.class );
            XmlUtils.processAnnotations ( WindowState.class );
            XmlUtils.processAnnotations ( CollapsiblePaneState.class );
            XmlUtils.processAnnotations ( AccordionState.class );
            XmlUtils.processAnnotations ( MultiSplitState.class );
            XmlUtils.processAnnotations ( TabbedPaneState.class );
            XmlUtils.processAnnotations ( DocumentPaneState.class );
            XmlUtils.processAnnotations ( TreeState.class );
            XmlUtils.processAnnotations ( NodeState.class );
            XmlUtils.processAnnotations ( GradientColorChooserState.class );
            XmlUtils.processAnnotations ( GradientData.class );
            XmlUtils.processAnnotations ( GradientColorData.class );
            XmlUtils.processAnnotations ( HSBColor.class );
            XmlUtils.processAnnotations ( DockablePaneState.class );
            XmlUtils.processAnnotations ( AbstractDockableElement.class );
            XmlUtils.processAnnotations ( DockableContentElement.class );
            XmlUtils.processAnnotations ( DockableFrameElement.class );
            XmlUtils.processAnnotations ( DockableListContainer.class );

            // Initializing base settings processors
            registerSettingsProcessor ( AbstractButton.class, ButtonSettingsProcessor.class );
            registerSettingsProcessor ( JComboBox.class, ComboBoxSettingsProcessor.class );
            registerSettingsProcessor ( JSlider.class, SliderSettingsProcessor.class );
            registerSettingsProcessor ( JPasswordField.class, PasswordFieldSettingsProcessor.class );
            registerSettingsProcessor ( JTextField.class, TextFieldSettingsProcessor.class );
            registerSettingsProcessor ( JTextComponent.class, TextComponentSettingsProcessor.class );
            registerSettingsProcessor ( JScrollBar.class, ScrollBarSettingsProcessor.class );
            registerSettingsProcessor ( JScrollPane.class, ScrollPaneSettingsProcessor.class );
            registerSettingsProcessor ( JSplitPane.class, SplitPaneSettingsProcessor.class );
            registerSettingsProcessor ( JTabbedPane.class, TabbedPaneSettingsProcessor.class );
            registerSettingsProcessor ( JRootPane.class, RootPaneSettingsProcessor.class );

            // Register additional component settings processors
            registerSettingsProcessor ( WebMultiSplitPane.class, MultiSplitPaneSettingsProcessor.class );
            registerSettingsProcessor ( WebDocumentPane.class, DocumentPaneSettingsProcessor.class );
            registerSettingsProcessor ( WebTree.class, TreeSettingsProcessor.class );
            registerSettingsProcessor ( WebDateField.class, DateFieldSettingsProcessor.class );
            registerSettingsProcessor ( WebCollapsiblePane.class, CollapsiblePaneSettingsProcessor.class );
            registerSettingsProcessor ( WebAccordion.class, AccordionSettingsProcessor.class );
            registerSettingsProcessor ( WebGradientColorChooser.class, GradientColorChooserSettingsProcessor.class );
            registerSettingsProcessor ( WebDockablePane.class, DockablePaneSettingsProcessor.class );

            // Settings listener that applies settings changes to components
            // It is added here instead of being added from processor to avoid memory leaks
            SettingsManager.addSettingsListener ( new SettingsListener ()
            {
                @Override
                public void settingsChanged ( final String group, final String key, final Object oldValue, final Object newValue )
                {
                    settingsProcessors.forEach ( new BiConsumer ()
                    {
                        @Override
                        public void accept ( final JComponent component, final SettingsProcessor processor )
                        {
                            final Configuration configuration = processor.configuration ();
                            if ( configuration.isApplySettingsChanges () &&
                                    group.equals ( configuration.group () ) && key.equals ( configuration.key () ) )
                            {
                                processor.load ();
                            }
                        }
                    } );
                }
            } );
        }
    }

    /**
     * Returns whether the specified {@link JComponent} is supported or not.
     *
     * @param component component
     * @return {@code true} if the specified {@link JComponent} is supported, {@code false} otherwise
     */
    public static boolean isComponentSupported ( final JComponent component )
    {
        return isComponentSupported ( component.getClass () );
    }

    /**
     * Returns whether the specified {@link JComponent} type is supported or not.
     *
     * @param componentType component type
     * @return {@code true} if the specified {@link JComponent} type is supported, {@code false} otherwise
     */
    public static boolean isComponentSupported ( final Class componentType )
    {
        return findSuitableSettingsProcessorClass ( componentType ) != null;
    }

    /**
     * Registers specified {@link SettingsProcessor} class for the specified {@link JComponent} type.
     *
     * @param componentType     {@link JComponent} type
     * @param settingsProcessor {@link SettingsProcessor} class
     * @return {@link SettingsProcessor} class previously registered for the specified {@link JComponent} type
     */
    public static Class registerSettingsProcessor ( final Class componentType,
                                                                                 final Class settingsProcessor )
    {
        return settingsProcessorClasses.put ( componentType, settingsProcessor );
    }

    /**
     * Registers {@link JComponent} for settings auto-save.
     *
     * Registered {@link JComponent} will be:
     * - tracked for settings changes (depends on {@link SettingsProcessor}) to save them when requested
     * - automatically updated with any loaded settings for that key if requested
     * - automatically updated with any changes made in its settings if requested
     *
     * @param component {@link JComponent} to register for settings auto-save
     * @param data      {@link Configuration} for {@link SettingsProcessor}
     */
    public static void registerComponent ( final JComponent component, final Configuration data )
    {
        registerComponent ( component, createSettingsProcessor ( component, data ) );
    }

    /**
     * Registers {@link JComponent} for settings auto-save.
     * {@link JComponent} is required here simply to check that registered component is the same as the one in {@link SettingsProcessor}.
     *
     * Registered {@link JComponent} will be:
     * - tracked for settings changes (depends on {@link SettingsProcessor}) to save them when requested
     * - automatically updated with any loaded settings for that key if requested
     * - automatically updated with any changes made in its settings if requested
     *
     * @param component {@link JComponent} to register for settings auto-save
     * @param processor {@link SettingsProcessor}
     */
    public static void registerComponent ( final JComponent component, final SettingsProcessor processor )
    {
        // Event Dispatch Thread check
        WebLookAndFeel.checkEventDispatchThread ();

        // Ensure that component within processor is the same one
        if ( component != processor.component () )
        {
            throw new SettingsException ( "SettingsProcessor cannot be referencing different component" );
        }

        // Saving component settings processor
        settingsProcessors.set ( processor.component (), processor, new BiConsumer ()
        {
            @Override
            public void accept ( final JComponent component, final SettingsProcessor settingsProcessor )
            {
                settingsProcessor.destroy ();
            }
        } );
    }

    /**
     * Unregisters {@link JComponent} from settings auto-save.
     *
     * @param component component to unregister
     */
    public static void unregisterComponent ( final JComponent component )
    {
        // Event Dispatch Thread check
        WebLookAndFeel.checkEventDispatchThread ();

        // Removing component settings processor
        settingsProcessors.clear ( component, new BiConsumer ()
        {
            @Override
            public void accept ( final JComponent component, final SettingsProcessor settingsProcessor )
            {
                settingsProcessor.destroy ();
            }
        } );
    }

    /**
     * Loads previously saved settings for the specified {@link JComponent} if it is registered.
     *
     * @param component {@link JComponent} to load settings for
     */
    public static void loadSettings ( final JComponent component )
    {
        // Event Dispatch Thread check
        WebLookAndFeel.checkEventDispatchThread ();

        // Loading settings if possible
        if ( settingsProcessors.contains ( component ) )
        {
            settingsProcessors.get ( component ).load ();
        }
        else
        {
            final String msg = "Processor is not registered for component: %s";
            throw new SettingsException ( String.format ( msg, component ) );
        }
    }

    /**
     * Saves settings for the specified {@link JComponent} if it is registered.
     *
     * @param component {@link JComponent} to save settings for
     */
    public static void saveSettings ( final JComponent component )
    {
        // Event Dispatch Thread check
        WebLookAndFeel.checkEventDispatchThread ();

        // Saving settings
        if ( settingsProcessors.contains ( component ) )
        {
            settingsProcessors.get ( component ).save ( false );
        }
        else
        {
            final String msg = "Processor is not registered for component: %s";
            throw new SettingsException ( String.format ( msg, component ) );
        }
    }

    /**
     * Saves all registered {@link JComponent}s settings.
     */
    public static void saveSettings ()
    {
        // Event Dispatch Thread check
        WebLookAndFeel.checkEventDispatchThread ();

        // Saving plain settings
        SettingsManager.saveSettings ();

        // Saving component settings
        settingsProcessors.forEach ( new BiConsumer ()
        {
            @Override
            public void accept ( final JComponent component, final SettingsProcessor settingsProcessor )
            {
                saveSettings ( component );
            }
        } );
    }

    /**
     * Returns {@link SettingsProcessor} class for the specified {@link JComponent} type.
     *
     * @param componentType {@link JComponent} type
     * @return {@link SettingsProcessor} class for the specified {@link JComponent} type
     */
    private static Class findSuitableSettingsProcessorClass ( final Class componentType )
    {
        // Looking through map with strict elements order for proper settings processor
        for ( final Class type : settingsProcessorClasses.keySet () )
        {
            if ( ReflectUtils.isAssignable ( type, componentType ) )
            {
                return settingsProcessorClasses.get ( type );
            }
        }
        return null;
    }

    /**
     * Returns new {@link SettingsProcessor} instance for the specified {@link JComponent} and {@link Configuration}.
     *
     * @param component     {@link JComponent} to create {@link SettingsProcessor} for
     * @param configuration {@link Configuration} for {@link SettingsProcessor}
     * @return new {@link SettingsProcessor} instance for the specified {@link JComponent} and {@link Configuration}
     */
    private static SettingsProcessor createSettingsProcessor ( final JComponent component, final Configuration configuration )
    {
        final Class settingsProcessorClass = findSuitableSettingsProcessorClass ( component.getClass () );
        if ( settingsProcessorClass != null )
        {
            try
            {
                // Creating new settings processor from registered class
                return ReflectUtils.createInstance ( settingsProcessorClass, component, configuration );
            }
            catch ( final Exception e )
            {
                // Throw processor instantiation exception
                final String msg = "Unable to instantiate SettingsProcessor class: %s";
                throw new SettingsException ( String.format ( msg, settingsProcessorClass ), e );
            }
        }
        else
        {
            // Throw missing processor mapping exception
            final String msg = "Unable to find SettingsProcessor for data: %s";
            throw new SettingsException ( String.format ( msg, configuration ) );
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy