/*******************************************************************************
 * Copyright (c) 2003, 2004 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials 
 * are made available under the terms of the Common Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/cpl-v10.html
 * 
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.ui.internal.themes;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.Set;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.jface.preference.ColorSelector;
import org.eclipse.jface.preference.PreferenceConverter;
import org.eclipse.jface.preference.PreferencePage;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.resource.StringConverter;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.viewers.AbstractTreeViewer;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.IFontProvider;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.LabelProviderChangedEvent;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerSorter;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StackLayout;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.FontDialog;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPreferencePage;
import org.eclipse.ui.internal.IWorkbenchGraphicConstants;
import org.eclipse.ui.internal.Workbench;
import org.eclipse.ui.internal.WorkbenchMessages;
import org.eclipse.ui.internal.WorkbenchPlugin;
import org.eclipse.ui.internal.dialogs.FilteredTree;
import org.eclipse.ui.internal.dialogs.PatternFilter;
import org.eclipse.ui.internal.misc.StatusUtil;
import org.eclipse.ui.themes.ITheme;
import org.eclipse.ui.themes.IThemeManager;
import org.eclipse.ui.themes.IThemePreview;

/**
 * Preference page for management of system colors, gradients and fonts.
 * 
 * @since 3.0
 */
public final class ColorsAndFontsPreferencePage extends PreferencePage implements IWorkbenchPreferencePage {
    
    private class ThemeContentProvider implements ITreeContentProvider {    	
    	
        private IThemeRegistry registry;

        /* (non-Javadoc)
         * @see org.eclipse.jface.viewers.ITreeContentProvider#getChildren(java.lang.Object)
         */
        public Object[] getChildren(Object parentElement) {
            if (parentElement instanceof ThemeElementCategory) {
                String categoryId = ((ThemeElementCategory)parentElement).getId();
        		Object [] defintions = (Object []) categoryMap.get(categoryId);
        		if (defintions == null) {	
        		    defintions = getCategoryChildren(categoryId);
        			categoryMap.put(categoryId, defintions);
        		}
        		return defintions;                
            }
            else {
                ArrayList list = new ArrayList();
                IHierarchalThemeElementDefinition def = (IHierarchalThemeElementDefinition) parentElement;
                String id = def.getId();
                IHierarchalThemeElementDefinition [] defs;
	            if (def instanceof ColorDefinition) 
	                defs = registry.getColors();
	            else 
	                defs = registry.getFonts();
	            
                for (int i = 0; i < defs.length; i++) {
                    if (id.equals(defs[i].getDefaultsTo()) 
                            && ColorsAndFontsPreferencePage.equals(
                                    ((ICategorizedThemeElementDefinition)def).getCategoryId(), 
                                    ((ICategorizedThemeElementDefinition)defs[i]).getCategoryId()))
                    	list.add(defs[i]);
                }
                return list.toArray();
            }            
        }

        private Object[] getCategoryChildren(String categoryId) {
		    ArrayList list = new ArrayList();
		    
		    if (categoryId != null) {
			    ThemeElementCategory [] categories = registry.getCategories();
			    for (int i = 0; i < categories.length; i++) {
	                if (categoryId.equals(categories[i].getParentId())) {
	                    Set bindings = themeRegistry.getPresentationsBindingsFor(categories[i]);
	                    if (bindings == null || bindings.contains(workbench.getPresentationId()))
	                        list.add(categories[i]);
	                }
	            }
		    }
		    {
				ColorDefinition[] colorDefinitions = themeRegistry.getColorsFor(currentTheme.getId());
				for (int i = 0; i < colorDefinitions.length; i++) {
				    if (!colorDefinitions[i].isEditable())
				        continue;
				    String catId = colorDefinitions[i].getCategoryId();
				    if ((catId == null && categoryId == null) || (catId != null && categoryId != null && categoryId.equals(catId))) {
				        if (colorDefinitions[i].getDefaultsTo() != null && parentIsInSameCategory(colorDefinitions[i]))
				            continue;
				        list.add(colorDefinitions[i]);
				    }
				}
		    }
		    {
				FontDefinition[] fontDefinitions = themeRegistry.getFontsFor(currentTheme.getId());
				for (int i = 0; i < fontDefinitions.length; i++) {
				    if (!fontDefinitions[i].isEditable())
				        continue;                
				    String catId = fontDefinitions[i].getCategoryId();
				    if ((catId == null && categoryId == null) || (catId != null && categoryId != null && categoryId.equals(catId))) {
				        if (fontDefinitions[i].getDefaultsTo() != null && parentIsInSameCategory(fontDefinitions[i]))
				            continue;
				        list.add(fontDefinitions[i]);
				    }
				}
		    }
			return list.toArray(new Object[list.size()]);			
        }
        
        /**
         * @param definition
         * @return
         */
        private boolean parentIsInSameCategory(ColorDefinition definition) {
            String defaultsTo = definition.getDefaultsTo();
            ColorDefinition [] defs = registry.getColors();
            for (int i = 0; i < defs.length; i++) {
                if (defs[i].getId().equals(defaultsTo) && ColorsAndFontsPreferencePage.equals(defs[i].getCategoryId(), definition.getCategoryId())) 
                    return true;                    
            }
            return false;
        }

        /**
         * @param definition
         * @return
         */
        private boolean parentIsInSameCategory(FontDefinition definition) {
            String defaultsTo = definition.getDefaultsTo();
            FontDefinition [] defs = registry.getFonts();
            for (int i = 0; i < defs.length; i++) {
                if (defs[i].getId().equals(defaultsTo) && ColorsAndFontsPreferencePage.equals(defs[i].getCategoryId(), definition.getCategoryId())) 
                    return true;                    
            }
            return false;
        }       
        
        /* (non-Javadoc)
         * @see org.eclipse.jface.viewers.ITreeContentProvider#getParent(java.lang.Object)
         */
        public Object getParent(Object element) {
            return null;
        }

        /* (non-Javadoc)
         * @see org.eclipse.jface.viewers.ITreeContentProvider#hasChildren(java.lang.Object)
         */
        public boolean hasChildren(Object element) {
            if (element instanceof ThemeElementCategory)
                return true;
            else {
	            IHierarchalThemeElementDefinition def = (IHierarchalThemeElementDefinition) element;
	            String id = def.getId();
	            IHierarchalThemeElementDefinition [] defs;
	            if (def instanceof ColorDefinition) 
	                defs = registry.getColors();
	            else 
	                defs = registry.getFonts();
	            
	            for (int i = 0; i < defs.length; i++) {
	                if (id.equals(defs[i].getDefaultsTo()) 
	                        && ColorsAndFontsPreferencePage.equals(
	                            ((ICategorizedThemeElementDefinition)def).getCategoryId(), 
	                            ((ICategorizedThemeElementDefinition)defs[i]).getCategoryId()))

	                	return true;
	            }
                
            }            
            return false;
        }

