/*******************************************************************************
 * Copyright (c) 2000, 2018 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *     Anton Leherbauer (Wind River Systems) - https://bugs.eclipse.org/bugs/show_bug.cgi?id=22712
 *******************************************************************************/
package org.eclipse.ui.internal.editors.text;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Link;
import org.eclipse.swt.widgets.List;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Spinner;
import org.eclipse.swt.widgets.Text;

import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IStatus;

import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.DialogPage;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.IMessageProvider;
import org.eclipse.jface.layout.PixelConverter;
import org.eclipse.jface.preference.ColorSelector;
import org.eclipse.jface.preference.IPreferenceStore;
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.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPreferencePage;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.dialogs.PreferencesUtil;
import org.eclipse.ui.internal.editors.text.OverlayPreferenceStore.OverlayKey;
import org.eclipse.ui.internal.editors.text.TextEditorDefaultsPreferencePage.EnumeratedDomain.EnumValue;

import org.eclipse.ui.texteditor.AbstractDecoratedTextEditorPreferenceConstants;
import org.eclipse.ui.texteditor.AbstractTextEditor;

import org.eclipse.ui.editors.text.ITextEditorHelpContextIds;


/**
 * The preference page for setting the editor options.
 * <p>
 * This class is internal and not intended to be used by clients.</p>
 *
 * @since 3.1
 */
public class TextEditorDefaultsPreferencePage extends PreferencePage implements IWorkbenchPreferencePage {

	private static abstract class Initializer {

		protected final Preference fPreference;

		protected Initializer(Preference preference) {
			fPreference= preference;
		}

		public abstract void initialize();
	}


	public static final class InitializerFactory {

		private final IPreferenceStore fPreferenceStore;

		public InitializerFactory(IPreferenceStore preferenceStore) {
			fPreferenceStore= preferenceStore;
		}

		private class TextInitializer extends Initializer {
			private final Text fText;

			public TextInitializer(Preference preference, Text control) {
				super(preference);
				fText= control;
			}
			@Override
			public void initialize() {
				String value= fPreferenceStore.getString(fPreference.getKey());
				fText.setText(value);
			}
		}

		private class CheckboxInitializer extends Initializer {
			private final Button fControl;

			public CheckboxInitializer(Preference preference, Button control) {
				super(preference);
				fControl= control;
			}
			@Override
			public void initialize() {
				boolean value= fPreferenceStore.getBoolean(fPreference.getKey());
				fControl.setSelection(value);
			}
		}

		private class ComboInitializer extends Initializer {
			private final Combo fControl;
			private final EnumeratedDomain fDomain;

			public ComboInitializer(Preference preference, Combo control, EnumeratedDomain domain) {
				super(preference);
				fControl= control;
				fDomain= domain;
			}
			@Override
			public void initialize() {
				int value= fPreferenceStore.getInt(fPreference.getKey());
				EnumValue enumValue= fDomain.getValueByInteger(value);
				if (enumValue != null) {
					int index= fDomain.getIndex(enumValue);
					if (index >= 0)
						fControl.select(index);
				}
			}
		}

		private class SpinnerInitializer extends Initializer {
			private final Spinner fControl;
			private final EnumeratedDomain fDomain;

			public SpinnerInitializer(Preference preference, Spinner control, EnumeratedDomain domain) {
				super(preference);
				fControl= control;
				fDomain= domain;
			}
			@Override
			public void initialize() {
				int value= fPreferenceStore.getInt(fPreference.getKey());
				EnumValue enumValue= fDomain.getValueByInteger(value);
				if (enumValue != null) {
					fControl.setSelection(value);
				}
			}
		}

		public Initializer create(Preference preference, Text control) {
			return new TextInitializer(preference, control);
		}

		public Initializer create(Preference preference, Button control) {
			return new CheckboxInitializer(preference, control);
		}

		public Initializer create(Preference preference, Combo control, EnumeratedDomain domain) {
			return new ComboInitializer(preference, control, domain);
		}

		public Initializer create(Preference preference, Spinner control, EnumeratedDomain domain) {
			return new SpinnerInitializer(preference, control, domain);
		}
	}


	abstract static class Domain {
		public abstract IStatus validate(Object value);
		protected int parseInteger(Object val) throws NumberFormatException {
			if (val instanceof Integer) {
				return ((Integer) val).intValue();
			}
			if (val instanceof String) {
				return Integer.parseInt((String) val);
			}
			throw new NumberFormatException(NLSUtility.format(TextEditorMessages.TextEditorPreferencePage_invalidInput, String.valueOf(val)));
		}
	}

	static class IntegerDomain extends Domain {
		private final int fMax;
		private final int fMin;
		public IntegerDomain(int min, int max) {
			Assert.isLegal(max >= min);
			fMax= max;
			fMin= min;
		}

		@Override
		public IStatus validate(Object value) {
			StatusInfo status= new StatusInfo();
			if (value instanceof String && ((String)value).length() == 0) {
				status.setError(TextEditorMessages.TextEditorPreferencePage_emptyInput);
				return status;
			}
			try {
				int integer= parseInteger(value);
				if (!rangeCheck(integer))
					status.setError(NLSUtility.format(TextEditorMessages.TextEditorPreferencePage_invalidInput, String.valueOf(integer)));
			} catch (NumberFormatException e) {
					status.setError(NLSUtility.format(TextEditorMessages.TextEditorPreferencePage_invalidInput, String.valueOf(value)));
			}
			return status;
		}

		protected boolean rangeCheck(int i) {
			return (i >= fMin && i <= fMax);
		}

	}

	static class EnumeratedDomain extends Domain {
		public final static class EnumValue {
			private final int fValue;
			private final String fName;
			public EnumValue(int value) {
				this(value, null);
			}
			public EnumValue(int value, String name) {
				fValue= value;
				fName= name;
			}
			public String getLabel() {
				return fName == null ? String.valueOf(fValue) : fName;
			}
			public int getIntValue() {
				return fValue;
			}
			@Override
			public final int hashCode() {
				return getIntValue();
			}
			@Override
			public boolean equals(Object obj) {
				if (obj instanceof EnumValue) {
					return ((EnumValue) obj).getIntValue() == fValue;
				}
				return false;
			}
		}

		private final java.util.List<EnumValue> fItems= new ArrayList<>();
		private final Set<EnumValue> fValueSet= new HashSet<>();

		public void addValue(EnumValue val) {
			if (fValueSet.contains(val))
				fItems.remove(val);
			fItems.add(val);
			fValueSet.add(val);
		}

		public int getIndex(EnumValue enumValue) {
			int i= 0;
			for (Iterator<EnumValue> it= fItems.iterator(); it.hasNext();) {
				EnumValue ev= it.next();
				if (ev.equals(enumValue))
					return i;
				i++;
			}
			return -1;
		}

		public EnumValue getValueByIndex (int index) {
			if (index >= 0 && fItems.size() > index)
				return fItems.get(index);
			return null;
		}

		public EnumValue getValueByInteger(int intValue) {
			for (Iterator<EnumValue> it= fItems.iterator(); it.hasNext();) {
				EnumValue e= it.next();
				if (e.getIntValue() == intValue)
					return e;
			}
			return null;
		}

		public void addValue(int val) {
			addValue(new EnumValue(val));
		}

		public void addRange(int from, int to) {
			while (from <= to)
				addValue(from++);
		}

		@Override
		public IStatus validate(Object value) {
			StatusInfo status= new StatusInfo();
			if (value instanceof String && ((String)value).length() == 0) {
				status.setError(TextEditorMessages.TextEditorPreferencePage_emptyInput);
				return status;
			}
			try {
				EnumValue e= parseEnumValue(value);
				if (!fValueSet.contains(e))
					status.setError(NLSUtility.format(TextEditorMessages.TextEditorPreferencePage_invalidRange, new String[] {getValueByIndex(0).getLabel(), getValueByIndex(fItems.size() - 1).getLabel()}));
			} catch (NumberFormatException e) {
				status.setError(NLSUtility.format(TextEditorMessages.TextEditorPreferencePage_invalidInput, String.valueOf(value)));
			}

			return status;
		}

		private EnumValue parseEnumValue(Object value) {
			if (value instanceof EnumValue)
				return (EnumValue) value;
			int integer= parseInteger(value);
			return getValueByInteger(integer);
		}

		public EnumValue getMinimumValue() {
			return getValueByIndex(0);
		}

		public EnumValue getMaximumValue() {
			return getValueByIndex(fItems.size() - 1);
		}
	}

	static class BooleanDomain extends Domain {
		@Override
		public IStatus validate(Object value) {
			StatusInfo status= new StatusInfo();
			if (value instanceof String && ((String)value).length() == 0) {
				status.setError(TextEditorMessages.TextEditorPreferencePage_emptyInput);
				return status;
			}
			try {
				parseBoolean(value);
			} catch (NumberFormatException e) {
				status.setError(NLSUtility.format(TextEditorMessages.TextEditorPreferencePage_invalidInput, String.valueOf(value)));
			}

			return status;
		}

		private boolean parseBoolean(Object value) throws NumberFormatException {
			if (value instanceof Boolean)
				return ((Boolean) value).booleanValue();

			if (value instanceof String) {
				if (Boolean.TRUE.toString().equalsIgnoreCase((String) value))
					return true;
				if (Boolean.FALSE.toString().equalsIgnoreCase((String) value))
					return false;
			}

			throw new NumberFormatException(NLSUtility.format(TextEditorMessages.TextEditorPreferencePage_invalidInput, String.valueOf(value)));
		}
	}

	private static class Preference {
		private String fKey;
		private String fName;
		private String fDescription; // for tooltips

		public Preference(String key, String name, String description) {
			Assert.isNotNull(key);
			Assert.isNotNull(name);
			fKey= key;
			fName= name;
			fDescription= description;
		}
		public final String getKey() {
			return fKey;
		}
		public final String getName() {
			return fName;
		}
		public final String getDescription() {
			return fDescription;
		}
	}

	private static class WhitespaceCharacterPainterOptionsDialog extends Dialog {

		private java.util.List<Initializer> fDialogInitializers= new ArrayList<>();

		private OverlayPreferenceStore fDialogOverlayStore;

		private final IPreferenceStore fParentPreferenceStore;

		private InitializerFactory fDialogInitializerFactory;

		private Text errorMessageText;

		protected WhitespaceCharacterPainterOptionsDialog(Shell parentShell, IPreferenceStore parent) {
			super(parentShell);
			fParentPreferenceStore= parent;
			fDialogOverlayStore= createDialogOverlayStore();
			fDialogInitializerFactory= new InitializerFactory(fDialogOverlayStore);
		}