        /* (non-Javadoc)
         * @see org.eclipse.jface.viewers.IStructuredContentProvider#getElements(java.lang.Object)
         */
        public Object[] getElements(Object inputElement) {
            ArrayList list = new ArrayList();
            Object [] uncatChildren = getCategoryChildren(null);
            list.addAll(Arrays.asList(uncatChildren));
            ThemeElementCategory [] categories = ((IThemeRegistry)inputElement).getCategories();
            for (int i = 0; i < categories.length; i++) {
                if (categories[i].getParentId() == null) {                    
                    Set bindings = themeRegistry.getPresentationsBindingsFor(categories[i]);
                    if (bindings == null || bindings.contains(workbench.getPresentationId()))
                        list.add(categories[i]);
                }
            }
            return list.toArray(new Object [list.size()]);
        }

        /* (non-Javadoc)
         * @see org.eclipse.jface.viewers.IContentProvider#dispose()
         */
        public void dispose() {
            categoryMap.clear();
        }

        /* (non-Javadoc)
         * @see org.eclipse.jface.viewers.IContentProvider#inputChanged(org.eclipse.jface.viewers.Viewer, java.lang.Object, java.lang.Object)
         */
        public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
            categoryMap.clear();
            registry = (IThemeRegistry) newInput;
        }

    }
    private class PresentationLabelProvider extends LabelProvider implements IFontProvider {
        
        private HashMap fonts = new HashMap();

        private HashMap images = new HashMap();
        
        private int imageSize = -1;
        private int usableImageSize = -1;
        
        private IPropertyChangeListener listener = new IPropertyChangeListener() {
            public void propertyChange(PropertyChangeEvent event) {
                fireLabelProviderChanged(new LabelProviderChangedEvent(PresentationLabelProvider.this));                
            }            
        };
        
        private Image emptyImage;        
        
        /**
         * 
         */
        public PresentationLabelProvider() {
            hookListeners();
        }
    	
        /**
         * Hook the listeners onto the various registries.
         */
        public void hookListeners() {
            colorRegistry.addListener(listener);        
            fontRegistry.addListener(listener);
        }

        /* (non-Javadoc)
         * @see org.eclipse.jface.viewers.IBaseLabelProvider#dispose()
         */
        public void dispose() {
            super.dispose();
            colorRegistry.removeListener(listener);
            fontRegistry.removeListener(listener);
            for (Iterator i = images.values().iterator(); i.hasNext();) {
                ((Image) i.next()).dispose();
            }
            images.clear();
                        
            if (emptyImage != null) {
            	emptyImage.dispose();
            	emptyImage = null;
            }            
            
            //clear the fonts.  Has a side effect of firing a label property change
            clearFontCache();
        }

		/**
		 * Clears and disposes all fonts and fires a label changed event.
		 */
		public void clearFontCache() {
			for (Iterator i = fonts.values().iterator(); i.hasNext();) {
                ((Font) i.next()).dispose();
            }
            fonts.clear();
            
            fireLabelProviderChanged(new LabelProviderChangedEvent(PresentationLabelProvider.this));
		}

		/* (non-Javadoc)
         * @see org.eclipse.jface.viewers.IFontProvider#getFont(java.lang.Object)
         */
        public Font getFont(Object element) {
            Display display = tree.getDisplay();
            if (element instanceof FontDefinition) {
                int parentHeight = tree.getViewer().getControl().getFont().getFontData()[0].getHeight();
    	        Font baseFont = fontRegistry.get(((FontDefinition)element).getId());
    	        Font font = (Font) fonts.get(baseFont);
    	        if (font == null) {
    	            FontData [] data = baseFont.getFontData();
    	            for (int i = 0; i < data.length; i++) {
                        data[i].setHeight(parentHeight);
                    }
    	            font = new Font(display, data);
	
		    		fonts.put(baseFont, font);
    	        }
    	        updateColumn(getText(element), font);
    	        return font;
    	    }

            updateColumn(getText(element), JFaceResources.getDialogFont());
            return JFaceResources.getDialogFont();
        }
    	
    	/**
         * Updates the table font based on the width required to render the 
	     * given text in the given font.  If this value is greater than 
         * largestFontWidth then the font on the table is set to the provided 
         * font and largestFotnWidth is updated.  This is required as a 
		 * workaround to bug 56593.
         */
        private void updateColumn(String text, Font font) {
        	Display display = tree.getDisplay();

		    GC gc = new GC(display);
		    gc.setFont(font);
			
			int width = gc.stringExtent(text).x;
			if (width > largestFontWidth) {
				largestFontWidth = width;
				if (!tree.isDisposed())				
				    tree.getViewer().getControl().setFont(font);
			}
			gc.dispose();    	        
        }

        /* (non-Javadoc)
    	 * @see org.eclipse.jface.viewers.ILabelProvider#getImage(java.lang.Object)
    	 */
    	public Image getImage(Object element) {
    	    if (element instanceof ColorDefinition) {	    			    		
    	        Color c = colorRegistry.get(((ColorDefinition)element).getId());
    	        Image image = (Image) images.get(c);
    	        if (image == null) {
    	        	Display display = tree.getDisplay();
    	            ensureImageSize(display);
		    		//int size = presentationList.getControl().getFont().getFontData()[0].getHeight();
	    	        image = new Image(display, imageSize, imageSize);
	    	        
		    		GC gc = new GC(image);
		    		gc.setBackground(tree.getViewer().getControl().getBackground());
		    		gc.setForeground(tree.getViewer().getControl().getBackground());
		    		gc.drawRectangle(0, 0, imageSize - 1, imageSize - 1);
		    		
		    		gc.setForeground(tree.getViewer().getControl().getForeground());
		    		gc.setBackground(c);
		    		
		    		int offset = (imageSize - usableImageSize) / 2;
		    		gc.drawRectangle(offset, offset, usableImageSize - offset, usableImageSize - offset);
		    		gc.fillRectangle(offset + 1, offset + 1, usableImageSize - offset - 1, usableImageSize - offset - 1);		    			    	
		    		gc.dispose();
		    		
		    		images.put(c, image);
    	        }
	    		return image;
    	        
    	    }
    	    else if (element instanceof FontDefinition){
    	        return workbench.getSharedImages().getImage(IWorkbenchGraphicConstants.IMG_OBJ_FONT);
    	    }
    	    else {
    	        return workbench.getSharedImages().getImage(IWorkbenchGraphicConstants.IMG_OBJ_THEME_CATEGORY);
    	    }
    	}

        /**
         * @param display
         * @return
         */
        private void ensureImageSize(Display display) {
            if (imageSize == -1) {    	        
	    		imageSize = tree.getViewer().getTree().getItemHeight();    
	    		usableImageSize = Math.max(1, imageSize - 4);
            }
        }

        /* (non-Javadoc)
    	 * @see org.eclipse.jface.viewers.ILabelProvider#getText(java.lang.Object)
    	 */
    	public String getText(Object element) {	 
    	    if (element instanceof IHierarchalThemeElementDefinition) {
		        if (((IHierarchalThemeElementDefinition)element).getDefaultsTo() != null) {
		            String myCategory = ((ICategorizedThemeElementDefinition)element).getCategoryId();
		            ICategorizedThemeElementDefinition def;
		            if (element instanceof ColorDefinition) 
		                def = (ICategorizedThemeElementDefinition) themeRegistry.findColor(((IHierarchalThemeElementDefinition)element).getDefaultsTo());
		            else 
		                def = (ICategorizedThemeElementDefinition) themeRegistry.findFont(((IHierarchalThemeElementDefinition)element).getDefaultsTo());
		            
		            if (!ColorsAndFontsPreferencePage.equals(def.getCategoryId(), myCategory)) {
		                return MessageFormat.format(RESOURCE_BUNDLE.getString("defaultFormat"), new Object[] {((IThemeElementDefinition) element).getLabel(), def.getLabel()}); //$NON-NLS-1$ 	                
		            }
		        }
    	    }
			return ((IThemeElementDefinition) element).getLabel();
    	}
    }    
    
	/**
	 * The translation bundle in which to look up internationalized text.
	 */
	private final static ResourceBundle RESOURCE_BUNDLE =
		ResourceBundle.getBundle(ColorsAndFontsPreferencePage.class.getName());


	/**
	 * Map to precalculate category color lists.
	 */
	private Map categoryMap = new HashMap(7);
	
    private Font appliedDialogFont;
	
	/**
	 * The composite containing all color-specific controls. 
	 */
    private Composite colorControls;

	/**
	 * Map of defintion id->RGB objects that map to changes expressed in this
	 * UI session.  These changes should be made in preferences and the 
	 * registry.
	 */
	private Map colorPreferencesToSet = new HashMap(7);
	
	private CascadingColorRegistry colorRegistry;
	private Button colorResetButton;

	private ColorSelector colorSelector;

	/**
	 * Map of defintion id->RGB objects that map to changes expressed in this
	 * UI session.  These changes should be made in the registry.
	 */
	private Map colorValuesToSet = new HashMap(7);	
    
	/**
	 * The composite that contains the font or color controls (or none).
	 */
    private Composite controlArea;
    
    /**
     * The layout for the controlArea.
     */
    private StackLayout controlAreaLayout;
    
    /**
     * The composite to use when no preview is available. 
     */
    private Composite defaultPreviewControl;
	private Text descriptionText;
    private List dialogFontWidgets = new ArrayList();    
    private Button fontChangeButton;
    
    /**
     * The composite containing all font-specific controls. 
     */
    private Composite fontControls;
	private Map fontPreferencesToSet = new HashMap(7);
	private CascadingFontRegistry fontRegistry;
	private Button fontResetButton;
    private Button fontSystemButton;
	
	/**
	 * Map of defintion id->FontData[] objects that map to changes expressed in 
	 * this UI session.  These changes should be made in preferences and the 
	 * registry.
	 */    
	private Map fontValuesToSet = new HashMap(7);
	
	/**
	 * The list of fonts and colors.
	 */
	//private TreeViewer presentationList;
	
	/**
	 * The composite that is parent to all previews.
	 */
    private Composite previewComposite;
    
    /**
     * A mapping from PresentationCategory->Composite for the created previews.
     */
	private Map previewMap = new HashMap(7);
	
	/**
	 * Set containing all IPresentationPreviews created. 
	 */
	private Set previewSet = new HashSet(7);
	
	/**
	 * The layout for the previewComposite.
	 */
    private StackLayout stackLayout;
   

    private final IThemeRegistry themeRegistry;

    private ITheme currentTheme;

    private PresentationLabelProvider labelProvider;

    private CascadingTheme cascadingTheme;
    
    /**
	 * The largest width so far encountered required to render a given label in it's font.
	 */
    private int largestFontWidth;

    private IPropertyChangeListener themeChangeListener;

    private Workbench workbench;
    
    private FilteredTree tree;
    
	/**
	 * Create a new instance of the receiver. 
	 */
	public ColorsAndFontsPreferencePage() {
        themeRegistry = WorkbenchPlugin.getDefault().getThemeRegistry();
		//no-op
	}
    
    /**
     * @param string
     * @param string2
     * @return
     */
    private static boolean equals(String string, String string2) {
        if ((string == null && string2 == null))
            return true;
        if (string == null || string2 == null)
            return false;
        if (string.equals(string2))
            return true;
        
        return false;
    }
	
	/**
	 * Create a button for the preference page.
	 * @param parent
	 * @param label
	 */
	private Button createButton(Composite parent, String label) {
		Button button = new Button(parent, SWT.PUSH | SWT.CENTER);
		button.setText(label);
		myApplyDialogFont(button);
		setButtonLayoutData(button);
		button.setEnabled(false);
		return button;
	} 

	/**
	 * Create the color selection control. 
	 */
	private void createColorControl() {	    
		Composite composite = new Composite(colorControls, SWT.NONE);
		GridLayout layout = new GridLayout(2, false);
		layout.marginHeight = 0;
		layout.marginWidth = 0;
		composite.setLayout(layout);

		colorSelector = new ColorSelector(composite);
		colorSelector.getButton().setLayoutData(new GridData());
		myApplyDialogFont(colorSelector.getButton());
		colorSelector.setEnabled(false);

		colorResetButton = createButton(composite, RESOURCE_BUNDLE.getString("reset")); //$NON-NLS-1$
	}



	/* (non-Javadoc)
	 * @see org.eclipse.jface.preference.PreferencePage#createContents(org.eclipse.swt.widgets.Composite)
	 */
	protected Control createContents(Composite parent) {
		parent.addDisposeListener(new DisposeListener() {
			public void widgetDisposed(DisposeEvent e) {
				if (appliedDialogFont != null)
					appliedDialogFont.dispose();
			}
		});		
		Composite mainColumn = new Composite(parent, SWT.NONE);
		GridLayout layout = new GridLayout();
		layout.marginWidth = 0;
		layout.marginHeight = 0;
		mainColumn.setFont(parent.getFont());
		mainColumn.setLayout(layout);

		GridData data = new GridData(GridData.BEGINNING);	
		Label label = new Label(mainColumn, SWT.LEFT);	
		label.setText(RESOURCE_BUNDLE.getString("colorsAndFonts")); //$NON-NLS-1$
		myApplyDialogFont(label);
		label.setLayoutData(data);
		
		Composite controlRow = new Composite(mainColumn, SWT.NONE);
		layout = new GridLayout();
		layout.numColumns = 2;
		layout.marginHeight = 0;
		layout.marginWidth = 0;
		controlRow.setLayout(layout);
		data = new GridData(GridData.FILL_HORIZONTAL);
		controlRow.setLayoutData(data);		
		
		createTree(controlRow);
		Composite controlColumn = new Composite(controlRow, SWT.NONE);
		data = new GridData(GridData.FILL_VERTICAL);
		controlColumn.setLayoutData(data);
		layout = new GridLayout();
		layout.marginHeight = 0;
		layout.marginWidth = 0;
		controlColumn.setLayout(layout);

		controlArea = new Composite(controlColumn, SWT.NONE);
		controlAreaLayout = new StackLayout();
		controlArea.setLayout(controlAreaLayout);
		
		colorControls = new Composite(controlArea, SWT.NONE);
		colorControls.setLayout(new FillLayout());
		createColorControl();
		
		fontControls = new Composite(controlArea, SWT.NONE);
		fontControls.setLayout(new FillLayout());
		createFontControl();
		
		createDescriptionControl(mainColumn);

		createPreviewControl(mainColumn);		
		
		hookListeners();
		
		return mainColumn;
	}

    /**
	 * Create the text box that will contain the current color/font description 
	 * text (if any).
	 * 
	 * @param parent the parent <code>Composite</code>.
	 */
	private void createDescriptionControl(Composite parent) {
		Composite composite = new Composite(parent, SWT.NONE);
		GridLayout layout = new GridLayout();
		layout.marginWidth = 0;
		layout.marginHeight = 0;
		composite.setLayout(layout);
		GridData data = new GridData(GridData.FILL_BOTH);
		composite.setLayoutData(data);

		Label label = new Label(composite, SWT.LEFT);
		label.setText(RESOURCE_BUNDLE.getString("description")); //$NON-NLS-1$
		myApplyDialogFont(label);

		descriptionText = new Text(composite, SWT.H_SCROLL | SWT.V_SCROLL | SWT.READ_ONLY | SWT.BORDER | SWT.WRAP);
		data = new GridData(GridData.FILL_BOTH);
		descriptionText.setText("\n\n"); //$NON-NLS-1$
		descriptionText.setLayoutData(data);
		myApplyDialogFont(descriptionText);
	}

	/**
     * @param fontControls2
     */
    private void createFontControl() {
		Composite composite = new Composite(fontControls, SWT.NONE);
		GridLayout layout = new GridLayout(1, false);
		layout.marginHeight = 0;
		layout.marginWidth = 0;
		composite.setLayout(layout);
		
		fontSystemButton = createButton(composite, WorkbenchMessages.getString("FontsPreference.useSystemFont")); //$NON-NLS-1$

		fontChangeButton = createButton(composite, JFaceResources.getString("openChange")); //$NON-NLS-1$

		fontResetButton = createButton(composite, RESOURCE_BUNDLE.getString("reset")); //$NON-NLS-1$
    }
    
	/**
	 * Create the <code>ListViewer</code> that will contain all color 
	 * definitions as defined in the extension point.
	 * 
	 * @param parent the parent <code>Composite</code>.
	 */
	private void createTree(Composite parent) {
		labelProvider = new PresentationLabelProvider();
		// create a new tree with a custom pattern matcher that will allow
		// non-category elements to be returned in the event that their children
		// do not
	    tree = new FilteredTree(
	            parent, 
	            SWT.SINGLE | SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER,
				new PatternFilter() {
	            /* (non-Javadoc)
				 * @see org.eclipse.ui.internal.dialogs.PatternFilter#select(org.eclipse.jface.viewers.Viewer, java.lang.Object, java.lang.Object)
				 */
				public boolean select(Viewer viewer, Object parentElement,
						Object element) {
			        Object [] children = ((ITreeContentProvider)((AbstractTreeViewer)viewer).getContentProvider()).getChildren(element);
			        if (children.length > 0 && element instanceof ThemeElementCategory)       
			            return filter(viewer, element, children).length > 0;
			        
			        String labelText = ((ILabelProvider)((StructuredViewer)viewer).getLabelProvider()).getText(element);
			        return match(labelText);
				}});
	    
		GridData data = new GridData(GridData.FILL_HORIZONTAL | GridData.VERTICAL_ALIGN_FILL);
		data.heightHint = Math.max(175, convertHeightInCharsToPixels(10));		
		tree.setLayoutData(data);
		myApplyDialogFont(tree.getViewer().getControl());
		myApplyDialogFont(tree.getFilterField());
	    
	    tree.getViewer().setLabelProvider(labelProvider);
	    tree.getViewer().setContentProvider(new ThemeContentProvider());
	    tree.getViewer().setSorter(new ViewerSorter() {
			        /* (non-Javadoc)
			         * @see org.eclipse.jface.viewers.ViewerSorter#category(java.lang.Object)
			         */
			        public int category(Object element) {
			            if (element instanceof ThemeElementCategory)
			                return 0;
			            return 1;
			        }});
		tree.getViewer().setInput(WorkbenchPlugin.getDefault().getThemeRegistry());
		tree.getViewer().addDoubleClickListener(new IDoubleClickListener() {
		    /*
			 * (non-Javadoc)
			 * 
			 * @see org.eclipse.jface.viewers.IDoubleClickListener#doubleClick(org.eclipse.jface.viewers.DoubleClickEvent)
			 */
			public void doubleClick(DoubleClickEvent event) {
				IStructuredSelection s = (IStructuredSelection) event.getSelection();
				Object element = s.getFirstElement();
				if (tree.getViewer().isExpandable(element)) {
				    tree.getViewer().setExpandedState(element, !tree.getViewer().getExpandedState(element));
				} 
			}
		});
	}

    /**
     * @param mainColumn
     */
    private void createPreviewControl(Composite mainColumn) {
        Composite composite = new Composite(mainColumn, SWT.NONE);
        GridData data = new GridData(GridData.FILL_BOTH);
        data.heightHint = 175;
        composite.setLayoutData(data);
        GridLayout layout = new GridLayout(1, true);
        layout.marginHeight = 0;
        layout.marginWidth = 0;
        composite.setLayout(layout);
        
        Label label = new Label(composite, SWT.LEFT);
        label.setText(RESOURCE_BUNDLE.getString("preview")); //$NON-NLS-1$
        myApplyDialogFont(label);
        previewComposite = new Composite(composite, SWT.NONE);
        data = new GridData(GridData.FILL_BOTH);
        previewComposite.setLayoutData(data);
        stackLayout = new StackLayout();
        previewComposite.setLayout(stackLayout);
    }
	
    /* (non-Javadoc)
     * @see org.eclipse.jface.dialogs.IDialogPage#dispose()
     */
    public void dispose() {        
        super.dispose();
        
        workbench.getThemeManager().removePropertyChangeListener(themeChangeListener);
        
        clearPreviews();
        
        colorRegistry.dispose();
        fontRegistry.dispose();
        
    }

	/**
     * Clear all previews.
     */
    private void clearPreviews() {
        if (cascadingTheme != null)
            cascadingTheme.dispose();

        for (Iterator i = previewSet.iterator(); i.hasNext();) {
            IThemePreview preview = (IThemePreview) i.next();
            try {
                preview.dispose();               
            }
            catch (RuntimeException e) {
                WorkbenchPlugin.log(RESOURCE_BUNDLE.getString("errorDisposePreviewLog"), StatusUtil.newStatus(IStatus.ERROR, e.getMessage(), e)); //$NON-NLS-1$
            }
        }
        
        previewSet.clear();
    }

    /**
	 * Get the ancestor of the given color, if any.
	 * 
	 * @param definition the descendant <code>ColorDefinition</code>.
	 * @return the ancestror <code>ColorDefinition</code>, or <code>null</code> 
	 * 		if none.
	 */
	private ColorDefinition getColorAncestor(ColorDefinition definition) {
		String defaultsTo = definition.getDefaultsTo();
		if (defaultsTo == null)
			return null;

		return themeRegistry.findColor(defaultsTo);
	}

	/**
	 * Get the RGB value of the given colors ancestor, if any.
	 * 
	 * @param definition the descendant <code>ColorDefinition</code>.
	 * @return the ancestror <code>RGB</code>, or <code>null</code> if none.
	 */
	private RGB getColorAncestorValue(ColorDefinition definition) {
		ColorDefinition ancestor = getColorAncestor(definition);
		if (ancestor == null)
			return null;

		return getColorValue(ancestor);
	}

	/**
	 * Get the RGB value for the specified definition.  Cascades through 
	 * preferenceToSet, valuesToSet and finally the registry.
	 * 
	 * @param definition the <code>ColorDefinition</code>.
	 * @return the <code>RGB</code> value.
	 */
	private RGB getColorValue(ColorDefinition definition) {
		String id = definition.getId();
		RGB updatedRGB = (RGB) colorPreferencesToSet.get(id);
		if (updatedRGB == null) {
			updatedRGB = (RGB) colorValuesToSet.get(id);
			if (updatedRGB == null)
				updatedRGB = currentTheme.getColorRegistry().getRGB(id);
		}
		return updatedRGB;
	}

	/**
     * @return Return the default "No preview available." preview.
     */
    private Composite getDefaultPreviewControl() { 
        if (defaultPreviewControl == null) {
	        defaultPreviewControl = new Composite(previewComposite, SWT.NONE);
	        defaultPreviewControl.setLayout(new FillLayout());
	        Label l = new Label(defaultPreviewControl, SWT.LEFT);
	        l.setText(RESOURCE_BUNDLE.getString("noPreviewAvailable")); //$NON-NLS-1$
	        myApplyDialogFont(l);
        }
        return defaultPreviewControl;
    }

	/**
	 * Get colors that descend from the provided color.
	 * 
	 * @param definition the ancestor <code>ColorDefinition</code>.
	 * @return the ColorDefinitions that have the provided definition as their 
	 * 		defaultsTo attribute.
	 */
	private ColorDefinition[] getDescendantColors(ColorDefinition definition) {
		List list = new ArrayList(5);
		String id = definition.getId();

		ColorDefinition[] colors = themeRegistry.getColors();
        ColorDefinition[] sorted = new ColorDefinition[colors.length];
		System.arraycopy(colors, 0, sorted, 0, sorted.length);

		Arrays.sort(sorted, new IThemeRegistry.HierarchyComparator(colors));

		for (int i = 0; i < sorted.length; i++) {
			if (id.equals(sorted[i].getDefaultsTo()))
				list.add(sorted[i]);
		}

		return (ColorDefinition[]) list.toArray(new ColorDefinition[list.size()]);
	}

    /**
     * @param definition
     * @return
     */
    private FontDefinition[] getDescendantFonts(FontDefinition definition) {
		List list = new ArrayList(5);
		String id = definition.getId();
		
		FontDefinition[] fonts = themeRegistry.getFonts();
        FontDefinition[] sorted = new FontDefinition[fonts.length];
		System.arraycopy(fonts, 0, sorted, 0, sorted.length);

		Arrays.sort(sorted, new IThemeRegistry.HierarchyComparator(fonts));

		for (int i = 0; i < sorted.length; i++) {
			if (id.equals(sorted[i].getDefaultsTo()))
				list.add(sorted[i]);
		}

		return (FontDefinition[]) list.toArray(new FontDefinition[list.size()]);
    }

    /**
     * @param definition
     * @return
     */
    private FontDefinition getFontAncestor(FontDefinition definition) {
		String defaultsTo = definition.getDefaultsTo();
		if (defaultsTo == null)
			return null;

		return themeRegistry.findFont(defaultsTo);
    }

    /**
     * @param definition
     * @return
     */
    private FontData[] getFontAncestorValue(FontDefinition definition) {
		FontDefinition ancestor = getFontAncestor(definition);
		if (ancestor == null)
			return PreferenceConverter.getDefaultFontDataArray(getPreferenceStore(), ThemeElementHelper.createPreferenceKey(currentTheme, definition.getId()));

		return getFontValue(ancestor);
    }

    /**
     * @param definition
     * @return
     */
    protected FontData[] getFontValue(FontDefinition definition) {
		String id = definition.getId();
		FontData [] updatedFD = (FontData []) fontPreferencesToSet.get(id);
		if (updatedFD == null) {
			updatedFD = (FontData []) fontValuesToSet.get(id);
			if (updatedFD == null)
				updatedFD = currentTheme.getFontRegistry().getFontData(id);
		}
		return updatedFD;
    }

    /**
     * @return
     */
    protected ColorDefinition getSelectedColorDefinition() {
        Object o = ((IStructuredSelection)tree.getViewer().getSelection()).getFirstElement();
        if (o instanceof ColorDefinition)
            return (ColorDefinition) o;
        return null;        
    }    
    
    /**
     * @return
     */
    protected FontDefinition getSelectedFontDefinition() {
        Object o = ((IStructuredSelection)tree.getViewer().getSelection()).getFirstElement();
        if (o instanceof FontDefinition)
            return (FontDefinition) o;
        return null;        
    }

	/**
	 * Hook all control listeners.
	 */
	private void hookListeners() {	    
		colorSelector.addListener(new IPropertyChangeListener() {

			/* (non-Javadoc)
			 * @see org.eclipse.jface.util.IPropertyChangeListener#propertyChange(org.eclipse.jface.util.PropertyChangeEvent)
			 */
			public void propertyChange(PropertyChangeEvent event) {
				ColorDefinition definition = getSelectedColorDefinition();

				RGB newRGB = (RGB) event.getNewValue();
				if (definition != null && newRGB != null && !newRGB.equals(event.getOldValue())) {
					setColorPreferenceValue(definition, newRGB);
					setRegistryValue(definition, newRGB);
				}

				updateColorControls(definition);
			}
		});

		tree.getViewer().addSelectionChangedListener(new ISelectionChangedListener() {

			/* (non-Javadoc)
			 * @see org.eclipse.jface.viewers.ISelectionChangedListener#selectionChanged(org.eclipse.jface.viewers.SelectionChangedEvent)
			 */
			public void selectionChanged(SelectionChangedEvent event) {
				if (event.getSelection().isEmpty()) {
					swapNoControls();
					updateColorControls(null);
					updateCategorySelection(null);
				} else {
					Object element = ((IStructuredSelection) event.getSelection())
							.getFirstElement();
					if (element instanceof ThemeElementCategory) {
					    swapNoControls();
						String description = ((ThemeElementCategory)element).getDescription();
						descriptionText.setText(description == null ? "" : description); //$NON-NLS-1$
					    updateCategorySelection((ThemeElementCategory) element);
					}
					else if (element instanceof ColorDefinition) {
						updateColorControls((ColorDefinition) element);
						swapColorControls();
						updateCategorySelection(WorkbenchPlugin.getDefault().getThemeRegistry().findCategory(((ColorDefinition) element).getCategoryId()));
					}
					else if (element instanceof FontDefinition) {
						updateFontControls((FontDefinition) element);
						swapFontControls();
						updateCategorySelection(WorkbenchPlugin.getDefault().getThemeRegistry().findCategory(((FontDefinition) element).getCategoryId()));
					}					
				}
			}
		});
		

		colorResetButton.addSelectionListener(new SelectionAdapter() {

			/* (non-Javadoc)
			 * @see org.eclipse.swt.events.SelectionListener#widgetSelected(org.eclipse.swt.events.SelectionEvent)
			 */
			public void widgetSelected(SelectionEvent e) {
				ColorDefinition definition = getSelectedColorDefinition();
				if (resetColor(definition))
					updateColorControls(definition);
			}
		});
		
		fontResetButton.addSelectionListener(new SelectionAdapter() {

			/* (non-Javadoc)
			 * @see org.eclipse.swt.events.SelectionListener#widgetSelected(org.eclipse.swt.events.SelectionEvent)
			 */
			public void widgetSelected(SelectionEvent e) {
				FontDefinition definition = getSelectedFontDefinition();
				if (resetFont(definition))
					updateFontControls(definition);
			}
		});
		
		
		fontChangeButton.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				FontDefinition definition = getSelectedFontDefinition();
				if (definition != null) {
					FontDialog fontDialog =
						new FontDialog(fontChangeButton.getShell());
					FontData[] currentData = getFontValue(definition);
					fontDialog.setFontList(currentData);
					if (fontDialog.open() != null) {
						setFontPreferenceValue(definition, fontDialog.getFontList());
						setRegistryValue(definition, fontDialog.getFontList());
					}
					
					updateFontControls(definition);					
				}
			}
		});
		
		fontSystemButton.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				FontDefinition definition = getSelectedFontDefinition();
				if (definition != null) {
					FontData[] defaultFontData =
						JFaceResources.getDefaultFont().getFontData();
					setFontPreferenceValue(definition, defaultFontData);
					setRegistryValue(definition, defaultFontData);
					
					updateFontControls(definition);
				}
			}
		});
	}

    /* (non-Javadoc)
	 * @see org.eclipse.ui.IWorkbenchPreferencePage#init(org.eclipse.ui.IWorkbench)
	 */
	public void init(IWorkbench aWorkbench) {
	    this.workbench = (Workbench)aWorkbench;
		setPreferenceStore(aWorkbench.getPreferenceStore());
		
		final IThemeManager themeManager = aWorkbench.getThemeManager();
        
		themeChangeListener = new IPropertyChangeListener() {

            /* (non-Javadoc)
             * @see org.eclipse.jface.util.IPropertyChangeListener#propertyChange(org.eclipse.jface.util.PropertyChangeEvent)
             */
            public void propertyChange(PropertyChangeEvent event) {
                if (event.getProperty().equals(IThemeManager.CHANGE_CURRENT_THEME)) {
                    updateThemeInfo(themeManager); 
                    refreshCategory();
                }
            }};
        themeManager.addPropertyChangeListener(themeChangeListener);
		
		updateThemeInfo(themeManager);
	}
	
	private void updateThemeInfo(IThemeManager manager) {
	    clearPreviews();
	    categoryMap.clear();
	    
	    if (labelProvider != null)
	        labelProvider.dispose(); // nuke the old cache
	    
        if (colorRegistry != null)
            colorRegistry.dispose();
        if (fontRegistry != null)                    
            fontRegistry.dispose();
        
        currentTheme = manager.getCurrentTheme();
        
        colorRegistry = new CascadingColorRegistry(
	            currentTheme.getColorRegistry());
	    fontRegistry = new CascadingFontRegistry(
	            currentTheme.getFontRegistry());

	    fontPreferencesToSet.clear();
	    fontValuesToSet.clear();
	    
	    colorPreferencesToSet.clear();
	    colorValuesToSet.clear();
	    
	    if (labelProvider != null)
	        labelProvider.hookListeners(); // rehook the listeners	    
	}

	/**
	 * Answers whether the definition is currently set to the default value.
	 * 
	 * @param definition the <code>ColorDefinition</code> to check.
	 * @return Return whether the definition is currently mapped to the default 
	 * 		value, either in the preference store or in the local change record 
	 * 		of this preference page.
	 */
	private boolean isDefault(ColorDefinition definition) {
		String id = definition.getId();

		if (colorPreferencesToSet.containsKey(id)) {
			if (definition.getValue() != null) { // value-based color
				if (colorPreferencesToSet
					.get(id)
					.equals(definition.getValue()))
					return true;
			} else {
				if (colorPreferencesToSet.get(id).equals(getColorAncestorValue(definition)))
					return true;
			}
		} else {
			if (definition.getValue() != null) { // value-based color
				if (getPreferenceStore().isDefault(ThemeElementHelper.createPreferenceKey(currentTheme, id)))
					return true;
			} else {
				// a descendant is default if it's the same value as its ancestor
				if (getColorValue(definition).equals(getColorAncestorValue(definition)))
					return true;
			}
		}
		return false;
	}

    /**
     * @param definition
     * @return
     */
    private boolean isDefault(FontDefinition definition) {
		String id = definition.getId();

		if (fontPreferencesToSet.containsKey(id)) {
			if (definition.getValue() != null) { // value-based font
				if (Arrays.equals((FontData[]) fontPreferencesToSet.get(id), 
				        definition.getValue()))
					return true;
			} else {
			    FontData [] ancestor = getFontAncestorValue(definition);
			    if (Arrays.equals((FontData[]) fontPreferencesToSet.get(id), ancestor))
					return true;
			}
		} else {
			if (definition.getValue() != null) { // value-based font
				if (getPreferenceStore().isDefault(ThemeElementHelper.createPreferenceKey(currentTheme, id)))
					return true;
			} else {
			    FontData [] ancestor = getFontAncestorValue(definition);
			    if (ancestor == null)
			        return true;
			    
				// a descendant is default if it's the same value as its ancestor
				if (Arrays.equals(getFontValue(definition), ancestor))
					return true;
			}
		}
		return false;
    }
	
	/**
	 * Apply the dialog font to the control and store 
	 * it for later so that it can be used for a later
	 * update.
	 * @param control
	 */
	private void myApplyDialogFont(Control control) {
		control.setFont(JFaceResources.getDialogFont());
		dialogFontWidgets.add(control);
	}	
    
	/**
	 * @see org.eclipse.jface.preference.PreferencePage#performApply()
	 */
	protected void performApply() {
		super.performApply();

		//Apply the default font to the dialog.
		Font oldFont = appliedDialogFont;
		
		FontDefinition fontDefinition = themeRegistry.findFont(JFaceResources.DIALOG_FONT);
		if (fontDefinition == null)
			return;
			
        FontData[] newData =
			getFontValue(fontDefinition);

		appliedDialogFont = new Font(getControl().getDisplay(), newData);

		updateForDialogFontChange(appliedDialogFont);
		getApplyButton().setFont(appliedDialogFont);
		getDefaultsButton().setFont(appliedDialogFont);

		if (oldFont != null)
			oldFont.dispose();
	}

    /**
     * 
     */
    private void performColorDefaults() {
        ColorDefinition[] definitions = themeRegistry.getColors();

		// apply defaults in depth-order.
		ColorDefinition[] definitionsCopy = new ColorDefinition[definitions.length];
		System.arraycopy(definitions, 0, definitionsCopy, 0, definitions.length);

		Arrays.sort(definitionsCopy, new IThemeRegistry.HierarchyComparator(definitions));

		for (int i = 0; i < definitionsCopy.length; i++)
			resetColor(definitionsCopy[i]);

		updateColorControls(getSelectedColorDefinition());
    }

    /**
     * @return
     */
    private boolean performColorOk() {
        for (Iterator i = colorPreferencesToSet.keySet().iterator(); i.hasNext();) {
			String id = (String) i.next();
			String key = ThemeElementHelper.createPreferenceKey(currentTheme, id);
			RGB rgb = (RGB) colorPreferencesToSet.get(id);
			String rgbString = StringConverter.asString(rgb);
			String storeString = getPreferenceStore().getString(key);

			if (!rgbString.equals(storeString)) {
		        getPreferenceStore().setValue(key, rgbString);
			}
		}

        colorValuesToSet.clear();
		colorPreferencesToSet.clear();
		return true;
    }

	/* (non-Javadoc)
	 * @see org.eclipse.jface.preference.PreferencePage#performDefaults()
	 */
	protected void performDefaults() {
		performColorDefaults();		
		performFontDefaults();
	}

	/**
     * 
     */
    private void performFontDefaults() {
		FontDefinition[] definitions = themeRegistry.getFonts();

		// apply defaults in depth-order.
		FontDefinition[] definitionsCopy = new FontDefinition[definitions.length];
		System.arraycopy(definitions, 0, definitionsCopy, 0, definitions.length);

		Arrays.sort(definitionsCopy, new IThemeRegistry.HierarchyComparator(definitions));

		for (int i = 0; i < definitionsCopy.length; i++)
			resetFont(definitionsCopy[i]);

		updateFontControls(getSelectedFontDefinition());		
    }

	/**
     * @return
     */
    private boolean performFontOk() {
        for (Iterator i = fontPreferencesToSet.keySet().iterator(); i.hasNext();) {
			String id = (String) i.next();
			String key = ThemeElementHelper.createPreferenceKey(currentTheme, id);
			FontData [] fd = (FontData []) fontPreferencesToSet.get(id);
			
			String fdString = PreferenceConverter.getStoredRepresentation(fd);
			String storeString = getPreferenceStore().getString(key);

			if (!fdString.equals(storeString)) {
		        getPreferenceStore().setValue(key, fdString);
			}
		}

        fontValuesToSet.clear();
        fontPreferencesToSet.clear();		
		return true;
    }
    
    /* (non-Javadoc)
	 * @see org.eclipse.jface.preference.IPreferencePage#performOk()
	 */
	public boolean performOk() {
		return performColorOk() && performFontOk();
	}
	
    /**
     * Refreshes the category.
     */
    private void refreshCategory() {        
        updateColorControls(null);
        updateFontControls(null);
    }
	

    /**
	 * Resets the supplied definition to its default value.
	 * 
	 * @param definition the <code>ColorDefinition</code> to reset.
	 * @return whether any change was made.
	 */
	private boolean resetColor(ColorDefinition definition) {
		if (!isDefault(definition)) {

			RGB newRGB;
			if (definition.getValue() != null) {
				newRGB = definition.getValue();
			} else {
				newRGB = getColorAncestorValue(definition);
			}

			if (newRGB != null) {
				setColorPreferenceValue(definition, newRGB);
				setRegistryValue(definition, newRGB);
				return true;
			}
		}
		return false;
	}

	/**
     * @param definition
     * @return
     */
    protected boolean resetFont(FontDefinition definition) {
		if (!isDefault(definition)) {

			FontData [] newFD;
			if (definition.getDefaultsTo() != null) {
				newFD = getFontAncestorValue(definition);
			} 
			else {
				newFD = PreferenceConverter.getDefaultFontDataArray(
						getPreferenceStore(),
						ThemeElementHelper.createPreferenceKey(currentTheme, definition.getId()));
			}

			if (newFD != null) {
				setFontPreferenceValue(definition, newFD);
				setRegistryValue(definition, newFD);
				return true;
			}
		}
		return false;
    }

    /**
	 * Set the value (in preferences) for the given color.  
	 * 
	 * @param definition the <code>ColorDefinition</code> to set.
	 * @param newRGB the new <code>RGB</code> value for the definitions 
	 * 		identifier.
	 */
	protected void setColorPreferenceValue(ColorDefinition definition, RGB newRGB) {
		setDescendantRegistryValues(definition, newRGB);
		colorPreferencesToSet.put(definition.getId(), newRGB);
	}

	/**
	 * Set the value (in registry) for the given colors children.  
	 * 
	 * @param definition the <code>ColorDefinition</code> whos children should 
	 * 		be set.
	 * @param newRGB the new <code>RGB</code> value for the definitions 
	 * 		identifier.
	 */
	private void setDescendantRegistryValues(ColorDefinition definition, RGB newRGB) {
		ColorDefinition[] children = getDescendantColors(definition);

		for (int i = 0; i < children.length; i++) {
			if (isDefault(children[i])) {
			    setDescendantRegistryValues(children[i], newRGB);
			    setRegistryValue(children[i], newRGB);
				colorValuesToSet.put(children[i].getId(), newRGB);				
			}
		}
	}

    /**
     * @param definition
     * @param datas
     */
    private void setDescendantRegistryValues(FontDefinition definition, FontData[] datas) {
		FontDefinition[] children = getDescendantFonts(definition);

		for (int i = 0; i < children.length; i++) {
			if (isDefault(children[i])) {
			    setDescendantRegistryValues(children[i], datas);
			    setRegistryValue(children[i], datas);
				fontValuesToSet.put(children[i].getId(), datas);				
			}
		}        
    }

    /**
     * @param definition
     * @param datas
     */
    protected void setFontPreferenceValue(FontDefinition definition, FontData[] datas) {
		setDescendantRegistryValues(definition, datas);
		fontPreferencesToSet.put(definition.getId(), datas);
    }
	
	/**
	 * Updates the working registry.
	 * @param definition
	 * @param newRGB
	 */
    protected void setRegistryValue(ColorDefinition definition, RGB newRGB) {
        colorRegistry.put(definition.getId(), newRGB);
    }	

    /**
     * @param definition
     * @param datas
     */
    protected void setRegistryValue(FontDefinition definition, FontData[] datas) {
		fontRegistry.put(definition.getId(), datas);        
    }

    /**
     * Swap in the color selection controls.
     */
    protected void swapColorControls() {    
        controlAreaLayout.topControl = colorControls; 
        controlArea.layout();
    }

    /**
     * Swap in the font selection controls.
     */
    protected void swapFontControls() {
        controlAreaLayout.topControl = fontControls;
        controlArea.layout();        
    }

    /**
     * Swap in no controls (empty the control area)
     */    
    protected void swapNoControls() {
        controlAreaLayout.topControl = null;
        controlArea.layout();        
    }

	/**
	 * Set the color list.
	 * @param category the category to use.
	 */
	private void updateCategorySelection(ThemeElementCategory category) {

	    largestFontWidth = 0;
		
		Composite previewControl = (Composite) previewMap.get(category);
		if (previewControl == null) {
		    if (category != null) {
		        try {
		            IThemePreview preview = getThemePreview(category);
                    if (preview != null) {
	                    previewControl = new Composite(previewComposite, SWT.NONE);
	                    previewControl.setLayout(new FillLayout());
	                    ITheme theme = getCascadingTheme();
	                    preview.createControl(previewControl, theme);
	                    previewSet.add(preview);
                    }
                } catch (CoreException e) {
                    previewControl = new Composite(previewComposite, SWT.NONE);
                    previewControl.setLayout(new FillLayout());
                    myApplyDialogFont(previewControl);
                    Text error = new Text(previewControl, SWT.WRAP | SWT.READ_ONLY);
                    error.setText(RESOURCE_BUNDLE.getString("errorCreatingPreview")); //$NON-NLS-1$
                    WorkbenchPlugin.log(RESOURCE_BUNDLE.getString("errorCreatePreviewLog"), StatusUtil.newStatus(IStatus.ERROR, e.getMessage(), e)); //$NON-NLS-1$
                }		                        
		    }
		}
		if (previewControl == null) {
		    previewControl = getDefaultPreviewControl();
		}
		previewMap.put(category, previewControl);
		stackLayout.topControl = previewControl;
		previewComposite.layout();
	}

	/**
     * @param category the category
     * @return the preview for the category, or its ancestors preview if it does not have one.
     */
    private IThemePreview getThemePreview(ThemeElementCategory category) throws CoreException {
        IThemePreview preview = category.createPreview();
        if (preview != null)
            return preview;
        
        if (category.getParentId() != null) {
            int idx = Arrays.binarySearch(themeRegistry.getCategories(), category.getParentId(), IThemeRegistry.ID_COMPARATOR);
            if (idx >= 0) 
                return getThemePreview(themeRegistry.getCategories()[idx]);
        }

        return null;
    }

    /**
     * @return
     */
    private ITheme getCascadingTheme() {
        if (cascadingTheme == null)
            cascadingTheme = new CascadingTheme(currentTheme, colorRegistry, fontRegistry);
        return cascadingTheme;
    }

    /**
	 * Update the color controls based on the supplied definition.
	 * 
	 * @param definition The currently selected <code>ColorDefinition</code>.
	 */
	protected void updateColorControls(ColorDefinition definition) {
	    if (definition == null) {
			colorResetButton.setEnabled(false);
			colorSelector.setEnabled(false);
			descriptionText.setText(""); //$NON-NLS-1$
			return;
	    }
	    
		colorSelector.setColorValue(getColorValue(definition));

		colorResetButton.setEnabled(!isDefault(definition));
		colorSelector.setEnabled(true);
		String description = definition.getDescription();
		descriptionText.setText(description == null ? "" : description); //$NON-NLS-1$		
	}

    protected void updateFontControls(FontDefinition definition) {
        if (definition == null) {
		    fontSystemButton.setEnabled(false);
			fontResetButton.setEnabled(false);
			fontChangeButton.setEnabled(false);
			descriptionText.setText(""); //$NON-NLS-1$
			return;
        }
        
	    fontSystemButton.setEnabled(true);
		fontResetButton.setEnabled(!isDefault(definition));
		fontChangeButton.setEnabled(true);
		String description = definition.getDescription();
		descriptionText.setText(description == null ? "" : description); //$NON-NLS-1$		
    }
    
	/**
	 * Update for a change in the dialog font.
	 * @param newFont
	 */
	private void updateForDialogFontChange(Font newFont) {
		Iterator iterator = dialogFontWidgets.iterator();
		while (iterator.hasNext()) {
			((Control) iterator.next()).setFont(newFont);
		}
		
		//recalculate the fonts for the tree
		labelProvider.clearFontCache();
	}    
}