		private OverlayPreferenceStore createDialogOverlayStore() {
			ArrayList<OverlayKey> overlayKeys= new ArrayList<>();

			overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SHOW_LEADING_SPACES));
			overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SHOW_ENCLOSED_SPACES));
			overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SHOW_TRAILING_SPACES));
			overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SHOW_LEADING_IDEOGRAPHIC_SPACES));
			overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SHOW_ENCLOSED_IDEOGRAPHIC_SPACES));
			overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SHOW_TRAILING_IDEOGRAPHIC_SPACES));
			overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SHOW_LEADING_TABS));
			overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SHOW_ENCLOSED_TABS));
			overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SHOW_TRAILING_TABS));
			overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SHOW_CARRIAGE_RETURN));
			overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SHOW_LINE_FEED));
			overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.INT, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_WHITESPACE_CHARACTER_ALPHA_VALUE));

			OverlayPreferenceStore.OverlayKey[] keys= new OverlayPreferenceStore.OverlayKey[overlayKeys.size()];
			overlayKeys.toArray(keys);
			return new OverlayPreferenceStore(fParentPreferenceStore, keys);
		}

		@Override
		protected void configureShell(Shell newShell) {
			super.configureShell(newShell);
			newShell.setText(TextEditorMessages.TextEditorDefaultsPreferencePage_showWhitespaceCharactersDialogTitle);
		}

		@Override
		protected Control createContents(Composite parent) {
			Control contents= super.createContents(parent);
			Dialog.applyDialogFont(contents);
			fDialogOverlayStore.load();
			fDialogOverlayStore.start();
			initializeShowWhitespaceCharactersPreferences();
			return contents;
		}

		private void initializeShowWhitespaceCharactersPreferences() {
			for (Iterator<Initializer> it= fDialogInitializers.iterator(); it.hasNext();) {
				Initializer initializer= it.next();
				initializer.initialize();
			}
		}

		@Override
		protected Control createDialogArea(Composite parent) {
			Composite composite= (Composite)super.createDialogArea(parent);

			Label description= new Label(composite, SWT.NONE);
			description.setText(TextEditorMessages.TextEditorDefaultsPreferencePage_configureWhitespaceCharacterPainterProperties);
			description.setLayoutData(new GridData(SWT.BEGINNING, SWT.BEGINNING, false, false));

			Composite tabularComposite= new Composite(composite, SWT.NONE);
			GridLayout layout= new GridLayout();
			layout.numColumns= 4;
			layout.marginHeight= 0;
			layout.marginWidth= 0;
			layout.makeColumnsEqualWidth= true;
			tabularComposite.setLayout(layout);

			Label label;
			Button checkbox;
			Preference preference;

			label= new Label(tabularComposite, SWT.NONE);
			label.setText(""); //$NON-NLS-1$
			label.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, false, false));

			label= new Label(tabularComposite, SWT.NONE);
			label.setText(TextEditorMessages.TextEditorDefaultsPreferencePage_leading);
			label.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, false, false));

			label= new Label(tabularComposite, SWT.NONE);
			label.setText(TextEditorMessages.TextEditorDefaultsPreferencePage_enclosed);
			label.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, false, false));

			label= new Label(tabularComposite, SWT.NONE);
			label.setText(TextEditorMessages.TextEditorDefaultsPreferencePage_trailing);
			label.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, false, false));

			label= new Label(tabularComposite, SWT.NONE);
			label.setText(TextEditorMessages.TextEditorDefaultsPreferencePage_space);
			label.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false));
			preference= new Preference(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SHOW_LEADING_SPACES, "", null); //$NON-NLS-1$
			addCheckBox(tabularComposite, preference, new BooleanDomain(), 0);
			preference= new Preference(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SHOW_ENCLOSED_SPACES, "", null); //$NON-NLS-1$
			addCheckBox(tabularComposite, preference, new BooleanDomain(), 0);
			preference= new Preference(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SHOW_TRAILING_SPACES, "", null); //$NON-NLS-1$
			addCheckBox(tabularComposite, preference, new BooleanDomain(), 0);

			label= new Label(tabularComposite, SWT.NONE);
			label.setText(TextEditorMessages.TextEditorDefaultsPreferencePage_ideographicSpace);
			label.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false));
			preference= new Preference(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SHOW_LEADING_IDEOGRAPHIC_SPACES, "", null); //$NON-NLS-1$
			addCheckBox(tabularComposite, preference, new BooleanDomain(), 0);
			preference= new Preference(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SHOW_ENCLOSED_IDEOGRAPHIC_SPACES, "", null); //$NON-NLS-1$
			addCheckBox(tabularComposite, preference, new BooleanDomain(), 0);
			preference= new Preference(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SHOW_TRAILING_IDEOGRAPHIC_SPACES, "", null); //$NON-NLS-1$
			addCheckBox(tabularComposite, preference, new BooleanDomain(), 0);

			label= new Label(tabularComposite, SWT.NONE);
			label.setText(TextEditorMessages.TextEditorDefaultsPreferencePage_tab);
			label.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false));
			preference= new Preference(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SHOW_LEADING_TABS, "", null); //$NON-NLS-1$
			addCheckBox(tabularComposite, preference, new BooleanDomain(), 0);
			preference= new Preference(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SHOW_ENCLOSED_TABS, "", null); //$NON-NLS-1$
			addCheckBox(tabularComposite, preference, new BooleanDomain(), 0);
			preference= new Preference(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SHOW_TRAILING_TABS, "", null); //$NON-NLS-1$
			addCheckBox(tabularComposite, preference, new BooleanDomain(), 0);

			label= new Label(tabularComposite, SWT.NONE);
			label.setText(TextEditorMessages.TextEditorDefaultsPreferencePage_carriageReturn);
			label.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false));
			checkbox= new Button(tabularComposite, SWT.CHECK);
			checkbox.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, false, false));
			checkbox.setEnabled(false);
			checkbox= new Button(tabularComposite, SWT.CHECK);
			checkbox.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, false, false));
			checkbox.setEnabled(false);
			preference= new Preference(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SHOW_CARRIAGE_RETURN, "", null); //$NON-NLS-1$
			addCheckBox(tabularComposite, preference, new BooleanDomain(), 0);

			label= new Label(tabularComposite, SWT.NONE);
			label.setText(TextEditorMessages.TextEditorDefaultsPreferencePage_lineFeed);
			label.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false));
			checkbox= new Button(tabularComposite, SWT.CHECK);
			checkbox.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, false, false));
			checkbox.setEnabled(false);
			checkbox= new Button(tabularComposite, SWT.CHECK);
			checkbox.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, false, false));
			checkbox.setEnabled(false);
			preference= new Preference(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SHOW_LINE_FEED, "", null); //$NON-NLS-1$
			addCheckBox(tabularComposite, preference, new BooleanDomain(), 0);

			Composite alphaComposite= new Composite(composite, SWT.NONE);
			layout= new GridLayout();
			layout.numColumns= 2;
			layout.marginHeight= 10;
			layout.marginWidth= 0;
			layout.makeColumnsEqualWidth= false;
			alphaComposite.setLayout(layout);
			preference= new Preference(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_WHITESPACE_CHARACTER_ALPHA_VALUE, TextEditorMessages.TextEditorDefaultsPreferencePage_transparencyLevel, null);
			addTextField(alphaComposite, preference, new IntegerDomain(0, 255), 5, 0);

			errorMessageText= new Text(composite, SWT.READ_ONLY | SWT.WRAP);
			errorMessageText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));

			errorMessageText.setBackground(errorMessageText.getDisplay()
						.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND));
			setErrorMessage(null);

			return composite;
		}

		/**
		 * Sets or clears the error message. If not <code>null</code>, the OK button is disabled.
		 * 
		 * @param errorMessage the error message, or <code>null</code> to clear
		 * @since 3.0
		 */
		public void setErrorMessage(String errorMessage) {
			if (errorMessageText != null && !errorMessageText.isDisposed()) {
				errorMessageText.setText(errorMessage == null ? "  " : errorMessage); //$NON-NLS-1$
				boolean hasError= errorMessage != null && (StringConverter.removeWhiteSpaces(errorMessage)).length() > 0;
				errorMessageText.setEnabled(hasError);
				errorMessageText.setVisible(hasError);
				errorMessageText.getParent().update();
				Control button= getButton(IDialogConstants.OK_ID);
				if (button != null) {
					button.setEnabled(errorMessage == null);
				}
			}
		}

		private Button addCheckBox(Composite composite, final Preference preference, final Domain domain, int indentation) {
			final Button checkBox= new Button(composite, SWT.CHECK);
			checkBox.setToolTipText(preference.getDescription());

			GridData gd= new GridData(SWT.CENTER, SWT.CENTER, false, false);
			gd.horizontalIndent= indentation;
			checkBox.setLayoutData(gd);
			checkBox.addSelectionListener(new SelectionAdapter() {
				@Override
				public void widgetSelected(SelectionEvent e) {
					boolean value= checkBox.getSelection();
					IStatus status= domain.validate(Boolean.valueOf(value));
					if (!status.matches(IStatus.ERROR))
						fDialogOverlayStore.setValue(preference.getKey(), value);
				}
			});

			fDialogInitializers.add(fDialogInitializerFactory.create(preference, checkBox));
			return checkBox;
		}

		private Control[] addTextField(Composite composite, final Preference preference, final Domain domain, int textLimit, int indentation) {
			Label labelControl= new Label(composite, SWT.NONE);
			labelControl.setText(preference.getName());
			GridData gd= new GridData(SWT.BEGINNING, SWT.CENTER, false, false);
			gd.horizontalIndent= indentation;
			labelControl.setLayoutData(gd);

			final Text textControl= new Text(composite, SWT.BORDER | SWT.SINGLE);
			gd= new GridData(SWT.BEGINNING, SWT.CENTER, false, false);
			gd.widthHint= convertWidthInCharsToPixels(textLimit + 1);
			textControl.setLayoutData(gd);
			textControl.setTextLimit(textLimit);
			textControl.setToolTipText(preference.getDescription());

			if (domain != null) {
				textControl.addModifyListener(e -> {
					String value= textControl.getText();
					IStatus status= domain.validate(value);
					if (!status.matches(IStatus.ERROR)) {
						fDialogOverlayStore.setValue(preference.getKey(), value);
						setErrorMessage(null);
					} else {
						setErrorMessage(NLSUtility.format(TextEditorMessages.TextEditorDefaultsPreferencePage_showWhitespaceCharactersDialogInvalidInput, value));
					}
				});
			}

			fDialogInitializers.add(fDialogInitializerFactory.create(preference, textControl));

			return new Control[] { labelControl, textControl };
		}

		@Override
		protected void okPressed() {
			super.okPressed();
			fDialogOverlayStore.propagate();
		}
	}

	private final String[][] fAppearanceColorListModel= new String[][] {
		{TextEditorMessages.TextEditorPreferencePage_lineNumberForegroundColor, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_LINE_NUMBER_RULER_COLOR, null},
		{TextEditorMessages.TextEditorPreferencePage_currentLineHighlighColor, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_CURRENT_LINE_COLOR, null},
		{TextEditorMessages.TextEditorPreferencePage_printMarginColor, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_PRINT_MARGIN_COLOR, null},
		{TextEditorMessages.TextEditorPreferencePage_findScopeColor, AbstractTextEditor.PREFERENCE_COLOR_FIND_SCOPE, null},
		{TextEditorMessages.TextEditorPreferencePage_selectionForegroundColor, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SELECTION_FOREGROUND_COLOR, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SELECTION_FOREGROUND_DEFAULT_COLOR},
		{TextEditorMessages.TextEditorPreferencePage_selectionBackgroundColor, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SELECTION_BACKGROUND_COLOR, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SELECTION_BACKGROUND_DEFAULT_COLOR},
		{TextEditorMessages.TextEditorPreferencePage_backgroundColor, AbstractTextEditor.PREFERENCE_COLOR_BACKGROUND, AbstractTextEditor.PREFERENCE_COLOR_BACKGROUND_SYSTEM_DEFAULT},
		{TextEditorMessages.TextEditorPreferencePage_foregroundColor, AbstractTextEditor.PREFERENCE_COLOR_FOREGROUND, AbstractTextEditor.PREFERENCE_COLOR_FOREGROUND_SYSTEM_DEFAULT},
		{TextEditorMessages.HyperlinkColor_label, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_HYPERLINK_COLOR, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_HYPERLINK_COLOR_SYSTEM_DEFAULT},
	};

	private OverlayPreferenceStore fOverlayStore;

	private List fAppearanceColorList;
	private ColorSelector fAppearanceColorEditor;
	private Button fAppearanceColorDefault;

	/**
	 * Tells whether the fields are initialized.
	 */
	private boolean fFieldsInitialized= false;

	private ArrayList<SelectionListener> fMasterSlaveListeners= new ArrayList<>();

	private java.util.List<Initializer> fInitializers= new ArrayList<>();

	private InitializerFactory fInitializerFactory;

	private Map<Domain, Text> fDomains= new HashMap<>();


	public TextEditorDefaultsPreferencePage() {
		setPreferenceStore(EditorsPlugin.getDefault().getPreferenceStore());

		fOverlayStore= createOverlayStore();
		fInitializerFactory= new InitializerFactory(fOverlayStore);
	}

	private OverlayPreferenceStore createOverlayStore() {

		ArrayList<OverlayKey> overlayKeys= new ArrayList<>();

		overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.STRING, AbstractTextEditor.PREFERENCE_COLOR_FIND_SCOPE));

		overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.STRING, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_CURRENT_LINE_COLOR));
		overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_CURRENT_LINE));

		overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.INT, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_TAB_WIDTH));
		overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, AbstractTextEditor.PREFERENCE_WORD_WRAP_ENABLED));
		overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SPACES_FOR_TABS));

		overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.STRING, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_PRINT_MARGIN_COLOR));
		overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.INT, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_PRINT_MARGIN_COLUMN));
		overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_PRINT_MARGIN));
		overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_PRINT_MARGIN_ALLOW_OVERRIDE));

		overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.INT, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_UNDO_HISTORY_SIZE));

		overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.STRING, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_LINE_NUMBER_RULER_COLOR));
		overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_LINE_NUMBER_RULER));

		overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.STRING, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SELECTION_FOREGROUND_COLOR));
		overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SELECTION_FOREGROUND_DEFAULT_COLOR));
		overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.STRING, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SELECTION_BACKGROUND_COLOR));
		overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SELECTION_BACKGROUND_DEFAULT_COLOR));

		overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.STRING, AbstractTextEditor.PREFERENCE_COLOR_FOREGROUND));
		overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, AbstractTextEditor.PREFERENCE_COLOR_FOREGROUND_SYSTEM_DEFAULT));
		overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.STRING, AbstractTextEditor.PREFERENCE_COLOR_BACKGROUND));
		overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, AbstractTextEditor.PREFERENCE_COLOR_BACKGROUND_SYSTEM_DEFAULT));

		overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_HYPERLINKS_ENABLED));
		overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.STRING, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_HYPERLINK_COLOR));
		overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_HYPERLINK_COLOR_SYSTEM_DEFAULT));
		overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.STRING, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_HYPERLINK_KEY_MODIFIER));
		overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.INT, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_HYPERLINK_KEY_MODIFIER_MASK));

		overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SHOW_WHITESPACE_CHARACTERS));
		overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, AbstractDecoratedTextEditorPreferenceConstants.SHOW_RANGE_INDICATOR));
		overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_WARN_IF_INPUT_DERIVED));
		overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SMART_HOME_END));
		overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_TEXT_DRAG_AND_DROP_ENABLED));
		overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SHOW_TEXT_HOVER_AFFORDANCE));
		overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.INT, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_HOVER_ENRICH_MODE));

		overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SHOW_LEADING_SPACES));
		overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SHOW_ENCLOSED_SPACES));
		overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SHOW_TRAILING_SPACES));
		overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SHOW_LEADING_IDEOGRAPHIC_SPACES));
		overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SHOW_ENCLOSED_IDEOGRAPHIC_SPACES));
		overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SHOW_TRAILING_IDEOGRAPHIC_SPACES));
		overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SHOW_LEADING_TABS));
		overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SHOW_ENCLOSED_TABS));
		overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SHOW_TRAILING_TABS));
		overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SHOW_CARRIAGE_RETURN));
		overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.BOOLEAN, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SHOW_LINE_FEED));
		overlayKeys.add(new OverlayPreferenceStore.OverlayKey(OverlayPreferenceStore.INT, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_WHITESPACE_CHARACTER_ALPHA_VALUE));

		OverlayPreferenceStore.OverlayKey[] keys= new OverlayPreferenceStore.OverlayKey[overlayKeys.size()];
		overlayKeys.toArray(keys);
		return new OverlayPreferenceStore(getPreferenceStore(), keys);
	}

	@Override
	public void init(IWorkbench workbench) {
	}

	@Override
	public void createControl(Composite parent) {
		super.createControl(parent);
		PlatformUI.getWorkbench().getHelpSystem().setHelp(getControl(), ITextEditorHelpContextIds.TEXT_EDITOR_PREFERENCE_PAGE);
	}

	private void handleAppearanceColorListSelection() {
		int i= fAppearanceColorList.getSelectionIndex();
		if (i == -1)
			return;

		String key= fAppearanceColorListModel[i][1];
		RGB rgb= PreferenceConverter.getColor(fOverlayStore, key);
		fAppearanceColorEditor.setColorValue(rgb);
		updateAppearanceColorWidgets(fAppearanceColorListModel[i][2]);
	}

	private void updateAppearanceColorWidgets(String systemDefaultKey) {
		if (systemDefaultKey == null) {
			fAppearanceColorDefault.setSelection(false);
			fAppearanceColorDefault.setVisible(false);
			fAppearanceColorEditor.getButton().setEnabled(true);
		} else {
			boolean systemDefault= fOverlayStore.getBoolean(systemDefaultKey);
			fAppearanceColorDefault.setSelection(systemDefault);
			fAppearanceColorDefault.setVisible(true);
			fAppearanceColorEditor.getButton().setEnabled(!systemDefault);
		}
	}

	private Control createAppearancePage(Composite parent) {

		Composite appearanceComposite= new Composite(parent, SWT.NONE);
		GridLayout layout= new GridLayout();
		layout.numColumns= 2;
		layout.marginHeight= 0;
		layout.marginWidth= 0;

		appearanceComposite.setLayout(layout);

		Link fontLink= new Link(appearanceComposite, SWT.NONE);
		fontLink.setText(TextEditorMessages.TextEditorPreferencePage_Font_link);
		fontLink.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				PreferencesUtil.createPreferenceDialogOn(getShell(), "org.eclipse.ui.preferencePages.ColorsAndFonts", null, "selectFont:" + JFaceResources.TEXT_FONT); //$NON-NLS-1$ //$NON-NLS-2$
			}
		});
		GridData gd= new GridData(GridData.HORIZONTAL_ALIGN_FILL);
		gd.horizontalSpan= 2;
		fontLink.setLayoutData(gd);

		addFiller(appearanceComposite, 2);

		String label= TextEditorMessages.TextEditorPreferencePage_undoHistorySize;
		Preference undoHistorySize= new Preference(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_UNDO_HISTORY_SIZE, label, null);
		IntegerDomain undoHistorySizeDomain= new IntegerDomain(0, 99999);
		addTextField(appearanceComposite, undoHistorySize, undoHistorySizeDomain, 15, 0);

		label= TextEditorMessages.TextEditorPreferencePage_displayedTabWidth;
		Preference tabWidth= new Preference(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_TAB_WIDTH, label, null);
		IntegerDomain tabWidthDomain= new IntegerDomain(1, 16);
		addTextField(appearanceComposite, tabWidth, tabWidthDomain, 15, 0);

		if(isWordWrapPreferenceAllowed()){
			label= TextEditorMessages.TextEditorPreferencePage_enableWordWrap;
			Preference enableWordWrap= new Preference(AbstractTextEditor.PREFERENCE_WORD_WRAP_ENABLED, label, null);
			addCheckBox(appearanceComposite, enableWordWrap, new BooleanDomain(), 0);
		}

		label= TextEditorMessages.TextEditorPreferencePage_convertTabsToSpaces;
		Preference spacesForTabs= new Preference(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SPACES_FOR_TABS, label, null);
		addCheckBox(appearanceComposite, spacesForTabs, new BooleanDomain(), 0);


		label= TextEditorMessages.TextEditorPreferencePage_highlightCurrentLine;
		Preference highlightCurrentLine= new Preference(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_CURRENT_LINE, label, null);
		addCheckBox(appearanceComposite, highlightCurrentLine, new BooleanDomain(), 0);

		label= TextEditorMessages.TextEditorPreferencePage_showPrintMargin;
		Preference showPrintMargin= new Preference(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_PRINT_MARGIN, label, null);
		final Button showPrintMarginButton= addCheckBox(appearanceComposite, showPrintMargin, new BooleanDomain(), 0);


		label= TextEditorMessages.TextEditorPreferencePage_printMarginColumn;
		Preference printMarginColumn= new Preference(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_PRINT_MARGIN_COLUMN, label, null);
		final IntegerDomain printMarginDomain= new IntegerDomain(20, 200);
		final Control[] printMarginControls= addTextField(appearanceComposite, printMarginColumn, printMarginDomain, 15, 20);
		createDependency(showPrintMarginButton, showPrintMargin, printMarginControls);
		
		label= TextEditorMessages.TextEditorPreferencePage_printMarginAllowOverride;
		Preference printMarginAllowOverride= new Preference(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_PRINT_MARGIN_ALLOW_OVERRIDE, label, null);
		final Button showPrintMarginAllowOverride= addCheckBox(appearanceComposite, printMarginAllowOverride, new BooleanDomain(), 0);
		createDependency(showPrintMarginButton, showPrintMargin, new Control[] { showPrintMarginAllowOverride });

		showPrintMarginButton.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				updateStatus(printMarginDomain);
			}
		});

		label= TextEditorMessages.TextEditorPreferencePage_showLineNumbers;
		Preference showLineNumbers= new Preference(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_LINE_NUMBER_RULER, label, null);
		addCheckBox(appearanceComposite, showLineNumbers, new BooleanDomain(), 0);

		label= TextEditorMessages.TextEditorDefaultsPreferencePage_range_indicator;
		Preference showMagnet= new Preference(AbstractDecoratedTextEditorPreferenceConstants.SHOW_RANGE_INDICATOR, label, null);
		addCheckBox(appearanceComposite, showMagnet, new BooleanDomain(), 0);

		label= TextEditorMessages.TextEditorDefaultsPreferencePage_showWhitespaceCharacters;
		String linkText= TextEditorMessages.TextEditorDefaultsPreferencePage_showWhitespaceCharactersLinkText;
		Preference showWhitespaceCharacters= new Preference(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SHOW_WHITESPACE_CHARACTERS, label, null);
		addCheckBoxWithLink(appearanceComposite, showWhitespaceCharacters, linkText, new BooleanDomain(), 0, new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				Dialog dialog= new WhitespaceCharacterPainterOptionsDialog(Display.getDefault().getActiveShell(), fOverlayStore);
				dialog.open();
			}
		});

		label= TextEditorMessages.TextEditorPreferencePage_showAffordance;
		Preference showAffordance= new Preference(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SHOW_TEXT_HOVER_AFFORDANCE, label, null);
		addCheckBox(appearanceComposite, showAffordance, new BooleanDomain(), 0);

		label= TextEditorMessages.TextEditorDefaultsPreferencePage_enrichHoverMode;
		Preference hoverReplace= new Preference(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_HOVER_ENRICH_MODE, label, null);
		EnumeratedDomain hoverReplaceDomain= new EnumeratedDomain();
		hoverReplaceDomain.addValue(new EnumValue(-1, TextEditorMessages.TextEditorDefaultsPreferencePage_enrichHover_disabled));
		hoverReplaceDomain.addValue(new EnumValue(1, TextEditorMessages.TextEditorDefaultsPreferencePage_enrichHover_immediately));
		hoverReplaceDomain.addValue(new EnumValue(0, TextEditorMessages.TextEditorDefaultsPreferencePage_enrichHover_afterDelay));
		hoverReplaceDomain.addValue(new EnumValue(2, TextEditorMessages.TextEditorDefaultsPreferencePage_enrichHover_onClick));
		addCombo(appearanceComposite, hoverReplace, hoverReplaceDomain, 0);

		label= TextEditorMessages.TextEditorDefaultsPreferencePage_textDragAndDrop;
		Preference textDragAndDrop= new Preference(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_TEXT_DRAG_AND_DROP_ENABLED, label, null);
		addCheckBox(appearanceComposite, textDragAndDrop, new BooleanDomain(), 0);

		label= TextEditorMessages.TextEditorDefaultsPreferencePage_warn_if_derived;
		Preference warnIfDerived= new Preference(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_WARN_IF_INPUT_DERIVED, label, null);
		addCheckBox(appearanceComposite, warnIfDerived, new BooleanDomain(), 0);

		label= TextEditorMessages.TextEditorDefaultsPreferencePage_smartHomeEnd;
		Preference smartHomeEnd= new Preference(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SMART_HOME_END, label, null);
		addCheckBox(appearanceComposite, smartHomeEnd, new BooleanDomain(), 0);

		addFiller(appearanceComposite, 2);

		Label l= new Label(appearanceComposite, SWT.LEFT);
		l.setText(TextEditorMessages.TextEditorPreferencePage_appearanceOptions);
		gd= new GridData(GridData.HORIZONTAL_ALIGN_FILL);
		gd.horizontalSpan= 2;
		l.setLayoutData(gd);

		Composite editorComposite= new Composite(appearanceComposite, SWT.NONE);
		layout= new GridLayout();
		layout.numColumns= 2;
		layout.marginHeight= 0;
		layout.marginWidth= 0;
		editorComposite.setLayout(layout);
		gd= new GridData(GridData.HORIZONTAL_ALIGN_FILL | GridData.FILL_VERTICAL);
		gd.horizontalSpan= 2;
		editorComposite.setLayoutData(gd);

		fAppearanceColorList= new List(editorComposite, SWT.SINGLE | SWT.V_SCROLL | SWT.BORDER);
		gd= new GridData(GridData.VERTICAL_ALIGN_BEGINNING | GridData.FILL_BOTH);
		gd.heightHint= fAppearanceColorList.getItemHeight() * 8;
		fAppearanceColorList.setLayoutData(gd);

		Composite stylesComposite= new Composite(editorComposite, SWT.NONE);
		layout= new GridLayout();
		layout.marginHeight= 0;
		layout.marginWidth= 0;
		layout.numColumns= 2;
		stylesComposite.setLayout(layout);
		stylesComposite.setLayoutData(new GridData(GridData.FILL_BOTH));

		l= new Label(stylesComposite, SWT.LEFT);
		l.setText(TextEditorMessages.TextEditorPreferencePage_color);
		gd= new GridData();
		gd.horizontalAlignment= GridData.BEGINNING;
		l.setLayoutData(gd);

		fAppearanceColorEditor= new ColorSelector(stylesComposite);
		Button foregroundColorButton= fAppearanceColorEditor.getButton();
		gd= new GridData(GridData.FILL_HORIZONTAL);
		gd.horizontalAlignment= GridData.BEGINNING;
		foregroundColorButton.setLayoutData(gd);

		SelectionListener colorDefaultSelectionListener= new SelectionListener() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				boolean systemDefault= fAppearanceColorDefault.getSelection();
				fAppearanceColorEditor.getButton().setEnabled(!systemDefault);

				int i= fAppearanceColorList.getSelectionIndex();
				if (i == -1)
					return;

				String key= fAppearanceColorListModel[i][2];
				if (key != null)
					fOverlayStore.setValue(key, systemDefault);
			}
			@Override
			public void widgetDefaultSelected(SelectionEvent e) {}
		};

		fAppearanceColorDefault= new Button(stylesComposite, SWT.CHECK);
		fAppearanceColorDefault.setText(TextEditorMessages.TextEditorPreferencePage_systemDefault);
		gd= new GridData(GridData.FILL_HORIZONTAL);
		gd.horizontalAlignment= GridData.BEGINNING;
		gd.horizontalSpan= 2;
		fAppearanceColorDefault.setLayoutData(gd);
		fAppearanceColorDefault.setVisible(false);
		fAppearanceColorDefault.addSelectionListener(colorDefaultSelectionListener);

		fAppearanceColorList.addSelectionListener(new SelectionListener() {
			@Override
			public void widgetDefaultSelected(SelectionEvent e) {
				// do nothing
			}
			@Override
			public void widgetSelected(SelectionEvent e) {
				handleAppearanceColorListSelection();
			}
		});
		foregroundColorButton.addSelectionListener(new SelectionListener() {
			@Override
			public void widgetDefaultSelected(SelectionEvent e) {
				// do nothing
			}
			@Override
			public void widgetSelected(SelectionEvent e) {
				int i= fAppearanceColorList.getSelectionIndex();
				if (i == -1)
					return;

				String key= fAppearanceColorListModel[i][1];
				PreferenceConverter.setValue(fOverlayStore, key, fAppearanceColorEditor.getColorValue());
			}
		});

		Link link= new Link(appearanceComposite, SWT.NONE);
		link.setText(TextEditorMessages.TextEditorPreferencePage_colorsAndFonts_link);
		link.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				PreferencesUtil.createPreferenceDialogOn(getShell(), "org.eclipse.ui.preferencePages.ColorsAndFonts", null, "selectCategory:org.eclipse.ui.workbenchMisc"); //$NON-NLS-1$ //$NON-NLS-2$
			}
		});

		GridData gridData= new GridData(SWT.FILL, SWT.BEGINNING, true, false);
		gridData.widthHint= 150; // only expand further if anyone else requires it
		gridData.horizontalSpan= 2;
		link.setLayoutData(gridData);

		addFiller(appearanceComposite, 2);

		appearanceComposite.layout();

		return appearanceComposite;
	}

	private boolean isWordWrapPreferenceAllowed() {
		return Boolean.getBoolean("eclipse.show.wrapByDefaultPreference"); //$NON-NLS-1$
	}

	@Override
	protected Control createContents(Composite parent) {

		initializeDefaultColors();

		fOverlayStore.load();
		fOverlayStore.start();

		Control control= createAppearancePage(parent);

		initialize();
		Dialog.applyDialogFont(control);
		return control;
	}

	private void initialize() {

		initializeFields();

		for (int i= 0; i < fAppearanceColorListModel.length; i++)
			fAppearanceColorList.add(fAppearanceColorListModel[i][0]);
		fAppearanceColorList.getDisplay().asyncExec(() -> {
			if (fAppearanceColorList != null && !fAppearanceColorList.isDisposed()) {
				fAppearanceColorList.select(0);
				handleAppearanceColorListSelection();
			}
		});
	}

	private void initializeFields() {
		for (Iterator<Initializer> it= fInitializers.iterator(); it.hasNext();) {
			Initializer initializer= it.next();
			initializer.initialize();
		}

		fFieldsInitialized= true;
		updateStatus(new StatusInfo());

		// Update slaves
		Iterator<SelectionListener> iter= fMasterSlaveListeners.iterator();
		while (iter.hasNext()) {
			SelectionListener listener= iter.next();
			listener.widgetSelected(null);
		}

	}

	private void initializeDefaultColors() {
		if (!getPreferenceStore().contains(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SELECTION_BACKGROUND_COLOR)) {
			RGB rgb= getControl().getDisplay().getSystemColor(SWT.COLOR_LIST_SELECTION).getRGB();
			PreferenceConverter.setDefault(fOverlayStore, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SELECTION_BACKGROUND_COLOR, rgb);
			PreferenceConverter.setDefault(getPreferenceStore(), AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SELECTION_BACKGROUND_COLOR, rgb);
		}
		if (!getPreferenceStore().contains(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SELECTION_FOREGROUND_COLOR)) {
			RGB rgb= getControl().getDisplay().getSystemColor(SWT.COLOR_LIST_SELECTION_TEXT).getRGB();
			PreferenceConverter.setDefault(fOverlayStore, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SELECTION_FOREGROUND_COLOR, rgb);
			PreferenceConverter.setDefault(getPreferenceStore(), AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SELECTION_FOREGROUND_COLOR, rgb);
		}
		if (!getPreferenceStore().contains(AbstractTextEditor.PREFERENCE_COLOR_FOREGROUND)) {
			RGB rgb= getControl().getDisplay().getSystemColor(SWT.COLOR_LIST_BACKGROUND).getRGB();
			PreferenceConverter.setDefault(fOverlayStore, AbstractTextEditor.PREFERENCE_COLOR_FOREGROUND, rgb);
			PreferenceConverter.setDefault(getPreferenceStore(), AbstractTextEditor.PREFERENCE_COLOR_FOREGROUND, rgb);
		}
		if (!getPreferenceStore().contains(AbstractTextEditor.PREFERENCE_COLOR_BACKGROUND)) {
			RGB rgb= getControl().getDisplay().getSystemColor(SWT.COLOR_LIST_FOREGROUND).getRGB();
			PreferenceConverter.setDefault(fOverlayStore, AbstractTextEditor.PREFERENCE_COLOR_BACKGROUND, rgb);
			PreferenceConverter.setDefault(getPreferenceStore(), AbstractTextEditor.PREFERENCE_COLOR_BACKGROUND, rgb);
		}
	}

	@Override
	public boolean performOk() {
		fOverlayStore.propagate();
		return true;
	}

	@Override
	protected void performDefaults() {

		fOverlayStore.loadDefaults();

		initializeFields();

		handleAppearanceColorListSelection();

		super.performDefaults();
	}

	@Override
	public void dispose() {

		if (fOverlayStore != null) {
			fOverlayStore.stop();
			fOverlayStore= null;
		}

		super.dispose();
	}

	private void addFiller(Composite composite, int horizontalSpan) {
		PixelConverter pixelConverter= new PixelConverter(composite);
		Label filler= new Label(composite, SWT.LEFT );
		GridData gd= new GridData(GridData.HORIZONTAL_ALIGN_FILL);
		gd.horizontalSpan= horizontalSpan;
		gd.heightHint= pixelConverter.convertHeightInCharsToPixels(1) / 2;
		filler.setLayoutData(gd);
	}

	private void checkboxControlChanged(final Preference preference, final Domain domain, final Button checkBox) {
		boolean value= checkBox.getSelection();
		IStatus status= domain.validate(Boolean.valueOf(value));
		if (!status.matches(IStatus.ERROR))
			fOverlayStore.setValue(preference.getKey(), value);
		updateStatus(status);
	}

	Button addCheckBox(Composite composite, final Preference preference, final Domain domain, int indentation) {
		final Button checkBox= new Button(composite, SWT.CHECK);
		checkBox.setText(preference.getName());
		checkBox.setToolTipText(preference.getDescription());

		GridData gd= new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING);
		gd.horizontalIndent= indentation;
		gd.horizontalSpan= 2;
		checkBox.setLayoutData(gd);
		checkBox.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				checkboxControlChanged(preference, domain, checkBox);
			}
		});

		fInitializers.add(fInitializerFactory.create(preference, checkBox));

		return checkBox;
	}

	private Button addCheckBoxWithLink(Composite parent, final Preference preference, String linkText, final Domain domain, int indentation, final SelectionListener listener) {
		GridData gd= new GridData(GridData.FILL, GridData.FILL, true, false);
		gd.horizontalSpan= 3;
		gd.horizontalIndent= indentation;

		Composite composite= new Composite(parent, SWT.NONE);
		GridLayout layout= new GridLayout();
		layout.marginHeight= 0;
		layout.marginWidth= 0;
		layout.horizontalSpacing= 0;
		layout.numColumns= 2;
		composite.setLayout(layout);
		composite.setLayoutData(gd);

		final Button checkBox= new Button(composite, SWT.CHECK);
		checkBox.setFont(JFaceResources.getDialogFont());
		checkBox.setText(preference.getName());
		gd= new GridData(GridData.FILL, GridData.CENTER, false, false);
		checkBox.setLayoutData(gd);
		checkBox.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				checkboxControlChanged(preference, domain, checkBox);
			}
		});

		gd= new GridData(GridData.FILL, GridData.CENTER, false, false);
		Link link= new Link(composite, SWT.NONE);
		link.setText(linkText);
		link.setLayoutData(gd);
		if (listener != null) {
			link.addSelectionListener(new SelectionAdapter() {
				@Override
				public void widgetSelected(SelectionEvent e) {
					listener.widgetSelected(e);
				}
			});
		}

		fInitializers.add(fInitializerFactory.create(preference, checkBox));

		return checkBox;
	}

	Control[] addCombo(Composite composite, final Preference preference, final EnumeratedDomain domain, int indentation) {
		Label labelControl= new Label(composite, SWT.NONE);
		labelControl.setText(preference.getName());
		GridData gd= new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING);
		gd.horizontalIndent= indentation;
		labelControl.setLayoutData(gd);

		final Combo combo= new Combo(composite, SWT.DROP_DOWN | SWT.READ_ONLY);
		gd= new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING);
		combo.setLayoutData(gd);
		combo.setToolTipText(preference.getDescription());
		for (Iterator<EnumValue> it= domain.fItems.iterator(); it.hasNext();) {
			EnumValue value= it.next();
			combo.add(value.getLabel());
		}

		combo.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				int index= combo.getSelectionIndex();
				EnumValue value= domain.getValueByIndex(index);
				IStatus status= domain.validate(value);
				if (!status.matches(IStatus.ERROR))
					fOverlayStore.setValue(preference.getKey(), value.getIntValue());
				updateStatus(status);
			}
		});

		fInitializers.add(fInitializerFactory.create(preference, combo, domain));

		return new Control[] {labelControl, combo};
	}

	/**
	 * Adds a spinner for the given preference and domain. Assumes that the
	 * <code>EnumeratedDomain</code> contains only numeric values in a
	 * continuous range, no custom entries (use <code>addCombo</code> in that
	 * case).
	 *
	 * @param composite the parent composite
	 * @param preference the preference
	 * @param domain its domain
	 * @param indentation the indentation
	 * @return the created controls, a <code>Label</code> and a
	 *         <code>Spinner</code> control
	 */
	Control[] addSpinner(Composite composite, final Preference preference, final EnumeratedDomain domain, int indentation) {
		Label labelControl= new Label(composite, SWT.NONE);
		labelControl.setText(preference.getName());
		GridData gd= new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING);
		gd.horizontalIndent= indentation;
		labelControl.setLayoutData(gd);

		final Spinner spinner= new Spinner(composite, SWT.READ_ONLY | SWT.BORDER);
		gd= new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING);
		spinner.setLayoutData(gd);
		spinner.setToolTipText(preference.getDescription());
		spinner.setMinimum(domain.getMinimumValue().getIntValue());
		spinner.setMaximum(domain.getMaximumValue().getIntValue());
		spinner.setIncrement(1);
		spinner.setPageIncrement(4);

		spinner.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				int index= spinner.getSelection();
				EnumValue value= domain.getValueByInteger(index);
				IStatus status= domain.validate(value);
				if (!status.matches(IStatus.ERROR))
					fOverlayStore.setValue(preference.getKey(), value.getIntValue());
				updateStatus(status);
			}
		});

		fInitializers.add(fInitializerFactory.create(preference, spinner, domain));

		return new Control[] {labelControl, spinner};
	}

	private Control[] addTextField(Composite composite, final Preference preference, final Domain domain, int textLimit, int indentation) {
		Label labelControl= new Label(composite, SWT.NONE);
		labelControl.setText(preference.getName());
		GridData gd= new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING);
		gd.horizontalIndent= indentation;
		labelControl.setLayoutData(gd);

		final Text textControl= new Text(composite, SWT.BORDER | SWT.SINGLE);
		gd= new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING);
		gd.widthHint= convertWidthInCharsToPixels(textLimit + 1);
		textControl.setLayoutData(gd);
		textControl.setTextLimit(textLimit);
		textControl.setToolTipText(preference.getDescription());

		if (domain != null) {
			textControl.addModifyListener(e -> {
				String value= textControl.getText();
				IStatus status= domain.validate(value);
				if (!status.matches(IStatus.ERROR))
					fOverlayStore.setValue(preference.getKey(), value);
				updateStatus(domain);
			});
		}

		fInitializers.add(fInitializerFactory.create(preference, textControl));

		fDomains.put(domain, textControl);

		return new Control[] {labelControl, textControl};
	}

	private void createDependency(final Button master, Preference preference, final Control[] slaves) {
		indent(slaves[0]);

		boolean masterState= fOverlayStore.getBoolean(preference.getKey());
		for (int i= 0; i < slaves.length; i++) {
			slaves[i].setEnabled(masterState);
		}

		SelectionListener listener= new SelectionListener() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				boolean state= master.getSelection();
				for (int i= 0; i < slaves.length; i++) {
					slaves[i].setEnabled(state);
				}
			}

			@Override
			public void widgetDefaultSelected(SelectionEvent e) {}
		};
		master.addSelectionListener(listener);
		fMasterSlaveListeners.add(listener);
	}

	private static void indent(Control control) {
		GridData gridData= new GridData();
		gridData.horizontalIndent= 20;
		control.setLayoutData(gridData);
	}

	void updateStatus(IStatus status) {
		if (!fFieldsInitialized)
			return;
		setValid(!status.matches(IStatus.ERROR));
		applyToStatusLine(this, status);
	}

	void updateStatus(Domain checkedDomain) {
		if (!fFieldsInitialized)
			return;

		if (updateStatusOnError(checkedDomain))
			return;

		Iterator<Domain> iter= fDomains.keySet().iterator();
		while (iter.hasNext()) {
			Domain domain= iter.next();
			if (domain.equals(checkedDomain))
				continue;
			if (updateStatusOnError(domain))
				return;
		}
		updateStatus(new StatusInfo());
	}

	private boolean updateStatusOnError(Domain domain) {
		Text textWidget= fDomains.get(domain);
		if (textWidget.isEnabled()) {
			IStatus status= domain.validate(textWidget.getText());
			if (status.matches(IStatus.ERROR)) {
				updateStatus(status);
				return true;
			}
		}
		return false;
	}

	/**
	 * Applies the status to the status line of a dialog page.
	 *
	 * @param page the dialog page
	 * @param status the status
	 */
	public void applyToStatusLine(DialogPage page, IStatus status) {
		String message= status.getMessage();
		switch (status.getSeverity()) {
			case IStatus.OK:
				page.setMessage(message, IMessageProvider.NONE);
				page.setErrorMessage(null);
				break;
			case IStatus.WARNING:
				page.setMessage(message, IMessageProvider.WARNING);
				page.setErrorMessage(null);
				break;
			case IStatus.INFO:
				page.setMessage(message, IMessageProvider.INFORMATION);
				page.setErrorMessage(null);
				break;
			default:
				if (message.length() == 0) {
					message= null;
				}
				page.setMessage(null);
				page.setErrorMessage(message);
				break;
		}
	}

}
